Google app script send email when sheet have no changes - google-apps-script

I'm trying to realize email notification at google spreadsheet, when sheet have no changes.
I'm tried to find smth opposite of onEdit() function.
I have email script, it fired by the trigger once at day. It's working nice.
function sendEmailAlert2() {
var ss = SpreadsheetApp.openById("MyID");
var sheetname = ss.getSheetByName("Sheet1");
var Toemail = 'email#gmail.com';
var subject = 'New Entry -' + ss.getName();
var body = 'Your file has a new entry in - ' + sheetname + ' Updated by - ' +
' check file- ' + ss.getUrl();
MailApp.sendEmail(Toemail,subject, body);
}
I need condition to this script when my sheet was no edited on this day.
Maybe someone have any idea?

You can achieve this by implementing a onEdit function which inserts a new time stamp into the sheet every time the file gets edited. And then, in your sendEmailAlert2() function, you can implement an if condition checking if the current day is equal to the day of last time stamp (i.e. the day of the last edit). If there was no edit today, the script email will be sent.
function onEdit(e) {
SpreadsheetApp.getActive().getActiveSheet()
.getRange('A1')
.setValue(new Date());
}
function sendEmailAlert2() {
var ss = SpreadsheetApp.openById("MyID");
var sheetname = ss.getSheetByName("Sheet1").getName();
var Toemail = 'email#gmail.com';
var subject = 'New Entry -' + ss.getName();
var body = 'Your file has a new entry in - ' + sheetname + ' Updated by - ' +
' check file- ' + ss.getUrl();
var NoEditSubject = 'No new Entry in' + ss.getName();
var NoEditBody = 'Your file has no new entry in - ' + sheetname + ss.getUrl();
if(sheetname.getRange('A1').getValue().getDay()==new Date().getDay())
{
MailApp.sendEmail(Toemail,subject, body);
}
else
{
MailApp.sendEmail(Toemail,NoEditSubject, NoEditBody);
}
}

On my side I would recommend you to use the DriveApp service and check if date Last Update is higher than 24h.
function getAlertedIfNoChange(){
var id = 'ID_SHEET';
var file = DriveApp.getFolderById(id);
var date = file.getLastUpdated();
var lastUpdated = date.getTime();
var today = (new Date()).getTime();
var delta = (24 * 60 * 60 * 1000)- (10*60*1000)
if((today - lastUpdated) > delta){
//Fire alert
Logger.log('No update during the last 24h')
}
}
For the 'delta' you have to set it to 24h in milliseconds and by experience I recommend you to remove some minutes, because when you set a trigger to run each day it will not fire at the exact same time. By removing some minutes to the 24h you are sure it will fire an alert.
You program a trigger to run each day and it will be ok.

Related

Selecting the day for a recurring event in CalendarApp on a spreadsheet

