Issue running an Installed Trigger in Google App Script - google-apps-script

Fairly new to app script so bare with me.
Wrote this massive script, then went to set it up on a times trigger and it just refuses to run. I've gone ahead an back tracked as much as I could, to get at least something to work, yet I can't even get a basic toast to appear on a minute interval.
This is the script I've built, which I'm running directly to enable the trigger:
function createTriggers() {
ScriptApp.newTrigger('testTime')
.timeBased()
.everyMinutes(1)
.create();
};
The function it's calling is super simple, I've used it a lot and change it a lot too:
var gSS = SpreadsheetApp.openById("this_1s/the.1d")
function testTime() {
var d = new Date()
var Start = d.getTime();
gSS.toast(Start, "Testing", 30)
};
So how it should work, and it does if I just call the 'testTime' function directly, is a little toast pop-up appears on the spreadsheet in question, and stays visible for 30s.
When I run the trigger function 'createTriggers', nothing happens..
Please help! All the code I wrote is for nothing if I can't get it to run on its own.. :(
***** EDIT - 08/04/20 - based on comments *****
It's possible this was an XY example, I tried to run a small segment of the original code which works when I run it directly, and its not working here either.. this snippit does not have any UI facing functions in it, so it shouldn't be the issue..
All i did was take the above trigger function and change the name to 'testClear', which calls to the following functions:
function testClear(){
sheetVars(1)
clearSheetData(sheetSPChange)
};
function sheetVars(numSprints) {
// returns the global vars for this script
try {
sheetNameSprints = "Name of Sprint Sheet"
sheetNameSPChange = "Name of Story Point Change Sheet"
sheetSprints = gSS.getSheetByName(sheetNameSprints)
sheetSPChange = gSS.getSheetByName(sheetNameSPChange)
arraySprints = iterateColumn(sheetSprints,"sprintIDSorted", 1, numSprints)
}
catch(err) {
Logger.log(err)
};
};
function iterateColumn(sheet, header, columnNum, numRows) {
// Create an array of first column values to iterate through
// numRows is an int, except for the string "all"
var gData = sheet.getDataRange();
var gVals = gData.getValues();
var gLastR = ""
var gArray = []
// check how many rows to iterate
if (numRows == "all") {
gLastR = gData.getLastRow();
}
else {
gLastR = numRows
};
// Iterate through each row of columnNum column
for (i = 1; i < gLastR; i++){
// iterate through
if(gVals[i][columnNum] !== "" && gVals[i][columnNum] !== header){
// push to array
gArray.push(gVals[i][columnNum]);
}
else if (gVals[i][columnNum] == "") {
break
};
};
return gArray
};
function clearSheetData(sheet) {
// Delete all rows with data in them in a sheet
try {
if (!sheet.getRange(sheet.getLastRow(),1).isBlank()){
sheet.getRange(2, 1, sheet.getLastRow()-1, sheet.getLastColumn()-1).clearContent()
Logger.log("Sheet cleared from old data.")
}
else {
sheet.deleteRows(2, sheet.getLastRow()-1)
Logger.log("Sheet rows deleted from old data.")
};
}
catch(err){
Logger.log(err)
emailLogs()
};
};
The 'emailLogs' function is a basic MailApp so i get notified of an issue with the script:
function emailLogs() {
// Email Nikita the loggs of the script on error
var email = "my work email#jobbie"
var subject = "Error in Sheet: " + gSS.getName()
var message = Logger.getLog()
MailApp.sendEmail(email, subject, message)
};
Thanks to a comment I've now discovered the executions page!! :D This was the error for the edited script.
Aug 4, 2020, 10:48:18 AM Error Exception: Cannot call
SpreadsheetApp.getUi() from this context.
at unknown function

To show a toast every certain "time" (every n iterations) add this to the for loop
if (!((i+1) % n)) spreadsheet.toast("Working...")
From the question
Aug 4, 2020, 10:48:18 AM Error Exception: Cannot call SpreadsheetApp.getUi() from this context. at unknown function
The above error means that your time-drive script is calling a method that can only be executed when a user has opened the spreadsheet in the web browser.
In other words, toast can't be used in a time-driven trigger. The solution is to use client-side code to show that message every minute. To do this you could use a sidebar and a recursive function that executes a setTimeout
References
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals

Based on all the comments, and new things I'd learned from that..:
I'd been calling to a global variable for my onOpen function:
var gUI = SpreadsheetApp.getUi();
even though I wasn't using it for the trigger, since it was global it tried to run and then failed.
I moved the actual definition of gUI into the onOpen function, and tried again and it worked.
Thank you all for the support!

