Trying to use onEdit to autofill timeline of a Gantt Chart - google-apps-script

I'm trying to create an apps script to autofill a gantt chart when the sheet is edited, but having trouble.
Here is a link to the spreadsheet if it helps.
function ganttChart()
{
const ss = SpreadsheetApp.getActiveSpreadsheet();
const ganttSheet = ss.getSheetByName("Gantt Chart");
var headerRow = ss.ganttSheet.getRange('headerRow').getRow();
var lastRow = ss.ganttSheet.getLastRow();
var lastCol = ss.ganttSheet.getLastColumn();
var firstTask = headerRow + 1
var taskRoleCol = ss.ganttSheet.getRange('taskRole').getColumn();
//I'm not sure if I need to do the below RoleCol if I already have a named range -- this will return an integer which is the column #
var roleCol = ss.getSheetByName("Roles").getRange('Roles').getColumn();
var taskCol = ss.ganttSheet.getRange('taskNames').getColumn();
var startWeekRow = ss.ganttSheet.getRange('startWeek').getRow();
var expDurationCol = ss.ganttSheet.getRange('expDuration').getColumn();
//set the requirements for the edit trigger -- not sure what these would be
//if (e.range)
//{
for (var i = firstTask; i < lastRow; i++)
{
var currentTask = ss.ganttSheet.getRange(i, taskCol).getValue();
var currentStartWeek = ss.ganttSheet.getRange(i, startWeekCol).getValue();
var currentTaskExpDuration = ss.ganttSheet.getRange(i,expDurationCol).getValue();
var currentTaskRole = ss.ganttSheet.getRange(i,taskRoleCol).getValue();
if (currentTask != null)
{
if (currentStartWeek != null)
{
//for loop to identify the column that matches the start week #
for (var j = 0; j < lastCol; j++)
{
var checkWeek = ss.ganttSheet.getRange(startWeekRow, j).getValue();
if (checkWeek == currentStartWeek)
{
//identify the range
var taskTimeRange = ss.ganttSheet.getRange(i,j - 1,(currentTaskExpDuration*2 +1), 1);
//for loop get the background color based on role
for (var k = 0; k < lastRow; k++)
{
var checkRole = ss.ganttSheet.getRange(k, roleCol).getValue();
//if role value matches the currentTaskRole
if (checkRole == currentTaskRole)
{
var roleColor = ss.ganttSheet.getRange(k, roleCol).getBackground();
//reformat the range based on duration
taskTimeRange.setBackground(roleColor);
}
}
}
}
}
}
}
//}
}
I took off the "onEdit" to try and get the program to work on run, but I'm still getting a "cannot read properties of undefined" error.
What should happen is:
when a user edits the "Gantt Chart" sheet
the program changes the background color in the corresponding range to indicate the weeks a task is being worked on, based on the start week and calculated duration
the background color should correspond to the task role, based on the colors set in the "Roles" sheet
If the above isn't clear, here is a link to a video where I try to explain what the program should do

The question relates to onEdit but the trigger is, at this point of development of the script, irrelevant, since the script is littered with syntax errors.
Even so, I suggest that the trigger is irrelevant in any event. The script can/should be triggered when the data has been populated. This trigger can be done manually, or (perhaps) via a menu option.
getRange(), getLastRow() and getLastColumn are sheet-based methods.
Incorrect
var headerRow = ss.ganttSheet.getRange('headerRow').getRow()
var lastRow = ss.ganttSheet.getLastRow()
var lastCol = ss.ganttSheet.getLastColumn()
Correct
var headerRow = ganttSheet.getRange('headerRow').getRow()
var lastRow = ganttSheet.getLastRow()
var lastCol = ganttSheet.getLastColumn()
Other
startWeekCol is not defined
for (var j = 0; j < lastCol; j++)
"j" substitutes for the column number, but a value of 0 is invalid
var taskTimeRange = ganttSheet.getRange(i,j - 1,(currentTaskExpDuration*2 +1), 1)
when "j" is 1, "j-1" resolves to 0 (zero) which is invalid
for (var k = 0; k < lastRow; k++)
"k" substitutes for the row number, but a value of 0 is invalid

Related

Hard sittuation : how to set value "permission of user" into google sheets correctly?

