Parse Json Array using Google Scripts - arrays

I am trying to parse this JSON Array but I'm not familiar with how to loop through a JSON in Google scripts. I'm getting an Undefined error for the price variable in this API.
API link.
function CBAPI() {
// Link the script with a spreadsheet using the unique identifier found in the spreadsheet web address
var ss = SpreadsheetApp.openById('16UqqC_MjnRfwbpREUcrcl7q69bUjzPgoUm6ZBMorizk');
var APIPullSheet = ss.getSheetByName("APIPull");
// Clear Columns A, B, C & D
APIPullSheet.getRange('A2:F19999').clearContent();
var url= "https://api.coinmarketcap.com/v2/ticker/132";
var responseAPI = UrlFetchApp.fetch(url);
var parcedData = JSON.parse(responseAPI.getContentText());
var id = [];
var price = [];
id.push(['id']);
price.push(['price']);
id.push([parcedData.data.id]);
price.push([parcedData.data.price]);
idRange = APIPullSheet.getRange(1, 1, id.length, 1); // Put isFrozen in column A
idRange.setValues(id);
priceRange = APIPullSheet.getRange(1, 2, price.length, 1); // Put lowestAsk in column B
priceRange.setValues(price);
// Append Latest Data to End of the File
var tableData = ss.getSheetByName("TableData");
var rangeData = tableData.getRange("H1:K1");
var latestData = rangeData.getValues(); // Put I1 to O1 in latestData variable
tableData.appendRow(latestData[0]); // Put the data at the bottom of the spreadsheet
// Keep 144 rows - Delete any extra starting at row 2
var rowsToKeep = 5000; // 5000 at request of Edwin
var totalRows = tableData.getLastRow();
var numToDelete = totalRows - rowsToKeep;
if (numToDelete > 0)
{
tableData.deleteRows(2, numToDelete); // Purge Extra Rows - Starting With Row 2 (oldest)
}
}

The API returns price inside the quotes object.
Replace:
price.push([parcedData.data.price]);
With:
price.push([parcedData.data.quotes.USD.price]);

Related

Filtering an Array of Sheets on three criteria with output being the URL link to the filtered sheets

