Adding Array to Custom Google Apps Script for Google Sheets - arrays

I have a bit of code to use a ZIP code to find the county and I want to be able to use the function as an array formula so that I can add it to a sheet for form responses.
I tried to look up how to make the function work with an array formula but I don't know enough to apply it.
Is it possible to make this code work as an array formula?
Sample sheet:
https://docs.google.com/spreadsheets/d/1S-TVchlE1sFTIOrbOL-v_N3LJJHLAHLJHkUtVpFeZok/edit#gid=0
/*
*
* Google Maps Formulas for Google Sheets
*
* Written by Amit Agarwal
*
* Web: https://www.labnol.org/google-maps-sheets-200817
*
*/
const md5 = (key = "") => {
const code = key.toLowerCase().replace(/\s/g, "");
return Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, key)
.map((char) => (char + 256).toString(16).slice(-2))
.join("");
};
const getCache = (key) => {
return CacheService.getDocumentCache().get(md5(key));
};
const setCache = (key, value) => {
const expirationInSeconds = 6 * 60 * 60; // max is 6 hours
CacheService.getDocumentCache().put(md5(key), value, expirationInSeconds);
};
/**
* Get the county name of an address on Google Maps.
*
* =GOOGLEMAPS_COUNTY("10 Hanover Square, NY")
*
* #param {String} address The address to lookup.
* #return {String} The county of the address.
* #customFunction
*/
const GOOGLEMAPS_COUNTY = (address) => {
if (!address) {
throw new Error("No address specified!");
}
if (address.map) {
return address.map("administrative_area_level_2");
}
const key = ["administrative_area_level_2", address].join(",");
const value = getCache(key);
if (value !== null) return value;
const { results: [data = null] = [] } = Maps.newGeocoder().geocode(address);
if (data === null) {
throw new Error("Address not found!");
}
const [{ long_name } = {}] = data.address_components.filter(
({ types: [level] }) => {
return level === "administrative_area_level_2";
}
);
if (!long_name) {
throw new Error("County not found!");
}
const answer = `${long_name}`;
setCache(key, answer);
return answer;
};

In your situation, the following sample script is your expected goal?
Sample script:
Please add the following script to your current script and save it.
const GOOGLEMAPS_COUNTY_ARRAY = values => values.map(r => r.map(c => c.toString() ? GOOGLEMAPS_COUNTY(c) : null));
When you use this script, please put a custom function of =GOOGLEMAPS_COUNTY_ARRAY(A2:A) to a cell.
Testing:
When your sample Spreadsheet is used, the following result is obtained.
Reference:
map()

Related

Run N/Task on multiple saved searches at once

Is there a straightforward way to modify the below code to execute on multiple saved searches and files simultaneously? I have all the file and search id's. but rather than creating 50 different scripts for each one can I just execute on a block of IDs by altering the below?
Thank you in advance.
All the Oracle documentation only seems to specify how to run these tasks on 1 item.
I know that a map/reduce script is a good path to go down for handling larger amounts of data, is that the way with this or can the below just be tweaked slightly?
/**
* #NApiVersion 2.x
* #NScriptType ScheduledScript
* #NModuleScope SameAccount
*/
define(['N/task'],
/**
* #param {record} record
* #param {search} search
*/
function(task) {
var FILE_ID = 433961;
var SEARCH_ID = 1610;
function execute(scriptContext) {
var searchTask = task.create({
taskType: task.TaskType.SEARCH
});
searchTask.savedSearchId = SEARCH_ID;
searchTask.fileId = FILE_ID;
var searchTaskId = searchTask.submit();
}
return {
execute: execute
};
});
/**
* #NApiVersion 2.x
* #NScriptType ScheduledScript
* #NModuleScope SameAccount
*/
define(['N/task'], function (task) {
const todos = [
{
FILE_ID: 433961,
SEARCH_ID: 1610
},
{
FILE_ID: '...',
SEARCH_ID: '...'
},
// ...
]
function execute(scriptContext) {
todos.forEach(function(todo) {
var searchTask = task.create({
taskType: task.TaskType.SEARCH
});
searchTask.savedSearchId = todo.SEARCH_ID;
searchTask.fileId = todo.FILE_ID;
var searchTaskId = searchTask.submit();
})
}
return {
execute: execute
};
});

Attach files to record in Netsuite

