Need random unique code using Google Apps Script - google-apps-script

I'd created a Google form for collecting data from users. In this form I'm going to ask the site name and want to replace it with Unique code.
https://docs.google.com/forms/d/1JTStfAZGFBAFjVxgH0aZlMUroVsBijC0sfOvuXvqce8/viewform?usp=send_form#start=invite
I'd written the codes with google apps script for this which is given below but every time it generate new unique code and also replace previous one also.
function SendConfirmationMail(e) {
try {
var ss, bcc, sendername, subject, columns, username;
var message, value, textbody, usermail, track, code;
// This is your email address and you will be in the BCC
bcc = Session.getActiveUser().getEmail();
// This will show up as the sender's name
sendername = "Support Team";
// This is the submitter's Name
username = e.values[1];
// This is the submitter's email address
usermail = e.values[2];
// Custom subject for Google Docs emails
subject = "Support Ticket";
// Random Code for Google Docs emails
ss = SpreadsheetApp.getActiveSheet();
track = new Array();
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
var string_length = 10;
var lastRow = ss.getLastRow()-1;
var randomstring = '';
for (var i=0; i<string_length; i++) {
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum+1);
}
track.push(randomstring);
code = ss.getRange(2, 5, lastRow, 1).setValue(track);
// This is the body of the auto-reply
message = "Hi <b>" +username+ " !!!</b> <br><br> Your message has been successfully received.<br>";
GmailApp.sendEmail(usermail, subject, messag,
{bcc: bcc, name: sendername, htmlBody: message});
} catch (e) {
Logger.log(e.toString());
}
}
But I want to generate unique code for every submission. Please update me.

