Google Apps Script Trigger not running - triggers

I have trouble with a time based trigger, which installs another time based trigger after work. I use this concept to split the work. Unfortunately the installed trigger never runs, but it is created.
The following code illustrates the concept:
function start() {
installTrigger();
}
function installTrigger(){
deleteClockBasedTriggers();
ScriptApp.newTrigger("sendMail").timeBased().after(5000).create();
}
function deleteClockBasedTriggers(){
var projectTriggers = ScriptApp.getProjectTriggers();
for(var i=0; i<projectTriggers.length; i++){
if(projectTriggers[i].getEventType() == ScriptApp.EventType.CLOCK){
ScriptApp.deleteTrigger(projectTriggers[i]);
}
}
}
function sendMail(){
var email = "xyz#gmail.com";
MailApp.sendEmail(email, "TriggerMail", "hello");
installTrigger();
}

You need to set trigger after 1 minute to get it fired.

Related

Email notification when changes are made to specific data in Google sheets

I have a general script that will send email notification of changes made to a live roster in Google sheets and have set up a trigger for the event to occur 'On change'. All data in the sheet is retrieved via IMPORTRANGE. This script is performing as expected, however I wish to make a couple of changes that am not sure how to go about. Here is a sample sheet: link to sheet
Wish to only send email notification when changes concern the name 'Craig'. For example my name is either added or taken off a rostered day. The script currently sends emails for all changes made across the sheet.
ID the row that this change occurs so as to reference the date and venue in the email body.
Thanks for any help and suggestions.
Full script:
function onEdit() {
var getProps = PropertiesService.getUserProperties();
var lenProp = getProps.getProperty('Len');
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Roster');
var data = sheet.getRange('E4:ACB562').getValues().toString().length;
if (data != lenProp) {
getProps.setProperty('Len', data );
MailApp.sendEmail('email#gmail.com', 'Changes have been made to your live roster', 'Previous value: ' + lenProp + ' New value: ' + data);
}
}
New revised script:
function installedOnEdit(e) {
var range = e.range;
var value = range.getValue();
if (value == "Craig" || (e.oldValue == "Craig" && value == "")) {
var rowNumber = range.getRow(); // You can retrieve the row number of the editor cell.
console.log(rowNumber)
var [colA, colB] = e.source.getActiveSheet().getRange(rowNumber, 1, 1, 2).getValues()[0];
var getProps = PropertiesService.getUserProperties();
var lenProp = getProps.getProperty('Len');
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Roster_LIVE');
var data = sheet.getRange('E4:ACB562').getValues().toString().length;
if (data != lenProp) {
getProps.setProperty('Len', data);
MailApp.sendEmail('email#gmail.com', 'Changes have been made to your roster!', 'Your live roster has been edited. These changes concern ' + colB + ' on ' + colA);
}
}
}
I believe your goal as follows.
You want to send an email using OnEdit trigger when the value of Craig is put to a cell.
You want to retrieve the edited row number.
You want to run the script when the value of Craig is removed.
Modification points:
From I have a general script that will send email notification of changes made to a live roster in Google sheets and have set up a trigger for the event to occur 'On change'., I understood that you are using the installable OnChange trigger. If it's so, in your situation, I would like to recommend to use the installable OnEdit trigger.
And, when I saw your script, the function name is onEdit. If the installable OnEdit trigger installs to onEdit, when a cell is edited, onEdit is run 2 times by the simple trigger and the installable trigger with the asynchronous process. Ref So, please rename the function name from onEdit to other and reinstall the installable OnEdit trigger to the renamed function name.
In order to retrieve the row number of the edited cell, you can use the event object.
When above points are reflected to your script, it becomes as follows.
Modified script:
After the script was modified, please install the OnEdit trigger to the function of installedOnEdit. In this script, when you put the value of Craig to a cell, the script below the if statement is run.
From:
function onEdit() {
var getProps = PropertiesService.getUserProperties();
var lenProp = getProps.getProperty('Len');
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Roster');
var data = sheet.getRange('E4:ACB562').getValues().toString().length;
if (data != lenProp) {
getProps.setProperty('Len', data );
MailApp.sendEmail('email#gmail.com', 'Changes have been made to your live roster', 'Previous value: ' + lenProp + ' New value: ' + data);
}
}
To:
function installedOnEdit(e) {
var range = e.range;
var value = range.getValue();
if (value == "Craig" || (e.oldValue == "Craig" && value == "")) {
var rowNumber = range.getRow(); // You can retrieve the row number of the editor cell.
console.log(rowNumber)
var getProps = PropertiesService.getUserProperties();
var lenProp = getProps.getProperty('Len');
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Roster');
var data = sheet.getRange('E4:ACB562').getValues().toString().length;
if (data != lenProp) {
getProps.setProperty('Len', data);
MailApp.sendEmail('email#gmail.com', 'Changes have been made to your live roster', 'Previous value: ' + lenProp + ' New value: ' + data);
}
}
}
In this modified script, when the value of Craig is put to a cell, the script below the if statement is run. If you want to also check the column and row, please tell me.
References:
Installable Triggers
Event Objects
This sheet does not get edited, but rather changes occur through IMPORTRANGE.
No events get triggered when a formula result changes, so I do not think that you can get an automatic notification when values get updated in the imported data.
To make it work, you will have to use a trigger in the source spreadsheet rather than the target spreadsheet.