I am transferring attachments from Zoho to Netsuite. But facing problems while attaching it to opportunity or any other object. I have already uploaded the file to the file cabinet in netsuite and tried to bind it with the records notes. But that doesn't work. It only adds the note to the record but no sign of any file in the file option.
Thank you.
enter image description here
You would use the record.attach function. You would need the internal id of the file and of the transaction. In SS1 (using nlapiAttachRecord) it was important to list the file arguments first. The SS2 syntax makes that clearer:
record.attach({
record:{
type:'file',
id:fileid
},
to:{
type:'transaction',
id:transactionid
}
});
/**
* #NApiVersion 2.1
* #NScriptType MapReduceScript
* #NModuleScope SameAccount
*/
/**
* In this I am using Map Reduce script to process & attach multiple files from
* FileCabinet of NetSuite. So that it never goes out of governance.
/
define(['N/record','N/query'],
(record,query) => {
const getInputData = (getInputDataContext) => {
try
{
/**
* Query for getting transaction ID & other header detail of record.
*/
let transQuery = "SELECT custrecord_rf_tid as tid, custrecord_rf_fid as fid, id FROM customrecord_rflink where custrecord_rf_comp <> 'T' and custrecord_rf_type = 11";
let transQueryResult = runSuiteQuery(transQuery);
if(transQueryResult.length > 0){
log.debug("Count of record left to process--->", transQueryResult.length);
return transQueryResult;
}else{ //Incase where no transaction was left to transform.
log.debug({title: "No Remaining Transaction!"});
return 1;
}
}
catch (e)
{
log.error({title: "Error inside getinput data.", details: [e.message,e.stack]});
}
}
const map = (mapContext) => {
try{
let mapData = JSON.parse(mapContext.value);
log.debug({title: "mapData after parse", details: mapData});
let staginRecId = Number(mapData.id);
let fileId = Number(mapData.fid);
let billId = Number(mapData.tid);
let outputVal = attachfile('file',fileId, 'inventoryadjustment', billId);
let staginRec;
if(outputVal === true){
staginRec = record.submitFields({
type: 'customrecord_rflink',
id: staginRecId,
values: {
'custrecord_rf_comp': true
}
});
log.debug("record saved with id-->", staginRecId);
}else{
log.debug("record saving failed with id-->", staginRecId);
}
}
catch(e){
log.error({title: "Error in Map", details: [e.message,e.stack]});
}
}
const reduce = (reduceContext) => {
}
const summarize = (summarizeContext) => {
log.debug('Summarize completed');
}
function runSuiteQuery(queryString) {
log.debug("Query", queryString);
let resultSet = query.runSuiteQL({
query: queryString
});
log.debug("Query wise Data", resultSet.asMappedResults());
if(resultSet && resultSet.results && resultSet.results.length > 0) {
return resultSet.asMappedResults();
} else {
return [];
}
}
function attachfile(recType, recId, recTypeTo, recIdTo) {
record.attach({
record: {
type: recType,
id: recId
},
to: {
type: recTypeTo,
id: recIdTo
}
});
return true;
}
return {getInputData,map,reduce,summarize};
});

Pass an Array as a query String Parameter node.js

