We're happy to announce an enhancement to Apps Script Tasks that will allow you to use the return value of a Google Apps Script function within your AppSheet automation and pass it to a later step in the same automation process.
Example uses:
This feature will be rolling out gradually starting today. When available, you will see a new option appear at the bottom of the Apps Script task configuration for an optional return value:
You can try returning values of different types from Apps Script. For example, a simple boolean return value:
function isPastDue(date) {
return new Date() > new Date(date);
}
Or more complex objects:
function returnASampleObject(userId) {
return {
"userId": userId,
"username": "jsmith",
"email": "john.smith@example.com",
"creationDate": new Date(2021, 11, 25),
"isAdmin": false,
};
}
See how to configure return values for Apps Script tasks for more details. A quick summary is below:
To send us feedback on this feature, you can reply or comment on this post.
Awesome! So now we can effectively handle responses from API calls, by going through a custom built GAS before passing a value back to Appsheet, without having to force the GAS itself to interact directly with the data source.
Use-cases that I can think of immediately:
I'm sure there are others...
I'm glad to hear that! That's very good news!
I was hoping to do some API calls to a adress form auto fill with GAS since i could get return i made a Spreadsheet formula, but now i will give it a try!!
Previously, unless you had enterprise/API access, scripts or integrations were limited to silently posting/updating data to the underlying spreadsheet. This effectively removes that limitation whenever the request is initiated from AppSheet.
Hi @nico
This is an exciting feature.
The column name of the return value is [Output], but if there is a reserved word, I think it would be good to have an underscore at the beginning, like other column names.
I think following this.
[Your StepName].[_Output]
Thanks for the input. Unlike table refs like `[_THISROW]` in the top-level namespace there is no namespace collision with `[Output]` because it's in the step name table ref so it's guaranteed to be unique.
Hi @nico
isPastDue may need to be changed.
Passing "2000/1/1" did not yield a return value.
The following changes gave the expected return value.
function isPastDue(date) {
// return new Date() > new Date(date);
if(new Date() > new Date(date)){
return new Date();
}
}
Takuya, the example in the post should be correct but the return value in AppSheet should be configured to be "Boolean" instead of a "Date" because it's just returning `true` if the current date is past the provided date "the due date" and `false` otherwise.
Of course you can modify it to return a date instead if you'd like for example:
function currentDate() {
return new Date();
}
These are both "toy" examples because you can do both of these function in AppSheet's expression system but hopefully it gives an idea of how you'd use the return value. I expect real-world use-cases to be more complex than this.
EDIT: One more thing, you should notice a conversion error in the automation monitor that says the conversion failed to help for debugging purposes.
Thanks @nico .
And I'm sorry. I was mistaken.🙏
I'll try something that AppSheet's features alone can't do.😄
This is really exciting!
One seeming clarification regarding the bold text in the following part of the announcement is that the subsequent step's task needs to be an unlinkable "custom action"--not just a linkable task with linking turned off.
Use the Return Value in your automation
- To reference the return value in an expression in a subsequent unlinked step in the process, you can refer to the output using the following syntax:
That's right, it needs to be an unlinked action or task. The reason is because the return value is scoped to the process so you can only access the return value in later steps in the process. The Apps Script Task itself with the return value can either be linked or not.
Understood. What initially confused me is probably the phrasing of the requirement from your original post for "a subsequent unlinked step". I believe "step" components are not themselves custom/reusable/unlinked/linked, but rather that status applies to the action or task that a step includes.
I created an automation by compiling independently created reusable components with linking turned on, including a step referencing a data action in my app, then realized the requirement from your original post for "a subsequent unlinked step", then turned linking off for various components of the automation (the step/action referencing the returned value, the automation's other step/task, its process, and its event components) each one by one, and it still didn't work throughout. I may have made some mistake along the way that I don't realize, but I ended up starting over with a new step and new data change action defined as a custom action from within the step, and that was finally what worked.
If anyone wants a quick-and-dirty script to try out this new functionality for a nonetheless real use case, here's one I put together. If you have a country code from a phone number, you can use a script (which in turn relies on an API) to get (and now return!) a corresponding flag emoji. I'm sure the script could be optimized in all sorts of ways, including trapping for errors and variations in what's considered a country code, but I confirmed it works at least in straightforward cases.
function getFlagEmojiForCountryCode(countryCode) {
// Call REST Countries API (https://restcountries.com/)
const response = UrlFetchApp.fetch("https://restcountries.com/v2/callingcode/" + countryCode + "?fields=alpha2Code");
// Parse response
const responseJSON = response.getContentText();
const responseData = JSON.parse(responseJSON);
let flagEmoji;
let countryAlphaCode;
if (responseData.length > 1) {
flagEmoji = "N/A (multiple)";
} else {
const objCountryAlphaCode = JSON.parse(responseJSON)[0];
countryAlphaCode = objCountryAlphaCode.alpha2Code
// From https://dev.to/jorik/country-code-to-flag-emoji-a21
const codePoints = countryAlphaCode
.toUpperCase()
.split('')
.map(char => 127397 + char.charCodeAt());
flagEmoji = String.fromCodePoint(...codePoints);
};
return flagEmoji;
}
Hi @nico
I'm currently trying to achieve geocode functionality using return values.
It is a simple Apps Script that will return Lat and Long if the address is passed as text.
function geocode(address) {
var response = Maps.newGeocoder().setLanguage('ja').geocode(address);
for (var i = 0; i < response.results.length; i++) {
var result = response.results[i];
Logger.log('%s: %s, %s', result.formatted_address, result.geometry.location.lat,
result.geometry.location.lng);
// return result.geometry.location.lat;
return {
"lat": result.geometry.location.lat,
"lng": result.geometry.location.lng
};
}
}
At this time, if I set the data type of the Key I receive in AppSheet to TEXT, the return value is Decimal, so I cannot get it and it will be NULL.
If I set it to Decimal, it works as expected.
There is nothing wrong with the functionality, but it is difficult to find the wrong data type in question because the error is not displayed in the Automation monitor nor in the Audit log.
I would like to see an error somewhere if the data type received is different.
Thanks.
Takuya, thanks for trying out the new feature!
A few things I wanted to mention. If you want a LatLong translation, one easy wait to do it is to return it as a string like `"-28.05722, -51.75104"` then you can just configure it as returning a string with "LatLong" as the AppSheet type and you get it directly as a pin on the map etc.
And
function geocode(address) {
...
return result.geometry.location.lat + ", " + result.geometry.location.lng;
}
But there are good reasons to return it like an object if you want (e.g. to do calculations). In your first screenshot notice there is an error even the execution is successful: `Failed Apps Script Type Translation: Type 'Number' failed to translate to 'Text'`
I made the decision that the automation would still complete even if the type translation fails because some users may return different variable types on errors and I wanted to support that as well as not break existing workflows that might return different type.
There are two ways to fix that, either change Apps Script side to be strings:
1) Explicitly convert it in Apps Script (notice the `.toString()` at the end:
function geocode(address) {
...
return {
"lat": result.geometry.location.lat.toString(),
"lng": result.geometry.location.lng.toString()
};
}
2) Just configure it to accept a decimal on AppSheet (as you noted)
You've observed we're stricter on translating types from Apps Script. One "trick" I'll mention is that all types can be converted from a string to any type but not the other way around so a string like `"123.42"` to decimal is fine but a number like `123.42` to text will not work.
Hopefully that all makes sense.
Hi @nico
Thanks for the detailed follow-up.
The handling of the Geo code is Apps Script's forte and is also information that AppSheet can make great use of.
Your suggestions will expand my handling of Geo codes. Thank you very much.🤗
One thing I would like to discuss with you is that when it contains a wrong type error as we mentioned, I would like it to be clear in the Automation error.
Specifically, when similar error messages are included, I think it would be a feature that would be easier to use if we could display something in the log to draw attention to it, such as Warning instead of Complete.
Hi @nico
Sorry, same content.
I think it would be a more user-friendly feature for citizen developers if Status could be expressed in more detail regarding Automation Monitor.
For example, this is the log when I forgot to set the Return value in my Apps Script.
It is true that the Error message shows the contents of the message, but the Status is Complete, so it is not highly visible.
Especially for non-English speaking users, it may take time to identify the problem if the Status information is green.
Since most citizen developers do not have good debugging skills, I think this feature will be more widely accepted by a wider range of users if Monitor is made more user-friendly.🤗
Just want to validate your suggestion, I think showing a `Warning` here instead of a success or `Error` is a good idea since it'll be easier to find but we currently don't have a Warning state and adding one requires a wider Automation change. I've posted a bug internally but due to low severity it's unlikely it'll be prioritized in the near future.
Thanks @nico
That's right.
I thought the request seemed to have a large scope of impact, so I will wait patiently, hoping that it will be modified.
Until then, I'll keep an eye on the Error message!😃
In my AppSheet application I am using this:
function geocode(address) { ... return result.geometry.location.lat + ", " + result.geometry.location.lng; }
But there is a problem with the return value.
I am trying to return the value into the Colum 'GPS', but I get an error.
Output 'GPS' is invalid: Error in expression '[geocode].[Output]' : Unable to find table 'geocode Output'
What am I doing wrong?
Is "geocode" the name of your call a script step?
Looks like this:
Are you confusing return values from the call a script task with a process returning a value?
How/where are you attempting to push the script return value to a column? Typically there'd be a data change action step in that bot for that.
Yes.....it is a data change action.
Hey @RensD I just saw this little bit of exchange and I can see what the problem is for you. It's just a misunderstanding between what you are naming your appscript function (geocode) and the name of the Step that is calling it.
When you return values from Appscript and want to write them into a column, the system is expecting you to give the name of the Step, not the name of your script.
In this case, [geocode].[Output] is the name of your function and not the name of your step (task).
Here is a bot that calls a script
On the following task (StoreValues) the data that is returned from the script is stored in columns using the name of the Step.
[CreateNote].[Output] NOT [CreateNewNote].[Output]
Apologies if you have already solved this and I missed it!
Thank you. That was the trick. 🙂
Two ideas:
1. is the Task configured as a "string" return type in the Apps Script Task? Should look like this:
2. Like Marc is alluding to, you can only use this return value in the same process as you're calling the script in (but you can write it back to a table if you'd like to persist the value).
This is what I am using.
It gets me the following error:
Output 'GPS' is invalid: Error in expression '[geocode].[Output]' : Unable to find table 'geocode Output'
Any insights @nico ?
Every advice would be great.
Your Apps Script Task configuration looks fine, can you also send a screenshot of the bot you're trying to run as well as the task/action that's reporting the error? It appears from the error message that it's looking for the `geocode Output` table when it should just be looking for the `geocode` table with the `Output` column (which contains your return value). The Apps Script Task also needs to be run in the same process in which you use the return value.
Has this not yet been rolled out to everyone? I do not see any Return Value toggle or section in the Task configuration panel.
It will finish rolling out to everyone tomorrow at roughly 10am PST (assuming monitoring looks okay).
Also note you'll have to select an Apps Script project before the option to select a use a return value will appear (at the bottom of the task configuration panel).
That's great news. I have been banging my head against the wall trying to figure out why I don't see it. And I do have an Apps Script project selected, that returns a value.
Did this get rolled out for everyone today? I don't see it.
I just made the last rollout bump just now. If you refresh the editor page and select an Apps Script task you should see it after you select a script (but let me know if you don't).
😀Hooray!!!
I see it! Thanks very much.
As I prepare to upgrade a production app to include new automations I developed in a non-production app, I have some questions regarding potential constraints and hiccups related to Call a script tasks. I'm sure I can test these details if necessary, but it would also be helpful to get the answers documented here for everyone's awareness. Thanks, @nico or community friends who may have already worked through these details, for any guidance.
Is it possible for a Call a script task to reference an Apps Script project that is shared to the app owner? I don't see a way to select a project that is only shared to the owner. Maybe by adding a shortcut to My Drive?
I do see that it's possible to transfer ownership of a project. If an app task already references a project whose ownership changes but is still shared back to the app owner, will the task continue to function?
Screenshot: Seemingly no way to select a project shared to the app owner
Screenshot: Option to transfer ownership of project
Does a Call a script task always call a project's head deployment? Does it depend on whether the project has any versioned deployments? Is it possible to specify in the task a specific deployment version? Flexibility here would facilitate maintaining production and non-production versions of a script within the same project. Without flexibility, it's presumably necessary to maintain separate projects and point production and non-production apps to distinct projects.
If I change the project referenced by a task (e.g., to reference a production project instead of a development project from an upgraded app), do the task's other project-related settings (e.g., Function Name, Function Parameters, Return Value) persist or do they reset to blank and need to be re-entered even though they're the exact same values compatible with both projects?
Screenshot: Project-related task properties
Update: I did some basic testing regarding some of my points.
1. Shared projects: If a GAS project is shared to you, add a shortcut to your Google Drive. Then you can select it from an AppSheet task and it functions fine.
If you transfer ownership of a GAS project that is already used in an existing AppSheet automation task, the task seemingly continues to function seamlessly with no changes necessary within the app. Upon transfer, it looks like a shortcut is automatically added to the previous owner's My Drive; I did not test whether there's a dependency on retaining that shortcut.
Note that it's not possible to transfer project ownership into/out of a Google Workspace organization (although maybe there's some workspace admin setting that could allow this). Nonetheless, it is still possible to share a project into/out of a Google Workspace.
3. Task properties persistence: If you change an existing task's Apps Script Project, all the other project-related task properties sufficiently persist:
Thanks for the questions and answering your own questions 🙂
1) I'm not an expert on Drive itself but so long as the app owner (which remember is the entity executing the Apps Script Task) has authority over the script it should be able to run. There are lots of different ways to organize: Shared Drive, shared authorship over the project, and (as you discovered) making the app as a shortcut might be required to see that in the file selector.
2) Yes, for configuration simplicity scripts are always executed at HEAD (as opposed to Apps Script web executable which allows you to version a script). A separate development project seems like a good way to mostly support development flow.
3) Yes, we try to maintain as many properties as we can so we don't clear configured properties especially if you have a function with the same name and number of parameters. We just keep the Return Value as is if a project is swapped.
Why Automatic Return Value is Hard:
This is probably more detail than you care about but I wanted to explain why we don't do any automatic computation of the return value you'll need to do this manually. Note that in general, it is impossible to determine the return type can only be determined at runtime (in the general case), consider a function that is just:
function simpleUnknownType(x) {
if (x > 0) {
return "some string"; // a string
}
return 42; // a number
}
In this case, the return type is either a string or a number depending on the input `x`. So by setting a return type you are saying that your function has a consistent return type. We could execute the function and then set that automatically as an enhancement but hopefully it's not too hard to manually select the return type.