Google Apps Script: MySQL function causes error in onEdit [duplicate] - google-apps-script

I'm new to GAS and I struggle with the permission system.
I'm a normal Google drive user and I started a spreadsheet and tried to add some code to it. My code is working, but only if I'm in the code editor. I want to use the onEdit() function so it's important for me that it works within the sheet as well. When I ran my code in the editor for the first time it opened a new window where I needed to enter my credentials to allow the script, then it worked. If I do some changes to a cell in my sheet and the onEdit() function is triggered I receive an error message that says something like this(translated):
Exception: You are not permitted to call UrlFetchApp.fetch. Required permission: https://www.googleapis.com/auth/script.external_request
In the editor I displayed the manifest file and added the permission to the oauthScopes but within the sheet I still receive the message. This is how my code looks like (simplified):
function onEdit(e)
{
var data = {
'key1': 'value1',
'key2': 'value2'
};
var options = {
'method' : 'post',
'contentType': 'application/json',
'payload' : JSON.stringify(data)
};
try{
var response = UrlFetchApp.fetch('https://a-working-url.com', options); //error happening in this line
//some more data wizardry
}catch(error)
{
Browser.msgBox(error)
}
}
Any ideas how I can open this permission screen in my sheet or any hints how to solve it in a different way? I want to create a sheet with some code running in the back online. I want to share the sheet with some friends, tried it with Excel and VBA before until I realized that it's not working with Excel Online, so I switched to GAS.

onEdit(), like all simple triggers, is bound by the following restrictions (see official documentation):
The script must be bound to a Google Sheets, Slides, Docs, or Forms file, or else be an add-on that extends one of those
applications.
They do not run if a file is opened in read-only (view or comment) mode.
Script executions and API requests do not cause triggers to run. For example, calling Range.setValue() to edit a cell does not cause
the spreadsheet's onEdit trigger to run.
They cannot access services that require authorization. For example, a simple trigger cannot send an email because the Gmail
service requires authorization, but a simple trigger can translate
a phrase with the Language service, which is anonymous.
They can modify the file they are bound to, but cannot access other files because that would require authorization.
They may or may not be able to determine the identity of the current user, depending on a complex set of security restrictions.
They cannot run for longer than 30 seconds.
In certain circumstances, editor add-ons run their onOpen(e) and onEdit(e) simple triggers in a no-authorization mode that presents some additional complications. For more information, see the guide
to the add-on authorization lifecycle.
Simple triggers are subject to Apps Script trigger quota limits.
The ones highlighted in bold apply to your question.
Basically, it boils down to this - UrlFetchApp.fetch() is a service that requires authorization, so you won't be able to execute it from your onEdit(e) trigger, even if you have its associated scope set in your manifest file.

Use installable trigger instead and write your own "onEdit" function (with a different name) that you bind to your installable trigger.
https://developers.google.com/apps-script/guides/triggers/installable#g_suite_application_triggers
This solved the issue for me.

Related

How to transfer data from webapp to addon

