I've got the following Google Apps Script code which is triggered by an edit to a cell that has a dropdown data validation (Pick from List) on it. Occasionally it adds two duplicate rows to the target table instead of the desired single row. I've added Logging in a variety of locations to see if I can detect when/where/why some piece of code is running twice. The logs don't show any sections of the script running unexpectedly. I've added Locking as well. It didn't help with this issue, although it did make the script run in a more reliable fashion. I really want to get to the bottom of this although it is really just a nuisance at this point. Please let me know if you need any additional info:
function onEdit(e) {
Logger.log('Running onEdit');
var lock = LockService.getUserLock();
lock.waitLock(10000);
var cache = CacheService.getUserCache();
var spreadsheet = SpreadsheetApp.getActive().getActiveSheet();
var range = e.range;
var col = range.columnStart;
var row = range.rowStart;
var status = range.getDisplayValue();
if (col == 7 && row >= 13 && row <= 53) { //Ongoing Block
cache.put("oldStatus", e.oldValue);
Logger.log(row,col,status, e.oldValue);
if (status == 'ToVendor') { //Send to Vendor Block
copyEquipRow(spreadsheet,row,col,1,true);
Logger.log(row,col,cache.get("equipID"),cache.get("dateAdded"),cache.get("priority"),cache.get("problem"),cache.get("location"),cache.get("targetDate"));
pasteToVendor(spreadsheet);
}
if (status == 'AddReq') { //Add Related Req to the Req Block
copyEquipRow(spreadsheet,row,col,1,false);
Logger.log(row,col,cache.get("equipID"),cache.get("dateAdded"),cache.get("priority"),cache.get("problem"),cache.get("location"),cache.get("targetDate"));
addReq(spreadsheet);
spreadsheet.getRange(row, col).setValue(e.oldValue)
}
}
if (col == 16 && row >= 31 && row <= 37) { //Vendor Block
cache.put("oldStatus", e.oldValue);
Logger.log(row,col,status, e.oldValue);
if (status == 'ToOngoing') {
copyEquipRow(spreadsheet,row,col,0,true);
Logger.log(row,col,cache.get("equipID"),cache.get("dateAdded"),cache.get("priority"),cache.get("problem"),cache.get("location"),cache.get("targetDate"));
pasteToOngoing(spreadsheet);
}
}
lock.releaseLock();
}
function copyEquipRow(spreadsheet,row,col,problemCol,deleteRow) { //Status Equip # Date Added Priority Problem Location Target Date
Logger.log('Running CopyEquipRow');
var cache = CacheService.getUserCache();
var locationCol = 5 + problemCol;
var targetCol = 6 + problemCol;
var clearCol = 7 + problemCol;
var dateAdded = Utilities.formatDate(spreadsheet.getCurrentCell().offset(0, 2).getValue(), "EST", "M/d/yyyy");
var targetDate = Utilities.formatDate(spreadsheet.getCurrentCell().offset(0, targetCol).getValue(), "EST", "M/d/yyyy");
cache.put("equipID", spreadsheet.getCurrentCell().offset(0, 1).getValue());
cache.put("dateAdded",dateAdded);
cache.put("priority",spreadsheet.getCurrentCell().offset(0, 3).getValue());
cache.put("problem",spreadsheet.getCurrentCell().offset(0, 4).getValue());
cache.put("location",spreadsheet.getCurrentCell().offset(0, locationCol).getValue());
cache.put("targetDate",targetDate);
if (deleteRow == true ) {
spreadsheet.getRange(row, col, 1, clearCol).clearContent();
}
}
function pasteRow(spreadsheet,startCell,problemCol,includeStatus,dateOverride) {
Logger.log('Running pasteRow');
var cache = CacheService.getUserCache();
var currentCell = spreadsheet.getRange(startCell).activate();
var row = 0;
while (currentCell.offset(row, 1).isBlank() == false) {
row = ++row;
}
var oldStatus = cache.get("oldStatus");
var equipID = cache.get("equipID");
var dateAdded = cache.get("dateAdded");
var priority = cache.get("priority");
var problem = cache.get("problem");
var location = cache.get("location");
var targetDate = cache.get("targetDate");
var locationcol = 5 + problemCol;
var targetcol = 6 + problemCol;
Logger.log(equipID,dateAdded,priority,problem,location,targetDate);
if (includeStatus == true) {
currentCell.offset(row, 0).setValue(oldStatus);
}
currentCell.offset(row, 1).setValue(equipID);
if (dateOverride == true ){
currentCell.offset(row, 2).setValue(Utilities.formatDate(new Date(), "EST", "M/d/yyyy"));
}
else {
currentCell.offset(row, 2).setValue(dateAdded);
}
currentCell.offset(row, 3).setValue(priority);
currentCell.offset(row, 4).setValue(problem);
currentCell.offset(row, locationcol).setValue(location);
currentCell.offset(row, targetcol).setValue(targetDate);
}
function pasteToVendor(spreadsheet) {
Logger.log('Running pasteToVendor');
pasteRow(spreadsheet,'P30',0,true);
}
function pasteToOngoing(spreadsheet) {
Logger.log('Running pasteToOngoing');
pasteRow(spreadsheet,'G12',1,true);
}
function addReq(spreadsheet) {
Logger.log('Running addReq');
pasteRow(spreadsheet,'O12',1,false,true);
}
The onEdit(e) function responds to the simple trigger which cannot perform operations that require permission.
The instable onEdit trigger is tied to a function of you choice and it gets the same event object as the simple trigger. If you don't rename the installable trigger to something other than onEdit() then you're definitely getting two triggers all of the time.
Also keep in mind onEdit triggered functions must complete within 30 seconds.
Simple Triggers
installable triggers
Event Objects
Related
I would to find a way to avoid to insert in the column A of a spreadsheet the same date for maximum 10 times, and so if in the column A there is already the same date 10 times, if a user try to insert another time the same date, a popup will alert that it is not possible.
I am trying with Apps Script writing the code below, but it works only with alphabetical values and not with dates.
Maybe I have to format something?
function onEdit(e) {
var r = e.range;
var s = r.getSheet();
if(s.getName()==='Foglio1' && r.getColumn()===1) {
var newValue = e.value;
var b = s.getRange('A1:A');
var bv = b.getValues();
var count = 0;
var flag = false;
for(var i=0;i<bv.length;i++) {
if(bv[i][0]===newValue)
count++;
if(count>10) {
flag = true;
break;
}
}
if(flag) {
r.setValue(e.oldValue);
SpreadsheetApp.flush();
SpreadsheetApp.getUi().alert('This date is already inserted 10 times');
}
}
}
For example, as a simple modification, how about using getDisplayValue as follows?
Modified script 1:
function onEdit(e) {
var r = e.range;
var s = r.getSheet();
if (s.getName() === 'Foglio1' && r.getColumn() === 1) {
var newValue = r.getDisplayValue();
var b = s.getRange('A1:A');
var bv = b.getDisplayValues();
var count = 0;
var flag = false;
for (var i = 0; i < bv.length; i++) {
if (bv[i][0] === newValue) {
count++;
}
if (count > 10) {
flag = true;
break;
}
}
if (flag) {
r.setValue(e.oldValue);
SpreadsheetApp.flush();
SpreadsheetApp.getUi().alert('This date is already inserted 10 times');
}
}
}
Modified script 2:
As other method, in this modification, filter is used for counting the values.
function onEdit(e) {
var r = e.range;
var s = r.getSheet();
if (s.getName() === 'Foglio1' && r.getColumn() === 1) {
var newValue = r.getDisplayValue();
if (newValue == "") return;
var count = s.getRange('A1:A').getDisplayValues().filter(([a]) => a === newValue).length;
if (count > 10) {
r.setValue(e.oldValue);
SpreadsheetApp.flush();
SpreadsheetApp.getUi().alert('This date is already inserted 10 times');
}
}
}
References:
getDisplayValue()
getDisplayValues()
filter()
I don't know much about programming but I created a script for one of my spreadsheets which worked very well a few months ago.
But lately it is impracticable to use any script, however simple it may be. Sometimes it runs perfectly in 1 or 2 seconds and most of the time it just times out and fails. 90% of executions result in "timed out".
In summary, the function must be executed every time column B or C of the spreadsheet is edited. For this I used the function OnEdit ().
When column B is edited, the date and time is inserted in column D in the respective line where the change occurred.
When column C is edited, the date and time is inserted in column E in the respective line where the change occurred.
Here is the code:
function onEdit(e)
{
var column = e.range.getColumn();
var aba = e.source.getActiveSheet().getName();
if (column == 2 && aba == "Controle")
{
var ss = SpreadsheetApp.getActiveSheet();
var cell = ss.getActiveCell();
if(cell.getValue() != "")
{
var add = cell.offset(0, 2);
var data = new Date();
data = Utilities.formatDate(data, "GMT-03:00","dd/MM/yyyy' 'HH:mm' '");
add.setValue(data);
}
else
{
var add = cell.offset(0, 2);
var data = new Date();
data = "";
add.setValue(data);
}
}
if (column == 3 && aba == "Controle")
{
var ss = SpreadsheetApp.getActiveSheet();
var cell = ss.getActiveCell();
if(cell.getValue() == true)
{
var add = cell.offset(0, 2);
var data = new Date();
data = Utilities.formatDate(data, "GMT-03:00","dd/MM/yyyy' 'HH:mm' '");
add.setValue(data);
}
else
{
var add = cell.offset(0, 2);
var data = new Date();
data = "";
add.setValue(data);
}
}
}
Even super simple recorded macros like applying a filter are showing this problem.
I would like to know if there is something that can be done in the code to make it more efficient to solve this problem or if the timeout problem reached is related to something else.
Thanks
Try this:
function onEdit(e) {
const sh=e.range.getSheet();
if(sh.getName()=='Controle' && e.range.columnStart>1 && e.range.columnStart<4) {
if(e.range.columnStart==2) {
if(e.value='') {
e.range.offset(0,2).setValue(Utilities.formatDate(new Date(), "GMT-3", "dd/MM/yyyy' 'HH:mm' '"));
}else{
e.range.offset(0,2).setValue(new Date());
}
}
if(e.range.columnStart==3) {
if(e.value==true) {
e.range.offset(0,2).setValue(Utilities.formatDate(new Date(), "GMT-3", "dd/MM/yyyy' 'HH:mm' '"));
}else{
e.range.offset(0,2).setValue(new Date());
}
}
}
}
Based on your logs, your script is reaching quota for exceeding maximum execution time, although it is also possible from your scenario to have more than 30 simultaneous executions, which will make the script fail to run too.
One workaround, as Cooper have said in the comments, is to implement a lock on parts of the script, especially on sections that make changes to the spreadsheet, to make subsequent executions wait on one script to complete.
Sample code:
function onEdit(e)
{
var column = e.range.getColumn();
var aba = e.source.getActiveSheet().getName();
var lock = LockService.getScriptLock();
try { lock.waitLock(10000); } // wait 10 sec
if (column == 2 && aba == "Controle")
{
var ss = SpreadsheetApp.getActiveSheet();
var cell = ss.getActiveCell();
if(cell.getValue() != "")
{
var add = cell.offset(0, 2);
var data = new Date();
data = Utilities.formatDate(data, "GMT-03:00","dd/MM/yyyy' 'HH:mm' '");
add.setValue(data);
}
else
{
var add = cell.offset(0, 2);
var data = new Date();
data = "";
add.setValue(data);
}
}
if (column == 3 && aba == "Controle")
{
var ss = SpreadsheetApp.getActiveSheet();
var cell = ss.getActiveCell();
if(cell.getValue() == true)
{
var add = cell.offset(0, 2);
var data = new Date();
data = Utilities.formatDate(data, "GMT-03:00","dd/MM/yyyy' 'HH:mm' '");
add.setValue(data);
}
else
{
var add = cell.offset(0, 2);
var data = new Date();
data = "";
add.setValue(data);
}
}
SpreadsheetApp.flush(); // applies all pending spreadsheet changes
lock.releaseLock();
}
References:
Quotas for Google Services
Class Lock
I have two functions in my Google Sheet script which are each triggered by means of a checkbox (since Google Sheets on mobile can't use images as buttons). They work on PC (rather slowly), but on tablets, they tend to fail more often than not, which then also affects PC users.
The script is set up to perform an onEdit check of two checkbox cells. If the checkbox in cell C3 is checked, the AUTOFILL function should run (which displays the A cell value of the last row on the Info sheet plus 1 in cell C4 of the Data Entry sheet, and then clears the checkbox), and if the checkbox in cell C12 is checked, the SUBMIT function should run (which takes the range of data entered on the Data Entry sheet and updates an existing row/adds a new row on the Data sheet with the information from the Data Entry sheet, adding a timestamp if cell C11 on the Data Entry sheet contains the word 'CLEANED', and then clears the checkbox).
I've tried experimenting with various WIFI signal strengths and more powerful tablets, but I am unable to pinpoint the exact culprit here - sometimes this will run, most often the checkbox will just remain checked and nothing happens. Laptops and desktop computers all seem to run, but if a tablet tries to run and fails, the computers will sometimes not run either, until I go into the script itself and manually force once of the functions to run, which seems to reset things and lets the computers work again.
Is it because of the processing required in order to run this code? I've tried to optimize it as much as possible, but is there something else that I might change here which would make this work, every time?
Here is the example sheet, and here is the script:
function onEdit(e) {
if (e.range.getSheet().getName() != "Data Entry") {
return
}
var isAutofill = SpreadsheetApp.getActiveSheet().getRange("C3").getValue();
var isSubmit = SpreadsheetApp.getActiveSheet().getRange("C12").getValue();
if (isAutofill && isSubmit) {
Browser.msgBox("You cannot autofill and submit data at the same time!");
SpreadsheetApp.getActiveSheet().getRange("C3").setValue(false);
SpreadsheetApp.getActiveSheet().getRange("C12").setValue(false);
} else if (isAutofill) {
AUTOFILL();
SpreadsheetApp.getActiveSheet().getRange("C3").setValue(false);
} else if (isSubmit) {
SUBMIT();
SpreadsheetApp.getActiveSheet().getRange("C12").setValue(false);
}
}
function AUTOFILL() {
var sheet1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Info');
var sheet2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Data Entry');
var valueOfData = sheet1.getRange(sheet1.getLastRow(), 1).getValue();
sheet2.getRange('C4').setValue(valueOfData + 1);
}
function SUBMIT() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formSS = ss.getSheetByName("Data Entry");
var dataSheet = ss.getSheetByName("Info");
var values = formSS.getRange("C4:C11").getValues().reduce(function(a, b) {
return a.concat(b)
});
var partNum = values[0];
var row;
dataSheet.getDataRange().getValues().forEach(function(r, i) {
if (r[0] === partNum) {
row = i + 1
}
})
row = row ? row : dataSheet.getLastRow() + 1;
var data = dataSheet.getRange(row, 1, 1, 8).getValues()[0].map(function (el, ind){
return el = values[ind] ? values[ind] : el;
})
var statusValue = formSS.getRange("C11").getValue();
if (statusValue != 'CLEANED') {
dataSheet.getRange(row, 1, 1, 8).setValues([data]);
}
if (statusValue == 'CLEANED') {
var now = [new Date()];
var newData = data.concat(now)
dataSheet.getRange(row, 1, 1, 9).setValues([newData]);
}
formSS.getRange("C4:C11").clearContent()
}
I made a few changes. Take a look. Hopefully you can use them to speed up the function.
function onEdit(e) {
var sh=e.range.getSheet();
if (sh.getName() != "Data Entry") {return;}
var rgC3=SpreadsheetApp.getActiveSheet().getRange("C3");
var rgC12=SpreadsheetApp.getActiveSheet().getRange("C12");
var isAutofill = rgC3.getValue();
var isSubmit = rgC12.getValue();
if (isAutofill && isSubmit) {
e.source.toast("You cannot autofill and submit data at the same time!");
rgC3.setValue(false);
rgC12.setValue(false);
} else if (isAutofill) {
AUTOFILL(e.source);
rgC3.setValue(false);
} else if (isSubmit) {
SUBMIT(e.source);
rgC12.setValue(false);
}
}
function AUTOFILL(ss) {
var sheet1 = ss.getSheetByName('Info');
var sheet2 = ss.getSheetByName('Data Entry');
var valueOfData = sheet1.getRange(sheet1.getLastRow(), 1).getValue();
sheet2.getRange('C4').setValue(valueOfData + 1);
}
function SUBMIT(ss) {
var formSS=ss.getSheetByName("Data Entry");
var dataSheet=ss.getSheetByName("Info");
var values=formSS.getRange("C4:C11").getValues().reduce(function(a, b) {return a.concat(b)});
var partNum = values[0];
var row;
var data=dataSheet.getDataRange().getValues()
for(var i=0;i<data.length;i++) {
if(data[i][0]==partNum) {
row=i+1;
break;
}
}
row = row ? row : dataSheet.getLastRow() + 1;
var data = dataSheet.getRange(row, 1, 1, 8).getValues()[0].map(function (el, ind){return el = values[ind] ? values[ind] : el;})
var statusValue = formSS.getRange("C11").getValue();
if (statusValue != 'CLEANED') {dataSheet.getRange(row, 1, 1, 8).setValues([data]);}
if (statusValue == 'CLEANED') {var now = [new Date()];var newData=data.concat(now);dataSheet.getRange(row, 1, 1, 9).setValues([newData]);}
formSS.getRange("C4:C11").clearContent();
}
I'm trying to develop a simple script to create a log table to show certain information when a checkbox is marked on one of the Sheets.
The script works correctly (it does what it needs to do) but sometimes works slow. If more than one user 'edits' the Sheet at the same time some entries get lost because the last edit overwrites the previous one. I need to optimize the script as much as possible to avoid losing information.
I think there are 2 possible ways to do it. One is using a function to identify the next available row and the other one is using a countA function within the sheet and get the value from the code (...getRange('whatever').getValue()).
Both of them turned to be slow, so I guess the issue is elsewhere.
I have also tried to use onEdit(e) and getting the active range by e.range but it is also too slow.
Please, find below the code.
function onEdit() {
var ss = SpreadsheetApp.getActive();
var user = Session.getActiveUser().getEmail();
var activeCell = ss.getActiveRange();
var sheet = activeCell.getSheet();
var row = activeCell.getRow();
var column = activeCell.getColumn();
var activeCellValue = activeCell.getValue();
if(!isSupervisor(user) && activeCellValue == true && sheet.getName() != "Activities" && sheet.getName() != "Edit Log" && row >= 10 && column >= 2 && column <= 26 && column % 2 != 0){
var editLog = ss.getSheetByName("Edit Log");
var nextRow = 4 + editLog.getRange('P1').getValue();
var date = new Date();
var language = sheet.getRange(4, column - 1).getValue();
var assignedTo = sheet.getRange(3, column - 1).getValue();
var startHour = sheet.getRange(row, 1).getValue();
var startHourHour = sheet.getRange(row, 27).getValue();
var startHourMinutes = sheet.getRange(row, 28).getValue();
var activity = sheet.getRange(row, column - 1).getValue();
var note = activeCell.getNote();
var generatingID = sheet.getName()+"_"+language+"_"+activity+"_"+startHourHour;
//var shortID = language+"_"+activity+"_"+startHour;
if(!isAlreadyAdded(generatingID, editLog.getRange("A4:A").getValues())) {
editLog.getRange("B1").setValue("Not added");
editLog.getRange("B2").setValue(generatingID);
editLog.getRange(nextRow, 1).setValue(generatingID);
editLog.getRange(nextRow, 3).setValue(date);
editLog.getRange(nextRow, 4).setValue(user);
editLog.getRange(nextRow, 5).setValue(sheet.getName());
editLog.getRange(nextRow, 6).setValue(activeCellValue);
editLog.getRange(nextRow, 7).setValue(language);
editLog.getRange(nextRow, 8).setValue(activity);
editLog.getRange(nextRow, 9).setValue(assignedTo);
editLog.getRange(nextRow, 10).setValue(startHour);
editLog.getRange(nextRow, 13).setValue(note);
} else {
editLog.getRange("B1").setValue("Already added");
editLog.getRange("B2").setValue(generatingID);
}
}
}
function isAlreadyAdded(generatingID, values) {
for(var i = 0; i < values.length; i++) {
if(values[i] == generatingID) {
return true;
}
}
return false;
}
function getFirstEmptyRow(data) {
for(var i = 0; i < data.length; i++) {
if(data[i][0] == null || data[i][0] == '') {
return i;
}
}
}
function isSupervisor(user) {
var supervisors = getSupervisors();
for(var i = 0; i < supervisors.length; i++) {
if(user == supervisors[i]) {
return true;
}
}
return false;
}
function getSupervisors() {
var supervisors = [];
//real values are hidden due to confidential information
supervisors.push("???");
supervisors.push("???");
supervisors.push("???");
supervisors.push("???");
supervisors.push("???");
supervisors.push("???");
supervisors.push("???");
supervisors.push("???");
return supervisors;
}
As previously mentioned, I need to make this script as fast as possible. I can't lose any entry due to the slowness of the script. Do you have any suggestion?
Thanks for your help :)
I have already tried this: Script to Change Row Color when a cell changes text but it can't get it to work. The color of the row does not change to #000000. This is what I have so far:
function onEdit(event)
{
var ss = event.source.getActiveSheet();
var r = event.source.getActiveRange();
var currentValue = r.getValue();
if(currentValue == "dags dato")
{
var dd = Utilities.formatDate(new Date(), "GMT", "yyyy-MM-dd");
r.setValue(dd);
}
else if(currentValue == "dialog")
{
setRowColor("yellow");
}
else if(currentValue == "besvaret")
{
setRowColor("yellow");
}
else if(currentValue == "afvist")
{
setRowColor("red");
}
}
function setRowColor(color)
{
var range = SpreadsheetApp.getActiveSheet().getDataRange();
var statusColumnOffset = getStatusColumnOffset();
for (var i = range.getRow(); i < range.getLastRow(); i++) {
rowRange = range.offset(i, 0, 1);
status = rowRange.offset(0, statusColumnOffset).getValue();
rowRange.setBackgroundColor("#000000");
}
//Returns the offset value of the column titled "Status"
//(eg, if the 7th column is labeled "Status", this function returns 6)
function getStatusColumnOffset() {
lastColumn = SpreadsheetApp.getActiveSheet().getLastColumn();
var range = SpreadsheetApp.getActiveSheet().getRange(1,1,1,lastColumn);
for (var i = 0; i < range.getLastColumn(); i++) {
if (range.offset(0, i, 1, 1).getValue() == "Status") {
return i;
}
}
}
I wrote way faster and cleaner method for myself and I wanted to share it.
function onEdit(e) {
if (e) {
var ss = e.source.getActiveSheet();
var r = e.source.getActiveRange();
// If you want to be specific
// do not work in first row
// do not work in other sheets except "MySheet"
if (r.getRow() != 1 && ss.getName() == "MySheet") {
// E.g. status column is 2nd (B)
status = ss.getRange(r.getRow(), 2).getValue();
// Specify the range with which You want to highlight
// with some reading of API you can easily modify the range selection properties
// (e.g. to automatically select all columns)
rowRange = ss.getRange(r.getRow(),1,1,19);
// This changes font color
if (status == 'YES') {
rowRange.setFontColor("#999999");
} else if (status == 'N/A') {
rowRange.setFontColor("#999999");
// DEFAULT
} else if (status == '') {
rowRange.setFontColor("#000000");
}
}
}
}
You could try to check your code for any errors or issues by using the Logger class like so:
try {
//your code
}
catch(e) {
Logger.log(e);
}
Then you can go to View -> Logs from the Script Editor to see if each line of code performs as expected. Also the Execution transcript might be useful to see if the code breaks at one particular line of code. You can view more details about how each troubleshooting method works.