I want to have google sheets create a recurring event on google calendar.
Is there a way to make this section of code dependent on variables from a spreadsheet?
I would like to be able to select the days of the week on my spreadsheet instead of having to type them in.
.onlyOnWeekday([CalendarApp.Weekday.MONDAY,CalendarApp.Weekday.WEDNESDAY])
Here is my function
function newStudentCalendar() {
var app = SpreadsheetApp;
var sheet = app.getActiveSpreadsheet().getActiveSheet();
var studentName = sheet.getRange(2, 1).getValue();
// set time variables for lesson creation
var year = sheet.getRange(2, 5).getValue();
var month = sheet.getRange(2, 3).getValue();
var day1 = sheet.getRange(2, 4).getValue();
var startTime = sheet.getRange(2, 6).getValue();
var endTime = sheet.getRange(2, 7).getValue();
//Creates the new students calendar
var calendar = CalendarApp.createCalendar(studentName);
Logger.log('Created the calendar "%s", with the ID "%s".',
calendar.getName(), calendar.getId());
var calendarID = calendar.getId()
//Creates the recuring lessons
var lessons = CalendarApp.getCalendarById(calendarID).createEventSeries(studentName + " lesson",
new Date(month + day1 + ',' + year + ' ' + startTime),
new Date(month + day1 + ',' + year + ' ' + endTime),
CalendarApp.newRecurrence().addWeeklyRule()
.onlyOnWeekdays([CalendarApp.Weekday.MONDAY, CalendarApp.Weekday.WEDNESDAY]));
Logger.log('Event Series ID: ' + lessons.getId());
}
That is totally possible. In order to do so, simply add a line that reads a value from a specific cell. That specific cell will hold the name of the recurring day. Something like this:
var weekDay = SpreadsheetApp.getActive().getSheetByName('YOUR SHEET')
.getRange('YOUR_WEEKDAY_RANGE').getValue();
Afterwards, and assuming the weekday is properly typed and in capitals, you can run this code to create the event with the appropriate repeating day:
// This will throw an error if the weekday is not valid.
var weekDayObject = CalendarApp.Weekday[weekDay];
CalendarApp.newRecurrence().addWeeklyRule()
.onlyOnWeekdays([weekDayObject]));
Furthermore, if you would like to have multiple days in a cell, such as:
MONDAY, WEDNESDAY, FRIDAY.
You could use the following code:
var weekDays = SpreadsheetApp.getActive().getSheetByName('YOUR SHEET')
.getRange('YOUR_WEEKDAY_RANGE').getValue();
var days = weekDays.split(',').map(function(i) { return CalendarApp.Weekday[i]; });
CalendarApp.newRecurrence().addWeeklyRule()
.onlyOnWeekdays(days);
The general idea is that CalendarApp.Weekday is a regular Javascript/GAS object, and so you can access its properties both using the . operator or the [] operator.

Time-driven trigger (daily) not triggering every day

