time triggert function, cell error handling - google-apps-script

The time triggered function I'm calling got a fixed range. It validates this range and sends an email if necessary. This works so far but sometimes I got just errors. I assume the content of the range is not loaded, but I'm not sure. With your help, I want to improve the error message I got or figure out how to solve that problem.
sendMailToCheckedMembers is the time triggert function
getMembersFromRange gets the values of the cells, returned as a map
function sendMailToCheckedMembers(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(MAIN_PAGE);
var rangeToCheck = sheet.getRange("L5:P28");
var map = getMembersFromRange(rangeToCheck);
//check for errors
for (var ele in map) {
if(isErrorOrLoading(ele)){
sendError(createStringFromMap(map));
return;
}
}
//do validation stuff
//send validation mail
}
function getMembersFromRange(range){
var numRows = range.getNumRows();
var numCols = range.getNumColumns();
var map = {};
for (var i = 1; i <= numRows; i++) {
for (var j = 1; j <= numCols; j++) {
var currentCell = range.getCell(i,j).getValue();
var entryArray = currentCell.split(" ");
for(var k=0;k<entryArray.length;k++){
map[entryArray[k]] = entryArray[k];
}
}
}
return map;
}
this line var currentCell = range.getCell(i,j).getValue(); contains sometimes the String #ERROR! but it seams like no further information.
I tried to work with sleep(time) to just wait a few seconds after I found errors and then just try it again, but this does not help at all. I don't know what the error is about.
I'm searching for something like range.getCell(i,j).getErrorText() or a way to just wait for the sheet to finish loading the cell's content.
Like I sad above the expected validation email is send 4 out of 7 days and the other 3 days I just get the error mail.
I hope you have some ideas that a can check.
thanks!
Edit:
The content of the cells are filled with the following custom script:
function conCatDates(rangeAsString,range){
var args = Array.prototype.slice.call(arguments, 1);
var rangeArr = rangeAsString.split(";");
var returnValue =[];
for(var i=0;i<rangeArr.length;i++){
var splited = rangeArr[i].split("!");
var sheetname = splited[0];
if(args[i] != ""){
returnValue.push(sheetname);
}
}
if(returnValue.length==0){
return "";
}
return returnValue.sort().join(" ");
}
conCatDates example input conCatDates("Sheetname1!G13;Sheetname2!G13";Sheetname1!G13;Sheetname2!G13), goal of that function is, to get the sheetname of all not empty cells

Related

I need to split a Google Sheet into multiple tabs (sheets) based on column value