google apps script to send recurring reminders doesn't work

I have a piece of code meant to send recurring emails every 8 weeks to a list of addresses in a google sheet.
It does not seem to be sending out the mails.
I have tried various examples that I found online, with no success
ScriptApp.newTrigger("sendEmails")
.timeBased()
.onWeekDay(ScriptApp.WeekDay.THURSDAY)
.atHour(11)
.nearMinute(00)
.everyWeeks(8)
.create();
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("EMAILS")
var startRow = 2;
var numRows = sheet.getRange(1,4).getValue();
var dataRange = sheet.getRange(startRow, 1, numRows, 2)
var data = dataRange.getValues();
for (i in data) {
var row = data[i];
var emailAddress = row[0];
var message = row[1];
var subject = "mail subject here";
MailApp.sendEmail(emailAddress, subject, message);
}
}
when I run the script manually it seems to be working fine, so I guess the problem is in the new trigger part?
Problem:
Currently you have the code in place but it won't be used at all because it is not part of a function.
Requirement:
Trigger to run code every 8 weeks.
Solution:
Separate your trigger builder into a separate function.
Run the following script, it'll delete any triggers you may have accidentally set up and create a new one that should run as you're expecting.
function newTrigger() {
//clear all triggers
var tg = ScriptApp.getProjectTriggers();
if(tg.length>0){
for(i=0;i<tg.length;i++){
ScriptApp.deleteTrigger(tg[i]);
}
}
//build new trigger
ScriptApp.newTrigger("sendEmails")
.timeBased()
.onWeekDay(ScriptApp.WeekDay.THURSDAY)
.atHour(11)
.nearMinute(00)
.everyWeeks(8)
.create();
}
Notes:
You'll only need to run this function once to set up the trigger.
In your project's triggers, it'll show as "every week" but should actually only run every 8 weeks like we specified in the code using .everyWeeks(8).
References:
Installable Triggers
Class ClockTriggerBuilder

Google form that turns on and off each day automatically

