How to programatically find and distinguish triggers in Apps Script? - google-apps-script

I have a function that updates a spreadsheet. It runs every morning and, every time it does, it creates 4 triggers.
I also have time-driven triggers, which I want to keep there.
Since there is a limit on the triggers, I need to remove the triggers created by the function (while maintaining the other ones). However, when I run this:
function Triggers () {
Logger.log(ScriptApp.getProjectTriggers())
}
I get this as a response:
[Trigger, Trigger, Trigger, Trigger, Trigger, Trigger, Trigger, Trigger]
How can I identify, of these, the ones created by that function so I can eliminate those only?

I used this code:
function Triggers () {
var triggers = ScriptApp.getProjectTriggers()
for (i in triggers)
if ((triggers[i].getHandlerFunction()) == "createStats") {
ScriptApp.deleteTrigger(triggers[i])
}
}

Related

Google apps script for docs: fire every second or mimic onEdit

I'm looking to allow a google docs user to turn the background of a given text range yellow by typing an exclamation point. This sounds like onEdit, which I know exists in sheets but not in docs. I saw this workaround on GitHub, but it requires adding a sidebar and inserting HTML, which I'd rather not do.
This answer discusses an onEdit workaround, but it still has the trigger fire every 60 seconds, rather than, say, every second.
This answer lays out how to call a function every second, and I'm trying to get it to work, but I can't figure it out. Here's what I have:
function myFunction() {
for (var i = 0; i < 10; i++) {
var doc = DocumentApp.openByUrl('Doc URL');
var body = doc.getBody();
var text = body.editAsText();
Logger.log(body.findText('!'))
if (body.findText('!') != null) {
text.setBackgroundColor(13, 50, '#FFFF00');
Utilities.sleep(1000);
}
ScriptApp.newTrigger("myFunction")
.timeBased()
.after(1000)
.create();
}
}
The function runs once, then sends me the error message, "Exception: This script has too many triggers. Triggers must be deleted from the script before more can be added." I don't have any triggers added other than what's in this function. I suspect the answer might be a while loop instead, but I'm a beginner and I'm not sure. What should I do?
The function you show will create ten triggers that run that very function after a second. When those ten copies run, they each create another ten triggers. So after one second you would have 10 triggers, after two seconds you would have 100 triggers, after three seconds you would have 1000 triggers, and so on. In practice, you get an error like the one you mention almost right away.
What you need is exactly one trigger. Use getProjectTriggers() to find whether a trigger already exists, and only create a trigger if there are none. You should also move the trigger creation code outside the loop.

How can I identify which time-driven trigger has called a function?

I have a Google App Script function in a Google Sheets spreadsheet I need to call once a day using a time-driven trigger.
This function often takes longer to run than the maximum time allowed for scripts, currently 6 minutes, so I've written it to do its work over several calls. If the function hasn't finished I would like to create a temporary time-driven trigger to run the function again in one minute and delete the temporary trigger when the function is called, but leave the daily trigger active. Pseudocode probably explains it better...
function run_job_via_trigger(trigger) {
if(trigger === temporary trigger) {
// If this is a 'temporary' trigger that was created to
// run the job after the first call then delete it.
// This must not delete the daily trigger that makes the
// first call to the function.
// If I check the UID of the trigger here I still
// would need to know which trigger is the daily trigger
// and which is a temporary trigger!
ScriptApp.deleteTrigger(trigger)
}
const job_finished = job_that_takes_several_calls_to_complete();
if(job_finished === false) {
// Create a temporary time-driven trigger to call this
// function again in 1 minute.
ScriptApp.newTrigger('run_job_via_trigger').timeBased().everyMinutes(1).create();
}
}
function job_that_takes_several_calls_to_complete() {
// This function often takes more time to complete than
// the maximum time allowed for scripts to run. It keeps
// track of its execution time and returns true if it has
// finished doing what it needs to do or false if it
// needs more time and should be called again.
return finished ? true : false;
}
How can I detect which time-driven trigger has called the run_job_via_trigger function so I can delete the temporary trigger(s) but not the daily trigger?
The spreadsheet has several other time-driven triggers so simply deleting all triggers at the end and creating a new daily trigger is not, as far as I can tell, an acceptable solution.
When your function is called with a trigger, it receives a trigger event as its parameter. You can check the trigger UID for example, like so:
function doWhatever(e) {
if(e.triggerUid === 'trigger_id') {
// do something
} else {
// do something else
}
}
UPDATE
There are a couple of ways to know which triggers are running.
The best-case scenario, when you created a trigger, you stored its ID somewhere, like user properties and then you always know when it's running. But I guess you haven't done that.
In your case you might want to do some manual work. Go to the triggers page, find your recurring trigger, click on the three dots on the right and select "Executions". You will then see the trigger ID in the filter:
Now you can use that in your code to check whether it's your recurring trigger or your temporary trigger.

How many time based trigger can your add-on run on a document / user?

