Adding multiple contacts exceeds time - google-apps-script

I'm trying to insert/update multiple contacts (1000+) to google like that:
(in cycle:)
if (contact == null) {
contact = ContactsApp.createContact(first_name, last_name, email);
group.addContact(contact);
} else {
contact.setFullName(first_name+' '+last_name);
contact.setGivenName(first_name);
contact.setFamilyName(last_name);
}
It is working properly however in about 1 minute (100-130 contacts added) the script stops with:
Error Exceeded maximum execution time
Is there a way around this?

Issue:
Apps Script has an execution time limit of 6 or 30 minutes, depending on your account. You are most likely reaching this limit.
Workaround:
To avoid this, you can split the actions you want to perform into different executions by setting a time-based trigger that will fire each successive execution after the previous one has finished. I guess your function has a loop, so you could follow these steps:
Find out how many iterations the script can go through before reaching the time limit (probably 6 minutes). Each execution will have to go through this amount of iterations before stopping.
Create the following time-based trigger at the end of your function: after(durationMilliseconds). Thanks to this, you can run whatever function you specify after the amount of milliseconds you indicate. After each execution, a trigger will be created to fire the next one.
Make your loop a for loop. Because you have to split the loop, you have to store the loop counter (lets call it i) somewhere (you could use PropertiesService at the end of each execution, or write it in the spreadsheet) and retrieve it at the beginning of the next, so that each in successive execution, the script knows where to resume the loop. See, for example, this answer if you don't know how to store and retrieve script properties.
Sample code (check inline comments):
function createContacts() {
// Whatever your code outside the loop is
var i_old = // Retrieve i stored in previous execution (from PropertiesService? Spreadsheet?) (should be 0 if first execution)
var n_int = 100 // Number of iterations that can be done in a single execution without reaching time limit (change accordingly)
var total = 1000 || yourVariable.length // Total number of iterations (change accordingly)
// Loop starts at previous i store until it reaches the specified number of iterations for one execution or reaches the total number:
for(var i = i_old; i <= i_old + n_int && i <= total; i++) {
// Whatever your code inside the loop is
}
// Store i somewhere (PropertiesService? Spreadsheet?)
if (i <= total) { // Create trigger if i hasn't reach the total number of iteration
ScriptApp.newTrigger("createContacts")
.timeBased()
.after(1000 * 60) // This fires the function 1 minute after the current execution ends. Change this time according to your preferences
.create();
}
}
Note:
Please notice that only 1,000 or 2,000 contacts (depending on the account type) can be created daily.
Reference:
Current quotas
Apps Script: Current limitations
PropertiesService
after(durationMilliseconds)

Related

How do you track automated data inputs in Google Sheets over time?

I have a google sheet that runs a report on data that is automatically updated. What I would like to do is compare the new inputs with the old inputs to determine if the changes were positive or negative. How would you go about automating a sheet to track these changes?
Changes happen monthly
there would be a score 1-100; 100 being the best
would like to store this data over time for a historical view
Any advice would surely be appreciated
The numbers in each criteria change every month producing a score at the end of the table called Current Score
This score is then pulled into the historical tab as the "Current Score"
What I would like to see happen is that the Current score be saved every month and processed with a percentage change month over month
So I would need a function that stores a copy of the results before they change, processes a new score, and then calculates the difference between the two. Example here is the Dec score (stored values) compared to the most recent score.
Here is a link to the working example
https://docs.google.com/spreadsheets/d/1ImbRhWqGjvIx2CFRKapZ2wmxC9qpSKxxCbHr5tPOBOs/edit#gid=0
Solution
You can automate this process by using Google Apps Script. Open the script editor by clicking on Tools > Script Editor. It is based on JavaScript and allows you to create, access and modify Google Sheets files with a service called Spreadsheet Service.
In addition, you can use Time-driven triggers to run the script automatically once a month. To set it up, click Triggers in the left bar, then Add Trigger and select Time-driven in Select event source. You can now specify the month timer and the exact day and hour you want the script to run. However, I recommend that you do some testing before setting up the trigger to check that you get the desired results. You can test the code by clicking Run in the Editor.
Explanation of the code
There are three functions in the code. The main function is called updateScores and it does what you described in the question. It takes the current score, stores it in a new column and calculates the difference from the last month. Try this function and if you like the result, you can put the trigger in the main function. This way, the trigger calls main which its only responsibility is to call the other two functions. The first is updateScores, which I have already explained, and the second is clearScores, which clears all the values of Reports so you don't have to do it manually and you can start writing the new values for the new month.
I have added some comments so you can understand what each line does.
var lr = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('report').getLastRow()
function updateScores() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Historical')
var currentValues = ss.getRange('B2:B'+lr).getDisplayValues() // get current score
ss.insertColumnsAfter(2,2) // insert two new columns (current score and percent difference)
ss.getRange('D2:D'+lr).setValues(currentValues) // paste stored score
ss.getRange('C2:C'+lr).setFormula('=if(D2=0,"N/A",B2/D2-1)') // apply formula for last stored scores
ss.getRange('E2:E'+lr).setFormula('=if(F2=0,"N/A",D2/F2-1)') // correct formula reference
ss.getRange('E2:E'+lr).copyFormatToRange(ss,3,3,2,lr) // copy format percent
ss.getRange('F2:F'+lr).copyFormatToRange(ss,4,4,2,lr) // copy format scores
var month = new Date().toString().split(' ')[1] // get current month
ss.getRange('D1').setValue(month + ' score') // write current month on last stored scores
var diff = ss.getRange('E1').getDisplayValue() // get diff symbol
ss.getRange('C1').setValue(diff) // write diff
}
function clearScores(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('report')
ss.getRange('B2:G'+lr).clear()
}
function main(){
updateScores()
clearScores()
}

