How to prevent trigger from activating multiple times? - google-apps-script

'On Edit' triggering multiple email responses
I have a code that sends an email once column W is filled with an email address (which occurs when somebody edits column U in that same row). It is an 'On Edit' trigger. However, when I enter something in column Y it sends another identical email. I understand it is probably because the trigger is 'On Edit' and it is responding to an edit in that row, but how do I make it only send an email when column W is the one that changes?
function confirmation() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var responses = spreadsheet.getActiveSheet();
var range = responses.getActiveCell();
var relevantRow = range.getRow()
responses.getRange(relevantRow,22).setFormula('=if(U'+relevantRow+'="","","Changes Made")');
responses.getRange(relevantRow,23).setFormula('=if(U'+relevantRow+'="","",C' + relevantRow + ')');
var email = responses.getRange(relevantRow,23).getValue();
var user = responses.getRange(relevantRow,2).getValue();
var emailBody = 'Hi' + ' ' + user + '\n\nThe online banking request you have submitted has been approved, and all necessary changes have been made. Please check and make sure that you are having no issues. If you are still having issues, please reach out to the appropriate bank admin email box. \n\nAPAC: APAC.bankadmin#nielsen.com \nEMEA: EMEA.bankadmin#nielsen.com \nNAM/LATAM: NAM.bankadmin#nielsen.com. \n\nThank you, \nNielsen Corporate Treasury';
var subject = 'Online Banking Request Completed'
MailApp.sendEmail(email, subject, emailBody) ;
}

function onEdit(e) {} has e (event) argument. You can consider e.range member to check where did the edit operation take place.
if (e.range.getColumn() == 23) { // W column is 23-th
// do something
}
Any code in place "do something" will be triggered only if a cell in column W was edited.

Related

Getting email notification via Google Sheets when =importxml cell value changes

I'm working on a spreadsheet that uses multiple =IMPORTXML functions to import changing text and price values from a webpage. At this moment I have the following columns in my Google Sheet:
A: 'URL info'
B: 'URL'
F-N: 'Price' (in every column a different price value)
What I have
Via a script, found on this page (thank you Umesh Agarwal) I will receive an email notification once a change has been made within the spreadsheet. Once I make a change in a cell within the range of F2:N200 I will receive an email with the cell that have been changed. The problem is that I have the script to sent me an email with the changed cell once the cell with a value of the =importxml function is changing.
At this moment, when a cell is changing due to the =importxml function the script is sending me an email that cell A1 has changed... it is not sending me the right cell that has been changed which makes it difficult to see what changed. How can I solve this problem?
function sendEmailonEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var cellValue = ss.getActiveSheet().getActiveRange().getA1Notation();
var getColumn = ss.getActiveSheet().getActiveRange().getColumn();
var sheetname = ss.getActiveSheet().getName();
var user = Session.getActiveUser().getEmail();
var Toemail = 'myemailid_1234#gmail.com';
var subject = 'New Entry in ' + data + '.' + ss.getName();
var body = 'Your file has a new entry in - ' + sheetname + ' Updated by - ' + user + data
' check file- ' + ss.getUrl();
if(data.indexOf('F2:N200')!=-1.23456789) {
MailApp.sendEmail(Toemail,subject, body);
}
};
I'm afraid this is not possible with triggers the way you have it set up.
For the trigger to fire when a formula is pulling data from an external source "On change" is the only trigger that will pick up the change. Unfortunately, it won't return which cell or value has changed, it will only return which sheet has changed.
The other alternative trigger you might run into is "on Edit", however, this trigger will not fire when the sheet is updated by formulas pulling data from an external source ¯\_(ツ)_/¯
Avenue for possible workaround:
You might be able to work around this with a Time-driven trigger AKA clock trigger. Writing a script that fires every so often to check for changes in the worksheet, and to send an e-mail if it does. You might copy all the data to another sheet and then compare the values, or use the Properties Service, to persist data within the script.