I am creating two lists of links on my opening sheet (9060DASH) in Google Sheets. It looks like this:
One list is links to all sheets that are not hidden (Active) and one to all that are hidden. Each item must meet two other criteria to be included:
The sheet name must not include the numbers "9060."
The cell GH3 in the sheet must contain "Protected."
I have a functioning script, but it is loading too slowly. Here is a sample to show how it loads during onOpen(). How can I do this more efficiently? Can it be done better using "push"? Here is the script:
function populateSheetList() {
SpreadsheetApp.getActive().getSheetByName("9060DASH").activate();
var ss = SpreadsheetApp.getActive();
var ui = SpreadsheetApp.getUi();
ss.getRange('9060DASH!D4:E100').clear();
var counter = 4;
var sheetName = "";
var cellID= "";
var richValue = "";
var sheetURL = ss.getUrl();
var sheetLink = ""
var mySheet = "";
var shouldNotContain = '9060'; //array code from stackover to build array without 9060 sheets
var sheetNames = SpreadsheetApp.getActiveSpreadsheet().getSheets().map(s => s.getName());
var filtered = sheetNames.filter(x => !x.toLowerCase().match(shouldNotContain.toLowerCase()));
/** CREATES ACTIVE SHEETS LIST */
for(var i =0;i<filtered.length;i++){
cellID = '9060DASH!D'+counter;
sheetName = filtered[i];
mySheet = ss.getSheetByName(sheetName);
sheetLink = sheetURL+'#gid='+ mySheet.getSheetId();
if(mySheet.isSheetHidden() == false){
if(ss.getRange(sheetName+"!GH3").getValue() == "Protected"){
richValue = SpreadsheetApp.newRichTextValue()
.setText(sheetName)
.setLinkUrl(sheetLink)
.build();
ss.getRange(cellID).setRichTextValue(richValue);
counter = counter+1;
}
}
}
/** GATHERING HIDDEN SHEETS */
var counter = 4;
for(var i =0;i<filtered.length;i++){
cellID = '9060DASH!E'+counter;
sheetName = filtered[i];
mySheet = ss.getSheetByName(sheetName);
sheetLink = sheetURL+'#gid='+ mySheet.getSheetId();
if(mySheet.isSheetHidden() == true){
if(ss.getRange(sheetName+"!GH3").getValue() == "Protected"){
richValue = SpreadsheetApp.newRichTextValue()
.setText(sheetName)
.setLinkUrl(sheetLink)
.build();
ss.getRange(cellID).setRichTextValue(richValue);
counter = counter+1;
}
}
}
}
I have reduced the time to load the dashboard by rearranging a number of things and by getting a final array before writing anything out to the dash. I reformatted the cells and sorted the records before the evaluation loops began. I added more status messages, to keep users engaged while the background work was going on. I put the criteria of separating active from hidden sheets last and, depending on which, wrote the values to two different arrays (actvSheets and hidnSheets). Then I processed each array out to the dashboard. I did this in for loops. I suspect I could save even more time to write them out as one command from each array, but I have not been able to figure out how to write out in vertical form. Here is the code. Any refinements are welcome!
/**
* =================
* populateSheetList
* =================
* This function builds the available sheets in the
* 9060DASH sheet--containing all with the Protected
* status.
*/
function populateSheetListNEW() {
msgDash("Clearing sheet list . . . ")
// SpreadsheetApp.getActive().getSheetByName("9060DASH").activate();
var ss = SpreadsheetApp.getActive();
var ui = SpreadsheetApp.getUi();
ss.getRange('9060DASH!D4:E100')
.clear()
.setFontSize(14)
.setHorizontalAlignment('left')
.sort({column: 4, ascending: true})
msgDash("Evaluating sheets . . . ")
var counter = 4;
var sheetName = "";
var cellID= "";
var richValue = "";
var sheetURL = ss.getUrl();
var sheetLink = ""
var mySheet = "";
var actvSheets = [];
var hidnSheets = [];
msgDash("Eliminating administrative sheets . . . ")
var shouldNotContain = '9060'; //array code from stackover to build array without 9060 sheets
var sheetNames = SpreadsheetApp.getActiveSpreadsheet().getSheets().map(s => s.getName());
var filtered = sheetNames.filter(x => !x.toLowerCase().match(shouldNotContain.toLowerCase()));
var sortFilt = filtered.sort();
/** CREATES SHEET ARRAYS */
msgDash("Gathering census worksheets . . . ")
for(var i =0;i<sortFilt.length;i++){
sheetName = sortFilt[i];
mySheet = ss.getSheetByName(sheetName);
if(ss.getRange(sheetName+"!GH3").getValue() == "Protected"){
if(mySheet.isSheetHidden() == false){
actvSheets.push(sheetName);
} else {
hidnSheets.push(sheetName);
}
}
}
msgDash("Gathering active sheets . . . ")
/** WRITING DATA FROM ACTVSHEETS ARRAY */
for(var j = 0; j < actvSheets.length; j++){
sheetName = actvSheets[j];
mySheet = ss.getSheetByName(actvSheets[j]);
sheetLink = sheetURL+'#gid='+ mySheet.getSheetId();
richValue = SpreadsheetApp.newRichTextValue()
.setText(sheetName)
.setLinkUrl(sheetLink)
.build();
cellID = '9060DASH!D' + counter;
counter = counter+1;
ss.getRange(cellID).setRichTextValue(richValue);
}
msgDash("Gathering hidden sheets . . . ");
/** WRITING DATA FROM HDDNSHEETS ARRAY */
counter = 4;
for(var k = 0; k< hidnSheets.length; k++){
sheetName = hidnSheets[k];
mySheet = ss.getSheetByName(hidnSheets[k]);
sheetLink = sheetURL+'#gid='+ mySheet.getSheetId();
richValue = SpreadsheetApp.newRichTextValue()
.setText(sheetName)
.setLinkUrl(sheetLink)
.build();
cellID = '9060DASH!E' + counter;
counter = counter+1;
ss.getRange(cellID).setRichTextValue(richValue);
}
msgDash('Dashboard ready. Enjoy your census work!');
ss.getRange('9060DASH!D2').setValue("For hidden sheets, respond to 'Unhide' instruction. Click Refresh button to rebuild list.");
Utilities.sleep(20);
msgDash(''); //clears messages
}

script & sheet timing out when trying to print large arrays in google script

Background
I have a function that makes a REST API call using UrlFetchApp in Google Scripts.
But the response only returns 2000 records at a time. If there are more records, there is, in the response, a key called nextRecordsUrl, which contains the endpoint and parameters needed to get the next batch of records.
I use a do...while loop to iterate through, pushing the records into a predesignated array, make the next api call. And when it reaches the last batch of records, it exists the do-while loop, then prints (not sure if that's the right term here) the entire to a Google Sheet.
The code
It looks like this:
function getCampaignAssociations() {
clearPage('CampaignAssociations');
var query = '?q=select+CampaignMember.FirstName,CampaignMember.LastName,CampaignMember.LeadId,CampaignMember.ContactId,CampaignMember.Name,CampaignMember.CampaignId,CampaignMember.SystemModstamp,CampaignMember.Email+from+CampaignMember+ORDER+BY+Email ASC,SystemModstamp+ASC';
try {
var arrCampAssociation = getInfoByQuery(query);
if (arrCampAssociation.records.length < 1) {
throw 'there are no records in this query';
}
var campaignAssoc = [];
do {
Logger.log(arrCampAssociation.nextRecordsUrl);
for (var i in arrCampAssociation.records) {
let data = arrCampAssociation.records[i];
let createDate = Utilities.formatDate(new Date(data.SystemModstamp), "GMT", "dd-MM-YYYY");
let a1 = "$A" + (parseInt(i) + 2);
let nameFormula = '=IFERROR(INDEX(Campaigns,MATCH(' + a1 + ',Campaigns!$A$2:A,0),2),"")';
let typeFormula = '=IFERROR(INDEX(Campaigns,MATCH(' + a1 + ',Campaigns!$A$2:A,0),3),"")';
campaignAssoc.push([data.CampaignId, nameFormula, typeFormula, data.Email, data.FirstName, data.LastName, data.LeadId, data.ContactId, createDate]);
}
var arrCampAssociation = getQueryWithFullEndPoint(arrCampAssociation.nextRecordsUrl);
} while (arrCampAssociation.nextRecordsUrl != null && arrCampAssociation.nextRecordsUrl != undefined);
let endRow = campAssocSheet.getLastRow(),
endColumn = campAssocSheet.getLastColumn(),
nameRange = campAssocSheet.getRange(2, 1, endRow, endColumn),
destRange = campAssocSheet.getRange(2, 1, campaignAssoc.length, campaignAssoc[0].length);
destRange.setValues(campaignAssoc);
sheet.setNamedRange('CampaignAssociation', nameRange);
} catch (e) {
Logger.log(e);
Logger.log(arrCampAssociation);
Logger.log(campaignAssoc);
Logger.log(i);
}
}
Issue
Everything works nicely until it comes to printing the array campaignAssoc to the Google Sheet.
See screenshot of the log below. It contains the endpoint for the next both. Notice the timestamp between the earlier logs and the timestamp between the last endPoint and the log where it timed out.
It seems to me that the issue is that when it comes to the printing of the data, it's having issues. If that's the case, have I overloaded the array? There are a total of over 36400 records.
Second attempt
I've tried resetting the array at each loop and printing the array to Google sheet. This is just 2000 records at each attempt and I've definitely done more rows at 1 time, but that didn't help.
Here's the code for that attempt.
function getCampaignAssociations() {
clearPage('CampaignAssociations');
var query = '?q=select+CampaignMember.FirstName,CampaignMember.LastName,CampaignMember.LeadId,CampaignMember.ContactId,CampaignMember.Name,CampaignMember.CampaignId,CampaignMember.SystemModstamp,CampaignMember.Email+from+CampaignMember+ORDER+BY+Email ASC,SystemModstamp+ASC';
try {
var arrCampAssociation = getInfoByQuery(query);
if (arrCampAssociation.records.length < 1) {
throw 'there are no records in this query';
}
do {
Logger.log(arrCampAssociation.nextRecordsUrl);
var campaignAssoc = [];
for (var i in arrCampAssociation.records) {
let data = arrCampAssociation.records[i];
let createDate = Utilities.formatDate(new Date(data.SystemModstamp), "GMT", "dd-MM-YYYY");
let a1 = "$A" + (parseInt(i) + 2);
let nameFormula = '=IFERROR(INDEX(Campaigns,MATCH(' + a1 + ',Campaigns!$A$2:A,0),2),"")';
let typeFormula = '=IFERROR(INDEX(Campaigns,MATCH(' + a1 + ',Campaigns!$A$2:A,0),3),"")';
campaignAssoc.push([data.CampaignId, nameFormula, typeFormula, data.Email, data.FirstName, data.LastName, data.LeadId, data.ContactId, createDate]);
}
let lastRow = campAssocSheet.getLastRow()+1;
campAssocSheet.getRange(lastRow,1,campaignAssoc.length,campaignAssoc[0].length).setValues(campaignAssoc);
var arrCampAssociation = getQueryWithFullEndPoint(arrCampAssociation.nextRecordsUrl);
} while (arrCampAssociation.nextRecordsUrl != null && arrCampAssociation.nextRecordsUrl != undefined);
let endRow = campAssocSheet.getLastRow(),
endColumn = campAssocSheet.getLastColumn(),
nameRange = campAssocSheet.getRange(2, 1, endRow, endColumn);
sheet.setNamedRange('CampaignAssociation', nameRange);
} catch (e) {
Logger.log(e);
Logger.log(arrCampAssociation);
Logger.log(campaignAssoc);
Logger.log(i);
}
}
So here, each loop took a lot longer. Instead of being 1-2 seconds between each loop, it took 45 seconds to a minute between each and timed out after the 4th loop. See the log below:
How do I fix this?

Loop script for all values of dropdown

I'm looking to make a script that cycles through a dropdown list and creates a pdf for each.
https://docs.google.com/spreadsheets/d/1HrXWkNXT7aEWOXkngiuSX9Sr1F0V4Y_rZH6Eg3mjaJQ/edit?usp=sharing
First I would like to check if B2 is not empty, then if so create pdf and change A2 to the next option until all are complete. I have a basic script but feel free to disregard!
function loopScript() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const interface = ss.getSheetByName("Interface");
var folderID = "###GOOGLE DRIVE FOLDER ID###";
var folder = DriveApp.getFolderById(folderID);
const exportOptions = 'exportFormat=pdf&format=pdf'
+ '&size=A4'
+ '&portrait=true'
+ '&scale=4'
+ '&fith=true&source=labnol'
+ '&top_margin=0.05'
+ '&bottom_margin=0.05'
+ '&left_margin=1.00'
+ '&right_margin=0.25'
+ '&sheetnames=false&printtitle=false'
+ '&pagenumbers=false&gridlines=false'
+ '&fzr=false'
+ '&gid=125740569';
var params = {method:"GET",headers:{"authorization":"Bearer "+ ScriptApp.getOAuthToken()}};
var response = UrlFetchApp.fetch(url+exportOptions, params).getBlob();
const nameFile = "NAME OF FILE" + ".pdf" ;
folder.createFile(response.setName(nameFile));
DriveApp.createFile(response.setName(nameFile));
}
I believe your goal is as follows.
You want to check the cell "B2". When the cell "B2" is not empty, you want to set the value of the dropdown list of cell "A2" to the next value of the list.
For example, when the dropdown list is Joe, Barry, Jane, Fred and the cell "A2" is Barry, you want to set the cell to Jane.
In this case, how about the following modified script?
Modified script:
From:
const ss = SpreadsheetApp.getActiveSpreadsheet();
const interface = ss.getSheetByName("Interface");
To:
const ss = SpreadsheetApp.getActiveSpreadsheet();
const interface = ss.getSheetByName("Interface");
if (interface.getRange("B2").isBlank()) return;
const range = interface.getRange("A2");
const values = [...new Set(range.getDataValidation().getCriteriaValues()[0].getValues().flat())];
const nextValue = values[values.indexOf(range.getValue()) + 1] || values[0];
range.setValue(nextValue);
In this modified script, when the cell "B2" is empty, the script is finished. When the cell "B2" is not empty, the script is run and the cell "A2" is updated and your script for creating the PDF file is run.
Note:
In above modified script, when the dropdown list is Joe, Barry, Jane, Fred and the cell "A2" is Fred, the value of Joe is set. If you want to change this, please modify the above script.
In your current script, url is not defined. Please be careful this.
References:
isBlank()
getDataValidation()
getCriteriaValues()
Issue:
If I understand you correctly, you want to do the following:
For each dropdown in A2, check if the formula in B2 populates any values (based on data from sheet Data).
If any value is populated in B due to the formula, create a PDF file using the value of A2 for the file name (you have achieved this already).
Method 1:
In this case, I'd suggest the following workflow:
Retrieve an array with the accepted values from A2 dropdown (you can use the method used in Tanaike's answer).
Iterate through these values, and for each one, set the A2 value, using Range.setValue.
Call flush in order to update the data in B2 according to the current value in A2.
Check if B2 is blank (using Range.isBlank, for example).
If B2 is not blank, create the drive file.
function loopScript() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const interface = ss.getSheetByName("Interface");
const range = interface.getRange("A2");
const values = [...new Set(range.getDataValidation().getCriteriaValues()[0].getValues().flat())].filter(String);
values.forEach(name => {
range.setValue(name);
SpreadsheetApp.flush();
if (!interface.getRange("B2").isBlank()) {
// CODE FOR CREATING FILE
}
});
}
Method 2:
In the previous method, setValue, flush, getRange and isBlank are used iteratively, greatly increasing the amount of calls to the spreadsheet. This is not the best practice, as it will slow down the script (see Minimize calls to other services), and this will get worse if there are more valid options for the dropdown.
Therefore, since the data this formula is using can be found in sheet Data, I'd suggest using that source data instead of the formula, in order to minimize the calls to the spreadsheet.
In this case, you could follow this workflow:
Get all data in Data at once using Range.getValues.
Get all valid options in the data validation from A2, as in method 1.
For each option, check if there's any row in Data that has this option in column A and a non-empty cell in B.
If there is some data for that option, create the file.
function loopScript() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const interface = ss.getSheetByName("Interface");
const data = ss.getSheetByName("Data");
const DATA_FIRST_ROW = 2;
const dataValues = data.getRange(DATA_FIRST_ROW,1,data.getLastRow()-DATA_FIRST_ROW+1,2).getValues();
const range = interface.getRange("A2");
const values = [...new Set(range.getDataValidation().getCriteriaValues()[0].getValues().flat())].filter(String);
values.forEach(name => {
const optionValues = dataValues.filter(dataRow => dataRow[0] === name);
const nonEmpty = optionValues.some(optionValue => optionValue[1] !== "");
if (nonEmpty) {
// CODE FOR CREATING FILE
}
});
}
Thanks to everyone's help, I ended up using Tanaike's advice and continued on to come up with this:
function sendAll() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var interface = ss.getSheetByName("Interface");
var range = interface.getRange("A2");
// Cell of validation
const values = [...new Set(range.getDataValidation().getCriteriaValues()
[0].getValues().flat())]; // Gets array of validation
var first = values[0];
// 1st cell of validation
var number = values.length - 1;
// Length of validation
range.setValue(first);
// Sets value to first one
for(i = 0;i < number;i++) {
// Loop number of names
if (interface.getRange("B2").getValue().length > 0) {
// Sheet isn't empty
var person = interface.getRange("A2").getValue();
const url = 'MY SHEET URL';
var folderID = "MY FOLDER ID";
var folder = DriveApp.getFolderById(folderID);
const exportOptions = 'exportFormat=pdf&format=pdf'
+ '&size=A4'
+ '&portrait=true'
+ '&scale=4'
+ '&fith=true&source=labnol'
+ '&top_margin=0.05'
+ '&bottom_margin=0.05'
+ '&left_margin=1.00'
+ '&right_margin=0.25'
+ '&sheetnames=false&printtitle=false'
+ '&pagenumbers=false&gridlines=false'
+ '&fzr=false'
+ '&gid=0';
var params = {method:"GET",headers:{"authorization":"Bearer "+
ScriptApp.getOAuthToken()}};
var response = UrlFetchApp.fetch(url+exportOptions, params).getBlob();
const nameFile = person + ".pdf" ;
folder.createFile(response.setName(nameFile));
DriveApp.createFile(response.setName(nameFile));
const nextValue = values[values.indexOf(range.getValue()) + 1] || values[0];
range.setValue(nextValue);
}
else {const nextValue = values[values.indexOf(range.getValue()) + 1] ||
values[0];
range.setValue(nextValue);}
}
}

