getSheetByName takes too long to process during onFormSubmit trigger [duplicate] - google-apps-script

This question already has answers here:
Formulas make SpreadsheetApp work slower in Google Sheets Script
(3 answers)
Very slow execution of for...in loop
(2 answers)
Google Apps Script Iterating over Spreadsheets very slow
(2 answers)
Closed 3 months ago.
I'm creating a spreadsheet wherein all projects were entered thru Google Form linked to a Google Spreadsheet and then retrieving the information to a specific Sheet intended for each Teams thru onFormSubmit Trigger. I am able to get what I want but I came to a point where my script takes too long to complete. And I figure out which part of the script is the culprit.
I'm getting around 20s just to return
var sheet = e.source.getSheetByName(TeamSheetNo);
Here is the script for the onFormSubmit function.
function onFormSubmit(e) {
Logger.log("onFormSubmit has been ran.");
var itemResponses = e.values;
var Rteam = itemResponses[7];
var Rjobno = itemResponses[1];
var Rprojectname = itemResponses[2];
var Rclientname = itemResponses[3];
var Rother = itemResponses[6];
var Rjobmanager = itemResponses[4];
var Rcomponent = itemResponses[5];
Logger.log("Item Response Lists - " + [Rteam, Rjobno, Rprojectname, Rclientname, Rother, Rjobmanager, Rcomponent]);
console.time('GetInsertJobIndex');
var Teams = JSON.parse(userproperties.getProperty('IndexTeams'));
Logger.log(Teams);
var search = Rteam
var TeamIndex = Teams.indexOf(search) + 1;
Logger.log("Insert Job at Sheet No. " + TeamIndex);
console.timeEnd('GetInsertJobIndex');
console.time('Insert_Response');
Logger.log("Start Insert Project");
// Get Team Sheet Tab
//var TeamSheetNo = userproperties.getProperty("TeamSheetNo");
var TeamSheetNo = TeamIndex;
Logger.log("Go to Sheet No. " + TeamSheetNo);
console.time('ActiveSheet');
var sheet = e.source.getSheetByName(TeamSheetNo);
//var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(TeamSheetNo); //this take too long to run
console.timeEnd('ActiveSheet');
//Logger.log("Get " + sheet.getRange("C1:C1").getValue());
var ColumnToCheck = sheet.getRange("A:A").getValues();
Logger.log("Get " + ColumnToCheck);
var LastRow = getLastRowSpecial(ColumnToCheck);
Logger.log("Get Last Row = " + LastRow);
// Get the ID Number for the Submitted Project
// var formId = '1_il_J6Hm_3D-ASB8cKVXYiLZVtVJcBdB0hfPRMQazEA';
var form = FormApp.openById(formId);
var IDnumber = form.getResponses().length;
Logger.log(IDnumber + " - ID Number for the Submitted Project")
// Get Form Responses 1 Tab
var FormSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Form Responses 1');
var FormColumntoCheck = FormSheet.getRange("A:A").getValues(); //Get all values from sheet
var FormLastRow = getLastRowSpecial(FormColumntoCheck);
// Check whether Submitted Project exists on the Existing Data
let found = false;
FormColumntoCheck.forEach(r => {
if (~r.indexOf(IDnumber)) {
Logger.log("Submitted Project Found on the Existing Data");
found = true;
};
});
if (found) {
Logger.log("Exit Function");
return;
} else {
Logger.log("Insert New Job then")
//SpreadsheetApp.getActiveSpreadsheet().toast("Consolidating New Project Data...", "💡",3); <----- Not working on OnFormSubmit Trigger
// Insert New Project
// Insert New Project to Form Responses 1 Tab
FormSheet.getRange(FormLastRow + 1, 1).setValue(IDnumber);
Logger.log("Insert New Project to Form Responses 1 Tab");
// Insert New Project to Team Sheet Tab
var value = [IDnumber]; // inserted to Team Sheet Tab
for (var i = 1; i <= 4; i = i + 1) {
var row_number = LastRow + i
// Value has been inserted
sheet.getRange(row_number, 1).setValues([value]);
};
};
SpreadsheetApp.flush();
Logger.log("Insert New Project to Task Tab");
//UpdateFilterRange(sheet, row_number);
//SpreadsheetApp.flush();
console.timeEnd('Insert_Response');
getEditResponseUrls(e);
SpreadsheetApp.flush();
//SpreadsheetApp.getActiveSpreadsheet().toast("Update Complete", "💡",3); <----- Not working on OnFormSubmit Trigger
}
Here is the logs for
console.time('ActiveSheet');
Logs
The script is too long because I tried making it work on a single function first and wishing it will help. Unfortunately, it did not. Actually, these are made from multiple functions called under onFormSubmit function.
Are there any solution/s on my problems?

console.time('ActiveSheet');
var sheet = e.source.getSheetByName(TeamSheetNo);
console.timeEnd('ActiveSheet');
(20060 ms)
The getSheetByName() call should not take that long unless the spreadsheet is on the heavy side. Chances are that you can improve the loading time by optimizing the spreadsheet. See these optimization tips.

Related

How to Update Import Range data from another Source when Google Sheet is not open using Google app script

I have a installable time driven trigger with 30 minutes interval in Google Sheet.
The Trigger will send alert if there is any pending task.
The trigger works fine when the Google Sheet is open.
But the problem is when the Google Sheet is not open the Trigger is fetching the Old Data and that is causing False Alert.
The pending task data is calculation using IMPORTRANGE function from another sheet.
The another sheet is linked with Google Form where the sheets get updates by user's output.
So if anyone can help the App Script Code which will refresh all the data of the sheet event the sheet is not opened and then the trigger will do the job.
Note: The Source file and the Destination files are set for Update automatically on change and every minute.
I have posted this to Google App Script Bug Report Tracker and they have suggested to ask help here. You can check that using the Link: https://issuetracker.google.com/issues/168644365
My Code:
function AutoTelegramReminder() {
var SpreadsheetId = 'My_SpreadSheetId';
var SheetName = 'Pending Details';
var ss = SpreadsheetApp.openById(SpreadsheetId)
var sheet = ss.getSheetByName(SheetName);
//Select the column we will check for the first blank cell
var columnToCheck = sheet.getRange("D:D").getValues();
// Get the last row based on the data range of a single column.
var lastRow = getLastRowSpecial(columnToCheck);
if (lastRow>1){
var SlipNo = sheet.getRange(2, 4, lastRow-1).getValues();
//getRange(row, column, numRows)
//Send alert to Telegram
var botSecret = "My_Bot_Secret"; //KCSL BOT ID
var chatId = "My_Chat_Id"; //M.B.D Group ID
var Url = "https://api.telegram.org/bot";
var Link = "My_Google_Sheet_Link_For_Users";
var whatsapLink = "My_Whatsapp_Link";
var body = "MY_TEXT" + SlipNo + "MY_TEXT " + whatsapLink + "MY_TEXT";
var response = UrlFetchApp.fetch( Url + botSecret + "/sendMessage?text=" + encodeURIComponent(body) + "&chat_id=" + chatId + "&parse_mode=HTML&disable_web_page_preview=TRUE");
}
}
//Function To Get Last Row
function getLastRowSpecial(range){
var rowNum = 0;
var blank = false;
for(var row = 0; row < range.length; row++){
if(range[row][0] === "" && !blank){
rowNum = row;
blank = true;
}else if(range[row][0] !== ""){
blank = false;
};
};
return rowNum;
};
If you want to get the data refreshed then in place of =importrange try using the script to call for sheets by using SpreadsheetApp.openById('Sheet Id') and then fetch the range using getRange().getValues().

create individualized google forms based on google sheets information

