I have a Google Sheet that is being used to track applicant interview data. I am trying to find the Round Average Score for each candidate based on their Interview Round and Round score. I figured out how to gather this data with a query function but for this use case in particular it has to be done in a script.
Here is an example of the sheet
Any help would be greatly appreciated.
Average of Average Scores
function lfunko() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
const vs = sh.getRange(2, 1, sh.getLastRow() - 1, sh.getLastColumn()).getValues();
let co = { pA: [] }
vs.forEach((r, i) => {
let p = `${r[0]}/${r[2]}`;
if (!co.hasOwnProperty(p)) {
co[p] = { cnt: 1, sum: r[4], idx: i }
co.pA.push(p);
} else {
co[p].cnt += 1;
co[p].sum += r[4];
}
});
let vo = vs.map((r, i) => {
let p = `${r[0]}/${r[2]}`;
if (i == co[p].idx) {
return [co[p].sum / co[p].cnt];
} else {
return [''];
}
})
sh.getRange(2, 6, vo.length, vo[0].length).setValues(vo);
}
Ouput:
Candidate
Position
Interview Round
Panelist
Round Score
Round Average Score
Bob
Tester
First
Jon
3
4
Bob
Tester
First
Janet
4
Bob
Tester
First
Joe
5
Bob
Tester
Second
Sal
4
3.333333333
Bob
Tester
Second
Riley
3
Bob
Tester
Second
Tae
3
Bob
Tester
Final
Wanda
5
4.666666667
Bob
Tester
Final
Kelly
4
Bob
Tester
Final
Arnold
5
Al
Senior Tester
First
Ben
2
3
Al
Senior Tester
First
Tori
3
Al
Senior Tester
First
Harry
4
Al
Senior Tester
Second
Kate
4
3.666666667
Al
Senior Tester
Second
Wendy
5
Al
Senior Tester
Second
Carl
2
Al
Senior Tester
Final
Sam
5
4
Al
Senior Tester
Final
Jake
3
Al
Senior Tester
Final
Troy
4
If you need to get the data as permanent static values that will not change later even if the source data gets modified, you can still use a query() formula to get the results, and then use a short script to replace the formula and its results with static values. To try it out, Insert > Sheet and use this:
=query(sumAve!A1:E, "select A, B, avg(D) where D is not null group by A, B", 1)
/**
* Replaces formulas with values in the active sheet.
*/
function replaceFormulasWithValuesInActiveSheet() {
const wholeSheet = SpreadsheetApp.getActiveSheet().getDataRange();
wholeSheet.setValues(wholeSheet.getValues());
}
Related
I was wondering which Google Apps Script function may help me to split a Google Sheets cell value into n parts (given a separator) and replicate the whole row as different occurrences for that split. So, f.i., given this table:
Name
Country
Sport
John
USA
Basketball_Golf_Tennis
Mary
Canada
Tennis_Golf
the desired output should be:
Name
Country
Sport
John
USA
Basketball
John
USA
Golf
John
USA
Tennis
Mary
Canada
Tennis
Mary
Canada
Golf
In this example, the separator is the char _
You could probably do this with a regular spreadsheet formula (lookout for incoming solution from Player0 who will probably point out something I should have thought of... ), but since you asked for an app script solution, this works:
/**
* Splits data
*
* #param {array} theRange The range of data.
* #param {string} theSplitter The text used to split.
* #return the new table
* #customfunction
*/
function goUSA(theRange, theSplitter) {
const splitColumn = 2;
var result = [];
for (r = 0; r < theRange.length; r++) {
var aRow = theRange[r];
//skips empty rows, enabling ability to select entire column
if (aRow.join('') != '') {
var tempSplit = aRow[splitColumn].split(theSplitter);
for (q = 0; q < tempSplit.length; q++) {
result.push([aRow[0], aRow[1], tempSplit[q]]);
}
}
}
return result;
}
see:
=INDEX(QUERY(SPLIT(FLATTEN(IF(IFERROR(SPLIT(C1:C, "_"))="",,
A1:A&""&B1:B&""&SPLIT(C1:C, "_"))), ""), "where Col2 is not null", ))
Splitting Column 3
function brkaprt() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
const osh = ss.getSheetByName("Sheet1");
osh.clearContents();
const vs = sh.getRange(2,1, sh.getLastRow() - 1, sh.getLastColumn()).getValues();
let obj = {pA:[]};
let o = vs.reduce((ac,[a,b,c],i) => {
c.split("_").forEach(e =>ac.push([a,b,e]) )
return ac;
},[]);
o.unshift(["Name","Country","Sport"]);
Logger.log(JSON.stringify(o));
osh.getRange(1,1,o.length,o[0].length).setValues(o);
}
Execution log
10:56:15 AM Notice Execution started
10:56:16 AM Info [["Name","Country","Sport"],["John","USA","Basketball"],["John","USA","Golf"],["John","USA","Tennis"],["Mary","Canada","Tennis"],["Mary","Canada","Golf"]]
10:56:17 AM Notice Execution completed
A
B
C
1
Name
Country
Sport
2
John
USA
Basketball
3
John
USA
Golf
4
John
USA
Tennis
5
Mary
Canada
Tennis
6
Mary
Canada
Golf
Current Issue:
Hey everyone, appreciate any help here as I'm still beginning my journey in coding.
I'm trying to see if I can make a script that will:
Look for duplicates (in column D), and
delete any data from the following duplicates after the 1st match in columns E-L (see desired outcome if that doesn't make sense verbally).
The script would need to use the column header names (ex. "snacks") instead of hard-coded column references
*So for example, the script finds ABC001, deletes only the duplicates for ABC001 in the corresponding columns then moves on to ABC004 and performs the same action.
I'm not sure how to write a script that would do this, and keep going to find duplicates after the 1st set is found. I think I know how to do a for loop now, but it's not clear to me how to make it do a search loop and stop after it find the first match and keep going.
Current Data:
Desired Outcome:
Code so far below. I think I would need to incorporate something like JSmith showed in this example? Or would I need to incorporate some form of .length with the duplicate range in a for statement so that it can find the duplicates, get the # of them, and then only perform the action on everything past the 1st instance?
function duplicateRemoval() {
ss = SpreadsheetApp.getActive().getSheetByName('Sheet1');//gets sheet by name
const [aB,...cd] = ss.getDataRange().getValues();//literal assignment that assigns aB to the header array and the rest of the data to 'cd'
let column = {}
let iData = {};//index into the row array for each column header
aB.forEach((a,i)=>{column[a] = i+1;iData[a]=i});//building column and iData so that headers can move anywhere
}//let & forEach derived from (https://stackoverflow.com/questions/70101896/search-column-for-text-and-use-array-list-to-insert-text-in-another-cell) #Cooper
Raw Data:
Name
Owner
Snack
Transaction #
# of snacks requested
#2
#3
#4
#5
#6
#7
#8
Bill Example
Snacktown
celery
ABC001
4
1
2
3
4
5
6
4
Bill Example
Snacktown
celery
ABC001
4
1
2
3
4
5
6
4
Bill Example
Snacktown
celery
ABC001
4
1
2
3
4
5
6
4
Jane Doe
Snacktown
chips
ABC002
1
1
1
1
1
1
1
1
Jane Doe
Chipworld
chips
ABC003
1
1
1
1
1
1
1
1
Jane Doe
Chipworld
chips
ABC004
5
5
1
1
1
1
1
5
Jane Doe
Chipworld
chips
ABC004
5
5
1
1
1
1
1
5
Jane Doe
Chipworld
chips
ABC004
5
5
1
1
1
1
1
5
Jane Doe
Chipworld
chips
ABC004
5
5
1
1
1
1
1
5
Sources:
google app script array delete duplicate value from top
Google Script App Delete Duplicate Rows with a Specific Value in Specific Column in Google Sheet
How do I find and delete duplicate values in a range of cells while keeping the first occurrence of a duplicated value in Google Sheets?
Assuming transaction ids are always grouped, iterate through rows and delete all specified columns where previous transactionId is equal to current transactionId.
function duplicateRemovalOfColsToRemove() {
const transactionsHeader = 'Transaction #',
colsToRemoveHeaders = ['# of snacks requested', '#2'],//add column headers as necessary
ss = SpreadsheetApp.getActive().getSheetByName('Sheet1'), //gets sheet by name
range = ss.getDataRange(),
[headers, ...values] = range.getValues(),
colsToRemove = colsToRemoveHeaders.map((h) => headers.indexOf(h)),
transactionsIdx = headers.indexOf(transactionsHeader);
let currTransaction = '';
values.forEach((row) =>
row[transactionsIdx] === currTransaction
? colsToRemove.forEach((idx) => (row[idx] = ''))
: (currTransaction = row[transactionsIdx])
);
range.setValues([headers, ...values]);
}
It is unclear why you want to use a script here, as this seems doable with a plain vanilla spreadsheet formula. It is also unclear whether you really need to repeat the values in A2:D many times with nothing in columns E2:L.
To remove duplicate rows, and get just one copy of each unique transaction, choose Insert > Sheet and put this spreadsheet formula in cell A1:
=unique(Sheet1!A2:L)
To get the expected result you show, including rows that are mostly blank, use this:
=arrayformula(
{
Sheet1!A2:D,
array_constrain(
if(
Sheet1!D2:D <> Sheet1!D1:D,
Sheet1!E2:L,
iferror(1/0)
),
rows(Sheet1!E2:L), columns(Sheet1!E2:L)
)
}
)
To determine row uniqueness based on all columns A2:D instead of just the transaction ID in column D2:D, replace the if() condition with A2:A & B2:B & C2:C & D2:D <> A1:A & B1:B & C1:C & D1:D, inserting the proper sheet reference.
If you need a script you can try this:
function main() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var sheet = ss.getActiveSheet();
var range = sheet.getDataRange();
// get all data from the sheet
var data = range.getValues();
// get column headers
var headers = data.shift();
// get the list of transactions
var transactions = data.map(x => x[headers.indexOf('Transaction #')]);
// loop through all the transactions
for (let transaction of transactions) {
// get indexes of rows to process
var rows = transactions.map((t, row) => t === transaction ? row : '' ).filter(String).slice(1);
// process the rows
for (let r of rows) {
data[r][headers.indexOf('# of snacks requested')] = '';
data[r][headers.indexOf('#2')] = '';
data[r][headers.indexOf('#3')] = '';
data[r][headers.indexOf('#4')] = '';
data[r][headers.indexOf('#5')] = '';
data[r][headers.indexOf('#6')] = '';
data[r][headers.indexOf('#7')] = '';
data[r][headers.indexOf('#8')] = '';
}
}
// put the updated data back to the sheet
range.setValues([headers, ...data]);
}
Update
Here is the improved variant of the same code. It still loops through all the rows, but it skips already processed transactions:
function main() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var range = sheet.getDataRange();
var [headers, ...data] = range.getValues();
var transactions = data.map(x => x[headers.indexOf('Transaction #')]);
var cols_to_clean = ['# of snacks requested','#2','#3','#4','#5','#6','#7','#8'];
var processed_transactions = [];
for (let transaction of transactions) {
// skip already processed transactions
if (processed_transactions.includes(transaction)) continue;
var rows_to_clean = transactions.map((t, row) => t === transaction ? row : '' )
.filter(String).slice(1);
for (let r of rows_to_clean) {
cols_to_clean.forEach(c => data[r][headers.indexOf(c)] = '');
}
processed_transactions.push(transaction);
}
range.setValues([headers, ...data]);
}
Thanks to #TheMaster for the noted deficiencies.
Update 2
Sorry for spamming, just figured out the final solution that has no redundant iterations (I hope):
function main() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var range = sheet.getDataRange();
var [headers, ...data] = range.getValues();
var cols_to_clean = ['# of snacks requested','#2','#3','#4','#5','#6','#7','#8'];
// get all transactions (9 items for this data)
var all_transactions = data.map(x => x[headers.indexOf('Transaction #')]);
// get the short list of unique transaction (4 items for this data)
var uniq_transactions = [... new Set(all_transactions)];
for (let transaction of uniq_transactions) {
// get all indexes of rows with given transaction
var rows_to_clean = all_transactions.map((t, row) => t === transaction ? row : '')
.filter(String).slice(1);
// clean the rows
for (let r of rows_to_clean) {
cols_to_clean.forEach(c => data[r][headers.indexOf(c)] = '');
}
}
range.setValues([headers, ...data]);
}
I didn't remove my first update, I think this can be useful for educational purposes.
I have a bunch of rows and I want to append duplicates except change two of the cells.
I need each person to have a row for 15000, 20000, 25000 for each 24 and 36 (if this makes sense?)
Input:
A B C D
1 15000 24 Susan Smith
2 15000 24 John Deer
Expected output
A B C D
1 15000 24 Susan Smith
2 20000 24 Susan Smith
3 25000 24 Susan Smith
4 15000 36 Susan Smith
5 20000 36 Susan Smith
6 25000 36 Susan Smith
7 15000 24 John Deer
8 20000 24 John Deer
9 25000 24 John Deer
10 15000 36 John Deer
11 20000 36 John Deer
12 25000 36 John Deer
I understand that I need to do a function that for each row copies and appends the row, but am unsure how this is done.
I believe your goal as follows.
You want to achieve the conversion in your question.
For example, when the values of the columns "C" and "D" are Susan and Smith, respectively, you want to put the following values to the Spreadsheet.
15000 24 Susan Smith
20000 24 Susan Smith
25000 24 Susan Smith
15000 36 Susan Smith
20000 36 Susan Smith
25000 36 Susan Smith
You want to achieve this using Google Apps Script.
In this case, I would like to propose the following flow.
Retrieve the values from the columns "C" and "D" from the source sheet.
Remove the empty rows.
Create an array for putting values using the values of 15000, 20000, 25000 and 24, 36 for the columns "A" and "B", respectively.
Put the values to the destination sheet.
When above flow is reflected to a Google Apps Script, it becomes as follows.
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet. And, please set the variables of srcSheetName and dstSheetName, and run the function of myFunction.
function myFunction() {
const srcSheetName = "Sheet1"; // Please set the source sheet name.
const dstSheetName = "Sheet2"; // Please set the destination sheet name.
const ss = SpreadsheetApp.getActiveSpreadsheet();
// 1. Retrieve the values from the columns "C" and "D".
const srcSheet = ss.getSheetByName(srcSheetName);
const values = srcSheet.getRange("C1:D" + srcSheet.getLastRow()).getValues();
// 2. Remove the empty rows.
const v = values.filter(([c,d]) => c && d);
// 3. Create an array for putting values using the values of `15000, 20000, 25000` and `24, 36` for the columns "A" and "B", respectively.
const colA = [15000, 20000, 25000];
const colB = [24, 36];
const res = v.reduce((ar, [c,d]) => {
colB.forEach(b => colA.forEach(a => ar.push([a, b, c, d])));
return ar;
}, []);
// 4. Put the values to the destination sheet.
const dstSheet = ss.getSheetByName(dstSheetName);
dstSheet.getRange(1, 1, res.length, res[0].length).setValues(res);
}
Note:
If you want to use above script as the custom function, you can also use the following script. In this case, please copy and paste the following script to the script editor of Spreadsheet. And, please put the custom function of =SAMPLE(C1:D) to a cell. By this, the result values are obtained.
const SAMPLE = values => values
.filter(([c,d]) => c && d)
.reduce((ar, [c,d]) => {
[24, 36].forEach(b => [15000, 20000, 25000].forEach(a => ar.push([a, b, c, d])));
return ar;
}, []);
References:
getValues()
setValues(values)
reduce()
forEach()
Custom Functions in Google Sheets
I have customer purchase data which is exported from SQL database. The output format in Google sheets looks like this:
ID name address phone product name
1 Bob 2030 random road 6265609245 A
1 B
2 Peter 5453 golden drive A
2 D
3 Jason 1 dna way C
4 James sfo drive A
4 B
4 C
I'd like to know if I could autofill the blank cells with the upper cell values if they have the same ID. To illustrate this please see the desired output format below:
ID name address phone product name
1 Bob 2030 random road 6265609245 A
1 Bob 2030 random road 6265609245 B
2 Peter 5453 golden drive A
2 Peter 5453 golden drive D
3 Jason 1 dna way C
4 James sfo drive A
4 James sfo drive B
4 James sfo drive C
Any help will be greatly appreciated!
I assume that you want to complete the row based on the ID. If my assumption is correct, you can use this Apps Script code:
function so62048323() {
var data = SpreadsheetApp.getActive().getDataRange().getValues();
var nameDictionary = new Object();
var addressDictionary = new Object();
var phoneDictionary = new Object();
for (var r = 1; r < data.length; r++) {
if (data[r][0] != '') {
if (data[r][1] != '') {
nameDictionary[data[r][0]] = data[r][1];
}
if (data[r][2] != '') {
addressDictionary[data[r][0]] = data[r][2];
}
if (data[r][3] != '') {
phoneDictionary[data[r][0]] = data[r][3];
}
}
}
for (var r = 1; r < data.length; r++) {
if (data[r][1] == '') {
data[r][1] = nameDictionary[data[r][0]];
}
if (data[r][2] == '') {
data[r][2] = addressDictionary[data[r][0]]
}
if (data[r][3] == '') {
data[r][3] = phoneDictionary[data[r][0]];
}
}
SpreadsheetApp.getActive().getDataRange().setValues(data)
}
The code will initiate reading all your table with SpreadsheetApp.getActive(), Spreadsheet.getDataRange() and Range.getValues(). After that it will create three objects as dictionaries.
After that the code will iterate each row and record in the dictionaries each value (name, phone and address) associated with the ID. Afterwards, the code will repeat the operation but writing down the values. Finally, all the data is loaded in the sheet with Range.setValues(). For example, if we have this spreadsheet:
And we use the script, we will end up with this:
Please, notice how some ID doesn't have any phones; these are left empty. I understand that you may be using a very big database, so I optimized the code so it will run the fastest possible without taking into account the data size. Also, this code will work even if the rows aren't ordered. Don't hesitate to ask me any question if you have doubts.
I've been working on a time in/time out google sheet with a script. My original question was how to share a sheet to a user(let's call them client) where the Client can edit the sheet that had an onEdit trigger that would then set values into another sheet that the Client did not have permission to use.
I first tried just sharing a spreadsheet and making one of the sheets in that spreadsheet locked. But this prevented the trigger from running correctly when used by the client. So I asked about it and someone suggested using an installable edit trigger on the client sheet. And have the information be appended to a new spreadsheet. However, I don't think this is what I wanted because the way the code works needs the clients to not be able to edit the information that will be appended, not because I don't want false information but because if they get rid of certain columns, the script doesn't know what to do.
But it did give me an idea, instead of having one spreadsheet with two sheets, just have 2 spreadsheets with 1 sheet each. Much less likely for the people using the client account to find the shared spreadsheet vs finding the sheet in the open spreadsheet they are using.
So now to show you some code. Sorry I feel I have to show all of it due to any small thing that could be wrong I am over looking.
Host Spreadsheet Id is the Id of the spreadsheet the information is being logged into. This is the information I don't want being edited by the client.
ACTIVE refers to the name of the sheet the client is clicking on and editing. PASSIVE refers to the name of the sheet that is collecting information. Originally these two sheets were on the same spreadsheet.
function setValue(cellName, value) {
SpreadsheetApp.getActiveSpreadsheet().getRange(cellName).setValue(value);
}
function getCurrentRow() {
var currentRow = SpreadsheetApp.getActiveSheet().getActiveSelection().getRowIndex();
return currentRow;
}
function getValue(cellName) {
return SpreadsheetApp.getActiveSpreadsheet().getRange(cellName).getValue()
}
function getNextRow() {
var sas = SpreadsheetApp.openById("Host Spreadsheet Id");
var ss = SpreadsheetApp.setActiveSpreadsheet(sas)
return SpreadsheetApp.getActiveSpreadsheet().getSheetByName('PASSIVE').getLastRow() +1;
}
function getLasttRow() {
var sas = SpreadsheetApp.openById("Host Spreadsheet Id");
var ss = SpreadsheetApp.setActiveSpreadsheet(sas)
return SpreadsheetApp.getActiveSpreadsheet().getSheetByName('PASSIVE').getLastRow();
}
function addRecord(a, b, c, d) {
var sas = SpreadsheetApp.openById("Host Spreadsheet Id");
SpreadsheetApp.setActiveSpreadsheet(sas)
var row = getNextRow();
setValue('PASSIVE!A' + row, a);
setValue('PASSIVE!B' + row, b);
setValue('PASSIVE!C' + row, c);
setValue('PASSIVE!D' + row, d);
}
/// this function is to find the row number(s) that match the criteria in
/// the timeIn() function so that the time in date can be placed
/// directly to the right of the individual that signed out.
function findRows(c1,n1,c2,n2,c3,n3,name) {
var sas = SpreadsheetApp.openById("Host Spreadsheet Id")
SpreadsheetApp.setActiveSpreadsheet(sas)
var ss=SpreadsheetApp.getActiveSpreadsheet()
var sh=ss.getSheetByName(name);
var rg=sh.getDataRange();
var vA=rg.getValues();
var rA=[];
for(var i=0;i<vA.length;i++) {
if(vA[i][c1-1]==n1 && vA[i][c2-1]==n2 && vA[i][c3-1]==n3) {
rA.push(i+1);
}
}
return rA
}
function timeIn() {
var row = getCurrentRow()
var LocationA = getValue('ACTIVE!A' + row)
var LocationB = getValue('ACTIVE!B' + row)
var passiveRow = findRows(1,LocationA,2,LocationB,5,"",'PASSIVE');
Logger.log(passiveRow);
setValue('PASSIVE!E' + passiveRow, new Date().toLocaleString());
}
function places() {
var row = getCurrentRow()
addRecord(getValue('A' + row), getValue('B' + row), getValue('C' + row), new Date().toLocaleString());
}
function onEdit(e) {
var row = getCurrentRow()
var Location = getValue('ACTIVE!C' + row)
var LocationA = getValue('ACTIVE!A' + row)
var LocationB = getValue('ACTIVE!B' + row)
var passiveRow = findRows(1,LocationA,2,LocationB,5,"",'PASSIVE');
Logger.log(row)
Logger.log(Location)
Logger.log(LocationA)
Logger.log(LocationB)
Logger.log(passiveRow)
if(SpreadsheetApp.getActiveSheet().getName() !== "ACTIVE") return;
if(Location !== 'HOME' && Location !== "" && passiveRow == "") {
places();
Logger.log(passiveRow)
}
else if(Location !== 'HOME' && Location !== "" && passiveRow !== "") {
timeIn();
places();
Logger.log(passiveRow)
}
else if(Location === 'HOME' && passiveRow !== "") {
timeIn();
Logger.log(passiveRow)
}
}
So this was my attempt at turning what used to be 2 sheets in the same spreadsheet into 2 different spreadsheets. However, the onEdit(e) function doesn't work. If I manually run the timeIn() function, it works and the places() function works as well. Even goes and finds the last row of the spreadsheet it is importing information into. But alas, when they edit Column C, the column that is suppose to activate the onEdit(e) if else to work, it doesn't run the functions. What am I missing?
Let me give some visuals of what I'm trying to do.
Here is the sheet the clients should see. Column C has drop down boxes in each row full of locations.
First | Last | LOCATIONS
=================================
James | Carter | HOME
Kyle | Johnson | MALL
Micheal | Wilson | BANK
Sarah | Smith | HOME
Tray | Tin | CLINIC
John | Becks | HOME
Here would be the sheet collected by the Host Spreadsheet
First | Last | LOCATION | OUT | IN
=====================================================
Tray | Tin | CLINIC | 10:00 |
James | Carter | MALL | 12:30 | 1:30
Kyle | Johnson | MALL | 12:45 |
Micheal | Wilson | BANK | 01:00 |
James | Carter | POOL | 01:30 | 2:00
I hope this is a good enough visual to get the point across as to what I'm trying to accomplish. The code did work on the same spreadsheet, but now I'm trying to make it work on two in order to prevent the Client from messing with the Collected information. Because if something like this happens :
First | Last | LOCATION | OUT | IN
=====================================================
Tray | Tin | CLINIC | 10:00 |
James | Carter | MALL | 12:30 | 1:30
Kyle | Johnson | MALL | 12:45 |
Micheal | Wilson | BANK | 01:00 |
James | Carter | POOL | 01:30 | 2:00
Sarah | Smith | POOL | 01:30 |
Sarah | Smith | POOL | 01:31 |
You see Sarah Smith is on twice with no Sign in. This causes a bug because of how I get passive rows, it now sees 2 rows where "IN" is empty, so the function findRows() it gives me 2 values where I can only have 1 value in the function setValue() inside the timeIn() function.