Project_1 is a container-bound script. A container is a readable spreadsheet (Template).
Code_1:
function doPost(e) {
return HtmlService.createHtmlOutput(JSON.stringify(e));
}
The user makes a copy of the Template, deploys the script (Project_1) as a webapp with permissions: "Execute as: Me, Who has access: Anyone". The user is the owner of Project_1.
Project_2 is a script deployed as an add-on. The user from point 1 is not the owner of Project_2.
Code_2:
function sendPost() {
var sheetURL = SpreadsheetApp.getActiveSpreadsheet().getUrl();
var webAppUrl = "https://script.google.com/macros/s/###/exec"; // 7: Part_1 - WebApp: Tester
// var auth = ScriptApp.getOAuthToken();
// var header = { 'Authorization': 'Bearer ' + auth };
var payload = { scriptName: 'updateData', sheetURL: 'sheetURL' };
var options = {
method: 'post',
// headers: header,
muteHttpExceptions: true,
payload: payload
};
var resp = UrlFetchApp.fetch(webAppUrl, options);
var respTxt = resp.getContentText();
console.log('resp: ' + respTxt);
}
function doPost(ev) {
var respWebapp = func(ev);
}
The user installs an add-on (Project_2).
The flow in the direction of addon -> webapp is fine: when sendPost() starts, it sends a request to the webapp and receives a response with the necessary data_1 in response.
The flow in the direction of "someone on the web" -> webapp also flows well: when requesting a webapp_url receives the transferred data_2.
I am trying to transfer data_2 to an addon.
I read a lot about scripts.run, but it seems that this option is not applicable in such a situation.
There is also nowhere to add an eventListener.
I would not want to deploy webapp from my account, so as not to spend my quota for simultaneous executions (<= 30).
Also I would not like to do a sidebar, etc. in the spreadsheet and try to screw eventListener to html. I assume that with this approach, the listener (if it is possible to add it there at all) will be active only when ui is active (the spreadsheet is open and the sidebar is active). Data can come at any time of the day and must be immediately transferred to the addon.
Added:
I feel like I'm stumped. Therefore I reaches out to the community in the hope that someone would suggest a workaround or a new strategy for this initial data. By initial data I mean provide the opportunity for more than 30 users to exchange messages in both directions Spreadsheet <--> External service (for example, Telegram) and at the same time not fall under the limit of 30 simultaneous script executions.
Added_2:
I'm going to assign a bounty, so I'm transferring here from the comments what is missing in the post and updating the question itself.
I rejected the option with immediate entry into the sheet, because this will cause constant calls to the spreadsheet and slow down the performance of the system as a whole.
I am aware of the existence of Google cloud functions and Google compute engine, but would like to stay within the free quotas.
QUESTION: How to transfer data from webapp to addon and execute func () or which workaround to use to achieve the goals?
Here is a list of your requirements:
Trigger add-on code to run from some external request, not using the add-on user interface or time based trigger.
Code runs from the user's account, using their quota
Run the add-on code regardless of whether the user is using the add-on or not. For example, their Google Sheet is closed, and the user may even be signed out.
I only know of one way to do that, and it's with a Sheet's add-on by triggering the "On Change" event by setting a value in a Sheet cell using the Sheets API. The Sheets API must use a special option to set the value "As the User."
The special setting is:
valueInputOption=USER_ENTERED
That option will trigger the "On Change" event even if the Sheet is closed.
Obviously the script making the request needs authorization from the user to set a value in a cell of the Sheet.
If the script sending the request is outside of the user's account then you'd need to use OAuth.
The add-on would need to install an "On Change" trigger for the Sheet and the function that the trigger is bound to would need to determine whether the change was from the special cell designated for this special functionality.
If the request to set a value in the users Sheet is from outside of that users Google account, then the user of the Sheet would need to somehow authorize the OAuth credentials for the Sheets API to make a change to the Sheet.
Depending upon the programming language being used with the Google Sheets API, there may be a Sheets API Library specifically for that language. You can also use the Sheets REST API.
There is an example here on StackOverflow of using the Sheets REST API from Apps Script, but if the external request is from some code that isn't Apps Script, it won't be exactly the same.
I understand that the solutions proposed in the comments, by others and myself, can't work in your scenario because it can't stand an average delay of 30 seconds. In that case I strongly advise you to set up a Cloud project that can be used as an instant server, as opposed to triggers/apps/etc.
In "Code_1" and "Code_2" use a shared data store. In other words, instead of directly passing the data from "Code_1" to "Code_2", make that Code_1 write to the datastore and "Code_2" read from it.
One possibility among many is to use a spreadsheet as a database. In this case you might use on change triggers to do some action when the spreadsheet is changed by one of the "Code_1" scripts and/or use time-driven triggers to do some action with certain frequency or at certain datetime.

Identifying the spreadsheet to use in Google Script Editor