I don't understand what are the real limits of App Script time-based triggers.
Let's say I need to create two different time based triggers on a document for a user using my add-on. I want to use two triggers because it is for two different features and they need different internals & management.
I did a simple test:
function testTriggers() {
var n = 10;
SpreadsheetApp.getUi()
.alert('Creating triggers' + n);
createTriggers(n);
}
function createTriggers(n) {
for(var i = 0; i < n; i++){
ScriptApp.newTrigger('myTriggerFunction' + i)
.timeBased()
.everyHours(i+1)
.create();
}
}
function myTriggerFunction(){
console.info("STARGED trigger");
for(var i = 0; i < 10; i++){
Utilities.sleep(10 * 1000);
console.info('time: ' + i);
}
console.info("ENDED trigger");
}
function myTriggerFunction0() {
myTriggerFunction();
}
function myTriggerFunction0() {
myTriggerFunction();
}
function myTriggerFunction1() {
myTriggerFunction();
}
function myTriggerFunction2() {
myTriggerFunction();
}
function myTriggerFunction3() {
myTriggerFunction();
}
function myTriggerFunction4() {
myTriggerFunction();
}
function myTriggerFunction5() {
myTriggerFunction();
}
function myTriggerFunction6() {
myTriggerFunction();
}
function myTriggerFunction7() {
myTriggerFunction();
}
function myTriggerFunction8() {
myTriggerFunction();
}
function myTriggerFunction9() {
myTriggerFunction();
}
When I make testTriggers run I receive the error:
This add-on has created too many time-based triggers in this document
for this Google user account.
And I can see in my triggers window that only the first trigger was created, on myTriggerFunction0.
Is this the expected behavior ? if I check the documentation I see nothing about this, on the quota page they only tell you: 20 user / script, which I think is for every document the add-on is installed on at this point.
By looking on stack I've found this answer where it seems that in the past documentation it was stated:
Each add-on can only have one trigger of each type, per user, per
document. For instance, in a given spreadsheet, a given user can only
have one edit trigger, although the user could also have a form-submit
trigger or a time-driven trigger in the same spreadsheet.
Has something changed ?
Also, I've a question about the 90 min / day total runtime stated in the quota page:
I think this limit is user based, but is it also document based ? Will 2 triggers on 2 different documents consuming the same "execution time quota" ?
Installable triggers in add-ons have the same set of restrictions applied to installable triggers in other kinds of Apps Script projects, but in addition to these, others are applied to installable triggers in add-ons specifically.
To answer your questions:
1. Is this the expected behavior?
Yes, this is the expected behavior in this case because you are using triggers in an add-on.
According to the Restrictions for Installabe Triggers in Add-Ons documentation:
Time-driven triggers cannot run more frequently than once per hour.
Which may essentially be the reason why you are getting the error message above because you are trying to run more than one time-driven trigger on an hour basis.
2. Has something changed?
No, the restriction is still up and valid which means that this restriction overlaps the 20 triggers per user per script one. The latter one refers to the fact that you can have up to 20 triggers for one Apps Script script.
3. Will 2 triggers on 2 different documents consuming the same "execution time quota"?
Yes and that it is because the 90 minutes quota applies to the user. This essentially means that the execution time of all your triggers from all your projects must not exceed the 90 minutes mark.
Reference
Installable Triggers in Add-Ons;
Quotas for Google Services.

How to delete triggers that own by other users in Google Apps Script?

I have an installable trigger that is supposed to created once. But I found out that some of my coworkers have rerun my code and recreated another 3 triggers that are exactly the same. I felt so silly that I didn't add any conditional statement for that...
Anyway, now I want to delete these extra triggers. But I cannot find the delete/edit button on the console UI of the GAS project. And I've also tried ScriptApp.getProjectTriggers(), but it lists the triggers of the project that are only owned by me.
How can I delete these extra triggers created by others (owned by other users)? Or can I restart my project from a clean start again?
In addition to #Alan Wells comment: no, you definitely cannot. What you can do, though, is make your users run a remover function on their behalf with something like this (if your script is container-bound, add it to onOpen() trigger if you have one, or to any other function that you expect to be called by others):
function deleteAllTriggers() {
var ts = ScriptApp.getProjectTriggers();
ts.forEach(function(trigger){
var handlerName = trigger.getHandlerFunction();
if(handlerName === 'yourFunctionName') { //check if you are deleting target trigger;
ScriptApp.deleteTrigger(trigger);
Utilities.sleep(1000); //wait (in this sample 1s) to avoid "too many times" error;
}
});
}
ES6-style:
const deleteAllTriggers = () => {
const triggers = ScriptApp.getProjectTriggers();
triggers.forEach((trigger) => ScriptApp.deleteTrigger(trigger));
};

Google script to edit a time trigger

I would like to edit the timing of a trigger based on a value in a cell of a spreadsheet. The only way I can think of is to set a trigger for a function that is deleting all triggers and making new triggers based on the value of a cell. Is there a way to edit the existing timing of existing triggers?
You can delete just one.
function deleteTrigger(funcName)
{
var triggers=ScriptApp.getProjectTriggers();
for(var i=0;i<triggers.length;i++)
{
if(funcName==triggers[i].getHandlerFunction())
{
ScriptApp.deleteTrigger(triggers[i]);
break;
}
}
}
I run things at variable time intervals by setting my trigger to the minimum interval I need, tying the trigger to a function that tests against criteria. If criteria is met, I execute the desired function. You could implement this principle where the criteria for whether to execute the main function is dependent on the cell contents (or direct it to different functions depending on the contents of the cell).