Hi I'm just new in Google Apps Script. I want to modify this formula below to Google Apps Script
=arrayFORMULA(iferror(VLOOKUP(J3,{'WO SR 22/23'!P:P,'WO SR 22/23'!B:B},2,FALSE)))
The lookup value is in wsPetitionStatusReport sheet. I tried code below but it always return null. Can you help me with this? Thank you so much
function vLookUpVALUE() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const wsWOSR = ss.getSheetByName("WO SR 22/23")
const wsPetitionStatusReport = ss.getSheetByName("Petition Status Report ")
const searchVALUES = wsPetitionStatusReport.getRange("J3:J").getValues()
const wsWOSRDATcolO = wsWOSR.getRange("B3:P" + wsWOSR.getLastRow()).getValues();
const matchDATAcolO = searchVALUES.map(searchROWcolO => {
const matchROWcolO = wsWOSRDATcolO.find (r => r[0] == searchROWcolO[0])
return matchROWcolO ? [matchROWcolO[0]] : [null]
})
console.log(matchDATAcolO)
}
You can evaluate the array formula for the whole column by replacing J3 with an open-ended reference like J3:J:
=arrayformula(iferror(vlookup(J3:J, { 'WO SR 22/23'!P:P, 'WO SR 22/23'!B:B }, 2, false)))
To do the same with Apps Script, use something like this:
function vlookupLookAlike() {
const ss = SpreadsheetApp.getActive();
const source = {
values: ss.getRange('WO SR 22/23!B3:P').getValues(),
keyColumn: 14, // =column(P3) - column(B3)
dataColumn: 0,
};
source.keys = source.values.map(row => row[source.keyColumn]);
source.data = source.values.map(row => row[source.dataColumn]);
const target = {
range: ss.getRange('Petition Status Report !J3:J'), // trailing space
};
target.keys = target.range.getValues().flat();
target.results = target.keys.map(key => [key && source.data[source.keys.indexOf(key)]]);
target.range.offset(0, 5).setValues(target.results);
}
Related
This Google Apps Script code to scrape historical data from from Yahoo Finance stopped working yesterday. It suddenly gives the error - No data (data.length == 0).
I think the bug is in the line 8 script while getting the JSON but I dont't have the necessary skill to fix it.
It woud be appreciate your help with issue.
function Scrapeyahoo(symbol) {
//Leemos de yahoo finance historical data
const s = encodeURI(symbol); // so that it works with a url
// turn it into an URL and call it
const url = 'https://finance.yahoo.com/quote/' +s +'/history?p=' +s;
const res = UrlFetchApp.fetch(url, { muteHttpExceptions: true }).getContentText();
const $ = Cheerio.load(res);
const data = $('script').toArray().reduce((ar, x) => {
const c = $(x).get()[0].children;
if (c.length > 0) {
const d = c[0].data.trim().match(/({"context"[\s\S\w]+);\n}\(this\)\);/);
if (d && d.length == 2) {
ar.push(JSON.parse(d[1]));
}
}
return ar;
}, []);
if (data.length == 0) throw new Error("No data.");
const header = ["date", "open", "high", "low", "close", "adjclose", "volume"];
var key = Object.entries(data[0]).find(([k]) => !["context", "plugins"].includes(k))[1];
if (!key) return;
const cdnjs = "https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js";
eval(UrlFetchApp.fetch(cdnjs).getContentText());
const obj1 = data[0];
const obj2 = JSON.parse(CryptoJS.enc.Utf8.stringify(CryptoJS.AES.decrypt(obj1.context.dispatcher.stores, key)));
const ar = obj2.HistoricalPriceStore.prices.map(o => header.map(h => h == "date" ? new Date(o[h] * 1000) : (o[h] || "")));
// ---
return ar
}
The original code was modified in December according to this solution, after it stopped working, but I can't find a solution for the issue now.
It seems that the specification for retrieving the key has been changed. In this case, var key = Object.entries(data[0]).find(([k]) => !["context", "plugins"].includes(k))[1]; doesn't return the correct key. By this, an error occurs at CryptoJS.enc.Utf8.stringify(CryptoJS.AES.decrypt(obj1.context.dispatcher.stores, key)).
In the current stage, when I saw this script, the modified script is as follows.
Modified script:
function Scrapeyahoo(symbol) {
const s = encodeURI(symbol);
const url = 'https://finance.yahoo.com/quote/' +s +'/history?p=' +s;
var html = UrlFetchApp.fetch(url).getContentText().match(/root.App.main = ([\s\S\w]+?);\n/);
if (!html || html.length == 1) return;
var obj = JSON.parse(html[1].trim());
var key = [...new Map(Object.entries(obj).filter(([k]) => !["context", "plugins"].includes(k)).splice(-4)).values()].join("");
if (!key) return;
const cdnjs = "https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js";
eval(UrlFetchApp.fetch(cdnjs).getContentText());
const obj1 = JSON.parse(CryptoJS.enc.Utf8.stringify(CryptoJS.AES.decrypt(obj.context.dispatcher.stores, key)));
const header = ["date", "open", "high", "low", "close", "adjclose", "volume"];
const ar = obj1.HistoricalPriceStore.prices.map(o => header.map(h => h == "date" ? new Date(o[h] * 1000) : (o[h] || "")));
return ar
}
In this case, Cheerio is not used.
Note:
In this sample, in order to load crypto-js, eval(UrlFetchApp.fetch(cdnjs).getContentText()) is used. But, if you don’t want to use it, you can also use this script by copying and pasting the script of https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js to the script editor of Google Apps Script. By this, the process cost can be reduced.
I can confirm that this method can be used for the current situation (January 27, 2023). But, when the specification in the data and HTML is changed in the future update on the server side, this script might not be able to be used. Please be careful about this.
Reference:
crypto-js
I found on internet a script allow me do Vlookup in app-script to lookup a value in other spreadsheet, but if value matching is blank it just copy "blank" cell into child spreadsheet.
I want if matching values found is blank it will write "Pending" string to cell, not just empty
please suggest me a script
thank
function getTracking() {
const sss = SpreadsheetApp.openById("1B2yhG1dhpqgtSfF4ngqRuH51kzwQ6VwxIvCmwZZ2yQU");
const ssh = sss.getSheetByName("Tổng Hợp");
const mDB = ssh.getRange(2,1,ssh.getLastRow()-1,4).getValues();
const dss = SpreadsheetApp.openById("1slt0ExJK2X8xkRVpOzdD8NCsUmj3UeC3urfyZWsV0To");
const dsh = dss.getSheetByName("test");
const searchValues = dsh.getRange("B2:B").getValues();
const matchingID = searchValues.map(searchRow => {
const matchRow = mDB.find(r => r[0] == searchRow[0])
return matchRow ? [matchRow [3]]: ["Không tìm thấy dữ liệu"];
})
dsh.getRange("C2:C").setValues(matchingID);
}
Modification points:
In your script, by const searchValues = dsh.getRange("B2:B").getValues();, all rows are used. In this case, getRange("B2:B" + dsh.getLastRow()) might be suitable.
About if value matching is blank it just copy "blank" cell into child spreadsheet. I want if matching values found is blank it will write "Pending" string to cell, in this case, I would like to propose modifying [matchRow [3]].
When these points are reflected in your script, how about the following modification?
From:
const searchValues = dsh.getRange("B2:B").getValues();
const matchingID = searchValues.map(searchRow => {
const matchRow = mDB.find(r => r[0] == searchRow[0])
return matchRow ? [matchRow[3]] : ["Không tìm thấy dữ liệu"];
})
dsh.getRange("C2:C").setValues(matchingID);
To:
const range = dsh.getRange("B2:B" + dsh.getLastRow())
const searchValues = range.getValues();
const matchingID = searchValues.map(searchRow => {
const matchRow = mDB.find(r => r[0] == searchRow[0])
return matchRow ? [matchRow[3].toString() != "" ? matchRow[3] : "Pending"] : ["Không tìm thấy dữ liệu"];
})
range.offset(0, 1).setValues(matchingID);
Reference:
Conditional (ternary) operator
I have many cells with multiple questions and answers in one cell like A1. When I run the apps script, I want the blue text formatting to be applied only to the answer line, like B1.
The algorithm I was thinking of is as follows.
Cut the questions and answers based on the newline character and make a list and condition processing within loop.
If the first character is -, it is a question, so pass or apply black format.
If the first character is ┗, it is an answer, so apply blue formatting.
But I'm new to apps script and google sheet api, so I don't know which way to go. Could you please write an example?
Try on active cell for instance
function formatCell() {
const range = SpreadsheetApp.getActiveRange()
// const range = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getDataRange() // for the whole sheet
const spec = { regex: /┗.*/gi, textColor: 'blue' };
const values = range.getDisplayValues();
let match;
const formattedText = values.map(row => row.map(value => {
const richText = SpreadsheetApp.newRichTextValue().setText(value);
const format = SpreadsheetApp.newTextStyle()
.setForegroundColor(spec.textColor)
.build();
while (match = spec.regex.exec(value)) {
richText.setTextStyle(match.index, match.index + match[0].length, format);
}
return richText.build();
}));
range.setRichTextValues(formattedText);
}
reference
Class RichTextValueBuilder
Use RichTextValue to set stylized text, and apply the corresponding TextStyle in lines that start with your desired character:
function changeAnswersColor() {
const sheet = SpreadsheetApp.getActiveSheet();
const textStyle = SpreadsheetApp.newTextStyle().setForegroundColor("blue").build();
const range = sheet.getRange("A1");
const values = range.getValues();
const richTextValues = values.map(row => {
return row.map(value => {
const valueLines = value.split("\n");
let builder = SpreadsheetApp.newRichTextValue().setText(value);
for (let i = 0; i < valueLines.length; i++) {
const vl = valueLines[i];
if (vl.trim()[0] === "┗") {
console.log(valueLines.slice(0,i).join("\n"))
const startOffset = valueLines.slice(0,i).join("\n").length;
const endOffset = startOffset + vl.length + 1;
builder.setTextStyle(startOffset, endOffset, textStyle);
}
}
const richTextValue = builder.build();
return richTextValue;
});
});
range.offset(0,range.getNumColumns()).setRichTextValues(richTextValues);
}
I'm trying to use the new Google Analytics Data API to pull in some very simple data from our GA4 property and into a spreadsheet.
This all works great.
However I now want to apply some Dimension Filters so that It returns only those rows that match the filter.
This is the code I've added but I think the format is wrong.
const dimensionfilter = AnalyticsData.newFilterExpression();
dimensionfilter.filter.fieldName = 'pageTitle';
dimensionfilter.filter.stringFilter.value = 'MYPAGETITLETEXT';
There are no examples in Apps script for adding DimensionFilter etc
Has anyone done this? Has anyone got any very simple examples.
Many thanks in advance.
Here is my full code
function runReport() {
const propertyId = '29045017783';
try {
const metric = AnalyticsData.newMetric();
metric.name = 'screenPageViews';
const pagetitle = AnalyticsData.newDimension();
pagetitle.name = 'pageTitle';
const pagepath = AnalyticsData.newDimension();
pagepath.name = 'pagePath';
const dateRange = AnalyticsData.newDateRange();
dateRange.startDate = '2022-05-01';
dateRange.endDate = 'today';
const dimensionfilter = AnalyticsData.newFilterExpression();
dimensionfilter.filter.fieldName = 'pageTitle';
dimensionfilter.filter.stringFilter.value = 'MYPAGETITLETEXT';
const request = AnalyticsData.newRunReportRequest();
request.dimensions = [pagetitle, pagepath];
request.metrics = [metric];
request.dateRanges = dateRange;
request.limit=10;
request.dimensionFilter = dimensionfilter;
const report = AnalyticsData.Properties.runReport(request,'properties/' + propertyId);
if (!report.rows) {
Logger.log('No rows returned.');
return;
}
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheet = spreadsheet.getActiveSheet();
// Append the headers.
const dimensionHeaders = report.dimensionHeaders.map(
(dimensionHeader) => {
return dimensionHeader.name;
});
const metricHeaders = report.metricHeaders.map(
(metricHeader) => {
return metricHeader.name;
});
const headers = [...dimensionHeaders, ...metricHeaders];
sheet.appendRow(headers);
// Append the results.
const rows = report.rows.map((row) => {
const dimensionValues = row.dimensionValues.map(
(dimensionValue) => {
return dimensionValue.value;
});
const metricValues = row.metricValues.map(
(metricValues) => {
return metricValues.value;
});
return [...dimensionValues, ...metricValues];
});
sheet.getRange(2, 1, report.rows.length, headers.length)
.setValues(rows);
Logger.log('Report spreadsheet created: %s',
spreadsheet.getUrl());
} catch (e) {
// TODO (Developer) - Handle exception
Logger.log('Failed with error: %s', e.error);
}
}
This is how you can apply Dimension Filter
const dimensionfilter = AnalyticsData.newFilterExpression()
dimensionfilter.filter = AnalyticsData.newFilter()
dimensionfilter.filter.fieldName = 'pageTitle'
dimensionfilter.filter.stringFilter = AnalyticsData.newStringFilter()
dimensionfilter.filter.stringFilter.value = 'MYPAGETITLETEXT'
Edit: For multiple filters
To combine multiple filters with OR condition
const pageTitles = [
'MYPAGETITLETEXT1',
'MYPAGETITLETEXT2'
]
const dimensionfilter = AnalyticsData.newFilterExpression()
dimensionfilter.orGroup = AnalyticsData.newFilterExpressionList()
dimensionfilter.orGroup.expressions = []
for (const pageTitle of pageTitles) {
const filterExpression = AnalyticsData.newFilterExpression()
filterExpression.filter = AnalyticsData.newFilter()
filterExpression.filter.fieldName = 'pageTitle'
filterExpression.filter.stringFilter = AnalyticsData.newStringFilter()
filterExpression.filter.stringFilter.value = pageTitle
dimensionfilter.orGroup.expressions.push(filterExpression)
}
You can refer to this document to understand How method signatures are determined
I try to create formula inside array after filter this array, my array sometime don't give me price and sometime don't give me total price:
I need if the Price in array has 0.0 I want to change this to TotalPrice/Qty like first array 600/20=30 , The price is 30. and in second array
This is my code : TotalPrice is 0.0 do QtyPrice=TotalPrice : 1020=200 , thee TotalPrice is 200 .
I need output like that:
[{Qty:20, Price:30, TotalPrice:600}, {Qty:10, Price:20, TotalPrice:200},
{Qty:5, Price:100, TotalPrice:500}]
And then I put it to sheet by this code:
sh.getRange(5,5,oA.length, oA[0].length).setValues(oA);
Thanks.
function arrayFormula() {
const data = '[{"Qty":"20", "Price":"0.0", "TotalPrice":"600"}, {"Qty":"10", "Price":"20",
"TotalPrice":"0.0"}, {"Qty":"5", "Price":"100", "TotalPrice":"500"}]';
Logger.log("Array: "+ data);
const obj = JSON.parse(data);
const ss = SpreadsheetApp.getActive();
const oA = obj.map(obj => [obj.Qty, obj.Price,obj.TotalPrice]);
// put array return to sheet
//sh.getRange(5,5,oA.length, oA[0].length).setValues(oA);
var filtered = oA.filter(function (el) {
if (el[1]!=0) {
return obj.Total/obj.Qty;
} else {
return false
}
});
console.log("Array After Filter: " + filtered);
}
Return me :
1:16:27 PM Info Array: [{"Qty":"20", "Price":"0.0", "Total":"600"}, {"Qty":"10",
"Price":"20", "Total":"0.0"}, {"Qty":"5", "Price":"100",
"Total":"500"}]
1:16:27 PM Info Array After Filter:
Like this photo:
enter image description here
Probably something like this:
function main() {
const data = `[
{"Qty":"20", "Price":"0.0", "TotalPrice":"600"},
{"Qty":"10", "Price":"20", "TotalPrice":"0.0"},
{"Qty":"5", "Price":"100", "TotalPrice":"500"}
]`;
const obj = JSON.parse(data);
const calc_price = obj =>
obj.Price == 0 ? obj.TotalPrice / obj.Qty : obj.Price;
const calc_total_price = obj =>
obj.TotalPrice == 0 ? obj.Price * obj.Qty : obj.TotalPrice;
const oA = obj.map(o => [o.Qty, calc_price(o), calc_total_price(o)]);
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
sh.getRange(5,5,oA.length,oA[0].length).setValues(oA);
}
Output: