optimise Google Apps script - performance

Could anyone please help me optimise this Google Sheets script?
It's painfully slow. It barely makes 300 copies in 24 hours (I need the Drive API to make the copies as "view only users can't copy/print/download")
function addCopies() {
var count = 10;
var input = DriveApp.getFileById('1GQI5m0g5XsCWi1dOTBWFs3VJkzW7e8N__09C3-2BEGI')
var results = DriveApp.getFileById('1gkOCTPJuqTQnNNlaE3jD2Hq2COU-lO02PmlO_xMepx0')
var indexSH = SpreadsheetApp.openById('1o6ZfweVylhnDXoVc9u_R8afKdVfXYgtdqtj_lSCdFEk').getSheetByName('Index')
for(var i=0;i<count;i++)
{
//make copies of both files (input & results)
var inputCopy = input.makeCopy(input.getName(), DriveApp.getFolderById('0B0go-fsdFA3ISG0xNGlhTDZoM0U'))
inputCopy.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.EDIT)
var inputID = inputCopy.getId()
//make results to be restricted using API
var resultsCopy = results.makeCopy(results.getName(), DriveApp.getFolderById('0B0go-fsdFA3ISG0xNGlhTDZoM0U'))
resultsCopy.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW)
var resultsID = resultsCopy.getId()
var apiFile = DriveAPI.Files.get(resultsID)
apiFile.labels.restricted = true
DriveAPI.Files.update(apiFile, resultsID)
//save IDs of both files to an index for later re-use
var lastRow = indexSH.getRange(1,1).getValue()
var addRng = indexSH.getRange(lastRow+1, 1,1,3)
addRng.setValues([['free',inputID,resultsID]])
}
}
Edit: techydesigner said "we don't optimise code on SO" . So please let me rephrase : am I using the wrong Drive services ? Is there a method to make those copies faster ?

Related

What is the most efficient way to get borders in Google Apps Script

What is the best way to get border details in Google Apps Script?
I cannot see anything with borders in the documentation for GAS, so I have had to resort to getting borders through the Spreadsheet API.
This works ok, other than when the number of borders gets large where it will take a long time to return, or not return at all.
Is there a better way to do this?
var fieldsBorders = 'sheets(data(rowData/values/userEnteredFormat/borders))';
var currSsId = SpreadsheetApp.getActiveSpreadsheet().getId();
var activeSheet = SpreadsheetApp.getActiveSheet();
var name = activeSheet.getName();
var data = Sheets.Spreadsheets.get(currSsId, {
ranges: name,
fields: fieldsBorders
});
You want to reduce the process cost for retrieving the borders from a sheet in Spreadsheet.
When I set the borders for 26 x 1000 cells and run your script, the process time was about 50 s in my environment.
For this situation, you want to reduce the cost more.
Your reply comment is perhaps it was a bigger sheet it was on, either way 50s is a long time to get the borders. The other calls to GAS take a very small amount of time to complete. Can you confirm this is the only way to get borders?
If my understanding is correct, how about this workaround? In this workaround, I request directly to the endpoint of Sheets API for retrieving the borders.
Workaround:
Sample situation
In this sample script, as a sample situation, I supposes the default sheet which has 26 columns x 1000 rows, and the borders are set to all cells.
Sample script 1:
In this sample script, the borders are retrieved by one API call.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var fileId = ss.getId();
var sheetName = ss.getActiveSheet().getName();
var token = ScriptApp.getOAuthToken();
var fields = "sheets/data/rowData/values/userEnteredFormat/borders";
var params = {
method: "get",
headers: {Authorization: "Bearer " + token},
muteHttpExceptions: true,
};
var range = sheetName + "!A1:Z1000";
var url = "https://sheets.googleapis.com/v4/spreadsheets/" + fileId + "?ranges=" + encodeURIComponent(range) + "&fields=" + encodeURIComponent(fields);
var res = UrlFetchApp.fetch(url, params);
var result = JSON.parse(res.getContentText());
Result:
When the sample script 1 was used, the average process time was 2.2 seconds.
Although I'm not sure about the internal process of Sheets API of Advanced Google Service, it was found that when it directly requests to the endpoint, the process cost can be reduced.
Sample script 2:
In this sample script, the borders are retrieved with the asynchronous process by several API calls.
var sep = 500; // Rows retrieving by 1 request.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var fileId = ss.getId();
var sheetName = ss.getActiveSheet().getName();
var token = ScriptApp.getOAuthToken();
var fields = "sheets/data/rowData/values/userEnteredFormat/borders";
var requests = [];
var maxRows = 1000;
var row = 1;
for (var i = 0; i < maxRows / sep; i++) {
var range = sheetName + "!A" + row + ":Z" + (row + sep - 1);
requests.push({
method: "get",
url: "https://sheets.googleapis.com/v4/spreadsheets/" + fileId + "?ranges=" + encodeURIComponent(range) + "&fields=" + encodeURIComponent(fields),
headers: {Authorization: "Bearer " + token},
});
row += sep;
}
var response = UrlFetchApp.fetchAll(requests);
var result = response.reduce(function(ar, e) {
var obj = JSON.parse(e.getContentText());
Array.prototype.push.apply(ar.sheets[0].data[0].rowData, obj.sheets[0].data[0].rowData);
return ar;
}, {sheets: [{data: [{rowData: []}]}]});
Result:
When the sample script 2 was used, the following results were obtained.
When sep is 500 (in this case, 2 API calls are run.), the average process time was 1.9 seconds.
When sep is 200 (in this case, 5 API calls are run.), the average process time was 1.3 seconds.
But if the number of requests in one run are increased, the error related to the over of quotas occurs. Please be careaful this.
Note:
This is a simple sample for testing above situation. So I think that above script cannot be used for all situations. If you use above sample script, please modify it for your situation.
References:
fetchAll(requests)
Benchmark: fetchAll method in UrlFetch service for Google Apps Script

For loop over a Google Sheets Range fails to change file owner

I need to transfer ownership of thousands of files I own to someone else with editing access, but I've learned that Google Drive requires you to do this one file at a time. With intermediate but growing JS experience I decided to write a script to change the owner from a list of file IDs in a spreadsheet, however I've run into trouble and I have no idea why. The debugger just runs through the script and everything looks sound for me. I'd greatly appreciate any help.
function changeOwner() {
var ss = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/********/').getSheetByName('Sheet1');
var range = ss.getRange(1,1,2211);
for (i = 0; i < range.length; i++){
var file = range[i][0].getValue();
var fileid = DriveApp.getFileById(file);
fileid.setOwner('******#gmail.com');
}
}
Tested below code working fine with IDs,
function myFunction() {
var spreadsheet = SpreadsheetApp.openById('ID');
var range = spreadsheet.getDataRange().getValues();
for (i = 0; i < range.length; i++){
var file = range[i][0].toString();
var fileid = DriveApp.getFileById(file);
fileid.setOwner('***#gmail.com');
}
}
Your issue is that the Range class had no length property. Instead, perform a getValues() call on the range to efficiently create a JavaScript array, and iterate on it:
function changeOwner() {
var ss = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/********/').getSheetByName('Sheet1');
var values = ss.getRange(1, 1, 2211).getValues();
for (i = 0; i < values.length; ++i){
var fileid = value[i][0];
var file = DriveApp.getFileById(fileid);
file.setOwner('******#gmail.com');
}
}
There are other improvements you can make, such as:
dynamic range read (rather than the fixed assumption of 2211 rows of data)
writing to a second array to keep track of which files you have yet to process
checks for if the file exists
checks for if the file is actually owned by you
checks for execution time remaining, to allow serializing any updates/tracking you do that prevents the next execution from repeating what you just did
and so on. I'll let you research those topics, and the methods available in Apps Script documentation for the DriveApp, SpreadsheetApp, Sheet, and Range classes. Note that you also have all features of Javascript 1.6 available, so many of the things you find on a Javascript Developer Reference (like MDN) are also available to you.

script to sort sheet by 2 colums

