Google App Script randomly stop executing - javascript

I have a script that basically take info from a website for multiple users, and put these info in a google spreadsheet, with one sheet per users.
I have a function that remove values of the firstline, resize every columns, and put back the setValues:
function adjustColumnsAndIgnoreFirstLine(sheet) {
Logger.log('--- Adjust columns ---')
const range = sheet.getRange("1:1")
// save the title line
const datas = range.getValues();
// clear it
range.clearContent();
// format without the title lines
var lastColumn = sheet.getLastColumn()
sheet.autoResizeColumns(1, lastColumn);
// set width to a minimum
for (var i = 1; i < 37; i++) { // fixed number of columns
if (sheet.getColumnWidth(i) < 30) {
sheet.setColumnWidth(i, 30);
}
}
// put back titles
range.setValues(datas);
}
my problem is that the script stop executing in the middle of the function. I still have the "execution please wait" popup, but in the logs, the script stopped like there was no error (execution finished) with this as the last log:
And, on the google spreadsheet:
One thing to note is that the problem doesn't comes from the script itself, as I do not encounter this problem on any of my machines, but my client does. My client ran the script on different navigator (chrome and edge), and had the same problem, but on different users (sometimes it blocks at the before-last user, sometimes at the before-before-last user...)
So I'm kinda lost on this problem...

The problem is actually a timeout. Google app script limit the execution time of a script at ~6 minutes.
There is existing issues for this

Related

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.

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.

Javascript Optimization for IE 11

I have a Website which only needs to support IE11
It is a single page application, which has about 200 table rows and each table row has 5 child rows
There is a pulsing function that updates the table as records come in. Table rows are skipped over if no update comes in.
However, when receiving large updates (which should only occasionally happening), the application will hang as it slowly processes the javascript. I've tried to limit the javascript as much as possible, but still have a long running function.
I am a back end developer by nature, and was wondering if anyone had any tips to help support large table ajax updates for IE since IE so poorly handles JS.
Here is the function
function writeTableLines(tempRows){
/* This Function takes care of updating the text and coloring of
required dynamic fields.
All other values are not dynamically written.
*/
for( i in tempRows){
//i is the computer name
tempValues = tempRows[i];
// For Row
selector = "[id='"+i+"']";
// Network Name
network_selector = "[id='"+i+"_network']";
$(network_selector).text(tempValues['network']);
if (tempValues['network_color']){
$(network_selector).addClass(tempValues['network_color']);
$(selector).find('.name').addClass(tempValues['network_color']);
}else{
$(network_selector).removeClass('warning');
$(selector).find('.name').removeClass('warning');
}
// Boot Time
boot_selector = "[id='"+i+"_boot']";
$(boot_selector).text(tempValues['boot']);
if (tempValues['boot_color']){
$(boot_selector).addClass(tempValues['boot_color']);
$(selector).find('.name').addClass(tempValues['boot_color'])
}else{
$(boot_selector).removeClass('issue');
$(selector).find('.name').removeClass('issue');
}
// Last Checked In Timestamp
check_in_selector = "[id='"+i+"_checked_in']";
$(check_in_selector).text(tempValues['checked_in']);
if (tempValues['service_unresponsive']){
$(check_in_selector).addClass('redline');
$(selector).find('.name').addClass('redline');
}else{
$(check_in_selector).removeClass('redline');
$(selector).find('.name').removeClass('redline');
}
util_selector = $(selector).find('td.util').find('a');
$(util_selector).text(tempValues['util'])
if (tempValues['util_class']){
$(util_selector).addClass(tempValues['util_class']);
}else{
$(util_selector).removeClass('redline warning');
}
workgroup_selector = $(selector).find('td.workgroup');
if (($.trim(tempValues['workgroup'])) != $.trim($(workgroup_selector).text())){
if ((tempValues['workgroup'] != selected) && (selected != 'All')){
$(workgroup_selector).addClass('warning');
}else{
$(workgroup_selector).removeClass('warning');
}
}
$(workgroup_selector).text(tempValues['workgroup'])
toggle_links(i, tempRows[i]);
$('#connectionGrid').trigger('updateAll', [false]);
}
}
this function iterates over only received data.
For each row item that was received, update the text of the cell, and add coloring as necessary.
I'm thinking I might just be screwed since its IE, but am open to all suggestions and ideas.
Thanks for reading
Image of the rows - child rows only available when expanded, but still need updates
So In case anyone was wondering.
the last line $('#connectionGrid').trigger('updateAll', [false]); was being executed inside the loop.
As opposed to just once when the loop was finished.
Woops

multiple Javascript run in parallel

I have a quite complex .Net page with an intensive usage of a third party libraries (DevExpress).
My page is composed of several parts:
A "Result Browser" to let user select the content of two widgets:
A Table widget
A Graphic widget
A timer (AspxTimer) to refresh the "Result browser" contents
A time widget which gives time every second
I make an intensive use of CallBacks (AspxCallBack) to minimize the volume of data to transfer from/to the server for the refresh of the browser/table and graphic.
During tests, each element separately is working well but one of the feature of the table is to re-calculate sub totals of the table when user click on a specific button. I have marked the correct subTotal cells (containing the span) during table construction with appropriate properties so I can find them on client side with javascript (<span ... SubTotal='true'>0.0</span>) and have a vector of class (code, number) to store actual numbers do recalculate subTotal from.
. Here is my code:
function recalcSubTotal() {
$('span[SubTotal="true"]').each(function() {
var subSpan = $(this);
var sTrends = subSpan.attr('trendsTotal');
var Trends = sTrends.split('|');
var subTotal = 0.0;
for (var i = 0; i < Trends.length - 1; i++) {
subTotal += Decision[Trends[i]];
}
subSpan.html(subTotal.toFixed(1));
});
}
This works pretty well in an isolated page but when mixing all this stuff in a single page I randomely have NaN (Not a numer) codes returned by this func or wrong totals, click again the action button and it can work correctly. Nothing else but the relative complexity and parallel usage of javascript+callbacks can really explain this behavior. I managed to avoid any parallel Callback (chaining them) but can't do that with client side events or date/time timer. I wonder If there is a general clean way of dealing with client side parallel run (timer+callbacks+user actions).
Thanks for any clue on this topic.
Try to call your function recalcSubTotal() with the methode setTimeout(), like this :
setTimeout(function(){
recalcSubTotal();
}, 0);

Categories

Resources