Run a trigger X amount of time after an edit - google-apps-script

I would like to get a trigger to run a specified amount of time after an edit occurs.
I'm thinking that I could someone combine an edit trigger and a time based trigger for this functionality, but am not clear how.
To provide some additional detail.
I have a Google Sheet where I track my insulin usage. I am supposed to check my blood sugar 2 hours after I administer my insulin.
When I administer the insulin I make an entry into Google Sheets, time/amount/etc... When I make this entry I would like to create a trigger that will run in 2 hours to send me an email notification as a reminder to check my blood sugar again and make a new entry.

This is how I use time triggers in Google App Script.
Function to create the time trigger.
function createTriggger(name,action,time){
// Time is in minutes
var trigger = ScriptApp.newTrigger(action)
.timeBased()
.after(time*60*1000)
.create();
var triggerID = trigger.getUniqueId();
// Store trigger id to delete the trigger once it has executed
PropertiesService.getScriptProperties().setProperty(name, triggerID);
};
This name is the name of the property to store the trigger id, so you can delete the trigger after it executes. action is the name of the function you want the trigger to execute, and time is in minutes.
On the last line of your function that you want the trigger to execute, use this line to grab the trigger id and delete it from your trigger list. Put the name of your trigger id property in name.
var triggerID = PropertiesService.getScriptProperties().getProperty(name)
deleteThisOne(triggerID)
Here is the delete funciton:
function deleteThisOne(id){
var triggers = ScriptApp.getProjectTriggers();
for(var i=0;i<triggers.length;i++){
if(triggers[i].getUniqueId() == id){
ScriptApp.deleteTrigger(triggers[i]);
break;
};
};
};
Look at google scripts Installable Triggers for Limitations

When your edit trigger fires, you could use a ClockTrigger - see the ClockTriggerBuilder documentation and example, which allows you to either set a time relative to the current execution time, or at an absolute time.

You can use a work around like this:
Set '0' value in some corner cell in spreadsheet which you won't modify ever. Hide that column.
Set onEdit trigger on spreadsheet and write its function, which sets the value '0'.
Set another trigger which is triggered every 10 minutes to increment that cell value by 10 every time. Also add this to that function:
if(cellValue%120 == 0)
{
setReminder(); //function to send you reminder
cellValue = 0;
}

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.

Run apps script (google sheets) again after 30 minutes if condition not met but maximum once per day

I would like to run a script on a daily trigger in google sheets. The script should first check if cell "A1" of Sheet1 has the value "DO NOT RUN" - then it should wait 30 minutes before running again (the value in A1 should change by then due to another script).
The script should not run more than once per day.
If the value of A1 will be changed by another script, it might be better to make the other script to create a time-driven trigger set to be run after the desired time.
const action = 'put here the name of the function';
const minutes = 30;
const trigger = ScriptApp.newTrigger(action)
.timeBased()
.after(minutes * 60 * 1000)
.create();
Notes:
You might use the Properties Service to manage a flag to control if the trigger for certain date was already create / or if the function was already executed.
You should have to implement a method to delete the old triggers. This could be done by the function called by the trigger.
Related
Programmatically delete a Google Apps Script time-based trigger by trigger name?
Run a trigger X amount of time after an edit

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.

Trigger bug when copying rows