Please help make the script. As written below, I wrote a script that worked for some time. Now it does not work and I need help writing a new one.
The challenge is this: when you make changes in column F, K and O - occurs first check the availability of the text in the column to the - then if there is a text should start sorting first by F column - then sorting by a column O
There is a scheme of action sequences by the link:
https://drive.google.com/file/d/0B5qSx6LqB8U-d01URS1BcEVtNGs/view?usp=sharing
I will be happy if someone can help me.
In any case, thanks for your time and attention :) Have a nice day :)
29/03/17 I need help "Service error: spreadsheets"
A very recently worked script:
function onEdit() {
var ss = SpreadsheetApp.getActiveSheet();
var r = SpreadsheetApp.getActiveRange();
var cols = r.getColumn();
var rows = r.getRow();
var who = ss.getRange(rows,11).getValue();
if (who !== "") {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0]
sheet.sort(6);
sheet.sort(15);}
}
Today received an error:
Service error: spreadsheets
The script stopped working at all, help, please.
I'm not following how this completes your action sequence flow chart, but I believe this should get it running as before. I've seen other posts where people get this same Service Error message form previously working scripts, and I believe Google is trying to change how certain tasks are done to help their server loads.
function onEdit() {
var ss = SpreadsheetApp.getActive();
var r = SpreadsheetApp.getActiveRange();
var cols = r.getColumn();
var rows = r.getRow();
var who = ss.getActiveSheet().getRange(rows,11).getValue();
if (who !== "") {
var sheet = ss.getSheets()[0];
sheet.sort(6);
sheet.sort(15);}
}
I removed the duplicate definition of var ss and tweaked var ss, var who and var sheet to work without it.

Faster Iterative Calculation

I've made a script that performs an iterative calculation to find values based on a gross percentage of project cost.
This is what I have so far:
//<!-- FEE, BOND, & INSURANCE CALCULATOR --!>
function feeBondInsCalculator (){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Pretty Sum Form");
sheet.activate();
// Get all variables from named ranges
var bondValue = ss.getRangeByName("bondValue");
var bondCalc = ss.getRangeByName("bondCalc");
var insValue = ss.getRangeByName("insValue");
var insCalc = ss.getRangeByName("insCalc");
var feeValue = ss.getRangeByName("feeValue");
var feeCalc = ss.getRangeByName("feeCalc");
var interCount = ss.getRangeByName("interCount")
// Interative calculation to paste calculated values into the body of the spreadsheet
for (var i = 0; i<11; i++){
feeCalc.copyTo(ss.getRangeByName("feeValue"), {contentsOnly: true});
bondCalc.copyTo(ss.getRangeByName("bondValue"), {contentsOnly: true});
insCalc.copyTo(ss.getRangeByName("insValue"), {contentsOnly: true});
interCount.setValue(i)
}
}
I used range names so that users can add/delete rows and columns and not have to reset the code. When executed, the code works fine, but takes about two seconds per iteration. Is there a more efficient way to make this work?
Instead of using copy, you might try referencing your ranges directly in a formula like:
=arrayformula(if(bondValue <>"",bondValue,""))

Import random columns from spreadsheet into order in new spreadsheet

