pre-loading custom functions when running sheets daily script - javascript

I'm using the 'Cryptofinance' custom function in Google spreadsheets. I have written a custom script which runs daily using the trigger functionality of the app scripts.
function daily() {
SpreadsheetApp.flush();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName("Liquidity");
var value = sh.getRange("B36").getValue();
var lastRow = whichRow();
lastRow += 1;
ss.getSheetByName("Liquidity over time").getRange("B" + lastRow).setValue(value);
ss.getSheetByName("Liquidity over time").getRange("A" + lastRow).setValue(new Date());
}
function whichRow() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var Avals = ss.getSheetByName("Liquidity over time").getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;
return Alast;
}
Basically it should pick up the value from a field and add a row so I can run a chart on this column.
The output when I run it manually from the script editor can look like this;
1337999,52
The output when it gets run by the daily function looks like this:
#NAME?
It didn't help with the .flush() function and I haven't been able to understand the sheets lifecycle in combination with the custom function.
How can I make sure the sheet is pre-loaded before the script runs?

You need to open the Spreadsheet and then set it as active:
var ss = SpreadsheetApp.openById("1234567890");
SpreadsheetApp.setActiveSpreadsheet(ss);
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#setActiveSpreadsheet(Spreadsheet)

Short answer
On time-driven triggers avoid the use of call functions, even custom functions, that use getActiveSpreadsheet and other "get active" methods. Instead use openById or other similar methods.
Explanation
SpreadsheetApp.flush() ensures the pending changes made to the script are applied, so it doesn't make sense to put it as the first action of a function.
On the other hand, on Google Apps Script, the user that has opened the spreadsheet establish which spreadsheet is active, on time-driven triggers getActiveSpreadsheet returns null, in other words, we could say that "active" means "being used at this time by an user".

Related

How to access data from an external spreadsheet with Google Apps Script

I have a spreadsheet with some data tables that I want to use in other spreadsheets script. How can I call this spreadsheet by its ID and access the data?\
From the documentation, I know that custom functions are not allowed to access other spreadsheets, a solution would be to put this function in a menu and run it, since it'd ask for the user's authorization. But this is not an option because I am using the data with the built-in method onEdit().\
Also, I tried accessing the spreadsheet via onOpen() since it's not a custom function but still no success. Any other solution?
My code:
function onOpen(){
ss = SpreadsheetApp.getActiveSpreadsheet();
src = SpreadsheetApp.openById("spreadsheetID");
Error message:
Exception: You do not have permission to call SpreadsheetApp.openById. Required permissions: https://www.googleapis.com/auth/spreadsheets
And appscript.json
{
"timeZone": "Europe/Paris",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets"
]
}
Lastly, I also tried creating an installable trigger with this code:
function createSpreadsheetOpenTrigger() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
ScriptApp.newTrigger('myFunction')
.forSpreadsheet(ss)
.onOpen()
.create();
}
But the trigger does not seem to be called upon opening the sheet.
As you already realized, custom functions and simple trigger cannot perform requests that require authorization
SpreadsheetApp.openById("spreadsheetID"); is one of those requests
You are on the right track with the installable triggers - they can trigger the execution of requests that require authorization
I think your problem is the correct implementation of installable triggers
function createSpreadsheetOpenTrigger creates an installable onOpen trigger that calls the function myFunction when the sheet is open
This means that you need to create the function myFunction first
Also, you need to run function createSpreadsheetOpenTrigger() once manually - to install the trigger
Sample complete code:
function createSpreadsheetOpenTrigger() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
ScriptApp.newTrigger('myFunction')
.forSpreadsheet(ss)
.onOpen()
.create();
}
function myFunction(){
ss = SpreadsheetApp.getActiveSpreadsheet();
src = SpreadsheetApp.openById("spreadsheetID");
Logger.log(src.getActiveSheet().getName());
}
Note:
Instead of implementing the function createSpreadsheetOpenTrigger(), you can install the trigger manually by going on Edit->My project's triggers and bind a trigger of the desired type to the desired function, see also Managing triggers manually
First, in the line src = SpreadsheetApp.openById("spreadsheetID"); did you replace the id with the actual spreadsheet ID?
That's the number at the end of the url. I assume you did, but it doesn't show that you did in your example.
Second, just for testing, try to do this action in a regular function (not a trigger) to get things to work. Have you done that? Then you can focus on the spreadsheet access without worrying if the trigger is causing a problem.