onSubmit Trigger Executed Without Form Submission

I created a Google Form, linked to a Sheet to capture responses, and added an Apps Script that runs each time the Form is submitted. Ran through a bunch of tests and everything was working fine - form responses fed through, onSubmit function working great. Last night, though, we received a few executions of the script even though the Form was not submitted.
Looking at the Responses page on the Form itself, there were no submissions when I got the notification. Also, this is not a public Form in my organization, and only one other person has the link besides myself. He confirmed he didn't submit the form at the time of the executions.
There are two sheets in the Google Sheet: 1> Form Responses and 2> Data. The data sheet uses a few QUERY functions to pull data from the responses sheet, formatting it differently (e.g. putting hyphens in phone numbers, rendering some fields in upper case, etc.). Also, the data sheet headers are labeled differently than the Form questions (e.g. 'homeAdd1' instead of 'Home Address Line 1'). This is because the script creates a PDF, using the Form responses to replace placeholders ('%homeAdd1%') on a template Google Doc. The script then takes the generated PDF and emails it to the submitter.
Again, everything was working great until yesterday's testing. I didn't realize it at the time, but when my colleague was inputting random values to test the Form, for the Home Address Line 2 he only input a 5-digit ZIP code. It generated a PDF fine, and also emailed it to him, but this caused the QUERY function to render a #VALUE error. The functions look like this:
=QUERY(Responses!L2:S,"SELECT UPPER(L) UPPER(M)...
So when Sheets saw a cell with just 5 digits, it automatically rendered it as a number, and UPPER doesn't work on number values. I (stupidly) didn't think to pre-format all of both sheets as plain text, so this occurred.
Would a #VALUE error on a Google Sheet linked to a Form and an Apps Script cause a misfire of the onSubmit function? This is the only thing I can see that could have possibly caused it, but it doesn't make sense. I've fixed the formatting issue, but I don't know if an erroneous execution could mean some other issue.
With the extra submissions, the script just sent the most recent PDF again and again. Within 20 seconds, it fired 5 times, sending the last PDF that was generated via email each time. Looking at the Stackdriver logs, there's nothing different from when we were testing it earlier yesterday. The console.log and console.info commands work fine, and they all come through listed as having been triggered by the onSubmit function.
Here's the script:
Submit function:
function onSubmit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Data');
var list = ss.getRange("A1:A").getValues();
var row = list.filter(String).length;
var email = ss.getRange(row,2).getValue();
var newResponse = ss.getRange(row,3).getValue();
if (newResponse == 'Generate New') {
newOne(ss,row,email);
} else if (newResponse == 'Upload Completed') {
completed(ss,row,email);
} else {
}
}
Function that was executed:
function newOne(ss,row,email) {
var name = ss.getRange(row,4).getValue();
console.log('Function Start - ' + name);
var newType = ss.getRange(row,6).getValue();
var copyFile = DriveApp.getFileById('[file id]').makeCopy();
var copyDoc = DocumentApp.openById(copyFile.getId());
var copyBody = copyDoc.getActiveSection();
// Replacing variables with values on spreadsheet
console.log('Create file start - ' + name);
var newInfo = ss.getRange(row, 1, 1, 29).getDisplayValues();
var header = ss.getRange(1, 1, 1, 29).getDisplayValues();
for (var i = 1; i <= 5; i++) {
copyBody.replaceText('%' + header[0][i] + '%', newInfo[0][i].toString());
}
var x;
if (newType == 'Office 1') {
x = 6;
} else if (newType == 'Office 2') {
x = 15;
} else {
}
for (var i = x; i <= (x + 8); i++) {
copyBody.replaceText('%' + header[0][i] + '%', newInfo[0][i].toString());
}
copyBody.replaceText('%' + header[0][26] + '%', newInfo[0][26].toString());
// Create the PDF file, rename it, and delete the doc copy
copyDoc.saveAndClose();
var newFile = DriveApp.createFile(copyFile.getAs('application/pdf'));
newFile.setName('New - ' + name + '.pdf');
copyFile.setTrashed(true);
console.log('Create file finished - ' + name);
//Mails PDF to submitter
console.info('Pre-email log for ' + name);
MailApp.sendEmail(email,'Email Subject','', {
noReply: true,
htmlBody: "<body>Hello, and thank you.</body>",
attachments: [newFile]
});
console.info('Email sent for ' + name);
appFile.setTrashed(true);
}
Any insight / help would be appreciated; thanks!
Josh
Spurious unwanted Event Triggers
I've had problems with spurious triggers coming from onFormSubmit event triggers. In my case they were always immediately after a real trigger occurred from a Form Submission. I found that I could identify them because none of my required questions were answered. I discuss it here.
It might be worth your time to capture the e.values array and see if you can find a consistent way to keep them from causing a misfire of your processing function.
As far as I know, onSubmit(e) doesn't work the way you're expecting it to.
I think what you're looking for is an onFormSubmit trigger, try using the following from Class SpreadsheetTriggerBuilder documentation to create a script trigger that executes every time someone submits a response to your linked form:
var sheet = SpreadsheetApp.getActive();
ScriptApp.newTrigger("function name")
.forSpreadsheet(sheet)
.onFormSubmit()
.create();
I have an installation of a spreadsheet and corresponding form where I get frequent duplicate on form submit events, inexplicably. It does not occur in other installations. If this is your situation you can't just check if the event is null because to test it for null you have to have something to test. If it's undefined you will get an error. So first test if it's undefined. Try this code:
`function formSubmitted(e) {
// Deal with the unusual case that this is a bogus event
if ((typeof e === "undefined") || (e == null) || (e.length == 0)) {
Logger.log("formSubmitted() received a bogus or empty event");
return;
}
...`

