How can I run my time based trigger after a certain amount of time. Let's say I would like to start the trigger right now and stop after two weeks. How can I implement that to the installable trigger? Is it even possible? I'd like to know.
ScriptApp.newTrigger("myFunction")
.timeBased()
.everyMinutes(10)
.create();
The easiest method would be to have a function for deleting the trigger and storing the trigger ID in the User Properties.
So you could do something along the lines of this:
function createTrigger() {
var trigObj = ScriptApp.newTrigger("test")
.timeBased()
.everyDays(7)
.create();
var props = PropertiesService.getUserProperties();
var endDate = new Date;
endDate.setDate(endDate.getDate() + 14);
props.setProperty('trigId', trigObj.getUniqueId());
props.setProperty('trigEnd', endDate);
}
function remTrig(){
var props = PropertiesService.getUserProperties().getProperties();
var now = new Date;
var trigs = ScriptApp.getProjectTriggers();
if (now.getDate() == new Date(props['trigEnd']).getDate()){
Logger.log('Trigger will be deleted');
for (var i = 0; i < trigs.length; i++) {
if (trigs[i].getUniqueId() == props['trigId']){
Logger.log(`Deleting ${trigs[i].getUniqueId()}`);
ScriptApp.deleteTrigger(trigs[i]);
}
}
}
else
Logger.log(`Trigger to be deleted on ${props['trigEnd']}`);
}
Obviously the trigger check and deletion would have to happen somewhere inside of the function your trigger is calling or you would need a separate daily trigger for the remTrig() function checking if it's time to delete it.
A few considerations to keep in mind is that this sample is meant for a single trigger per user if you use the first method. For a daily check if it's time to remove the trigger you'd need to have the function that creates the trigger to create a second one for the deletion check.
Related
I have this trigger that I want to run every morning at 9am.
function copyFormulasToValues() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getSheets()[1];
var destination = ss.getSheets()[2];
var range = source.getRange("A1:C6015");
range.copyValuesToRange(destination, 1, 3, 1, 6030);
ScriptApp.newTrigger("myFunction")
.timeBased()
.atHour(9)
.nearMinute(1)
.everyDays(1)
.create();
The issue is, after a few days, my triggers fill up and it can't run anymore without me manually clearing them. I know there's a way to build in a function that essentially replaces the old trigger (by way of deletion) with the new trigger each day.
This post here gives an example of how to do it, but I just can't seem to get it to work. Would anybody be able to help me write this so that it deletes the existing trigger and replaces it with the new one each day? I'm a beginner so any help is much appreciated!!
Both of these methods will create a single trigger and replace a single trigger. If you need to create and replace multiple triggers, the code would be slightly different.
Method 1
Creates a new trigger, and replaces any existing triggers that are using the same event handler ("myFunction" in your example). This assumes that you won't change the name of your handler function between executions and that you don't have multiple triggers running with the same handler. It is the weaker of the two methods for those reasons. However, it is slightly easier to understand since it isn't using any services that aren't already in your code sample.
// this is the function that gets called by your trigger
function myFunction() {
Logger.log("I am an event handler");
}
function replaceTrigger(handlerName) {
const currentTriggers = ScriptApp.getProjectTriggers(); // get the projects triggers
const existingTrigger = currentTriggers.filter(trigger => trigger.getHandlerFunction() === handlerName)[0]
if (existingTrigger) ScriptApp.deleteTrigger(existingTrigger) // delete the existing trigger that uses the same event handler
// create a new trigger
if (existingTrigger[0])
ScriptApp.newTrigger(handlerName)
.timeBased()
.atHour(9)
.nearMinute(1)
.everyDays(1)
.create();
}
replaceTrigger('myFunction')
Method 2
Store a trigger ID when you create a trigger, then use that ID to delete it. The benefit of using this method is that it will allow you to change the name of your handler function, and it won't have side effects on other triggers in your project. It uses the PropertiesService, which you may be unfamiliar with.
function myFunction() {
Logger.log("I am an event handler");
}
function replaceTriggerWithId(handlerName) {
// read the existing trigger id from script properties and delete it if it exists
const currentTriggers = ScriptApp.getProjectTriggers(); // get the projects triggers
const existingTriggerId = PropertiesService.getScriptProperties().getProperty('triggerId');
if (existingTriggerId) {
const existingTrigger = ScriptApp.getProjectTriggers().filter(trigger => trigger.getUniqueId() === existingTriggerId)[0];
if (existingTrigger) ScriptApp.deleteTrigger(existingTrigger)
}
// create a new trigger and store its id in script properties
const triggerId =
ScriptApp.newTrigger(handlerName)
.timeBased()
.atHour(9)
.nearMinute(1)
.everyDays(1)
.create()
.getUniqueId()
PropertiesService.getScriptProperties().setProperty('triggerId', triggerId);
}
replaceTriggerWithId('myFunction');
You could set replaceTrigger or replaceTriggerWithId to run on a time trigger. When they are triggered they will delete any triggers that were defined with the same handler name (method 1), or whichever trigger was last defined by the trigger handler (method 2). After deleting the old trigger, they will create a new one.
References:
ScriptApp
PropertiesService
#RayGun 's answer method 2 seems Error , so I fixed up a little bit:
function myFunction() {
Logger.log("I am an event handler");
}
function replaceTrigger(handlerName) {
const currentTriggers = ScriptApp.getProjectTriggers(); // get the projects triggers
const existingTrigger = currentTriggers.filter(trigger => trigger.getHandlerFunction() === handlerName)[0]
if (existingTrigger) ScriptApp.deleteTrigger(existingTrigger) // delete the existing trigger that uses the same event handler
// create a new trigger
//if (existingTrigger[0]) Yes, I commented this
ScriptApp.newTrigger(handlerName)
.timeBased()
.atHour(9)
.nearMinute(1)
.everyDays(1)
.create()
.getUniqueId()
console.log('Create New Triger:...');
}
function main(){
replaceTrigger('myFunction')
}
Don't forget: you must Run 'main' function
, and then 'myFunction' will always be created only once, forever...
I am currently using app script to sync my google sheet to google calendar. The process is quite simple, my script only takes a date and title from the spreadsheet and creates an all day event on that date with that title.
The problem I am facing is that if I accidentally key in the wrong date or I have to update one of the dates inside the spreadsheet, on running the scheduleShifts function again, all the events are created again which results in many duplicate events that I did not intend to be there. I'm trying to find a solution that helps either updates the title of the event or deletes the event and create a new one in the case where the date that is in the spreadsheet is wrong.
It also isn't very efficient to update the data in the spreadsheet and then update the calendar because in the event where quite a few dates or titles have to be changed, it would take quite a bit of time to change them all in the calendar. It would also be very troublesome to delete the current calendar, create a new one, copy that id into the spreadsheet and then update everything again.
This is what my current code looks like:
function scheduleShifts()
{
/*Identify calendar*/
var spreadsheet = SpreadsheetApp.getActiveSheet();
var calendarId = spreadsheet.getRange("C1").getValue();
var eventCal = CalendarApp.getCalendarById(calendarId);
/*Import data from the spreadsheet*/
var signups = spreadsheet.getRange("C4:F73").getValues();
/*Create events*/
for (x=0; x<signups.length; x++)
{
var shift = signups[x];
var title = shift[0];
var date = shift[3];
eventCal.createAllDayEvent(title, date);
}
}
/*Make the script shareable for others to use*/
function onOpen()
{
var ui = SpreadsheetApp.getUi();
ui.createMenu('Sync to Calendar')
.addItem('Schedule shifts', 'scheduleShifts')
.addToUi();
}
I have tried to avoid duplicating events by retrieving all the events with Advanced Calendar Service, pushing their titles into an array and then verifying with IndexOf. However, I am unsure if this method will work if the title stays the same while there is an update in the date of that event.
The code that I referenced from to do this:
var existingEvents=Calendar.Events.list(calendarId);
var eventArray=[];
existingEvents.items.forEach(function(e){eventArray.push(e.summary)});
for (x=0; x<signups.length; x++) {
var shift = signups [x];
var startTime = shift[0];
var endTime = shift[1];
var inspector = shift[2];
if(eventArray.indexOf(inspector)==-1){
eventCal.createEvent(inspector, startTime, endTime);
}else{
Logger.log('event exists already');
}
}
If anyone needs more info feel free to ask in the comments, your help would be greatly appreciated.
You have developed a script to create events using data from a sheet.
You want to be able to update previously created events while avoiding creating duplicates.
Step 1. Avoid creating duplicates:
In order to avoid creating duplicates, you could make the script write the corresponding eventId when each event has been created. This way, next time the script runs, it can check whether the eventId is populated (in which case the event already exists), and only create the event if it doesn't exist. It could be something like this (in this sample, the eventIds are written to column G):
function scheduleShifts() {
const spreadsheet = SpreadsheetApp.getActiveSheet();
const calendarId = spreadsheet.getRange("C1").getValue();
const eventCal = CalendarApp.getCalendarById(calendarId);
const signups = spreadsheet.getRange("C4:G7").getValues();
for (let x = 0; x < signups.length; x++) {
const shift = signups[x];
const title = shift[0];
const date = shift[3];
const eventId = shift[4];
if (eventId == "") { // Check the event doesn't exist
const newEvent = eventCal.createAllDayEvent(title, date);
const newEventId = newEvent.getId();
spreadsheet.getRange(4 + x, 7).setValue(newEventId); // Write new eventId to col G
}
}
}
Step 2. Update events:
Regarding the update process, I'd suggest you to install an onEdit trigger, either manually or programmatically. This this action requires authorization, a simple onEdit trigger would not work here (see Restrictions).
This way, you can use the event object to only update the event corresponding to the row that was edited, thus avoiding having to update all events every time, which would make the process very inefficient. The update process itself would consist on calling setTitle and setAllDayDate.
The function fired by the onEdit trigger could be something like this:
function updateEvent(e) {
var editedRow = e.range.getRow();
var editedData = e.source.getActiveSheet().getRange(editedRow, 3, 1, 5).getValues()[0];
var eventId = editedData[4];
try {
var event = CalendarApp.getEventById(eventId);
event.setTitle(editedData[0]);
event.setAllDayDate(editedData[3]);
} catch(err) {
console.log("There was a problem updating the event. This event might not exist yet.");
}
}
Notes:
You may not want to manually set the range (C4:G73) if the number of events might vary. You can use methods like getLastRow() no make this range dynamic, based on the spreadsheet content.
The eventId that are used here, corresponding to Class CalendarEvent, are not identical to the API Event resource. Take this into account in case you use the Advanced Service.
I have a script which uses googlesheets, I am new to this also, there the trigger onOpen() and when the sheet is opened it runs whats inside it, however I wanted to run a function even when sheet is not opened. Do I need a trigger to do this? What should I use to do this?
I tried:
OnCreate
Installable
OnEdit
I want to create a time-based trigger will the script run, even if the sheet is not open and/or even if I'm not logged in to Sheets.
I tried:
function onOpen() {
// Trigger every 10 minutes.
ScriptApp.newTrigger('myFunc') //Run Update All Sheets
.timeBased()
.everyMinutes(10)
.create();
}
and outside the script. I wonder which of these works on background.
// Trigger every 10 minutes.
ScriptApp.newTrigger('myFunc') //Run Update All Sheets
.timeBased()
.everyMinutes(10)
.create();
function onOpen() {
}
It is possible to add a trigger programmatically.
https://developers.google.com/apps-script/guides/triggers/installable
Managing triggers programmatically
You can also create and delete triggers programmatically with the Script service. Start by calling ScriptApp.newTrigger(functionName), which returns a TriggerBuilder.
The following example shows how to create two time-driven triggers—one that fires every 6 hours, and one that fires every Monday at 9 a.m. (in the time zone that your script is set to).
triggers/triggers.gsView on GitHub
/**
* Creates two time-driven triggers.
*/
function createTimeDrivenTriggers() {
// Trigger every 6 hours.
ScriptApp.newTrigger('myFunction')
.timeBased()
.everyHours(6)
.create();
// Trigger every Monday at 09:00.
ScriptApp.newTrigger('myFunction')
.timeBased()
.onWeekDay(ScriptApp.WeekDay.MONDAY)
.atHour(9)
.create();
}
This next example shows how to create an installable open trigger for a spreadsheet. Note that, unlike for a simple onOpen() trigger, the script for the installable trigger does not need to be bound to the spreadsheet. To create this trigger from a standalone script, simply replace SpreadsheetApp.getActive() with a call to SpreadsheetApp.openById(id).
triggers/triggers.gsView on GitHub
/**
* Creates a trigger for when a spreadsheet opens.
*/
function createSpreadsheetOpenTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger('myFunction')
.forSpreadsheet(ss)
.onOpen()
.create();
}
To programmatically modify an existing installable trigger, you must delete it and create a new one. If you have previously stored the ID of a trigger, you can delete it by passing the ID as an argument to the function below.
triggers/triggers.gsView on GitHub
/**
* Deletes a trigger.
* #param {string} triggerId The Trigger ID.
*/
function deleteTrigger(triggerId) {
// Loop over all triggers.
var allTriggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < allTriggers.length; i++) {
// If the current trigger is the correct one, delete it.
if (allTriggers[i].getUniqueId() === triggerId) {
ScriptApp.deleteTrigger(allTriggers[i]);
break;
}
}
}
Open your google sheet
Click Tools
Click Script Editor
Make a function. Example Birth day greeting every day is shown below
function hbd_mail() { //birth day mail daily 7 am from hrd login
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Emp");
var today=new Date();
lastRow = sheet.getLastRow();
var values = sheet.getRange('A:T').getValues();
for (i=1;i<lastRow;i++){
if (values[i][14]=="") {} else {
if ( Utilities.formatDate( values[i][14] ,"GMT+05:30", "dd-MMM") == Utilities.formatDate( today ,"GMT+05:30", "dd-MMM") && values[i][18]=="Yes" ) {
var message = 'Dear '+ values[i][1]+',\n\nMegawin Collective wishes you a very happy birth day ('+ Utilities.formatDate( values[i][14] ,"GMT+05:30", "dd-MMM")+ ').\n\nHRD Megawin Switchgear Pvt Ltd';
var subject = "Happy Birth Day "+values[i][1];
if (values[i][7]=="") {} else {GmailApp.sendEmail(values[i][7], subject, message);}
}//if
}
}//for
}
Save the script
Click the Clock in the menu
It will ask you the function name - select the above function
Then select time based
Clcik every hour / day / week etc
Save it.
Now the function will run automatically from google server (at the frequency selected by you) without your having to do anything.
It is pretty simple.
When I first started coding in GAS I was using time based triggers and then deleting them when I started the next iteration (to get around time limits). I have since seen someone use a trigger that fires just once so I don't have to worry about deleting it, but I can't seem to find that example.
Old way:
//- This function deletes the running triggers from the repository creation process. it does NOT delete the form or
//- timed run triggers
function deleteTrigger() {
Logger.log("Deleting Triggers?");
// Loop over all triggers and delete them
var allTriggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < allTriggers.length; i++) {
var id = allTriggers[i].getHandlerFunction();
Logger.log(id);
if (id == "callCopy"){ScriptApp.deleteTrigger(allTriggers[i]); Logger.log("I deleted the copy files trigger");}
}
}
//This is the trigger to restart the call copy script in the right place.
function createTrigger() {
// Trigger every 1 minute
Logger.log("Creating File Copy Trigger");
ScriptApp.newTrigger('callCopy')
.timeBased()
.everyMinutes(1)
.create();
}
I am working on pulling data from mySQL database via jdbc into a google spreadsheet everyday at 8 am. I created the following time based trigger.
function createTimeBasedTrigger() {
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < triggers.length; i++) {
if ( triggers[i] === 'myFunction' ) ScriptApp.deleteTrigger(triggers[i]);
};
ScriptApp.newTrigger('myFunction')
.timeBased()
.everyDays(1)
.atHour(8)
.create();
}
I also tried to create the trigger in the script editor and they either execute multiple times or do not execute at all.
A trigger is not a string, it's an object. It can't be equal to 'myFunction'. A correct comparison looks like
if (triggers[i].getHandlerFunction() === 'myFunction') {
ScriptApp.deleteTrigger(triggers[i]);
}
That said, managing triggers via script editor interface is convenient, and they run as expected, provided that the function you are calling actually works.