Querying from an API to a Google Spreadsheet / G Apps Script and obtaining filtered results

I'm trying to build a spreadsheet based around DataDT's excellent API for 1-minute Forex data. I'm trying to build a function that 1) Reads a value ("Date time") from a cell 2) Searches for that value in a given URL from the aforementioned API 3) Prints 2 other properties (open & close price) for that same date.
In other words, It would take input from rows N and O, and output the relevant values (OPEN and CLOSE from the API) in rows H and I.
(Link to current GSpreadsheet)
This spreadsheet would link macroeconomic news and historic prices and possibly reveal useful insights for Forex users.
I already managed to query data from the API effectively but I can't find a way to filter only for the datetimes I'm asking. Much less iterating for different dates! With the help from user #Cooper I got the following code that can query entire pages from the API but can't efficiently filter yet. I'd appreciate any help that you might provide.
This is the current status of the code in Appscript:
(Code.gs)
function searchOnEdit(e) {
//e.source.toast('Entry');// I use these lines for debugging
var sh=e.range.getSheet();
if(sh.getName()!='API') return;
var checkedValue='TRUE';//these are the defaults if you install the checkboxes from the Insert Menu
var uncheckedValue='FALSE';
if(e.range.columnStart==17 && e.range.rowStart>1 && e.value==checkedValue) {
e.range.setValue(uncheckedValue);//this was commented out it should not have been sorry for that Cooper
//e.source.toast('flag1');
var r=sh.getRange(e.range.rowStart,1,1,sh.getLastColumn()).getValues()[0];
var obj={datetime:r[14],url:r[13],event:e};
var dObj=getDataDT1(obj);
//Logger.log(dObj);
sh.getRange(e.range.rowStart,4).setValue(dObj.OPEN);//loading OPEN on your spreadsheet
sh.getRange(e.range.rowStart,5).setValue(dObj.CLOSE);//loadding CLOSE on your spreadsheet
}
}
//{datetime:'',url:'',event:e}
function getDataDT1(obj) {
Logger.log(JSON.stringify(obj));//I need to see this
var r=UrlFetchApp.fetch(obj.url);
var data=JSON.parse(r.getContentText("UTF-8"));
//Logger.log(data);
var pair='USDJPY';
var dat=new Date(obj.datetime);
var dtv=new Date(dat.getFullYear(),dat.getMonth(),dat.getDate(),dat.getHours(),dat.getMinutes()).valueOf();
for(var i=0;i<data.length;i++) {
var dt=data[i].DATE_TIME.split(' ');
var sd=new Date(data[i].DATE_TIME);
var sdv=new Date(sd.getFullYear(),sd.getMonth(),sd.getDate(),sd.getHours(),sd.getMinutes()).valueOf();
if(sdv==dtv) {
var d=dt[0].split('-');
var t=dt[1].split(':');
var datestring=Utilities.formatString('%s/%s/%s',d[1],d[2],d[0]);
var timestring=Utilities.formatString('%s:%s',t[0],t[1]);
var rObj={DATE:datestring,TIME:timestring,PAIR:pair,OPEN:data[i].OPEN.toFixed(3),CLOSE:data[i].CLOSE.toFixed(3)};
break;
}
}
//Logger.log(rObj);
return rObj;
}
(Appscript.json)
{
"timeZone": "America/Caracas",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"oauthScopes": ["https://www.googleapis.com/auth/userinfo.email","https://www.googleapis.com/auth/script.external_request","https://www.googleapis.com/auth/script.scriptapp","https://www.googleapis.com/auth/spreadsheets"]
}
(Trigger.js)
function createSearchOnEditTrigger() {
var ss=SpreadsheetApp.getActive();
ScriptApp.newTrigger('searchOnEdit').forSpreadsheet(ss.getId()).onEdit().create();
}
onEdit Search
You will need to add a column of checkboxes to column 17 and also create an installable onEdit trigger. You may use the code provided or do it manually via the Edit/Project Triggers menu. When using the trigger creation code please check to insure that only one trigger was creates as multiple triggers can cause problems.
Also, don't make the mistake of naming your installable trigger onEdit(e) because it will respond to the simple trigger and the installable trigger causing problems.
I have an animation below showing you how it operates and also you can see the spreadsheet layout as well. Please notice the hidden columns. I had to do that to make the animation as small as possible. But I didn't delete any of your columns.
It's best to wait for the the check box to get reset back to off before checking another check box. It is possible to check them so fast that script can't keep up and some searches may be missed.
I also had to add these scopes manually:
"oauthScopes":["https://www.googleapis.com/auth/userinfo.email","https://www.googleapis.com/auth/script.external_request","https://www.googleapis.com/auth/spreadsheets"]
You can put them into your appsscript.json file which is viewable using the View/Show Manifest File. Here's a reference that just barely shows you what they look like. But the basic idea is to put a comma after the last entry before the closing bracket and add the needed lines.
After you have created the trigger it's better to go into View/Current Project triggers and set the Notifications to Immediate. If you get scoping errors it will tell you which ones to add. You add them and then run a function and you can reauthorize the access with the additional scopes. You can even run a null function like function dummy(){};.
This is the onEdit function:
function searchOnEdit(e) {
//e.source.toast('Entry');// I use these lines for debugging
var sh=e.range.getSheet();
if(sh.getName()!='API') return;
var checkedValue='TRUE';//these are the defaults if you install the checkboxes from the Insert Menu
var uncheckedValue='FALSE';
if(e.range.columnStart==17 && e.range.rowStart>1 && e.value==checkedValue) {
e.range.setValue(uncheckedValue);
//e.source.toast('flag1');
var r=sh.getRange(e.range.rowStart,1,1,sh.getLastColumn()).getValues()[0];
var obj={datetime:r[14],url:r[13],event:e};//you dont really need e here
var dObj=getDataDT1(obj);
//Logger.log(dObj);
sh.getRange(e.range.rowStart,4).setValue(dObj.OPEN);//loading OPEN on your spreadsheet
sh.getRange(e.range.rowStart,5).setValue(dObj.CLOSE);//loadding CLOSE on your spreadsheet
}
}
This is the search function. I tried caching the data but it was too large. So if you can reduce the size significantly that would help speed things up for consecutive searches.
//{datetime:'',url:'',event:e}
function getDataDT1(obj) {
var r=UrlFetchApp.fetch(obj.url);
var data=JSON.parse(r.getContentText("UTF-8"));
//Logger.log(data);
var pair='USDJPY';
var dat=new Date(obj.datetime);
var dtv=new Date(dat.getFullYear(),dat.getMonth(),dat.getDate(),dat.getHours(),dat.getMinutes()).valueOf();
for(var i=0;i<data.length;i++) {
var dt=data[i].DATE_TIME.split(' ');
var sd=new Date(data[i].DATE_TIME);
var sdv=new Date(sd.getFullYear(),sd.getMonth(),sd.getDate(),sd.getHours(),sd.getMinutes()).valueOf();
if(sdv==dtv) {
var d=dt[0].split('-');
var t=dt[1].split(':');
var datestring=Utilities.formatString('%s/%s/%s',d[1],d[2],d[0]);
var timestring=Utilities.formatString('%s:%s',t[0],t[1]);
var rObj={DATE:datestring,TIME:timestring,PAIR:pair,OPEN:data[i].OPEN.toFixed(3),CLOSE:data[i].CLOSE.toFixed(3)};
break;
}
}
//Logger.log(rObj);
return rObj;
}
This is the create Trigger function. Becareful not to run this more than once and always go check to see that there is only one and set the notifications to immediate when you first turn it on so you'll get emails pretty quickly after errors occur.
function createSearchOnEditTrigger() {
var ss=SpreadsheetApp.getActive();
ScriptApp.newTrigger('searchOnEdit').forSpreadsheet(ss.getId()).onEdit().create();
}
Animation:
This is a copy of your spreadsheet with the check boxes.
If you have any difficulties feel free to come back and get some help. I know some of this stuff sounds a bit daunting but it's kind of like going metric. Once you've measured and weighed a few things it begins to sound and feel natural.
Here's my code exactly as I have it in my script which is running right now. Perhaps I have a typo in it due to editing in comments. This has some debugging lines in which run the Logger and display toasts which you should probably comment out.
function searchOnEdit(e) {
e.source.toast('Entry');
var sh=e.range.getSheet();
if(sh.getName()!='API') return;
var checkedValue='TRUE';
var uncheckedValue='FALSE';
if(e.range.columnStart==17 && e.range.rowStart>1 && e.value==checkedValue) {
e.range.setValue(uncheckedValue);
e.source.toast('flag1');
var r=sh.getRange(e.range.rowStart,1,1,sh.getLastColumn()).getValues()[0];
var obj={datetime:r[14],url:r[13],event:e};
var dObj=getDataDT1(obj);
Logger.log(dObj);
sh.getRange(e.range.rowStart,4).setValue(dObj.OPEN);
sh.getRange(e.range.rowStart,5).setValue(dObj.CLOSE);
}
}
//{datetime:'',url:''}
function getDataDT1(obj) {
var r=UrlFetchApp.fetch(obj.url);
var data=JSON.parse(r.getContentText("UTF-8"));
//Logger.log(data);
var pair='USDJPY';
var dat=new Date(obj.datetime);
var dtv=new Date(dat.getFullYear(),dat.getMonth(),dat.getDate(),dat.getHours(),dat.getMinutes()).valueOf();
for(var i=0;i<data.length;i++) {
var dt=data[i].DATE_TIME.split(' ');
var sd=new Date(data[i].DATE_TIME);
var sdv=new Date(sd.getFullYear(),sd.getMonth(),sd.getDate(),sd.getHours(),sd.getMinutes()).valueOf();
if(sdv==dtv) {
var d=dt[0].split('-');
var t=dt[1].split(':');
var datestring=Utilities.formatString('%s/%s/%s',d[1],d[2],d[0]);
var timestring=Utilities.formatString('%s:%s',t[0],t[1]);
var rObj={DATE:datestring,TIME:timestring,PAIR:pair,OPEN:data[i].OPEN.toFixed(3),CLOSE:data[i].CLOSE.toFixed(3)};
break;
}
}
//Logger.log(rObj);
return rObj;
}
function createSearchOnEditTrigger() {
var ss=SpreadsheetApp.getActive();
ScriptApp.newTrigger('searchOnEdit').forSpreadsheet(ss.getId()).onEdit().create();
}