I've struggled with this sort of thing before, and the complexity depends on the security it needs (as far as I can tell, this is all internal, so not much).
I'm not sure I understand why you'd put your unique code as an editable text box (I'm assuming site name is just a holdover and you would eventually either make it a non-editable div or not show it at all).
Either way, one method is to keep an array of all your previous unique codes in properties, although if this is used a lot, then that could be too much (look at the quotas, which Wolfram Alpha says is about 500 words conservatively).
So, maybe you could have a few scriptDBs (or even a spreadsheet, although it's slower) that you combine to get a final array of all your previous codes. Your randomstring algorithm could be fine (as long as you check to make sure it doesn't exist in the overarching scriptDB array).
Look at Generate random string/characters in JavaScript for other random string ideas, but just make sure (again, if this gets very popular) that there are enough new possibilities.
You don't want this hanging while looking for a new code, so I would also add a counter and do something else if it takes more than 100 iterations (if this is all internal, it might not be a bad idea to have a 5-10 character code just to keep the chances of it not finding a new string quickly much lower).

Related

Apps Script Gmail createDraftReplyAll wrong recipient: Puts me as recipient instead of other party

I have a working script for creating a new Gmail draft but when I use createDraftReplyAll in an existing thread, it puts the sender of the last email of the thread as the recipient of the draft.
So if I sent the last email, I become the recipient of the reply, and the other party is Cc'd. And if the other person wrote the last email, they are the recipient but I get CC'd. This is not normal behavior compared with manually creating a draft in Gmail (i.e. from clicking Reply To All), in which case the recipient will always be the other party.
Shown here (result of running the script below):
And normal behavior from clicking Reply All in Gmail:
This is the function I use to create the draft. The thread has already been found via the Subject and original message date:
function createDraftReplyInThread(emailThread, htmlTemplateFileName) {
Logger.log(">> IN createDraftReplyInThread");
Logger.log("htmlTemplateFileName: " + htmlTemplateFileName);
if (emailThread) {
let emailTo = emailThread.to;
Logger.log('Thread TO: ' + emailTo);
let threadMessages = emailThread.getMessages();
let messageCount = threadMessages.length;
let lastMessage = threadMessages[messageCount - 1];
let htmlTemplate = HtmlService.createTemplateFromFile(htmlTemplateFileName);
let htmlBodyContent = htmlTemplate.evaluate().getContent();
var myReplyID = lastMessage.createDraftReplyAll(
"Plain text for draft reply (not used)",
{
'htmlBody': htmlBodyContent
}).getId();
Logger.log(`Draft created in thread; myReplyID: ${myReplyID}`); // just to show it's executed okay
return;
}
return null;
}
It's probably best to ignore this, but I did try to use the limited existing update methods to copy the content, change the recipient and reinstate the the content in a new draft (pure guess work). Noting that draft.update() does work to change the recipient but it also requires a Message argument which I failed to get to work. Possible?
// based off stackoverflow: https://stackoverflow.com/questions/54745999/modify-only-the-subject-and-send-the-draft
function _sandbox_draftModify() {
var draft = GmailApp.getDrafts()[0];
var draftAsMessage = draft.getMessage();
var recipients = draftAsMessage.getTo();
var subject = draftAsMessage.getSubject();
var origRaw = draftAsMessage.getRawContent();
var newRaw = origRaw.replace("new draft", "old draft");
var newRawB64 = Utilities.base64EncodeWebSafe(newRaw, Utilities.Charset.UTF_8);
var emailTo = 'recipientsemail#gmail.com';
Logger.log(`recipients: ${recipients}`);
Logger.log(`subject: ${subject}`);
Logger.log(`newRaw: ${newRaw}`);
draft.update(emailTo, subject, { message: { raw: newRawB64 } });
var draftNew = GmailApp.getDrafts()[0];
userId = 'me';
Gmail.Users.Drafts.update({ message: { raw: newRawB64 } }, userId, draftNew.getId());
Logger.log("Done");
}
In the end all I need is to be able to create a draft email to an existing thread (which I can do already) but with the correct recipient(s), however that may be achieved.
This seems to be the expected behavior for the createDraftReplyAll method.
If you check the documentation page here:
createDraftReplyAll(body)
Creates a draft message replying to the sender of the last message in this thread, using the reply-to address and all recipients of this message. The size of the email (including headers) is quota limited.
However, you can file a feature request on Issue Tracker if you would like to have a functionality which replicates the UI behavior. The form can be found at this link and make sure to fill in all the necessary information.
Reference
Apps Script GmailThread Class - createDraftReplyAll().

Split Gmail thread and label by date in a google script

Hy,
I have a server sending me several log mails by day and I want to automaticly label this mails.
I can't touch the server configuration to adapt the mail subject, so the work must be done by "receiver".
The Subject is still same so gmail merge them in a thread of 100, but I want to split them by date. So One Date, one thread. In addition, I want label them whith a nested label: "Server1" -> "Date"
I've only found a way to add label to the thread in globality and no way to split them.
Is it even possible?
After a new look on my issue, perhaps add the date at the message subject can split threads.
Like:
function AddLogSubjectADate() {
var threads = GmailApp.search('from:sender#server.com has:nouserlabels');
threads.forEach(function(messages){
messages.getMessages().forEach(function(msg){
var date = msg.getDate();
var date_of_mail = Utilities.formatDate(date, "GMT+1", "yyyy/MM/dd")
var subj = msg.getSubject()
var newsubj = subj + date_of_mail
//A way to modify subject
});
});
}
But I didn't find a way to change the subject.
Post Scriptum
I don't think it's relevant, but here is my previous work. but it add label to the thread. Like I said I haven't find a way to split threads.
function AddLogLabelbyDate() {
var today = new Date();
var tomorrow = new Date();
var yesterday = new Date();
tomorrow.setDate(today.getDate()+1);
yesterday.setDate(today.getDate()-1);
var date_today = Utilities.formatDate(today, "GMT+1", "yyyy/MM/dd")
var date_tomorrow = Utilities.formatDate(tomorrow, "GMT+1", "yyyy/MM/dd")
var date_yesterday = Utilities.formatDate(yesterday, "GMT+1", "yyyy/MM/dd")
var threads = GmailApp.search('from:sender#server.com has:nouserlabels before:'+ date_tomorrow +' after:'+ date_yesterday +'');
label.addToThreads(threads);
}
Per the API documentation, Gmail follows some rules about thread grouping:
In order to be part of a thread, a message or draft must meet the following criteria:1. The requested threadId must be specified on the Message or Draft.Message you supply with your request.2. The References and In-Reply-To headers must be set in compliance with the RFC 2822 standard.3. The Subject headers must match.
So, you can prevent the automatic grouping into a given conversation thread by modifying any of those 3 parameters.
Alternately, you can apply per-message conversation labels, though this will not really help you if you use "Conversation View" UI.
Both of these methods require the use of the Gmail REST API, for which Apps Script provides an "advanced service" client library. The native GmailApp does not provide a method for per-message thread alteration, or for manipulating messages in the manner needed.
Thread Separation
If you wanted to disable the conversation grouping, in theory you could do this:
Message#get to obtain a full message representation
Modify one of the properties Gmail uses to perform thread grouping
Message#insert or import to create the new message on the server
Message#delete to remove the original
Message#get to get the inserted message metadata, after Gmail has given it a threadId.
Get the remaining messages that should share that new threadId, modify them appropriately, and insert.
Repeat.
I haven't tested that approach, hence my "in theory" comment.
Per-message labeling
The relevant API methods include Gmail.User.Labels.list, Gmail.User.Messages.list, Gmail.User.Messages.modify, and Gmail.User.Messages.batchModify. You'll probably want to use the list and messages.batchModify methods, since you seem to have a large number of messages for which you'd like to make alterations. Note, there are non-trivial rate limits in place, so working in small batches is liable to be most resource-efficient.
This is likely to be the simplest method to implement, since you don't have to actually create or delete messages - just search for messages that should have a given label, add (or create and add) it to them, and remove any non-desired labels. To start you off, here are some minimal examples that show how to work with the Gmail REST API. I expect you will need to refer to the API documentation when you use this information to construct your actual script.
An example Labels#list:
function getLabelsWithName(labelName) {
const search = Gmail.Users.Labels.list("me");
if (!search.labels || !search.labels.length)
return [];
const matches = search.labels.filter(function (label) {
// Return true to include the label, false to omit it.
return label.name === labelName;
});
return matches;
}
An example Messages#list:
function getPartialMessagesWithLabel(labelResource) {
const options = {
labelIds: [ labelResource.id ],
fields: "nextPageToken,messages(id,threadId,labelIds,internalDate)"
};
const results = [];
// Messages#list is paginated, so we must page through them to obtain all results.
do {
var search = Gmail.Users.Messages.list("me", options);
options.pageToken = search.nextPageToken;
if (search.messages && search.messages.length)
Array.prototype.push.apply(results, search.messages);
} while (options.pageToken);
return results;
}
An example Messages#batchModify:
function batchAddLabels(messageArray, labels) {
if (!messageArray || !messageArray.length || !messageArray[0].id)
throw new Error("Missing array of messages to update");
if (!labels || !labels.length || !labels[0].id)
throw new Error("Missing array of label resources to add to the given messages");
const requestMetaData = {
"addLabelIds": labels.map(function (label) { return label.id; }),
"ids": messageArray.map(function (msg) { return msg.id; }) // max 1000 per request!
};
Gmail.Users.Messages.batchModify(requestMetaData, "me");
}
Additional Resources:
Message Searches
"fields" parameter

How to prevent Google Forms from converting form input to scientific notation format

I have a simple script set up that sends emails based on Google Form entries using a script-based VLookup to get the contact emails. In some cases, Google Forms converts longer numbers entered into the form field to scientific notation. A workaround I have been using is to enter an apostrophe before the number - for some reason this keeps the cell formatted to plaintext. I would like to find a solution that does not require this extra step.
The sheet has a form with a single field, eGCs. The eGCs field can contain ANY combination of letters and numbers and may be a multi-line string. The script sends an email to the user onFormSubmit with the eGCs field entry in the body of the email. The problem arises when I try to submit a very long string that is only numbers and the form entry variable is converted to scientific notation.
I need whatever the user enters in the eGCs field to appear EXACTLY as they entered it on both the Responses 1 sheet and in the body of the email that is sent. Here is the code:
function onFormSubmit(e) {
var eGCs = e.values[1];
var email = Session.getActiveUser().getEmail();
//Replace the Google Sheets formatted line breaks with HTML line breaks so they display properly in the email:
eGCs = eGCs.replace(/\n/g, '<br>');
//Send the email:
var subject = "This is only a test";
var body = eGCs;
MailApp.sendEmail(email, subject, body, {htmlBody: body})
return
}
If I submit
6110523527643880
...into the form, the number is changed to scientific notation format and appears as 6.11052E+15 both on the sheet and in the email that is sent. If I submit a multi-line string such as:
6110523527643880
6110523527643880
6110523527643880
...then the script works fine and the form field entry is not converted (probably because Google does not consider it a number any more). I need it to appear exactly as entered whether or not the form entry is a single line or multiple lines.
Here is my example sheet / script / form. It should be public, so please feel free to test it.
Form responses in Forms (as opposed to Spreadsheets) store responses as Strings. Your trigger function could grab the response from the form to get the string as entered by the respondent.
function onFormSubmit(e) {
// Get response sheet. First version works only in contained script,
// second works even in stand-alone scripts.
// var sheet = SpreadsheetApp.getActiveSheet();
var sheet = e.range.getSheet();
// Get URL of associated form & open it
var formUrl = sheet.getParent().getFormUrl();
var form = FormApp.openByUrl(formUrl);
// Get response matching the timestamp in this event
var timestamp = new Date(e.namedValues.Timestamp);
// NOTE: There is a race condition between the updates in Forms and Sheets.
// Sometimes (often!) the Spreadsheet Form Submission trigger function is invoked
// before the Forms database has completed persisting the new Responses. As
// a result, we might get no results when asking for the most recent response.
// To work around that, we will wait and try again.
var timeToGiveUp = 0;
do {
if (timeToGiveUp > 0) Utilities.sleep(1000); // sleep 1s on subsequent tries
timeToGiveUp++;
var responses = form.getResponses(timestamp);
} while (responses.length == 0 && (timeToGiveUp < 3));
Logger.log("time to give up "+timeToGiveUp);
var response = responses[0]; // assume just one response matches timestamp
var itemResponses = response.getItemResponses();
var eGCsItemNumber = 1; // Indicates where the question appears in the form
var eGCs = itemResponses[eGCsItemNumber-1].getResponse().toString();
// You now have exactly what the respondent typed, as a string.
// It can be used as-is in an email, for example.
var body = "The user entered: "+eGCs;
MailApp.sendEmail(
Session.getActiveUser().getEmail(),
"This is only a test",
body
);
// To preserve the value in the spreadsheet, we must
// force it to remain a string by prepending a tick (')
var eGCsCol = 2;
e.range.offset(0,eGCsCol-1,1,1).setValue("'"+eGCs);
}
Note wrt Race condition comment: The actual work that was needed in this area of the code was a single line:
var responses = form.getResponses(timestamp);
While playing with this, I found that I was frequently receiving an exception, the same as noted in comments below this answer...
Cannot find method getResponses(object)
It turned out that this only happened when the function was triggered by a form submission event, not when running from the editor/debugger with simulated events. That implies that, for a short period of time, the response we're trying to handle is not returned by the call to getResponses().
Because of the way that shared documents are implemented, there is a propagation delay for any change... that's the time it takes for a change in one view of an asset to propagate to all other views.
In this situation, our trigger function has launched with a spreadsheet event, and then opens a view of the Form and tries to read the newest responses before that view contains them.
A simple work-around would be to sleep() for a period of time that would allow propagation to complete.
Utilities.sleep(5000); // 5s pause
var responses = form.getResponses(timestamp);
Simple, yes - but inefficient, because we'd be waiting even when we didn't need to. A second problem would be determining how long was long enough... and what if that changed tomorrow?
The chosen work-around will retry getting responses only if it is not successful the first time. It will only wait when doing a retry. And it won't wait forever - there's a limiting condition applied, via timeToGiveUp. (We could have added an additional check for success after the loop, but since the next statement will through an exception if we've blown through our time limit, we can let it do the dirty work.)
var timeToGiveUp = 0;
do {
if (timeToGiveUp > 0) Utilities.sleep(1000); // sleep 1s on subsequent tries
timeToGiveUp++;
var responses = form.getResponses(timestamp);
} while (responses.length == 0 && (timeToGiveUp < 3));
Lots more than one line of code, but more robust.
I am assuming eGCs is the response with the number.
e.values[2] will always come back as a string (in this case "6.15312E+16"), therefore you cannot convert that to the original number as you loose everything after that last 2. Even if you convert it, the best you can get is "61531200000000000"
Instead, you can pull the value from the spreadsheet.
In the beginning of your onFormSubmit() function add this code:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Form Responses 1");
var eGCs = sheet.getRange(sheet.getLastRow(), 2, 1, 1).getValue();
try {
eGCs = eGCs.toFixed();
} catch (e) {
Logger.log(eGCs);
}
Your eGCs will now return as the full number if it's a number, otherwise it will return as text.
If you want the spreadsheet to have the right format when new responses are submitted add this to your code:
sheet.getRange(sheet.getLastRow(), 2, 1, 1).setNumberFormat("000");
This will convert the new row added by the form in to the correct format. This does not affect the actual value in the spreadsheet, only the way it is formatted.

addEditors to Document from formSubmit

I'm Working on creating script that will perform the actions described below. so far, I've managed to get the first two parts to function, but am now stuck on getting anything more to work. I've reviewed several response forums and tried the suggestions, but no success.
Desired script flow:
form submitted from spreadsheet form
completes fields:
Timestamp
Username (email collected on submission)
Student
Grade
Intervention Plan
Core Reading/Math
Team (email list)
1 script runs onFormSubmit that then creates a copy of a template document, renames that new copy to the e.value "student" submitted in form,
2 then replaces selected text strings within the document with values submitted in the form.
3 Add editors to new document and sends notification with desired instructions
4 creates event (one week from submission date) event details include instructions and link to shared document for team members to complete with their input, sends event invitation to email list.
Here is the working script so far.
function formSubmitValues(e) {
var timeStamp = e.values[0];
var userEmail = e.values[1];
var student = e.values[2];
var grade = e.values[3];
var conern = e.values[4];
var readingCore = e.values[5];
var mathCore = e.values[6];
var interventions = e.values[7];
var team = e.values[8].toString(); // "just to be sure"..Henrique says add .toString this allowed the replaceText part to work
//Makes copy of template document and renames
var tempID = ("1Rq0pDAnuGNfL6W3GB0ZuLeWM2uYzHpKzoyxoXlwjtgE") // use document ID from Template Document
var copyId = DocsList
.getFileById(tempID)
.makeCopy(student + " Initial Referral") // names new copy as student's name
.getId();
// trying to add editors to new document using email list generated in form submit value of "team"
DocsList.getFileById(copyId).addEditors([team]);
// replaces text within template with selected fields from formSubmitValues
var doc = DocumentApp.openById(copyId)
var body = doc.getActiveSection();
body.replaceText("%STUDENT%", student);
body.replaceText("%DATE%", timeStamp);
body.replaceText("%TEACHER%", userEmail);
body.replaceText("%TEAM%", team);
return doc;
}
REPORTED ISSUE RESPONSE: Here is what they said: "The function takes an array or strings, like: DocsList.getFileById(copyId).addEditors(['parent#domain.com', 'parent2#domain.com']);
I tried entering emails directly into script like this and things worked.
So my problem is not the 'addEditors method, but lies in getting the form submitted emails to be passed correctly. Any suggestions on how I would do this?
I have tried what I believe to be all combinations of using .toString(), or not, and using .Split(',').
RE-DEFINE PROBLEM : So it is an issue of how the emails are passed from the e.values form submit.
Here is where I'm at:
When I type emails into script directly: .addEditors(['parent#domain.com', 'email2#domain.net', 'email3#gmail.com']) it works, (I did have to move the addEditors method in the script to go right after the .makeCopy instead of at the end.)
This is what the Execution Transcript shows:
File.addEditors([[parent#domain.com, email2#domain.net]]) and it runs the rest of the script. note: One part I don't understand is the single quotes i.e. 'email' They must be typed in the script, but don't show up on Transcript when run. I've tried putting them around emails in the form, but it makes them show in Transcript and still doesn't run anyway.
So this is what script looks like now:
var tempID = ("1Rq0pDAnuGNfL6W3GB0ZuLeWM2uYzHpKzoyxoXlwjtgE") // use document ID from Template Document
var copyId = DocsList
.getFileById(tempID)
.makeCopy(student + " - TestingCopy") // names new copy as student's name + text
.addEditors([team.split(',')])
.getId();
But when I use the var team with or without .split(',') it does not work. But in the Transcript it shows:
File.addEditors([[rreynolds#domain.net, parent#domain.com]])
which looks identical as to what shows when it does work, but that is the last thing shown in Transcript and editors are not added to document and the script does not finish.
I'm obviously not understanding something here. Is there a way I could get the emails in the team e.values to be treated in a way that the addEditors method is requiring? In the spreadsheet cell they appear as a CSV. i.e rreynolds#domain.net, parent#domain.com
Do they have to be read one at a time, or something?
I'm learning a lot, and appreciate all your help. I am sorry for the confusion with all the comments, but am not sure of the correct way to address issues in this forum. For example: should I go back and edit my original script to show the current version, or add it someplace else? I'm trying to keep the conversation flowing, so that it is easier for others to follow - Thanks rob
please let me give a last (hopefully) clear answer : (thanks for sharing the spreadsheet, this is far more easy to work on ;-)
here is your code fully working.
I have created some intermediate variables to show how it works.
function formSubmitEditors(e) {
// defines spreadsheet form events on submit of form. This function formSubmitEditors is triggered on formSubmit
var timeStamp = e.values[0];
var fileName = e.values[1];
var team = e.values[2].replace(/, /g,"|"); // remove unwanted spaces and commas replace by | for visibility ;-)
Logger.log(team);// contains | as separators
var teamArray = team.split('|');
Logger.log(teamArray.length+' : '+teamArray);// check that it is an array of x elements
//Makes copy of template document and renames
var tempID = '1Rq0pDAnuGNfL6W3GB0ZuLeWM2uYzHpKzoyxoXlwjtgE' // use document ID from Template Document
var copyId = DocsList
.getFileById(tempID)
.makeCopy(fileName + " - TestingCopy") // names new copy as student's name + text
.getId(); //
var file = DocsList.getFileById(copyId).addEditors(teamArray);
// replaces merged-text values within template with selected fields from formSubmitValues
var doc = DocumentApp.openById(copyId)
var body = doc.getActiveSection();
body.replaceText("%FILE%", fileName);// you wrote %FILENAME% in place of %FILE%
body.replaceText("%DATE%", timeStamp);
body.replaceText("%TEAM%", team);// it will be shown with | as separators, if you don't like it replace team by teamArray.toString() to get commas again.
}
EDIT : I removed the toString() for team event, not necessary since e.parameters are already strings.
EDIT2 : to be complete and do what you needed in the initial question you could replace the end of the code with this one that creates the Cal event on next week and sends invites with link to the doc.
var file = DocsList.getFileById(copyId).addEditors(editorsArray);
var fileurl = file.getUrl();
Logger.log(fileurl)
// replaces merged-text values within template with selected fields from formSubmitValues
var doc = DocumentApp.openById(copyId)
var body = doc.getActiveSection();
body.replaceText("%FILE%", fileName);
body.replaceText("%DATE%", timeStamp);
body.replaceText("%MAILS%", editors);
var Cal = CalendarApp.getCalendarsByName('testencodage')[0];// replace with your calendar name you want to use
var newEvent = Cal.createAllDayEvent('Fill the questionnary', new Date(new Date(newtimeStamp).getTime()+7*24*3600*1000), { guests : e.values[2] , sendInvites : true , description :"Don't forget to fill this document "+fileurl})
}
I'd suggest a couple of things
Add a few Logger.log statements in your code to print out debug information.
Add a try ... catch block around the entire section of code and print out the exception. See if you are getting any exception.
Last, use the Execution transcript window to see where your script stopped, if it did.
There are 2 ways to add an editor by email : addEditors([emailAddresses]) and addEditor(emailAddress)
The first has an "s" and needs an array of email adress strings, the second takes a single string as argument. You should use the second or add [brackets] to the email string in your code.
concerning you comment // need to figure out where/how to: .addEditors(email1,email2,etc);
// is this done from DocsList or DocumentApp class?
addEditor() and addEditors() belong to the file class, a member of DocsList class , you can add user(s) using user(s) objects or user(s) email(s) as explained in the doc.
It could be used like this DocFile.addEditors([email1,email2])
EDIT : A lot of comments on this post, sorry about that, it has become quite uneasy to read... I tested these addEditors feature with spreadsheet and it works as expected, using simple array for multiple user emails and string for single email. The document service seems to have a problem with the addEditor() method and it should be reported to the issue tracker.
REPORTED I've reported issue #1512 - Rocketrob

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