Say I have a spreadsheet in google sheets with three columns EMAIL, TOPIC and TIME where I´d like to send an email to each of the EMAILs in the spreadsheet containing a link to a google form that has a question asking the preferred TIME for the given TOPIC in the spreadsheet. Is it doable to create such individualized google forms based on sheets?
The first thing that should be done is creating the forms and sending the emails. To do that, I wrote a function that loops through all the rows in your sheet (called "Sheet1", change it according to your preferences), creates a form for each row and sends it to the emails found in column A (in the sheet I've been working on, data starts at row 2 and columns are: A - email / B - topic / C - time):
function sendMails() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var values = ss.getSheetByName("Sheet1").getDataRange().getValues();
for(var i = 1; i < values.length; i++) {
var email = values[i][0];
var topic = values[i][1]
var formName = email + " - " + topic;
var form = FormApp.create(formName);
var url = form.getPublishedUrl();
form.setTitle(formName);
form.setDestination(FormApp.DestinationType.SPREADSHEET, ss.getId()); // This line bounds the created form to your spreadsheet so that responses will be written in here, in an automatically created sheet
// You can set another destination spreadsheet if you don't want all these sheets created in your spreadsheet
var question = form.addDateTimeItem();
question.setTitle("Please provide a preferred time for your assigned lecture on " + topic);
var userEmail = form.addCheckboxItem();
userEmail.setChoices([userEmail.createChoice(email)]);
var topicName = form.addCheckboxItem();
topicName.setChoices([topicName.createChoice(topic)]);
var checkBoxValidation = FormApp.createCheckboxValidation()
.requireSelectExactly(1)
.build();
userEmail.setValidation(checkBoxValidation);
topicName.setValidation(checkBoxValidation);
MailApp.sendEmail(email, topic, url);
}
}
Next, you need to install an onFormSubmit trigger in your spreadsheet. You can do this to run a function that will write the preferred TIME the user chose in the form every time a form is submitted. To create the trigger, run this function in your script, only once:
function createOnFormSubmitTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger('writeTime')
.forSpreadsheet(ss)
.onFormSubmit()
.create();
}
Finally, below is the function that the trigger trigs when a form is submitted. It looks for the row where topic and email match the ones coming from the form, and sets the time:
function writeTime(e) {
var response = e.values;
var time = response[1];
var email = response[2];
var topic = response[3];
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var values = sheet.getDataRange().getValues();
for(var i = 1; i < values.length; i++) {
if(values[i][0] == email && values[i][1] == topic) {
sheet.getRange(i + 1, 3).setValue(time);
}
}
}
I hope this is useful to you.

Getting Service Time Out error on Google Scripts while copying data from one sheet to another?

I am using Google Script to copy content of one Google Sheet to another sheet. I am using the following code.
function copySheet()
{
// open the spreadsheets where the data will be stored
var sourceTable = SpreadsheetApp.openById("SheetID"); // source spreadsheet
var srcSheet = sourceTable.getSheetByName("RawData");
var targetTable = SpreadsheetApp.openById("SheetID"); // target spreadsheet
var tarSheet = targetTable.getSheetByName("Sheet3");
// get the last row in the source sheet
var lastRowSource = srcSheet.getLastRow();
var lastCol = "L";
//read the source into an array
var aSrc = srcSheet.getRange("A1:" + lastCol + lastRowSource).getValues();
//save src array to destination
tarSheet.getRange("A1:" + lastCol + lastRowSource).setValues(aSrc);
}
The Script ran perfectly the first time but now is throwing the Service Time Out Error. Is there a way I can make it more efficient? The data is somewhat large ( around 25000 rows and 12 columns).

Copy only non-blank rows from one tab to another

I have a Google Sheets script that copies a range of cells from one tab to another. The problem is that I'd like it to only copy the non-blank range of cells, but instead it copies the entire range including blank rows. There are many versions of this problem already discussed, but I can't seem to find the right solution so I'm asking it again with all the specifics below.
The range I'm copying is comprised of:
Column A contains a formula that has a text output if column B is
non-blank. If column B is blank, then the formula in column A
creates a blank entry ("").
Columns B:J is an =IMPORTRANGE from a different sheet with the range set to A5:H (open ended range).
Example Source data (imported into the main sheet)
Example Main sheet (this contains the script, which can be triggered in the menu at the top under "Copy Data")
Here's the current script:
// custom menu function
function onOpen() {
var ui = SpreadsheetApp.getUi();
var menu = ui.createMenu('Copy Data');
var item = menu.addItem('Copy Data','copyData');
item.addToUi();
}
function copyData() {
// START1: get current sheet and tabs
var ss = SpreadsheetApp.getActiveSpreadsheet();
var current = ss.getSheetByName('ImportRange');
var database = ss.getSheetByName('RunningList');
// count rows to snap
var current_rows = current.getLastRow();
var database_rows = database.getLastRow() + 1;
var database_rows_new = current_rows + database_rows - 3;
var rows_new = current.getRange('A3:J' + current_rows).getValues();
var nonblank_values = rows_new.filter(String);
// snap rows, can run this on a trigger to be timed
database.getRange(database_rows, 1, nonblank_values.length, nonblank_values[0].length).setValues(nonblank_values);
}
Thank you for your time reviewing this problem.
EDIT 1
When I debug the script, it looks like the filter function is not actually filtering out blank rows. So how would I actually do that?
Debugging Info:
I believe I've found a suitable solution that currently works for my use case. I'm sure there are ways to improve it if you'd like to share your thoughts. I found a script to count nonblank rows here, and integrated it along with some adjustments to the rest of the script:
// add custom menu function "Copy Data"
function onOpen() {
var ui = SpreadsheetApp.getUi();
var menu = ui.createMenu('Copy Data');
var item = menu.addItem('Copy Data','copyData');
item.addToUi();
}
// function to identify last populated row of any tab (based on column A)
function getLastPopulatedRow(sheet) {
var data = sheet.getDataRange().getValues();
for (var i = data.length-1; i > 0; i--) {
for (var j = 0; j < data[0].length; j++) {
if (data[i][j]) return i+1;
}
}
return 0;
}
// function to copy data from one tab to another
function copyData() {
// step 1: get current sheet and tabs
var ss = SpreadsheetApp.getActiveSpreadsheet();
var current = ss.getSheetByName('ImportRange');
var database = ss.getSheetByName('RunningList');
// step 2: count number of new rows needed and grab non-blank rows from first tab
var current_lastrow = getLastPopulatedRow(current);
var database_rows = getLastPopulatedRow(database) + 1;
var database_rows_new = current_lastrow + database_rows - 3;
var rows_new = current.getRange('A3:I' + current_lastrow).getValues();
// step 3: add values to second tab
database.getRange("A" + database_rows + ":I" + database_rows_new).setValues(rows_new);
}