How to trigger a google apps script in google sheets when a cell is changed by an external source (firebase)?

I am trying to trigger a Google App Script that I have bound to a google sheet.
Data from firebase is pulled into a sheet which should trigger the script. I'm currently using onEdit(e) which works when I manually add the data in but doesn't work when the data is updated from firebase. Is there any way I can set up a trigger to run the function every time the cell values in a specific range are changed.
Thanks!
Script
function onEdit(e) {
var ss = SpreadsheetApp.getActive()
var dumpingGround = ss.getSheetByName("Dumping Ground")
var dataDump = ss.getSheetByName("Data Dump")
var ddLastRow = dataDump.getLastRow() + 1
var editCol = e.range.getColumn();
var editRow = e.range.getRow();
Logger.log("edit")
/*Copy from dumping ground to data dump*/
if ((editRow == 1 && editCol == 2) || (editRow == 1 && editCol == 3)){
/*Set required range for data dump and dumping ground*/
var ddRange = '\'Data Dump\'!A' +ddLastRow+ ':BF' + ddLastRow
var dgCopyRange = '\'Dumping Ground\'!B1:BF1'
/*Copy action*/
ss.getRange('\'Dumping Ground\'!B1:BF1').copyTo(ss.getRange(ddRange), SpreadsheetApp.CopyPasteType.PASTE_NORMAL, false);
/*Check data dump row count and delete 1000 rows if necessary*/
if(ddLastRow = 9999){
dataDump.deleteRows(1, 1000);
}
}
};
From the Apps Script documentation:
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.
I think your best but would be to create a time trigger and check for changes in the specified range. Naturally, I can't really say for sure if that's something that would even work for your purposes, but it'd be worth a try.
Here are some alternatives to consider:
If the data pull is initiated by your script, then simply have that function complete the desired actions.
If you have a separate service populating the data, you can use the Sheets API.
You could publish the script as a web app and trigger via GET or POST
You could try executing a function using Apps Script API. Please review the requirements for doing so, because it may not work for your requirements.

