How to transfer data from webapp to addon - google-apps-script

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.

Related

Google Apps Script: MySQL function causes error in onEdit [duplicate]

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.

HTTP Request to a function in Google Scripts

Since I'm not experienced at all with HTTP Request and Google Scripts, I'm having trouble wraping my head around it.
So, my problem is the following:
I'm currently trying to get information in my lua script and send it to a google Spreadsheet. However, the way the google spreadsheet should save the info would be dependent on which function on the Google Script I'm calling and passing information.
SO, my question is: How would my lua script (that only gives me access to HTTP Requests at this time) connect to a specific function like the one bellow?
function callName(name) {
// Get the last Row and add the name provided
var sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange(sheet.getLastRow() + 1,1).setValue([name]);
}
Also, I think my script is wrong as well, but I'm more worried about how to actually make the connection.
Answer:
You can publish your script as a Web Application and use URL parameters to pass the script the information you need.
More Information:
From the Google documentation about web apps:
If you build a user interface for a script, you can publish the script as a web app. For example, a script that lets users schedule appointments with members of a support team would best be presented as a web app so that users can access it directly from their browsers.
However, even without building a user interface, you can use this functionality to run scripts on your sheet by utilising HTTP requests.
Modifying your Script:
In order to allow your script to accept URL parameters, you must first modify your code so that processing is done on a HTTP GET request. You can do this with the Apps Script doGet() function and the event parameter e:
function doGet(e) {
callName(e.parameter.name);
}
function callName(name) {
// Get the last Row and add the name provided
var sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange(sheet.getLastRow() + 1,1).setValue([name]);
}
Setting up the Web App:
From the Apps Script user interface, follow the Publish > Deploy as web app... menu item, and in the newly-opened modal window, you'll want to select the following settings:
Project version: New
Execute the app as: Me (your-email#address.here)
Who has access to the app: Anyone, even anonymous
And click Deploy. Here, you will be given a URL in a new, smaller modal in the following form:
https://script.google.com/a/your-domain.com/macros/s/some-script-id/exec
Making the request:
The rest of this is now trivial - you can make your HTTP request to the script URL in the previous step, but providing the URL parameter that you need in order to give te app the information of the value you wish to set.
For example, if you want to set the value to the number 20, make your get request as so:
GET https://script.google.com/a/your-domain.com/macros/s/some-script-id/exec?name=20
Note the ?name=20 at the end gives the Web App the parameter name with a value of 20. The doGet(e) function reads this from e.parameter.name and sends it to your callName(name) function for processing and execution.
References:
Web Apps | Apps Script | Google Developers
Request Parameters

Trigger webhook from Google Sheets when button in cell pressed

We have our sprint capacity planning in Google Sheets, as JIRA wasn't able to efficiently break out tasks up into sprints as per number of story points per task.
We're bringing in a list of all tasks in JIRA, and their associated story points, and with some spreadsheet magic are building the sprints according to the number of available story points.
The spreadsheet updates via Zapier every time there's an edit in JIRA in the specific board, but at times when lots of things are changing it fires a lot of events, as there's so many steps in the Zapier Zap.
I was wondering if it's possible to have a button in a Google Sheets cell that can fire a webhook, perhaps with something as trivial as the date/time that can trigger the Zap to update the spreadsheet?
This means it's less 'expensive' on tasks in Zapier and it's only updating when a user clicks the button in the spreadsheet.
Hello tectomics I believe I have worked out a solution that should work for you.
The solution at a high level is as follows:
Create a script, located in your Google Sheet, that is triggered by a button press. The script makes an HTTP POST request to a Zapier webhook which is listening for any incoming traffic. Once the webhook is caught on Zapier's end it triggers all of your downstream steps located in the zap. Note that for this to work you will require a premium subscription to Zapier as Zapier's webhook app is premium only access.
And now for the details:
The google script is as follows:
function fireZap() {
var endpoint = "ZAPIER_WEBHOOK_URL";
var payload = {
"uuid" : Utilities.getUuid()
};
var options = {
"method" : "post",
"payload" : payload
};
UrlFetchApp.fetch(endpoint, options)
}
NOTE: The endpoint variable is just holding an example, you will have to update that value with the endpoint URL provided by Zapier when you first create your webhook trigger.
The google script makes use of the UrlFetchApp class. Zapier's webhook endpoint only seems to trigger on unique payload data (at least in testing mode, could be different for a live zap) so we generate a unique UUID for the payload.
Once the script is written you must link it to a button located in your workbook. There are many tutorials such as this one that describe the straight forward process of doing this and so I will save myself the trouble.
Once we have all of this in place we head over to Zapier and create a new zap with the Webhooks zap for a trigger. For the trigger event select 'Catch Hook'. Proceed to the 'Customize Hook' section. Here Zapier will provide you with a unique URL endpoint, place this in the endpoint variable back in the google script.
With this complete setup whatever downstream zaps you would like and it will trigger whenever you hit the button located on your sheet.
Hope this helps,
Any questions please let me know.

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;

Getting thread id's of selected Emails in Gmail, using Google Apps Script [duplicate]

Is it possible to process multiple selected emails by an addon?
The code from Google shows how to process only one selected email. I suppose 'e' would be some kind of array of accesstokens but I don't know how to access it.
function getContextualAddOn(e) {
var accessToken = e.messageMetadata.accessToken;
Logger.log("token:"+ accessToken);
GmailApp.setCurrentMessageAccessToken(accessToken);
var messageId = e.messageMetadata.messageId;
var card = createCard();
return [card.build()];
}
Thanks.
Your Apps Script code never runs on the client device, where the messages are selected in the Gmail UI - it runs on Google's servers. You can't access UI information from add-ons (there's no GmailApp.getSelectedThreads(), CardService.getActiveEmails(), etc.). You are only able to access the opened message / draft, and others in the same thread (or mailbox, depending on your add-on's scopes).
The event object received by your triggered callback function will only have a single access token. Until additional manifest triggers are added (currently only contextual and compose exist), this won't change.
As this is not Google, no one here can tell you if or when other triggers may or may not be added. You best bet is to request the feature.