Mode set to have the script make calls only every 2 minutes inside the every 1 minute trigger. (Google App Script)

function All() {
var ss = SpreadsheetApp.getActive();
ss.getRange('Página1!A6').setFormula('=IF(ISEVEN(MINUTE(NOW())),"Ok","Error")')
ss.getRange('Página1!A6').copyTo(ss.getRange('Página1!A6'), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
if (ss.getSheetByName('Página1').getRange("A6").getValues()[0][0]=="Ok"){
//History
ss.getRange('Página1!D1').setFormula('=IFERROR(FILTER({C:C;H1},{C:C;H1}<>""))');
ss.getRange('Página1!D1:D').copyTo(ss.getRange('Página1!C1'),
}
}
Google has triggers every 1 minute, 5 minutes, 10 minutes, 15 minutes and 30 minutes.
For this model, I use the 1 minute trigger!
To bypass this and be able to turn it on every 2 minutes instead of 1 minute (because it weighs a lot in the spreadsheet and occasionally creates errors), to deflect this I created this model where it analyzes if the minute of the current time is odd or even. If even, it activates the rest of the script, if odd it ends without doing anything else.
I would like to know if I could do this same thing, but instead of throwing the function into a cell, copy the value so that the formula NOW() doesn't keep updating all the time and so on ... same step but directly in the script, without moving the spreadsheet with unnecessary calls.
And if it would also be possible to do this to set the script to work every 3 minutes instead of 2 minutes as I managed to do.
Instead of using a sheet with a formula to determine if the minute is even or odd, you can use the Apps Script alternative.
I am using the %(Remainder) operator to get the reminder of a division by 2. If it's zero then the number is odd.
The equivalent for MINUTE(NOW()) is achieved with the Javascript Date new Date().getMinutes()
function myFunction() {
if (new Date().getMinutes()%2==0) { //If the minute is odd.
//Your code here
} //No need for else.
}
Instead of modifying your spreadsheet use the Properties Service to store the last time you script ran. Bear in mind that the Properties Service only stores strings, so you will have to convert the Date object to an string an viceversa.
Related
How can I modify a trigger so that it emails upon edit, but not so quickly?

How to fix the "Service invoked too many times for one day: urlfetch" error?

I am getting the following error in my Google sheet:
Service invoked too many times for one day: urlfetch
I know for a fact I am not making 100k calls, but I do have quite a few custom functions in my sheet. I tried to make a new sheet and copy/paste the script into that one, but I still get the same error. I then switched my account, made a new sheet, added the code, and I still got the error.
Is this just because I am on the same computer? Is Google smart enough to realize I am the same person trying to do it? I highly doubt that, so I am wondering why it would be throwing this error, even after switching accounts and making a new sheet.
In addition to that, is there any way to make sure I don't go over the limit in the future? This error sets me back at least a day with what I was working on. I do plan to write a script to just copy/paste the imported HTML as values into another sheet, but until I get that working, I need a temporary fix.
Sample code:
function tbaTeamsAtEvent(eventcode){
return ImportJSON("https://www.thebluealliance.com/api/v3/event/" + eventcode + "/teams?X-TBA-Auth-Key=" + auth_key);
}
function ImportJSONForTeamEvents(url, query, options){
var includeFunc = includeXPath_;
var transformFunc = defaultTransform_;
var jsondata = UrlFetchApp.fetch(url);
var object = JSON.parse(jsondata.getContentText());
var newObject = [];
for(var i = 0; i < object.length; i++){
var teamObject = {};
teamObject.playoff = object[i].alliances
newObject.push(teamObject);
}
return parseJSONObject_(object, query, "", includeFunc, transformFunc);
}
That is one "set" of code that is used for a specific function. I am pulling two different functions multiple times. I have about 600 of one function, and 4 of another. That would only be just over a thousand calls if all were run simultaneously.
I should note that I also have another sheet in my drive that automatically updates every hour with a UrlFetch. I do no believe this should affect this though, due to the very low pull rate.
I had a similar issue even though I was only calling two fetch calls in my functions and each function per data row. It exponentially grew, and with my data changing, every recalculate call also called those functioned, which VERY quickly hit the max.
My solution? I started using the Cache Service to temporarily store the results of the fetch calls, even if only for a few seconds, to allow for all the cells triggered by the same recalculation event to propagate using only the single call. This simple addition saved me thousands of fetch calls each time I accessed my sheets.
For reference:
https://developers.google.com/apps-script/reference/cache?hl=en

Creating a script that runs every second

I am trying to create a script that edits a particular cell on a particular sheet every second with a random text like, "SetTime".
This Particular Cell is: X2
This Particular Sheet is: "System_Info"
You may ask why I need this, essentially, I have a cell that displays a time using the =NOW formula. When a spreadsheet is edited, it will refresh the =NOW formula.
So, I need a script that loops every second and runs a function that edits that cell.
I've used this:
setInterval(function(){ SpreadsheetApp.getSheet("System_Info").getRange('X2').setValue('SetTime'); }, 1000);
However, set interval is not defined.
Thanks for any help,
Shaun.
you are mixing server with client code. even if you use time driven apps script triggers its not possible because they run at most once a minute, and changes through api do not cause a refresh.
Alternative: go to spreadsheet menu,file,properties. Select the option to update calculated functions every minute. No script needed.
Here is a function that will update the time in a cell every second for 15 seconds. It should be at least a starting point for you.
function updateCell() {
for (i=0; i<15; i++){
Utilities.sleep(1000);
var date = new Date();
SpreadsheetApp.getActiveSheet().getRange("A1").setValue(date);
SpreadsheetApp.flush();
}
}

How can I enhance my auto hide script in Google Sheets so it wont time out

I have a script that I run on multiple Spreadsheets... it auto hides rows that contain a certain value. Currently this script it setup to run daily around 3:00 am, to ensure no one is active in it while it processes. The issue is I am now running into is these sheets are getting too large to use my current script, which runs line by line. The script times out and doesn't finish. I'm guessing it still runs the script on all the lines that are already hidden.
Here is my current script, which is pretty basic:
function autoHide() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("SHIPPING");
//get data from column
var data = sheet.getRange('AD:AD').getValues();
//iterate over all rows
for(var i=0; i< data.length; i++){
//compare first character, if greater than 0, then hide row
if(data[i][0] > 0){
sheet.hideRows(i+1);
}
}
}
I have tried searching for better options, and found where people were talking about using array filters, or running in batches, just different things that didn't seem to be explained enough for me to translate to what I was working on. I know running this line by line isn't the best way, especially with over a 1,000 rows and growing.
For a best case scenario, I would like to have a very efficient script that uses fractions of the processing my current script does. Otherwise, if there was just a way to run the script on the rows that are visible, that would be almost as good. Worst case scenario, if there is just a way to tell it to pick up where it left off when it gave a time out error... by placing some type of tag or something to know where to start back up.
I don't think linking a sheet is necessary, I just need to be able to hide any row that has a number greater than 0 in column AD, on a sheet called "SHIPPING".
While you could speed up the script itself (for example by batching consecutive rows that need to be hidden) that will also evenctually time out.
Instead, your script should remember in script properties the last row it processed, ao that if the script times out it will continue starting from that row.
You will also need to change the trigger times. Make it run every 10 minutes but only start processing if 1) its past 3am and b) the last row processed is not yet the very last row (which you reset to zero when finished).
this should handle huge sheets just fine. by 5am it would have run 12 times since 3am so it should be able to process 12 times more rows.
Note I chose 10min trigger so that a previous trigger (which could run for 6 minutes) wont ever overlap the next trigger.
Do make sure to set your timezone in the sheet and script file properties so that you use your timezones when checking if its past 3am already.