I have the similar issue as described at How to make sure a daily time trigger runs?.
I have a specific script in one of the Google sheets with a daily trigger (time-driven, should trigger every morning, set up through interface, not programmatically). But the script doesn't execute every day. I can see this in the execution report, where there're just successful executions and no failed ones. I can also see if the script executed by checking a cell in the sheet which gets updated with the execution timestamp when the script runs. And I've set up an immediate notification for the failed executions in the trigger settings.
In my specific case, the script should ran every day from Nov 9 - Nov 13, but it ran just on Nov 9, Nov 10, Nov 12. And I didn't get any notification about the failed execution.
The script itself doesn't use any API, it's pretty basic: reading data in one sheet, doing some calculation and writing to another sheet (talking about the sheets in single Google Sheet file).
If I run the main function manually, it always works.
I'd be very glad to get some ideas what could be wrong. Thanks.
EDIT: Code sample (main function and prototype for Array.includes)
function main(){
var date = new Date();
//var date = new Date(2019, 9, 1); // year, month (zero-indexed!!!), day
//var date = new Date(date.getYear(), date.getMonth()-3); // testing
var currentDay = Utilities.formatDate(date, "CET", "d");
Logger.log('currentDate: ' + Utilities.formatDate(date, "CET", "YYYY-MM-dd HH:mm:ss.S") + ' | currentDay: ' + currentDay);
if (currentDay == 1) {
Logger.log('currentDay is 1st of the month');
date = new Date(date.getYear(), date.getMonth() - 1);
var newCurrentDay = Utilities.formatDate(date, "CET", "d");
}
var monthToCheck = Utilities.formatDate(date, "CET", "MMMM").toUpperCase();
var yearToCheck = Utilities.formatDate(date, "CET", "YYYY");
Logger.log('dateToCheck: ' + Utilities.formatDate(date, "CET", "YYYY-MM-dd HH:mm:ss.S") + ' | monthToCheck: ' + monthToCheck + ' | yearToCheck: ' + yearToCheck);
var firstProjectRow = 7; // first row with the project data
var firstProjectCol = 1; // first column with project data - should contain Tool IDs
var numOfProjectRows = 999; // num of project rows to check (counted from and including var firstProjectRow)
var numOfProjectCols = 21; // num of project columns to check (counted from and including var firstProjectCol the last one contains number of hours for the last service)
var firstProjectHoursCol = 7; // first column with data about project hours (usually PM hours)
// ************* DO NOT EDIT BELOW THIS LINE ************* //
//return;
var indexedFirstProjectHoursCol = firstProjectHoursCol - 1;
var ss = SpreadsheetApp.getActiveSpreadsheet();
//var sheet = ss.getSheets()[3];
var sheetName = monthToCheck + ' ' + yearToCheck;
var sheet = ss.getSheetByName(sheetName);
Logger.log('sheet: ' + sheetName);
var range = sheet.getRange(firstProjectRow, firstProjectCol, numOfProjectRows, numOfProjectCols); // getRange(row, column, numRows, numColumns)
var rangeValues = range.getValues();
//Logger.log('rangeValues: "' + rangeValues);
var toolData = new Array();
var toolIds = new Array();
var toolHours = new Array();
//return;
for (var row in rangeValues) {
Logger.log('row: "' + row);
var clientId = rangeValues[row][0];
var projectId = rangeValues[row][1];
var hoursSum = 0;
// we have Tool ID so it's OK to proceed
if (clientId != "" && projectId != "") {
var clientProjectId = clientId + "-" + projectId;
for (var col in rangeValues[row]) {
var cellValue = rangeValues[row][col];
//Logger.log('col: ' + col + ' value: ' + value);
// get hours sum
if (col >= indexedFirstProjectHoursCol)
hoursSum += typeof cellValue == 'number' ? cellValue : 0;
}
//Logger.log('hoursSum: [' + hoursSum + ']');
var record = {id: clientProjectId, hours: hoursSum};
Logger.log("Data: " + record.id + " : " + record.hours);
// don't yet have a record of clientId-projectId
if (!toolIds.includes(clientProjectId)) {
toolData.push(record);
}
else {
recordIdx = toolIds.indexOf(clientProjectId);
toolData[recordIdx].hours += hoursSum;
}
toolIds = [];
toolHours = [];
toolData.forEach(function(item) {
toolIds.push(item.id);
toolHours.push(item.hours);
});
}
//Logger.log(toolData);
//Logger.log('ROW DONE!');
}
Logger.log('ROWS DONE!');
Logger.log('toolData.length: ' + toolData.length);
toolData.forEach(function(item) {
Logger.log('toolData: ' + item.id + " : " + item.hours);
});
Logger.log('DONE!!!');
// fill the table in the sheet with assigned number of hours
fillTheSheet(sheetName, toolData);
}
Apps Script triggers have always been a bit finicky. But of late they have been far more unreliable than usual (there have been several reports of spurious triggers and other maladies).
In this case, you can avoid using them altogether by leveraging an external service such as cron-jobs.org.
You'll have to refactor your app script project and deploy it as a public Web App with a doPost(e) function. You'd then pass the Web App's url to the external service as a web-hook endpoint that is invoked daily.

Updating different tab with a script from a pivot table????