Sending Email Notification Google Sheets

My company has created a Google Form set up to make one of our processes a lot easier. The Google Form is based off of the Master Spreadsheet that contains all of the data inputted from the Form. This spreadsheet then filters out the the form submission and sends the data to each department’s spreadsheet, which as previously stated before, gets all of the information from the "Master Spreadsheet."
We previously had it set up so when employees would go in and approve or deny these requests in their spreadsheet, we would receive an email notification if someone entered "Approved" or "Denied." Recently we changed it so if a certain person submitted a request for a customer, it would be automatically approved, but when we did this the email notification stopped working because no one is manually entering in "Approved" or "Denied" for these requests. It still works when it's manually typed in, but when the cell is automatically filled in, the sendNotification does not work.
Since no actual data is being input into the individual department sheets, we wanted to put the notification trigger on the "Master Sheet," but we are having a heck of a time getting the email notification to send. Basically we want it so if any cell in "Column F" contains a certain list of email addresses it will send an email to a third party notifying them to actually go ahead and make the changes.
Here is what we have so far. Keep in mind this is the code that worked originally. I've tried many different variations of things and have had no luck whatsoever, but I'm not the most educated coder:
function sendNotification() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Form Responses 3");
//Get Active cell
var mycell = ss.getActiveSelection();
var cellcol = mycell.getColumn();
var cellrow = mycell.getRow();
var cellValue = mycell.getValue();
var activeUser = Session.getActiveUser();
var recipients = "xxxx#xxxxxxxxxx.com";
var subject = "Update to "+ss.getName();
var body = activeUser + " has marked row " + cellrow + " \"" + cellValue + "\" in \"" + ss.getName() + "\". Visit " + ss.getUrl() + " to view the changes.";
if (cellcol == 2) {
if (cellValue.indexOf('test1#test.com') >= 0) {
var subject = "Lunch Is Served";
Logger.log('Sending approval notification of row ' + cellrow + ' to ' + recipients);
MailApp.sendEmail(recipients, subject, body);
}
}
}
Please keep in mind that we can't use lastRowNumber (at least I didn't think we could) because we already have over one thousand rows listed so the information will fill in to the array automatically.Lastly, our current trigger is set to "On Form Submission" because we want these emails to come in as the forms are submitted.
I have included a sample spreadsheet for you guys to look at. Please use test1#test.com as your email address when completing the form.
The Google Sheet can be found at the following site:
Test Sheet!
Thank you so much and I look forward to reading your responses!
You can't use the line:
var mycell = ss.getActiveSelection();
If that function is running from an "On Form Submit" trigger, there is no active selection. Although, there is a property available to the "On Form Submit" event object that gives the currently edited range. You must get the event object from the form submission. Then you have 3 options. 1) Just get the values 2) Get an object of questions and their values 3) Get the range of the range edited. First, you get the event object that is passed into the function. When the form is submitted, data is automatically made available to the function associated with the On Form Submit trigger. The letter e is typically used as the variable name to get the event object:
function sendNotification(e) {
But you can use any variable name:
function sendNotification(objOfData) {
Apps Script Documentation - Spreadsheet - On Form Submit
function sendNotification(e) {
var cellValue = e.values[4];//Get the value in column 5

Google Forms Script send email malfunction

I'm trying to make a google form of mine send me the contents of the form via email each time someone fills out the form. I'm using the instructions below, and I've followed them exactly. For some reason, however, when I fill out a form, I get an error saying "TypeError: Cannot call method "getRange" of null. (line 7, file "Code")"
I look at the line of code right above it, in which "s" is defined and I'm thinking that the problem is that getActiveSpreadsheet is not working for some reason. I think that maybe the script can't find the active sheet. Below is the code snippet which is causing the problem, according to the error email I get from google.
var s = SpreadsheetApp.getActiveSheet();
var headers = s.getRange(1,1,1,s.getLastColumn()).getValues()[0];
Whole piece of code I'm using;
function sendFormByEmail(e)
{
// Remember to replace this email address with your own email address
var email = "support#itjones.com";
var s = SpreadsheetApp.getActiveSheet();
var headers = s.getRange(1,1,1,s.getLastColumn()).getValues()[0];
var message = "";
var subject = "Jones IT - New User Form";
// The variable e holds all the form values in an array.
// Loop through the array and append values to the body.
for(var i in headers)
message += headers[i] + ': '+ e.namedValues[headers[i]].toString() + "\n\n";
// Insert variables from the spreadsheet into the subject.
// In this case, I wanted the new hire's name and start date as part of the
// email subject. These are the 3rd and 16th columns in my form.
// This creates an email subject like "New Hire: Jane Doe - starts 4/23/2013"
subject += e.namedValues[headers[1]].toString() + " - starts " + e.namedValues[headers[2]].toString();
// Send the email
MailApp.sendEmail(email, subject, message);
}
Ok, thank you, that makes sense but when I changed my code to use that code snippet I get this error.
2/12/15 4:56 PM sendFormByEmail TypeError: Cannot call method "getRange" of
null. (line 9, file "Code") formSubmit 2/12/15 4:56 PM
Line 9 is the line with the function call for sheet.getRange().
var s = SpreadsheetApp.openById('1WYhDQct8vF-3rlca9dP6lYC2Z23vLLofwJt_DpQU--c');
var sheet = s.getSheetByName('Jones IT - New User Form (Responses)');
var headers = sheet.getRange(1,1,1,s.getLastColumn()).getValues()[0];
Is there any way you recommend me changing my code to get the desired result. I don't really care what I have to change, I just want it to email me the contents of the forms when submitted.
The reason you are getting the error, is because the code is being run from a script project bound to the form, and not a spreadsheet. The script bound to the form has no connection to the spreadsheet. It can't find the active sheet. There is no active sheet associated with the form. The form is associated with the spreadsheet as far as where to submit the data, but not in terms of the code. You can set a trigger in the spreadsheet to run when the form is submitted, but you might want to get the spreadsheet by ID.
function test() {
var s = SpreadsheetApp.openById('your SS ID');
var sheet = s.getSheetByName('your sheet name');
var headers = sheet.getRange(1,1,1,s.getLastColumn()).getValues()[0];
};

Getting active spreadsheet row after form submission

I am relatively new to Google Apps Script and have been using a very simple script that I created about a year ago that is triggered when a user submits inputs via a Google Form. The script has been working perfectly until approximately this past May and I've been scrambling trying to figure out what happened when I haven't made any changes to my code. I have searched many different places and cannot seem to find what is wrong.
Basically, I have a form that users complete and then submit. Upon submission, the script takes the inputs from the most recent row and stores them in variables that would then be assembled into an email message confirmation that acknowledges each user's submitted input.
Here is my code:
function acknowledgement() {
var ActiveSheet = SpreadsheetApp.getActiveSheet();
var ActiveRow = ActiveSheet.getActiveRange().getRow();
var emailAddy = ActiveSheet.getRange("E"+ActiveRow).getValue();
var locvar = ActiveSheet.getRange("C"+ActiveRow).getValue();
var employeevar = ActiveSheet.getRange("B"+ActiveRow).getValue();
var sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Variables");
var contactvar = sh.getRange("A2").getValue();
var subject = sh.getRange("B2").getValue();
var contactvar2 = sh.getRange("C2").getValue();
var linebrk = "<br />";
var msg = "Dear " + employeevar + ","
+ linebrk + linebrk
+ "This confirms that you have completed your review of the latest security presentation."
+ linebrk + linebrk
+ "Your location number is " + locvar + "."
+ "Thank you very much for your participation."
+ linebrk + linebrk
+ contactvar;
var msghtml = '<p>'+msg+'</p>';
var advancedargs = {cc:contactvar2, htmlBody:msghtml};
MailApp.sendEmail(emailAddy, subject, msg, advancedargs);
}
What is currently happening is that my code is no longer grabbing the current row number (i.e. the active row that was just submitted by a user). Instead, it is simply grabbing the top row of the sheet (i.e. my row headings like 'Employee Name', 'Email Address', etc.) and assigning those row headings to the variables thus producing an error when trying to send the email confirmation. For instance my variable emailAddy would contain "Email Address" causing sendEmail to fail.
Any feedback would be appreciated!
Using getActiveRow in the context of a form submission is somewhat strange as one cannot consider that a user is actually active on the sheet... I don't know why you did choose that approach and I'm actually wondering how it happened to work for so long...
There are other possibilities to handle form submissions but the one that will need the fewest changes in your code is to simply use getLastRow() instead of getActiveRange().getRow()
There are a few risks to use that simple "strategy" as there might be concurrency issues when 2 or more people send a form simultaneously.
The other solution is to get all the field values directly from the event properties that comes on form submission as in that case each event is unique, no matter how it comes into the spreadsheet but your script will have to be rewritten more deeply.
I believe that Google Forms has well covered the case you mention, the trigger "onFormSubmit" the spreadsheet, receives an object as a parameter with all the information you need.
I agree with Serge that the script be rewritten deeply, but definitely, it will save many problems.
Go to the documentation, specifically in "Spreadsheet Form Submit Events" https://developers.google.com/apps-script/understanding_events.
You are looking for the code e.range.getRow().
So we have:
function onFormSubmit(e) {
const row = e.range.getRow()
}
You'll also need to setup a trigger to the onFormSubmit function.
Edit > Current project's triggers
Now (finally) some fun: everytime a form is submitted, you'll know the row it corresponds to. Have fun!
Note that the onFormSubmit function could have any name - the trigger will map to any named function and pass to it an "e" argument that contains range.getRow() among other information.