Run Google Sheets script when formula updates cell - javascript

After much searching, I have solved the first part of my problem: I found the below script which copies data from one range and adds it to another sheet.
However, the range that it is copying from is going to be automatically updating via a formula. So, my next challenge is - how do I get the script to run when the cell changes?
I believe there is a way to 'watch cells' for changes, but I'm really not very technical so I haven't been able to figure it out!
Potentially added complication - I believe 'on edit' scripts only run when the spreadsheet is open, is that right? If so, I'm also going to need to figure out how to get the script to run to check for new values on timed intervals.
Here's my current script:
function moveValuesOnly() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getRange("Sheet1!F1:H3");
var destSheet = ss.getSheetByName("Feuil2"); // Déterminer l'emplacement de la première ligne vide. var destRange = destSheet.getRange(destSheet.getLastRow()+1,1); source.copyTo (destRange, {contentsOnly: true}); source.clear ();
}

How to run a script on time-driven trigger:
You can use the script you already have
You can bind to it a time-driven trigger by
Going on Edit -> Current Project's triggers
Selecting create new trigger
Specifying the function to which you want to bind the trigger
Specifying that the trigger shall be time-driven
Select type of time based trigger and interval as desired
Click on Save

Related

Can I get info about a trigger time interval used to launch google apps script?

I recently discovered google apps script and it's amazing. I wrote a script that adds label to messages using logic that would not be available in normal GMail filters. I have then set that filter script to run on a time trigger, as unfortunately it is not possible to run it when message is received.
How my filter works:
function processInbox(age = "2.5h") {
// process all recent threads in the Inbox
var threads = GmailApp.search("newer_than:"+age+" in:inbox");
Logger.log("Processing "+threads.length + " threads.");
for (const thread of threads) {
if(hasLabels(thread))
continue;
// get all messages in a given thread
var messages = thread.getMessages();
for (const message of messages) {
if(matchesMyFilter(message)) {
thread.addLabel(GmailApp.getUserLabelByName("My label"));
}
}
}
}
As you can see, there is an optional time parameter. Currently, the filter is set to run every 2 hours, so I check messages received in that timeframe with a little margin. However, if I want to set the trigger to run once per day, I would have to manually change the time in the script.
Is it possible to get information about the trigger used to launch the script and set the search range accordingly to the trigger timer interval?
You can create the time-based trigger programmatically instead and pass the timeframe wanted/needed by using the everyHours() method.
function createTimeDrivenTrigger() {
ScriptApp.newTrigger('processInbox')
.timeBased()
.everyHours(YOUR_NUMBER_HERE)
.create();
}
However, unfortunately, you cannot modify an already existing trigger using provided methods since the Trigger class doesn't offer any options of setting any properties to a trigger, unless a new one is created.
Reference
Trigger Class Apps Script;
Managing triggers programmatically.

pre-loading custom functions when running sheets daily script

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".

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
}
}

Click play button in Google Apps Script with Greasemonkey?