I have "script A" that can list of any file in "source folder" with url
Then i write "script B" to scan all ID of them and get every email user of file's permission, but when i set "email" value to [i] rows, it will overwrite till the last user of that permission.
Examples: if file "A" has 3 users of view, a#gmail, b#gmail, c#gmail, then "script B" will overwrite till c#gmail at file "A" row. (then we dont know about a#gmail, b#gmail have viewer permission.)
function Listpermission() {
var ss = SpreadsheetApp.getActiveSheet();
var dataLength = getDataLength(); var data = getSheetValues();
for(var i = 0; i < dataLength; i++) {
if(data[i]["Condition"] != "1") continue
var thisid = SpreadsheetApp.getActiveSheet().getRange(i+1, 12).getValue()
Logger.log(thisid)
var editors = DriveApp.getFileById(thisid).getEditors()
for (var x = 0; x < editors.length;x++){
var edit = editors[x].getEmail() }
SpreadsheetApp.getActiveSheet().getRange(i+1, 14).setValue(edit)
var viewers = DriveApp.getFileById(thisid).getViewers()
for (var x = 0; x < viewers.length;x++) {
var view = viewers[x].getEmail()
Logger.log(view)
SpreadsheetApp.getActiveSheet().getRange(i+1, 13).setValue(view)
}}}
This is log of runing script, in the red box, there are 2 user of view but it overwrite at: Range(i+1,13)
Any idea to solve this isue, thank you very much.
You want to retrieve emails of editors and viewers from the file ID.
File IDs are in the column "L".
You want to put the retrieved values to the cells.
From your script, I thought that you might want to put the values of editors and viewers to a cell, respectively.
You want to put the viewers and editors to the column "M" and "N", respectively.
You want to achieve this using Google Apps Script.
If my understanding is correct, how about creating a value using join() and putting the value to the cells? For this situation, how about the following modifications? Please think of this as just one of several answers.
Modified script 1:
Please modify your script as follows. In this modification, the for loop is modified.
function Listpermission() {
var ss = SpreadsheetApp.getActiveSheet();
var dataLength = getDataLength();
var data = getSheetValues();
for(var i = 0; i < dataLength; i++) {
if (data[i]["Condition"] != "1") continue
var thisid = ss.getRange(i+1, 12).getValue()
var editors = DriveApp.getFileById(thisid).getEditors()
var edit = [];
for (var x = 0; x < editors.length;x++) {
edit.push(editors[x].getEmail());
}
ss.getRange(i+1, 14).setValue(edit.join(","));
var viewers = DriveApp.getFileById(thisid).getViewers();
var view = [];
for (var x = 0; x < viewers.length;x++) {
view.push(viewers[x].getEmail());
}
ss.getRange(i+1, 13).setValue(view.join(","));
}
}
Modified script 2:
Please modify your script as follows. In this modification, the values are retrieved by getValues() and are created in the for loop and the created values are put to the Spreadsheet using setValues(). I think that this becomes lower cost of the process than that of getValue() and setValue().
function Listpermission() {
var ss = SpreadsheetApp.getActiveSheet();
var dataLength = getDataLength();
var data = getSheetValues();
var thisids = ss.getRange(1, 12, dataLength, 1).getValues();
var values = [];
for (var i = 0; i < thisids.length; i++) {
if (data[i]["Condition"] != "1") {
values.push(["", ""]);
continue;
}
var thisid = thisids[i][0];
var view = [];
var viewers = DriveApp.getFileById(thisid).getViewers();
for (var x = 0; x < viewers.length;x++) {
view.push(viewers[x].getEmail());
}
var edit = [];
var editors = DriveApp.getFileById(thisid).getEditors();
for (var x = 0; x < editors.length;x++) {
edit.push(editors[x].getEmail());
}
values.push([view.join(","), edit.join(",")]);
}
ss.getRange(1, 13, values.length, 2).setValues(values);
}
References:
join()
getValue()
setValue(value)
getValues()
setValues(values)
Benchmark: Reading and Writing Spreadsheet using Google Apps Script
If I misunderstood your question and this was not the result you want, I apologize.
Fill array with all values to Array and then join all values to one column.
var viewers = DriveApp.getFileById(thisid).getViewers();
var viewerEmails = [];
for (var x = 0; x < viewers.length;x++) {
var view = viewers[x].getEmail();
Logger.log(view);
viewerEmails.push(view);
}
SpreadsheetApp.getActiveSheet().getRange(i+1, 13).setValue(viewerEmails.join());

Google spreadsheets - find all hidden rows - only include rows that are not hidden