Find a specific Google Document in Drive

I am trying to write a script to get the URL of a specific yet dynamic Google Document in Drive. The Google Document is created every Tuesday with its date at the end of the title of the Google Document.
"TypeError: Cannot find function getUrl in object FileIterator." error shows on line var agendaURL = file.getUrl();. Not sure how to debug this.
var ssUrl = 'LINK BUFFERED';
var sheetName = 'SHEET1'; // name of sheet to use
var rangeName = 'C30'; // range of values to include
var dateRange = SpreadsheetApp.openByUrl(ssUrl)
.getSheetByName(sheetName)
.getRange(rangeName)
.getValues();
// NEED TO find how to find file by name below!
var file = DriveApp.getFilesByName("Weekly Agenda | " +dateRange);
var agendaURL = file.getUrl();
It is because you need to iterate through all of the files that meet the search criteria. The solution is here: https://developers.google.com/apps-script/reference/drive/drive-app
var files = DriveApp.getFilesByName("Weekly Agenda | " +dateRange);
while (files.hasNext()) {
var file = files.next();
var agendaURL = file.getUrl();
return agendaURL //for first one, or you can return each one, push to an array, etc...
}
The value which got by DriveApp.getFilesByName() is FileIterator. So it is necessary to retrieve the filename from the value as follows.
var filename = "Weekly Agenda | " + dateRange;
var file = DriveApp.getFilesByName(filename);
while (file.hasNext()) {
var file = files.next();
if (file.getName() == filename) {
var agendaURL = file.getUrl();
}
}