Related

Change from manually triggered script to time-driven trigger using Google Apps script

I have an app script for Google Sheets that works when I trigger it manually, but I would like it to be time driven, running automatically once an hour. I've tried setting that up using the Apps Script UI, and it looked like this:
Trigger
But I consistently get this error message:
Exception: Cannot call SpreadsheetApp.getUi() from this context.
at unknown function
I also tried writing the time trigger into the script, but kept getting an error. Here's the current script, which does work fine when I trigger it manually.
var ui = SpreadsheetApp.getUi();
function onOpen(e){
ui.createMenu("Gmail Manager").addItem("Get Emails by Label", "getGmailEmails").addToUi();
}
function getGmailEmails(){
var label = GmailApp.getUserLabelByName('EmailsToBeExported');
var threads = label.getThreads();
for(var i = threads.length - 1; i >=0; i--){
var messages = threads[i].getMessages();
for (var j = 0; j <messages.length; j++){
var message = messages[j];
extractDetails(message);
}
threads[i].removeLabel(label);
}
}
function extractDetails(message){
var dateTime = message.getDate();
var subjectText = message.getSubject();
var senderDetails = message.getFrom();
var bodyContents = message.getPlainBody();
var activeSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
activeSheet.appendRow([dateTime, senderDetails, subjectText, bodyContents]);
}`
You can not use SpreadsheetApp.getUi() from a time driven trigger. Change you triggers to:
You can also maintain the usability by altering the code like so:
try {
const UI = SpreadsheetApp.getUi();
} catch (err) {
const UI = {
alert: function() {return;},
prompt: function() {return true;},
button: {YES: false},
ButtonSet: {YES_NO: false}
}
}
You'll have to set parameters for the UI manually, but what it does it makes it so that your code runs and handles things as you expect.
I specifically put the prompt as true and the buttons as false. That way, if I am checking if they are equal to a specific button, it will always evaluate false.
e.g.
var result = UI.prompt("Some title", "Some question", UI.ButtonSet.YES_NO);
if (result == UI.Button.YES) {//always false
//This part does not evaluate
}
You'd have to make sure that you can do this consistently throughout your script. You may like to change the assignments of the values (depending on your conditional statements) to strings, numbers, or other unique things, or change your conditional statements to include "===" instead, etc. The prompts are the trickiest ones. With this change in code, you can have the UI prompts and alerts when you run the script from within Google Sheets. It will skip them when you run it from outside, such as timed executions or other things that would activate your script externally.

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.

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

"Cannot call SpreadsheetApp.getUi() from this context" error while not using getUi() on time-based trigger

I am trying to run a function every night that checks a list of dates and does work if it finds that a date has passed and all the checkboxes on that row are checked. Every day, though, I get an email saying
"Cannot call SpreadsheetApp.getUi() from this context. (line 172, file "Code")".
The weird thing is that I don't use getUi() anywhere in my CheckHireDates function and the line that it specifies is not even in the function that is supposed to run. Line 172 is in my onEdit function which also doesn't use getUi(). Can anybody help me understand why I'm getting this error?
I did use getUi in my onOpen function, so I commented it out when I started having this problem but it still didn't fix anything.
function CheckHireDates() {
var spreadsheet = SpreadsheetApp.getActive();
var PaycomSheet = spreadsheet.getSheetByName('Paycom');
var TechSheet = spreadsheet.getSheetByName("Tech Key");
var SoftwareTracker = spreadsheet.getSheetByName('Software Tracking');
var range = "R2:R";
var Hvals = PaycomSheet.getRange("H2:H").getValues();
var Hlast = Hvals.filter(String).length;
var data = PaycomSheet.getRange(range).getValues();
var today = new Date().toLocaleDateString();
for(var i = 0; i < Hlast;i++)
{
Hvals[i].toLocaleString();
if(Hvals[i] <= today && (PaycomSheet.getRange('R' + (i+2)).getValue() == true))
{
var fullName = PaycomSheet.getRange('D' + (i+2)).getValue();
var techRow = findMatchingRow(fullName, "Tech Key");
var softwareRow = findMatchingRow(fullName, "Software Tracking");
var position = PaycomSheet.getRange('G' + (i+2)).getValue();
TechSheet.getRange('E' + techRow).setValue(1);
SoftwareTracker.getRange('G' + softwareRow).setValue(1);
if (position == "Pre-Employment, Initial")
{
position = "Initial";
}
else if (position == "Pre-Employment, Ace")
{
position = "Route";
}
else if (position == "Pre-Employment, Expert")
{
position = "Route";
}
else
{
Logger.log("Position not known");
}
TechSheet.getRange('G' + techRow).setValue(position);
SoftwareTracker.getRange('D' + softwareRow).setValue(position);
SoftwareTracker.getRange('H' + softwareRow + ':M' + softwareRow).setValue(2);
if (position !== "Initial")
{
SoftwareTracker.getRange('N' + softwareRow).setValue(2);
}
}
}
}
I had that problem and found it involved another google script in the project set up by a different user. The error message had a mixture of details from my script and the other script:
myFunctionName Cannot call SpreadsheetApp.getUi() from this context.
(line 2, file "OtherScript")
Line 2 of the other script did use getUi()
var app = SpreadsheetApp.getUi();
It seemed that when my script ran (triggered by onChange event) then the other script would also get run (maybe also triggered by a change event). The other script set up some UI elements, some simple menus. Normally that caused no problem. However, SpreadsheetApp.getUi() only works in the context of there being a current instance of an open editor (see https://developers.google.com/apps-script/reference/base/ui). So if a change event happened without an open editor then it fails, causing the error.
I resolved the problem by slightly modifying the other script to catch the problem:
try {
app = SpreadsheetApp.getUi();
} catch (error) {}
if (app != null) {
//Use app...
}
A different approach that might also have worked is to speak with the person and how their script was triggered, and see if they'd change it to trigger by onOpen event that just occurs when someone opens a spreadsheet and hence there is a Ui context.
So, I think your problem would be coming from SpreadsheetApp.getUi() in a different script in the project. See if the error message mentions a different file, like mine did. When your script runs in the night, there would be no context, which explains why the error occurs at that time.

The coordinates or dimensions of the range are invalid

No matter what i do, i get the error in the title. If i replace my script with the standard
function doGet(e) {
var params = JSON.stringify(e);
return HtmlService.createHtmlOutput(params);
}
from Googles very own example: https://developers.google.com/apps-script/guides/web#url_parameters i have to run it once (inside the script editing thingy) to not show the error anymore, but when i put my code back in place it stays at the output of that dummy function. Probably because i cannot run my own function without errors (see below).
Settings for the web-app deployment:
version: 1
execute as: me
access: anyone, even anonymous
I think it has something to do with having to run a function once (or atleast that's what it looks like to me), but i cannot run my function since it's relying on url parameters. This already fails at e.parameter == undefined obviously.
function doGet(e) {
Logger.log(JSON.stringify(e));
var result = 'Ok';
if(e.parameter == undefined) {
result = 'no param';
}
else {
var id = 'id is normally here obviously not now';
var sheet = SpreadsheetApp.openById(id).getActiveSheet();
var newRow = sheet.getLastRow()+1;
var rowData = [];
for (var param in e.parameter) {
var value = stripQuotes(e.parameter[param]);
Logger.log(param + ': ' + e.parameter[param]);
switch (param) {
case 'timeStampBegin': //Parameter
rowData[0] = value; //Value in column B
break;
case 'timeStampEnd':
rowData[1] = value;
break;
default:
result = 'unsupported parameter';
}
}
Logger.log(JSON.stringify(rowData));
var newRange = sheet.getRange(newRow, 1, 1, rowData.length);
newRange.setValues([rowData]);
}
return ContentService.createTextOutput(result);
}
function stripQuotes( value ) {
return value.replace(/^["']|['"]$/g, "");
}
I appreciate any suggestions why this could be happening or how i could run my function with a test input.
You do need to run your script once in the IDE after you add any new services (e.g. HTML service, spreadsheet service, etc.) to approve those changes from a security standpoint.
To test your code without making tons of new versions and deploying each one (i.e. steps 2-3), you can either run it in the web IDE, or navigate to "Publish" > "Deploy as web app..." then click on "Test web app for your latest code." Unlike the live version, that link uses whatever code is currently saved, not the last deployed version.
I don't know of this is intentional by Google, but this is kinda ridiculous. Apparently a function has to be run once in the editor to be able to used. But here it is anyway:
Make an empty function which generates the needed input for your functions that you need to run, and call each. If they call each other, you only need to call the "parent"-function.
Make a new version under File->Manage versions.
Deploy your app again. Select the new version in the process.
I'm not sure 2+3 are really necessary, but for me it still ran the old dummy code (print back json) without doing that.