I have a google sheet that has an auto refreshing IMPORTXML function on it based on a timed trigger and am trying to write a script that will auto email a notification alert when it retrieves a row with a "HELP" message type.
I compiled this script based on some examples I found. I have tested it and it works fine with manually entered data with an onEdit installable trigger but from my testing (and based on research) I need to use the onChange trigger to have the IMPORTXML trigger the script.
However, when I set the script to a onChange installable trigger, the script doesn't seem to execute either automatically with the IMPORTXML loading data or by me manually entering it on the sheet.
Am I hitting some limitation of Good Apps Script? This is my first time using it (and JavaScript)
function helpAlertEmail(e)
{
if (e.range.columnStart !== 5 || e.value !== 'HELP' && e.value !== 'HELP-CANCEL') return;
var ss = e.source.getActiveSheet()
var details = ss.getRange(e.range.rowStart, 1, 1,11).getValues()[0];
var headers = ss.getRange(1, 1, 1, 11).getValues()[0];
var subject = "SPOT BEACON ALERT: " + details[2] + " Sent a " + details[4] + " Message at " + details[9];
var body = "SPOT Beacon " + details[2] + " (" + details[1] + ") Sent a " + details[4] + " Message at " + details[9] + "\n\n";
var email = "xxx#xxx.org";
var cols = [0, 4, 5, 6, 9, 10];
for (var i = 0; i < details.length; i++)
{
if (cols.indexOf(i) === -1) continue;
body += headers[i] + ": " + details[i] + "\n"
}
body += "\n\n\n Please do not respond to this email as it is automatically generated by an account that is not checked.";
MailApp.sendEmail(email, subject, body, {noReply:true});
}
By what I understand from your explanation about the use case, I would like you to know this:
1) onEdit - Specifies a trigger that will fire when the spreadsheet is edited.
2) onChange - Specifies a trigger that will fire when the spreadsheet's content or structure is changed.
These are the conditions stated by Google.
Now what it does not tell us is:
1) The edit has to be manual
Now you said you tried manually as well but the trigger did not fire. A possible reason for that is that you might have copied the data from somewhere and then pasted it where you want it. So, you cannot expect the trigger to fire in that case.
2) The edit cannot be any form of automation (it cannot be done using a spreadsheet formula or using any script). In this case as well, the trigger would not fire.
So, in all, what you are facing is not a consequence of any of the limitations stated by Google. Sadly, It is what it is.
What you will have to do is, think of another way to go about what you are trying to achieve, which is, use something else except the onChange and the onEdit triggers.
Related
I asked this question and was able to have emails auto-send using the modified script shared with me in the answer...however I'm running into a couple of issues.
The Google Sheet has one tab with data imported via a Google Sheets add-on called Data Connector. It auto-refreshes data connected from Salesforce, to Sheets, every 24 hours. In the "First Time Users" tab I'm using a query to pull all new users from the imported data tab and running the script off of this tab. Newest users are always added to the top of the sheet, so rows of data which the script has already sent an email based on, will move down as new data is added to the top of the sheet.
I want this script to run anytime the sheet changes (this will typically happen when the imported data refreshes) and I want an email to be sent out to a specific email address, if there's a new user (one email per new user and multiple new users may be added at one time).
This is the script I got from my last posted question:
function email() {
var ActiveSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("First Time Users");
var StartRow = 2;
var LastRow = ActiveSheet.getLastRow();
var RowRange = LastRow - StartRow + 1;
var WholeRange = ActiveSheet.getRange(StartRow, 1, RowRange, 9);
var AllValues = WholeRange.getValues();
var ranges = [];
for (var i = 0; i < AllValues.length; i++) {
var CurrentRow = AllValues[i];
var EmailSent = CurrentRow[8];
if (CurrentRow[7] == "Y" && EmailSent != "Y") {
var message =
"<p><b>Request: </b>" + CurrentRow[0] + "</p>" +
"<p><b>Account: </b>" + CurrentRow[1] + "</p>" +
"<p><b>Appointment Created Date: </b>" + CurrentRow[4] + "</p>" +
"<p><b>User: </b>" + CurrentRow[5] + "</p>";
var setRow = i + StartRow;
var SendTo = "testemail#gmail.com";
var Subject = "First Time User Submitted Ask: " + CurrentRow[1];
MailApp.sendEmail
({
to: SendTo,
cc: "",
subject: Subject,
htmlBody: message,
});
ranges.push("I" + setRow);
}
}
ActiveSheet.getRangeList(ranges).setValue("Y");
}
I added the following trigger, hoping it would execute the script every time new users are added to the "First Time Users" tab:
My issues are the following:
Trigger/execution error: Exception: Ranges must have at least one range.
at email(Code:103:15)
The email notification contains information from the last row of the sheet, moving up, rather than the row with the new user which is the row(s) where there's no "Y" under the "Sent" column, Column I. The "Y" is added to this column once the script executes and sends out an email. (Ex., if I receive 3 notification emails for 3 new users, the data in the email has data from the last row, the next email has data from the second to last row, and the third email has data from the third to last row in the sheet).
Here's a sample sheet of what the "First Time Users" tab looks like. Any help is greatly appreciated.
Thanks!
I just tried the script and it's working flawlessly, I used a Time-Driven trigger instead of an OnChange trigger, in your situation as you mentioned that the Salesforce connector is refreshing data every 24 hours it's best for you to have the trigger run every couple of minutes.
You may want to try using it as a Time driven trigger instead. This is how the latest test emails were received:
This is how the emails were received in my mailbox:
This is how the execution is displayed in the logs:
As you may notice there are no errors shown in the logs so I would recommend using Time-driven triggers instead.
I'm looking for a script that would send an automatic email to a seller (column J) when a change is made to one of his customers. The change is made through a google form. So the form is linked to a spreadsheet and I added a tab where I did a vlookup in column J of the customer number. It works when I manually typed the email adress but it doesn't when it's the vlookup formula... here's the code I have for now.
Thanks in advance.
function twchange3(e) {
var range = e.range;
if (e.range.getColumn()== 10) {
var sheet = SpreadsheetApp.getActiveSheet();
var row = SpreadsheetApp.getActiveRange().getRow();
var value = sheet.getRange(row, 1, 1, 10).getValues();
var email = value[0][9];
var router = value[0][1];
var noclient = value[0][3];
var nomclient = value[0][4];
var twactuel = value[0][5];
var twdemande = value[0][6];
var raison = value[0][7];
MailApp.sendEmail(value[0][9], "Demande de changement de Time-Window",
"Bonjour voici une demande de changement de Time-Window pour votre client " + nomclient + "\n\n" +
"Numéro de client: " + noclient + "\n\n" +
"Time-Window Actuel: " + twactuel + "\n\n" +
"Time-Window demandé: " + twdemande + "\n\n" +
"Raison: " + raison + "\n\n" +
"Auteur de la demande: " + router + "\n\n");
}
}
Let me rephrase what you've said.
A client submits a Google Form, which sends a result to Tab A of a Google Sheet.
Then there is Tab B that is using a VLOOKUP function to check the latest result in the Google Sheet.
When you change a cell with the email manually in Tab A, the script works.
When you use the Google Form to submit a response, the script does not run.
There are a few problems here.
When you manually change the Sheet, you activate the onEdit trigger.
When you update the Sheet via the Google Form, it is unlikely that onEdit gets triggered because onEdit is normally a user domain. It is likely that onChange is supposed to be triggered instead, and you might not even have it set up.
Then, even if you set up onChange and it fires, I'd say it is unlikely that the change will be occurring in the VLOOKUP cell of Tab B because the latter is just a filter, and the actual change is likely to be happening in Tab A. I am saying likely because I decided not to test it since your description of the issue was not very clear.
So you do not actually need VLOOKUP here, just read the changing cell directly with the script.
Even a better solution might be reading the response directly from the Google Form with its own script and triggers.
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;
}
...`
I'm having an issue where a script I wrote is sending duplicate emails when a form is submitted. The script is also executing on occasion when I simply open the spreadsheet. I only have one trigger set up to run the script when the form is submitted, and I'm the only one with edit access to the sheet. I tried deleting the script project altogether and creating a new one, which didn't resolve the issue. I'm not sure if there's anything wonky with my script, but here it is:
function sendEmails() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Raw Data'); // Gets Raw Data Sheet
var lastRow = sheet.getLastRow(); // Gets last row of sheet everytime the form is submitted
var lastColumn = sheet.getLastColumn(); // Gets last column of sheet everytime the form is submitted
var value = sheet.getRange(lastRow,1,lastRow,lastColumn).getValues().toString();
var comments = sheet.getRange(lastRow, 41).getValue().toString(); // Gets additional comments from inspection form submitted
if (value.indexOf("NOT OK") > -1) {
MailApp.sendEmail({
to: "test#test.com",
subject: 'Machine Issue',
htmlBody: "An inspection of the xyz machine has returned issues: " + "<br/><br/>"
+ "<b>" + comments + "</b>" + "<br/><br/>" +
" Click " + ' <b>HERE</b>'
+ " to see the last inspection report.",
});
} // Produces email based on defined parameters.
}
I've also tried deleting the trigger and setting up a new one, which hasn't worked either.
Check your executions log. If you have multiple executions but only one Form Responses line, this is a known bug with the form submit trigger. The only way to get around it is to use a script lock.
Like this:
SpreadsheetApp.flush();
var lock = LockService.getScriptLock();
try {
lock.waitLock(15000); // wait 15 seconds for others' use of the code section and lock to stop and then proceed
} catch (e) {
Logger.log('Could not obtain lock after 30 seconds.');
return HtmlService.createHtmlOutput("<b> Server Busy please try after some time <p>")
// In case this a server side code called asynchronously you return a error code and display the appropriate message on the client side
return "Error: Server busy try again later... Sorry :("
}
START NORMAL CODE HERE
I've only got one script where this is really a problem but it was a really awful problem, up to six executions per form submission and the scriptlock is the tidiest way to lock it down. If your code itself takes less than 15 seconds reduct your waitlock time so the extra copies give up faster. If you use this method you'll still see the extra copies in the executions log, but they'll only be 15 seconds long. It's very satisfying to watch them caught and killed in this way.
I had an issue with multiple emails on form submission too. I noticed that my code was sending an email for every send email function i had. Like, it was based on score email, so what i did is, i closed function for every alternative, so, it would check points, if it didnt met the requirements, then it just wouldnt send email, and go for the next function, until it found a the function that met requirementes.
I think that in your code, its reading send emails twice, so he reads on the send emails, look for requirements, and sends. Then, it reads the next send emails, looks again for requirements and send email again.
Its like your giving an order to send emails twice.
This line has a problem:
var value = sheet.getRange(lastRow,1,lastRow,lastColumn).getValues().toString();
Let's say lastRow is 20. Then this code with get the last row plus the next 19 rows of values which presumably are all blank. The third parameter is the number of rows and the fourth is the number of columns.
It's better to pass the event object into the function and use e.values rather than having to go get the last line. If you get multiple form submissions one after another you may in fact be getting the wrong data.
This line also has a problem:
htmlBody: "An inspection of the xyz machine has returned issues: " + "<br/><br/>"
+ "<b>" + comments + "</b>" + "<br/><br/>" +
" Click " + ' <b>HERE</b>'
+ " to see the last inspection report.",
});
The comma at the end of the htmlBody parameter should be removed.
Try this code:
function sendEmails(e) {
var value=e.values.toString();
var comments=e.values[40];
if (value.indexOf("NOT OK") > -1) {
var html="An inspection of the xyz machine has returned issues: ";
html+="<br/><br/>" + "<b>" + comments + "</b>" + "<br/><br/>" + " Click "
html+=' <b>HERE</b>' + " to see the last inspection report.";
MailApp.sendEmail({to: "test#test.com",subject: 'Machine Issue',htmlBody: html});
//Logger.log(html);
}
}
Form Submit Event Object
I played around with this a little more and according to #J. G. there is a problem with onFormSubmit triggers returning multiple triggers. I solved the situation for my testing by using the following code which I was using to log onFormSubmit triggers.
function testFormSubmission(ev) {
var lock=LockService.getUserLock();
try{
if(ev.values && !ev.values[1]){throw('Spurious Returns Error');}
if(lock.tryLock(10000)) {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('LogSheet');
var tA=[Utilities.formatDate(new Date(), Session.getScriptTimeZone(),"d/M/yyyy HH:mm:ss")];
tA=tA.concat(ev.values);
tA.splice(tA.length-1,1,ev.triggerUid,ev.range.rowStart,ev.range.columnEnd,JSON.stringify(ev.values));
sh.appendRow(tA);
lock.releaseLock();
}
}
catch(error){
console.error(error);
return;
}
}
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