I have searched many possible answers but cannot seem to find one that works. I have a Google Sheet with about 1600 rows that I need to split into about 70 different tabs (with about 20-30 rows in each one) based on the value in the column titled “room”. I have been sorting and then cutting and pasting but for 70+ tabs this is very tedious.
I can use the Query function but I still need to create a new tab, paste the function and update the parameter for that particular tab.
This script seemed pretty close:
ss = SpreadsheetApp.getActiveSpreadsheet();
itemName = 0;
itemDescription = 1;
image = 2;
purchasedBy = 3;
cost = 4;
room = 5;
isSharing = 6;
masterSheetName = "Master";
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Update Purchases')
.addItem('Add All Rows To Sheets', 'addAllRowsToSheets')
.addItem('Add Current Row To Sheet', 'addRowToNewSheet')
.addToUi();
}
function addRowToNewSheet() {
var s = ss.getActiveSheet();
var cell = s.getActiveCell();
var rowId = cell.getRow();
var range = s.getRange(rowId, 1, 1, s.getLastColumn());
var values = range.getValues()[0];
var roomName = values[room];
appendDataToSheet(s, rowId, values, roomName);
}
function addAllRowsToSheets(){
var s = ss.getActiveSheet();
var dataValues = s.getRange(2, 1, s.getLastRow()-1, s.getLastColumn()).getValues();
for(var i = 0; i < dataValues.length; i++){
var values = dataValues[i];
var rowId = 2 + i;
var roomName = values[room];
try{
appendDataToSheet(s, rowId, values, roomName);
}catch(err){};
}
}
function appendDataToSheet(s, rowId, data, roomName){
if(s.getName() != masterSheetName){
throw new Error("Can only add rows from 'Master' sheet - make sure sheet name is 'Master'");
}
var sheetNames = [sheet.getName() for each(sheet in ss.getSheets())];
var roomSheet;
if(sheetNames.indexOf(roomName) > -1){
roomSheet = ss.getSheetByName(roomName);
var rowIdValues = roomSheet.getRange(2, 1, roomSheet.getLastRow()-1, 1).getValues();
for(var i = 0; i < rowIdValues.length; i++){
if(rowIdValues[i] == rowId){
throw new Error( data[itemName] + " from row " + rowId + " already exists in sheet " + roomName + ".");
return;
}
}
}else{
roomSheet = ss.insertSheet(roomName);
var numCols = s.getLastColumn();
roomSheet.getRange(1, 1).setValue("Row Id");
s.getRange(1, 1, 1, numCols).copyValuesToRange(roomSheet, 2, numCols+1, 1, 1);
}
var rowIdArray = [rowId];
var updatedArray = rowIdArray.concat(data);
roomSheet.appendRow(updatedArray);
}
But I always get an unexpected token error on line 51 or 52:
var sheetNames = [sheet.getName() for each(sheet in ss.getSheets())];
(And obviously the column names, etc. are not necessarily correct for my data, I tried changing them to match what I needed. Not sure if that was part of the issue.)
Here is a sample of my data: https://docs.google.com/spreadsheets/d/1kpD88_wEA5YFh5DMMkubsTnFHeNxRQL-njd9Mv-C_lc/edit?usp=sharing
This should return two separate tabs/sheets based on room .
I am obviously not a programmer and do not know Visual Basic or Java or anything. I just know how to google and copy things....amazingly I often get it to work.
Let me know what else you need if you can help.
Try the below code:
'splitSheetIntoTabs' will split your master sheet in to separate sheets of 30 rows each. It will copy only the content not the background colors etc.
'deleteTabsOtherThanMaster' will revert the change done by 'splitSheetIntoTabs'. This function will help to revert the changes done by splitSheetIntoTabs.
function splitSheetIntoTabs() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
var header = rows[0];
var contents = rows.slice(1);
var totalRowsPerSheet = 30; // This value will change no of rows per sheet
//below we are chunking the toltal row we have into 30 rows each
var contentRowsPerSheet = contents.map( function(e,i){
return i%totalRowsPerSheet===0 ? contents.slice(i,i+totalRowsPerSheet) : null;
}).filter(function(e){ return e; });
contentRowsPerSheet.forEach(function(e){
//crate new sheet here
var currSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet();
//append the header
currSheet.appendRow(header);
//populate the rows
e.forEach(function(val){
currSheet.appendRow(val);
});
});
}
// use this function revert the sheets create by splitSheetIntoTabs()
function deleteTabsOtherThanMaster() {
var sheetNotToDelete ='Master';
var ss = SpreadsheetApp.getActive();
ss.getSheets().forEach(function(sheet){
if(sheet.getSheetName()!== sheetNotToDelete)
{
ss.deleteSheet(sheet);
}
});
}
I was using Kessy's nice script, but started having trouble when the data became very large, where the script timed out. I started looking for ways to reduce the amount of times the script read/wrote to the spreadsheet (rather than read/write one row at a time) and found this post https://stackoverflow.com/a/42633934
Using this principle and changing the loop in the script to have a loop within the loop helped reduce these calls. This means you can also avoid the second call to append rows (the "else"). My script is a little different to the examples, but basically ends something like:
`for (var i = 1; i < theEmails.length; i++) {
//Ignore blank Emails and sheets created
if (theEmails[i][0] !== "" && !completedSheets.includes(theEmails[i][0])) {
//Set the Sheet name = email address. Index the sheets so they appear last.
var currentSheet = theWorkbook.insertSheet(theEmails[i][0],4+i);
//append the header
//To avoid pasting formulas, we have to paste contents
headerFormat.copyTo(currentSheet.getRange(1,1),{contentsOnly:true});
//Now here find all the rows containing the same email address and append them
var theNewRows =[];
var b=0;
for(var j = 1; j < rows.length; j++)
{
if(rows[j][0] == theEmails[i][0]) {
theNewRows[b]=[];//Initial new array
theNewRows[b].push(rows[j][0],rows[j][1],rows[j][2],rows[j][3],rows[j][4],rows[j][5],rows[j][6],rows[j][7]);
b++;
}
}var outrng = currentSheet.getRange(2,1,theNewRows.length,8); //Make the output range the same size as the output array
outrng.setValues(theNewRows);
I found a table of ~1000 rows timed out, but with the new script took 6.5 secs. It might not be very neat, as I only dabble in script, but perhaps it helps.
I have done this script that successfully gets each room and creates a new sheet with the corresponding room name and adding all the rows with the same room.
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
// This var will contain all the values from column C -> Room
var columnRoom = sheet.getRange("C:C").getValues();
// This var will contain all the rows
var rows = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
//Set the first row as the header
var header = rows[0];
//Store the rooms already created
var completedRooms = []
//The last created room
var last = columnRoom[1][0]
for (var i = 1; i < columnRoom.length; i++) {
//Check if the room is already done, if not go in and create the sheet
if(!completedRooms.includes(columnRoom[i][0])) {
//Set the Sheet name = room (except if there is no name, then = No Room)
if (columnRoom[i][0] === "") {
var currentSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet("No Room");
} else {
var currentSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet(columnRoom[i][0]);
}
//append the header
currentSheet.appendRow(header);
currentSheet.appendRow(rows[i]);
completedRooms.push(columnRoom[i][0])
last = columnRoom[i][0]
} else if (last == columnRoom[i][0]) {
// If the room's sheet is created append the row to the sheet
var currentSheet = SpreadsheetApp.getActiveSpreadsheet()
currentSheet.appendRow(rows[i]);
}
}
}
Please test it and don't hesitate to comment for improvements.

using getNotes with blanks inbetween?

I'm trying to use a for loop to change values and to add notes to the ones that I change. since I'm doing it through arrays and not each row individually, my question is, is it possible to get where there isnt a note on the cell to be nothing in the array?
It's through the google scripts system, I've tried looking everywhere but can't find anything on the subject, hoping there's a wizard here.
edit: snippet of script
function CheckHours() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Copy of PNC Roster');
var lastrow = sheet.getLastRow();
var response;
var hoursList = [];
var notesList = [];
var bmidRange = sheet.getRange("L:L").getValues();
var notesRange = sheet.getRange("J:J").getNotes();
for (var i = 0; i < lastrow; i++) {
if (bmidRange[i] > 1) {
//response = UrlFetchApp.fetch('InsertWebsite/?bm=' + bmidRange[i] + '&fr=7');
var seconds = 59378;
var hours = seconds/60/60;
hoursList.push([hours]);
var note = "Activity Check performed: "+ new Date() + "\n\n" + notesRange[i];
notesList.push([note]);
Logger.log(hours);
} else if(bmidRange[i] == "") {
notesList.push('INSERT BATTLEMETRICS ID');
} else {
hoursList.push(bmidRange[i]);
notesList.push(notesRange[i]);
}
}
sheet.getRange("J:J").offset(0, 0, hoursList.length).setNotes(notesList);
sheet.getRange("J:J").offset(0, 0, hoursList.length).setValues(hoursList);
}
What I'm trying to do when I'm pushing the cell values is that they get an appropriate note along with them, but because the variable notesRange is not the same amount as getValues gets, then where there is no notes, instead having a null value in the array basically, is this possible??

Copy and Past multiple rows. Ljava.lang.Object response

I'm writing a little script to automatically copy/past some data from one sheet to another sheet.
I've come up with the following script :
function test() {
var sheet = SpreadsheetApp.openById('1sLVRDzzOopFQrXLNtAYKfyBfJthCkjvK8xe_PtOmso0').getSheetByName('Sheet1');
var numRows = sheet.getLastRow()-1;
for(var i = 0; i < numRows; i++) {
// Dispatching Data
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet2")
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1")
var data1 = sheet.getRange('Sheet1!A2:G2').getValues();
sheet.appendRow([data1]);
ss.deleteRow(2)
}
}
It's working perfectly fine but return me the following response :
Ljava.lang.Object response... for each row.
What should I do ? thanks !
If you want to use your script then the simple fix is to change:
sheet.appendRow([data1]);
to:
sheet.appendRow(data1[0]);
because you only want the 1 row.
However I'm not sure what the point of your for loop is? This might be a little cleaner:
function test() {
var ss = SpreadsheetApp.openById('ID')
var sheet1 = ss.getSheetByName("Sheet1")
var sheet2 = ss.getSheetByName("Sheet2")
var data1 = sheet1.getRange('Sheet1!A2:G2').getValues();
sheet2.appendRow(data1[0]);
sheet1.deleteRow(2)
}

for each in ss.getSheets() get a range of data

I'm struggling on a child task within a function I'm working on.
For each sheet in sheets, I would like to get the data in range A16:D, combine into one big array and then out put the combined data into a new sheet. But each time I try to select each data range it comes as undefined?
How can I get the data from each sheet in sheets and then add into one big array?
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ranges = [];
function combineData() {
var activeSheetName = ss.getActiveSheet().getName();
var sheets = ss.getSheets();
sheets.forEach(function(e) {
var sheetName = e.getName();
if (sheetName != activeSheetName && sheetName != 'Report Configuration' && sheetName != 'Sheet1')
var wee_data = e.getRange('A16:D').getValues(); // TROUBLE HERE, WEE_DATA IS UNDEFINED
for(var j = 0; j<wee_data.length; j++) {
store.push(wee_data[j]);
}
ranges.push(wee_data);
});
if (ranges.length) {
Logger.log('range length is: ' + ranges.length);
adjustSheetLength(); // ensure there are enough rows in the destination sheet.
// figure out how to output data array "ranges" into a sheet "combined".
}
}
function adjustSheetLength(){
var comb = ss.getSheetByName('combined');
var lastRow = 4;
var maxRows = comb.getLastRow();
comb.deleteRows(lastRow, maxRows-lastRow);
var datapullSize = ranges.length;
comb.insertRows(4, datapullSize-2);// insert exactly the number of rows you need.
}
I think your issue is in the .forEach callback function. When I debugged those functions I got a not allowed in callback exception.
Change the foreach to a for in I think that'll fix your issue.
for( var i in sheets ) {
Logger.log(sheets[i]);
...
}

Handling time duration in Google Sheets Api

I'm making a basic script that fetches time durations from external sheets, and sums them. What I have so far is:
function getHorasCasoUso() {
var libros = {
"key1" : "externalSheetURL1",
"key2" : "externalSheetURL2",
...
};
var horas_por_caso_uso = {};
for (var key in libros) {
var libro = SpreadsheetApp.openByUrl(libros[key]);
var sheets = libro.getSheets();
for (var i = 0; i < sheets.length; i++) {
var sheet = sheets[i];
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
for (var j = 5; j < numRows; j++) {
var row = values[j];
var caso_uso = row[6];
var horas = row[4]; //The cell format is 'Duration'
if (!caso_uso)
continue;
if (!!horas_por_caso_uso[caso_uso])
horas_por_caso_uso[caso_uso] += horas;
else
horas_por_caso_uso[caso_uso] = horas;
}
}
}
var ss = SpreadsheetApp.getActiveSheet();
for (var key in horas_por_caso_uso) {
ss.appendRow([key, horas_por_caso_uso[key]]);
}
}
The problem is that the data stored in 'horas' is a string. I want to get the time duration in that cell. How can I do that?
Your issue seems quite similar to the one in this post but at a larger scale...
You should convert row[4] value to minutes (or seconds if you need this accuracy) before adding that value to the total counter.
If the cells are formatted as duration (as you say it is) it should work without changes.(see code at the end of this post)
If not, ie if these values are returned as strings then a simple string manipulation will do the job for you like this :
example : testString = 12:34
function toMinutes(value){
var minutes = Number(value.split(':')[0].replace(' ',''))*60+Number(value.split(':')[1].replace(' ',''));
return minutes;
}
(code working as a function) will return 754 (60*12+34)
usage in your code : var horas = toMinutes(row[4]);
function toMinutes(value){
var duration = new Date(value);
var min = duration.getMinutes()+60*duration.getHours();
return min;
}
You can eventually improve this function with a few error trapping features to handle cases where cell is empty of malformed... Since I don't know what the data look like I prefer let you do it yourself.