I'm working on a javascript project that will list everything from user name, email, phone number, and notes from AD into into a spreadsheet, so far i have two functions. however anything i put after getExternalID(users[i].externalIds ||[] does not output any information. here's a snippet of what i have. I'm a bit of a novice, would this be due to how I formatted the script?
function writeToSpreadsheet(){
var values = [];
var users = AdminDirectory.Users.list({domain:'domain'}).users;
for (var i=0; i<users.length; i++){
values.push([users[i].name.fullName, getExternalID(users[i].externalIds ||[], getPhones(users[i].phones ||[], ))]); // accounts for blank data
}
var spreadsheetUrl = 'https://docslink';
SpreadsheetApp.openByUrl(spreadsheetUrl).getSheets()[0].getRange(1, 1, values.length, values[0].length).setValues(values);
}
function getExternalID(arr) {
return arr.map(function(ExternalIDsObj) {
return ExternalIDsObj.value;
}).join(', ') + ('#differentemail') //takes employeeID and adds Email Address
}
function getPhones(arr) {
return arr.map(function(phoneObj) {
return phoneObj.value;
}).join(', ')
}
Since you are aiming to output the data from Active Directory into a spreadsheet, you need to use Sheets API to do that after populating the values array.
Also, for best practice the two get functions are separated from the main function.
Edit: There must be a typo on your closing parenthesis on your getExternalID() call. It's only supposed to get one argument.
Sample Code:
function writeToSpreadsheet(){
var values = [];
var users = AdminDirectory.Users.list({domain:'domain'}).users;
for (var i=0; i<users.length; i++){
values.push([users[i].name.fullName, getExternalID(users[i].externalIds || []), getPhones(users[i].phones || [])]);
}
var sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange(1,1,values.length,values[0].length).setValues(values);
}
function getExternalID(arr) {
return arr.map(function(ExternalIDsObj) {
return ExternalIDsObj.value;
}).join(', ') + ('#differentemail.com');
}
function getPhones(arr) {
return arr.map(function(phoneObj) {
return phoneObj.value;
}).join(', ');
}
Sample Output: (note that these "accounts" do not have external ID information in Admin Console)
Related
I made files with table. On those table there is some datas that are changing according to each files. So I created a file where there is a table that sum all of the others tables that are in others files.
First i made my code that sum tables of two files :
function juneFunction() {
var sheet_origin1=SpreadsheetApp
.openById("ID1")
.getSheetByName("Calcul/Machine");
var sheet_origin2=SpreadsheetApp
.openById("ID2")
.getSheetByName("Calcul/Machine");
var sheet_destination=SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
//define range as a string in A1 notation
var range1="BU4:CF71"
var values1=sheet_origin1.getRange(range1).getValues();
var values2=sheet_origin2.getRange(range1).getValues();
var destinationrange=sheet_destination.getRange("C3:N70");
//Here you sum the values of equivalent cells from different sheets
for(var i=0; i<values1.length;i++)
{
for(var j=0; j<values1[0].length;j++)
{
sum=values1[i][j]+values2[i][j];
destinationrange.getCell(i+1,j+1).setValue(sum);
}
}
}
This code is working perfectly.
But my goal is to sum 26 tables of 26 files.
So I tried to do it with 3 files, here is the code :
function juneFunction() {
var sheet_origin1=SpreadsheetApp
.openById("ID1")
.getSheetByName("Calcul/Machine");
var sheet_origin2=SpreadsheetApp
.openById("ID2")
.getSheetByName("Calcul/Machine");
var sheet_origin3=SpreadsheetApp
.openById("ID3")
.getSheetByName("Calcul/Machine");
var sheet_destination=SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
//define range as a string in A1 notation
var range1="BU4:CF71"
var values1=sheet_origin1.getRange(range1).getValues();
var values2=sheet_origin2.getRange(range1).getValues();
var values3=sheet_origin3.getRange(range1).getValues();
var destinationrange=sheet_destination.getRange("C3:N70");
//Here you sum the values of equivalent cells from different sheets
for(var i=0; i<values1.length;i++)
{
for(var j=0; j<values1[0].length;j++)
{
for(var k=0; k<values1[0].length;k++){
sum=values1[i][j][k]+values2[i][j][k]+values3[i][j][k];
destinationrange.getCell[[i+1,j+1,k+1]].setValues(sum);
}
}
}
}
Here I have this error : TypeError : Cannot read properly 'setValues' of undefined. This error happen here :
destinationrange.getCell[[i+1,j+1,k+1]].setValues(sum);
I think that the error is coming from the .getCell but i dont know why ...
Range.getCell() takes two arguments: the row offset and the column offset. You are handing it just one argument: an array of three numbers.
You should not be using Range.getCell() in the first place. Instead, use Array.map() to get the sheets, Array.forEach() to iterate over them, and finally Range.setValues() to write all results in one go, like this:
function sumRangeAcrossManySpreadsheets() {
const spreadSheetIds = ['id1', 'id2', 'id3',];
const sheetName = 'Calcul/Machine';
const rangeA1 = 'BU4:CF71';
const targetRange = SpreadsheetApp.getActiveSheet().getRange('C3');
const sheets = spreadSheetIds.map(id => SpreadsheetApp.openById(id).getSheetByName(sheetName));
const result = [];
sheets.forEach((sheet, sheetIndex) => {
if (!sheet) {
throw new Error(`There is no sheet ${sheetName} in spreadsheet ID '${spreadSheetIds[sheetIndex]}'.`);
}
const values = sheet.getRange(rangeA1).getValues();
values.forEach((row, rowIndex) => row.forEach((value, columnIndex) => {
if (!result[rowIndex]) {
result[rowIndex] = new Array(row.length).fill(0);
}
result[rowIndex][columnIndex] += Number(value) || 0;
}));
});
targetRange.offset(0, 0, result.length, result[0].length).setValues(result);
}
Here is my current script and attached sheet.
I have been able to successfully find the index value with function getColumnIndex(label) and then return that function into function getColumnValues(index) to pull all the rows in that specific column. I can't seem to use the input field from the autocomplete question id="courseCode" Enter Course Code as the search string to be used in the function getExpectations(); to populate the HTML page question id="expectations" as a multi selection question.
It works if I manually add the search string text to return the column rows. I would like to take the first 4 characters of the input field id="courseCode" Enter Course Code (3 letter followed by a number) as the search string to determine what selection options will populate the id="expectations" question.
I am a bit confused with calling back functions within another function and when and how to use a parameter/condition to pass through the function.
I hope this is enough information to solve my script error. Thanks in advance for this concern. Take care.
Added the following lines of code to get all options selected in the multi-selection Course Expectations question.
function collectForm(){
var submission = {};
// gets you the values for all id="specific_names"
submission.grade = document.getElementById("grade").value;
submission.courseCode = document.getElementById("courseCode").value;
var list = document.getElementsByClassName('selectedExpectations');
var selection = ' ';
for (i = 0; i < list.length; i++){
if (list[i].checked === true) {
selection += list[i].value + ", ";
}
}
submission.expectations = selection;
google.script.run.userClicked(submission);
}
In short
You need something like this
/**
*
* #param {string} code
*/
function getExpectations2(code) {
var patt = new RegExp(code.slice(0, 5), 'i');
var data = SpreadsheetApp.openById(
'1evNXXgFITrdNwsSdGXmprgzti74AQy03dg0igP5nT0I'
)
.getSheetByName('expectations')
.getDataRange()
.getValues();
var colIndex = data[0].reduce(function(p, c, i) {
return patt.test(c) ? i : p;
}, -1);
return colIndex === -1
? []
: data
.slice(1)
.filter(function(row) {
return row[colIndex] !== '';
})
.map(function(row) {
return row[colIndex];
});
}
getExpectations2 - returns a column by code as a list.
Also you have to update your listExpectations
function listExpectations(listLabels) {
console.log(listLabels);
const elm = document.getElementById('expectations');
const label = listLabels
.map(row => `<option value="${row}">${row}</option>`)
.join('');
elm.innerHTML =
'<option disabled selected>Select expectations not met</option>' + label;
setTimeout(() => M.FormSelect.init(elm), 0);
}
Of course you need bind all of them
function populateCodes(codes) {
var autocomplete = document.getElementById('courseCode');
var instances = M.Autocomplete.init(autocomplete, {
data: codes,
onAutocomplete: onAutocompleteCourseCode,
});
}
Where onAutocompleteCourseCode is
const onAutocompleteCourseCode = courseCode => {
google.script.run
.withSuccessHandler(listExpectations)
.getExpectations2(courseCode);
};
I am trying to build a search function where the search is inclusive of all the words in the search. For example if I had 3 items:
The Red House
The Red Car
Home Garage for your car
In the search box (Which currently is dynamic as you type), if you type Red you would get the the first two above. If I type Red, Car, I should only get line two, The Red Car, because that's the only one that has BOTH red and car in it.
I tried building an array of the search items then I have this for the search box:
$scope.search = function(item) {
var str = $scope.searchText;
if(!str || str === undefined) {
return true;
}
if (!item || item === undefined) return false;
var arr = str.toString().split(' ');
console.log('ARRAY - ' + arr);
var found = true;
arr.forEach(function(element) {
console.log ( "Element " + element.toString()) ;
if(item.tags.toString().toLowerCase().indexOf(element.toLowerCase()) >= 0 ) {
console.log ("Tags " + item.tags.toString()) ;
found = true;
return true;
}
found = false;
return false;
});
return found;
Right now with that code it's only giving me the results to the last word in my search... And if I delete and add words it doesn't seem to respond correctly.
Not sure how far off I am on this.
You should use Array.prototype.every to check that every word passes the test. You may also extract the lowercase tags before looping, so that it's not done on every loop unnecessarily:
const lowerTags = item.tags.toString().toLowerCase();
const arr = str.toString().split(' ');
const haveAll = arr.every(wordToFind => lowerTags.includes(wordToFind.toLowerCase()));
return haveAll;
If str is already a string, there's no need to call toString() on it (and similarly for item.tags) - might make the code more readable.
The following script contains an array of your searchable strings. When the function Search is called, it accepts a string as an argument. That string is split into array elements for the various words in the string. Then the function loops through each search term and compares it to each searchable item. If the item and term match, the term is pushed into a results array, which the function will return for you to use as you see fit.
<script>
searchableItems = [
"The Red House",
"The Red Car",
"Home Garage For Your Car"
];
Search("Red");
function Search(searchTerm){
results = [];
searchWords = searchTerm.split();
i = 0;
while(i < searchWords.length){
j = 0;
while(j < searchableItems.length){
if(searchableItems[j].indexOf(searchWords[i]) > -1){
results.push(searchableItems[j]);
}
j++;
}
i++;
}
return results;
}
</script>
I'm new to Google Apps Script and I'm trying to make a script for a spreadsheet where I'll store all the email addresses found by .getFrom() method in the sheet and ignore the same email addresses so that I get only one email address instead of multiple times. So far storing is working successfully but ignoring same emails is not working. I get same emails multiple times in my sheet's column.
Here's my code:
var n=threads.length;
var messages=thread.getMessages();
var getfrom = 0;
var allMails = [];
for (var i=0; i<n; i++)
{
for (var j=0; j<messages.length; j++)
{
var message=messages[j];
getfrom = message.getFrom();
var first_name = getfrom.substring(0, getfrom.indexOf(" "));
var last_name = getfrom.substring(getfrom.indexOf(" ")+1, getfrom.indexOf(" <"));
var email_address = 0;
if (first_name == '' && last_name == '')
{
email_address = getfrom;
} else {
email_address = getfrom.substring(getfrom.indexOf("<")+1, getfrom.indexOf(">"));
}
// This is how I check if I already have the email address or not
if (email_address == my_email || email_address[j] == email_address[j-1])
{
continue;
}
}
allMails.push([email_address]);
}
Logger.log(allMails);
sheet1.getRange(2, 3, n, 1).setValues(allMails);
Browser.msgBox("Operation complete");
How can I ignore duplicate values and get one email address instead of multiple times?
You can either ensure uniqueness before adding emails to the list, or build the full list first and remove duplicates later.
Option 1: Pre-filter
This example builds a one-dimensional array of addresses; because it's a simple array we can use the JavaScript built-in .indexOf() method to check for uniqueness. After all threads have been examined, the simple array is converted to a two-dimensional array for storage in the spreadsheet, using another Array built-in, map(). Before that though, the array gets sorted - just because we can. You might want to do other filtering, such as removing "no-reply" addresses.
function getUniqueFromAddresses1() {
var my_email = Session.getActiveUser().getEmail();
var threads = GmailApp.getInboxThreads();
var n=threads.length;
var allMails = [];
for (var i=0; i<n; i++)
{
var thread = threads[i];
var messages=thread.getMessages();
for (var j=0; j<messages.length; j++)
{
var message=messages[j];
var getfrom = message.getFrom();
// Use RegEx to extract just email address
var email_address = getfrom.match(/[^<> ]*\#[^> ]*/)[0];
// Skip messages I sent or addresses already collected
var index = allMails.indexOf(email_address);
if (email_address !== my_email && allMails.indexOf(email_address) == -1) {
allMails.push(email_address);
}
}
}
// Could do further filtering & sorting of allEmails here
allMails = allMails.sort()
Logger.log(JSON.stringify(allMails));
// convert allMails array to two-dimensional array
allMails = allMails.map( function(item){
return [item];
});
Logger.log(JSON.stringify(allMails));
// Store in spreadsheet; use dimensions of array to avoid mismatching range size
sheet1.getRange(2, 3, allMails.length, allMails[0].length).setValues(allMails);
debugger; // Pause in debugger
Browser.msgBox("Operation complete");
}
Option 2: Post-filter
Here's the alternate approach, removing duplicates after the array is built. The JavaScript magic here was lifted from this answer. We still use a one-dimensional array to collect and filter addresses. There's also an extra step required to remove our own address from the list.
Performance: This should be faster than approach 1, as there will be fewer comparisons required. HOWEVER, the bulk of the time used in the whole operation is tied up in accessing messages, so time savings in native JavaScript are negligible.
function getUniqueFromAddresses2() {
var my_email = Session.getActiveUser().getEmail();
var threads = GmailApp.getInboxThreads();
var n=threads.length;
var allMails = [];
for (var i=0; i<n; i++)
{
var thread = threads[i];
var messages=thread.getMessages();
for (var j=0; j<messages.length; j++)
{
var message=messages[j];
var getfrom = message.getFrom();
// Use RegEx to extract just email address
var email_address = getfrom.match(/[^<> ]*\#[^> ]*/)[0];
// Save the address
allMails.push(email_address);
// Skip messages I sent or addresses already collected
var index = allMails.indexOf(email_address);
if (email_address !== my_email && allMails.indexOf(email_address) == -1) {
allMails.push(email_address);
}
}
}
// Remove duplicates - https://stackoverflow.com/a/32533637/1677912
allMails = allMails.sort().reduce(function(a, b){ if (b != a[0]) a.unshift(b); return a }, []);
// Remove my address
if ((mine=allMails.indexOf(my_email)) > -1) allMails.splice(mine,1);
// Could do further filtering & sorting of allEmails here
allMails = allMails.sort()
Logger.log(JSON.stringify(allMails));
// convert allMails array to two-dimensional array
allMails = allMails.map( function(item){ return [item]; });
Logger.log(JSON.stringify(allMails));
sheet1.getRange(2, 3, n, 1).setValues(allMails);
debugger; // Pause in debugger
Browser.msgBox("Operation complete");
}
How did you get the email addresses?
The original function took several steps to identify an email address in the string returned by message.getFrom(). It's tricky, because that string can contain just an email address, or a name and an address. The operation can be simplified by using a regular expression to match just the email address, and ignore whatever other text is in the string.
// Use RegEx to extract just email address
var email_address = getfrom.match(/[^<> ]*\#[^> ]*/)[0];
The expression looks for # and the text immediately before and after it, bordered by a space or angle braces. You can try this out in an online demo.
/[^<> ]*\#[^> ]*/
[^<> ]* match a single character not present in the list below
Quantifier: * Between zero and unlimited times, as many times as possible, giving back as needed [greedy]
<> a single character in the list "<> " literally (case sensitive)
\# matches the character # literally
You need to cross check your allMails array for a given email address to ensure it's not in the list, however you can't easily check against allMails directly because it is a two-dimensional array.
I would add a single dimensional array purely for the purpose of cross-checking.
var n=threads.length;
var messages=thread.getMessages();
var getfrom = 0;
var allMails = [];
var cross_check = [];
for (var i=0; i<n; i++)
{
for (var j=0; j<messages.length; j++)
{
var message=messages[j];
getfrom = message.getFrom();
var first_name = getfrom.substring(0, getfrom.indexOf(" "));
var last_name = getfrom.substring(getfrom.indexOf(" ")+1, getfrom.indexOf(" <"));
var email_address = 0;
if (first_name == '' && last_name == '')
{
email_address = getfrom;
} else {
email_address = getfrom.substring(getfrom.indexOf("<")+1, getfrom.indexOf(">"));
}
if(email_address != my_email && cross_check.indexOf(email_address) == -1){
cross_check.push(email_address);
allMails.push([email_address]);
}
}
}
Logger.log(allMails);
sheet1.getRange(2, 3, n, 1).setValues(allMails);
Browser.msgBox("Operation complete");
See the documentation for the indexOf function, which explains why we check against -1, here:
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
Also check the Gmail Extractor - it saves the email addresses from Gmail in a Google spreadsheet.
I've been trying to work with Trello and the Google Apps Script this week. I am trying to create an array of hashes that I can then use to load the spreadsheet. Google apps script doesn't like the typical javascript code of creating hashes. I've looked up the docs but they don't have anything like hashes...they say to:
var object = [];
var object1 = {};
object.push(object1);
This wont work because I'm essentially trying to do something like:
var hash={name: , label: };
var n= someNumber;
var l= someLabel
var hash.push(name: n, label: l);
Essentially that is the code I have right now. But here is my entire function:
function getData(){
var list={};
//get the list of delivered cards from Trello
var listRequest = authorizeToTrello(); // get authorization
var result = UrlFetchApp.fetch("https://trello.com/1/lists/4fea3a2c3a7038911ebff2d8/cards",
listRequest);//fetch list
var listOfCards = Utilities.jsonParse(result.getContentText());//Google app utility format json
//outer loop to iterate through list of Cards
for(var i=0; i < listOfCards.length; i++){
var cardId = listOfCards[i].id; //get the id of a single card
var l = listOfCards[i]["label"]; //get the label for the our structure
//get a json object for a single card within the list of cards iteration
var cardRequest = authorizeToTrello();
var getCard = UrlFetchApp.fetch("https://trello.com/1/cards/" + cardId + "/actions", cardRequest);
var singleCard = Utilities.jsonParse(getCard.getContentText());
//inner loop to iterate the single cards JSON objects
for(var j=0; j < singleCard.length; j++) {
if(singleCard[j].data != undefined && singleCard[j].data.listAfter != undefined)
{
var str = singleCard[j]["data"]["listAfter"]['name'];
if(str === "Delivered Q3 2012"){
var n = singleCard[j]['memberCreator']['fullName'];
}
}
}
//push the data to list
list.push(n,l);
}
return name, label; //return list for output
}
Reading the question, I understood that the author needs to know how to create an associative array in a GAS. If it is correct then here is a couple of links (here and here) and a sample code is bellow.
function testMap() {
var map = {};
map["name1"] = "value1";
map["name2"] = "value2";
return map;
}
If the author needs really
an array of hashes
then there are a couple of ways depending on which hash algorithm is required.
to use the Utilities.computeDigest method to calculate a hash of a string using one of available algorithms.
if the required hash calculation algorithm is not supported by the Utilities.computeDigest, then is possible to write own implementation as it is done for the BLAKE function.
Here is a sample of how to create an array of hashes using the MD5 hash.
function testHash() {
var array = [];
array.push(Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, "value1"));
array.push(Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, "value2"));
return array;
}
P.S. The return line of the author code return name, label; //return list for output
is not correct - only the label variable value is returned. To return a couple of variables as an array is necessary to write return [name, label];. Or may be the author needs to return the list variable and not name and label.
I know this is an old post / question, but i would like to update my answer since the original anwer (1st answer) is misleading. I was myself looking for how to return associative arrays back to a cell in the spreadsheet, but alas.. "YOU CANNOT". Google spreadsheet MUST want an numerically indexed array or an object. Otherwise it returns "#ERROR".
Here are the steps to replicate the issue.
function testMap() {
var map = {};
map["name1"] = "value1";
map["name2"] = "value2";
return map
Formula in your cell: =testMap()
Value in your cell: Thinking... #ERROR
Solution (rather a workaround)
1: Transfer your objects from your associative array into a numerically indexed array using for-each type loop.
var temp = new Array();
for (var i in map) {
temp.push([i,map[i]])
// optionally use activeSheet.getRange(X:X).setValue([i,map[i]])) function here.
// set values will not work in cell functions. To use it via cell functions, rerun / trigger the functions using an on_edit event.
}
If you used a temp like numerically indexed array, you can return "temp" back to the calling cell.
Summary: For onEdit() purposes, use Cache Service to define associative array data.
Here's a shared Gsheet demonstrating this curious behavior. I tried the following solution in programmatically defining an associative array based on data in a Google sheet.
var assocArr = {
labels: {},
init: function () {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('sheetName');
var values = sheet.getDataRange().getValues();
for(var row in values) {
assocArr.labels[values[row][0]] = values[row][1];
};
for(var key in assocArr.labels) {
Logger.log("key: %s, value: %s",key, assocArr.labels[key]);
};
return(void(0));
},
};
To execute this, you run the init() method in the onOpen() event handler.
function onOpen() {
assocArr.init();
var key = 'test';
SpreadsheetApp.getUi().alert( assocArr.labels[key] );
Logger.log("onOpen: key: %s, value: %s",key, assocArr.labels[key]);
};
The logger message confirms that init() loads the data from the worksheet.
Now if I try to reference this assocArr object in onEdit() it returns undefined for all key values.
function onEdit(event) {
var key = 'test';
SpreadsheetApp.getUi().alert( assocArr.labels[key] );
Logger.log("onEdit: key: %s, value: %s",key, assocArr.labels[key]);
};
I infer that for security reasons, Google limited the simple-trigger onEdit() to not have global variable scope, same as they voided the utility of the event.user property.
Now instead if I simply put the key-value pair in the cache, it works! Here is the complete code that works using the Cache Service.
var cache = CacheService.getPrivateCache();
var assocArr = {
init: function () {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Account Labels');
var values = sheet.getDataRange().getValues();
for(var row in values) {
cache.put(values[row][0], values[row][1], 3600);
};
return(void(0));
},
};
function onOpen() {
assocArr.init();
var key = 'test';
SpreadsheetApp.getUi().alert( cache.get(key) );
Logger.log("onOpen: key: %s, value: %s",key, cache.get(key));
};
function onEdit(event) {
var key = 'test';
SpreadsheetApp.getUi().alert( cache.get(key) );
Logger.log("onEdit: key: %s, value: %s",key, cache.get(key));
};
Curiously, the onEdit() has the cache variable in its scope.
Here again is the shared Gsheet demonstrating this curious behavior.
I found this really quick way that is not listed
Create a json object (array style)
var myArray = {
1:{"id": "inprogress","title" : "in Progress"},
2:{"id": "notstarted","title" : "Not Started"},
3:{"id": "completed" ,"title" : "Completed"}
};
read the json
// get the lenght of the json object
var jsonSize = Object.keys(myArray).length;
// use this in a loop
for (var i = 1; i < Object.keys(jsonSize).length; i++) {
var title = myArray[i].title;
}
Works like a charm for me