Want to retrieve values not other details from array object in rally

Want to retrieve below values from array object not other details, but getting whole data like events, listeners, etc.
2014-10-01: 02014-10-02: 02014-10-06: 42014-10-08: 50.2857142857142852014-10-09: 42014-10-10: 32014-10-13: 32014-10-14: 2.52014-10-15: 52014-10-16: 02014-10-20: 32014-10-21: 12014-10-27: 32014-10-28: 6.7777777777777782014-10-29: 12014-10-31: 0.66666666666666662014-11-03: 42014-11-04: 19.252014-11-05: 33.62014-11-06: 12014-11-07: 32014-11-10: 32014-11-11: 3.6666666666666665
Below is my some of the code which generate this object, any help on this please..
var daysOfMonth = new Ext.util.HashMap();
//console.log("startdate", this.startDate);
//console.log("enddate", this.endDate);
start = new Date(this.startDate);
end = new Date(this.endDate);
for (start; start <= end; start.setDate(start.getDate() + 1)) {
daysOfMonth.add(new Date(start), null);
}
//daysOfMonth = Ext.Array.flatten(daysOfMonth);
console.log("days of month", daysOfMonth);
var userstory_cycle_times_by_date = this._getCycleTimes(userstory_snaps_by_date);
var storydaysOfMonth = Ext.Object.merge(daysOfMonth, userstory_cycle_times_by_date);
//console.log("days of month", daysOfMonth);
var defect_cycle_times_by_date = this._getCycleTimes(defect_snaps_by_date);
var defectdaysOfMonth = Ext.Object.merge(daysOfMonth, defect_cycle_times_by_date);
You can get an object of all the key/value pairs like so:
_.reduce(daysOfMonth.getKeys(), function(result, key) {
result[key] = daysOfMonth.get(key);
return result;
}, {});
I'm curious why you're using a HashMap to begin with rather than just a regular plain old javascript object literal?

Resources