Google Sheets App Script timing out and time based trigger not running - javascript

Below is all of my code from a function that I have been working on for a while. Essentially I am trying to create a load of class analysis packs (I am a teacher) and print a google sheet as a PDF and then change a drop down (effectively changing the page and data) and then print again until all of the class codes in the drop down have been completed. The function works really well but the issue I have is that it will take longer than 6 mins to run as there are about 150 packs to create. I have looked into triggers and created a time based trigger which should start a few minutes after it has timed out. It seems to successfully create the trigger but the trigger never actually runs. Is this the correct approach? If so can anybody spot why its not working? Any feedback would be amazing as this has been driving me crazy!
function CreateClassPacks() {
SpreadsheetApp.getUi() // Or DocumentApp or FormApp
var startTime= (new Date()).getTime();
var REASONABLE_TIME_TO_WAIT = 100000
var MAX_RUNNING_TIME = 340000
// Getting the date and putting it into the format we want
var d= new Date();
var dateStamp = d.getDate()+"/"+d.getMonth()+"/"+d.getYear();
// Getting a token which will give me the authorisation I need
var request = {
"method": "GET",
"headers":{"Authorization": "Bearer "+ScriptApp.getOAuthToken()},
"muteHttpExceptions": true
};
// This is the key for the spreadsheet I am working on and then it gets fetched
var ss = SpreadsheetApp.getActiveSpreadsheet();
var getKeys = ss.getSheetByName("Settings");
var mainSSKey= getKeys.getRange("B1").getValue();
// Key for the folder we will save the documents into
var folderCPKey = getKeys.getRange("B2").getValue();
var foldersave=DriveApp.getFolderById(folderCPKey);
var fetch='https://docs.google.com/spreadsheets/d/'+mainSSKey+'/export?format=pdf&size=A4&portrait=false'
// This section gets all of the class codes from whichever sheet we choose.
// The first variable will need changing to whichever number sheet holds the codes.
var classCodeSheetNum = 0
var classCodeSheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[classCodeSheetNum]
var maxRowNum = classCodeSheet.getLastRow()-1;
// This variable must contain the correct column for the class codes
var dataRange = classCodeSheet.getRange(1, 1, maxRowNum, 1);
var data = dataRange.getValues();
Logger.log(data)
// This must be the sheet number for the class analysis packs
var sheetNum = 4
var newTrig = false
// This will loop through my data variable which contains all the class codes
for (var r=0; r<(data.length)-1; r++) {
for (i in data[0]) {
var scriptProperties = PropertiesService.getScriptProperties();
var startRow= scriptProperties.getProperty('start_row');
var currTime = (new Date()).getTime();
if(currTime - startTime >= MAX_RUNNING_TIME) {
if (newTrig == false){
ScriptApp.newTrigger("CreateClassPacks")
.timeBased()
.at(new Date(currTime+REASONABLE_TIME_TO_WAIT))
.create();
newTrig = true
break;
}
} else {
// This sets the value of A2 on the analysis sheet to the value from the data structure
SpreadsheetApp.getActiveSpreadsheet().getSheets()[sheetNum].getRange('O1').setValue(data[r][i]);
var source = SpreadsheetApp.getActiveSpreadsheet();
var sheet = source.getSheets()[sheetNum];
// This gets the value from A2 and sorts out the name of the file
var classCode = data[r][i]
var name = classCode + " " + dateStamp + ".pdf";
// This checks if the file already exists which will hopefully fix any timeout issues
var file = DriveApp.getFilesByName(name)
var chk = file.hasNext()
if (chk === false) {
// This hides all the sheets except for the one I am printing
for(var w=0; w< sheetNum;w++)
{
sheet = source.getSheets()[w];
sheet.hideSheet();
}
// This PDFs the page and has a timeout delaying the access requests so I don't get the annoying errors
var pdf = UrlFetchApp.fetch(fetch, request);
pdf = pdf.getBlob().setName(name);
Utilities.sleep(4000);
var file = foldersave.createFile(pdf)
// This shows all the sheets that I previously hid
for(var q=0; q< sheetNum;q++)
{
sheet = source.getSheets()[q];
sheet.showSheet();
}
}
}
}
}
}
This shows that the trigger seems to be created even though it doesn't run the function again

