I'm struggling to understand why this script is executing 2, 3, even 4 times simultaneously every time a form is submitted. I have added the LockService, but while it does sometimes keep multiple emails from being sent, it doesn't seem to prevent the simultaneous executions from occurring.
Everything else is working fine--I just don't want the failed executions every time, because these are also disrupting subsequent legitimate trigger executions due to script timeouts.
I have to execute from the sheet, because I am calling data that is retrieved to the sheet based on form responses. The sheet is not shared, and there are no "orphaned" triggers. I've removed/re-created the trigger, and even re-created the sheet and script but still getting extra executions.
Thanks in advance for any suggestions!
function zoneRepEmail(e) {
var lock = LockService.getScriptLock();
lock.waitLock(30000); // wait 30 seconds before conceding defeat.
// got the lock, you may now proceed
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('SupervisorApprovals');
var lookupRow = e.range.getRow();
var rowRange = sheet.getRange(1,1,sheet.getLastRow(),37);
var appEmailSentLog = rowRange.getCell(lookupRow,36);
var repEmailSentLog = rowRange.getCell(lookupRow,37);
Logger.log(appEmailSentLog);
var form = FormApp.openById('1efkzyAgHf3PjEnPXSXP4P5mTCnII_BQaf5jf2Apr8gM');
var startRow = 3;
var headers = sheet.getRange(2, 1, 1, sheet.getLastColumn()).getValues()[0];
var timestampColumn = sheet.getRange('K'+lookupRow);
var dateColumns = sheet.getRangeList(['X'+lookupRow, 'Y'+lookupRow]);
var pctColumns = sheet.getRange('Z'+lookupRow);
var phoneColumns = sheet.getRangeList(['P'+lookupRow,'Q'+lookupRow,'U'+lookupRow]);
var dataRange = sheet.getRange(lookupRow,1,sheet.getLastRow(),37);
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i){
var row = data[i];
var timestamp = e.namedValues["Timestamp"];
var columnOfEditUrl = 46;
var email_address = "email#email.com";
var columnOfHomeUnit = 44;
var columnOfAppId = 45;
var columnOfZoneRep = 47;
var columnOfZoneRepEmail = 48;
var colEmplLName = 2;
var emplLName = sheet.getRange(lookupRow, 12).getValue();
var colEmplFName = 3;
var emplFName = sheet.getRange(lookupRow, 13).getValue();
var emplMName = sheet.getRange(lookupRow, 14).getValue();
var locDisp = sheet.getRange(lookupRow, 15).getValue();
var empPhone = row[15];
var emplAltPhone = row[16];
var emplEmail = row[17];
var emplClass = row[18];
var emplSup = row[19];
var supPhone = row[20];
var supEmail = row[21];
var trainPosition = row[22];
var firstAssign = row[23];
var lastAssign = row[24];
var tbPercent = row[25];
var trainIMT = row[26];
var imtName = row[27];
var reqTraining = row[28];
var trainNeeded = row[29];
var appComments = row[30];
var homeUnit = row[31];
var appId = row[1];
var zoneRep = row[33];
var zoneRepEmail = sheet.getRange(lookupRow, 35).getValue();
var appStatus = row[6];
var statusReason = row[7];
var statusComment = row[8];
timestampColumn.setNumberFormat("mm/dd/yyyy hh:mm:ss");
pctColumns.setNumberFormat("###%");
dateColumns.setNumberFormat("mm/dd/yyyy");
phoneColumns.setNumberFormat("###-###-####");
var emailTo = emplEmail;
var message = "The following application to the Rocky Mountain Area Priority Trainee Program has been "+appStatus+" for "+emplFName+" "+emplMName+" "+emplLName+". Please carefully review all details in the application and update your zone sheet accordingly."+"\n"+"If corrections are required, please contact the applicant directly."+"\n"+"A status email has been forwarded to the applicant and supervisor listed."+"\n"+"\n"+"--------------------------------------------------------------"+"\n"+"\n"+"Applicant Name: "+emplFName+" "+emplMName+" "+emplLName+"\n"+"Application Reference ID: "+appId+"\n"+"Dispatch: "+locDisp+"\n"+"Employee Phone: "+empPhone+"\n"+"Employee Alternate Phone: "+emplAltPhone+"\n"+"Employee Email: "+emplEmail+"\n"+"Home Unit ID: "+homeUnit+"\n"+"Employment Class: "+emplClass+"\n"+"\n"+"Supervisor Name: "+emplSup+"\n"+"Supervisor Phone: "+supPhone+"\n"+"Supervisor Email: "+supEmail+"\n"+"\n"+"Trainee Position: "+trainPosition+"\n"+"Taskbook Percentage Complete: "+tbPercent+"\n"+"Taskbook First Assignment: "+firstAssign+"\n"+"\n"+"IMT Trainee: "+trainIMT+"\n"+"Assigned to IMT: "+imtName+"\n"+"\n"+"All Required Training Completed?: "+reqTraining+"\n"+"Training Still Needed for Position: "+trainNeeded+"\n"+"\n"+"Comments: "+appComments+"\n"+"\n"+"Application Status: "+appStatus+"\n"+"Reason for Denial (if applicable): "+statusReason+"\n"+"Supervisor Comments: "+statusComment;
var appMessage = "The following application to the Rocky Mountain Area Priority Trainee Program has been "+appStatus+" for "+emplFName+" "+emplMName+" "+emplLName+". The application has been forwarded to the Zone Training Representative for processing."+"\n"+"\n"+"--------------------------------------------------------------"+"\n"+"\n"+"Applicant Name: "+emplFName+" "+emplMName+" "+emplLName+"\n"+"Application Reference ID: "+appId+"\n"+"Dispatch: "+locDisp+"\n"+"Employee Phone: "+empPhone+"\n"+"Employee Alternate Phone: "+emplAltPhone+"\n"+"Employee Email: "+emplEmail+"\n"+"Home Unit ID: "+homeUnit+"\n"+"Employment Class: "+emplClass+"\n"+"\n"+"Supervisor Name: "+emplSup+"\n"+"Supervisor Phone: "+supPhone+"\n"+"Supervisor Email: "+supEmail+"\n"+"\n"+"Trainee Position: "+trainPosition+"\n"+"Taskbook Percentage Complete: "+tbPercent+"\n"+"Taskbook First Assignment: "+firstAssign+"\n"+"\n"+"IMT Trainee: "+trainIMT+"\n"+"Assigned to IMT: "+imtName+"\n"+"\n"+"All Required Training Completed?: "+reqTraining+"\n"+"Training Still Needed for Position: "+trainNeeded+"\n"+"\n"+"Comments: "+appComments+"\n"+"\n"+"Application Status: "+appStatus+"\n"+"Reason for Denial (if applicable): "+statusReason+"\n"+"Supervisor Comments: "+statusComment;
var subject = appStatus+": PT App for "+emplFName+" "+emplMName+" "+emplLName+" // "+trainPosition+" // "+timestamp;
Utilities.sleep(5000);//allow all column formats to be applied, then proceed.
if (repEmailSentLog.isBlank()){
GmailApp.sendEmail(zoneRepEmail, subject, message,{noReply:true});
repEmailSentLog.setValue('EmailSent');
}
if (appEmailSentLog.isBlank()){
GmailApp.sendEmail(emplEmail, subject, appMessage,{cc:supEmail,noReply:true});
appEmailSentLog.setValue('EmailSent');
}
}
lock.releaseLock(); Utilities.sleep(10000);
}
I'd definitely remove the line:
Utilities.sleep(10000);
There's no reason to wait.
If you want to avoid rate limiting errors caused by Apps Script restrictions, then put calls to services inside a for loop, and only wait if there is an error. That way the code isn't waiting if it doesn't need to, and only waiting if it needs to.
And I'd increase the timeout time. Normally, the code will release the lock, so a long timeout doesn't affect that situation, but if the timeout ends before the code is done, that could cause a problem.
And if there is an error, then release the lock. So, the following code adds a try/catch block to handle that.
Also, you should test for an obtained lock before proceeding, and if the lock isn't obtained, then quit and notify someone that there was an error.
function zoneRepEmail(e) {
try{
var lock = LockService.getScriptLock();
lock.waitLock(60000); // wait 60 seconds before timing out
if (!lock.hasLock()) {//Failed to get lock
MailApp.sendEmail(Session.getEffectiveUser().getEmail(),
'Code Failed', 'The code for ABC failed');
//Logger.log('Could not obtain lock');
return;
}
for (i=1;i<4;i++) {//Try up to 3 times
try{
//Rate limited service call code here
break;
}catch(e){
if (i!==3){Utilities.sleep(i*2000);}
if (i>=3) {
MailApp.sendEmail(Session.getEffectiveUser().getEmail(),
'Code Failed', 'The code for ABC failed - ' + e.message + "\n\n" +
e.stack);
}
};
}
//Code here
lock.releaseLock();
} catch(e) {
lock.releaseLock();//Release the lock if there is an error
MailApp.sendEmail(Session.getEffectiveUser().getEmail(),
'Code Failed', 'The code for ABC failed - ' + e.message + "\n\n" +
e.stack);
//Logger.log('error: ' + e.message + ' stack: ' + e.stack);
}
}
Related
Any ideas why I'm getting an "Exception: Failed to send email: no recipient (line 38, file "Code")" error? This is my script from Google Sheets. I get the popup asking if I want to send an email but it never sends. The macro compiles OnEdit but not on send.Emails. I do have correct data on line 150 in my Active Sheet.
function onEdit(e)
{
var sheet = SpreadsheetApp.getActiveSheet();
var editRange = sheet.getActiveRange();
var editRow = editRange.getRow();
var editCol = editRange.getColumn();
var monitorCol = 17 // Set this to the number column you want to monitor
if (editCol == monitorCol)
{
var response = Browser.msgBox('Notify User?', 'You have updated the status for this job, do you want to email user with this update?', Browser.Buttons.YES_NO);
// Process the user's response.
if (response == 'Yes') {
sendEmails()
}
}
}
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSheet();
var dataRange = sheet.getRange("A150:F150");
var data = dataRange.getValues();
data.forEach(function (rowData) {
var recipient2 = rowData[4];
var emailAddress = rowData[6];
var status = rowData[17];
if (status == 'Finished') {
return
}
var greeting = 'Hi ' + recipient2 + ',\n'
var finishedMessage = 'Your Makerspace project is complete. You can pick it up Wednesdays 11:00AM - 1:00PM or Friday 4:00PM - 5:30PM.';
var keepMakingMessage = 'Keep making!!';
var signatureMessage = 'UCA Makerspace Staff!!';
var message = [greeting, finishedMessage, keepMakingMessage, signatureMessage].join('\n');
var subject = 'Good news, your Makerspace project is complete';
MailApp.sendEmail(emailAddress, subject, message);
})
}
Data Range is: var dataRange = sheet.getRange("A150:F150"); six columns
var emailAddress = rowData[6]; is column 7
solution might be: var dataRange = sheet.getRange("A150:G150");
or
var emailAddress = rowData[5];
Not enough information to know which.
I presume you are also missing var status = rowData[17];
I am creating a simple email notification using 2 conditions in Google spreadsheet. I created for I function so that it will scan the rows I needed for my if condition. If challengeStatus and challengeSent are met, it will send an email notification. The email works fine, however, every time I run the function, it won't be finished running the code. Looks like there is a problem with my loop statement somewhere but can't figure it out. Here is my code please help.
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Challenges");
var lr= ss.getLastRow();
for (var i = 2;i<=lr;i++) {
var qaEmail = ss.getRange(i, 1).getValue()
var adminEmail = ss.getRange(i, 2).getValue()
var VideoId = ss.getRange(i, 3).getValue()
var QA1comment = ss.getRange(i, 10).getValue()
var QA2comment = ss.getRange(i, 11).getValue()
var QA3comment = ss.getRange(i, 12).getValue()
var challengeStatus = ss.getRange(i, 13).getValue()
var challengeSent = ss.getRange(i, 14).getValue()
var subject,mailbody
subject = "Challenge Result: ("+ VideoId + ")";
mailbody = 'Hi, ' +adminEmail+ ' <br><br>This email is to notify you that we have completed to process your dispute on Video Id: <b>' +VideoId+ '</b> and the result is <b>' +challengeStatus+ '</b>.<br><br> Scores will be updated based on the result. <br><br>Comments: <br><br> QA 1: ' +QA1comment+' <br><br>QA 2: ' +QA2comment+'<br><br> QA 3: ' +QA3comment+' <br><br>Thank you. <br><br>Quality Team';
if (challengeStatus =="Non Revert" && challengeSent ==""){
MailApp.sendEmail(adminEmail,subject," ",{
name: 'Quality Team (No Reply)',
cc: qaEmail,
htmlBody: mailbody,
});
//Logger.log(VideoId);
}
}
sendstatus = ss.getRange('N' + i).setValue('Sent')
}
I'm struggling to understand why this script is executing 2, 3, even 4 times simultaneously every time a form is submitted. I have added the LockService, but while it does sometimes keep multiple emails from being sent, it doesn't seem to prevent the simultaneous executions from occurring.
Everything else is working fine--I just don't want the failed executions every time, because these are also disrupting subsequent legitimate trigger executions due to script timeouts.
I have to execute from the sheet, because I am calling data that is retrieved to the sheet based on form responses. The sheet is not shared, and there are no "orphaned" triggers. I've removed/re-created the trigger, and even re-created the sheet and script but still getting extra executions.
Thanks in advance for any suggestions!
function zoneRepEmail(e) {
var lock = LockService.getScriptLock();
lock.waitLock(30000); // wait 30 seconds before conceding defeat.
// got the lock, you may now proceed
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('SupervisorApprovals');
var lookupRow = e.range.getRow();
var rowRange = sheet.getRange(1,1,sheet.getLastRow(),37);
var appEmailSentLog = rowRange.getCell(lookupRow,36);
var repEmailSentLog = rowRange.getCell(lookupRow,37);
Logger.log(appEmailSentLog);
var form = FormApp.openById('1efkzyAgHf3PjEnPXSXP4P5mTCnII_BQaf5jf2Apr8gM');
var startRow = 3;
var headers = sheet.getRange(2, 1, 1, sheet.getLastColumn()).getValues()[0];
var timestampColumn = sheet.getRange('K'+lookupRow);
var dateColumns = sheet.getRangeList(['X'+lookupRow, 'Y'+lookupRow]);
var pctColumns = sheet.getRange('Z'+lookupRow);
var phoneColumns = sheet.getRangeList(['P'+lookupRow,'Q'+lookupRow,'U'+lookupRow]);
var dataRange = sheet.getRange(lookupRow,1,sheet.getLastRow(),37);
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i){
var row = data[i];
var timestamp = e.namedValues["Timestamp"];
var columnOfEditUrl = 46;
var email_address = "email#email.com";
var columnOfHomeUnit = 44;
var columnOfAppId = 45;
var columnOfZoneRep = 47;
var columnOfZoneRepEmail = 48;
var colEmplLName = 2;
var emplLName = sheet.getRange(lookupRow, 12).getValue();
var colEmplFName = 3;
var emplFName = sheet.getRange(lookupRow, 13).getValue();
var emplMName = sheet.getRange(lookupRow, 14).getValue();
var locDisp = sheet.getRange(lookupRow, 15).getValue();
var empPhone = row[15];
var emplAltPhone = row[16];
var emplEmail = row[17];
var emplClass = row[18];
var emplSup = row[19];
var supPhone = row[20];
var supEmail = row[21];
var trainPosition = row[22];
var firstAssign = row[23];
var lastAssign = row[24];
var tbPercent = row[25];
var trainIMT = row[26];
var imtName = row[27];
var reqTraining = row[28];
var trainNeeded = row[29];
var appComments = row[30];
var homeUnit = row[31];
var appId = row[1];
var zoneRep = row[33];
var zoneRepEmail = sheet.getRange(lookupRow, 35).getValue();
var appStatus = row[6];
var statusReason = row[7];
var statusComment = row[8];
timestampColumn.setNumberFormat("mm/dd/yyyy hh:mm:ss");
pctColumns.setNumberFormat("###%");
dateColumns.setNumberFormat("mm/dd/yyyy");
phoneColumns.setNumberFormat("###-###-####");
var emailTo = emplEmail;
var message = "The following application to the Rocky Mountain Area Priority Trainee Program has been "+appStatus+" for "+emplFName+" "+emplMName+" "+emplLName+". Please carefully review all details in the application and update your zone sheet accordingly."+"\n"+"If corrections are required, please contact the applicant directly."+"\n"+"A status email has been forwarded to the applicant and supervisor listed."+"\n"+"\n"+"--------------------------------------------------------------"+"\n"+"\n"+"Applicant Name: "+emplFName+" "+emplMName+" "+emplLName+"\n"+"Application Reference ID: "+appId+"\n"+"Dispatch: "+locDisp+"\n"+"Employee Phone: "+empPhone+"\n"+"Employee Alternate Phone: "+emplAltPhone+"\n"+"Employee Email: "+emplEmail+"\n"+"Home Unit ID: "+homeUnit+"\n"+"Employment Class: "+emplClass+"\n"+"\n"+"Supervisor Name: "+emplSup+"\n"+"Supervisor Phone: "+supPhone+"\n"+"Supervisor Email: "+supEmail+"\n"+"\n"+"Trainee Position: "+trainPosition+"\n"+"Taskbook Percentage Complete: "+tbPercent+"\n"+"Taskbook First Assignment: "+firstAssign+"\n"+"\n"+"IMT Trainee: "+trainIMT+"\n"+"Assigned to IMT: "+imtName+"\n"+"\n"+"All Required Training Completed?: "+reqTraining+"\n"+"Training Still Needed for Position: "+trainNeeded+"\n"+"\n"+"Comments: "+appComments+"\n"+"\n"+"Application Status: "+appStatus+"\n"+"Reason for Denial (if applicable): "+statusReason+"\n"+"Supervisor Comments: "+statusComment;
var appMessage = "The following application to the Rocky Mountain Area Priority Trainee Program has been "+appStatus+" for "+emplFName+" "+emplMName+" "+emplLName+". The application has been forwarded to the Zone Training Representative for processing."+"\n"+"\n"+"--------------------------------------------------------------"+"\n"+"\n"+"Applicant Name: "+emplFName+" "+emplMName+" "+emplLName+"\n"+"Application Reference ID: "+appId+"\n"+"Dispatch: "+locDisp+"\n"+"Employee Phone: "+empPhone+"\n"+"Employee Alternate Phone: "+emplAltPhone+"\n"+"Employee Email: "+emplEmail+"\n"+"Home Unit ID: "+homeUnit+"\n"+"Employment Class: "+emplClass+"\n"+"\n"+"Supervisor Name: "+emplSup+"\n"+"Supervisor Phone: "+supPhone+"\n"+"Supervisor Email: "+supEmail+"\n"+"\n"+"Trainee Position: "+trainPosition+"\n"+"Taskbook Percentage Complete: "+tbPercent+"\n"+"Taskbook First Assignment: "+firstAssign+"\n"+"\n"+"IMT Trainee: "+trainIMT+"\n"+"Assigned to IMT: "+imtName+"\n"+"\n"+"All Required Training Completed?: "+reqTraining+"\n"+"Training Still Needed for Position: "+trainNeeded+"\n"+"\n"+"Comments: "+appComments+"\n"+"\n"+"Application Status: "+appStatus+"\n"+"Reason for Denial (if applicable): "+statusReason+"\n"+"Supervisor Comments: "+statusComment;
var subject = appStatus+": PT App for "+emplFName+" "+emplMName+" "+emplLName+" // "+trainPosition+" // "+timestamp;
Utilities.sleep(5000);//allow all column formats to be applied, then proceed.
if (repEmailSentLog.isBlank()){
GmailApp.sendEmail(zoneRepEmail, subject, message,{noReply:true});
repEmailSentLog.setValue('EmailSent');
}
if (appEmailSentLog.isBlank()){
GmailApp.sendEmail(emplEmail, subject, appMessage,{cc:supEmail,noReply:true});
appEmailSentLog.setValue('EmailSent');
}
}
lock.releaseLock(); Utilities.sleep(10000);
}
I'd definitely remove the line:
Utilities.sleep(10000);
There's no reason to wait.
If you want to avoid rate limiting errors caused by Apps Script restrictions, then put calls to services inside a for loop, and only wait if there is an error. That way the code isn't waiting if it doesn't need to, and only waiting if it needs to.
And I'd increase the timeout time. Normally, the code will release the lock, so a long timeout doesn't affect that situation, but if the timeout ends before the code is done, that could cause a problem.
And if there is an error, then release the lock. So, the following code adds a try/catch block to handle that.
Also, you should test for an obtained lock before proceeding, and if the lock isn't obtained, then quit and notify someone that there was an error.
function zoneRepEmail(e) {
try{
var lock = LockService.getScriptLock();
lock.waitLock(60000); // wait 60 seconds before timing out
if (!lock.hasLock()) {//Failed to get lock
MailApp.sendEmail(Session.getEffectiveUser().getEmail(),
'Code Failed', 'The code for ABC failed');
//Logger.log('Could not obtain lock');
return;
}
for (i=1;i<4;i++) {//Try up to 3 times
try{
//Rate limited service call code here
break;
}catch(e){
if (i!==3){Utilities.sleep(i*2000);}
if (i>=3) {
MailApp.sendEmail(Session.getEffectiveUser().getEmail(),
'Code Failed', 'The code for ABC failed - ' + e.message + "\n\n" +
e.stack);
}
};
}
//Code here
lock.releaseLock();
} catch(e) {
lock.releaseLock();//Release the lock if there is an error
MailApp.sendEmail(Session.getEffectiveUser().getEmail(),
'Code Failed', 'The code for ABC failed - ' + e.message + "\n\n" +
e.stack);
//Logger.log('error: ' + e.message + ' stack: ' + e.stack);
}
}
Google App script not firing all JavaScript pop up boxes using on edit trigger. Does not fire when a change is made to the sheet for multiple users. Also only works when it feels like it. I have set the trigger to onEdit(). My code:
function Error() {
var sheet = SpreadsheetApp.getActiveSheet();
if (sheet.getName() == "Protocal Check List 2"){
var ui = SpreadsheetApp.getUi(); // Same variations.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var active = ss.getActiveCell();
var getactive = ss.getActiveCell().getDisplayValue();
var column = ss.getActiveRange().getColumn();
var row = ss.getActiveRange().getRow();
var msg = '';
var section = sheet.getRange('C'+ row);
switch (column) {
case 9:
var msg = "error 1";
break;
case 10:
var msg = "Error 2";
break;
default:
msg = "Error";
break;
}
if(getactive == "TRUE"){
if(column >= 9 && column <= 60
){
var result = ui.alert(
"pika Says",
msg,
ui.ButtonSet.YES_NO);
// Process the user's response.
if (result == ui.Button.YES) {
// User clicked "Yes".
window.alert('Task Complete: \n\n' + msg);
active.setValue('True');
} else {
var i = 0;
while (i < 1) {
var result = ui.prompt(
'Please detail cause of the problem:',
ui.ButtonSet.OK);
var text = result.getResponseText();
var textcount = text.length;
if(textcount > 0) {
i++;
}}
var cell = "H" + row;
var emailAddress = "email#gmail.com";
var d = new Date();
var n = d.toDateString();
var t = d.toTimeString();
var staffname = ss.getRange(cell).getValues();
var message = "Date: " + n +"\n\nTime: " + t +"\n\nStaff: " + staffname + "\n\nError: " + msg + "\n\nProblem: " + text;
var subject = msg;
var thedate = n + " / " + t;
ss.getRange('A1').setValue(thedate);
ss.getRange('B1').setValue(staffname);
ss.getRange('C1').setValue(msg);
ss.getRange('D1').setValue(text);
var s1 = ss.getRange('A1:D1'); //assign the range you want to copy
var s1v = s1.getValues();
var tss = SpreadsheetApp.openById('1mOzdRgKxiP5iB9j7PqUWKKo5oymWuAeQZ1jJ1s6qL9E'); //replace with destination ID
var ts = tss.getSheetByName('AB Protocal'); //replace with destination Sheet tab name
ts.getRange(ts.getLastRow()+1, 1, 1,4).setValues(s1v); //you will need to define the size of the copied data see getRange()
MailApp.sendEmail(emailAddress, subject, message);
// User clicked "No" or X in the title bar.
//ui.alert("The following note has been sent to the Duty Manager: \n\n" + text);
active.setValue('FALSE');
}}}}
}
Any help would be appreciated. I need it to run every signal time for users who have access to the sheet.
You'll have to
assume that onEdit triggers are best-effort, but may not catch all
edits done to the spreadsheet.
Bug thread is here.
To get around the bug as mentioned by Edward implemente a dumb router.
// create a new file -> router.gs
function routerOnEdit(e) {
myOnEditHook1(e);
myOnEditHook2(e);
myOnEditHook3(e);
}
I have submitted an issue, which is the updated version of a long-running 7 year issue. They closed it in 2022, saying the feature is intended. They have now added a feature where onEdit can queue up to 2 trigger events. The issue I'm having is that the second edit still does not trigger. Despite what the documentation says.
Note: The onEdit() trigger only queues up to 2 trigger events.
View the new issue here: https://issuetracker.google.com/issues/221703754
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.