I love Google Forms I can play with them for hours. I have spent days trying to solve this one, searching for an answer. It is very much over my head. I have seen similar questions but none that seemed to have helped me get to an answer. We have a café where I work and I created a pre-order form on Google Forms. That was the easy part. The Café can only accept pre-orders up to 10:30am. I want the form to open at 7am and close at 10:30am everyday to stop people pre ordering when the café isn't able to deal with their order. I used the very helpful tutorial from http://labnol.org/?p=20707 to start me off I have added and messed it up and managed to get back to the below which is currently how it looks. It doesn't work and I can't get my head around it. At one point I managed to turn it off but I couldn't turn it back on!! I'm finding it very frustrating and any help in solving this would be amazing. To me it seems very simple as it just needs to turn on and off at a certain time every day. I don't know! Please help me someone?
FORM_OPEN_DATE = "7:00";
FORM_CLOSE_DATE = "10:30";
RESPONSE_COUNT = "";
/* Initialize the form, setup time based triggers */
function Initialize() {
deleteTriggers_();
if ((FORM_OPEN_DATE !== "7:00") &&
((new Date()).getTime("7:00") < parseDate_(FORM_OPEN_DATE).getTime ("7:00"))) {
closeForm("10:30");
ScriptApp.newTrigger("openForm")
.timeBased("7:00")
.at(parseDate_(FORM_OPEN_DATE))
.create(); }
if (FORM_CLOSE_DATE !== "10:30") {
ScriptApp.newTrigger("closeForm")
.timeBased("10:30")
.at(parseDate_(FORM_CLOSE_DATE))
.create(); }
if (RESPONSE_COUNT !== "") {
ScriptApp.newTrigger("checkLimit")
.forForm(FormApp.getActiveForm())
.onFormSubmit()
.create(); } }
/* Delete all existing Script Triggers */
function deleteTriggers_() {
var triggers = ScriptApp.getProjectTriggers();
for (var i in triggers) {
ScriptApp.deleteTrigger(triggers[i]);
}
}
/* Allow Google Form to Accept Responses */
function openForm() {
var form = FormApp.getActiveForm();
form.setAcceptingResponses(true);
informUser_("Your Google Form is now accepting responses");
}
/* Close the Google Form, Stop Accepting Reponses */
function closeForm() {
var form = FormApp.getActiveForm();
form.setAcceptingResponses(false);
deleteTriggers_();
informUser_("Your Google Form is no longer accepting responses");
}
/* If Total # of Form Responses >= Limit, Close Form */
function checkLimit() {
if (FormApp.getActiveForm().getResponses().length >= RESPONSE_COUNT ) {
closeForm();
}
}
/* Parse the Date for creating Time-Based Triggers */
function parseDate_(d) {
return new Date(d.substr(0,4), d.substr(5,2)-1,
d.substr(8,2), d.substr(11,2), d.substr(14,2));
}
I don't think you can use .timebased('7:00'); And it is good to check that you don't have a trigger before you try creating a new one so I like to do this. You can only specify that you want a trigger at a certain hour like say 7. The trigger will be randomly selected somewhere between 7 and 8. So you really can't pick 10:30 either. It has to be either 10 or 11. If you want more precision you may have to trigger your daily triggers early and then count some 5 minute triggers to get you closer to the mark. You'll have to wait to see where the daily triggers are placed in the hour first. Once they're set they don't change.
I've actually played around with the daily timers in a log by creating new ones until I get one that close enough to my desired time and then I turn the others off and keep that one. You have to be patient. As long as you id the trigger by the function name in the log you can change the function and keep the timer going.
Oh and I generally created the log file with drive notepad and then open it up whenever I want to view the log.
function formsOnOff()
{
if(!isTrigger('openForm'))
{
ScriptApp.newTrigger('openForm').timeBased().atHour(7).create()
}
if(!isTrigger('closeForm'))
{
ScriptApp.newTrigger('closeForm').timeBased().atHour(11)
}
}
function isTrigger(funcName)
{
var r=false;
if(funcName)
{
var allTriggers=ScriptApp.getProjectTriggers();
var allHandlers=[];
for(var i=0;i<allTriggers.length;i++)
{
allHandlers.push(allTriggers[i].getHandlerFunction());
}
if(allHandlers.indexOf(funcName)>-1)
{
r=true;
}
}
return r;
}
I sometimes run a log entry on my timers so that I can figure out exactly when they're happening.
function logEntry(entry,file)
{
var file = (typeof(file) != 'undefined')?file:'eventlog.txt';
var entry = (typeof(entry) != 'undefined')?entry:'No entry string provided.';
if(entry)
{
var ts = Utilities.formatDate(new Date(), "GMT-6", "yyyy-MM-dd' 'hh:mm:ss a");
var s = ts + ' - ' + entry + '\n';
myUtilities.saveFile(s, file, true);//this is part of a library that I created. But any save file function will do as long as your appending.
}
}
This is my utilities save file function. You have to provide defaultfilename and datafolderid.
function saveFile(datstr,filename,append)
{
var append = (typeof(append) !== 'undefined')? append : false;
var filename = (typeof(filename) !== 'undefined')? filename : DefaultFileName;
var datstr = (typeof(datstr) !== 'undefined')? datstr : '';
var folderID = (typeof(folderID) !== 'undefined')? folderID : DataFolderID;
var fldr = DriveApp.getFolderById(folderID);
var file = fldr.getFilesByName(filename);
var targetFound = false;
while(file.hasNext())
{
var fi = file.next();
var target = fi.getName();
if(target == filename)
{
if(append)
{
datstr = fi.getBlob().getDataAsString() + datstr;
}
targetFound = true;
fi.setContent(datstr);
}
}
if(!targetFound)
{
var create = fldr.createFile(filename, datstr);
if(create)
{
targetFound = true;
}
}
return targetFound;
}