I have a function that copies rows from one sheet to another, which works when I run it manually:
function updateDataRange() {
var formSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Form Responses 1");
var lastFormRow = formSheet.getLastRow();
var dataSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("DataRange");
dataSheet.clear();
for(var rowCounter = 1; rowCounter <= lastFormRow; rowCounter++) {
var sourceRange = formSheet.getRange('B'+rowCounter+':H'+rowCounter);
var insertRow = dataSheet.getLastRow() + 1;
var targetRange = dataSheet.getRange('A'+insertRow);
sourceRange.copyTo(targetRange);
}
}
However, when I run this via a trigger (e.g. function onEdit(e) { updateDateRange(); } ), there are gaps in the rows and some of the values will be left out. In this case, I know there is a "fix" by using rowCounter instead of insertRow to write the files instead of using getLastRow(), but one can easily see how this is a problem in another scenario.
So I guess my question is simple: Why isn't this working correctly when using a trigger?
Edit: To clarify, I have a 3rd sheet with some conditions/cells (i.e. conditions that influence which rows are to be copied) and changing them (like changing a different date) trigger the function.
Explanation:
You are using an onEdit trigger to capture sheet changes. But onEdit triggers work only when the user changes the value of a cell. Referencing the official documentation:
The onEdit(e) trigger runs automatically when a user changes the value
of any cell in a spreadsheet.
In your case, I think you are trying to execute this code when the Form Responses sheet is filled in with data by the form.
There are two kind of trigger functions that work with form submissions and they are both installable.
One is a google sheet event based trigger and the other one a form event based trigger.
Your goal is to execute some code from the google sheets side, so it makes sense to use the first one.
Modifications:
Change the name of the function from onEdit to a different name of your choice e.g. myFormSubmitTrigger.
Since the trigger is installable you need to "install" a onFormSubmit trigger for the myFormSubmitTrigger function. Here you can find some simple instructions on how to do that.
If your question is why doesn't your function work when the form makes changes to sheet Form Responses 1 then Marios has answered you question accept it and move on. If your trying to run your function when a user is making edits to sheet Form Responses 1 using a simple trigger then a possible explanation for not getting all of the rows completely copied is that simple trigger must complete in 30 seconds. If you continue to accept more data in Form 1 Responses then soon or later you will have problems but with this function it will be a lot later because it will run much faster than your code:
function updateDataRange() {
const sss=SpreadsheetApp.openById('ssid');
const ssh=e.source.getSheetByName('Form Responses 1');
const sr=2;
const svs=ssh.getRange(sr,2,sh.getLastRow()-ssr+1,7).getValues();
const dsh=e.source.getSheetByName('DataRange');
dsh.clear();
dsh.getRange(1,1,svs.length,svs[0].length).setValues(svs);
}
However, I would recommend that you never edit a form responses sheet. I would consider using the onFormSubmit trigger to capture all of the data to a second sheet that is available to edit. And it will have additional data automatically appended to it via dsh.appendRow(e.values). And so now you would no longer require an onEdit trigger because your data sheet is kept upto data with an onFormSubmit trigger and you may feel free to edit the datasheet any time you wish.
If neither of these questions suffices then I would recommend that you be more clear about what you are asking.

Creating a timestamp on a time trigger?

-Need to create a timestamp every day at 6, checking to see if a specific column has been updated. I have the time trigger running, I get the emails that it tried to run but 'script function not found: myFunction'.
I know I need to make 'myFunction' but should I just be using a onEdit? I'm not sure how to get it to work so every day at 1 pm, it checks a specific column for an edit, then gives a timestamp in another column if it was edited.
I tried a few times to figure out how to get the active range sorted, but I don't know if the trigger is just running 'myFunction' at the specified time, and if I just need to write code then point the trigger at it.
function createTimeDrivenTriggers() {
ScriptApp.newTrigger('myFunction')
.timeBased()
.everyDays(1)
.atHour(13)
.create();
var timeZone = Session.getScriptTimeZone();
var timestamp_format = "mm/dd/yyyy";
var updateColName = "Updated";
var timeStampColName = "Timestamp";
var ss = SpreadsheetApp.getActiveSpreadsheet();
Do not use onEdit. That means "run every time someone edits this sheet."
Yes, you must write myFunction first. Run it and debug it manually before you attach a trigger to it.
Do not attach the trigger with ScriptApp. Click on the trigger button in the script editor, which will take you to the script.google.com dashboard where you can use a trigger setup wizard to set the trigger correctly to run 'myFunction' between 1-2pm.