The code provided copies data from sheet "Feuille 3" to an another sheet named "INITIALE". Some of the rows in the source sheet tab, "Feuille 3" are hidden.
I hide some rows in "Feuille 3" when col D has a checkbox with a "true" value.
I don't know how I can check the rows hidden in "Feuille 3" and remove these rows in my array "Nouvelleliste."
Here is the code :
function copiertableau() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var orig = ss.getSheetByName("Feuille 3");
var dest = ss.getSheetByName("INITIALE");
var Ancienneliste = orig.getDataRange().getValues();
var Nouvelleliste = new Array();
for (var i = 0; i < Ancienneliste.length; i++) {
var Nouvelleligne = new Array();
for (var j = 0; j < 3; j++) {
Nouvelleligne[j] = Ancienneliste[i][j];
}
Nouvelleliste[i] = Nouvelleligne;
}
ss.getSheetByName("INITIALE").getRange(22, 1, Nouvelleliste.length, 3).setValues(Nouvelleliste);
SpreadsheetApp.flush();
}
The Advanced Sheets API has a way of finding all the rows that were hidden by the user or by code. You must explicitly enable the Advanced Sheets API from the code editor. From the code editor choose, "Resources" and "Advanced Google Services." Scroll down to "Google Sheets API" Turn the button ON. Then click the link to Google API Console. Enable the Google Sheets API in your console.
First get all the rows that are hidden. In the example below, that is done in a separate function. Then compare the current row index to the values in the array. If there is a hidden row, then don't put the row's data into the array.
function copiertableau() {
var arrOfHiddenRows,Nouvelleligne,o,sourceSheetTab,ss;
ss = SpreadsheetApp.getActiveSpreadsheet();
sourceSheetTab = ss.getSheetByName("Feuille 3");
var dest = ss.getSheetByName("INITIALE");
sourceSheetTab = ss.getSheetByName("INITIALE");
var Ancienneliste = sourceSheet.getDataRange().getValues();
var Nouvelleliste = [];
o = {};//Object for arguments to pass to function to get the hidden rows
o.L = sourceSheetTab.getLastRow();
o.ssID = ss.getId();//Put the spreadsheet file ID into the object with key name ssID
o.sheetId = sourceSheetTab.getSheetId();
arrOfHiddenRows = getRowsHiddenByUsr(o);//Get a list of all hidden rows in sheet tab sourceSheet
//Logger.log('arrOfHiddenRows: ' + arrOfHiddenRows)
for (var i = 0; i < Ancienneliste.length; i++) {
if (arrOfHiddenRows.indexOf(i+1) !== -1) {//This row is hidden in the sheet sourceSheet
continue;//continue to loop without putting this rows data into the array
}
Nouvelleligne = [];
for (var j = 0; j < 3; j++) {
Nouvelleligne[j] = Ancienneliste[i][j];
}
Nouvelleliste[i] = Nouvelleligne;
}
ss.getSheetByName("INITIALE").getRange(22, 1, Nouvelleliste.length, 3).setValues(Nouvelleliste);
SpreadsheetApp.flush();
}
function getRowsHiddenByUsr(po) {
try{
var arrHiddenRows,data,fields,i,j,L,L_sh,rows,sheets,sheetId,spreadsheetId,thisSheet,thisShID;
/*
po.L - row length of the sheet tab
po.ssID - the spreadsheet file ID of the spreadsheet
po.sheetID - The ID of the sheet tab
*/
L = po.L;
spreadsheetId = po.ssID;
sheetId = po.sheetID;
//Logger.log(L)
//Logger.log('sheetId: ' + sheetId)
arrHiddenRows = [];
fields = "sheets(data(rowMetadata(hiddenByUser)),properties/sheetId)";//Get only metadata of hidden rows by user
sheets = Sheets.Spreadsheets.get(spreadsheetId, {fields: fields}).sheets;
L_sh = sheets.length;
//Sheets.Spreadsheets.get(spreadsheetId)
//Logger.log('sheets.length: ' + sheets.length)
for (i = 0; i < L_sh; i++) {
thisSheet = sheets[i];
//Logger.log('thisSheet === undefined: ' + thisSheet === undefined)
if (thisSheet === undefined) {
continue;
}
thisShID = thisSheet.properties.sheetId;
//Logger.log('thisShID: ' + thisShID)
if (thisShID === sheetId) {
//Logger.log('they are equal')
data = thisSheet.data;
rows = data[0].rowMetadata;
//Logger.log('thisShID: ' + thisShID)
//Logger.log('rows.length: ' + rows.length)
for (j = 0; j < L; j++) {
//Logger.log(rows[j].hiddenByUser)
if (rows[j].hiddenByUser) arrHiddenRows.push(j+1);
}
}
}
return arrHiddenRows;
}catch(e) {
console.log(e.message);
console.log(e.stack);//log the stack
}
}
thanks #SandyGood It seems it's missing something.
Firstly, i changed
var Ancienneliste = sourceSheet.getDataRange().getValues();
to put "sourceSheetTab" variable; and i delete a line because you put twice "sourceSheetTab" with 2 contents so i keep the first one.
Secondly, your function continue to copy hidden rows in the sourcesheet table.
maybe i can put my other code i did to hide rows in "Feuille 3" to help?
//global variables
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Feuille 3");
function cacherRow() {
for (var i=1; i < 300; i ++){
var nb = sheet.getRange('D'+i).getValue();
if (nb == true){
sheet.hideRows(i);
}
}
}
i enable googlesheets API in the console.
Google Apps Script now offers a new method - isRowHiddenByUser - to determine if a particular row is hidden or not.
for (var i = 0; i < Ancienneliste.length; i++) {
if (sheet.isRowHiddenByUser(i+1)) {
// do something
}
}
Note that you need to specify the row position in the method and it begins from 1, not 0.

