Looking to provide a request object to BatchUpdate so both cell values and formatting can be updated. I am constructing the following request object:
const request = [{
updateCells: {
range: {
sheetId:grid.sheetId,
startRowIndex: grid.startRow,
endRowIndex: grid.endRow,
startColumnIndex: grid.startCol,
endColumnIndex:grid.endCol
},
rows: [
{
values: [
{
userEnteredFormat: {
backgroundColor: {
red: 1,
green: 0.4,
blue: 0.4
}
},
userEnteredValue: {
stringValue: {object containing the row}
}
}
]
}
],
fields: "userEnteredFormat,userEnteredValue"
}
}];
Apparently I get an error "Starting an object on a scalar field".
Is there some different request type to be made to provide the row as an array or object?
Looking at the docs [https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/sheets#RowData], it doesn't look like it.
TIA
Although unfortunately, I'm not sure whether I could correctly understand {object containing the row} of stringValue: {object containing the row}, in your request body, if {object containing the row} of stringValue: {object containing the row} is an object like {key: "value"}, an error like starting an object on a scalar fieldoccurs. I thought that this might be the reason for your current issue. And also, when an array like["sample value"]is used, an error likeProto field is not repeating, cannot start listoccurs. In this case, please use a string value likestringValue: "sample value"`. Please be careful about this.
From your question of Is there some different request type to be made to provide the row as an array or object?, if you want to use an array including values to this request body, how about the following modification?
Modified script:
function myFunction() {
const spreadsheetId = "###"; // Please set your Spreadsheet ID.
const grid = { sheetId: 0, startRow: 0, startCol: 0 }; // Please set your gridrange.
const values = [["sample value1", "sample value2"], ["sample value3", "sample value4"]]; // Please set your values as 2 dimensional array.
const request = [{
updateCells: {
range: {
sheetId: grid.sheetId,
startRowIndex: grid.startRow,
startColumnIndex: grid.startCol,
},
rows: values.map(r => ({
values: r.map(c => ({
userEnteredFormat: { backgroundColor: { red: 1, green: 0.4, blue: 0.4 } },
userEnteredValue: { stringValue: c }
}))
})),
fields: "userEnteredFormat,userEnteredValue"
}
}];
Sheets.Spreadsheets.batchUpdate({ requests: request }, spreadsheetId);
}
When this script is run, values are put from the cell "A1" in sheetId.
About grid, in this case, I think that when the start cell coordinate (the top left side) is put, this script can be used.
Note:
This is a simple sample script for using an array. So, if your values include numbers, boolean, and formulas, please use the keys of numberValue, boolValue and formulaValue. Please be careful about this. And, if you are using a JSON object, when you convert it to a 2-dimensional array, you can use it using this script.
Reference:
UpdateCellsRequest
Related
I'm trying to build a bot (with Zapier's help) to notify different people in Slack with different sets of links once a week, but every time the script run it sends all the messages to the same person with the same link, I'm not able to make the script loop to the different values in my sheet.
In Google Sheets, the column B has de ID of the person and column C has the link for that person
function buildReport() {
const ss = SpreadsheetApp.getActive();
let data = ss.getSheetByName('Team').getRange("B2:C").getValues();
data.forEach(function(row, index){
})
let payload = buildAlert(data);
sendAlert(payload);
};
function buildAlert(data) {
let id = data[0][0];
let link = data[0][1];
let payload = {
"blocks": [
{
"id": id,
"link": link
}
]
};
return payload;
}
function sendAlert(payload) {
const webhook = "https://hooks.zapier.com/hooks/catch/9909120/xxxxxxx/";
var options = {
"method": "post",
"contentType": "application/json",
"muteHttpExceptions": true,
"payload": JSON.stringify(payload)
};
try {
UrlFetchApp.fetch(webhook, options);
} catch(e) {
Logger.log(e);
}
}
You don't have anything in the forEach loop
In the buildReport function you have these lines:
data.forEach(function(row, index){
})
let payload = buildAlert(data);
sendAlert(payload);
The way a forEach works is that you pass in a function:
data.forEach(function() { /*Function body*/})
Now what will happen is that for each item in the data variable, the forEach will apply the function to it, passing in two arguments, the item and the index. This is why these functions usually handle two arguments
data.forEach(function(item, index){
console.log(item, index)
})
When you getValues from a range, your data is in this form:
[
[1,2,3],
[4,5,6],
[7,8,9]
]
That is an array of arrays or a 2D array.
When you put this through a forEach as above, the output looks like this:
[1,2,3] 0
[4,5,6] 1
[7,8,9] 2
That is, the item, and the index.
In your code, you don't have anything in the function body. So the forEach loops through each row, and does nothing!
You should move the lines into the body of the forEach function.
data.forEach(function(row, index){
let payload = buildAlert(row); // Note that the data is changed to row here.
sendAlert(payload);
})
Then in buildAlert, you need to change the code to handle just a 1D array since you are passing in a row, not the whole data.
function buildAlert(row) {
let id = row[0];
let link = row[1];
let payload = {
"blocks": [
{
"id": id,
"link": link
}
]
};
return payload;
}
Reference
forEach
function buildReport() {
const ss = SpreadsheetApp.getActive();
let data = ss.getSheetByName('Team').getRange("B2:C").getValues();
data.forEach(function(row, index){
let payload = buildAlert(row);
sendAlert(payload);
});
};
function buildAlert(row) {
let id = row[0];
let link = row[1];
let payload = {
"blocks": [
{
"id": id,
"link": link
}
]
};
return payload;
}
This SO answer correctly explains that since the require Node/JS library is not supported by Google Apps Script, the following code changes must be made to get Stripe to work properly in a GAS project:
from
const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
(async () => {
const product = await stripe.products.create({
name: 'My SaaS Platform',
type: 'service',
});
})();
to
function myFunction() {
var url = "https://api.stripe.com/v1/products";
var params = {
method: "post",
headers: {Authorization: "Basic " + Utilities.base64Encode("sk_test_4eC39HqLyjWDarjtT1zdp7dc:")},
payload: {name: "My SaaS Platform", type: "service"}
};
var res = UrlFetchApp.fetch(url, params);
Logger.log(res.getContentText())
}
Now, I want to convert the following code into the Google Apps Script friendly version.
from https://stripe.com/docs/payments/checkout/accept-a-payment#create-checkout-session
const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card', 'ideal'],
line_items: [{
price_data: {
currency: 'eur',
product_data: {
name: 'T-shirt',
},
unit_amount: 2000,
},
quantity: 1,
}],
mode: 'payment',
success_url: 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'https://example.com/cancel',
});
So, I'm trying the following.
to
function myFunction() {
var url = "https://api.stripe.com/v1/checkout/sessions";
var params = {
method: "post",
headers: {
Authorization:
"Basic " + Utilities.base64Encode("sk_test_4eC39HqLyjWDarjtT1zdp7dc:"),
},
payload: {
payment_method_types: ["card", "ideal"],
line_items: [
{
price_data: {
currency: "eur",
product_data: {
name: "T-shirt",
},
unit_amount: 2000,
},
quantity: 1,
},
],
mode: "payment",
success_url:
"https://example.com/success?session_id={CHECKOUT_SESSION_ID}",
cancel_url: "https://example.com/cancel",
},
};
var res = UrlFetchApp.fetch(url, params);
Logger.log(res.getContentText());
}
However, instead of getting the expected response object returned, I get the following error:
Exception: Request failed for https://api.stripe.com returned code 400. Truncated server response:
Log.error
{
error: {
message: "Invalid array",
param: "line_items",
type: "invalid_request_error",
},
}
What am I doing wrong? And how can I generalize the first example? What is the specific documentation I need that I'm not seeing?
Edit:
After stringifying the payload per the suggestion from the comments, now I get the following error:
Exception: Request failed for https://api.stripe.com returned code 400. Truncated server response:
Log.error
{
"error": {
"code": "parameter_unknown",
"doc_url": "https://stripe.com/docs/error-codes/parameter-unknown",
"message": "Received unknown parameter: {\"payment_method_types\":. Did you mean payment_method_types?",
"param": "{\"payment_method_types\":",
"type": "invalid_request_error"
}
}
I modified the script mentioned in the comments and now have a one that works for this purpose.
// [ BEGIN ] utilities library
/**
* Returns encoded form suitable for HTTP POST: x-www-urlformencoded
* #see code: https://gist.github.com/lastguest/1fd181a9c9db0550a847
* #see context: https://stackoverflow.com/a/63024022
* #param { Object } element a data object needing to be encoded
* #param { String } key not necessary for initial call, but used for recursive call
* #param { Object } result recursively populated return object
* #returns { Object } a stringified object
* #example
* `{
"cancel_url": "https://example.com/cancel",
"line_items[0][price_data][currency]": "eur",
"line_items[0][price_data][product_data][name]": "T-shirt",
"line_items[0][price_data][unit_amount]": "2000",
"line_items[0][quantity]": "1",
"mode": "payment",
"payment_method_types[0]": "card",
"payment_method_types[1]": "ideal",
"success_url": "https://example.com/success?session_id={CHECKOUT_SESSION_ID}"
}`
*/
const json2urlEncoded = ( element, key, result={}, ) => {
const OBJECT = 'object';
const typeOfElement = typeof( element );
if( typeOfElement === OBJECT ){
for ( const index in element ) {
const elementParam = element[ index ];
const keyParam = key ? `${ key }[${ index }]` : index;
json2urlEncoded( elementParam, keyParam, result, );
}
} else {
result[ key ] = element.toString();
}
return result;
}
// // test
// const json2urlEncoded_test = () => {
// const data = {
// time : +new Date,
// users : [
// { id: 100 , name: 'Alice' , } ,
// { id: 200 , name: 'Bob' , } ,
// { id: 300 , name: 'Charlie' , } ,
// ],
// };
// const test = json2urlEncoded( data, );
// // Logger.log( 'test\n%s', test, );
// return test;
// // Output:
// // users[0][id]=100&users[0][name]=Stefano&users[1][id]=200&users[1][name]=Lucia&users[2][id]=300&users[2][name]=Franco&time=1405014230183
// }
// // quokka
// const test = json2urlEncoded_test();
// const typeOfTest = typeof test;
// typeOfTest
// test
// [ END ] utilities library
From this question and this sample script, I thought that in this case, the values are required to be sent as the form data. So how about the following modification?
Modified script:
function myFunction() {
var url = "https://httpbin.org/anything";
var params = {
method: "post",
headers: {Authorization: "Basic " + Utilities.base64Encode("sk_test_4eC39HqLyjWDarjtT1zdp7dc:")},
payload: {
"cancel_url": "https://example.com/cancel",
"line_items[0][price_data][currency]": "eur",
"line_items[0][price_data][product_data][name]": "T-shirt",
"line_items[0][price_data][unit_amount]": "2000",
"line_items[0][quantity]": "1",
"mode": "payment",
"payment_method_types[0]": "card",
"payment_method_types[1]": "ideal",
"success_url": "https://example.com/success?session_id={CHECKOUT_SESSION_ID}"
}
};
var res = UrlFetchApp.fetch(url, params);
Logger.log(res.getContentText()); // or console.log(res.getContentText())
}
I think that point might be payment_method_types: ["card", "ideal"] is required to be sent as "payment_method_types[0]": "card" and "payment_method_types[1]": "ideal".
References:
Create a Session of official document
How to integrate Stripe payments with Google Apps Script
I am trying to update formatting of the charts using Sheets API's UpdateChartSpec request.
However, the script returns the error:
"API call to sheets.spreadsheets.batchUpdate failed with error: Internal error encountered"
Here's the snippet of my code that raises the exception:
var request = [{
'updateChartSpec': {
'chartId': chart_id,
'spec': {
'fontName': 'Arial',
'basicChart': { //to update font name, it seems that chart type should be provided
'chartType': 'BAR'
}
}
}
}];
Sheets.Spreadsheets.batchUpdate({'requests': request}, spreadsheet_id);
Can anybody tell, what's wrong with the request, if anything?
Per the "Samples" section on the Google Sheets API description, you cannot perform a partial chart specification update - you must replace the existing spec with a whole new spec.
If you just want to change a small bit of the current spec, then the simplest approach is to
Query the current chartSpec
Change the necessary bits
Issue the update with the (whole) modified spec.
In Apps Script this might be implemented as such:
function getChartSpecs(wkbkId) {
const fields = "sheets(charts(chartId,spec),properties(sheetId,title))";
var resp = Sheets.Spreadsheets.get(wkbkId, { fields: fields });
// return an object mapped by chartId, storing the chart spec and the host sheet.
return resp.sheets.reduce(function (obj, sheet) {
if (sheet.charts) {
sheet.charts.forEach(function (chart) {
obj[chart.chartId] = {
spec: chart.spec,
sheetName: sheet.properties.title,
sheetId: sheet.properties.sheetId
};
});
}
return obj;
}, {});
}
function makeChartUpdateRequest(chartId, newSpec) {
return {
updateChartSpec: {
chartId: chartId,
spec: newSpec
}
};
}
function setNewFontOnChart(newFontName, chartId, chartSpecs) {
const wb = SpreadsheetApp.getActive();
const wbId = wb.getId();
if (!chartSpecs)
chartSpecs = getChartSpecs(wbId);
var requests = [];
if (!chartId) { // Update all charts
requests = Object.keys(chartSpecs).map(function (id) {
var chart = chartSpecs[id];
chart.spec.fontName = newFontName;
return makeChartUpdateRequest(id, chart.spec);
});
} else if (chartSpecs[chartId]) { // Update just one chart.
chartSpecs[chartId].spec.fontName = newFontName;
requests.push(makeChartUpdateRequest(chartId, chartSpecs[chartId].spec));
} else {
// oops, the given chartId is not valid.
}
if (requests.length) {
Sheets.Spreadsheets.batchUpdate({ requests: requests }, wbId);
}
}
Useful links:
Partial Responses / "Fields"
APIs Explorer - spreadsheets#get
APIs Explorer - spreadsheets#batchUpdate
Array#map
Array#forEach
Array#reduce
I have a React component which is performing an axios.get call on JSON file on componentDidMount. The JSON has an object with an embedded object, like so:
This is the initial format:
"objects": {
"5bd4a1a4-f806-4355-bf34-1b4054c2881e": {
"type": "tosca.resourceTypes.TPE",
"label": "BVI 610",
"value": "801070217_BVI610"
},
and this is the console.log after my initial axios.get call.
5bd4a1a0-fd74-4a9f-b7e2-c5ab15360839: {type: "tosca.resourceTypes.TPE", label:
"Bundle-Ether 33", value: "801070217_Bundle-Ether33"}
So I could succesfully get a list of the first item using:
const finalTree = Object.keys(fullTree).map(({item}) => ({id: fullTree[item]}));
but what I need to do is convert finalTree into an array which also contains the type, label and value for each item and then put that into state. I have been messing with jsonQuery but it's not working for me and I'm a relative noobie when it comes to manipulating JSON data. Thanks in advance for any help!
You can use Object.keys to get an array of all the keys, and map that array and create a new object for each key that contains all the fields in its object and the key.
Example
const obj = {
"5bd4a1a4-f806-4355-bf34-1b4054c2881e": {
"type": "tosca.resourceTypes.TPE",
"label": "BVI 610",
"value": "801070217_BVI610"
}
};
const result = Object.keys(obj).map(key => ({
...obj[key],
id: key
}));
console.log(result);
const obj = {
"5bd4a1a4-f806-4355-bf34-1b4054c2881e": {
"type": "tosca.resourceTypes.TPE",
"label": "BVI 610",
"value": "801070217_BVI610"
}
};
const result = Object.keys(obj).map(key => ({
...obj[key],
id: key
}));
console.log(result);
I'm using the JQuery UI autocomplete plugin (cached version) with JQuery-UI 1.11.1
Due to some server-side changes in the JSON I am using as source, I need to adapt my code.
Here is an example of my JSON:
[{
"name": "Varedo"
}, {
"name": "Varena"
}, {
"name": "Varenna"
}, {
"name": "Varese"
}]
produced by an URL with this style:
[url]/?name=vare
Since the GET variable is different from the default one ("term"), I already adapted my code for the custom request as suggested here:
$(function () {
var cache = {};
$("#searchTextField").autocomplete({
minLength: 3,
source: function (request, response) {
var term = request.term;
if (term in cache) {
response(cache[term]);
return;
}
$.getJSON("[url]", {
name: request.term
}, function (data, status, xhr) {
cache[term] = data;
response(data);
});
}
});
});
However I need to also adapt the code in order to use a custom JSON value (the default is "value" http://api.jqueryui.com/autocomplete/#option-source) which is in my case is "name" (as you can see from the JSON).
How can I do that?
At the moment this is what I get from the autocomplete:
So I guess I am somehow giving as response JS Objects and not strings.
Thanks in advance.
Currently you're saving the response as it is into your cache object, which is not valid format for jQuery UI autocomplete. You should convert the data into proper format digestable for autocomplete.
Either you should pass an array of strings, or an array of objects having label and value properties.
Since the response only contains name properties, you can convert it into an array of strings using jQuery map() method and save it in cache variable as follows:
$("#searchTextField").autocomplete({
minLength: 3,
source: function (request, response) {
var term = request.term;
if (term in cache) {
response(cache[term]);
return;
}
$.getJSON("[url]", {
name: request.term
}, function (data, status, xhr) {
cache[term] = $.map(data, function (obj) { // returns array of strings
return obj.name
});
// return the new array rather than original response
response(cache[term]);
});
}
});