I am new to coding scripts in google and come into a problem I can not work through and my script "mentor" is still not that experienced and can't figure it out either.
I am trying to update a different tab on my sheet from a pivot table when I use my script to send out bulked emails.
So the link below will bring you to the dummy sheet built off my real sheet. The 'PM' tab column L is what I am trying to update. It starts at 1 on all jobs. The emails are sent out by the Blue button on the next tab 'Follow up email' (this is just a pivot table so I can always adjust who is getting the emails easily). But I cant figure out how to have it update the 'PM' tab with the button at the same time as the emails go out.
Link to the open shared spreadsheet. Feel free to play around if you can help.
https://docs.google.com/spreadsheets/d/1_hipIj4suI2xMGUrZhMTBDvkQv9Y9O3JRNNQUpSeAP0/edit?usp=sharing
(only got the emails to send properly so far)
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2; // First row of data to process
var rows = sheet.getLastRow()
var dataRange = sheet.getRange(2, 1, rows-1, 7);
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (i in data) {
var row = data[i];
var emailAddress = row[1]; // Second column
var message = 'Hello, we have submitted this job ' + row[2] + ' days Ago. ' + row[4] + ' \n\n' + ' -' + row[5];
var subject = row[0]; // First column
MailApp.sendEmail(emailAddress, subject, message);
}
}
I need the button to send out emails on the 'Follow up email' tab and at the same time "email counter" (column L) would get 1 added to it on the 'PM' Tab. This way I can keep track of how many times that job was emailed from the sheet.
Try this code:
function sendEmails() {
var pmSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("PM");
var emailSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Follow up email");
var startRow = 2; // First row of data to process
var rows = emailSheet.getLastRow()
var dataRange = emailSheet.getRange(2, 1, rows-1, 7);
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (i in data) {
var row = data[i];
var emailAddress = row[1]; // Second column
var message = 'Hello, we have submitted this job ' + row[2] + ' days Ago. ' + row[4] + ' \n\n' + ' -' + row[5];
var subject = row[0]; // First column
MailApp.sendEmail(emailAddress, subject, message);
updatePM(pmSheet, emailAddress);
}
}
function updatePM(sheet, email){
var value;
var emails = sheet.getRange("G3:G" + sheet.getLastRow()).getValues();
for (var i = 0; i < emails.length; i++)
if (emails[i][0] == email){
value = sheet.getRange("K" + (i+3)).getValue() + 1
sheet.getRange("K" + (i+3)).setValue(value);
}
}
I changed the way you got the sheets just to be safe, then I just wrote a small function that gets called after the email is sent, which checks the list of emails in the PM sheet and then updates the value in the sent email column.

Google App Script Optimisation

