Skip to content

Instantly share code, notes, and snippets.

@RyanSchw
Last active August 20, 2019 21:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RyanSchw/4c54cba93c9c91ea106e03b85b416fd5 to your computer and use it in GitHub Desktop.
Save RyanSchw/4c54cba93c9c91ea106e03b85b416fd5 to your computer and use it in GitHub Desktop.
Google Form Post-Submit Hook
/**
* @OnlyCurrentDoc
*
* The above comment directs Apps Script to limit the scope of file
* access for this add-on. It specifies that this add-on will only
* attempt to read or modify the files in which the add-on is used,
* and not all of the user's files. The authorization request message
* presented to users will reflect this limited scope.
*/
// Heavily modified https://github.com/gsuitedevs/apps-script-samples/tree/master/forms/notifications
/**
* A global constant String holding the title of the add-on. This is
* used to identify the add-on in the notification emails.
*/
var ADDON_TITLE = 'Post-Submit Hook';
/**
* Adds a custom menu to the active form to show the add-on sidebar.
*
* @param {object} e The event parameter for a simple onOpen trigger. To
* determine which authorization mode (ScriptApp.AuthMode) the trigger is
* running in, inspect e.authMode.
*/
function onOpen(e) {
FormApp.getUi()
.createAddonMenu()
.addItem('Configure notifications', 'showSidebar')
.addToUi();
}
/**
* Runs when the add-on is installed.
*
* @param {object} e The event parameter for a simple onInstall trigger. To
* determine which authorization mode (ScriptApp.AuthMode) the trigger is
* running in, inspect e.authMode. (In practice, onInstall triggers always
* run in AuthMode.FULL, but onOpen triggers may be AuthMode.LIMITED or
* AuthMode.NONE).
*/
function onInstall(e) {
onOpen(e);
}
/**
* Opens a sidebar in the form containing the add-on's user interface for
* configuring the notifications this add-on will produce.
*/
function showSidebar() {
var ui = HtmlService.createHtmlOutputFromFile('Sidebar')
.setTitle('Post-Submit Hook');
FormApp.getUi().showSidebar(ui);
}
/**
* Save sidebar settings to this form's Properties, and update the onFormSubmit
* trigger as needed.
*
* @param {Object} settings An Object containing key-value
* pairs to store.
*/
function saveSettings(settings) {
PropertiesService.getDocumentProperties().setProperties(settings);
adjustFormSubmitTrigger();
}
/**
* Queries the User Properties and adds additional data required to populate
* the sidebar UI elements.
*
* @return {Object} A collection of Property values and
* related data used to fill the configuration sidebar.
*/
function getSettings() {
var settings = PropertiesService.getDocumentProperties().getProperties();
// Get all text fields so user can select email question
var form = FormApp.getActiveForm();
var textItems = form.getItems(FormApp.ItemType.TEXT);
settings.textItems = [];
for (var i = 0; i < textItems.length; i++) {
settings.textItems.push({
title: textItems[i].getTitle(),
id: textItems[i].getId()
});
}
var listItems = form.getItems(FormApp.ItemType.LIST);
settings.listItems = [];
for (var i = 0; i < listItems.length; i++) {
settings.ListItems.push({
title: listItems[i].getTitle(),
id: listItems[i].getId()
});
}
return settings;
}
/**
* Adjust the onFormSubmit trigger based on user's requests.
*/
function adjustFormSubmitTrigger() {
var form = FormApp.getActiveForm();
var triggers = ScriptApp.getUserTriggers(form);
var settings = PropertiesService.getDocumentProperties();
// Create a new trigger if required; delete existing trigger
// if it is not needed.
var existingTrigger = null;
for (var i = 0; i < triggers.length; i++) {
if (triggers[i].getEventType() == ScriptApp.EventType.ON_FORM_SUBMIT) {
existingTrigger = triggers[i];
break;
}
}
if (!existingTrigger) {
var trigger = ScriptApp.newTrigger('respondToFormSubmit')
.forForm(form)
.onFormSubmit()
.create();
} else if (existingTrigger) {
ScriptApp.deleteTrigger(existingTrigger);
}
}
/**
* Responds to a form submission event if an onFormSubmit trigger has been
* enabled.
*
* @param {Object} e The event parameter created by a form
* submission; see
* https://developers.google.com/apps-script/understanding_events
*/
function respondToFormSubmit(e) {
var settings = PropertiesService.getDocumentProperties();
var authInfo = ScriptApp.getAuthorizationInfo(ScriptApp.AuthMode.FULL);
// Check if the actions of the trigger require authorizations that have not
// been supplied yet -- if so, warn the active user via email (if possible).
// This check is required when using triggers with add-ons to maintain
// functional triggers.
if (authInfo.getAuthorizationStatus() ==
ScriptApp.AuthorizationStatus.REQUIRED) {
// Re-authorization is required. In this case, the user needs to be alerted
// that they need to reauthorize; the normal trigger action is not
// conducted, since authorization needs to be provided first. Send at
// most one 'Authorization Required' email a day, to avoid spamming users
// of the add-on.
sendReauthorizationRequest();
} else {
// All required authorizations have been granted, so continue to respond to
// the trigger event.
pingServer(e.response);
}
}
/**
* Called when the user needs to reauthorize. Sends the user of the
* add-on an email explaining the need to reauthorize and provides
* a link for the user to do so. Capped to send at most one email
* a day to prevent spamming the users of the add-on.
*/
function sendReauthorizationRequest() {
var settings = PropertiesService.getDocumentProperties();
var authInfo = ScriptApp.getAuthorizationInfo(ScriptApp.AuthMode.FULL);
var lastAuthEmailDate = settings.getProperty('lastAuthEmailDate');
var today = new Date().toDateString();
if (lastAuthEmailDate != today) {
if (MailApp.getRemainingDailyQuota() > 0) {
var template =
HtmlService.createTemplateFromFile('AuthorizationEmail');
template.url = authInfo.getAuthorizationUrl();
template.notice = NOTICE;
var message = template.evaluate();
MailApp.sendEmail(Session.getEffectiveUser().getEmail(),
'Authorization Required',
message.getContent(), {
name: ADDON_TITLE,
htmlBody: message.getContent()
});
}
settings.setProperty('lastAuthEmailDate', today);
}
}
function pingServer(response) {
var form = FormApp.getActiveForm();
var settings = PropertiesService.getDocumentProperties();
var apiurl = settings.getProperty('apiurl');
var secretkey = settings.getProperty('secretkey');
var emailtype = settings.getProperty('emailtype');
var form = FormApp.getActiveForm();
var settings = PropertiesService.getDocumentProperties();
var emailId = settings.getProperty('respondentEmailItemId');
var emailItem = form.getItemById(parseInt(emailId));
var respondentEmail = response.getResponseForItem(emailItem)
.getResponse();
var nameId = settings.getProperty('respondentNameItemId');
var nameItem = form.getItemById(parseInt(nameId));
var respondentName = response.getResponseForItem(nameItem)
.getResponse();
var subteam = settings.getProperty('subteam');
var subteamItem = form.getItemById(parseInt(subteam));
var respondentSubteam = response.getResponseForItem(subteamItem)
.getResponse();
var payload = {
"email": respondentEmail,
"type": emailtype,
"name": respondentName,
"subteam": respondentSubteam
};
var options = {
"method": "POST",
"body" : payload
};
Logger.log(payload)
var response = UrlFetchApp.fetch(apiurl, options)
Logger.log(response)
}
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
<!-- The CSS package above applies Google styling to buttons and other elements. -->
<style>
.branding-below {
bottom: 54px;
top: 0;
}
.branding-text {
left: 7px;
position: relative;
top: 3px;
}
.logo {
vertical-align: middle;
}
.width-100 {
width: 100%;
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
}
label {
font-weight: bold;
}
#api-url,
#secret-key,
#button-bar, {
margin-bottom: 10px;
}
#response-step {
display: inline;
}
</style>
</head>
<body>
<div class="sidebar branding-below">
<form>
<div class="block">
<label for="api-url">
API URL
</label>
<input type="text" class="width-100" id="api-url">
</div>
<div class="block">
<label for="secret-key">
Secret Key
</label>
<input type="text" class="width-100" id="secret-key">
</div>
<div class="block">
<label for="email-type">
Email Template Name
</label>
<input type="text" class="width-100" id="email-type">
</div>
<div class="block form-group">
<label for="respondent-email">
Which question asks for their email?
</label>
<select class="width-100" id="respondent-email"></select>
</div>
<div class="block form-group">
<label for="respondent-name">
Which question asks for their name?
</label>
<select class="width-100" id="respondent-name"></select>
</div>
<div class="block form-group">
<label for="subteam">
Which question asks for their preferred subteam?
</label>
<select class="width-100" id="subteam"></select>
</div>
<div class="block" id="button-bar">
<button class="action" id="save-settings">Save</button>
</div>
</form>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js">
</script>
<script>
/**
* On document load, assign required handlers to each element,
* and attempt to load any saved settings.
*/
$(function() {
$('#save-settings').click(saveSettingsToServer);
google.script.run
.withSuccessHandler(loadSettings)
.withFailureHandler(showStatus)
.withUserObject($('#button-bar').get())
.getSettings();
});
/**
* Callback function that populates the notification options using
* previously saved values.
*
* @param {Object} settings The saved settings from the client.
*/
function loadSettings(settings) {
$('#api-url').val(settings.apiurl);
$('#secret-key').val(settings.secretkey);
$('#email-type').val(settings.emailtype);
// Fill the respondent email select box with the
// titles given to the form's text Items. Also include
// the form Item IDs as values so that they can be
// easily recovered during the Save operation.
for (var i = 0; i < settings.textItems.length; i++) {
var option = $('<option>').attr('value', settings.textItems[i]['id'])
.text(settings.textItems[i]['title']);
$('#respondent-email').append(option);
}
$('#respondent-email').val(settings.respondentEmailItemId);
// Fill the respondent email select box with the
// titles given to the form's text Items. Also include
// the form Item IDs as values so that they can be
// easily recovered during the Save operation.
for (var i = 0; i < settings.textItems.length; i++) {
var option = $('<option>').attr('value', settings.textItems[i]['id'])
.text(settings.textItems[i]['title']);
$('#respondent-name').append(option);
}
$('#respondent-name').val(settings.respondentNameItemId);
for (var i = 0; i < settings.subteam.length; i++) {
var option = $('<option>').attr('value', settings.listItems[i]['id'])
.text(settings.listItems[i]['title']);
$('#subteam').append(option);
}
$('#subteam').val(settings.subteam);
}
/**
* Collects the options specified in the add-on sidebar and sends them to
* be saved as Properties on the server.
*/
function saveSettingsToServer() {
this.disabled = true;
$('#status').remove();
var settings = {};
settings.apiurl = $('#api-url').val();
settings.secretkey = $('#secret-key').val();
settings.emailtype = $('#email-type').val();
settings.respondentEmailItemId = $('#respondent-email').val();
settings.respondentNameItemId = $('#respondent-name').val();
settings.subteam = $('#subteam').val();
// Save the settings on the server
google.script.run
.withSuccessHandler(
function(msg, element) {
showStatus('Saved settings', $('#button-bar'));
element.disabled = false;
})
.withFailureHandler(
function(msg, element) {
showStatus(msg, $('#button-bar'));
element.disabled = false;
})
.withUserObject(this)
.saveSettings(settings);
}
/**
* Inserts a div that contains an status message after a given element.
*
* @param {String} msg The status message to display.
* @param {Object} element The element after which to display the Status.
*/
function showStatus(msg, element) {
var div = $('<div>')
.attr('id', 'status')
.attr('class','error')
.text(msg);
$(element).after(div);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment