问题
Overview: the workbook calculates an Overall Score for each Record (or row) based on the Category Scores for each Record. Category Scores are derived from computations performed on hundreds of data points for each Record. I compiled code to send an email onChange called checkComplete (below) based on several examples and tutorials I found online. checkComplete would only send an email if the status of the Record changed to "Complete." An onChange trigger is required since the workbook uses Importrange to load the raw data, and no human interaction takes place for an onEdit trigger.
The problem began when I noticed an email would be sent upon any change being made, not just the status change in column O from a blank cell to "Complete". But the code worked well enough until I made significant edits to the Google Sheets workbook that included:
Modifying computational formulas to display a set number of decimals
e.g. =if(round('2-Comp'!F14,4)=0,"",round('2-Comp'!F14,4))
Moving columns around
e.g. switching columns N and O, where N was the original column checked by the onChange trigger
Updating source data, which therefore updated scores.
e.g. a Record's data point changed from $52,420 to $54,323
Then the email bombardment began. The net result is that the script began to send an email for every Record starting with the first record, and would generate the following error messages in succession:
Service invoked too many times for one day: email. (line 47, file "checkComplete")
Exceeded maximum execution time
Service using too much computer time for one day
I have since commented out MailApp.sendEmail() to cease blowing up my inbox, but the script continues to generate the "Exceeded maximum execution time" and "Service using too much computer time for one day". My thought was that the script was working off all the changes described above, but now that this problem has persisted for over a week I decided to seek out help.
I thought the issue is with the array, specifically var oldValues, so I tried:
to call flush() function to clear out the array, which did not solve the issue.
to empty the array as per this thread - How do I empty an array in JavaScript?, but that did not resolve the issue either.
I now think the problem may be related to the logger, but to be honest I am stumped.
Below is the script's source code in its entirety:
checkComplete.gs
var admin_email='xxxxxxx@xxxxxxx.com'; //<- list of email recipients goes here
function checkComplete() {
var sh = SpreadsheetApp.getActiveSheet();
var values = sh.getRange('O14:O').getValues().join('-');
if(PropertiesService.getScriptProperties().getKeys().length==0){ // first time you run the script
PropertiesService.getScriptProperties().setProperty('oldValues', values);
return;
}
var oldValues = PropertiesService.getScriptProperties().getProperty('oldValues').split('-');
var valuesArray = values.split('-');
while (valuesArray.length>oldValues.length){
oldValues.push('x'); // if you append some rows since last exec
}
Logger.log('oldValues = '+oldValues)
Logger.log('current values = '+valuesArray)
for(var n=0;n<valuesArray.length;n++){
if(oldValues[n] != valuesArray[n]){ // check for any difference
sendMail(n+1,valuesArray[n]);
}
}
PropertiesService.getScriptProperties().setProperty('oldValues', values);
}
function sendMail(row,val){
Logger.log('value changed on row '+row+' value = '+val+' , mail sent');
var rank = SpreadsheetApp.getActiveSheet().getRange(row,1).getValue();
// other var that I deleted here to shorten this post
// MailApp.sendEmail(admin_email,'message that calls vars');
}
I expect to receive an email only upon the change of a Record's status, indicated in column O, from a blank cell to "Complete". All other data updates should not trigger any email. Any advice/help is appreciated. Thank you for taking the time to review my problem.
回答1:
UPDATE
After specifying that the entries in Column O will be made by a human and will fire an
onEdit()
trigger, here is an easy and elegant solution making use of event objects:
onEdit(e){
if( e.range.getSheet().getName()=='Sheet1' &&
e.range.getColumn()==15&&e.value=="Complete"&&e.oldValue!="Complete"){
sendMail(e.range.getRow(),e.value);
}
}
You can replace
function checkComplete()
and its onChange trigger by thisonEdit
function.
After reviewing your spreadsheet I noticed following issues:
As mentioned in my comment, onEdit will be triggered by a change in any of the sheets within the spreadsheet, but you want the code to run only for changes in the sheet "Dashboard". And also, you do not want to compare the column O in
Dashboard
against the columns O in the other sheets - where all entries will be different and would trigger the call ofsendMail
. You should implement a condition statement to verify at the very beginning of"Dashboard
that you are in the right sheet, before continuing with the rest of the code.Your function
sendMail
contains
var rank = SpreadsheetApp.getActiveSheet().getRange(row,1).getValue();
var totalranked = SpreadsheetApp.getActiveSheet().getRange(4,1).getValue();
var address = SpreadsheetApp.getActiveSheet().getRange(row,2).getValue();
var score = SpreadsheetApp.getActiveSheet().getRange(row,6).getValue();
var outlook = SpreadsheetApp.getActiveSheet().getRange(row,14).getValue();
var ildemand = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('1-Dmnd').getRange(row+1,9).getValue();
var aldemand = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('1-Dmnd').getRange(row+1,14).getValue();
var mcdemand = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('1-Dmnd').getRange(row+1,19).getValue();
var increase = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('2-Comp').getRange(row,11).getValue();
var occupancy = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('2-Comp').getRange(row,13).getValue();
var medHHI = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('4-Demo').getRange(row+2,17).getValue();
var medHV = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('4-Demo').getRange(row+2,19).getValue();
var walkscore = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('3-Muni').getRange(row,19).getValue();
var dmnd = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('1-Dmnd').getRange(row+1,5).getValue()
var comp = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('2-Comp').getRange(row,5).getValue();
var muni = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('3-Muni').getRange(row,5).getValue();
var grade = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('3-Muni').getRange(row,17).getValue();
var demo = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('4-Demo').getRange(row+2,5).getValue();
This is 18 calls to SpreadsheetApp
for each call of sendMail
. And this multiplied by the amount of time sendMail
will be called if you compare columns O of two different sheets against each other (see 1.)! Be aware that any requests to external services are very time consuming and inefficient and should be avoided, see Best Practices. You should retrieve all those variables only once, outside of sendMail
. And given that after this the code of sendMail
will be short, you can implement it directly within your for
loop.
If a row is appended in the beginning rather than the end of the sheet - this will shift the new values against the old values and thus, each row will trigger the call of
sendMail
. You can avoid but checking either the new contents are contained within the array with the old contents with indexOf.Go from the Apps Script editor UI to
Edit->Current project's triggers
and check if you accidentally attached multiple triggers to the file, which all run simultaneously.
Taking in consideration the points 1., 2. and 3., here is an improvement suggestion for your code:
var admin_email=XXXXX; //<- list email addresses here
function checkComplete() {
var ss=SpreadsheetApp.getActive();
var sh = ss.getActiveSheet();
if(sh.getName()=="Dashboard"){
var values = sh.getRange('O14:O').getValues().join('-');
if(PropertiesService.getScriptProperties().getKeys().length==0){ // first time you run the script
PropertiesService.getScriptProperties().setProperty('oldValues', values);
return;
}
var oldValues = PropertiesService.getScriptProperties().getProperty('oldValues').split('-');
var valuesArray = values.split('-');
while (valuesArray.length>oldValues.length){
oldValues.push('x'); // if you append some rows since last exec
}
Logger.log('oldValues = '+oldValues)
Logger.log('current values = '+valuesArray)
for(var n=0;n<valuesArray.length;n++){
if(oldValues.indexOf(valuesArray[n])==-1){ // check for any difference
Logger.log('value changed on row '+n+1+' value = '+valuesArray[n]+' , mail sent');
sendMail(n+1,valuesArray[n]);
}
}
PropertiesService.getScriptProperties().setProperty('oldValues', values);
}
}
function sendMail(row,val){
Logger.log('value changed on row '+row+' value = '+val+' , mail sent');
var rank = sh.getRange(row,1).getValue();
var totalranked = sh.getRange(4,1).getValue();
var address = sh.getRange(row,2).getValue();
var score = sh.getRange(row,6).getValue();
var outlook = sh.getRange(row,14).getValue();
var ildemand = ss.getSheetByName('1-Dmnd').getRange(row+1,9).getValue();
var aldemand = ss.getSheetByName('1-Dmnd').getRange(row+1,14).getValue();
var mcdemand = ss.getSheetByName('1-Dmnd').getRange(row+1,19).getValue();
var increase = ss.getSheetByName('2-Comp').getRange(row,11).getValue();
var occupancy = ss.getSheetByName('2-Comp').getRange(row,13).getValue();
var medHHI = ss.getSheetByName('4-Demo').getRange(row+2,17).getValue();
var medHV = ss.getSheetByName('4-Demo').getRange(row+2,19).getValue();
var walkscore = ss.getSheetByName('3-Muni').getRange(row,19).getValue();
var dmnd = ss.getSheetByName('1-Dmnd').getRange(row+1,5).getValue()
var comp = ss.getSheetByName('2-Comp').getRange(row,5).getValue();
var muni = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('3-Muni').getRange(row,5).getValue();
var grade = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('3-Muni').getRange(row,17).getValue();
var demo = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('4-Demo').getRange(row+2,5).getValue();
// MailApp.sendEmail(admin_email,'EARLY results for '+address+' are ready!','EARLY says the Site is '+outlook+' and ranks # '+rank+' out of '+totalranked+' with an overall Score of '+score+'. The projected Net Demand is '+dmnd+' with: IL '+ildemand+' units, AL '+aldemand+' units and MC '+mcdemand+' units. The competitive environment is '+comp+' with an occupancy rate of '+occupancy*100+'% for nearby properties, and an expected supply increase of '+increase*100+'%. The location is '+walkscore+', and its public schools have a grade of '+grade+' making it '+muni+'. The demographic trends are '+demo+' with Median HHI of $'+medHHI+' and a median Home Value of $'+medHV+'. For more information, please visit https://docs.google.com/spreadsheets/d/1ydcXQilx6hxhI6HPpWTPT7Bq9a-gXPa7h8UC6KL9W8c/edit?usp=sharing.');
// add senior growth rate(s) to the Demographic summary sentence - Sept 3 2019
}
来源:https://stackoverflow.com/questions/57787791/how-to-fix-an-onchange-script-that-worked-perfectly-then-stopped-working-when-t