I have a script that gets all the values of a spreadsheet and uses those values to create entries in a calendar.
However, it takes too long to run and times out.
It didn't used to take that long because there wasn't enough entries, but now there's enough entries that it cant finish before it times out, so I need to increase the speed.
I believe the reason it runs so slow is because there's a loop that runs through every row of the spreadsheet and at the end of every loop it writes a calendar event. I think it's this that adds to the execution time because it has to reconnect to the calendar over and over again. I think this massively adds to the execution time.
I believe I can reduce this with caching but I have not even the slightest clue how that works.
Here is my code:
/**
* Export events from spreadsheet to calendar
*/
function exportEvents() {
var sheet = SpreadsheetApp.getActiveSheet();
var headerRows = 1; // Number of rows of header info (to skip)
var range = sheet.getDataRange();
var data = range.getValues();
var calId = "trhcom7eiadkcn39mg9d0hfceg#group.calendar.google.com";
var cal = CalendarApp.getCalendarById(calId);
for (i in data) {
if (i < headerRows) continue; // Skip header row(s)
var row = data[i];
var date = new Date(row[9]);
if (!(isValidDate(date))) continue; // Skip rows without a date
var title = row[19]+" - "+row[3]+" - "+row[1]+" - "+row[2];
var id = row[31];
// Check if event already exists, delete it if it does
try {
var event = cal.getEventSeriesById(id);
event.deleteEventSeries();
row[31] = ''; // Remove event ID
}
catch (e) {
// do nothing - we just want to avoid the exception when event doesn't exist
}
var newEvent = cal.createAllDayEvent(title, date).addEmailReminder(4320).addEmailReminder(60).addSmsReminder(4320).addSmsReminder(60).getId();
row[31] = newEvent; // Update the data array with event ID
}
i=0;
for (i in data) {
if (i < headerRows) continue; // Skip header row(s)
var row = data[i];
var date = new Date(row[13]);
if (!(isValidDate(date))) continue; // Skip rows without a date
var title = "Expected Pay Date: "+row[19];
var id = row[32];
// Check if event already exists, delete it if it does
try {
var event = cal.getEventSeriesById(id);
event.deleteEventSeries();
row[32] = ''; // Remove event ID
}
catch (e) {
// do nothing - we just want to avoid the exception when event doesn't exist
}
date.setDate(date.getDate() + 12);
var newEvent = cal.createAllDayEvent(title, date).addEmailReminder(4320).addEmailReminder(60).addSmsReminder(4320).addSmsReminder(60).getId();
row[32] = newEvent; // Update the data array with event ID
}
// Record all event IDs to spreadsheet
range.setValues(data);
};
I'm trying to use the information from this page but I don't even know where to begin.
Is the cache stored locally or on the server? How do I access it? What is a key and where do I find it? What url do I use? How will this end up increasing my speed?
I feel like this is simple but I just don't grasp the concept.
Update: After doing some research I'm not sure a cache can help me since it's not getting data that's taking a long time but rather creating it.
Maybe instead I should be trying to figure out a way to simply write all the events to the calendar at once at the end of the loop but I wouldn't know how to do that either.
Your question contains a lot of question but you already answered a couple of them yourself... ;-) it is indeed not a matter of reading sheet data (which you already do the right way using getDataRange().getValues() ) but a problem with the event creation that takes a long time...
Unfortunately there is no way to speed that up, the only thing we can do is proceed by reduced size batch and let the script run automatically every 10 minutes or so until all the events are created.
Nothing really complicated, here is a script that shows the process :
function createEvents() {
// check if the script runs for the first time or not,
// if so, create the trigger and PropertiesService.getScriptProperties() the script will use
// a start index to know were from it has to continue
if(PropertiesService.getScriptProperties().getKeys().length==0){
PropertiesService.getScriptProperties().setProperties({'startRow':0 });
ScriptApp.newTrigger('createEvents').timeBased().everyMinutes(10).create();
}
// initialize all variables when we start
var startRow = Number(PropertiesService.getScriptProperties().getProperty('startRow'));
var sheet = SpreadsheetApp.getActiveSheet();
var headerRows = 1; // Number of rows of header info (to skip)
var range = sheet.getDataRange();
var data = range.getValues();
var calId = "trhcom7eiadkcn39mg9d0hfceg#group.calendar.google.com";
var cal = CalendarApp.getCalendarById(calId);
var counter = 0
for (var i=tstartRow ; i < data.length ; i++) {
counter++ ;
if(counter == 30){ break }
if (i < headerRows) continue; // Skip header row(s)
var row = data[i];
...
... continue your own code
}
// update the spreadsheet
// if i== data.length then kill the trigger and eventually send yourself a message
// to tell you that the script has finished successfully .
// killing the current trigger goes like this :
var trigger = ScriptApp.getProjectTriggers()[0];
ScriptApp.deleteTrigger(trigger);
Good luck.
Related
I have a google sheet with a list of job names in column F. Each job name can appear multiple times in the list and I need to count or add a unique ID into another column in the same row for every new Jobname entered in column F.
The caveat is, the entries in column F can be "penciled" meaning a value will be entered before it is added to the dropdown list that validates column F (a named range called "JobList"). Those entries should not have a unique ID added.
I've seen a few different solutions online and wondering what could be the best option for my requirements to proceed with. Linked below
Automatically generate a unique sequential ID in Google Sheets
https://yagisanatode.com/2019/02/09/google-apps-script-create-custom-unique-ids-in-google-sheets/
Apologies if we are not allowed to link other sites, I will remove immediately if so.
Currently, I'm thinking of building this solution
Function1 to return number of times jobname appears in Col F
Function2 to add +1 to Function1 (unless its 1) and append to row
onEdit(e) > update this function to call Function2 when Col F is edited and send parameters of current row and value (jobname)
Seems like it could work - will come back with my progress
---- UPDATE ---
I have made a function that seems to work, it does take about 8 seconds to run, however, and seems like it could be prone to issues on many edits, but any onEdit function I think would behave in a similar way.
function onEdit(e) {
var range = e.range;
var ss = e.source;
var sheet = ss.getActiveSheet().getName();
var row = range.getRow();
var col = range.getColumn();
var jobname = e.value;
var count = 0;
var job = jobname;
var sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Calendar v2");
var joblist = sh.getRange("F:F").getValues();
if(sheet=="Calendar v2" && col==6){
//range.setNote(sheet+' + '+row+' + '+col+' + '+jobname+ 'Last modified: ' + new Date());
// count = countJobname();
for (var i=0;i<joblist.length;i++){
if(joblist[i]==job){
count = count + 1;
Logger.log(count);
} SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheet).getRange(row,15).setValue("TRIGGER "+count);
}
}
}
I did build out working separate functions but for some reason, they were not working when called in onEdit, I rebuilt the onEdit function line by line and it eventually worked, will rework the functions back in but still open to suggestions if anyone has a better method. Still a rookie at this stuff :)
Another UPDATE
the last setValue line was running inside the for loop, badly formatted code introduces the dumbest problems. it runs almost instantly now!
Here is the revised code, it's a pretty simple solution and hits the core functionality and I will be building it out further to provide unique ID codes based off 2 columnns (Jobname + Category).
I haven't added the check for "pencil" entries, currently it checks all values in column F.
To implement that check instead we would add the commented code instead in the countJobname function find the Named Range instead
function onEdit(e) {
var range = e.range;
var ss = e.source;
var sheet = ss.getActiveSheet().getName();
var row = range.getRow();
var col = range.getColumn();
var jobname = e.value;
var count = 0;
if(sheet=="Calendar v2" && col==6){
count = countJobname(jobname);
SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheet).getRange(row,15).setValue("TRIGGER "+count);
}
}
function countJobname(jobname){
var job = jobname;
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Calendar v2");
var joblist = sheet.getRange("F:F").getValues(); //.getRangeByName("JobList").getValues();
var count = 0;
for (var i=0;i<joblist.length;i++){
if(joblist[i]==job){
count = count + 1;
Logger.log(count);
}
}return count
}
I am trying to make a dynamic dropdown list and Section Navigation in Google Form. However, my script can auto delete the choice when the quota has been met, the choice can’t navigate to the related page for other selections.
I am planning a health check event for my hospital. It needs to reserve by timeslot and date due to the crow control policy. The links below are my Google Spreadsheet for the form and my daft Google Form of the function.
https://forms.gle/ZV9Djni8hyQGdAd86
https://docs.google.com/spreadsheets/d/1F1dpGCTSlpEOUMh5txsZouhx784JmJvh66IsiGfDTtg/edit?usp=sharing
Reference:
How to set the go to sections on a Google Forms question using app script
https://www.pbainbridge.co.uk/2019/04/dynamically-remove-google-form-options.html
function appointmentSlots() {
var form = FormApp.openById("1VqFBKBD_-iKYk_3Ze40j2tvRIi093-alaoCDsXpFi8k");
var ss = SpreadsheetApp.getActiveSpreadsheet();
var date1timelist = form.getItemById("2101588132").asListItem();
var optionsSheet = ss.getSheetByName('Date Options');
var dateoptions = optionsSheet.getRange('A2:A3').getValues();
var dateleft = optionsSheet.getRange('C2:C3').getValues();
var day1sheet = ss.getSheetByName('9/3');
var day1timeoptions = day1sheet.getRange('A2:A4').getValues();
var day1left = day1sheet.getRange('C2:C4').getValues();
var formFieldsArray = [
["9/3", 2061926149],
["10/3", 1632977105]
];
for(var h = 2; h < formFieldsArray.length; h++) {
var datelist = form.getItemById(formFieldsArray[h][2]).asListItem();
var avaibledateoptions = [];
var sectionday1timeslots = form.getItemById("2101588132").asPageBreakItem();
var sectionday2timeslots = form.getItemById("1630116063").asPageBreakItem();
var datechoice = datelist.getChoices();
var optionsDataLength = dateoptions.length;
for (var i=0; i<optionsDataLength; i++) {
var choice = dateoptions[i][0];
var left = dateleft[i][0];
if ((choice != '') && (left > 0) == formFieldsArray[h][2]) {
if (formFieldsArray[h]= "9/3") {
datechoice.push(datelist.createChoice(avaibledateoptions,sectionday1timeslots));
}
else {
datechoice.push(datelist.createChoice(avaibledateoptions,sectionday2timeslots));
datelist.setChoices(avaibledateoptions);
}
}
}
}
var day1avaibledateoptions = [];
var optionsday1Length = day1timeoptions.length;
for (var i=0; i<optionsday1Length; i++) {
var day1timechoice = day1timeoptions[i][0];
var day1timeleft = day1left[i][0];
if ((day1timechoice != '') && (day1timeleft > 0)) {
day1avaibledateoptions.push(day1timechoice);
}
}
date1timelist.setChoiceValues(day1avaibledateoptions)
}
//etc for day2 timeslots choice and day3 timeslots
}
}
}
In order to modify your form depending on the changing cell values in your Spreadsheet (caused by new form submissions) you will need to set up an installable onChange trigger that will basically run your function when a change on your Spreadsheet is done (like one coming from a form submission). To create such a trigger, please access your trigger pannel and then click on Create trigger and select as the event type onChange assigning it to the function you will be using to create/delete the form items.
Once a user submits a new form and you do certain calculations on your Spreadsheet to determine how many slots are free for that time slot, you can take the value of the cell that tells you how many free appointments are free for that time and if that number is 0 you can proceed to delete that question element using the method deleteItem().
If you eventually end up resetting the form (because your time slot is free again or someone cancels the meeting), you can undo this by creating back the element.
The following piece of code is a basic example on how to delete and create form items based on the changes of a Spreadsheet cell. It has self explanatory comments:
function onChange() {
// Get the different sheets where you have all your left places in your time slots
var sheet = SpreadsheetApp.getActive().getSheetByName('A');
// Get your form
var form = FormApp.openById('FORMID');
// Here you would get each element that might depend on whether there are any
// appointments left or not
var element = form.getItems()[2];
// Get the cell value that tells you if the time slot is already full (full=0)
var value = sheet.getRange('C2').getValue();
// If the value is 0 it means that this time slot is all completed and nobody
// should be able to select it again
if (value == 0) {
// delete this item
form.deleteItem(element);
// if it is not full yet, it might be because your reset the time slot and therefore
// the element does not exist any more
} else {
// if the element exists dont do anything but if it doesnt and there are available
// apointments create it again
if (!element) {
form.addMultipleChoiceItem().setTitle('B').setChoiceValues(['Cats', 'Dogs']);
}
}
}
If you want to remove a choice option rather than an Item, you can look for the item, get all the choices as an array and then remove the choice you don't want any more from this array. Finally, you can update the item with your updated options with setChoices(). Here is a code example on how to achieve this:
function myFunction() {
// This is an example where I only have a single multiple choice item
var choiceItem = FormApp.getActiveForm().getItems(FormApp.ItemType.MULTIPLE_CHOICE)[0].asMultipleChoiceItem();
// Get current choices array
var choices = choiceItem.getChoices();
// Get choice you want to delete, this would be your times or dates obtained from
// the cell values
var choiceToBeRemoved = "A";
// remove choice from array
choices = choices.filter(function(choice){return choice.getValue() !== choiceToBeRemoved});
// Set updated choices
choiceItem.setChoices(choices);
}
References
setChoices
Javascript filter()
I have a sheet with rows I'd like to move to another sheet based on a cell value. I tried following this post's solution (refer below), but I'm having trouble editing the script towards what I want it to do.
I'm doing Check-Ins for an event. I would like to be able to change the value in Column F, populate Column G with the time the status changed, and for the row data to migrate to the Attendee Arrived sheet.
I believe the script already does this, but one has to run it manually. It also takes care of deleting the row data in A (Event) after migrating it to B (Attendee Arrived).
My question is could someone please help me set it up in order for script to run continuously (on edit), and also accomplish all of the above if I missed something?
I don't believe the script will respect the drop down format as it runs so I'm willing to manually type in something. It'd be cool if it could stay that way though - makes it easier for one.
Here's the sheet I'm testing on.
https://docs.google.com/spreadsheets/d/1HrFnV2gFKj1vkw_UpJN4tstHVPK6Y8XhHCIyna9TLJg/edit#gid=1517587380
Here's the solution I tried following. All credit to Jason P and Ritz for this.
Google App Script - Google Spreadsheets Move Row based on cell value efficiently
Thank you D:
function CheckIn() {
// How Many Columns over to copy
var columsCopyCount = 7; // A=1 B=2 C=3 ....
// What Column to Monitor
var columnsToMonitor = 6; // A=1 B=2 C=3 ....
//TARGET SPREAD SHEETS
var target1 = "Attendee Arrived";
//Target Value
var cellvalue = "Attendee Arrived";
//SOURCE SPREAD SHEET
var ss = SpreadsheetApp.openById('1HrFnV2gFKj1vkw_UpJN4tstHVPK6Y8XhHCIyna9TLJg');
var sourceSpreadSheetSheetID = ss.getSheetByName("Event");
var sourceSpreadSheetSheetID1 = ss.getSheetByName(target1);
var data = sourceSpreadSheetSheetID.getRange(2, 1, sourceSpreadSheetSheetID.getLastRow() - 1, sourceSpreadSheetSheetID.getLastColumn()).getValues();
var attendee = [];
for (var i = 0; i < data.length; i++) {
var rValue = data[i][6];
if (rValue == cellvalue) {
attendee.push(data[i]);
} else { //Fail Safe
attendee.push(data[i]);
}
}
if(attendee.length > 0){
sourceSpreadSheetSheetID1.getRange(sourceSpreadSheetSheetID1.getLastRow() + 1,
1, attendee.length, attendee[0].length).setValues(attendee);
}
//Will delete the rows of importdata once the data is copided to other
sheets
sourceSpreadSheetSheetID.deleteRows(2,
sourceSpreadSheetSheetID.getLastRow() - 1);
}
Try this:
I imagine that you already know that you'll need an installable onEdit Trigger and that you can't test a function of this nature by running it without the event object.
function checkIn(e) {
var sh=e.range.getSheet();
if(sh.getName()!="Event") return;
if(e.range.columnStart==6) {
if(e.value=="Attendee Arrived"){
e.range.offset(0,1).setValue(Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "M/d/yyyy HH:mm:ss"));
var row=sh.getRange(e.range.rowStart,1,1,sh.getLastColumn()).getValues()[0];
e.source.getSheetByName("Attendee Arrived").appendRow(row);
sh.deleteRow(e.range.rowStart);
}
}
}
Im trying to create a simple script that firstly checks (all cells in row 3 starting from column 3) for whether they contain a name different from the available sheets and if so create a new one. If not go to the next cell down. Preferably until the row is empty but I didnt get that far. Currently I have
var row = 3; //Global Variable
function Main() { // Main Function
Activate();
GetNames();
}
function Activate() { // Initialize
var get = SpreadsheetApp.getActiveSpreadsheet();
var import = get.getSheetByName("Import");
}
function GetNames() { // Get Names and check for existing Sheets
var ss = SpreadsheetApp.getActiveSpreadsheet();
var import = ss.getSheetByName("Import");
var sheet = ss.getSheets()[0];
for (i = 0; i < 1000; i++) { // Loop which I think is broken
var names = import.getRange(row,3).getValue();
if (import.getSheetByName(names) == null) {
import.insertSheet(names);
import.activate();
}
row + 1;
}
}
And here is the Data
It succeeds to add the first sheet but fails to continue in the loop I think.
As you will probably see I'm very new to this and any help would be appreciated.
You probably wanted to increase row by 1 here:
row + 1;
But you're not. row + 1 is just an expression with a value (4, in your example, because row remains 3 throughout). What you would need is the statement
row = row + 1;
But if this is all that you're using the global variable row for, you don't need it at all. Just use the loop variable i that's already counting from 0 to 1000. You probably want something like import.getRange(i+3,3).
Thanks to everyone for their help in advance.
As a general novice to scripts and Installable Triggers specifically, I have searched all over for the technique of how to do this and haven't found what I need. I will try to break down what I am looking for as best I can.
1.) I am using a form service that is not through Google and I need this email script to trigger when a form is submitted via this service.
Currently I am using a Simple Trigger that runs every 5 minutes which is super annoying because it slows down the whole project and is obviously not very efficient.
2.) I don't know where the 'Trigger' function needs to be placed within the script so I will post the simple script that I am using and hopefully you guys can direct me to where it needs to be placed (and if it even matters?).
Finally, I have already visited all of the links both through Google and Formstack on this issue and have been unable to piece together the result I am looking for. Again, I am a novice at this trigger part so a walk-through is very much appreciated! :-)
Thanks again!
Edit: script
function Email1() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.setActiveSheet(ss.getSheetByName("Sheet3"));
var startRow = 2; // First row of data to process
var numRows = 18; // Number of rows to process
// Fetch the range of cells A2:B19
var dataRange = (ss.getSheetByName('Sheet3')).getRange(startRow, 1, numRows, 19)
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var EMAIL_SENT = "EMAIL_SENT";
var row = data[i];
var emailAddress = row[0]; // First column
if(row[0]!="")
var message = row[1]; // Second column
var emailSent = row[2]; // Third column
var clientEmail = row[13]; // Thirteenth column
if (emailSent != EMAIL_SENT) { // Prevents sending duplicates
var subject = "APPOINTMENT SCHEDULED";
MailApp.sendEmail(emailAddress, subject, message, {noReply:true, cc:clientEmail, bcc:
'xxxxx.myemail.com'});
(ss.getSheetByName('Sheet3')).getRange(startRow + i, 3).setValue(EMAIL_SENT);
// Make sure the cell is updated right away in case the script is interrupted
SpreadsheetApp.flush();
}
}
}