Using an older deployment after update - google-apps-script

I'm trying to make a wedding website using this person's blog/github: https://blog.rampatra.com/wedding-website. I had this to where when someone filled out the form on my website, I received an email and my google spreadsheet updated. Then we needed a shorter invite code, so I changed the logic. Now, when I enter the new code, I get an error message saying the new code is invalid and the old code still works even though anything other than the new code should be throwing an error.
I have an HTML form on my index.html exactly like the original.
I also have a section in my scripts.js that was identical to the example:
/********************** RSVP **********************/
$('#rsvp-form').on('submit', function (e) {
e.preventDefault();
var data = $(this).serialize();
$('#alert-wrapper').html(alert_markup('info', '<strong>Just a sec!</strong> We are
saving your details.'));
if (MD5($('#invite_code').val()) !== 'b0e53b10c1f55ede516b240036b88f40'
&& MD5($('#invite_code').val()) !== '2ac7f43695eb0479d5846bb38eec59cc') {
$('#alert-wrapper').html(alert_markup('danger', '<strong>Sorry!</strong> Your
invite code is incorrect.'));
} else {
$.post
('https://script.google.com/macros/s/<my deployment id>-/exec', data)
.done(function (data) {
console.log(data);
if (data.result === "error") {
$('#alert-wrapper').html(alert_markup('danger', data.message));
} else {
$('#alert-wrapper').html('');
$('#rsvp-modal').modal('show');
}
})
.fail(function (data) {
console.log(data);
$('#alert-wrapper').html(alert_markup('danger', '<strong>Sorry!</strong>
There is some issue with the server. '));
});
}
});
For my google sheet, I used the example script and initially only changed the email address
var TO_ADDRESS = "youremailaddress#gmail.com"; // email to send the form data to
/**
* This method is the entry point.
*/
function doPost(e) {
try {
Logger.log(e); // the Google Script version of console.log see: Class Logger
var mailData = e.parameters; // just create a slightly nicer variable name for the
data
if (mailData.invite_code != "271117") { // validate invite code before saving data
Logger.log("Incorrect Invite Code");
return ContentService
.createTextOutput(JSON.stringify({"result":"error", "message": "Sorry, your
invite code (" + mailData.invite_code + ") is incorrect."}))
.setMimeType(ContentService.MimeType.JSON);
}
record_data(e);
MailApp.sendEmail({
to: TO_ADDRESS,
subject: "A new guest RSVP'd for your wedding",
replyTo: String(mailData.email), // This is optional and reliant on your form
actually collecting a field named `email`
htmlBody: formatMailBody(mailData)
});
return ContentService // return json success results
.createTextOutput(JSON.stringify({"result":"success","data":
JSON.stringify(e.parameters) }))
.setMimeType(ContentService.MimeType.JSON);
} catch(error) { // if error return this
Logger.log(error);
return ContentService
.createTextOutput(JSON.stringify({"result":"error", "message": "Sorry, there is
an issue with the server."}))
.setMimeType(ContentService.MimeType.JSON);
}
}
/**
* This method inserts the data received from the html form submission
* into the sheet. e is the data received from the POST
*/
function record_data(e) {
Logger.log(JSON.stringify(e)); // log the POST data in case we need to debug it
try {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName('responses'); // select the responses sheet
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
var nextRow = sheet.getLastRow()+1; // get next row
var row = [ new Date().toUTCString() ]; // first element in the row should always
be a timestamp
// loop through the header columns
for (var i = 1; i < headers.length; i++) { // start at 1 to avoid Timestamp column
if(headers[i].length > 0) {
row.push(e.parameter[headers[i]]); // add data to row
}
}
// more efficient to set values as [][] array than individually
sheet.getRange(nextRow, 1, 1, row.length).setValues([row]);
}
catch(error) {
Logger.log(error);
Logger.log(e);
throw error;
}
finally {
return;
}
}
After I got the example working, I changed my Google AppScript to if(mailData.invite_code !="271117") to a new code and then hit deploy -> new deployment. I copied the new deployment ID and hit deploy. The new version seemed to deploy.
In scripts.js I changed:
(MD5($('#invite_code').val()) !== 'b0e53b10c1f55ede516b240036b88f40'
to the hash of the new code and
$.post
('https://script.google.com/macros/s/<my deployment id>-/exec', data)
to the new deployment id
I saved my code with the new deployment id, committed, and ran terraform apply and updated the S3 bucket. I went back to my website and completed the RSVP form again.
The website changes URL to one that has the json response and says "Sorry, your invite code (mycode) is incorrect". That code matches the code in my script exactly. If I fill out a new RSVP with the code from the example, it works as if I didn't change anything. When I check the 'executions' table in Apps Scripts, the deployment version kicked off is the older one.
Do I need to archive the old deployment? Is there a delay, and if there is a delay why isn't my scripts.js failing?

Related

Google apps script permissions issue

I've been trying to set up Google apps script with a spreadsheet getting values from Tag Manager and I've used this before so I know it is working.
This is the tutorial Im using - https://measureschool.com/google-sheets-tracking-google-tag-manager/
However, when I try to set this up now I am getting an error and it has always worked before. I have clicked also the permission to "allow" the app.
The error I get is this:
{"result":"error","error":{"name":"Exception"}}
This error is given simply if I create a new apps script and deploy it. When I click on the link to test it, it shows me this error and the sheet remains disfunctional.
I also tried just creating the most simplest app with just "myFunction" function inside as the default and that doesnt work either and gives this error:
Script function not found: doGet
This is so confusing. Such a simple problem. Always worked before. Never had problems like this before. It's bizarre. Would be grateful for any helps.
This is the code that gives me the "name: error" message if I put this in a app script it.
// Usage
// 1. Enter sheet name where data is to be written below
// 1. Enter sheet name and key where data is to be written below
var SHEET_NAME = "Sheet1";
var SHEET_KEY = "1jO5LaaIOfnAwkCCRpNPq0nee97ZjYh9D2YeJD_5OVys";
// 2. Run > setup
//
// 3. Publish > Deploy as web app
// - enter Project Version name and click 'Save New Version'
// - set security level and enable service (most likely execute as 'me' and access 'anyone, even anonymously)
//
// 4. Copy the 'Current web app URL' and post this in your form/script action
//
// 5. Insert column names on your destination sheet matching the parameter names of the data you are passing in (exactly matching case)
var SCRIPT_PROP = PropertiesService.getScriptProperties(); // new property service
// If you don't want to expose either GET or POST methods you can comment out the appropriate function
function doGet(e){
return handleResponse(e);
}
function doPost(e){
return handleResponse(e);
}
function handleResponse(e) {
var lock = LockService.getPublicLock();
lock.waitLock(30000); // wait 30 seconds before conceding defeat.
try {
// next set where we write the data - you could write to multiple/alternate destinations
var doc = SpreadsheetApp.openById(SHEET_KEY);
var sheet = doc.getSheetByName(SHEET_NAME);
// we'll assume header is in row 1 but you can override with header_row in GET/POST data
var headRow = e.parameter.header_row || 1;
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
var nextRow = sheet.getLastRow()+1; // get next row
var row = [];
// loop through the header columns
for (i in headers){
if (headers[i] == "Timestamp"){ // special case if you include a 'Timestamp' column
row.push(new Date());
} else { // else use header name to get data
row.push(e.parameter[headers[i]]);
}
}
// more efficient to set values as [][] array than individually
sheet.getRange(nextRow, 1, 1, row.length).setValues([row]);
// return json success results
return ContentService
.createTextOutput(JSON.stringify({"result":"success", "row": nextRow}))
.setMimeType(ContentService.MimeType.JSON);
} catch(e){
// if error return this
return ContentService
.createTextOutput(JSON.stringify({"result":"error", "error": e}))
.setMimeType(ContentService.MimeType.JSON);
} finally { //release lock
lock.releaseLock();
}
}
I resolved this because I made a silly mistake in that my spreadsheet didnt contain the values timestamp and any params in the 1st line.

Posting to Google Sheet is giving {"result":"error","error":{"name":"Exception"}}

I am trying to post to a google sheet using an html form, I followed a tutorial here, but I keep getting an error {"result":"error","error":{"name":"Exception"}} and the sheet is not being updated.
Here is code.gs
// original from: http://mashe.hawksey.info/2014/07/google-sheets-as-a-database-insert-with-apps-script-using-postget-methods-with-ajax-example/
// original gist: https://gist.github.com/willpatera/ee41ae374d3c9839c2d6
/**
* #OnlyCurrentDoc
*/
function doGet(e){
console.log("running");
return handleResponse(e);
}
// Usage
// 1. Enter sheet name where data is to be written below
var SHEET_NAME = "Sheet1";
// 2. Run > setup
//
// 3. Publish > Deploy as web app
// - enter Project Version name and click 'Save New Version'
// - set security level and enable service (most likely execute as 'me' and access 'anyone, even anonymously)
//
// 4. Copy the 'Current web app URL' and post this in your form/script action
//
// 5. Insert column names on your destination sheet matching the parameter names of the data you are passing in (exactly matching case)
var SCRIPT_PROP = PropertiesService.getScriptProperties(); // new property service
// If you don't want to expose either GET or POST methods you can comment out the appropriate function
function doPost(e){
console.log("running2");
return handleResponse(e);
}
function handleResponse(e) {
console.log("running3");
// shortly after my original solution Google announced the LockService[1]
// this prevents concurrent access overwritting data
// [1] http://googleappsdeveloper.blogspot.co.uk/2011/10/concurrency-and-google-apps-script.html
// we want a public lock, one that locks for all invocations
var lock = LockService.getPublicLock();
lock.waitLock(30000); // wait 30 seconds before conceding defeat.
try {
// next set where we write the data - you could write to multiple/alternate destinations
console.log("running4");
var doc = SpreadsheetApp.openById(SCRIPT_PROP.getProperty("key"));
console.log("running5");
var sheet = doc.getSheetByName(SHEET_NAME);
// we'll assume header is in row 1 but you can override with header_row in GET/POST data
var headRow = e.parameter.header_row || 1;
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
var nextRow = sheet.getLastRow()+1; // get next row
var row = [];
// loop through the header columns
for (i in headers){
if (headers[i] == "Timestamp"){ // special case if you include a 'Timestamp' column
row.push(new Date());
} else { // else use header name to get data
row.push(e.parameter[headers[i]]);
//row.push("Test")
}
}
// more efficient to set values as [][] array than individually
sheet.getRange(nextRow, 1, 1, row.length).setValues([row]);
// return json success results
return ContentService
.createTextOutput(JSON.stringify({"result":"success", "row": nextRow}))
.setMimeType(ContentService.MimeType.JSON);
} catch(e){
// if error return this
return ContentService
.createTextOutput(JSON.stringify({"result":"error", "error": e}))
.setMimeType(ContentService.MimeType.JSON);
} finally { //release lock
lock.releaseLock();
}
}
function setup() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
SCRIPT_PROP.setProperty("key", doc.getId());
}
I put in the console.logs to see where the issue was and it seems to have to with the line var doc = SpreadsheetApp.openById(SCRIPT_PROP.getProperty("key")); as "running4" is logged, but "running5" is not.
Figured it out, it was a permissions error caused by the
/**
* #OnlyCurrentDoc
*/
In the script. I had added that because I keep getting a security error when trying to give my script access, but I solved that with this answer.

Triggering GAS function via URL

Very new to this, but giving it a shot. I am trying to set up an Arduino motion sensor to trigger a script. At this point, my goal is to trigger a script via URL. I found this code below that I am working through, but I continue to get this error when running/debugging.
TypeError: Cannot read property "parameter" from undefined. (line 4, file "Code")
I have been looking into e.parameter object, but have not been able to make any headway
function doGet(e) {
Logger.log(e)
var passedString,whatToReturn;
passedString = e.parameter.searchStringName;
if (passedString === 'tylog') {
whatToReturn = tylog(); //Run function One
};
return ContentService.createTextOutput(whatToReturn);
};
var mns = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Monster")
var tyl = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("tyLog")
var tyd = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("tyData")
var twl = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("twLog")
var twd = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("twData")
var tym = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("tyMaster")
var twm = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("twMaster")
var test = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("test")
var tydate = tyd.getRange('A2');
var tydur = tyd.getRange(2, 2);
// Start functions
function start() {
tyl.getRange('A1').setValue(new Date());
twl.getRange('A1').setValue(new Date());
}
//Log Typhoon ride
function tylog() {
tyl.getRange(tyl.getLastRow() + 1, 1).setValue(new Date());
}
//Log Twister ride
function twlog() {
twl.getRange(twl.getLastRow() + 1, 1).setValue(new Date());
}
//Send Data to both logs and clear
function tyclear() {
tyd.getRange('A2:H2').copyTo(tym.getRange(tym.getLastRow() + 1, 1), {contentsOnly: true});
twd.getRange('A2:H2').copyTo(twm.getRange(twm.getLastRow() + 1, 1), {contentsOnly: true});
tyl.getRange('A1:A100').clearContent();
twl.getRange('A1:A100').clearContent();
}
URL Request:
https://script.google.com/macros/s/AKfycbxC5zYevR1IhfFcUMjmIqUaQ1dKNHTm4mhmWBq_Rc9HgemJQ6Q/exec?searchStringName=tylog
I put this into a new project by itself and it still returned undefined​.
function doGet(e) {
var passedString,whatToReturn;
passedString = e.parameter.searchStringName;
if (passedString === 'functionOne') {
whatToReturn = functionOne(); //Run function One
};
return ContentService.createTextOutput(whatToReturn);
};
function functionOne() {
var something;
return ContentService.createTextOutput("Hello, world!"); }
I believe that your URL should be https://script.google.com/macros/s/AKfycbxC5zYevR1IhfFcUMjmIqUaQ1dKNHTm4mhmWBq_Rc9HgemJQ6Q/exec?searchStringName=functionOne
After pondering this question for a while it makes no sense to require a return from functionOne. I was getting the client server communication mixed up with the Get request process. For most Get requests the request suggests some type of response since in general we're looking for some type of content to be displayed. In this situation that may not be required since the requestor is a machine.
The use of e.parameter.paramname; just enables us to send key/value pairs from within our querystring that we can recover to redirect our server actions.
2020 UPD:
Upon revisiting the question, I noticed that the OP runs the doGet trigger in the context of script editor, hence the e becoming undefined (as it is only constructed when a request hits the URL with an HTTP request with GET method).
Thus, the answer to the debugging part is:
When running a trigger manually from the script editor, event object will be unavailable
The answer to the running part is as a result of an extended discussion:
When assigning a result of the function, one has to put the return statement inside the function, and the tylog function did not return anything.
Also note that any change to a Web App code, unless accessing it via /dev endpoint (i.e. via /exec endpoint), won't be available until after redeployment.
References
Web Apps guide

Find & Replace - Google Sheets Script after GetData

I'm having some trouble with my script. Basically, that works getting data from one course and inputting the values to one Sheet. That's working perfectly. But when one of my students input 'enter' command at that course, I have trouble to read it in Excel.
SO, I have to find (it's a regular Expression: \r\n|\n|\r) and replace the enter at Google Spreadsheet and change it for "; ". Works perfectly doing it manually, but I can't do it by script. Here the piece:
// 1. Enter sheet name where data is to be written below
var SHEET_NAME = "DATA";
// 2. Run > setup
//
// 3. Publish > Deploy as web app
// - enter Project Version name and click 'Save New Version'
// - set security level and enable service (most likely execute as 'me' and access 'anyone, even anonymously)
//
// 4. Copy the 'Current web app URL' and post this in your form/script action
//
// 5. Insert column names on your destination sheet matching the parameter names of the data you are passing in (exactly matching case)
var SCRIPT_PROP = PropertiesService.getScriptProperties(); // new property service
// If you don't want to expose either GET or POST methods you can comment out the appropriate function
function doGet(e){
return handleResponse(e);
}
function doPost(e){
return handleResponse(e);
}
function handleResponse(e) {
// shortly after my original solution Google announced the LockService[1]
// this prevents concurrent access overwritting data
// [1] http://googleappsdeveloper.blogspot.co.uk/2011/10/concurrency-and-google-apps-script.html
// we want a public lock, one that locks for all invocations
var lock = LockService.getPublicLock();
lock.waitLock(30000); // wait 30 seconds before conceding defeat.
try {
// next set where we write the data - you could write to multiple/alternate destinations
var doc = SpreadsheetApp.openById(SCRIPT_PROP.getProperty("CHANGED BY SECURITY REASON"));
var sheet = doc.getSheetByName(SHEET_NAME);
// we'll assume header is in row 1 but you can override with header_row in GET/POST data
var headRow = e.parameter.header_row || 1;
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
var nextRow = sheet.getLastRow()+1; // get next row
var row = [];
// loop through the header columns
for (i in headers){
if (headers[i] == "Timestamp"){ // special case if you include a 'Timestamp' column
row.push(new Date());
} else { // else use header name to get data
row.push(e.parameter[headers[i]]);
}
}
// more efficient to set values as [][] array than individually
sheet.getRange(nextRow, 1, 1, row.length).setValues([row]);
// return json success results
return ContentService
.createTextOutput(JSON.stringify({"result":"success", "row": nextRow}))
.setMimeType(ContentService.MimeType.JSON);
} catch(e){
// if error return this
return ContentService
.createTextOutput(JSON.stringify({"result":"error", "error": e}))
.setMimeType(ContentService.MimeType.JSON);
} finally { //release lock
lock.releaseLock();
}
}
function setup() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
SCRIPT_PROP.setProperty("1Un5A61M8CJDBGDAB-Tx-lYgKYaVB2RSfn9QAQ5Q-sZs", doc.getId());
}
I tried this and worked well, but needs a trigger that's play only minute per minute. Not ok.. And change just the first value from the cell. Sometimes there's more than one at same cell:
function Replace() {
var redeletar = new RegExp("\r\n|\n|\r");
var range = SpreadsheetApp.getActiveSheet()
.getRange('A1:CH');
range.setValues(range.getValues()
.map(function (r) {
return r.map(function (c) {
//Replace string
return c.toString().replace(redeletar, ";");
});
}));
}
Any idea to play this script at same time after the Sheet receive de data from my course AND play it recursively?
Thanks so mucho
I think all you need is the global flag as shown below:
function Replace()
{
var re = /(\r\n|\n|\r")/g;
var range = SpreadsheetApp.getActiveSheet().getActiveRange();
range.setValues(range.getValues().map(function (r){return r.map(function (c){return c.toString().replace(re, ";");});}));
}
I think this will make all of the replacements rather than just the first ones;

How to pass parameters from one Google-Apps-Script to another and execute?

Goal is to pass data from Google Apps Script A to Google Apps Script
B.
Script A is published with execute as user permissions.
Script B is published with execute as me permissions (owner).
At the very least I want to be able to pass Session.getActiveUser.getEmail() from script A to script B.
This is what I have so far...
Script A
// Script-as-app template.
function doGet() {
var app = UiApp.createApplication();
var button = app.createButton('Click Me');
app.add(button);
var handler = app.createServerHandler('myClickHandler');
button.addClickHandler(handler);
return app;
}
function myClickHandler(e) {
var url = "https://script.google.com/macros/s/AKfycbzSD3eh_SDnbA4a7VCkctHoMGK8d94SAPV2IURR3pK7_MwLXIb4/exec";
var payload = {
name : "Gene",
activeUser : Session.getActiveUser().getEmail(),
time : new Date()
};
var params = {
method : "post",
payload : payload
}
Logger.log("Hello World!");
var HTTPResponse;
try{
HTTPResponse = UrlFetchApp.fetch(url, params);
}catch(e){
Logger.log(e);
}
return HTTPResponse;
}
Script B
function doPost(e){
if(typeof e === 'undefined')
return;
var app = UiApp.createApplication();
var panel = app.add(app.createVerticalPanel());
for(var i in e.parameter){
panel.add(app.createLabel(i + ' : ' + e.parameter[i]));
Logger.log(i + ' : ' + e.parameter[i]);
}
ScriptProperties.setProperty('Donkey', 'Kong');
return app;
}
output
Going to script A here the page loads the button. Clicking the button causes "Hello World!" to be logged in Script A's project log but the log of Script B's project remains empty.
TryCatch does not log any error.
I believe your problem is due that you try to pass as a response argument a uiapp element.
here a little variation of your script in html service.
the demo
the script:
// #### Part A
function doGet(e) {
var html ="<input type='text' id='text' /><input type='button' onclick='myClick()' value='submit'>"; // a text to be passed to script B
html+="<div id='output'></div>"; // a place to display script B answer
html+="<script>";
html+="function myClick(){google.script.run.withSuccessHandler(showResults).myClickHandler(document.getElementById('text').value);}"; // handler to do the job in script A
html+="function showResults(result){document.getElementById('output').innerHTML = result;}</script>"; // function to show the result of the urlfetch (response of script B)
return HtmlService.createHtmlOutput(html);
}
function myClickHandler(text) {
var url = ScriptApp.getService().getUrl();
var payload = {
name : "Gene",
text : text,
time : new Date()
};
var params = {
method : "post",
payload : payload
}
Logger.log("text: "+text);
var HTTPResponse;
try{
HTTPResponse = UrlFetchApp.fetch(url, params);
}catch(e){
Logger.log(e);
}
return HTTPResponse.getContentText();
}
// ###### Part B
function doPost(e){
if(typeof e === 'undefined'){
return "e was empty!!";
}
var htmlOut="<ul>";
for(var i in e.parameter){
htmlOut+="<li>"+i+ " : " + e.parameter[i]+"</li>";
if(i=="text"){
htmlOut+="<li> Text hash : "+Utilities.base64Encode(Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, e.parameter[i]))+"</li>";
}
}
htmlOut+="</ul>";
return ContentService.createTextOutput(htmlOut);
}
It is important to note that you won't have the ability to get the logger events of the script B (because it is triggered when you are not there - you are not the one who trigger script B. this is script A that's trigger script B and script A is not identified as "you" when it make a urlfetch). If you want to get the result of the script b logger you should return it to the script a.
It's important to note: again, when script A do the UrlFetch to script B it is not identified as "you" so the script B must accept to be opened by anyone (in the publish option under "Who has access to the app:" you need to select anyone even anonymous).
NB: i put everything in the same script for commodity (you can split that in two differents script it's not a problem) and because B part need to be accessed to anonymous persons I can't retrieve automatically the email adress in the part A so I changed a little bit what was done here.