Ok, I'm in need of some help optimising (where possible) and error checking my code.
My code has ran error free for 20+ weeks. Now all of a sudden, the script 'hangs' while executing the .setvalues on line 190. This is the section that archives the information.
Error received is "Service Timed Out : Spreadsheets" and "Exception: Service Error: Spreadsheets".
The Scripts runs between 2-3am on Sundays, when the servers should be less congested. The Script has also never timed out when running manually. I have not been able to replicate this error, even when tripling or quadrupling the working data.
So, I'll start.
My script runs in 4 sections.
Section 1 :
Validate information - Remove filters, Unhide Rows/columns and delete blank rows.
Section 2 :
Copy the selected sheet to a new spreadsheet and email this to selected users as an attachment in Excel format.
Section 3 :
Clear the data from the original sheet to prevent the possibility of duplication.
Section 4 :
This is the part that fails, TRY and paste the copied values into the archived spreadsheet.
Previously, there was no loop to re-attempt this. If it failed I would receive an email with the excel document.
The loop doesn't seem to be helping. Other than, it pasted half the information into my archive this weekend past.
If this helps, the data that is being moved is about 8000 rows and 15 columns, so about 120,000 cells. (Not that much)
If anyone can suggest any amendments or improvements, please feel free.
Full code below.
//******************** Menu Start ************************//
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Admin')
.addItem('Update to Raw', 'moveData')
.addSeparator()
.addSubMenu(ui.createMenu('Authorise')
.addItem('Authorise Scripts', 'Auth'))
.addToUi();
}
//******************** Menu End ************************//
//******************** Authorisation Start ************************//
function Auth(){
var email = Session.getActiveUser().getEmail();
var temp = new Date();
if (temp == "Blank") {
// These calls will never be visited
onOpen();
moveData();
clearData();
RemoveFilter();
DeleteBlankRows();
UnhideAllRowsAndColumns();
UnhideAllRowsAndColumnsRaw();
clearDataRaw();
} else {
Browser.msgBox("The Backup script has now been authorized for "+email+". Each user only has to do this once.");
}
}
//******************** Authorisation End ************************//
//******************** Clear Source Sheet Start ************************//
function clearData() {
var Spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var source = Spreadsheet.getSheetByName("Data");
source.deleteRows(2,source.getLastRow()-1);
}
//******************** Clear Source Sheet End ************************//
//******************** Copy Data Start ************************//
function ArchiveData() {
var Spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var source = Spreadsheet.getSheetByName("Data");
var targetkey = Spreadsheet.getSheetByName("Archive").getRange("C1").getValue();
var tSpreadsheet = SpreadsheetApp.openById(targetkey);
var target = tSpreadsheet.getSheetByName("Raw");
try{
// Information Quality Checks
RemoveFilter();
UnhideAllRowsAndColumns();
DeleteBlankRows();
var storedata = source.getRange(2,1,source.getLastRow(),source.getLastColumn()).getValues();
var sn = Spreadsheet.getName();
var URL = Spreadsheet.getUrl();
var message = URL;
var date = Utilities.formatDate(new Date(), "GMT+1", "dd-MM-yyyy HH:mm");
var subject = sn + " - Script Complete : " + date;
var emailTo = ["Recipient1#gmail.co.uk","Recipient2#gmail.co.uk",
"Recipient3#gmail.co.uk","Recipient4#gmail.co.uk","Recipient5#gmail.co.uk"];
// Google Sheets Extract Sheet Hack //
// Create a new Spreadsheet and copy the current sheet into it//
var newSpreadsheet = SpreadsheetApp.create("Call Log Script Export");
source.copyTo(newSpreadsheet);
newSpreadsheet.getSheetByName('Sheet1').activate();
newSpreadsheet.deleteActiveSheet();
// newSpreadsheet.getSheetByName('!Copied Sheet Name!').setName("Source Export") //
var ssID = newSpreadsheet.getId();
var url = "https://docs.google.com/spreadsheets/d/" + ssID + "/export?format=xlsx&id=" + ssID;
var requestData = {"method": "GET","headers":{"Authorization":"Bearer "+ScriptApp.getOAuthToken()}};
var result = UrlFetchApp.fetch(url , requestData);
var contents = result.getContent();
MailApp.sendEmail(emailTo, subject, message,
{attachments:[{fileName:"Call Log Script Export.xls", content:contents, mimeType:"application//xls"}]});
//------------------------- Move Data -------------------------//
var senddata = target.getRange(target.getLastRow()+1, 1, source.getLastRow(),source.getLastColumn() );
//------------------------- Clear Data Call -------------------------//
// ------------- Clears Source Sheet ------------- //
clearData();
var retryLimit = 4;
var retryDelay = 1000;
var retry;
for (retry = 0; retry <= retryLimit; retry++) {
try {
// do the spreadsheet operation that might fail
senddata.setValues(storedata);
// Delete the wasted sheet we created, so our Drive stays tidy
DriveApp.getFileById(ssID).setTrashed(true);
SpreadsheetApp.flush();
break;
}
catch (e) {
Logger.log('Failed on try ' + retry + ', exception: ' + e);
if (retry == retryLimit) {
throw e;
}
Utilities.sleep(retryDelay);
}
}
//------------------------- Copy Data Mid -------------------------//
}
//------------------------- Catch and Send Error Start -------------------------//
catch(err){
var error = err.lineNumber + ' - ' + err;
var URL = Spreadsheet.getUrl();
var sn = Spreadsheet.getName();
var date = Utilities.formatDate(new Date(), "GMT+1", "dd-MM-yyyy HH:mm");
var emailadd = ["Recipient1#gmail.co.uk","Recipient2#gmail.co.uk",
"Recipient3#gmail.co.uk","Recipient4#gmail.co.uk","Recipient5#gmail.co.uk"];
var subject = sn + " : Archive Script Error";
var body = URL + " - - - Date - - - " + date + " - - - Error Code - - - " + error
MailApp.sendEmail(emailadd,subject,body);
}
//------------------------- Catch and Send Error End -------------------------//
}
//******************** Copy Data End ************************//
//******************** Unhide Start ************************//
function UnhideAllRowsAndColumns() {
var Spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var source = Spreadsheet.getSheetByName("Data");
var fullSheetRange = source.getRange(1,1,source.getMaxRows(), source.getMaxColumns() )
source.unhideColumn( fullSheetRange );
source.unhideRow( fullSheetRange ) ;
}
//******************** Unhide End ************************//
//******************** Delete Blank Start ************************//
function DeleteBlankRows() {
var Spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var source = Spreadsheet.getSheetByName("Data");
var rows = source.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var rowsDeleted = 0;
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (row[1] == '') {
source.deleteRow((parseInt(i)+1) - rowsDeleted);
rowsDeleted++;
}
}
}
//******************** Delete Blank End ************************//
//******************** Remove Filter Start ************************//
function RemoveFilter(){
var Spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var source = Spreadsheet.getSheetByName("Data");
var row = 1 //the row with filter
var rowBefore = row
source.insertRowBefore(row); //inserts a line before the filter
row++;
var Line = source.getRange(row + ":" + row); //gets filter line in A1N
Line.moveTo(source.getRange(rowBefore + ":" + rowBefore)); //move to new line in A1N
source.deleteRow(row); //deletes the filter line
}
//******************** Remove Filter End ************************//
I looked over the code pretty thoroughly and I don't see any obvious problems. But I'm curious about this line:
var senddata = target.getRange(target.getLastRow()+1, 1, source.getLastRow(),source.getLastColumn() );
Since I can't see your data I can't confirm that the range height and width for senddata are the same as the dimensions of storedata and if they aren't then that could cause a problem.
"Service Timed Out : Spreadsheets"
error started happening on my script today and after a few trials what I could do to get away with that was to:
Make a full copy of the sheet (that contains script) and start using that sheet.
Cheers,
So here's what's bugging me:
var storedata = source.getRange(2,1,source.getLastRow(),source.getLastColumn()).getValues();
var senddata = target.getRange(target.getLastRow()+1, 1, source.getLastRow(),source.getLastColumn() );
storedata is an array that has to have the same dimensions as the range senddata.
number of rows in the storedata range is source.getLastRow()-1.
number of columns in the storedata range is source.getLastColumn() = 15
number of rows in senddata is source.getLastRow() - target.getLastRow() + 1
number of columns in senddate is source.getLastColumn() = 15
so:
source.getLastRow()-1 = source.getLastRow() - target.getLastRow() + 1
// add 1 to both sides
source.getLastRow() = source.getLastRow() - target.getLastRow() + 2
subtract source.getLastRow() from both sides
target.getLastRow() = 2 //is this true
Is that always true? Or am I just totally off the mark here.

