sheet.getLastRow() not working when launched by trigger - google-apps-script

A short description of the issue:
When running an automatic script with trigger head timebased the script failed
With the following error description
Exception: The starting row of the range is too small. at fetchData(wkapi2:4:20)
(which means that the sheet is empty) and this is not the case the sheet has 300 rows.
It worked well until 2 days ago....I did not change the script nor any settings
When I run the script manually, then everything works fine...
The line of code is as follows :
function fetchData() {
var options = {'Cache-Control' : 'max-age=0, must-revalidate'}; //I've used this before to try and prevent google from giving me cached data when making API calls..seems to get the job done
var sheet = SpreadsheetApp.getActiveSheet(); //This script is bound to the corresponding sheet so we can use this conveinience method
var cell = sheet.getRange(sheet.getLastRow(),1); //Get a reference to current last cell in Column A
The scripts Fails at the
sheet.getRange (... ) function when automatically run
When I run manually it works

When run without a "active" spreadsheet, (i.e., a spreadsheet that is visible in the browser), "active" sheet would refer to the first sheet. Use Spreadsheet.getSheetByName() instead of getActiveSheet().

Related

Google Sheets App Script Edit Protected Cell or Object Error

I have a Google Sheets script that I've been using for the past 2+ years that grabs a chunk of data and transcribes it into a pair of tally sheets, then clears the cells that had been filled creating said chunk of data so the process can be started over. This is used for an inventory calculation system. The script has reliably worked up until roughly 3 weeks ago, but I now encounter an edit protection error when users with limited access to the sheet attempt to run the script. None of the editable cells/ranges reference in the script are locked to any user, but the sheet does have protection on cells I do not want anybody to make inadvertent changes to.
The script is:
function CopyErADD() {
var sss=SpreadsheetApp.getActiveSpreadsheet();
var sheet = sss.getSheetByName('ADDER'); //Entry Sheet
var rangeIn = sheet.getRange('B32:N45'); //Range to copy into "Incoming" sheet
var dataIn = rangeIn.getValues();
var ts = sss.getSheetByName('Incoming'); //Tally sheet in
ts.getRange(ts.getLastRow()+1, 1, dataIn.length, dataIn[0].length).setValues(dataIn);
var rangeOut = sheet.getRange('B48:O54'); //Range to copy into "Outgoing" sheet
var dataOut = rangeOut.getValues();
var tss = sss.getSheetByName('Outgoing'); //Tally sheet out
tss.getRange(tss.getLastRow()+1, 1, dataOut.length, dataOut[0].length).setValues(dataOut);
SpreadsheetApp.flush() // NEWLY ADDED PER METAMAN'S SUGGESTION
sheet.getRange('E2:E5').clearContent();
sheet.getRange('B7:B20').clearContent();
sheet.getRange('E7:H20').clearContent();
sheet.getRange('I7:I20').setValue('kg');
sheet.getRange('L7:L20').clearContent();
sheet.getRange('B24:B29').clearContent();
sheet.getRange('J24:J29').clearContent();
}
I also have an "erase only" script that runs the second second part of the script only (if data has been entered incorrectly) that executes perfectly fine. It is only when coupled with the copy/transcribe portion of the script that the protection error occurs.
function ErADD() {
var sss=SpreadsheetApp.getActiveSpreadsheet();
var sheet = sss.getSheetByName('ADDER');
sheet.getRange('E2:E5').clearContent();
sheet.getRange('B7:B20').clearContent();
sheet.getRange('E7:H20').clearContent();
sheet.getRange('I7:I20').setValue('kg');
sheet.getRange('L7:L20').clearContent();
sheet.getRange('B24:B29').clearContent();
sheet.getRange('J24:J29').clearContent();
}
Commenting out the sheet.getRange.... etc clearContent and setValue portion of the first script allows it to complete successfully, so I attempted to create a master function that calls the CopyErAdd script (sans clear portion) and then calls the ErADD script, and I encounter the same error. Both script can be run on their own successfully, but when combined the erase portion encounters the error.
Does anybody see any issues that I am missing, or did something occur over the past few weeks that I'm not aware of that could cause this protection error?
I appreciate any ideas anybody might have.
Edit - Thank you MetaMan for the tips for making the script more efficient.
As for the protection, "Incoming" and "Outgoing" copy destination sheets are completely open, no protection at all. "ADDER" sheet is protected except certain cells that are edited by user, and are referenced in the script.
E2:E5 (actually E2:I5 open, since the macro button sits on top of the F2:I5 range), B7:B20, E7:I20, L7:L20, B24:B29, J24:J29, All unprotected.
Adding the line
SpreadsheetApp.flush()
in between the copy section and clearing section of the code worked (as shown in the updated code snippet). I'm no longer encountering a range protection error.
I really appreciate the fix, but I'm boggled over why I've never needed that line until recently. I guess you don't need flush() until you do.
You could replace this:
sheet.getRange('I7').setValue('kg');
sheet.getRange('I8').setValue('kg');
sheet.getRange('I9').setValue('kg');
sheet.getRange('I10').setValue('kg');
sheet.getRange('I11').setValue('kg');
sheet.getRange('I12').setValue('kg');
sheet.getRange('I13').setValue('kg');
sheet.getRange('I14').setValue('kg');
sheet.getRange('I15').setValue('kg');
sheet.getRange('I16').setValue('kg');
sheet.getRange('I17').setValue('kg');
sheet.getRange('I18').setValue('kg');
sheet.getRange('I19').setValue('kg');
sheet.getRange('I20').setValue('kg');
with this:
sheet.getRange('I7:I20).setValue('kg');
also replace this:
var ss = sss.getSheetByName('ADDER'); //Entry sheet
var sheet = SpreadsheetApp.getActive().getSheetByName('ADDER');
with this:
var sheet = sss.getSheetByName('ADDER);
If you are doing anything immediate after this function with the data then you might wish to add SpreadsheetApp.flush()
I can't comment on the protection since none of that information was provided.

Writing in a newly created Google spreadsheet using an external script

I have built a short script that takes inputs from a Google Form and creates a new spreadsheet. But when I try to set that values inside the sheet, nothing happens. Not sure if this is due to my code or to the lack of authorization given this is a newly created file. Here is my code:
var templates = DriveApp.getFilesByName('Template'); // get the files named Template (only one)
var template = templates.next(); // get the first files in the list
var newFile = template.makeCopy(name,newFolder); // Make a copy of the Template file and put it in NewFolder
var ss = SpreadsheetApp.open(newFile);
var sheet = ss.getSheets()[0];
sheet.getActiveRange('B1').setValue(name);
Thanks for your help
I think that in your script, an error occurs at getActiveRange('B1'). Because the method getActiveRange() of Class Sheet has not arguments. Ref I think that this is the reason of your issue. In this case, an error like The parameters (String) don't match the method signature for SpreadsheetApp.Sheet.getActiveRange. occurs. I thought that the reason of nothing happens might be that from your question, the script is run by the OnSubmit event trigger might be used. In this case, please modify as follows.
From:
sheet.getActiveRange('B1').setValue(name);
To:
sheet.getRange('B1').setValue(name);
When you modify like above and run again, the value of name is put to the cell "B2" on the 1st tab in the created Spreadsheet.
Note:
If you want to append the values when the script is run, please modify sheet.getActiveRange('B1').setValue(name); as follows.
sheet.appendRow([, name]); // In this case, the value is append to the column "B".
References:
getActiveRange()
getRange(a1Notation)

Google Sheet Script not fully executing

I'm using a Google Form to trigger this script.
When I run the script with the Play button it works perfect.
When I let the onsubmit trigger run it, the check box populates fine but the setValue date does not.
I've also tried using setFormula but I get the same result.
function AddCheckBox_toSchoolLunchForm(F) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Form Responses 1")
var criteria = SpreadsheetApp.DataValidationCriteria.CHECKBOX;
var rule = SpreadsheetApp.newDataValidation().requireCheckbox().build();
var range = sheet.getRange(sheet.getLastRow(), 8);
range.clearContent();
range.setDataValidation(rule);
var FormulaCell = sheet.getRange(sheet.getLastRow(), 10);
FormulaCell.setFormula("=DATEVALUE(A" + (sheet.getLastRow()) + ")");
}
The end goal is to have column J populate with the shot formatted date from column A each time the form is submitted.
I need this format to run a countIfs on another sheet.
Another option would be to somehow embed a format tag into this CountIfs command so that they match.
=COUNTIFS('Form Responses 1'!J:J,A2,'Form Responses 1'!D:D,B2)
UPDATED ANSWER
If you have problems running a function on trigger while it works as intended without a trigger, check the following:
For heavy files / form data they can be a delay in populating the sheet with new form data.
To avoid conflicts, give the spreadsheet some time to populate the new row before retrieving sheet.getLastRow() or accessing data in the sheet.
You can dot is easily e.g. with Utilities.sleep().
Check that your trigger has been installed correctly. For this check that
the type of trigger is correct
the trigger is bound to the correct function
avoid conflicts by creating a new trigger when renaming the funciton.
Check that the problem is not due to trigger restrictions
This is a common error source when using simple triggers.
Check that account under whose authorization the trigger is running has permissions to edit the sheet /range. This is important to check when the trigger owner is a diferent person that the person who runs the script manualy.
Note that for setFormula() it is not necessary to incorporate the = into the formula
Note that DATEVALUE() will only return the expected result when the cell is formatted correctly as a date

Apps Script: how do you copy values from specific columns to a different spreadsheet via a time trigger, button press or sheet update?

I need to copy the values of multiple ranges of data from my main spreadsheet into another spreadsheet and then copy a different range of data back to the main spreadsheet.
Here are my example spreadsheets:
The main spreadsheet (two sheets: 1 - A sheet of pasted values alongside columns of user comments. 2 - A sheet with a button that runs the Apps Script)
The live data spreadsheet (pulls and formats the required data from other sheets).
Previously, I’ve used .copyTo() to copy the values of data and paste them elsewhere on the same spreadsheet. However, this method can’t be used to copy data to a different spreadsheet. Copying data from multiple ranges is also causing me issues. Here is my code:
function RefreshSheetData() {
// 1a. Run the script when a button is pressed (Main Spreadsheet - ‘Update report button!A3’)
// 1b. Run the script at a certain time (every Monday at 10 AM)
// 1c. Run the script when data in a sheet is replaced with new data via a formula – NOT POSSIBLE.
// 2. Copy email address and user comments (Main Spreadsheet - Editable report - J3:J & AM3:AR)
// 3. Clear the “Updated user comments” sheet below the header row (Live data Spreadsheet - Updated user comments - A2:G)
// 4. Paste the values (Live data Spreadsheet - Updated user comments - A2:G)
// 5. Clear the “Editable report” below the header rows (Main Spreadsheet - Editable report – B3:AR)
// 6. Copy the Live sheet (which should now include the most recent user comments via array vlookup) (Live data Spreadsheet - live data – A3:AQ)
// 7. Paste the values (Main Spreadsheet - Editable report - B3:AR)
// 8. Add the (United Kingdom) time and date (Main Spreadsheet - Update report button - A10)
// 9. Add the time and date (Live data Spreadsheet - Updated user comments - J1)
ScriptApp.newTrigger('RefreshSheetData')
.timeBased()
.onWeekDay(ScriptApp.WeekDay.MONDAY)
.atHour(10)
.create();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var startSheet = ss.getSheetByName('Editable report');
var sourceRange = startSheet.getRangeList(['J3:J', 'AM3:AR']);
var sourceValues = sourceRange.getValues();
var target = SpreadsheetApp.openById('1OHQHefYvE4vZZPr8jgziy_L3-UBf1WSoKzMWQ8LUz6w');
var targetSheet = target.getSheetByName('Updated user comments');
var clearTargetRange = targetSheet.getRange('A2:G').clearContent();
var targetRange = targetSheet.getRange('A2').setValues(sourceValues);
var liveTargetSheet = target.getSheetByName('Live data');
var liveSourceRange = liveTargetSheet.getRange('A3:AQ').getValues();
var clearMainRange = startSheet.getRange('B3:AR').clearContent();
var startRange = startSheet.getRange('B3').setValues(liveSourceRange);
SpreadsheetApp.getActive().getRange('A10').setValue(new Date());
targetSheet.getRange('J1').setValue(new Date())
}
Any tips would be appreciated. Thank you.
Part of my question was how to trigger a script when a formula output changes. That’s not possible. Neither .onEdit or .onChange triggers work as they only respond to user actions. They won't run when the value of an IMPORTRANGE or alternative formula changes.
Here some suggestions referring to each of the steps mentioned in your Apps Script file
1b. To run the script at a certain time: please use the installable trigger "Time driven"
1c. To run the script when data in the sheet is updated: please use the onEdit trigger
1d. You can combine all triggers by simply adding as many, as required.
To copy and paste data, you just need the methods getValues() and setValues(), which you were using already, the important thing is that you chose the "to copy" range correctly.
You can clear a range with clear()
See 3.
You can copy a sheet with copyTo(), however keep in mind that if using this method, your data in the copied sheet will automatically be updated if there is a change in the original sheet. If you want the values to remain static, you have to copy and paste them with copyValues() and setValues().
See 2. and 5.
See here how to get and format the date in Apps Script
Assign the date to a variable and use setValue()
I encourage you to try and build the script based on those steps
yourself, the Apps Script documentation provides you good reference
and guidance, how to do so. If you encounter specific problems during
one of the steps which you cannot solve with the documentation, feel
free to ask!

Use Google Sheet Formula in Script

After each time a new entry is added to my google sheet I need to have a script validate a combination of cells then make sure the output of the validation has changed before sending an email. I know how to do the validation and provide the correct output using a formula in the spreadsheet and I know how to have the script check that field and run. This works when someone edits the sheet from the UI. The problem is the spreadsheet is not being updated via the UI (it's being done via a IFTTT recipe), so I don't think the validation field is recalculating before the script runs who means no email is sent.
The following formula and script work perfectly when it is updated via the UI. My question is - is there a way to calculate the "value" variable from the script below with the formula from the spreadsheet?
Formula which is in Cell "D2" of Sheet "state":
=IF((COUNTIF(INDEX(data!A1:D5032,(SUMPRODUCT(MAX((data!A:A="meloc")*(ROW(data!A:A))))),4),"entered")+COUNTIF(INDEX(data!A1:D5032,(SUMPRODUCT(MAX((data!A:A="youloc")*(ROW(data!A:A))))),4),"entered"))>0,9999999999999,1111111111111)
Script that works fine with onEdit trigger when sheet is edited via the UI:
function myNotification() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var value = ss.getSheetByName("state").getRange("D2").getValue();
if( value > 0 ) {
var last = ScriptProperties.getProperty("last");
value = value.toString();
if( value != last ) {
MailApp.sendEmail('trigger#recipe.ifttt.com', 'Status #'+value+'\n\n',
'1="not here", 9="here": '+value+'\n\n', {
cc: 'myemail#gmail.com'
});
ScriptProperties.setProperty("last", value);
}
}
}
what you found cannot be worked arround. currently those triggers do not get called when external code uses the spreadsheets or drive api to change contents.
if you need it to work realtime, its not possible. else if you can live with a 1 minute delay, you could use a recurring 1minute trigger to check if the last modified date has changed on the spreadsheet ( by comparing with previously stored on script properties).
if it changed, process the entire sheet again.