I am trying to automate a google spreadsheet on google script editor. However, when I try to identify the spreadsheet and select as the one that we are working on, it is written in the documentation cited below (openById) that scripts that use this method require authorization with one or more of the following scopes:
--> https://www.googleapis.com/auth/spreadsheets.currentonly
--> https://www.googleapis.com/auth/spreadsheets
When I add these scopes, my function doesn't run. It only runs when those scopes are not added. Either way, I am met with the error msg that reads: "We are sorry, a server error occurred. Please wait a bit and try again."
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#openbyidid
I tried various methods include OpenByUrl and getActiveSpreadsheet...
function automatedInvoiceEmailing() {
var people = [];
// selecting the spreadsheet (without the bottom line, the function works just fine)
var ss = SpreadsheetApp.openById("1jdn3S1Iv2zDAqF6Hyy3fybKARZJYmg-LJVdUWJJS3LA");
}
Either way, I am met with the error msg that reads: "We are sorry, a server error occurred. Please wait a bit and try again."
I expected the sheet to have been selected
Edit: hmm, when I copied the google sheets and saved the new code, it runs properly!! :)
Since you are working with a bounded script (bound to the sheet you’re working with), the way to get a reference to the sheet is with var ss = SpreadsheetApp.getActiveSheet();, this will provide a reference to the bound document. You would use openById() to access another file.
As for scopes, they are added automatically when you run the script for the first time, it will ask for your permission to access your data, and when you accept, google adds them to the script project automatically.
Here are some quick-start examples on working with sheets and apps script

Why Isn't My Spreadsheet Changing From Input Values of An Outside Source?