How can I pass an array as a query string parameter?
I've tried numerous ways including adding it to the path but i'm not able to pull the array on the back end.
If I hard code the array it works fine, but when I try to pass the array from my front end to the backend it does not work properly.
Can anyone point me in the right direction?
FrontEnd
function loadJob() {
return API.get("realtorPilot", "/myTable/ListJobs", {
'queryStringParameters': {
radius,
availableServices,
}
});
BackEnd
import * as dynamoDbLib from "./libs/dynamodb-lib";
import { success, failure } from "./libs/response-lib";
export async function main(event, context) {
const data = {
radius: event.queryStringParameters.radius,
availableServices: event.queryStringParameters.availableServices,
};
// These hold ExpressionAttributeValues
const zipcodes = {};
const services = {};
data.radius.forEach((zipcode, i) => {
zipcodes[`:zipcode${i}`] = zipcode;
});
data.availableServices.forEach((service, i) => {
services[`:services${i}`] = service;
});
// These hold FilterExpression attribute aliases
const zipcodex = Object.keys(zipcodes).toString();
const servicex = Object.keys(services).toString();
const params = {
TableName: "myTable",
IndexName: "zipCode-packageSelected-index",
FilterExpression: `zipCode IN (${zipcodex}) AND packageSelected IN (${servicex})`,
ExpressionAttributeValues : {...zipcodes, ...services},
};
try {
const result = await dynamoDbLib.call("scan", params);
// Return the matching list of items in response body
return success(result.Items);
} catch (e) {
return failure(e.message);
}
}
Pass a comma seperated string and split it in backend.
Example: https://example.com/apis/sample?radius=a,b,c,d&availableServices=x,y,z
And in the api defenition split the fields on comma.
const data = {
radius: event.queryStringParameters.radius.split(','),
availableServices: event.queryStringParameters.availableServices.split(',')
};

Protractor endurance test memory leak

I made a very small test using protractor to open my application (build on angular) and start clicking at random links in a random order. We use this to let the web application run for several days and check for memory leaks.
After some time the memory of the browser (chrome) is huge and when i create a memory dump i find 33k strings all containing the same piece of code (shown below).
I tried searching where this was coming from and it that led me to Webdriver and the code looks like something injected by webdriver to trigger the mouse click ability but from there i'm lost.
Am i doing something wrong or should i clean the memory somewhere in my test?
(When i run the application normally for multiple hours and opening all pages there is no occurrence at all of this string)
"(function() { // Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* Enum for WebDriver status codes.
* #enum {number}
*/
var StatusCode = { STALE_ELEMENT_REFERENCE: 10, JAVA_SCRIPT_ERROR: 17, };
/**
* Enum for node types.
* #enum {number}
*/
var NodeType = { ELEMENT: 1, DOCUMENT: 9, };
/**
* Dictionary key to use for holding an element ID.
* #const
* #type {string}
*/
var ELEMENT_KEY = 'ELEMENT';
/**
* True if using W3C Element references.
* #const
* #type {boolean}
*/
var w3cEnabled = false;
/**
* True if shadow dom is enabled.
* #const
* #type {boolean}
*/
var SHADOW_DOM_ENABLED = typeof ShadowRoot === 'function';
/**
* Generates a unique ID to identify an element.
* #void
* #return {string} Randomly generated ID.
*/
function generateUUID() {
var array = new Uint8Array(16);
window.crypto.getRandomValues(array);
array[6] = 0x40 | (array[6] & 0x0f);"
.... (continues for a lot more lines)
The test i try to run is:
import {browser, by, element} from 'protractor';
describe('Webclient', () => {
const MAX_SAFE_TIMEOUT = Math.pow(2, 31) - 1;
beforeAll(async function () {
await browser.get('/');
});
it('Endurance test', (done) => {
runTest();
}, MAX_SAFE_TIMEOUT);
});
let counter = 0;
async function runTest() {
try {
const elements = await element.all(by.css('.header a, .sidebar-left a, .footer a, .content a'));
let random = Math.floor(Math.random() * elements.length);
while (!(await elements[random].isDisplayed())) {
random = Math.floor(Math.random() * elements.length);
}
await elements[random].click();
console.log('click counter:', counter++, await browser.getCurrentUrl());
} catch (e) {
// Do nothing, just continue
}
// Break the chain so that we don't get one big recursive function
setTimeout(() => {
runTest();
}, 0);
}

Underscore.js groupBy multiple values

Using Underscore.js, I'm trying to group a list of items multiple times, ie
Group by SIZE then for each SIZE, group by CATEGORY...
http://jsfiddle.net/rickysullivan/WTtXP/1/
Ideally, I'd like to have a function or extend _.groupBy() so that you can throw an array at it with the paramaters to group by.
var multiGroup = ['size', 'category'];
Probably could just make a mixin...
_.mixin({
groupByMulti: function(obj, val, arr) {
var result = {};
var iterator = typeof val == 'function' ? val : function(obj) {
return obj[val];
};
_.each(arr, function(arrvalue, arrIndex) {
_.each(obj, function(value, objIndex) {
var key = iterator(value, objIndex);
var arrresults = obj[objIndex][arrvalue];
if (_.has(value, arrvalue))
(result[arrIndex] || (result[arrIndex] = [])).push(value);
My head hurts, but I think some more pushing needs to go here...
});
})
return result;
}
});
properties = _.groupByMulti(properties, function(item) {
var testVal = item["size"];
if (parseFloat(testVal)) {
testVal = parseFloat(item["size"])
}
return testVal
}, multiGroup);
A simple recursive implementation:
_.mixin({
/*
* #mixin
*
* Splits a collection into sets, grouped by the result of running each value
* through iteratee. If iteratee is a string instead of a function, groups by
* the property named by iteratee on each of the values.
*
* #param {array|object} list - The collection to iterate over.
* #param {(string|function)[]} values - The iteratees to transform keys.
* #param {object=} context - The values are bound to the context object.
*
* #returns {Object} - Returns the composed aggregate object.
*/
groupByMulti: function(list, values, context) {
if (!values.length) {
return list;
}
var byFirst = _.groupBy(list, values[0], context),
rest = values.slice(1);
for (var prop in byFirst) {
byFirst[prop] = _.groupByMulti(byFirst[prop], rest, context);
}
return byFirst;
}
});
Demo in your jsfiddle
I think #Bergi's answer can be streamlined a bit by utilizing Lo-Dash's mapValues (for mapping functions over object values). It allows us to group the entries in an array by multiple keys in a nested fashion:
_ = require('lodash');
var _.nest = function (collection, keys) {
if (!keys.length) {
return collection;
}
else {
return _(collection).groupBy(keys[0]).mapValues(function(values) {
return nest(values, keys.slice(1));
}).value();
}
};
I renamed the method to nest because it ends up serving much the same role served by D3's nest operator. See this gist for details and this fiddle for demonstrated usage with your example.
lodash nest groupby
How about this rather simple hack?
console.log(_.groupBy(getProperties(), function(record){
return (record.size+record.category);
}));
Check out this underscore extension: Underscore.Nest, by Irene Ros.
This extension's output will be slightly different from what you specify, but the module is only about 100 lines of code, so you should be able to scan to get direction.
This is a great use case for the reduce phase of map-reduce. It's not going to be as visually elegant as the multi-group function (you can't just pass in an array of keys to group on), but overall this pattern gives you more flexibility to transform your data. EXAMPLE
var grouped = _.reduce(
properties,
function(buckets, property) {
// Find the correct bucket for the property
var bucket = _.findWhere(buckets, {size: property.size, category: property.category});
// Create a new bucket if needed.
if (!bucket) {
bucket = {
size: property.size,
category: property.category,
items: []
};
buckets.push(bucket);
}
// Add the property to the correct bucket
bucket.items.push(property);
return buckets;
},
[] // The starting buckets
);
console.log(grouped)
But if you just want it in an underscore mixin, here's my stab at it:
_.mixin({
'groupAndSort': function (items, sortList) {
var grouped = _.reduce(
items,
function (buckets, item) {
var searchCriteria = {};
_.each(sortList, function (searchProperty) { searchCriteria[searchProperty] = item[searchProperty]; });
var bucket = _.findWhere(buckets, searchCriteria);
if (!bucket) {
bucket = {};
_.each(sortList, function (property) { bucket[property] = item[property]; });
bucket._items = [];
buckets.push(bucket);
}
bucket._items.push(item);
return buckets;
},
[] // Initial buckets
);
grouped.sort(function (x, y) {
for (var i in sortList) {
var property = sortList[i];
if (x[property] != y[property])
return x[property] > y[property] ? 1 : -1;
}
return 0;
});
return _.map(grouped, function (group) {
var toReturn = { key: {}, value: group.__items };
_.each(sortList, function (searchProperty) { toReturn.key[searchProperty] = group[searchProperty]; });
return toReturn;
});
});
The improvements by joyrexus on bergi's method don't take advantage of the underscore/lodash mixin system. Here it is as a mixin:
_.mixin({
nest: function (collection, keys) {
if (!keys.length) {
return collection;
} else {
return _(collection).groupBy(keys[0]).mapValues(function(values) {
return _.nest(values, keys.slice(1));
}).value();
}
}
});
An example with lodash and mixin
_.mixin({
'groupByMulti': function (collection, keys) {
if (!keys.length) {
return collection;
} else {
return _.mapValues(_.groupBy(collection,_.first(keys)),function(values) {
return _.groupByMulti(values, _.rest(keys));
});
}
}
});
Here is an easy to understand function.
function mixin(list, properties){
function grouper(i, list){
if(i < properties.length){
var group = _.groupBy(list, function(item){
var value = item[properties[i]];
delete item[properties[i]];
return value;
});
_.keys(group).forEach(function(key){
group[key] = grouper(i+1, group[key]);
});
return group;
}else{
return list;
}
}
return grouper(0, list);
}
Grouping by a composite key tends to work better for me in most situations:
const groups = _.groupByComposite(myList, ['size', 'category']);
Demo using OP's fiddle
Mixin
_.mixin({
/*
* #groupByComposite
*
* Groups an array of objects by multiple properties. Uses _.groupBy under the covers,
* to group by a composite key, generated from the list of provided keys.
*
* #param {Object[]} collection - the array of objects.
* #param {string[]} keys - one or more property names to group by.
* #param {string} [delimiter=-] - a delimiter used in the creation of the composite key.
*
* #returns {Object} - the composed aggregate object.
*/
groupByComposite: (collection, keys, delimiter = '-') =>
_.groupBy(collection, (item) => {
const compositeKey = [];
_.each(keys, key => compositeKey.push(item[key]));
return compositeKey.join(delimiter);
}),
});

Resources