Google App Maker: date-driven email notifications - google-apps-script

I need to set up email notifications when the current date is 90, 60, and 30 days from an expiration date.
I've solved the problem (know basically what process is necessary) but am struggling to write the code. :/
Here's what I've pieced together from research. I think it's probably close, but I'm sure it's not correct.
Suggestions? Solutions?
function sendAlerts() {
var query = app.models.Customers.newQuery();
var today = new Date();
var expiresOn = new Date();
// query only for non-notified customers with upcoming due date
query.filters.NotificationAttempts._lessThan = ATTEMPTS_THRESHOLD;
query.filters.Notified._equals = false;
query.filters.DueDate._lessThan = expiresOn;
var customers = query.run();
customers.forEach(function(customer) {
var success = sendNotification(customer);
if (success) {
customer.Notified = true;
} else {
customer.NotificationAttempts++;
}
// performance trade off in favor of consistency
app.saveRecords([customer]);
});
function sendNotification_(to, subject, body)
{
MailApp.sendEmail({
to: 'beth#egaassociates.com',
subject: 'Expiration approaching',
body: 'You have a license, certification, or immunization expiring soon!',
noReply: true
});
}
}
/**
* Creates a time-driven trigger.
*/
function createTimeDrivenTriggers() {
// Trigger on set schedule.
ScriptApp.newTrigger('sendAlerts')
.timeBased()
.everyMinutes(2)
.create();
}
google.scripts.run.createTimeDrivenTriggers();

Related

google calendar api v3 get id of last modified event

I want to script behavior when people insert event in my calendar.
(e.g. when they add something into my "focus time"). I was able to connect an appscript "trigger" to call onEventUpdate. Sadly AppScript does not give you the event id for the event that was modified... (can you confirm the API does not offer this?).
So I tried to fetch the "last updated" events instead:
function getOptions() {
var now = new Date();
var yesterday = new Date();
yesterday.setDate(now.getDate() - 1);
console.log(yesterday.toISOString())
return {
updateMin: yesterday.toISOString(),
maxResults: 2,
orderBy: 'updated',
singleEvents: true,
showDeleted: false
}
}
function onEventUpdate() {
var options = getOptions();
var calendarId = Session.getActiveUser().getEmail();
// var calendar = CalendarApp.getCalendarById(calendarId);
// console.log(calendar.getName());
var events = Calendar.Events.list(calendarId, options);
if(!events.items) return
for (var i = 0; i < events.items.length; i++) {
var event = events.items[i];
console.log(event.summary + " # " + event.start['dateTime']);
}
}
Yet, I have just modified an event, but instead I am getting events from the past (i.e. August, 2mo ago...):
5:11:21 PM Notice Execution started
5:11:21 PM Info 2022-10-29T00:11:21.826Z
5:11:22 PM Info Old Meeting # 2022-08-08T17:00:00-07:00
5:11:22 PM Info Old Meeting # 2022-08-03T14:00:00-07:00
5:11:22 PM Notice Execution completed
Thoughts?
I believe your goal is as follows.
You want to retrieve the last updated event in a Google Calendar using Google Apps Script.
In the current stage, orderBy of "Events: list" can be used with only "ascending". When I saw your script, I thought that the reason for your current issue might be due to this. If "descending" was used with orderBy, your script might be able to be used. But, in the current stage, it seems that this cannot be used.
So, in the current stage, in order to retrieve the last updated event, I thought that it is required to retrieve all events with your getOptions(), and the last element is required to be retrieved. When this is reflected in your script, how about the following modification?
Modified script:
function getOptions() {
var now = new Date();
var yesterday = new Date();
yesterday.setDate(now.getDate() - 1);
console.log(yesterday.toISOString())
return {
updatedMin: yesterday.toISOString(),
maxResults: 2500, // Modified
orderBy: 'updated',
singleEvents: true,
showDeleted: false
}
}
function onEventUpdate() {
var options = getOptions();
var calendarId = Session.getActiveUser().getEmail();
// I modified the below script.
var eventList = [];
var pageToken = null;
do {
options.pageToken = pageToken;
var events = Calendar.Events.list(calendarId, options);
if (events.items.length > 0) {
eventList = [...eventList, ...events.items];
}
pageToken = events.nextPageToken;
} while (pageToken);
var lastUpdatedEvent = eventList.pop(); // You can retrieve the last updated event.
var lastUpdatedEventId = lastUpdatedEvent.id; // You can retrieve the event ID of the last updated event.
}
Note:
About I was able to connect an appscript "trigger" to call onEventUpdate. Sadly AppScript does not give you the event id for the event that was modified, how about reporting this to the Google issue tracker as a future request?
Reference:
Events: list
Alright, I achieved what I wanted to (thanks for spotting my typo). I take some risk, as I assume there is no more than 50 edited events in the last hour (although risk could be reduced by making this delta smaller).
This is what the overly-convoluted way appscript needs (i.e. they could have just given me a calendar-event object as an argument to the trigger? oh well)
This is just a simple example, as now I can programmatically edit my calendar at will :)
function getOptions() {
var now = new Date();
TIME_DIFF = 60 * 60 * 1000;
var earlier = new Date(now.getTime() - TIME_DIFF)
return {
updatedMin: earlier.toISOString(),
maxResults: 50,
orderBy: 'updated',
singleEvents: true,
showDeleted: false
}
}
function getLastEditedEvent() {
// returns https://developers.google.com/apps-script/reference/calendar/calendar-event
var options = getOptions();
var calendarId = Session.getActiveUser().getEmail();
var events = Calendar.Events.list(calendarId, options);
if(!events.items) return undefined;
var _event = events.items[events.items.length-1];
return CalendarApp.getEventById(_event.id);
}
function onEventUpdate() {
// sadly event update contains no information
var event = getLastEditedEvent()
if(event == undefined) return;
console.log('Modifying: ' + event.getTitle() + ' # ' + event.getStartTime());
event.setColor(CalendarApp.EventColor.PALE_BLUE);
}

Listing Google calendar events using API

I am trying to write a Google Apps script to modify calendar events so I have modified an example to list events first. When I try debugging this it reports an error that "Calendar is not defined" on the line "events = Calendar.Events.list(calendarId, options);"
I have enabled the advanced calendar API, and am basing my script on one from the Google documentation, so I assume that one worked. Is there anything else I need to do to access the relevant objects and methods?
/*
Adapted from code in https://developers.google.com/apps-script/advanced/calendar
*/
function syncColourCode() {
var calendarId = CalendarApp.getDefaultCalendar();
var properties = PropertiesService.getUserProperties();
var fullSync = properties.getProperty('fullSync'); // sync status is stored in user properties
var options = {
maxResults: 100
};
var syncToken = properties.getProperty('syncToken'); // pointer token from last sync also stored in user properties
if (syncToken && !fullSync) { // if there is a sync token from last time and sync status has not been set to full sync
options.syncToken = syncToken; // adds the current sync token to the list of sync options
} else {
// Sync events from today onwards.
options.timeMin = new Date().toISOString(); //change to new Date().toISOString() from getRelativeDate(-1, 0).toISOString()
}
// Retrieve events one page at a time.
var events;
var pageToken;
do {
try {
options.pageToken = pageToken;
events = Calendar.Events.list(calendarId, options);
} catch (e) {
Not a google-apps expert, but from reviewing the code, I see a possible problem. At no point do I see your code checking to see if getDefaultCalendar() actually returned a valid calendar ID. Later your code uses that ID under the assumption that it is good. Have you checked the value of calendarId that is returned?
Sometimes you have to read a little deeper into the message, but I always try to start with trusting the error return. In this case "Calendar is not defined" makes me question the value of calendarId.
It seem that Google made some change so that there is no Calendar reference from the AppScript API.
Anyway to get the event you may use this API:
CalendarApp.getEvents(startTime, endTime)
https://developers.google.com/apps-script/reference/calendar/calendar-app#geteventsstarttime-endtime
Below are my example function running within google sheet.
function listEventsWithinTwoMonth(){
var calendar = CalendarApp.getDefaultCalendar();
var spreadsheet = SpreadsheetApp.getActiveSheet();
var now = new Date();
var twoMonthFromNow = new Date(now.getTime() + (24 * 60 * 60 * 30 * 4 * 1000));
var events = calendar.getEvents(now, twoMonthFromNow);
if (events.length > 0) {
// Header Rows
spreadsheet.appendRow(["#่","id","StartTime","EndTime","Title","Description"]);
for (i = 0; i < events.length; i++) {
var event = events[i];
Logger.log("" + (i+1) + event.getId() +" "+ event.getStartTime()+" "+event.getEndTime()+" "+event.getTitle()+" "+event.getDescription())
spreadsheet.appendRow([(i+1),event.getId(),event.getStartTime(),event.getEndTime(),event.getTitle(),event.getDescription()]);
}
} else {
Logger.log('No upcoming events found.');
}
}
Hope this help.
CalendarApp.getDefaultCalendar() retrieves an object of the class Calendar that has multiple properties, among others an Id.
To retrieve the calendar Id, you need to define
var calendarId = CalendarApp.getDefaultCalendar().getId();

Google API Apps Script Calendar Create Calendar Event Succeeds but Delete Calendar Event Fails

These functions work on our test Google Corporate Calendars but fail on our production Google Corporate Calendars. In production, the function, createCalendarEvent, works but the function, deleteCalendarEvent, fails and no error is returned. I am using OpenID connect with the same user and same apps script. The security access for the user has been verified to be the same on all calendars. Here are the functions:
function createCalendarEvent(calendarId, startDate, endDate, eventTitle, eventDescription) {
var cal = CalendarApp.getCalendarById(calendarId);
var start = new Date(startDate);
var end = new Date(endDate);
var options = {
description: eventDescription,
etags: {
"title": eventTitle,
"start": start,
"end": end
}
}
var event = cal.createAllDayEvent(eventTitle, start, end, options);
return event.getId();
}
function deleteCalendarEvent(calendarId, eventId) {
var cal = CalendarApp.getCalendarById(calendarId);
var event = cal.getEventById(eventId);
event.deleteEvent();
}
You could do something like this to insure that you have all parameters.
function deleteCalendarEvent(calendarId, eventId) {
if(calendarId && eventId){
CalendarApp.getCalendarById(calendarId).getEventById(eventId).deleteEvent();
}else{
throw('Error: in function deleteCalendarEvent. Invalid parameters.');
}
}
I'd change your create function to:
function createCalendarEvent(calendarId, startDate, endDate, eventTitle, eventDescription) {
var cal = CalendarApp.getCalendarById(calendarId);
var start = new Date(startDate);
var end = new Date(endDate);
var options = {"description": eventDescription};
var event = cal.createAllDayEvent(eventTitle, start, end, options);
return event.getId();
}
And again you might want to wrap them with logic to insure that you have all parameters.

Trigger on calculated date on Google AppMaker

I have function called alertnotice(to, message, body) that will be executed on a user onClick() event. The function will execute sendEmail(to, message, body) to send the email base on a calculated trigger as in variable triggerDay such as below:
function alertnotice(to, subject, body, dueDate, notifyBeforeDay) {//start of this class
function sendEmail(to, subject, body){
try {
MailApp.sendEmail({
to: to,
subject: subject,
htmlBody: body
});
} catch(e) {
console.error(JSON.stringify(e));
}
}
//check if there is an existing trigger for this process
var existingTrigger = PropertiesService.getScriptProperties().getProperty("sendEmailTrigger");
//set the renewal notice day
var triggerDay = (dueDate - notifyBeforeDay) / (1000*60*60*24);
//if the trigger already exists, inform user about it
if(existingTrigger) {
return "Alert notice had been sent";
} else { // if the trigger does not exists, continue to set the trigger to send alert notice
//runs the script every day at 1am on the time zone specified
var newTrigger = ScriptApp.newTrigger('sendEmail')
.timeBased()
.atTime(triggerDay)
.create();
var triggerId = newTrigger.getUniqueId();
if(triggerId) {
PropertiesService.getScriptProperties().setProperty("autoExportTrigger", triggerId);
return "Alert notice send successfully!";
} else {
return "Failed to send alert notice. Try again please";
}
}
}//end of this class
So for example, if the dueDate is 30/07/2018 and the notifyBeforeDay = 30, the function should send the email 30 days before the due date. I tried to achieve that but not so sure whether my algorithm will work. Can anyone give advice on this?
This implementation looks fragile to me. I would rather go with single trigger to avoid any possible duplicated emails and ensure at least one. Smth like this:
// I am server script, trigger me on schedule (for instance nightly)
function sendAlerts() {
var query = app.models.Customers.newQuery();
// query only for non-notified customers with upcoming due date
query.filters.NorificationAttempts._lessThan = ATTEMPTS_THRESHOLD;
query.filters.Notified._equals = false;
query.filters.DueDate._lessThan = <dueDate>;
var customers = query.run();
customers.forEach(function(customer) {
var success = sendNotification(customer);
if (success) {
customer.Notified = true;
} else {
customer.NorificationAttempts++;
}
// performance trade off in favor of consistency
app.saveRecords([customer]);
});
}
Trigger for such script can be installed by app's admin, you can find similar implementation in People Skills template.

Are expired time-based triggers deleted automatically, or should they be manually removed?

I set triggers in Google Apps Script programmatically to defer fetching data for some minutes while the job is being processed elsewhere.
I have noted that in Script Editor's "view current script's triggers" old (expired) triggers persist - I suspect that they also count against the max amount of triggers limit.
Will Google Apps Script delete these expired triggers or do I have to go through the list of triggers and delete expired ones? If yes, does somebody want to share a code example? (This should also be included in the docs, I suggest).
I create triggers by using .at
var d = new Date();
d.setMinutes(d.getMinutes() + 5);
try {
ScriptApp.newTrigger("retrieveOrder")
.timeBased()
.at(d)
.create();
} catch(e) {
sendErrorMail(e + " \n \n" + "Date: " + d);
return false;
}
If you know where it's being called from then you can just delete by handler function ...
The function "updateGmailPhotoFromDriveBatcher" is called then calls itself
// this is called by a daily trigger. I don't want it deleted
function runPhotoUpdates() {
// NEEDS to be a separate fnc so it doesn't get deleted
updateGmailPhotoFromDriveBatcher()
}
// this function does a job, then reschedules itself until a blank is returned
function updateGmailPhotoFromDriveBatcher() {
var ss = SpreadsheetApp.getActiveSpreadsheet(); //fetches the spreadsheet that the script is running in
var sheet = ss.getSheetByName("Groups");
var sgfrom = sheet.getRange(10, 21).getValue();
var npt = sheet.getRange(11, 21).getValue();
var oneJob = oneBatch(npt,sgfrom)
var currTime = (new Date()).getTime();
// WRITE THE new values; oneJob[0] is blank when completed; npt is a next page token of something that times out if you run the whole user base
sheet.getRange(10, 21).setValue(oneJob[1]);
sheet.getRange(11, 21).setValue(oneJob[0]);
sheet.getRange(12, 21).setValue(oneJob[2]);
sheet.getRange(13, 21).setValue(currTime)
SpreadsheetApp.flush();
if (oneJob[0] != "") {
// SCHEDULE NEW JOB
ScriptApp.newTrigger("updateGmailPhotoFromDriveBatcher")
.timeBased()
.at(new Date(currTime+(20*1000))) // 20 seconds from NOW
.create();
} else { // FINISHED
// KILL TRIGGERS
deleteTriggers()
}
}
function deleteTriggers() { // cleans up the triggers made above
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < triggers.length; i++) {
var thf = triggers[i].getHandlerFunction() //String Returns the function that will be called when the trigger fires.
// if it's the one I kept making above
if (thf == "updateGmailPhotoFromDriveBatcher") {
ScriptApp.deleteTrigger(triggers[i]);
}
}
}
You had to delete the triggers. Look at the docs for an example
I chucked the Trigger's unique id along with a timestamp of when it is set to expire into the ScriptDb. Then I clear it with this cludge:
/**
* #param {ScriptDbInstance} db
*/
function clearExpiredTimeTriggers(db){
var now = new Date();
var nts = now.getTime();
var res = db.query({trigId:db.anyValue(), trigExpiration: db.lessThan(nts)});
var expiredTriggers = [];
while (res.hasNext()){
var item = res.next();
expiredTriggers.push(item.trigId);
db.remove(item);
}
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < triggers.length; i++) {
if (expiredTriggers.indexOf(triggers[i].getUniqueId()) > -1){
ScriptApp.deleteTrigger(triggers[i]);
}
}
}