Google App script onEdit?

really feel like I'm missing something with the Spreadsheet object scripts.
I'm trying to automatically email collaborators onEdit. I successfully emailed when explicitly runnign the script in test, but the onEdit event never seems to get fired (not seeing log messages even). Script seems pretty straightforward.
function onEdit(e) {
var sheet = e.source;
var viewers = sheet.getViewers();
var ct = viewers.length;
var recipients = [];
for(var i=0;i<ct;i++){
recipients.push(viewers[i].getEmail());
};
var subject = 'Update to '+sheet.getName();
var body = sheet.getName() + ' has been updated. Visit ' + sheet.getUrl() + ' to view the changes ' + e.range;
Logger.log('Running onedit');
MailApp.sendEmail(recipients, subject, body);
};
the simple onEdit function has a very limited set of possible actions since it runs without the authorization of the potential user. You have to create another function and set a specific trigger on this function.(installable trigger)
See this post as an example. It shows examples of simple onEdit and installable edit (for email send)
This is explained in the documentation here.
EDIT : here is your code working :
function sendAlert() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var cell = ss.getActiveCell().getA1Notation()
var viewers = ss.getViewers();
var ct = viewers.length;
var recipients = [];
for(var i=0;i<ct;i++){
recipients.push(viewers[i].getEmail());
};
var subject = 'Update to '+sheet.getName();
var body = sheet.getName() + ' has been updated. Visit ' + ss.getUrl() + ' to view the changes on cell ' + cell;
MailApp.sendEmail(recipients, subject, body);
};