So I am trying to use an outside source (Zapier) to input values into my spreadsheet. These input values are then "transposed (formula wise)" into my spreadsheet to fit the cell coordinates with which they are to align.
I have the spreadsheet set to run 'onEdit' and when these incoming values arrive, it is supposed to cause the rest of the spreadsheet to change, but the function is not running.
However, if I were to edit the spreadsheet 'manually,' the onEdit function runs perfectly.
So why then would the spreadsheet not be running the function, when the outside source brings its input values?
UPDATE:
So I discovered that if I manually authorize an 'onChange' installable trigger, it will work. But if I create a copy of the same exact spreadsheet, the installable trigger will not exist in the copy. The copy needs to have the trigger without me having to do it manually. So I am trying to create a code inside of Google Script Editor that will either allow me to use the onChange function or install the onChange function in the Developer Hub. Any thoughts? Here is the code I tried but did not work:
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("My
Sheet")
var ssid = "My SpreadSheet ID";
ScriptApp.newTrigger('My Sheet)
.forSpreadsheet(ss.getId())
.onChange()
.create();
myFunction()
{
If there is an alternative for the onChange function, then I'm all ears. I just need a function that can run itself in my copies.
As a part of a collaborative effort, let me clarify the Zapier part (this answer does not concern the copy part).
Part 1. Zapier setup
Assuming you have a third-party application that you pull data from (btw, since you decided to use apps script, isn't it easier to drop the middleman like Zapier and connect to the 3P app API – if it has one, ofc – directly?), you created a Catch Hook and a POST Action.
The POST Action setup contains several fields:
URL field - this is where your /exec URL goes (WebApp is deployed via Publish->Deploy as a WebApp). After you deploy your script as a WebApp, you will get a URL that users and scripts can make requests to (it is always of this format https://script.google.com/macros/s/{yourProjectId}/exec - with some slight diff. due to access permissions). To avoid permissions issue, set the Who has access to the app option to anyone or anyone, even anonymous (otherwise, you'll have to devise auth handling).
Payload Type field is irrelevant here, but I suggest using JSON.
Data field is required if you chose the POST Action and should contain key-value pairs of data you would like to transmit via Zapier (the data will be available in parameter/parameters property of the event object).
Part 2. WebApp setup
Published WebApps should have either a doGet() or doPost() function to be able to receive and process requests (or both). Each of them accepts one special argument, which is constructed each time a request to the WebApp is made – an event object.
The event object will contain all the data that you sent from Zapier. You can then use this data to conditionally trigger different functions, pass data to handlers, etc. So, instead of relying on triggers, you can create a function that is called inside the doGet / doPost that will a) populate your target sheet with new values; b) do anything else after that, thus acting as an analogue of onEdit / onChange.
Useful links
Event object structure;
Passing event objects around;
Creating triggers on other documents;

You do not have permission to call openById

Problem: When I run the script, Google tells me,
You do not have permission to call openById
I had copied a script from another one of my Google spreadsheets and changed the target_ssKey variable's cell reference and created properly-sized Named Ranges in both the Source and Target spreadsheets.
Google Apps Script documentation says nothing about reasons why it might not be working:
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#openById%28String%29
Another Google Apps Script documentation says that it should work for me because I invoke it from a custom menu:
https://developers.google.com/apps-script/guides/sheets/functions#using_apps_script_services
The second link above says:
If your custom function throws the error message You do not have
permission to call X service., the service requires user authorization
and thus cannot be used in a custom function.
To use a service other than those listed above, create a custom menu
that runs an Apps Script function instead of writing a custom
function. A function that is triggered from a menu will ask the user
for authorization if necessary and can consequently use all Apps
Script services.
I tried putting the function into a "Custom Functions" project and then into an "Add-on" project, but still got the same error message.
Any ideas on what I am doing wrong and how to make this work?
Here is my exact code:
function exportData_SStoSS() {
// Get the source data.
var source_ss = SpreadsheetApp.getActiveSpreadsheet();
var data = source_ss.getRangeByName("exportData").getValues();
// Identify the target.
var controls_sh = source_ss.getSheetByName("Controls");
var target_ssKey = controls_sh.getRange('C2').getValue();
var target_ss = SpreadsheetApp.openById(target_ssKey);
// Paste the data to the target.
target_ss.getRangeByName("importData").setValues(data);
};
I thought that I would throw in a similar issue that I had which brought me to this question, where I received the error You don't have permission to call by openById.
In my case I was trying to call functions from translate.gs which I copied from this example:
https://developers.google.com/apps-script/quickstart/docs
Note that at the top of translate.gs
/**
* #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.
*/
The culprit here is the #OnlyCurrentDoc comment. See here for reference:
https://developers.google.com/apps-script/guides/services/authorization
Removing #OnlyCurrentDoc fixed this issue for me
I could resolved this issue with this autorization guide of google developers.
https://developers.google.com/apps-script/concepts/scopes#setting_explicit_scopes
This entry It's necesary in json file.
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets.readonly",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/spreadsheets"
],
I found this official note which I believe clears up what caused the issue.
If your function is a custom function, that is one which can be used like a regular spreadsheet function in the sheet itself, then it has limited access to things and cannot open other spreadsheets.
The same script can however open other spreadsheets from a menu button or similar.
Link: Documentation at developers.google.com
The method openById can be called from a "Blank Project" but not a "Custom Functions in Sheets" nor a "Google Sheets Add-on" project.
I thought a "Blank Project" would create a project that was not connected to my spreadsheet, but I was wrong. The Blank Project is connected to my spreadsheet. The other types of projects that I tried to use seem to be limited-scope versions of script projects, not able to carry out some GAS methods.
Had this same issue and came to share my solution. In my case I had two spreadsheets, call them A and B. Both used scripts bound to each respective spreadsheet. Spreadsheet B was able to write data to a tab of spreadsheet A. But Spreadsheet A kept getting the "You do not have permission to call openById" error when trying to read from spreadsheet B. I then tried adding it as a custom menu item but still the same issue.
The solution in my case turned out to be really simple. I created a new unbound script in script.google.com that calls both spreadsheets using openById. The first time running put a smile on my face as it asked for authorization. Thereafter smooth sailing.

onedit why cannot send email?

i've this simple script that should send an email when a cell is changed
function onEdit(e) {
var doc = e.source;
var r = doc.getActiveRange().getValue();
if (r == "Niccolò"){
var a = doc.getActiveRange().setBackground('#ff0000');
var b = GmailApp.sendEmail('name#gmail.com', 'subject', 'body');
}
}
This function change also cell colour.
THe problem is that the cell colour works, so it's change while doesn't send any email.
It looks so simple i don't understand why doesn't works!
Simple triggers like onEdit(), onOpen() or onFormSubmit() have a limited set of possible actions because they run without authorization , see the documentation for further details.
So this behavior you describe is normal.
You should use an installable trigger instead as explained in the same doc page.
here is an summary of the documentation :
These simple triggers run in response to actions in Google Spreadsheets, and they run as the active user. For example, if Bob opens the Spreadsheet, then the onOpen function runs as Bob, irrespective of who added the script to the Spreadsheet. For this reason, the simple triggers are restricted in what they are permitted to do:
They cannot execute when the Spreadsheet is opened in read-only mode.
They cannot determine the current user.
They cannot access any services that require authentication as that user. For example, the Google Translate service is anonymous and can be accessed by the simple triggers. Google Calendar, Gmail, and Sites are not anonymous and the simple triggers cannot access those services.
They can only modify the current Spreadsheet. Access to other Spreadsheets is forbidden.
For more information on event permissions, see Executing from a Container-Specific Trigger.