How do I delete individual responses in FormApp - google-apps-script

Shouldn't FormResponse have a remove or delete response method?
https://developers.google.com/apps-script/reference/forms/form-response
Is it there and I'm just missing it in the docs?
I'm talking about Responses here not Items.

Nope, not there. And no external API to fill the gap.
So here's a crazy idea.
You could write a script that gets all the responses, calls deleteAllResponses(), then writes back all but the one(s) you want deleted. You'd then have summary info that reflects the responses you care about, but you'd have lost the submission dates (...which you could add as non-form data in a spreadsheet), and submitter UserID (in Apps Domains only, and again you could retain it outside the form).
Whether or not the compromises are acceptable depend on what your intended use of the form data is.
Code
This is a forms-contained script with a simple menu & UI, which will delete indicated responses. The content of deleted responses are logged.
/**
* Adds a custom menu to the active form, containing a single menu item for
* invoking deleteResponsesUI() specified below.
*/
function onOpen() {
FormApp.getUi()
.createMenu('My Menu')
.addItem('Delete response(s)', 'deleteResponsesUI')
.addToUi();
}
/**
* UI for Forms function, deleteResponses().
*/
function deleteResponsesUI() {
var ui = FormApp.getUi();
var response = ui.prompt("Delete Response(s)",
"List of resonse #s to delete, separated by commas",
ui.ButtonSet.OK_CANCEL);
if (response.getSelectedButton() == ui.Button.OK) {
var deleteCSV = response.getResponseText();
var numDeleted = deleteResponses(deleteCSV.split(/ *, */));
ui.alert("Deleted "+numDeleted+" responses.", ui.ButtonSet.OK);
}
}
/**
* Deletes the indicated response(s) from the form.
* CAVEAT: Timestamps for all remaining responses will be changed.
* Deleted responses are logged, but cannot be recovered.
*
* #parameter {Number or Number[]} Reponse(s) to be deleted, 0-indexed.
*
* #returns {Number} Number of responses that were deleted.
*/
function deleteResponses(trash) {
if (!trash) throw new Error( "Missing parameter(s)" );
Logger.log(JSON.stringify(trash))
if (!Array.isArray(trash)) trash = [trash]; // If we didn't get an array, fix it
var form = FormApp.getActiveForm();
var responses = form.getResponses();
// Really feels like we should ask "ARE YOU REALLY, REALLY SURE?"
form.deleteAllResponses();
var numDeleted = 0;
for (var i = 0; i < responses.length; i++) {
if ( trash.indexOf(i.toString()) !== -1 ) {
// This response to be deleted
Logger.log( "Deleted response: " + JSON.stringify(itemizeResponse(responses[i] )) )
numDeleted++
}
else {
// This response to be kept
var newResponse = form.createResponse();
var itemResponses = responses[i].getItemResponses();
for (var j = 0; j < itemResponses.length; j++) {
newResponse.withItemResponse(itemResponses[j]);
}
newResponse.submit();
}
}
return numDeleted
}
/**
* Returns item responses as a javascript object (name/value pairs).
*
* #param {Response} Form Response object
*
* #returns Simple object with all item responses + timestamp
*/
function itemizeResponse(response) {
if (!response) throw new Error( "Missing parameter(s)" );
var itemResponses = response.getItemResponses();
var itemizedResponse = {"Timestamp":response.getTimestamp()};
for (var j = 0; j < itemResponses.length; j++) {
itemizedResponse[itemResponses[j].getItem().getTitle()] = itemResponses[j].getResponse();
}
return itemizedResponse;
}

#james-ferreira
New Google Forms allows you to delete even individual responses from within a Google Form itself without the need of a script.
The answer that this wasn't possible by #mogsdad and #john was true until very recently.
--This is now possible on the New Google Forms--
Google announcement on the 10th of February 2016. (New Google Forms is now the default option)
Delete ALL of the responses:
Delete individual responses:
To delete individual responses you click on the "Responses" tab and choose "Individual". You locate the record you wish to delete and click on the trash can icon to delete that individual response.
Make a note however that the response/s will NOT be deleted from the connected to the form spreadsheet.