Maybe I'm missing something here but I think your code could be a lot quicker. For one thing create the var ss=SpreadsheetApp.getActive() and then use ss everywhere else. All create var allSheets=ss.getSheets() and then use allSheets[i] instead of 'ss.getSheets()[i]`. That section for i in data[0] makes no sense to me. The data is one column wide. All I would hide all of the sheets before the loop and then if necessary show the one that I'm printing if you have to. Hiding and showing sheets takes time. Try not to do it. I have gone through the code a bit but admittedly I don't have any context so I doubt that it runs. But I tried to take as much out of the loop as possible and you should too. You may find that you can get it to run in the allotted time.
function CreateClassPacks() {
var dateStamp=Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "dd/MM/yyyy")
var ss=SpreadsheetApp.getActive();
var getKeys=ss.getSheetByName("Settings");
var mainSSKey=getKeys.getRange("B1").getValue();
var folderCPKey=getKeys.getRange("B2").getValue();
var foldersave=DriveApp.getFolderById(folderCPKey);
var fetch='https://docs.google.com/spreadsheets/d/'+mainSSKey+'/export?format=pdf&size=A4&portrait=false'
var classCodeSheetNum=0;
var classCodeSheet=SpreadsheetApp.getActiveSpreadsheet().getSheets()[classCodeSheetNum]
var maxRowNum=classCodeSheet.getLastRow()-1;
var dataRange=classCodeSheet.getRange(1, 1, maxRowNum, 1);
var data=dataRange.getValues();
var sheetNum=4;
var newTrig=false;
var allSheets=ss.getSheets();
for(var i=0;i<allSheets.length;i++){
allSheets[i].hideSheet();
}
var srcrng=allSheets[sheetNum].getRange('O1');
var sheet=allSheets()[sheetNum];
for (var r=0;r<data.length-1;r++){
srcrng.setValue(data[r][0]);
var classCode = data[r][i]
var name=classCode + " " + dateStamp + ".pdf";
var file=DriveApp.getFilesByName(name);
var chk=file.hasNext()
sheet.showSheet();
var pdf = UrlFetchApp.fetch(fetch, request);
pdf = pdf.getBlob().setName(name);
var file = foldersave.createFile(pdf);
sheet.hideSheet();
}
}

Related

Google Sheets Script Exceeded maximum execution time, even with flush option?

So I am a noob in coding but managed to adjust the code a bit to do what I need it to do and that is to list the files inside a folder and its subfolder files.
The issue is that the files total is about 50k or more and keeps increasing each day XD
so now most of the time I get a "Exceeded maximum execution time" and sometimes's I don't. Inside the script, there is a flush function so it should reset the timer if I am correct?
I run the script every day I do not know what to do to fix this?
I think the best would be to have a function that will check if it is already listed and if not updated to skip it to speed up the script but again I just do not know atm ware to start.
If someone could help me to fix the " Exceeded maximum execution time" I would be extremely grateful.
this is the script:
function ListarTodo() {
/* Adapted from Code written by #Andres Duarte in this link:
https://stackoverflow.com/questions/59045664/how-to-list-also-files-inside-subfolders-in-google-drive/63182864#63182864
*/
// List all files and sub-folders in a single folder on Google Drive
// declare the folder name
var foldername = 'Beelden';
// declare this sheet
var sheet = SpreadsheetApp.getActiveSheet();
// clear any existing contents
sheet.clear();
// append a header row
sheet.appendRow(["Folder","Name", "Last Updated", "Size MB", "URL"]);
// getFoldersByName = Gets a collection of all folders in the user's Drive that have the given name.
// folders is a "Folder Iterator" but there is only one unique folder name called, so it has only one value (next)
var folders = DriveApp.getFoldersByName(foldername);
var foldersnext = folders.next();
var lintotal = 2;
// Initiate recursive function
lintotal = SubCarpetas(foldersnext, foldername, lintotal);
}
function SubCarpetas(folder, path, cantlineas) {
cantlineas = ListarArchivos(folder, path, cantlineas);
var subfolders = folder.getFolders();
while (subfolders.hasNext()) {
var mysubfolders = subfolders.next();
var mysubfolderName = mysubfolders.getName();
var newpath = "";
newpath = path + "/" + mysubfolderName;
cantlineas = SubCarpetas(mysubfolders, newpath, cantlineas);
}
return(cantlineas)
}
// list files in this folder
// myfiles is a File Iterator
function ListarArchivos(mifoldersnext, mipath, milintotal) {
var datos = []; //temporary array that we are going to use to record on the sheet
var files = []; //array with all the files that we find in the folder that we are evaluating
var file = []; //array that we use to dump the data of each file before saving it
var total = 0;
var sheet = SpreadsheetApp.getActiveSheet();
var myfiles = mifoldersnext.getFiles();
// We create an array with the data of each file and save the total number of files
while (myfiles.hasNext()) {
files.push(myfiles.next());
total++;
}
// we sort the array by file names alphabetically // sorts the files array by file names alphabetically
files = files.sort(function(a, b){
var aName = a.getName().toUpperCase();
var bName = b.getName().toUpperCase();
return aName.localeCompare(bName);
});
////
var vuelta = 0;
var bulk = 500; // We define the number of lines to record each time, in the GoogleDoc spreadsheet
var linea = milintotal; // we define in which line we are going to save in the spreadsheet
for (var i = 0; i < files.length; i++) { // we go through the array of files and format the information we need for our spreadsheet
file = files[i];
var fname = file.getName(); //file name
var fdate = file.getLastUpdated(); // date and time last modified
var fsize = file.getSize()/1024/1024; // file size, we pass it from byte to Kbyte and then to Mb
fsize = +fsize.toFixed(2); // we format it to two decimal places
var furl = file.getUrl(); //File URL
datos[vuelta] = [mipath+" ("+total+")", fname, fdate, fsize, furl]; // we put everything inside a temporary array
vuelta++;
if (vuelta == bulk) {// when it reaches the defined quantity, save this array with 10 lines and empty it
linea = milintotal;
// Logger.log("linea = "+linea); //DEBUG
// Logger.log("vuelta = "+vuelta); //DEBUG
// Logger.log("total = "+total); //DEBUG
// Logger.log("lintotal = "+milintotal); //DEBUG
// Logger.log("registros en datos = "+datos.length); //DEBUG
// Logger.log("data = "+datos); //DEBUG
sheet.getRange(linea, 1, bulk,5).setValues(datos); // we save the data of the temporary array in the sheet
SpreadsheetApp.flush(); // we force the data to appear on the sheet - without this the data does not appear until finished (it generates a lot of impatience)
milintotal = milintotal + vuelta;
datos = []; // empty the temporary array
vuelta = 0;
}
}
if (datos.length>0) {// When exiting the loop we record what is left in the data array
linea = milintotal;
// Logger.log("linea = "+linea); //DEBUG
// Logger.log("vuelta = "+vuelta); //DEBUG
// Logger.log("total = "+total); //DEBUG
// Logger.log("lintotal = "+milintotal); //DEBUG
// Logger.log("records in data = "+ data.length); //DEBUG
// Logger.log("data = "+datos); //DEBUG
sheet.getRange(linea, 1, datos.length,5).setValues(datos);
SpreadsheetApp.flush(); //ansiolítico
milintotal = milintotal + datos.length;
datos = [];
vuelta = 0;
}
return (milintotal)
}
Thank you all
UPDATE!
So after looking at multiple forumes i found that the best thing atm i can do is make a break for this script by using the following script:
function runMe() {
var startTime= (new Date()).getTime();
//do some work here
var scriptProperties = PropertiesService.getScriptProperties();
var startRow= scriptProperties.getProperty('start_row');
for(var ii = startRow; ii <= size; ii++) {
var currTime = (new Date()).getTime();
if(currTime - startTime >= MAX_RUNNING_TIME) {
scriptProperties.setProperty("start_row", ii);
ScriptApp.newTrigger("runMe")
.timeBased()
.at(new Date(currTime+REASONABLE_TIME_TO_WAIT))
.create();
break;
} else {
doSomeWork();
}
}
//do some more work here
}
But after trying I do not understand where to divide this to make it work T-T any ideas?
I think the best would be to have a function that will check if it is already listed and if not updated to skip it to speed up the script but again I just do not know atm ware to start.
These links from the official documentation should help:
https://developers.google.com/apps-script/reference/drive/drive-app#searchFiles(String)
https://developers.google.com/apps-script/reference/drive/drive-app#searchFolders(String)
https://developers.google.com/drive/api/v2/search-files
https://developers.google.com/drive/api/v2/ref-search-terms
You can use the information in the documentation to create a script that only pulls the most recently added and/or updated files.
However, if you need to process a large volume of recently updated files you'll need to leverage some kind of batch processing solution that spans multiple sessions.

How can I make my Google Apps Script function work on cell input?

I have a project that I've been working on for a bit. I've received some excellent help here, and I think I'm almost done and just need one more bit of help to get it working.
The script looks at a Google Sheet and takes a place name entered in Column A and uses the Google Places API to find requested information about it (address, phone number, etc.)
The last bit of help that I need will be able to implement the cell input component. The last user to help me said that
function writeToSheet(){
var ss = SpreadsheetApp.getActiveSheet();
var data = COMBINED2("Food");
var placeCid = data[4];
var findText = ss.createTextFinder(placeCid).findAll();
if(findText.length == 0){
ss.getRange(ss.getLastRow()+1,1,1, data.length).setValues([data])
}
}
would be able use TextFinder to check if the place url exists in the Sheet. If the result of TextFinder is 0, it will call COMBINED2() to get the place information and populate the Sheet with writeToSheet()
They noted that
You can use a cell input in your COMBINED2 by using
ss.getRange(range).getValue()
Not having a coding background, I have been able to stitch most of this together on my own, but I could use a bit of help in adding that capability to my code. Any help or guidance would be great.
Here is the code in full:
// This location basis is used to narrow the search -- e.g. if you were
// building a sheet of bars in NYC, you would want to set it to coordinates
// in NYC.
// You can get this from the url of a Google Maps search.
const LOC_BASIS_LAT_LON = "40.74516247433546, -73.98621366765816"; // e.g. "37.7644856,-122.4472203"
function COMBINED2(text) {
var API_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxx';
var baseUrl = 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json';
var queryUrl = baseUrl + '?input=' + text + '&inputtype=textquery&key=' + API_KEY + "&locationbias=point:" + LOC_BASIS_LAT_LON;
var response = UrlFetchApp.fetch(queryUrl);
var json = response.getContentText();
var placeId = JSON.parse(json);
var ID = placeId.candidates[0].place_id;
var fields = 'name,formatted_address,formatted_phone_number,website,url,types,opening_hours';
var baseUrl2 = 'https://maps.googleapis.com/maps/api/place/details/json?placeid=';
var queryUrl2 = baseUrl2 + ID + '&fields=' + fields + '&key='+ API_KEY + "&locationbias=point:" + LOC_BASIS_LAT_LON;
if (ID == '') {
return 'Give me a Google Places URL...';
}
var response2 = UrlFetchApp.fetch(queryUrl2);
var json2 = response2.getContentText();
var place = JSON.parse(json2).result;
var weekdays = '';
place.opening_hours.weekday_text.forEach((weekdayText) => {
weekdays += ( weekdayText + '\r\n' );
} );
var data = [
place.name,
place.formatted_address,
place.formatted_phone_number,
place.website,
place.url,
weekdays.trim()
];
return data;
}
function getColumnLastRow(range){
var ss = SpreadsheetApp.getActiveSheet();
var inputs = ss.getRange(range).getValues();
return inputs.filter(String).length;
}
function writeToSheet(){
var ss = SpreadsheetApp.getActiveSheet();
var data = COMBINED2("Food");
var placeCid = data[4];
var findText = ss.createTextFinder(placeCid).findAll();
if(findText.length == 0){
ss.getRange(ss.getLastRow()+1,1,1, data.length).setValues([data])
}
}
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu("Custom Menu")
.addItem("Get place info","writeToSheet")
.addToUi();
}
Update
Here is a link to a Shared Sheet in case anyone wants to work on it with me.
https://docs.google.com/spreadsheets/d/1KGsk6nkin1CUgpjfHU_AdhF17T_Eh41_g4MLb1CG_Tk/edit#gid=2100307022
Here is what I might not have articulated properly.
I wanted to be able to enter the names of places in Column A
Then, I want to be able to run the function with the custom menu feature. If TextFinder does not find the Place URL for the given place, it will look up the data and write it to the Sheet.
I wanted to limit the number of API calls with this and to make sure the data was written to the Sheet so that it does not need to be pulled each time the Sheet is reopened.
Finished Product
Big thanks to Lamblichus for sticking this out with me. I hope this helps other people some day.
Here is the finished code:
// This location basis is used to narrow the search -- e.g. if you were
// building a sheet of bars in NYC, you would want to set it to coordinates
// in NYC.
// You can get this from the url of a Google Maps search.
const LOC_BASIS_LAT_LON = "ENTER_GPS_COORDINATES_HERE"; // e.g. "37.7644856,-122.4472203"
function COMBINED2(text) {
var API_KEY = 'ENTER_API_KEY_HERE';
var baseUrl = 'https://maps.googleapis.com/maps/api/place/findplacefromtext/json';
var queryUrl = baseUrl + '?input=' + text + '&inputtype=textquery&key=' + API_KEY + "&locationbias=point:" + LOC_BASIS_LAT_LON;
var response = UrlFetchApp.fetch(queryUrl);
var json = response.getContentText();
var placeId = JSON.parse(json);
var ID = placeId.candidates[0].place_id;
var fields = 'name,formatted_address,formatted_phone_number,website,url,types,opening_hours';
var baseUrl2 = 'https://maps.googleapis.com/maps/api/place/details/json?placeid=';
var queryUrl2 = baseUrl2 + ID + '&fields=' + fields + '&key='+ API_KEY + "&locationbias=point:" + LOC_BASIS_LAT_LON;
if (ID == '') {
return 'Give me a Google Places URL...';
}
var response2 = UrlFetchApp.fetch(queryUrl2);
var json2 = response2.getContentText();
var place = JSON.parse(json2).result;
var weekdays = '';
if (place.opening_hours && place.opening_hours.weekday_text) {
place.opening_hours.weekday_text.forEach((weekdayText) => {
weekdays += ( weekdayText + '\r\n' );
} );
}
var data = [
place.name,
place.formatted_address,
place.formatted_phone_number,
place.website,
place.url,
weekdays.trim()
];
return data;
}
function writeToSheet() {
const sheet = SpreadsheetApp.getActiveSheet();
const FIRST_ROW = 2;
const sourceData = sheet.getRange(FIRST_ROW, 1, sheet.getLastRow()-FIRST_ROW+1, 6)
.getValues().filter(row => String(row[0]));
for (let i = 0; i < sourceData.length; i++) {
const sourceRow = sourceData[i];
if (sourceRow[4] === "") {
const text = sourceRow[0];
const data = COMBINED2(text);
sheet.getRange(FIRST_ROW+i, 2, 1, data.length).setValues([data]);
}
}
}
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu("Custom Menu")
.addItem("Get place info","writeToSheet")
.addToUi();
}
Desired goal:
If I understand you correctly, for each value in column A, you want to retrieve some related data from Maps API and paste it to columns B-F, if column E is not currently populated.
Issues:
You are only providing the last value from column A to COMBINED2, but you want to loop through all values in column A and fetch the desired information for all of them (as long as the Place URL -column E- is not already populated).
If you want to avoid calling Maps API if the Place URL is not populated, using TextFinder after calling Maps API doesn't make sense; you don't limit your calls to the API if you do that. If you just want to check whether the Place URL column is populated, I'd suggest checking whether the cell is empty or not, and calling Maps API if it's empty.
Proposed workflow:
Retrieve all values from the sheet, including not just column A but also E (for practical purposes, all 6 columns are fetched in the sample below, since it can be done in one call), using Range.getValues().
Iterate through the rows (for example, using for), and for each row, check that the cell in E is populated.
If the cell in E (Place URL) is empty, use the value in A as the parameter for COMBINED2 and write the resulting data to columns B-F, as you are currently doing.
Code sample:
function writeToSheet() {
const sheet = SpreadsheetApp.getActiveSheet();
const FIRST_ROW = 2;
const sourceData = sheet.getRange(FIRST_ROW, 1, sheet.getLastRow()-FIRST_ROW+1, 6)
.getValues().filter(row => String(row[0]));
for (let i = 0; i < sourceData.length; i++) {
const sourceRow = sourceData[i];
if (sourceRow[4] === "") {
const text = sourceRow[0];
const data = COMBINED2(text);
sheet.getRange(FIRST_ROW+i, 2, 1, data.length).setValues([data]);
}
}
}
Update:
For names in which Places API doesn't return opening_hours, consider checking if this exists first:
function COMBINED2(text) {
// ... REST OF YOUR FUNCTION ...
var weekdays = '';
if (place.opening_hours && place.opening_hours.weekday_text) {
place.opening_hours.weekday_text.forEach((weekdayText) => {
weekdays += ( weekdayText + '\r\n' );
} );
}
var data = [
place.name,
place.formatted_address,
place.formatted_phone_number,
place.website,
place.url,
weekdays.trim()
];
return data;
}
By using the event trigger function...
function onEdit(e){
SpreadsheetApp.getActiveSheet().getRange(insert your range in A1 format).setValue("anything you want to add into the cell")
}
function onEdit(e){
var ss = SpreadsheetApp.getActiveSheet();
var data = COMBINED2("Food");
var placeCid = data[4];
var findText = ss.createTextFinder(placeCid).findAll();
if(findText.length == 0){
ss.getRange(ss.getLastRow()+1,1,1, data.length).setValues([data])
}
}
u need to specifically tell google apps script that the function is as such so that your function will execute when a event object known as e has happened.
You can read more about it on Simple Triggers

"The JavaScript runtime exited unexpectedly" error when parsing through Google Sheets

I have a function getFnF() that iterates through a Google Drive folder and all of it's subfolders. When getFnF() encounters a Google Sheets file, I have the script parse through that Google Sheets file and extract out any URL Links it finds using my function getLinksFromSheet(). The functions both work, but after about 10 minutes of iterating through the Drive folder and calling getLinksFromSheet() on the Google Sheets files encountered, I get a The JavaScript runtime exited unexpectedly. error. Does anybody have any ideas what would be causing this error? The Google Drive folder is quite large (~500 files total in the subfolders, ~75 of which are Google Sheets). Code below:
function getFnF(folder) {
var folder= folder || DriveApp.getFolderById("0AFZNRhJpE8LKUk9PVA"); //hard goded DEP-Gotham folder
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Sheet1');
var files=folder.getFiles();
while(files.hasNext()) {
var file=files.next();
var firg=sh.getRange(sh.getLastRow() + 1,level + 1);
firg.setValue(Utilities.formatString('File: %s', file.getName()));
Logger.log(file.getName())
//if (file.getMimeType() == 'application/vnd.google-apps.document') {getAllLinks(file.getId(), false);};
//if (file.getMimeType() == 'application/vnd.google-apps.presentation') {getLinksFromSlides(file.getId());};
if (file.getMimeType() == 'application/vnd.google-apps.spreadsheet') {getLinksFromSheet(file.getId());};
}
var subfolders=folder.getFolders()
while(subfolders.hasNext()) {
var subfolder=subfolders.next();
var forg=sh.getRange(sh.getLastRow() + 1,level + 1);
forg.setValue(Utilities.formatString('Fldr: %s', subfolder.getName()));
level++;
getFnF(subfolder);
}
level--;
}
function getLinksFromSheet(sheetId){
var ss = SpreadsheetApp.openById(sheetId);
var sheets = ss.getSheets();
var parentDocName = ss.getName();
var destSs=SpreadsheetApp.getActive();
var destSh=destSs.getSheetByName('Extracted Links');
sheets.forEach(sheet => {
var rangeData = sheet.getDataRange();
var lastColumn = rangeData.getLastColumn();
var lastRow = rangeData.getLastRow();
var searchRange = sheet.getRange(1,1, lastRow, lastColumn);
//var rangeValues = searchRange.getValues();
var rangeValues = searchRange.getRichTextValues();
for (var i = 0; i < lastRow; i++){
for (var j = 0; j < lastColumn; j++){
const runs = rangeValues[i][j].getRuns();
for (const v of runs) {
var nextLink = v.getLinkUrl();
if (nextLink != null) {
var row = destSh.getLastRow() + 1;
var r1=destSh.getRange(row, 1);
r1.setValue(parentDocName);
var r2=destSh.getRange(row, 2);
r2.setValue(nextLink);
};
}
}
}
});
Issue stems most likely from exceeding the limitation of runtime execution.
You can use getFilesByType to further whittle down the list and save time. Also did modify a bit in your code but should be able to do the same thing. They should have comments above them. Kindly check.
Usage:
function getFnF(folder) {
var folder = folder || DriveApp.getFolderById("0AFZNRhJpE8LKUk9PVA"); //hard goded DEP-Gotham folder
var ss = SpreadsheetApp.getActive();
var sh = ss.getSheetByName('Sheet1');
// limit files to only google sheets
var files = folder.getFilesByType(MimeType.GOOGLE_SHEETS);
// assign getLastRow to lessen method calls
var lastRow = sh.getLastRow();
// initialize level value
var level = 1;
while (files.hasNext()) {
var file = files.next();
// I can use appendRow here, but I did't since column has a variable
// and you might change it. Feel free to update if necessary
var firg = sh.getRange(lastRow + 1, level + 1);
firg.setValue(Utilities.formatString('File: %s', file.getName()));
getLinksFromSheet(file.getId());
// iterate lastRow
lastRow++;
}
var subfolders = folder.getFolders()
while (subfolders.hasNext()) {
var subfolder = subfolders.next();
// I can use appendRow here, but I did't since column has a variable
// and you might change it. Feel free to update if necessary
var forg = sh.getRange(lastRow + 1, level + 1);
forg.setValue(Utilities.formatString('Fldr: %s', subfolder.getName()));
level++;
getFnF(subfolder);
// iterate lastRow
lastRow++;
}
// not sure what this does but you can freely remove this if not being used
level--;
}
function getLinksFromSheet(sheetId) {
var ss = SpreadsheetApp.openById(sheetId);
var sheets = ss.getSheets();
var parentDocName = ss.getName();
var destSs = SpreadsheetApp.getActive();
var destSh = destSs.getSheetByName('Extracted Links');
sheets.forEach(sheet => {
// getDataRange already gets all the data
var rangeData = sheet.getDataRange();
// Flatten 2d array
var rangeValues = rangeData.getRichTextValues().flat();
rangeValues.forEach(v => {
var link = v.getLinkUrl();
if(link)
// Use appendRow instead. Adjust array if needed to be in a different column.
destSh.appendRow([parentDocName, link]);
});
});
}
Run time difference:
The 6-second runs is the optimized one above while the 7-second runs is your code.
Test Conditions:
Two sheet files in parent folder, one sheet file in 1 sub-folder:
Each spreadsheet has 2 sheets, each sheet has 1 link.
Parent folder has one non-sheet file.
Note:
Given the significant runtime difference on a lesser number of files, certainly this will have greater impact on a huge number of files.
If you want to include other type of files, then you need to create a separate loop to process each filetype if you have different type of process for each.
You can also join 2 getFilesByType outputs into 1 array but getting links from different file types might vary so separate loop will be much safer.
To get around the time problems you are having, I'd suggest one script to write all the spreadsheet ids and then another script to go down and process them (run getLinksFromSheet()) a line at a time and then mark each line as done so you can rerun it until you can finish.
Getrichtextvalue > getlinkURL is just slowwwwwwwww and you can't get around that.
I think this could also be a memory size, or too much data in a single variable type of issue. I'm getting the same error after seconds of starting a debugging session, but no indication of what is causing it.

Why did my code break when I copied it over from one workbook into another?

So I just had someone on Fiverr write this cote for me and it works great! I had him write it on a copy of the one I needed for work. when I copied into the one that was assigned to me at work it breaks at line 40. Any and all fixes would be great!!
I think it doesn't have the correct sheet selected so it is kicking back a null error
TypeError: Cannot read property 'getRange' of null
This is the error I get after running it. Again tho this works on the workbook I had him design it in but I need to know how to fix it.
// If you want to change any tab's name, also change it from here.
var ss = SpreadsheetApp.getActive();
var tem = ss.getSheetByName("Template");
var frm = ss.getSheetByName("Form Responses 1");
var cde = ss.getSheetByName("Code");
//---------------------------------------------------------------------------------------------------------------------------------------
lr = frm.getLastRow();
// Logger.log(lr)
for(i=4; i<=lr; i++){
//----------These are values from the Form Responses tab, 2,1, 3, 4, 5 ... represent the column numbers.
// For example, date is in column 1, so var date = frm.getRange(i,1).getValue();
// store name is in column 3, so var store = frm.getRange(i,3).getValue();
var tabName = frm.getRange(i,2).getValue();
var date = frm.getRange(i,1).getValue();
var store = frm.getRange(i,3).getValue();
var add1 = frm.getRange(i,4).getValue();
var add2 = frm.getRange(i,5).getValue();
var add3 = frm.getRange(i,6).getValue();
var zip = frm.getRange(i,7).getValue();
var city = frm.getRange(i,8).getValue();
var state = frm.getRange(i,9).getValue();
var phone = frm.getRange(i,10).getValue();
var email = frm.getRange(i,11).getValue();
var po = frm.getRange (i,16).getValue();
var glowHeart = frm.getRange(i,17).getValue();
var glowSquare = frm.getRange(i,18).getValue();
var rainHeart = frm.getRange(i,19).getValue();
var rainSquare = frm.getRange(i,20).getValue();
///-----The following line of code checks if there is already a tab for a store name, if not create new.
if(ss.getSheetByName(tabName)==null && tabName!==""){
var create = ss.insertSheet(tabName);
}
if ( tabName==""){
break;
}
///- - - - - - Now write the row values to the template tab.
tem.getRange("B2").setValue(tabName); // cell B2 is set to name
tem.getRange("B3").setValue(store);// cell B3 is set to store name and so on
tem.getRange("F3").setValue(date);
tem.getRange("B5").setValue(add1);
tem.getRange("B6").setValue(add2);
tem.getRange("B7").setValue(add3);
tem.getRange("B8").setValue(zip);
tem.getRange("B9").setValue(city);
tem.getRange("B10").setValue(state);
tem.getRange("B12").setValue(phone);
tem.getRange("B13").setValue(email);
tem.getRange("C17").setValue(glowHeart);
tem.getRange("C18").setValue(glowSquare);
tem.getRange("D17").setValue(rainHeart);
tem.getRange("D18").setValue(rainSquare);
tem.getRange("F4").setValue(po);
/// Once the template is filled, it is moved to the newly created tab
moveVal(tabName)
// After that, new file is created for that
createNewFile(tabName)
/// These lines clear the template once done.
tem.getRange("B2").setValue("");
tem.getRange("B3").setValue("");
tem.getRange("F3").setValue("");
tem.getRange("B5").setValue("");
tem.getRange("B6").setValue("");
tem.getRange("B7").setValue("");
tem.getRange("B8").setValue("");
tem.getRange("B9").setValue("");
tem.getRange("B10").setValue("");
tem.getRange("B12").setValue("");
tem.getRange("B13").setValue("");
tem.getRange("C17").setValue("");
tem.getRange("C18").setValue("");
tem.getRange("D17").setValue("");
tem.getRange("D18").setValue("");
tem.getRange("F4").setValue("");
}
}
// This fucntion creates new tabs
function moveVal(tabName) {
var ss = SpreadsheetApp.getActive();
var spreadsheet = ss.getSheetByName("Template");
//var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('A1:G29').activate();
ss.setActiveSheet(ss.getSheetByName(tabName), true);
ss.getRange('Template!A1:G29').copyTo(ss.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_NORMAL, false);
ss.setActiveSheet(ss.getSheetByName('Template'), true);
spreadsheet.getRange('F9').activate();
};
// This fucntion creates new files.
function createNewFile(tabName){
var ss = SpreadsheetApp.getActive();
var cde = ss.getSheetByName("Code");
var newF = SpreadsheetApp.create(tabName);
var dd = newF.getId();
Logger.log(dd)
var source = SpreadsheetApp.getActiveSpreadsheet();
var sheet = source.getSheetByName("Price");
var destination = SpreadsheetApp.openById(dd);
sheet.copyTo(destination);
destination.getSheetByName("Copy of Price").setName("Price");
var sheet = source.getSheetByName("Template");
var destination = SpreadsheetApp.openById(dd);
sheet.copyTo(destination);
destination.getSheetByName("Copy of Template").setName(tabName);
destination.getSheetByName("Sheet1").getRange('D26').activate();
destination.deleteActiveSheet();
destination.getSheetByName(tabName).activate()
destination.setActiveSheet(destination.getSheetByName('Price'), true);
destination.getActiveSheet().hideSheet();
sourceFileId =dd;
//targetFolderId = "xxxxxxxxxxxxxx";
////////////////////////////// You can change the cell from here. ////////////
targetFolderId = cde.getRange("G3").getValue(); // This gets the folder's id from cell G3 in Code tab
///..........................................................................................
//Logger.log(targetFolderId)
moveFiles(sourceFileId,targetFolderId)
}
// THis fucntion moves the file to the specified folder.
function moveFiles(sourceFileId,targetFolderId) {
var file = DriveApp.getFileById(sourceFileId);
var folder = DriveApp.getFolderById(targetFolderId);
file.moveTo(folder);
}
It breaks right when it gets to these lines
tem.getRange("B2").setValue(tabName); // cell B2 is set to name
tem.getRange("B3").setValue(store);// cell B3 is set to store name and so on
tem.getRange("F3").setValue(date);
tem.getRange("B5").setValue(add1);
tem.getRange("B6").setValue(add2);
tem.getRange("B7").setValue(add3);
tem.getRange("B8").setValue(zip);
tem.getRange("B9").setValue(city);
tem.getRange("B10").setValue(state);
tem.getRange("B12").setValue(phone);
tem.getRange("B13").setValue(email);
tem.getRange("C17").setValue(glowHeart);
tem.getRange("C18").setValue(glowSquare);
tem.getRange("D17").setValue(rainHeart);
tem.getRange("D18").setValue(rainSquare);
tem.getRange("F4").setValue(po);

Google script trigger to run only on Monday/Wednesday/Friday

I'm running a SendEmail script with a 3 triggers to be sent out on Mondays, Wednesdays and Fridays.
I have 10 sheets on the spreadsheet (each one contains an SentEmail script and each needs to be sent out on those days but I have only 20 trigger limitation)
This is the code:
function sendEmail() {
var s = SpreadsheetApp.getActive().getSheetByName('BCX');
var ss = SpreadsheetApp.getActiveSpreadsheet();
var range = ss.getActiveSheet().getDataRange();
var range = s.getRange('B5:Q20');
var row = ss.getSheetByName('BCX').getRange("J1").getValue();
var to = "info#google.com";
var body = '';
var htmlTable = SheetConverter2.convertRange2html(range);
var body = "Hi Team!"
+ htmlTable
+ "<br/><br/><b><i>**This is an automated email**</i></b><br/><br/>Any question please let me know.<br/><br/>Regards,<br/><br/>";
var subject = "Google | Report " + row;
MailApp.sendEmail(to, subject, body, {htmlBody: body});
};
But if I use something like the following script it will create 3 triggers each week until it reaches 20 triggers (trigger limit).
function createTriggers() {
var days = [ScriptApp.WeekDay.MONDAY,
ScriptApp.WeekDay.WEDNESDAY,
ScriptApp.WeekDay.FRIDAY];
for (var i=0; i<days.length; i++) {
ScriptApp.newTrigger("sendEmail")
.timeBased().onWeekDay(days[i])
.atHour(7).create();
}
};
One solution to this question would be to combine the various scripts into a single script that can be triggered to run on Monday, Wednesday and Friday.
Within the script, the sequence of processing would be:
1) loop through the spreadsheets in a given folder/sub-folders of Google Drive. - this provides the unique spreadsheet ID.
2) for each spreadsheet, get the ID and use the openById(ID) to open the spreadsheet.
3) get the sheets for the spreadsheet
4) for each sheet, use the original code to build and send an email.
5) rinse and repeat for the next sheet, and next spreadsheet.
The following untested code combines the search for every spreadsheet within a specific folder and sub-folders, opening the spreadsheet and getting the sheets, and then looping through each sheet. The questioner need only add the name of the Google Drive Folder to initiated the search, and put their own code in the two places indicated.
function 53383834() {
/* Adapted from Code written by #hubgit https://gist.github.com/hubgit/3755293
Updated since DocsList is deprecated https://ctrlq.org/code/19854-list-files-in-google-drive-folder
*/
// List all files and sub-folders in a single folder on Google Drive
// declare the folder name
var foldername = 'XXXXXXXXXXXXXXXXXX'; // enter the folder name
// declare this sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActivesheet();
// getFoldersByName = Gets a collection of all folders in the user's Drive that have the given name.
// folders is a "Folder Iterator" but there is only one unique folder name called, so it has only one value (next)
var folders = DriveApp.getFoldersByName(foldername);
var foldersnext = folders.next();
// list files in this folder
var myfiles = foldersnext.getFiles();
// spreadsheets have a unique MIME-Type = application/vnd.google-apps.spreadsheet
var searchTerm = 'spreadsheet';
// loop through files in this folder
while (myfiles.hasNext()) {
var myfile = myfiles.next();
var fname = myfile.getName();
var fid = myfile.getId();
// get the MIME-Type and test whether the file is a spreadsheet
var ftype = myfile.getMimeType();
var indexOfFirst = ftype.indexOf(searchTerm);
if (indexOfFirst != -1) {
var ssid = fid;
// open the spreadsheet
var sso = SpreadsheetApp.openById(ssid);
// get the sheets
var sheets = sso.getSheets();
var sheetlen = sheets.length;
for (var i = 0; i < sheetlen; i++) {
// get the sheets, one by one
var thissheet = sso.getSheets()[i];
<<
insert questioners code here >>
}
}
}
// Now get the subfolder
// subfolders is a Folder Iterator
var subfolders = foldersnext.getFolders();
// now start a loop on the SubFolder list
while (subfolders.hasNext()) {
var subfolderdata = [];
var mysubfolders = subfolders.next();
var mysubfolder = mysubfolders.getName();
// Get the files
var mysubfiles = mysubfolders.getFiles();
// now start a loop on the files in the subfolder
while (mysubfiles.hasNext()) {
var smyfile = mysubfiles.next();
var sfname = smyfile.getName();
var sfid = smyfile.getId();
var sftype = smyfile.getMimeType();
var indexOffolder = sftype.indexOf(searchTerm);
if (indexOffolder != -1) {
var ssid = sfid;
// open the spreadsheet
var sso = SpreadsheetApp.openById(ssid);
// get the sheets
var sheets = sso.getSheets();
var sheetlen = sheets.length;
for (var i = 0; i < sheetlen; i++) {
// get the sheets, one by one
var thissheet = sso.getSheets()[i];
<<
insert questioners code here >>
}
}
}
}
}

Categories