I'm trying to code a script that imports specific columns for a spreadsheet to another spreadsheet. However, currently i'm writing/reading every column separately, which takes quite a while. I do this because I don't need all the columns from the original sheet into the new sheet. I was wondering if my current script could be changed to get to a script where you once read the necessary columns and write them all together? I tried to search for the same but the different order of the columns and the need for specific columns makes it hard.
I hope someone could help me get to the answer.
Thanks in advance,
Tim
function getPDdataupper() {
var sheet = SpreadsheetApp.getActive().getSheetByName('SPR');
var Num = Browser.inputBox("What URL are do you want to copy from?");
var ssraw = SpreadsheetApp.openById(Num);//put Address in here*****
var sheetraw = ssraw.getSheetByName("SPR");
sheet.getRange(10,11,(sheet.getLastRow())-9,3).clearContent();
sheet.getRange(10,1,(sheet.getLastRow())-9,3).clearContent();
//ADJUST ROW
var lastrow = sheet.getLastRow()-10;
var lastrowcopiedsheet = sheetraw.getLastRow()-10;
var diff = lastrowcopiedsheet - lastrow;
if(diff > 0){sheet.insertRowsAfter(11,diff);}
if(diff < 0){sheet.deleteRows(20,(diff*-1)); }
sheet.getRange(11,1,(sheet.getLastRow())-11,33).clearContent();
var values = sheetraw.getRange(1,1,5,1).getValues();
sheet.getRange(1,1,5,1).setValues(values);
var values = sheetraw.getRange(10,3,sheetraw.getLastRow()-9,1).getValues();
sheet.getRange(10,2,(sheet.getLastRow()-9),1).setValues(values);
var values = sheetraw.getRange(10,38,sheetraw.getLastRow()-9,2).getValues();
sheet.getRange(10,3,(sheet.getLastRow()-9),2).setValues(values);
var values = sheetraw.getRange(10,43,sheetraw.getLastRow()-9,1).getValues();
sheet.getRange(10,6,(sheet.getLastRow()-9),1).setValues(values);
var values = sheetraw.getRange(10,44,sheetraw.getLastRow()-9,1).getValues();
sheet.getRange(10,5,(sheet.getLastRow()-9),1).setValues(values);
var values = sheetraw.getRange(10,6,sheetraw.getLastRow()-9,1).getValues();
sheet.getRange(10,7,(sheet.getLastRow()-9),1).setValues(values);
var values = sheetraw.getRange(10,54,sheetraw.getLastRow()-9,2).getValues();
sheet.getRange(10,8,(sheet.getLastRow()-9),2).setValues(values);
var values = sheetraw.getRange(10,58,sheetraw.getLastRow()-9,1).getValues();
sheet.getRange(10,10,(sheet.getLastRow()-9),1).setValues(values);
var values = sheetraw.getRange(10,14,sheetraw.getLastRow()-9,19).getValues();
sheet.getRange(10,14,(sheet.getLastRow()-9),19).setValues(values);
var values = sheetraw.getRange(10,51,sheetraw.getLastRow()-9,1).getValues();
sheet.getRange(10,33,(sheet.getLastRow()-9),1).setValues(values);
var values = sheetraw.getRange(10,62,sheetraw.getLastRow()-9,1).getValues();
sheet.getRange(10,34,(sheet.getLastRow()-9),1).setValues(values);
var values = sheetraw.getRange(10,4,sheetraw.getLastRow()-9,1).getValues();
sheet.getRange(10,35,(sheet.getLastRow()-9),1).setValues(values);
I have sorted it myself now. For someone who is looking for the same thing, please see the code below. If anyone else thinks of something better please feel free to amend.
function getPDdataupper() {
var sheet = SpreadsheetApp.getActive().getSheetByName('SPR');
var folder= DriveApp.getFolderById('FolderID');
var files = folder.getFilesByType(MimeType.GOOGLE_SHEETS);
while (files.hasNext()) {
var ssraw = SpreadsheetApp.open(files.next());
//var ssraw = SpreadsheetApp.open(Num);
}
sheet.getRange(11,1,(sheet.getLastRow())-10,53).clearContent();
var sheetraw = ssraw.getSheetByName("SPR");
sheet.getRange(10,11,(sheet.getLastRow())-9,3).clearContent();
sheet.getRange(10,1,(sheet.getLastRow())-9,3).clearContent();
//ADJUST ROW
var lastrow = sheet.getLastRow()-10;
var lastrowcopiedsheet = sheetraw.getLastRow()-10;
var diff = lastrowcopiedsheet - lastrow;
if(diff > 0){sheet.insertRowsAfter(11,diff);}
if(diff < 0){sheet.deleteRows(20,(diff*-1)); }
//var values = sheetraw.getRange(10,("C10:C,AL10:AM,AQ10:AR,F10:F,BB10:BC,BF10:BF"),(sheetraw.getLastRow()-9),53).getValues();
//sheet.getRange(10,2,(sheet.getLastRow()-9),(sheet.getLastColumn()-1)).setValues(values);
var data = sheetraw.getDataRange().getValues();{
var target = new Array();
for(n=0;n<data.length;++n){
target.push( [[data[n][2]], [data[n][37]], [data[n][38]], [data[n][43]], [data[n][42]], [data[n][5]], [data[n][53]], [data[n][54]], [data[n][57]]] );
}
sheet.getRange(1,2,target.length,target[0].length).setValues(target);
}

Resources