Programmatically created trigger is only for current file, despite giving key of other file

I'm trying to programmatically create a trigger for a Google Spreadsheet when a form is submitted with code from another Spreadsheet, but it won't work. Here is my code:
function createOnFormSubmitTrigger()
{
//Id of "Spreadsheet 1"
var ssId = "18bq-67nP4y7F9Hp4jzpbKPCyAsR6hglgcfmxCi_zj14";
ScriptApp.newTrigger("formInput").forSpreadsheet(ssId).onFormSubmit().create();
}
If I put that method into "Spreadsheet 1" and run it, it works fine and creates the script in Spreadsheet 1 as intended. However, if I put that method into say "Spreadsheet 2" and run it, it creates the trigger in Spreadsheet 2 instead of Spreadsheet 1, which is not as intended. What am I doing wrong?
Here is the code for formInput for the original script and the dummy script I made for testing:
Original:
function formInput()
{
var spreadSheet = SpreadsheetApp.getActiveSpreadsheet();
var inSheet = spreadSheet.getSheetByName("Form Responses");
var data = inSheet.getSheetValues(2,2,1,7);
var minutes = data[0][0];
var seconds = data[0][1];
var numMissed = data[0][2];
var sgIncorrect = data[0][3];
var sfMissed = data[0][4];
var year = data[0][5];
var testLevel = data[0][6];
var date = new Date();
var outSheet = spreadSheet.getSheetByName(testLevel);
var testLevelExists = (outSheet != null);
if(testLevelExists)
{
outSheet.insertRowBefore(2);
outSheet.getRange(2,1).setValue(date.getMonth()+1+ "/" + date.getDate());
outSheet.getRange(2,2).setValue(year);
var secStr = ("0" + seconds);
outSheet.getRange(2,3).setValue(minutes + ":" + secStr.substring(secStr.length-2));
outSheet.getRange(2,4).setValue(numMissed);
outSheet.getRange(2,5).setValue(sgIncorrect);
outSheet.getRange(2,6).setValue(sfMissed);
outSheet.getRange(2,7).setValue(350-7*(sgIncorrect+numMissed)-2*(sfMissed));
shiftBox(outSheet);
setFormulas(outSheet);
updateAverageTime(outSheet);
inSheet.deleteRow(2);
copyToMaster(spreadSheet,outSheet);
}
}
Dummy:
function formInput()
{
var hi = "hello";
}
I'd like to thank #JSmith for all his help first of all. I seemed to have misunderstood the creation of new triggers using the ScriptApp API. When the script is created, it links the trigger to actions performed on the target sheet, but the code must be in the original sheet which created the trigger. The trigger is also only displayed on the sheet which created the trigger.
As seen together,
try all your functions from google's IDE once.
Also it seems a trigger created from a certain spreadsheet is viewable from the trigger menu of the original spreadsheet script even if this trigger is linked to another spreadsheet.