Embedding onFormSubmit Triggers for multiple Google forms that I generate with Script

I have a script for creating multiple Google forms. within each Google form, I want to create an onFormSubmit trigger to post the responses to a selected spreadsheet.
Using installable triggers but the trigger doesn't seem to get embedded into the forms I create. The code as follows:
for(i = 0; i<arrayOfArrays.length; i++) {
var form = FormApp.create("Name");
form.setTitle('Blah blah');
form.addTextItem();
// my questions
function writeToSpreadsheet() {
var formResponses = form.getResponses();
var ss = SpreadsheetApp.getSpreadsheetByName("1bFjwHt_8Ct_iJCDc5F3lFgrCqSTMjQVOHrL3DQEzLmM");
var sheet = ss.getSheets()[0];
var newRow = sheet.getLastRow() + 1;
for (j=0; j<array.length; j++) {
var questionResponse = formResponses[j];
ss.getRange(newRow,j+1).setValue(questionResponse);
}
}// end writeToSpreadsheet
function createFormSubmitTrigger() {
ScriptApp.newTrigger(writeToSpreadsheet)
.forForm(form)
.onFormSubmit()
.create();
}
}// ends loop to create many forms
Am I doing it right, or is this not even possible? Thanks!
One thing to note is that you cannot create a function inside a for loop but you can call the function.
Second thing is that a trigger does not fire unless you have authorized the script that is you need to manually run the trigger function for every form to get it to work
You need to manually install each script to each form or you can install the script in one form and copy the form and the script would be installed in the copied form but you need to run the trigger once to authorize it.
Your script is error prone because functions cannot be defined inside another function let alone a for loop.

