App Script how to customize a non-error message in doGet() - google-apps-script

[edited]
I have a doGet() function that searches for a specific timestamp from a Gsheet and update that row's status based on what the approver clicked in the email (i.e. approved or disapproved).
Searching and updating work perfectly but the custom message I want to convey to the Approver is a bit confusing because the tab shows "Error" and a reference to the code's line number. Please refer to screenshot below:
It might be because I'm using "throw" to show my custom message so I'd like to know the right way to show custom message. I've seen posts in try/catch but I couldn't understand how it is used for this case where there's actually no error.
Will appreciate any help. And apologies for this newbie question.
Snippet of my code is below:
function doGet(e) {
....
// now search again using the adjusted timestamp
for (var i = 0; i < data.length; ++i) {
var row = data[i][1].toString().indexOf(revisedtimestamp);
if (row > -1) { //found
var a = i;
sheet.getRange(a + 1, approvalCol).setValue(e.parameter.approval);
throw ("Your decision has been sent to the requestor cc: Finance for processing");
break;
} else { //not found
throw ("There is a problem locating this request. Finance is automatically notified to look into this.");
}
}

Today I found a reference for web apps using HtmlService.createHtmlOutput or ContentService.createTextOutput for this purpose. So I replaced throw with these lines:
var HTMLString = "<style> h1,p {font-family: 'Helvetica', 'Arial'}</style>"
+ "<h1>Approval process completed.<br>"
+ UImsgForApprover + "</h1><p>";
HTMLOutput = HtmlService.createHtmlOutput(HTMLString);
return HTMLOutput
My custom message shows nicely enough for my needs, as shown below.
ContentService.createTextOutput also works ok, it's just unformatted text.

Related

how to use nextPageToken

I have a script that archives old classrooms, until the end of 2021 it was working fine.
In the lasts months I got an error (the script works ok, but terminate with error) and today I was investigating it, the script runs only once per month.
The error is due to a supposed change in .nextPageToken function.
var parametri = {"courseStates": "ARCHIVED"};
var page = Classroom.Courses.list(parametri);
var listaClassi = page.courses;
var xyz = page.nextPageToken;
if (page.nextPageToken !== '') {
parametri.pageToken = page.nextPageToken;
page = Classroom.Courses.list(parametri);
listaClassi = listaClassi.concat(page.courses);
};
var xyz has been added to better understand what was happening.
So, in this case the list does not have pagination, is only one page. var xyz returns "undefined", and the "if" statement results "true", this makes that variable listaClassi got appended the same content a second time. That generate the error and the abnormal end of the script.
I found an issue reported here https://issuetracker.google.com/issues/225941023?pli=1 that may be related with my problem.
Now I could change .nextPageToken with .getNextPageToken but I found no docs on the second function and many issues reporting that is not working, can anyone help me?
When using the nextPageToken value obtained to the response make sure to enter it as a separate parameter with a slightly different name. You will obtain nextPageToken in the response, the pageToken parameter needs to be entered in the request. It does look like you are doing it right, the way you add the parameter is a bit odd, yet it should be functional.
To discard problems with the Classroom API (that we can certainly take a look at) try with this simple code example in a new Google Apps Script project, remember you will need to add an Advanced service, information about advanced services can be found in this documentation article https://developers.google.com/apps-script/guides/services/advanced. Use listFiles as the main method in your Apps Script project.
function listFiles() {
var totalClasses = 0;
nextPageToken = "";
console.log("Found the following classes:")
do {
var response = loadPage(nextPageToken);
var classes = response.courses;
for (let x in classes){
console.log("Class ID: " + classes[x].id + " named: '" + classes[x].name + "'.");
}
totalClasses += classes.length;
} while (nextPageToken = response.nextPageToken)
console.log("There are " + totalClasses + " classes.")
}
function loadPage(token = ""){
return Classroom.Courses.list({
fields: 'nextPageToken,courses(id,name)',
pageSize: 10,
pageToken: token
});
}
When we first make the API call with Apps Script we don't specify a pageToken, since it is the first run we don't have one. All calls to the List method may return a nextPageToken value if the returned page contains an incomplete response.
while (nextPageToken = response.nextPageToken)
In my code at the line above once response.nextPageToken is empty (not in the response) inside the condition block JavaScript will return false, breaking the loop and allowing the code to finish execution.
To have your incident reviewed by a Google Workspace technician you can also submit a form to open a ticket with the Google Workspace API Support team at https://support.google.com/a/contact/wsdev.

"We're sorry, a server error occurred. Please wait a bit and try again" while creating charts

We are running into sporadic errors (more often than not) on a project that generates Google Doc documents based on info entered into Google Sheet spreadsheets.
The Google Apps Script project pulls data (from sheets), caches it in-memory with standard var statements, massages the data a bit (formatting, de-duping, etc), then copies a Doc template and does a bunch of token substitution and also inserts some charts based on the in-memory variables.
We are encountering the following error during a high quantity of executions:
Exception: We're sorry, a server error occurred. Please wait a bit and try again.
at insertChart(Code:5890:10)
at processForm(Code:4540:5)
The main method creates several (between 5 - 20 or more) charts depending on parameters entered at run-time by the user (which happens via a standard Web Form created by GAS:
function doGet() {
return HtmlService
.createTemplateFromFile('index')
.evaluate();
}
The web page returned by the above is a very standard GAS HTMLService webapp. It contains a form that allows a user to select a few standard types of reporting criteria (date range, filter by certain types of data, etc).
We are duplicating a "template" Docs file which has a bunch of common text and some tokens that we use as placeholders to find and replace with data loaded from the Sheets. The Sheets are very common spreadsheets with the kind of data you'd imagine: date ranges, selections from drop-down lists, free-text columns, etc. (The filtering info specified by the user is used to query the Sheets for the data we want to "merge" into the new Doc. It's basically a much more complex version of your standard "mail merge".)
var templateId = 'asdfasdfasdfasdfasdfasdfasdfasdfasdf'; // DEV
var documentId = DriveApp.getFileById(templateId).makeCopy().getId();
DriveApp.getFileById(documentId).setName('Demo Report - Project Report');
DriveApp.getFileById(documentId).setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.COMMENT);
Logger.log('templateId: ' + templateId);
Logger.log('documentId: ' + documentId);
var doc = DocumentApp.openById(documentId);
var body = doc.getBody();
We have the following method which takes the Doc body, placeholder, and formatted data from a chart builder. It then identifies the Doc element token to replace (simply a string in the Doc file), and runs a body.insertImage after that token element.
function insertChart(body, placeholder, chart) {
if (placeholder.getParent().getType() === DocumentApp.ElementType.BODY_SECTION && typeof chart !== 'undefined' && chart !== null) {
var offset = body.getChildIndex(placeholder);
//Logger.log(chart);
body.insertImage(offset + 1, chart);
}
}
We have several helper methods like the following buildStackedColumnChart(). It's meant to wrap the Chart API commands to use a dataTable and Chart Builder to return a specific type of chart (bar, stacked bar, line, etc).
Its resulting chart gets passed into the insertChart() method above.
function buildStackedColumnChart(title, header, data) {
Logger.log('buildStackedColumnChart');
Logger.log('title: ' + title);
Logger.log('header: ' + header);
Logger.log('data: ' + data);
try {
var dataTable = Charts.newDataTable();
for (var hr = 0; hr < header.length; hr++) {
var headerRow = header[hr];
dataTable.addColumn(headerRow[0], headerRow[1]);
}
for (var r = 0; r < data.length; r++) {
var row = data[r];
dataTable.addRow(row);
}
dataTable.build();
var chart = Charts.newColumnChart()
.setDataTable(dataTable)
.setDimensions(600, 500)
.setTitle(title)
.setStacked()
.build();
return chart;
} catch (ex) {
Logger.log('ex: ' + ex);
return null;
}
}
In the main processForm() method (called directly by the webapp menioned above when the user selects their criteria and clicks a "Generate Report" button. We have a few calls to a method which finds the token in the template file (just text with {{}} around it as shown below). It iterates through a hard-coded list of values in the in-memory data variables storing accumulations from the Sheets and creates chartHeader and chartData (which contains the values to be charted in a mechanism the helper methods above can translate back into calls that make sense for the Chart API) and uses the insertChart() and `` helper methods to insert the chart after the bookmark token and then remove the bookmark from the document (cleanup so the tokens aren't present in the end report Doc).
var chartTrainingsSummaryBookmark = findPlaceholder(body, '{{CHART_TRAININGS_SUMMARY}}');
Logger.log('chartTrainingsSummaryBookmark: ' + chartTrainingsSummaryBookmark);
var chartCategories = [
'Call',
'Conference Attendee',
'Meeting (External)',
'Partnership',
'Quarterly Board Meeting',
'Alliance or Workgroup',
'Training (Others)',
'Training (Professional Development)',
'Youth Training',
'Webinar or Zoom',
'Other',
'Coalition',
'Email',
'Face-to-face',
'Phone',
'Site Visit',
];
// {Call=10.0, Conference Attendee=10.0, Quarterly Board Meeting=10.0, Alliance or Workgroup=10.0, Coalition=10.0, Meeting (External)=10.0}
var chartHeaderData = [];
var chartData = [];
var monthChartHeader = [[Charts.ColumnType.STRING, 'Month']];
var monthChartData = [monthName];
for (var cci = 0; cci < chartCategories.length; cci++) {
var chartCategory = monthName + ':' + chartCategories[cci];
if (getChartData(chartCategory, trainingsChartData) > 0) {
monthChartHeader.push([Charts.ColumnType.NUMBER, chartCategory.split(':')[1]]);
monthChartData.push(getChartData(chartCategory, trainingsChartData));
}
}
if (monthChartData.length > 1) {
if (chartHeaderData.length < 1) {
chartHeaderData = chartHeaderData.concat(monthChartHeader);
}
chartData.push(monthChartData);
}
Logger.log('----- CHART - TRAININGS: SUMMARY -----');
if (chartData.length > 0 && chartData[0].length > 1) {
insertChart(body, chartTrainingsSummaryBookmark, buildStackedColumnChart('', chartHeaderData, chartData));
}
chartTrainingsSummaryBookmark.removeFromParent();
We are also seeing slight variations that also error out the document generation with a Timeout. (This process can take a while if there is a lot of data that fits within the parameters which the user specified.)
We were wondering why these errors would happen on a seemingly random interval. We can literally just click the button that makes the call again and it might work, or might error out on a different chart, etc.
This script has been working for the most part (we did encounter a few specific errors) in months previous; however, this "Exception: We're sorry, a server error occurred. Please wait a bit and try again." started occuring last weekend and got far worse on Monday / Tuesday (nearly every execution failed, very few succeeded). It's not happening quite as badly today; but we are seeing a few errors.
Also, it's a bit strange, but copying the GAS Project and executing the copy has thrown the error less frequently than the original project in production.
We have some ideas for workarounds; bu, ideally, we would like to identify a root cause so we can correctly fix the issue.

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;
}
...`

Run server-side code with google chart select

I have a google visualization table that I'm publishing in a web app.
Background:
I run a script that lists all the documents in a google folder in a spreadsheet. I then push that list into the google table I have published in the web app.
The need:
I want to manage those that same list of documents directly from the web app. I want to be able to to move the document from one folder to another when I select the applicable row on the table.
Two things I have accomplished:
I have a script that will move the document to a specific folder on
the google drive using it's doc id.
I have an event listener on the table so that when you click a row and
then click the delete icon, you get a prompt that asks, "are sure
you want to archive [enter document name]?" When I click ok, I get my
test prompt that says "document archived". When I click no, I get my
test prompt that says "request cancelled". So from this, I know I
have the appropriate code (at least that's how it seems).
What I'm struggling with:
I can't seem to get the codes above to work together. The event listener is providing me the url of the document which I have parsed to give me only the id. This is what I was hoping to use to get the rest of the code to run, but I think because I'm trying to interact with the server-side from the client-side, it's not working. Can anyone help me figure it out? I know that I need to use google.script.run.withSuccessHandler when running a server side script from the client side, but I don't know how it applies to this case the docid I need is being collected on table select. Any help is appreciated and I hope the above makes sense!
// Draw Dashboard
h2dashboard.bind([h2stringFilter, h2typeFilter], [h2chart]);
h2dashboard.draw(h2dataView);
google.visualization.events.addOneTimeListener(h2chart, 'ready', function() {
google.visualization.events.addListener(h2chart.getChart(), 'select', function() {
var selection = h2chart.getChart().getSelection();
var dt = h2chart.getDataTable();
// Get Value of clicked row
if (selection.length) {
var item = selection[0];
var docurl = dt.getValue(item.row, 1);
var docname = dt.getValue(item.row, 0);
var source = dt.getValue(item.row, 3);
// When button is clicked, show confirm box with value
$(document).ready(function() {
$("#hu2archive").on("click", function() {
var answer = confirm("Are you sure you want to archive " + docname + "?");
if (answer === true) {
var archive = DriveApp.getFolderById("FOLDER ID");
var docid = docurl.match(/[-\w]{25,}/); // This is where I'm grabbing the value from the row.
var doc = DriveApp.getFileById(docid);
doc.makeCopy(archive).setName(doc.getName());
source.removeFile(doc);
alert(docname + " has been archived!");
} else {
alert("Request cancelled");
}
});
});
}
});
});
I just got it! What I was having a hard time understanding was how to pass a variable from the client side to code.gs. I have only run a script in code.gs from the client side on button submit but never passed anything back.
So I ended up changing my code to the below which passes the variable I need into a successhandler where archiveDoc is the function in my code.gs and docurl is the name of the variable I need to pass from the eventlistener.
if (answer === true) { google.script.run.withSuccessHandler(onSuccess).withFailureHandler(err).archiveDoc(docurl);
I'm still new to coding so I just learned something new! So thanks Spencer Easton. I did in fact answer my own question.

How to add "Edit Response" link to Google Forms emails?

I have a simple Google Form that collects data, and, using AppScript, sends confirmation emails to users who fill it out. After user submits the form, on confirmation, s/he will see a link to edit his/her response.
I'd like to include that link as a part of the confirmation email (Right now, it only shows up on the page.) How can I obtain the URL to edit a submitted response?
I am able to get the link to the Form through SpreadsheetApp.getActiveSpreadsheet().getFormUrl(). It gives me the following format: https://docs.google.com/a/domain.com/spreadsheet/viewform?formkey=<formKey>
The link however doesn't include the edit key, which is required for users to edit his/her response. The expected URL should look like this: https://docs.google.com/a/domain.com/spreadsheet/viewform?formkey=<formKey>&edit=<editKey>
Thanks for the help in advance!
-K
Edited:
Added a feature request on this: http://code.google.com/p/google-apps-script-issues/issues/detail?id=1345&thanks=1345&ts=1337773007
The answer that this wasn't possible by #Henrique Abreu was true until very recently. Google seems to have added getEditResponseUrl() to the FormResponse class and with that it becomes possible to use code like this to get the edit URL for a bunch of existing forms:
function responseURL() {
// Open a form by ID and log the responses to each question.
var form = FormApp.openById('1gJw1MbMKmOYE40Og1ek0cRgtdofguIrAB8KhmB0BYXY'); //this is the ID in the url of your live form
var formResponses = form.getResponses();
for (var i = 0; i < formResponses.length; i++) {
var formResponse = formResponses[i];
Logger.log(formResponse.getEditResponseUrl());
}
}
To make it automatically email the user as they respond one could add a trigger on form submit. As The situation I'm working with doesn't require people to log in with an apps account I don't have access to an email address automatically so I have a text question that captures the user's email address.
It does ask the question about whether or not editing the forms is what you want. I've been grappling with the relative advantages of editing an existing response or sending a prefilled form using toPrefilledUrl() so that I can see how things have changed over time. I guess this comes down to the value that tracking this will provide you.
If you are using Google Apps your responders can edit there form responses.
See: How to Edit Form Responses
--edit this is now possible. See other answers.
After user submits the form, on confirmation, s/he will see a link to
edit his/her response. I'd like to include that link as a part of the confirmation email
That is not possible, period.
That link is not accessible anywhere and one can't guess/construct it. But, there's some workarounds that might suit you (some suggested here that I'll re-phrase), e.g.
Send a per-populated form link and have the user re-send it. You'd need to have some kind of control field (e.g. the username), so you can know and delete/ignore his older submits. Possibly automatically via a script.
You could also develop and publish an apps-script GUI and send a link to this apps script plus a parameter that you generate where you can determine which entry you should edit. The down-side of this approach is that it's somewhat cumbersome and overkill to re-design the whole form on Apps Script. But again, it works.
At last, you could open an "Enhancement Request" on Apps Script issue tracker and wait until they and Google Spreadsheet/Forms team get together to develop a solution.
Here is a clear blog post that shows you how to do it step by step and explains what's going on under the hood for AppsScripts newbies:
http://securitasdato.blogspot.com/2014/11/sending-confirmation-emails-from-google.html
While collectively you can get there from the all the excellent answers provided here, the script from that post worked best for me.
Does this help - I haven't tried it but I was looking for the same thing a while ago and noticed this.
From this page
https://developers.google.com/apps-script/reference/forms/
code from there contains this:
Logger.log('Published URL: ' + form.getPublishedUrl());
Logger.log('Editor URL: ' + form.getEditUrl());
Jon
Great, script works! Thanks.
For newbies, like me: Just paste the andre's code for function SendConfirmationMail(e) into your spreadsheet's code editor and set 'on form submit' trigger to run it. That's in spreadsheet script editor, not form script editor.
You need to hack in some values. Read the code. For me the confusing one was the need to replace the ********COLUMN SEQUENCE EX 14****** with the sheet column number where you want the edit urls to end up. I used 39 which is one column more than my form was using up.
However, I got runtime probs in this part:
for (var i in headers) {
value = e.namedValues[headers[i]].toString();
// Do not send the timestamp and blank fields
if ((i !== "0") && (value !== "")) {
message += headers[i] + ' :: ' + value + "<br>";
}
}
Dunno why, but I replaced it with this:
for (var keys in columns) {
var key = columns[keys];
if ( e.namedValues[key]) {
message += key + ' :: '+ e.namedValues[key] + "<br>";
}
}
Works for me.
Try This: (Credits is not for me, because i merge two solutions of the third part)
Source: Send Confirmation Email with Google Forms
/* Send Confirmation Email with Google Forms */
function Initialize() {
var triggers = ScriptApp.getScriptTriggers();
for (var i in triggers) {
ScriptApp.deleteTrigger(triggers[i]);
}
ScriptApp.newTrigger("SendConfirmationMail")
.forSpreadsheet(SpreadsheetApp.getActiveSpreadsheet())
.onFormSubmit()
.create();
}
function SendConfirmationMail(e) {
var form = FormApp.openById('***YOUR FORM CODE***');
//enter form ID here
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('***SHEET NAME***');
//Change the sheet name as appropriate
var data = sheet.getDataRange().getValues();
var urlCol = ***************COLUMN SEQUENCE EX 14******; // column number where URL's should be populated; A = 1, B = 2 etc
var responses = form.getResponses();
var timestamps = [], urls = [], resultUrls = [], url;
for (var i = 0; i < responses.length; i++) {
timestamps.push(responses[i].getTimestamp().setMilliseconds(0));
urls.push(responses[i].getEditResponseUrl());
}
for (var j = 1; j < data.length; j++) {
resultUrls.push([data[j][0]?urls[timestamps.indexOf(data[j][0].setMilliseconds(0))]:'']);
url = resultUrls[i-1]
}
sheet.getRange(2, urlCol, resultUrls.length).setValues(resultUrls);
try {
var ss, cc, sendername, subject, headers;
var message, value, textbody, sender;
// This is your email address and you will be in the CC
cc = Session.getActiveUser().getEmail();
// This will show up as the sender's name
sendername = "****YOUR NAME******";
// Optional but change the following variable
// to have a custom subject for Google Docs emails
subject = "Registro de Oportunidade submetido com sucesso";
// This is the body of the auto-reply
message = "Nós recebemos seu registro de oportunidade.<br>Muito Obrigado!<br><br>";
ss = SpreadsheetApp.getActiveSheet();
headers = ss.getRange(1, 1, 1, ss.getLastColumn()).getValues()[0];
// This is the submitter's email address
sender = e.namedValues["********COLUMN NAME OF DESTINATION E-MAIL************"].toString();
for (var i in headers) {
value = e.namedValues[headers[i]].toString();
// Do not send the timestamp and blank fields
if ((i !== "0") && (value !== "")) {
message += headers[i] + ' :: ' + value + "<br>";
}
}
message += "<br>Link to edit" + ' :: ' + url + "<br>";
textbody = message.replace("<br>", "\n");
GmailApp.sendEmail(sender, subject, textbody,
{cc: cc, name: sendername, htmlBody: message});
} catch (e) {
Logger.log(e.toString());
}
}
you can try to populate a form with the values given from that email address than delete previous answers ...
it's not a beautiful way but it can works ...
I don't think we have access to what that value is through the Spreadsheet API (which means Apps Script doesn't have it either). The closest I can think of would be the "key" value in this feed. You'd have to test to find out though. There's no other alternative that I know of other than accessing the Spreadsheet API directly. So first, you'd have to get the last row through the api use ?reverse=true&max-results=1