Google script - Exceeded maximum execution time , help optimize

google script spreadsheet
Novice
I try to create a matrix , if the array is a small database everything works fine, of course if it exceeds 800 lines and more rests on the error "You have exceeded the maximum allowed run time ." Not effectively create a matrix :
var s = SpreadsheetApp.getActiveSheet(); //List
var toAddArray = []; //Greate Arr
for (i = 1; i <= s.getLastRow()+1; ++i){ //Start getting Value
var numbr = s.getRange(i,4); //detect range
var Valus = numbr.getValues().toString(); //get value
//filter value
var newznach = Valus.replace(/\-/g, "").replace(/[0-9][0-9][0-9][0-9][0-9][a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "").replace(/[a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "");
toAddArray.push([i.toFixed(0),Valus,newznach]); //add to array 0- Row numb, 1- Value, 2- "filtered" value
}
toAddArray =
{
Row, Value, NewValue - filtered
Row, Value, NewValue - filtered
Row, Value, NewValue - filtered
...
}
Can I somehow get an array of the same the other way ( faster, easier ) ?
You're doing a call to getValues every row, that eats a lot of performance.
It is better to do one big call to have all the data and then go through it sequentially.
var s = SpreadsheetApp.getActiveSheet();
var data = s.getRange(1,4, s.getLastRow()).getValues();
var toAddArray = data.map(function(row, i) {
var Valus = row[0].toString();
var newznach = Valus.
replace(/\-/g, "").
replace(/[0-9][0-9][0-9][0-9][0-9][a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "").
replace(/[a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "");
return [i.toFixed(0), Valus, newznach];
});
this code:
var Valus = numbr.getValues().toString();
slows you down because you read data from the sheet in a loop.
Try reading data once into array and then work with it:
var data = s.getDataRange().getValues();
And then work with data, in a loop. This sample code log each cell in active sheet:
function logEachCell() {
var s = SpreadsheetApp.getActiveSheet();
var data = s.getDataRange().getValues();
// loop each cell
var row = [];
for (var i = 0; i < data.length; i++) {
row = data[i];
for (var j = 0; j < row.length; j++) {
Logger.log(row[j])
}
}
}

Trying to add an IF statement to an array of objects in Google Apps Script for Sheets

Trying to send an email to a list in a Google Sheet, but only if column N gets marked "Yes." Acceptable data in column N is "Yes" or "No" so I want to test that and send emails only to the rowData that contain "yes" there. Then write the date in the adjacent column on the same row. I can't seem to figure out how to iterate through the array of objects and couldn't find any good resources to explain this. Help greatly appreciated. My best effort was emailing all the rows and then also filling in the date no matter what was in column N (Yes/No/Blank).
function sendEmails() {
validateMySpreadsheet() //a function that checks for "Yes" in column N
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataSheet = ss.getActiveSheet();
var dataRange = dataSheet.getRange(2, 1, dataSheet.getMaxRows() - 1, 16);
var d = new Date();
var dd = d.getDate();
var mm = d.getMonth() + 1; //Months are zero based
var yyyy = d.getFullYear();
var date = mm + "/" + dd + "/" + yyyy;
var needsaYes = "Yes";
//Gets the email template
var templateSheet = ss.getSheetByName("Template");
var emailTemplate = templateSheet.getRange("A1").getValue();
// Create one JavaScript object per row of data.
objects = getRowsData(dataSheet, dataRange);
//This is where I am stuck - how to check if column N contains a "Yes" before allowing the MailApp.SendEmail command to run.
for (var i = 0; i < objects.length; ++i) {
// Get a row object
var rowData = objects[i];
var values = dataRange.getValues();
for (var j = 0; j < values.length; ++j) {
var row = values[j];
var checkFirst = row[13]; //row J, Column N?
if (checkFirst = needsaYes) { //does column N contain "Yes"?
var emailText = fillInTemplateFromObject(emailTemplate, rowData);
var emailSubject = "mySubject";
MailApp.sendEmail(rowData.email, emailSubject, emailText);
dataSheet.getRange(2 + i, 15).setValue(date); //then write the date
SpreadsheetApp.flush();
}
}
}
}
I didn't check whole code but
if (checkFirst = needsaYes)
It may be the problem, you need to use ==
I figured it out I needed to just use the .getValues() method for my data range rather than the whole sheet and also remove the unnecessary second for loop:
var rowData = objects[i]; var checkData = ss.getActiveSheet().getDataRange().getValues();
var row = checkData[i]
var colN = row[13] if (colN == needsaYes) { //etc.

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.