Is there a way to run google docs script under the script owner's authorization?

I have a google sheet that is a master database of orders entered by a number of dealers. Therefore, this master sheet must have limited edit access. I want created a number of other sheets (one for each dealer) through which they can enter orders and added to my master sheet via a script running in those sheets. Is this possible? When I tried this, it denied them access to the master sheet. Is there a way around this?
One work around that I've used for this problem is to set an installable trigger to run on a regular time interval.
Installable triggers run on the owner of the trigger's authority, so it will be able to access both the main spreadsheet as well as each dealer's. The trick is to assign a timed trigger to a function that checks each dealer's sheet for updates and then makes the appropraite change to the main spreadsheet. You'll want to be sure that the trigger doesn't take too long to run or run too frequently since you have a limited amount of script run time each day.
Something like the following:
function myTrigger() {
var time = new Date();
// maybe check to see if it's night or other times that dealers won't update
// that way you can return early and don't waste quota hours
var dealerSpreadsheetIds = ["id1", "id2", ...];
var dealerSheetName = "Deals";
for (var i = 0; i < dealerSpreadsheetIds.length; i++) {
var sheet = SpreadsheetApp.openById(dealerSpreadsheetIds[i]).getSheetByName(dealerSheetName);
// check Deals sheet ranges for updates
// if there is an update, update the main spreadsheet
}
}

Categories

Resources