Based on this post, I'm working on a proof of concept to capture an item request via Google form, email it for approval and the approval result posted back to the corresponding row in Google sheet. The row is searched from an array, using the timestamp as key.
I faced 2 challenges: First, the timestamp in the array is in a different format and sometimes differed by 1 second. I tweaked this by adjusting the format to match how the array values look like and run a 2nd search if the first timestamp search fails. I hope this is foolproof but let me know if there is a better way. I couldn't figure out why there is a 1-second difference sometimes.
I got stuck on the second challenge: I'm unable to search successfully the timestamp in the array at all. indexOf() always returns a value of -1.
Will appreciate any help.
Please be detailed if needed, I'm a newbie.
Here's my code:
function sendEmail(e) {
// Response columns: Timestamp Requester Email Item Cost
var email = e.namedValues["Requester Email"];
var item = e.namedValues["Item"];
var cost = e.namedValues["Cost"];
var timestamp = e.namedValues["Timestamp"];
var url = ScriptApp.getService().getUrl();
// Enhancement: include timestamp to coordinate response
var options = '?approval=%APPROVE%×tamp=%TIMESTAMP%&reply=%EMAIL%'
.replace("%TIMESTAMP%",encodeURIComponent(e.namedValues["Timestamp"]))
.replace("%EMAIL%",e.namedValues["Requester Email"])
var approve = url+options.replace("%APPROVE%","Approved");
var reject = url+options.replace("%APPROVE%","Rejected");
var html = "<body>"+
"<h2>Please review</h2><br />"+
"Request from: " + email + "<br />"+
"For: "+item +", at a cost of: $" + cost + "<br /><br />"+
"Approve<br />"+
"Reject<br />"+
"</body>";
MailApp.sendEmail(Session.getEffectiveUser().getEmail(),
"Approval Request",
"Requires html",
{htmlBody: html});
}
function doGet(e) {
var answer = (e.parameter.approval === 'Approved') ? 'Buy it!' : 'Not this time, Keep saving';
var timestamp = e.parameter.timestamp;
var newtimestamp = Utilities.formatDate(new Date(timestamp), "GMT+8", "EEE MMM dd yyyy HH:mm:ss 'GMT+0800 (SGT)'"); //reformat timestamp to match the ones in the data array
var approvalCol = 5;
MailApp.sendEmail(e.parameter.reply, "Purchase Request",
"Your manager said: "+ answer);
// Update approval status back to the sheet
var wsID = "<myworksheetID>";
var sheet = SpreadsheetApp.openById(wsID).getSheetByName("Requests");
var data = sheet.getDataRange().getValues();
var oneColArray = new Array();
for(i=0;i<data.length;++i){
oneColArray.push(data[i][0]); // taking index 0 means I'll get column A of each row and put it in the new array
}
var row = oneColArray.indexOf(newtimestamp);
Logger.log("\ntimestamp: " + timestamp + "\n\n" + "newtimestamp: " + newtimestamp + "\n\n" + "oneColArray: \n" + oneColArray);
if (row < 0) { //not found
//Lower timestamp by 1 second. Sometimes there is a 1-second difference than the one in the array. I don't know why
var dateString = Utilities.formatDate(new Date(timestamp), "GMT+8",'EEE, d MMM yyyy HH:mm:ss');
var date = new Date(dateString);
var revisedtimestamp = new Date((date.getTime() - (1/(24*60*60)*1000)));
// now search again using the adjusted timestamp
var row = oneColArray.indexOf(revisedtimestamp);
if (row < 0) {
Logger.log("\ntimestamp: " + timestamp + "\n\n" + "newtimestamp: " + newtimestamp + "\n\n" + "oneColArray: \n" + oneColArray + "\n\n" + "revisedtimestamp: " + revisedtimestamp);
throw new Error ("Request not found in list.");
} else {
sheet.getRange(row + 1, approvalCol).setValue(e.parameter.approval);
}
} else {
sheet.getRange(row + 1, approvalCol).setValue(e.parameter.approval);
}
}
I found a solution based on this post.
I changed this line:
var row = oneColArray.indexOf(newtimestamp);
to this to convert the array into string, and indexOf() returns the starting point where it finds the first occurrence of the timestamp. From the return value I just calculated what row it is in the worksheet:
var row = oneColArray.join().indexOf(newtimestamp);
Another variation which I preferred more is looping through the data array's timestamp column, in each loop the value is converted into string before doing indexOf(). I just need to add 1 to [i] to get the actual row in the worksheet.
for (var i = 0; i < data.length; ++i) {
var row = data[i][0].toString().indexOf(newtimestamp);
if (row > -1) {
var a = i;
break;
}
}
Related
I am quite new to Google Script, I'm learning on the job.
I have a range of data as a variable. It's only one column, column F in this case, but there are empty cells between values. I have a working script (got it from here earlier), which only loops through the cells with values in them. So lets say value1 is in F5, value2 is in F13, it's all random and always changing.
I'm trying to get the row number for these values, so that script should give back "5" for value1 and "13" for value2, ideally together with the value itself.
So far, that's what I have and I can not seem to progress further.
var sourceID = "sourceID";
var main = SpreadsheetApp.openById("mainID");
var mainsheet = main.getSheetByName("Lab Data");
var sourcesheet = source.getSheetByName("sheet name");
var dataRange = sourcesheet.getDataRange(); // range full sheet
var values = dataRange.getValues(); // values full sheet
var SWrowss = findCellForSW(); // getting start row from another function
var CQrowss = findCellForCQ(); // getting last row from another function
var noRows = CQrowss - SWrowss; // gets number of rows in range
var colss = sourcesheet.getRange(SWrowss,6,noRows,1).getValues(); // range we need, column F
// get rid of empty cells from range - copied script from stack overflow
var cResult = colss.reduce(function(ar, e) {
if (e[0]) ar.push(e[0])
return ar;
}, []);
Logger.log("cResult: " + cResult); // cResult contains all sub headers - no empty cells
// gets element's position in array
for(var b = 0; b < cResult.length; b++){
var position = b+1;
Logger.log("pos " + position);
} // end for
If you want to know the row number, I would propose you a different approach
Just loop through your values and retrieve the position of the ones that are not empty:
...
var colss = sourcesheet.getRange(SWrowss,6,noRows,1).getValues();
var rows = [];
var calues = [];
for(var b = 0; b < colss.length; b++){
if(colss[b][0] != "" && colss[b][0] != " "){
var row = SWrowss+b+1;
rows.push(row);
var value = colss[b][0];
values.push(value);
}
}
...
With the other solution you can build a single object that can do the conversion for you very quickly.
var colss = sourcesheet.getRange(SWrowss,6,noRows,1).getValues();
var rvObj={};
for(var b = 0; b < colss.length; b++){
if(colss[b][0] != "" && colss[b][0] != " "){
rvObj[colss[b][0]]=SWrowss+b+1;
}
}
With rvObj now you can get any row with var row = rvObj[value];
function onEdit(evt) {
var range = evt.range;
showAutoValue(range);
}
function showAutoValue(range){
var spreadSheet = SpreadsheetApp.getActiveSpreadsheet();
var activeSheet = spreadSheet.getActiveSheet();
var row = range.getRow();
var column = range.getColumn();
if(column == 1 && row >= 1){
var Avalue = activeSheet.getRange("A" + row).getValue();
var B_Jrange = activeSheet.getRange("B" + row + ":" + "J" + row);
if(String(Avalue).trim()){
for(var i = 0; i < B_Jrange.length; i++){
if(String(B_Jrange[i].getValue()).trim().toLowerCase() == "change moi"){
B_Jrange[i].setBackground(Avalue);
}
}
}
}
}
Selecting and Highlighting Ranges with onEdit()
Okay I thought about this a bit more and I decided to make columnA a data validation list of white,yellow,lightgreen,lightblue because you must keep in mind that in order to generate the onEdit trigger you must actually change something. So in this case you're changing the background color of the range e.range.getSheet().getRange(e.range.rowStart,2,1,8) to the value that's selected in column A of that row. So no loop is required to accomplish this.
function onEdit(e){
var sh=e.range.getSheet();
if(e.range.columnStart==1){
if(String(sh.getRange(e.range.rowStart,1).getValue()).trim()){
sh.getRange(e.range.rowStart,2,1,8).setBackground(sh.getRange(e.range.rowStart,1).getValue());
}else{
sh.getRange(e.range.rowStart,2,e.range.rowEnd,8).setBackground('white').getValue();
}
}
}
This version also clears the entire range if you select all of column A and hit return it sets column A to no selection and the second part of the if/else statement clears background of the entire range.
I have created a script to combine information from several tabs to a "master elements" tab. The script pull in rows tagged with the "MS" designation and it works great. However, I also want to pull in the Sheetname, Sheet IT, and possiby a link to the source tab for each row with "MS". That information is currently not stored in any of the source tabs. Is that possible to do in script?
function combineData() {
var destination = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Masterelements');
// TO CLEAR DESTINATION TAB BEFORE REPOPULATION
destination.getRange('A2:g1000').clearContent();
//VARIABLE TO CYCLE THROUGH SPECIFIC SHEETS
var tabs = [
'A-000',
'B-123',
];
var ss = SpreadsheetApp.getActiveSpreadsheet();
for (var s = 0; s < tabs.length; s++) {
var sheet = ss.getSheetByName(tabs[s]);
Logger.log(sheet.getLastRow());
var range = sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn());
var destination = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Masterelements');
var values = range.getValues();
//Destination parameters - equivalent to destination.getRange();
var numberOfColumns = 7;
var numberOfRows = 1;
var startColumn = 1;
var startRow = 2;
var count = 0
// IDENTIFY THE FIRST ROW TO CONSOLIDATE ITEMS
var destRow = destination.getLastRow() + 1
for (var i = 0; i < values.length; i++) {
Logger.log("i is now: " + i);
Logger.log("Current row value: " + values[i][0])
if (values[i][0] == 'MS') {
Logger.log("*** Value found in cloumn: " + values[i][0] + "**** \n Count: " + i);
count++;
var rangeToCopy = sheet.getRange((i + 1), 1, numberOfRows, numberOfColumns);
var destinationRange = destination.getRange(destRow, startColumn, numberOfRows, numberOfColumns);
destRow++;
Logger.log("Next Destination row: " + destRow);
rangeToCopy.copyTo(destinationRange, SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
}
}
}
}
The OP supplied working code that copies rows, from two specific sheets, with a value of "MS" in Column A. However the OP also wanted:
Sheetname
Sheet ID
possiby a link to the source tab for each row with "MS"
These are fairly easy to produce, but require too much information to explain in a comment.
I've taken the OP original code. I moved and/or edited some lines to make the code slightly more efficient. I've also added some lines of code to insert the Sheet name in Masterelements:Column H, the sheet ID in Masterelements:Column I and a link to the relevant sheet in Masterelements:Column J.
I think there is scope to make the code more efficient (specifically within the loops), but this is something that the OP can consider if the "real data" execution time is unreasonable.
function so54432164() {
// setup the spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// get the spreadsheet URL - required for the hyperlink
var ssurl = ss.getUrl();
//Logger.log("DEBUG: The url for this spreadsheet is "+ssurl);//DEBUG
var destination = ss.getSheetByName('Masterelements');
// CLEAR DESTINATION TAB BEFORE REPOPULATION
// clear the entire destination sheet; this is more efficient that just clearing a nominated range
destination.clearContents();
// IDENTIFY THE first ROW TO CONSOLIDATE ITEMS
var destRow = destination.getLastRow() + 1
//VARIABLE TO CYCLE THROUGH SPECIFIC SHEETS
// note: no comma after the second tab name
var tabs = [
'A-000',
'B-123'
];
//Destination parameters
// note: brought this out of the "for" loop since only need to be declared once.
var numberOfColumns = 7;
var numberOfRows = 1;
var startColumn = 1;
var startRow = 2;
// used for destination.getRange();
// Loop through the sheets
for (var s = 0; s < tabs.length; s++) {
var sheet = ss.getSheetByName(tabs[s]);
// create variables to get the Sheet Name and the sheetID
var sheetname = sheet.getSheetName();
var sheetID = sheet.getSheetId();
//Logger.log("DEBUG: sheet: "+sheetname+", SheetID: "+sheetID);//DEBUG
// get the data: the var 'range' was never used, so we can just call the values direct to 'values'.
var values = sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn()).getValues();
//LOOP through the rows in the target sheet
for (var i = 0; i < values.length; i++) {
//Logger.log("DEBUG: row = " + i+", and Column A value: " + values[i][0]);//DEBUG
// test for "MS" in Column A
if (values[i][0] == 'MS') {
//Logger.log("DEBUG: Found MS, Count: " + i);//DEBUG
// define the range to copy
var rangeToCopy = sheet.getRange((i + 1), 1, numberOfRows, numberOfColumns);
// Logger.log("DEBUG: the rangetocopy is "+rangeToCopy.getA1Notation()); //DEBUG
// Define the destination range
var destinationRange = destination.getRange(destRow, startColumn, numberOfRows, numberOfColumns);
//Logger.log("DEBUG: the destination range "+destinationRange.getA1Notation());//DEBUG
// copy the source to the target
rangeToCopy.copyTo(destinationRange, SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
// create a variable for the hyperlink formula
var formula = '=HYPERLINK("' + ssurl + '#gid=' + sheetID + '")';
// Logger.log("DEBUG: the formula = "+formula);//DEBUG
// create a variable for the extra data: Sheet name, Sheet ID, and the hyperlink to the sheet.
var extradata = [
[sheetname, sheetID, formula]
];
//define the destination range and set the values
destination.getRange(destRow, numberOfColumns + 1, numberOfRows, 3).setValues(extradata);
// increment the destination row
destRow++;
}
}
}
}
I am working on a script that, in part, takes an array of names, compares each name to column A in a sheet, and returns with a row matched value in column B. (Like the vLookup command in sheets)
The setup
var ss = SpreadsheetApp.getActiveSpreadsheet();
var clientsSheet = ss.getSheetByName("Clients");
var cRow = clientsSheet.getLastRow();
var cColumn=clientsSheet.getLastColumn();
var cData=clientsSheet.getRange(1,1,cRow,cColumn).getValues(); //create array of client data
The trouble code
//put each client on their own row and add hour
for(i=0; i < client.length; ++i){
var cl = client[i]
//iterate over array of clients (column A) and hours (Column B) to find match and log the number in column B
for(j=0;j<cData.length;++j){
if (cData[j][0]==cl){
var hour = cData[j][1];
}
}
//return the matched values
Logger.log(cl+" - "+hour);
}
The var 'client' is an array that was split from a list of names in a single cell that are separated by commas (see whole code below)
At the moment it works great except that it misses the last element in the array.
for example:
if I have a sheet with two columns and three rows like so:
A 1
B 2
C 3
I would get back
A-1
B-2
C-
It is missing that last piece on the last element - it should be
A-1
B-2
C-3
I am stumped, and I know that it must be some simple little thing.
Any help would be amazing
Thanks!
The Code:
function logClients()
/*
Take data from a google form check box submissions. Check box submissions put all checked answers into a single cell separated by a comma. The function first takes the most recently submitted row, removes unneeded spaces, and splits each element into its own part of an array.
Then, the function compares each clients name in the array to a sheet with other info, such as the default number of hours we meet. It takes the clients name, the date of submission, and the hours, and logs them on a new row in two different sheets, the Hours sheet and the Trans Log sheet.
*/
var ss = SpreadsheetApp.getActiveSpreadsheet();
var logSheet = ss.getSheetByName("Log"); //Raw data from the Google form
var hourSheet = ss.getSheetByName("Hours"); //logged data for my records, separated into individual clients
var transLog = ss.getSheetByName("Trans Log"); // logged data minus "other" catagory
var clientsSheet = ss.getSheetByName("Clients"); //sheet containing all clients names and the typical hours we meet
var lRow = logSheet.getLastRow();
var hRow = hourSheet.getLastRow();
var tRow = transLog.getLastRow();
var cRow = clientsSheet.getLastRow();
var cColumn = clientsSheet.getLastColumn();
var cData = clientsSheet.getRange(1, 1, cRow, cColumn).getValues();
//get list of clients from cell and split it into an array
var Client = logSheet.getRange(lRow, 2).getValue().replace(", ", ","); //remove all spaces after a comma
var client = Client.split(",");
//get "other" information and do the same
var Other = logSheet.getRange(lRow, 5).getValue().replace(", ", ",");
var other = Other.split(",");
//check the date and set to today if nothing else has been entered
var dcell = logSheet.getRange(lRow, 4).getValue();
var date = new Date()
if (dcell == "") {} else if (dcell == "Yesterday") {
date = new Date(date.getTime() - 1 * (24 * 3600 * 1000));
} else {
date = dcell
}
var date = Utilities.formatDate(date, "GMT-8", "MM/dd/yy"); //format date
//put each client on their own row
for (i = 0; i < client.length; ++i) {
var cl = client[i]
var hour = logSheet.getRange(lRow, 3).getValue(); //hours
if (hour == !"") {
break;
}
for (j = 0; j < cData.length; ++j) {
if (cData[j][0] == cl) {
var hour = cData[j][1];
}
}
Logger.log(date + " - " + cl + " - " + hour);
hourSheet.appendRow([date,cl, hour]);
transLog.appendRow([date, cl, hour]);
}
//put each client on their own row
for (i = 0; i < other.length; i++) {
hourSheet.appendRow([date, other[i], getHour(client[i])]);
}
} //end of function
``
This is a code that I have been working on to teach myself Java and Apps-script
Yup, simple mistake.
Not all of the spaces before each name were being removed from the array, so the if statement would skip right over them because they were not a true match
I have performance issue on salesforce
I am trying to load table from salesforce into excel cvs, for that used, I tested the ApexDataLoader and found out that to load whole lead table for my organization take around 4~5 min, and have around 60,000 records.
Now I want to do the same with a C# code, for that I write this code:
var user = "xxx";
var password = "xxx";
var token = "xxx";
var sforceService = new SforceService();
var login = sforceService.login(user, String.Concat(password, token));
sforceService.Url = login.serverUrl;
sforceService.SessionHeaderValue = new SessionHeader { sessionId = login.sessionId };
var query = "The full query that I took from ApexDataLoder";
var startTime = DateTime.Now.TimeOfDay;
var firstTime = DateTime.Now.TimeOfDay;
var result = sforceService.query(query);
int i = 0;
while (!result.done)
{
var endTime = DateTime.Now.TimeOfDay;
Debug.Print(endTime.Subtract(startTime) + " " + i * result.records.Count() + " - " + (i + 1) * result.records.Count() +
" Time from start: " + endTime.Subtract(firstTime));
startTime = DateTime.Now.TimeOfDay;
result = sforceService.queryMore(result.queryLocator);
i++;
}
After few lines I saw that I read only 2000 lines in total (out of 60,000) in 2 miniuts.
That's mean that to get the whole table I will need 60 min.
Why there is so big difference between that ApexDataLoader (5 min) to my code? what I am doing wrong?
Thanks for the help!
I found 2 things that really improve performance,
one, set up the EnableDecompression to true
sforceService.EnableDecompression = true;
And the second is based on this thread, it is better to do a query "select Id from Lead" and collect all the id's and then do a multi thread processing to get the data with the retrieve function.
Hope that will help people and if someone have any other improvement tricks, please let me know