It is now possible by script:
To delete a response you need the id of the response you wish to delete:
FormApp.getActiveForm().deleteResponse(responseId)
note: you may also get the form with openById
FormApp.openById(form_id).deleteResponse(responseId)
ref:
https://developers.google.com/apps-script/reference/forms/form#deleteresponseresponseid
note: the response is permanently removed from both the summary and individual responses.

You can delete all responses from a form using deleteAllResponses(), but not individual responses. You can't even delete individual responses manually. If your form responses are directed to a spreadsheet, you use the Spreadsheet Service to select and delete individual responses there.

Related

Using Google Apps Script to get responses from multiple forms and send an email once to each respondent

I have been puzzling this over for some time and have searched extensively, but found no solution.
I'm using Google Apps Script and I run events for a large organization and we have about 80 different registration Google Forms for these events. I want to get the registrant's email address and send them an email when they submit their form. This is easy to accomplish by setting up each individual form. Ideally, I would set up the onSubmit trigger for the form and then copy that form for each new event. However, it seems you cannot install a trigger programmatically without going to the form and running the script manually, and then authorize it. When a form is copied it also loses all its triggers. Am I wrong about this? Doing this for each form is not realistic given that events are added all the time and I have other responsibilities.
Is there no way to set a trigger for these files without running and authorizing each one?
My other solution is:
I am trying to get all the forms in a folder and then get the responses and send a single email to each registrant. This seems overly complicated and requires checking all the forms regularly since there are no triggers for the individual forms. I tried setting triggers in my spreadsheet for the forms and this works, but the number of triggers for a spreadsheet is limited to 20, so doesn't work here. Running my script every minute and then checking if an email has been sent to each respondent seems possible, but complex and possibly prone to errors...
Thanks for any help you can offer!
pgSystemTester's answer worked for me.
I added two bits of code.
One, to declare the time stamp value to zero if there wasn't one
there.
Two, the code needed a "-1" when you get dRange or you insert a new
row which each run.
function sendEmailsCalendarInvite() {
const dRange = sheet.getRange(2, registrationFormIdId, sheet.getLastRow() - 1, 2);
var theList = dRange.getValues();
for (i = 0; i < theList.length; i++) {
if (theList [i][1] == ''){
theList[i][1] = 0;
}
if (theList[i][0] != '') {
var aForm = FormApp.openById(theList[i][0]);
var latestReply = theList[i][1];
var allResponses = aForm.getResponses();
for (var r = 0; r < allResponses.length; r++) {
var aResponse = allResponses[r];
var rTime = aResponse.getTimestamp();
if (rTime > theList[i][1]) {
//run procedure on response using aForm and aResponse variables
console.log('If ran')
if (rTime > latestReply) {
//updates latest timestamp if needed
latestReply = rTime;
}
//next reply
}
}
theList[i][1] = latestReply;
//next form
}
}
//updates timestamps
dRange.setValues(theList);
}
This is probably a simple solution that will work. Setup a spreadsheet that holds all Form ID's you wish to check and then a corresponding latest response. Then set this below trigger to run every ten minutes or so.
const ss = SpreadsheetApp.getActiveSheet();
const dRange = ss.getRange(2,1,ss.getLastRow(),2 );
function loopFormsOnSheet() {
var theList = dRange.getValues();
for(i=0;i<theList.length;i++){
if(theList[i][0]!=''){
var aForm = FormApp.openById(theList[i][0]);
var latestReply = theList[i][1];
var allResponses = aForm.getResponses();
for(var r=0;r<allResponses.length;r++){
var aResponse = allResponses[r];
var rTime = aResponse.getTimestamp();
if(rTime > theList[i][1]){
//run procedure on response using aForm and aResponse variables
if(rTime >latestReply){
//updates latest timestamp if needed
latestReply=rTime;
}
//next reply
}
}
theList[i][1] = latestReply;
//next form
}
}
//updates timestamps
dRange.setValues(theList);
}