I am coding a Greasemonkey script to click the play button for a script in Google Apps Script every five 5 minutes to avoid the execution time limit set by Google.
I was able to identify with the script when the time is over but am unable to click the "run" button by using JavaScript. I have been inspecting the button with Google Chrome and tried a few things but I couldn't make it work.
Can anyone please help me?
I guess clicking any button in the toolbar of Google Sheets would be exactly the same..
Thanks!
You are approaching this in a completely wrong way. You should be including the possibility to restart the execution with a trigger internally in your script. I will show you how I did it. Keep in mind that my script is quite large and it performs a loop and I had to make it remember where in the loop it stopped, so it could continue with the same data.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Purpose: Check if there is enough time left for another data output run
// Input: Start time of script execution
// Output: Boolean value if time is up
function isTimeUp(start, need) {
var cutoff = 500000 // in miliseconds (5 minutes)
var now = new Date();
return cutoff - (now.getTime() - start.getTime()) < need;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here the start is simply a new Date() that you create when you start the script. The need is simply an average time it takes for my script to perform 1 loop. If there is not enough time for a loop, we will cut off the script with another function.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Purpose: Store current properties and create a trigger to start the script after 1 min
// Input: propertyCarrier object (current script execution properties)
// Output: Created trigger ID
function autoTrigger(passProperties, sysKeys) {
var sysProperties = new systemProperties();
if (typeof sysKeys === 'undefined' || sysKeys === null) {
sysKeys = new systemKeys();
}
var triggerID = ScriptApp.newTrigger('stateRebuild')
.timeBased()
.after(60000)
.create()
.getUniqueId();
Logger.log('~~~ RESTART TRIGGER CREATED ~~~');
//--------------------------------------------------------------------------
// In order to properly retrieve the time later, it is stored in milliseconds
passProperties.timeframe.start = passProperties.timeframe.start.getTime();
passProperties.timeframe.end = passProperties.timeframe.end.getTime();
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// Properties are stored in User Properties using JSON
PropertiesService.getUserProperties()
.setProperty(sysKeys.startup.rebuildCache, JSON.stringify(passProperties));
//--------------------------------------------------------------------------
Logger.log('~~~ CURRENT PROPERTIES STORED ~~~');
return triggerID;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Yours can be more simplistic if you do not need to remember where you stopped (judging by your current implementation you do not care whether you start from the beginning or not).
The trigger you create should either aim at the main function or if you need to pass on the data like I do, you will need to have a separate starter function to get the data back from the user properties and pass it on.

Class Session not working in some Google spreadsheets

I have a problem with the class Session in Google Apps Scripts
I wrote a script that records the date and user who edits a sheet in a spreadsheet. It works good with my test page, but when I put it in another spreadsheet (a copy named like "test") it does not work!! I have 8 Spreadsheets with the same number of sheets, all named similar, and the script only works in one of the spreadsheets (and test). This is the script:
function onEdit(event)
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
//Last Session record
var actSht = event.source.getSheetByName("somepage")
var actRng = event.source.getActiveRange();
var lastCell0 = actSht.getRange(2,16);
var lastCell1 = actSht.getRange(2,17);
var date = Utilities.formatDate(new Date(), "GMT-6", "dd/MM/yyyy HH:mm");
var r= actSht.getActiveCell().getRow();
lastCell0.setValue("Date_Mod");
lastCell1.setValue("user");
if(r=='3'){
actSht.getRange(r,17).setValue(Session.getEffectiveUser());
actSht.getRange(r,16).setValue(date);
}
if(r=='4'){
actSht.getRange(r,17).setValue(Session.getEffectiveUser());
actSht.getRange(r,16).setValue(date);
}
}
This is the same code for two spreadsheets, in one it works OK, in the other it does not work.
How do I fix this?
It is most likely a security issue and Session calls are supposed to be avoided in simple triggers due to simple triggers limitations (look at https://developers.google.com/apps-script/guides/triggers/#restrictions, especially in regard to the very vague 5th bullet point - "They may or may not be able to determine the identity of current the user" (mistake on page) and where the link sends you back to the session page). In your case I wonder whether it worked only for the users who shared your domain name.
If you look at https://developers.google.com/apps-script/reference/base/session#getEffectiveUser() there is no reference for simple triggers (which for me is suspicious already) but the statement in regard to installable triggers tells me that it should probably not be used in simple ones.
I just went through a similar exercise myself where both getActiveUser and getEffectiveUser were returning blank when the domain of the script owner did not match the domain of the person who launched the script (in other words this happens when someone shares the script outside the domain and run it under a different username such as their personal Google account). I therefore created an installable trigger instead that calls getEffectiveUser and it works now (note that if you define your installable trigger programmatically rather than through Resources->Current project's triggers make sure the trigger function is in the invoking script file).
Hope this helps.

Categories

Resources