Find & Replace - Google Sheets Script after GetData - google-apps-script

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;

Related

How to return google sheet values using doPost using x-www-form-urlencoded?

I try to use google sheets to write and read some data using post requests,
the writing part works, but it never returns any value back.
function doPost(e) { return handleResponse(e); }
function handleResponse(e) {
// Get public lock, one that locks for all invocations
// (https://gsuite-developers.googleblog.com/2011/10/concurrency-and-google-apps-script.html)
var lock = LockService.getPublicLock();
// Allow the write process up to 2 seconds
lock.waitLock(2000);
try {
// Generate a (not very good) UUID for this submission
var submissionID = e.parameter.id || 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
// Open the spreadsheet document and select the right sheet page
var sheetName = e.parameter.sheet_name|| 'Sheet1';
var doc = SpreadsheetApp.openById(SCRIPT_PROP.getProperty("key"));
var sheet = doc.getSheetByName(sheetName);
//get information out of post request
var action = e.parameter.action || 'save';
var pName = e.parameter.name;
var rowNumber = findRow(pName,sheetName);
var headRow = e.parameter.header_row || 1;
var headers = sheet.getRange(headRow, 1, 1, sheet.getLastColumn()).getValues()[0];
// check for action is loading
if(action == 'load'){
//check if the name has data
if (rowNumber){
//loads all the give values out of the parameters
var answer = [];
Logger.log('hadders: ' + headers);
for (i in headers) {
if (e.parameter[headers[i].toLowerCase()] !== undefined) {
var val = sheet.getRange(rowNumber, 1, 1,sheet.getLastColumn()).getValues()[0][i];
answer.push(val);
}
}
Logger.log('answer: '+ answer);
// Return result in JSON
return ContentService
.createTextOutput({body:{parameter:{answer}}})
.setMimeType(ContentService.MimeType.JSON)
;
}else{
// return error name wasn't found in sheet.
return ContentService
.createTextOutput("can't find Name")
.setMimeType(ContentService.MimeType.TEXT)
;
}
}
The logger returns all the right values,
but logging the return value from this function ends up in an empty object.
I tried just making my own return object like:
return ContentService
.createTextOutput({body={parameter={answer=JSON.stringify(answer)}}})
.setMimeType(ContentService.MimeType.TEXT)
;
I know that I need to use &= instead of ,: but it still returned nothing.
In your script, how about modifying as follows?
From:
return ContentService
.createTextOutput({body:{parameter:{answer}}})
.setMimeType(ContentService.MimeType.JSON)
To:
return ContentService
.createTextOutput(JSON.stringify({body:{parameter:{answer}}}))
.setMimeType(ContentService.MimeType.JSON)
In the case of createTextOutput({body:{parameter:{answer}}}), the object cannot be directly put. So I thought that it is required to convert it to the string.
Note:
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful this.
You can see the detail of this in the report of "Redeploying Web Apps without Changing URL of Web Apps for new IDE".

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.

This script does not populate sheet after parsing retrieved data

I hope this is well explained. First of all, sorry because my coding background is zero and I am just trying to "fix" a previously written script.
Problem The script does not populate sheet after parsing retrieved data if the function is triggered by timer and the sheet is not open in my browser .
The script works OK if run it manually while sheet is open.
Problem details:
When I open the sheet the cells are stuck showing "Loading" and after a short time, data is written.
Expected behavior is to get the data written no matter if I don't open the sheet.
Additional info: This is how I manually run the function
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [
{name: "Manual Push Report", functionName: "runTool"}
];
sheet.addMenu("PageSpeed Menu", entries);
}
Additional info: I set the triggers with Google Apps Script GUI See the trigger
Before posting the script code, you can see how the cells look in the sheet:
Script code
function runTool() {
var activeSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Results");
var rows = activeSheet.getLastRow();
for(var i=3; i <= rows; i++){
var workingCell = activeSheet.getRange(i, 2).getValue();
var stuff = "=runCheck"
if(workingCell != ""){
activeSheet.getRange(i, 3).setFormulaR1C1(stuff + "(R[0]C[-1])");
}
}
}
// URL check //
function runCheck(Url) {
var key = "XXXX Google PageSpeed API Key";
var strategy = "desktop"
var serviceUrl = "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=" + Url + "&key=" + key + "&strategy=" + strategy +"";
var array = [];
var response = UrlFetchApp.fetch(serviceUrl);
if (response.getResponseCode() == 200) {
var content = JSON.parse(response.getContentText());
if ((content != null) && (content["lighthouseResult"] != null)) {
if (content["captchaResult"]) {
var score = content["lighthouseResult"]["categories"]["performance"]["score"];
} else {
var score = "An error occured";
}
}
array.push([score,"complete"]);
Utilities.sleep(1000);
return array;
}
}
You can try the code using the sheet below with a valid Pagespeed API key.
You only need to add a Trigger and wait for it's execution while the sheet is not open in your browser
https://docs.google.com/spreadsheets/d/1ED2u3bKpS0vaJdlCwsLOrZTp5U0_T8nZkmFHVluNvKY/copy
I suggest you to change your algorithm. Instead of using a custom function to call UrlFetchApp, do that call in the function called by a time-driven trigger.
You could keep your runCheck as is, just replace
activeSheet.getRange(i, 3).setFormulaR1C1(stuff + "(R[0]C[-1])");
by
activeSheet.getRange(i, 3, 1, 2).setValues(runCheck(url));
NOTE
Custom functions are calculated when the spreadsheet is opened and when its arguments changes while the spreadsheet is open.
Related
Cache custom function result between spreadsheet opens

Timed based triggered Twilio SMS from Google Sheets

I am using the script to send time-based SMS for Date/Time row in a google sheet using Twilio example from this tutorial
https://github.com/jmadden/twilio-sms-for-google-sheets/blob/master/README.md
I have set up everything like on the tutorial and it works, sort off. Instead of the script sending the message at the exact time from the cells using the triggers, it sends an SMS at each trigger execution until the exact time has passed. So if the trigger is set to 5 min it will send an SMS every 5 minutes instead of checking when to send the SMS. Can anybody help with this issue?
// Gets predefined properties for this script. See: File -> Project properties -> Script properties
var prop = PropertiesService.getScriptProperties();
// Returns a specific Google Sheet by URL.
var spreadSheet = SpreadsheetApp.openByUrl(prop.getProperty('spreadsheetUrl'));
// Defines how we want the date to be formatted for scheduling.
var dateFormat = prop.getProperty('DateFormat');
// Returns the specific sheet/tab inside a Google Sheed doc.
var sheet = spreadSheet.getSheets()[0];
// The Row where data starts. This skips the headers row.
var startRow = 2;
// Returns the number of rows with values in this sheet.
var numRows = sheet.getLastRow() - 1;
// Returns all the data to be processed in this sheet. i.e. to # and message body.
var data = sheet.getRange(startRow, 1, numRows, 4).getValues();
// Whenever this function is called it will send an SMS using Twilio
// if all of the required parameters are passed into the function.
function sendSms(to, body) {
// URL used for sending request to Twilio's Messages API. Be sure to include your Account SID
var messages_url = "https://api.twilio.com/2010-04-01/Accounts/"+prop.getProperty('ACCOUNT_SID')+"/Messages.json";
// Parameters needed to send an SMS.
var payload = {
"To": "+"+to,
"Body" : body,
"From" : prop.getProperty('TWNUM')
};
// Contains the method of communicating with the API (POST) and the parameters needed to build a message.
var options = {
"method" : "post",
"payload" : payload
};
// Authorize your account to send this message.
options.headers = {
"Authorization" : "Basic " + Utilities.base64Encode(prop.getProperty('ACCOUNT_SID')+":"+prop.getProperty('AUTH_TOKEN'))
};
UrlFetchApp.fetch(messages_url, options)
}
// This function loops through your Google Sheet and uses the sendSms() function to send messages.
function sendAll() {
// For loop through your Google Sheet's data.
for (i in data) {
var row = data[i];
// Returns the Google Sheet's timezone info as an object.
var when = Moment.moment.tz(data[i][3], dateFormat, spreadSheet.getSpreadsheetTimeZone());
var now = new Date();
// Compares the current time to the "When" time in the sheet.
// Sends SMS if "When" time is older or equal to the current time.
if (isNaN(when) || !when.isValid() || (when.toDate() >= now)){
// Try sending SMS.
try {
response_data = sendSms(row[0], row[1]);
status = "sent";
} catch(err) {
Logger.log(err);
status = "error";
}
sheet.getRange(startRow + Number(i), 3).setValue(status);
}
}
}
// Runs the full script.
function runApp() {
sendAll();
}
I could not make sense of if (isNaN(when) || !when.isValid() || (when.toDate() >= now)) and why it puts the status string in place of the date/time with sheet.getRange(startRow + Number(i), 3).setValue(status); but here is something else you could try.
As there is a status column (the third one) which gets set with "send" after the row is processed, this can be used to prevent running the "send message" code again for the given row.
In the code you posted, replace the sendAll() function with the code below.
function sendAll() {
// For loop through your Google Sheet's data.
for (i in data) {
var row = data[i];
// Returns the Google Sheet's timezone info as an object.
var when = Moment.moment.tz(data[i][3], dateFormat, spreadSheet.getSpreadsheetTimeZone());
var now = new Date();
// Compares the current time to the "When" time in the sheet.
// Sends SMS if current time is older or equal to the time in the sheet.
if ((data[i][2] != "sent") && (now >= when.toDate())) {
// Try sending SMS.
try {
response_data = sendSms(row[0], row[1]);
status = "sent";
} catch (err) {
Logger.log(err);
status = "error";
}
sheet.getRange(startRow + Number(i), 3).setValue(status);
}
}
}