How do I DumpLive results of a long running process?

I've tried Observable.Create
waits to finish before showing any results.
Possibly because the example I'm trying to follow is a changing live value, not a changing live collection.
and
ObservableCollection<FileAnalysisResult> fileAnalysisResults = new ObservableCollection<FileAnalysisResult>();
I can't seem to apply because .DumpLive() isn't applicable to an ObservableCollection.
Short answer: use LINQPad's DumpContainer:
var dc = new DumpContainer().Dump();
for (int i = 0; i < 100; i++)
{
dc.Content = i;
Thread.Sleep(100);
}
Long answer: DumpContainer writes to LINQPad's standard HTML results window, so you can see the value change in place while the main thread is blocked, whereas calling DumpLive on an IObservable uses a WPF control to render the updates, so the main thread must remain unblocked in order to see updates as they occur.
It's also possible to dump a WPF or Windows Forms control and update it in place:
var txt = new TextBox().Dump();
for (int i = 0; i < 100; i++)
{
txt.Text = i.ToString();
await Task.Delay(100);
}
Just as with DumpLive, you must be careful not to block the main thread. If you replaced await Task.Delay with Thread.Sleep, you'd block the UI thread and nothing would appear until the end.

A GMail Script to unstar email loop

I have emails in my inbox and ones that get archived throughout the day. Every night I want to create a script to automatically unstar them for the next day. I created this script but it doesn't seem to work. The Google docs don't seem to be much help in the way of syntax.
Here is the code I was using. Will this code access the archive as well?
function processInbox() {
var threads = GmailApp.getInboxThreads();
for (var i = 0; i < threads.length; i++) {
var firstThread = GmailApp.getInboxThreads(0,1)[0];
var message = firstThread.getMessages()[0];
GmailApp.unstarMessage(message);
}
};
You are working only on the first thread in your inbox.
GmailApp.getInboxThreads(0,1)[0];
You needed to put your 'i' variable in that line in order to iterate on the messages.
Try something like that:
// first limit the script for the top 50 emails (or a bit more) but don't run on ALL of them -it's not efficient.
var threads = GmailApp.getInboxThreads(0, 50);
for (var i = 0; i < threads.length; i++) {
var message = threads[i].getMessages()[0];
GmailApp.unstarMessage(message);
}
Good luck.