Listing Google calendar events using API

I am trying to write a Google Apps script to modify calendar events so I have modified an example to list events first. When I try debugging this it reports an error that "Calendar is not defined" on the line "events = Calendar.Events.list(calendarId, options);"
I have enabled the advanced calendar API, and am basing my script on one from the Google documentation, so I assume that one worked. Is there anything else I need to do to access the relevant objects and methods?
/*
Adapted from code in https://developers.google.com/apps-script/advanced/calendar
*/
function syncColourCode() {
var calendarId = CalendarApp.getDefaultCalendar();
var properties = PropertiesService.getUserProperties();
var fullSync = properties.getProperty('fullSync'); // sync status is stored in user properties
var options = {
maxResults: 100
};
var syncToken = properties.getProperty('syncToken'); // pointer token from last sync also stored in user properties
if (syncToken && !fullSync) { // if there is a sync token from last time and sync status has not been set to full sync
options.syncToken = syncToken; // adds the current sync token to the list of sync options
} else {
// Sync events from today onwards.
options.timeMin = new Date().toISOString(); //change to new Date().toISOString() from getRelativeDate(-1, 0).toISOString()
}
// Retrieve events one page at a time.
var events;
var pageToken;
do {
try {
options.pageToken = pageToken;
events = Calendar.Events.list(calendarId, options);
} catch (e) {
Not a google-apps expert, but from reviewing the code, I see a possible problem. At no point do I see your code checking to see if getDefaultCalendar() actually returned a valid calendar ID. Later your code uses that ID under the assumption that it is good. Have you checked the value of calendarId that is returned?
Sometimes you have to read a little deeper into the message, but I always try to start with trusting the error return. In this case "Calendar is not defined" makes me question the value of calendarId.
It seem that Google made some change so that there is no Calendar reference from the AppScript API.
Anyway to get the event you may use this API:
CalendarApp.getEvents(startTime, endTime)
https://developers.google.com/apps-script/reference/calendar/calendar-app#geteventsstarttime-endtime
Below are my example function running within google sheet.
function listEventsWithinTwoMonth(){
var calendar = CalendarApp.getDefaultCalendar();
var spreadsheet = SpreadsheetApp.getActiveSheet();
var now = new Date();
var twoMonthFromNow = new Date(now.getTime() + (24 * 60 * 60 * 30 * 4 * 1000));
var events = calendar.getEvents(now, twoMonthFromNow);
if (events.length > 0) {
// Header Rows
spreadsheet.appendRow(["#่","id","StartTime","EndTime","Title","Description"]);
for (i = 0; i < events.length; i++) {
var event = events[i];
Logger.log("" + (i+1) + event.getId() +" "+ event.getStartTime()+" "+event.getEndTime()+" "+event.getTitle()+" "+event.getDescription())
spreadsheet.appendRow([(i+1),event.getId(),event.getStartTime(),event.getEndTime(),event.getTitle(),event.getDescription()]);
}
} else {
Logger.log('No upcoming events found.');
}
}
Hope this help.
CalendarApp.getDefaultCalendar() retrieves an object of the class Calendar that has multiple properties, among others an Id.
To retrieve the calendar Id, you need to define
var calendarId = CalendarApp.getDefaultCalendar().getId();

Script onForm Submit trigger not working properly

I created a google form that will take the latest response and move the selected choice to the other section. So if someone checks out a laptop, once they submit the form the laptop choice will appear in the check in section. When I manually run the script it works perfectly fine but once I add the trigger it works for the first few times then it starts creating multiple triggers for one submission which then creates multiple new checkboxes on the form that all say the same thing. Like for example I'll have three different laptop choices when there should only be one. So I had to take the trigger off and I've looked at other similar questions about this problem but they all involve spreadsheets but mine is purely working with the google form so I'm not sure if those solutions will work for me.
I didn't add all my code since part of it is the same thing just with different variables for moving choices from check in to checkout.
var form = FormApp.openById('1I5uMesHbeVZ_RSP8wxmmpPA7-Sgcc4b6dzzH305c8K8');
/**
Responds to a form submission event when the on formSubmit trigger is
enabled
*
* #param {Event} e The event parameter created by a form submission
*/
//function that gets checkout responses
function myFunction(e) {
//getting form responses
var formResponses = form.getResponses();
//getting latest response
var latestFR = formResponses[form.getResponses().length-1];
//getting the item/question responses, checkout check in
var itemResponses = latestFR.getItemResponses();
//looping through item responses to see which item has a response
for (var i=0; i<itemResponses.length; i++) {
switch (itemResponses[i].getItem().getTitle()) {
//if only response to checkout
case "Checkout":
var outAnswer = itemResponses[i].getResponse();
outAnswer.forEach(addOut);
outAnswer.forEach(deleteOut);
break;
//if only response to check in
case "Check In":
var inAnswer = itemResponses[i].getResponse();
inAnswer.forEach(addToCheckOut);
inAnswer.forEach(deleteIn);
break;
//if response to both check out/in
case "Checkout" && "Check In":
var outAnswer = itemResponses[i].getResponse();
var inAnswer = itemResponses[i].getResponse();
outAnswer.forEach(addOut);
outAnswer.forEach(deleteOut);
inAnswer.forEach(addToCheckOut);
inAnswer.forEach(deleteIn);
break;
}}
//getting email response to send email
var email = itemResponses[0].getResponse();
//testing to see if it gets the latest submission
//delete my email later
var subject = 'Response';
var emailTo = [email];
var body = 'Response is' + outAnswer + inAnswer;
MailApp.sendEmail(emailTo, subject, body, {
htmlBody: body});
}
//function that adds the latest response from checkout to check in
section
function addOut(outAnswer) {
//getting check in section item with its choices
var a = form.getItems(FormApp.ItemType.CHECKBOX)[1].asCheckboxItem();
//getting choices from check in
var choices = a.getChoices();
//creating new choice for check in
var choice = a.createChoice(outAnswer);
//adding the choice to the choices
choices.push(choice);
//setting the choices with new choice for check in
a.setChoices(choices);
}
//function that deletes answer from checkout
//only works when its a string so convert outAnswer to string value with
toString but only works with a single choice
function deleteOut(outAnswer) {
var del = form.getItems(FormApp.ItemType.CHECKBOX)
[0].asCheckboxItem();
del.setChoices(del.getChoices().filter(function (choice) {
return choice.getValue() !== outAnswer.toString(); }));
}
You're going to need to do the same kind of thing as the spreadsheet answers suggested, create a script lock and use it to dump quick successive triggers.
Just add the following lines to the top of your script:
var lock = LockService.getScriptLock();
try {
lock.waitLock(3000);
} catch (e) {Logger.log('Could not obtain lock after 3 seconds.');}
Utilities.sleep(3000);
You can also add a "lock.releaseLock();" to the end of your script, but it isn't necessary, locks release on their own.
All this code does is reject any new submissions in the next three seconds after it is triggered. If that isn't enough, change the time in the waitlock AND the sleep to 5000 (forms generally take less than three seconds to run a script like this so you are forcing the script to take longer).

Google Apps Script: "Script function not found" error

I have been searching around for a solution for my issue but have not been able to find one, and as a result am writing my first question in Stack Overflow.
I am currently writing a simple program on Google Apps Script that sends out an email reminder to a user if they forget to submit a Google Form by a certain date. Right now I have 3 different functions: onFormSubmit, scheduleTrigger, and reminderEmail. Below is the code I have written out thus far:
/**
This function searches the Google Form for the date when the previous
session was held & the date when the next session will be held.
This function also calculates the date when to remind the user if they
forget to submit the Google Form. This reminder date is calculated to be 2
days after the date when the next session is held.
This function calls the next function: scheduleTrigger.
*/
function onFormSubmit(e) {
var form = FormApp.getActiveForm();
var frm = FormApp.getActiveForm().getItems();
var futureDate = new Date(e.response.getResponseForItem(frm[3]).getResponse());
var pastDate = new Date(e.response.getResponseForItem(frm[0]).getResponse());
var reminderDate = new Date(futureDate.setDate(futureDate.getDate() + 2));
futureDate.setDate(futureDate.getDate() - 2);
scheduleTrigger(reminderDate, futureDate, pastDate, form);
}
/**
This function schedules the reminder email trigger at a specific date. The
specific date is the reminder date that was calculated in the previous function.
This function calls the next function: reminderEmail.
*/
function scheduleTrigger(reminderDate, futureDate, pastDate, form) {
ScriptApp.newTrigger('reminderEmail(reminderDate, futureDate, pastDate, form)').timeBased().atDate(reminderDate.getFullYear(), reminderDate.getMonth(), reminderDate.getDate()).inTimezone('America/New_York').create();
}
/**
This function checks the submissions if the form has been submitted.
If there is no submission, then it sends out an email reminder.
*/
function reminderEmail(reminderDate, futureDate, pastDate, form) {
var count = 0;
var formResponses = form.getResponses();
for (var i = 0; i < formResponses.length; i++) {
var formResponse = formResponses[i];
var itemResponses = formResponse.getItemResponses();
for (var j = 0; j < itemResponses.length; j++) {
var itemResponse = itemResponses[j];
if (itemResponse == futureDate) {
count++;
}
}
}
if (count != 2) {
MailApp.sendEmail("test#gmail.com",
"Submit Form Reminder",
"Hey! You didn't fill out the form yet!");
};
}
The issue I am running into is that I receive an error message stating "The selected function cannot be found" for function reminderEmail whenever I run the program. I have looked around and followed previous posts made to solve this issue, such as renaming the function and restarting with a whole new different Google From but nothing has worked. I also have full ownership and authorization to the script so permissions shouldn't be an issue.
Please let me know if you have any ideas on how to solve this problem. Any and all feedback is welcome! If there is any part of my question that is unclear please let me know. Thank you for taking the time to read through this lengthy post.
The reason it's failing is because you need to only pass the function name to ScriptApp.newTrigger. You can't send parameters with it, that's something you'll have to handle in other ways.
Documentation
/**
This function searches the Google Form for the date when the previous session was held & the date when the next session will be held.
This function also calculates the date when to remind the user if they forget to submit the Google Form. This reminder date is calculated to be 2 days after the date when the next session is held.
This function calls the next function: scheduleTrigger.
*/
function onFormSubmit(e) {
var form = FormApp.getActiveForm();
var frm = FormApp.getActiveForm().getItems();
var futureDate = new
Date(e.response.getResponseForItem(frm[3]).getResponse());
var pastDate = new
Date(e.response.getResponseForItem(frm[0]).getResponse());
var reminderDate = new Date(futureDate.setDate(futureDate.getDate() + 2));
futureDate.setDate(futureDate.getDate() - 2);
scheduleTrigger(reminderDate);
}
/**
This function schedules the reminder email trigger at a specific date. The specific date is the reminder date that was calculated in the previous function.
This function calls the next function: reminderEmail.
*/
function scheduleTrigger(reminderDate) {
ScriptApp.newTrigger('reminderEmail').timeBased().atDate(reminderDate.getFullYear(), reminderDate.getMonth(), reminderDate.getDate()).inTimezone('America/New_York').create();
}
/**
This function checks the submissions if the form has been submitted. If there is no submission, then it sends out an email reminder.
*/
function reminderEmail() {
var form = FormApp.getActiveForm();
var count = 0;
var formResponses = form.getResponses();
for (var i = 0; i < formResponses.length; i++) {
var formResponse = formResponses[i];
var itemResponses = formResponse.getItemResponses();
for (var j = 0; j < itemResponses.length; j++) {
var itemResponse = itemResponses[j];
if (itemResponse == futureDate) { // Unsure what this part is for, perhaps you should find another way to calculate this.
count++;
}
}
}
if (count != 2) {
MailApp.sendEmail("test#gmail.com",
"Submit Form Reminder",
"Hey! You didn't fill out the form yet!");
};
}
I was able to clean up your code to a way it should work, but not sure about futureDate in the reminderEmail function.

Copying google apps script with forms

I'm running into a strange issue that is causing me considerable difficulty hoped someone her might have some insight.
I have a form that is accepting responses and sending the results to a sheet. I've written an additional onsubmit script that then marks up some other sheets both in order to facilitate tracking and to provide a prefilled url that can be sent next time the form needs to be submitted.
The forms are for student data collection and I therefor need three each collecting data about students in different years. My issue is that I can't seem to copy the scripts to the other forms without them ceasing to work.
I have taken the following approaches:
Initially I just tried duplicating the entire form and then changing it, this meant the scripts came along with the copy.
I then tried keeping the duplicated form but deleting out all the script information and copy and pasting the script to a new project within the form.
I then tried creating a new test form and copy and pasting the script to that - again no success.
All attempts have resulted in the data being successfully submitted to the responses sheet but the script either not triggering or failing silently.
I can't see anything in the script I have produced that would cause it to work only on one form but I'm not very experienced in JS and it may be I am not seeing something obvious ( I am aware there are some foolish bits in the script but this is the version I know works).
If anyone can point out anything that would make the script not work for any other forms or can explain how the script can be successfully copied I'd be really grateful.
Code:
/* onsubmit function that creates a prefilled url and prints to sheet */
function onSubmit(e) {
var subject = "ICT";
//generate url
var url=e.response.toPrefilledUrl();
var responses = e.response;//e is of type formresponse.
var name = responses.getItemResponses()[0].getResponse();
var sheet1 = SpreadsheetApp.openByUrl("https://docs.google.com/a/rbair.org.uk/spreadsheets/d/1w_rCPJR-O9_fUs1T5HKmaTUsRjk_9JRVRPxV2kJzNMk/edit#gid=0").getSheetByName("Admin");
update(name, subject);
//print to cell in feedback sheet
var data = sheet1.getDataRange().getValues();
for(i=0; i<100; i++)
{
if(data[i][1]==name)
{
sheet1.getRange(i+1, 3).setValue(url);
}
}
}
function test()
{
var name = "Piers Hartley";
var subject = "ICT";
update(name, subject);
}
function update (name, subject)
{
//var name = "Piers Hartley";
//var subject = "ICT";
var msheet = SpreadsheetApp.openByUrl("https://docs.google.com/a/rbair.org.uk/spreadsheets/d/1IauIkNCrE95qNAL2KxfJhespEcRWuMXYGzkkjwFnezg/edit#gid=0");
var refsheet = msheet.getSheetByName("Subject Teachers");
var track = refsheet.getRange(2,10).getValue();
var tsheet = msheet.getSheetByName(track);
var trdr = tsheet.getDataRange();
var trdata = trdr.getValues();
var lcol = trdr.getLastColumn();
var lrow = trdr.getLastRow();
for(i=0; i<lcol; i++)
{
if (trdata [0] [i] == subject)
{
for (j=1; j<lrow; j++)
{
if (trdata [j] [i] == name)
{
tsheet.getRange(i+2, j+1).setValue("Done");
}
}
}
}
}
Have you added the On Form Submit trigger? (under Resources in the script editor).