Quota exceeded Sheets API write requests per minute. Node.js - node.js

I'm having quota issues with the Sheets API provided by Google (using node.js). I am already using batch requests and have had my write quota increased to 900 per minute via a written request, but I still this error (question at the bottom after explanation):
What I am trying to do is generate a sports league schedule for a chosen amount of weeks (spreadsheets). Each week has 3 separate games (sheets). The user would first just choose how many weeks:
When the user clicks "Yes" for confirming the schedule, spreadsheets are created for whatever the chosen amount is:
Each spreadsheet gets 3 sheets created for the 3 different time slots and the team roster data is copied into the sheets:
Edit 3: I'm using the following code to try and generate these spreadsheets:
// Create Result Sheets
exports.createGoogleResultsSheets = async (req,res) => {
console.log("createGoogleResultsSheets");
const season = req.body.teamData[0].stats[0].season;
// console.log(season);
const resultSeasonFolders = await getChildFiles(resultParentFolderID);
// console.log(resultSeasonFolders);
const exists = propertyExists(resultSeasonFolders,'name',season);
// Create season folder if it does not exist yet and get its id
let seasonResultsFolderId = null;
if (exists) {
// console.log("Season Exists");
const result = resultSeasonFolders.filter(folder=>folder.name == season).map(folder=>folder.id);
seasonResultsFolderId = result[0];
} else {
// console.log("Season does not exist");
// Create Season Folder
let fileMetadata = {
'name': season,
'mimeType': 'application/vnd.google-apps.folder',
'parents' : [resultParentFolderID]
};
const result = await drive.files.create({auth: jwtClient, resource: fileMetadata});
// console.log(result);
seasonResultsFolderId = result.data.id;
}
// console.log("Folder ID:");
// console.log(seasonResultsFolderId);
var i = 0;
for (const week of req.body.schedule) {
//console.log(util.inspect(week, false, null, true));
i++;
// create sheet for each week
let fileMetadata = {
'name': "Week-"+i,
'mimeType': 'application/vnd.google-apps.spreadsheet',
'parents' : [seasonResultsFolderId]
};
let result = await drive.files.create({auth: jwtClient, resource: fileMetadata});
let spreadsheetId = result.data.id;
for (const game of week) {
game.teamA.data.forEach(player => {
player.push(game.teamA.name);
});
game.teamB.data.forEach(player => {
player.push(game.teamB.name);
});
// Can't have sheet names containing : if we want to use .append()
let sheetName = game.time.toString().replace(':', '.').trim();
// add each game to weeks spreadsheet
await sheets.spreadsheets.batchUpdate ({
spreadsheetId : spreadsheetId,
resource: {requests: [
{addSheet: {properties: {title: sheetName }}}
]}
});
console.log("Spreadsheet Id:");
console.log(spreadsheetId);
// console.log(sheetName);
// let sheetId = await getSheetId(spreadsheetId,sheetName);
// // format cells
// let formatResources = {
// spreadsheetId: sheetId,
// resource: {
// requests: [
// {
// repeatCell: {
// range: {
// sheetId: sheetId,
// startRowIndex: 0,
// },
// cell: {
// userEnteredFormat: {
// textFormat: {
// bold: true,
// },
// },
// },
// fields: "userEnteredFormat.textFormat",
// },
// },
// ],
// },
// };
//await sheets.spreadsheets.batchUpdate(formatResources);
// add data to each game sheet
let resources = {
spreadsheetId: spreadsheetId,
resource:{
valueInputOption: "RAW",
data:[
{
range: "'" + sheetName + "'!A1:I1",
values: [['First','Last','Email','Position','Number','Team','Goals','Assists','Penalties']]
},
{
range: "'" + sheetName + "'!K1:L1",
values: [['Team','Shots']]
},
{
range: "'" + sheetName + "'!K2:L3",
values: [[game.teamA.data.name,''],[game.teamB.data.name,'']]
},
{
range: "'" + sheetName + "'!A2:F12",
values: game.teamA.data
},
{
range: "'" + sheetName + "'!A14:F24",
values: game.teamB.data
}
]
}
};
await sheets.spreadsheets.values.batchUpdate(resources);
}
// delete "Sheet1" (gid=0) from every spreadsheet
await sheets.spreadsheets.batchUpdate({
spreadsheetId: spreadsheetId,
resource: { requests: [
{deleteSheet : {sheetId :0}}
]}
});
}
};
Note: Google Sheets API/Service Details shows that I am not using that many write requests by the way:
Note 2: And I have had my quota limit increased:
Question 1: Am I correctly using batchUpdates, or am I missing a concept that can streamline this code further?
Question 2: My calculations are that I am using 245 write calls to the sheets api, but the "API/Service Details" console is showing 31. Am I missing some concept or miscalculating this somehow? How is my quota being exceeded here? Are the nested 5 arrays a single batchUpdate or 5 batchUpdates? This would add hundreds of write calls to the sheets api if the latter applies.
API Write Calculations:
Create 105 sheets (35 weeks * 3 games)
+105 write calls
Add 5 data ranges to each of the 105 sheets
+105 write calls (or is this 105 * 5 ?)
Delete "Sheet1" from all 35 sheets using:
+35 write calls
Edit: Per request, this is what a week looks like (I cut the length of 'data' , which is normally 10 people for readability. I'd also like to note that I am not having errors with writing these ranges if I choose 1 or 2 weeks instead of 35.
week:
[
{
teamA: {
data: [
[ 'Robert', 'Manning', 'robert.manning#email.com', 'C', '45' ],
[ 'Adrian', 'Martin', 'adrian.martin#email.com', 'RW', '5' ],
],
name: 'Green'
},
teamB: {
data: [
[ 'Isaac', 'Payne', 'isaac.payne#email.com', 'C', '11' ],
[ 'Alan', 'Lewis', 'alan.lewis#email.com', 'RW', '13' ],
],
name: 'Orange'
},
time: '4:30'
},
{
teamA: {
data: [
[ 'Stewart', 'Taylor', 'stewart.taylor#email.com', 'RW', '56' ],
[ 'Lucas', 'Davies', 'lucas.davies#email.com', 'RW', '85' ],
],
name: 'Yellow'
},
teamB: {
data: [
[ 'Dylan', 'Baker', 'dylan.baker#email.com', 'C', '11' ],
[ 'Edward', 'Dowd', 'edward.dowd#email.com', 'D', '65' ],
],
name: 'Black'
},
time: '6:00'
},
{
teamA: {
data: [
[ 'Gavin', 'Knox', 'gavin.knox#email.com', 'C', '45' ],
[ 'Paul', 'Wallace', 'paul.wallace#email.com', 'RW', '5' ],
],
name: 'Red'
},
teamB: {
data: [
['Andrew','Sanderson','andrew.sanderson#email.com','C','11'],
['Stewart','MacLeod','stewart.macleod#email.com','RW','13'],
],
name: 'Teal'
},
time: '7:30'
}
]
Edit 2:
I took bits of code out to restrict the scope of the question to API calls, so things like sheetName definitions were omitted, but here it is:

I believe your goal is as follows.
You want to reduce the number of requests of Sheets API by modifying your script.
In this case, how about the following modification?
In this modification, 2 API calls are used in the loop of for (const week of req.body.schedule) {,,,}.
Modified script:
for (const week of req.body.schedule) {
i++;
let fileMetadata = {
name: "Week-" + i,
mimeType: "application/vnd.google-apps.spreadsheet",
parents: [seasonResultsFolderId],
};
let result = await drive.files.create({auth: jwtClient, resource: fileMetadata});
let sheetId = result.data.id;
const sheetNames = week.map((game) =>
game.time.toString().replace(":", ".").trim()
);
// Add sheets and delete 1st tab.
await sheets.spreadsheets.batchUpdate({
spreadsheetId: sheetId,
resource: {
requests: [
...sheetNames.map((title) => ({
addSheet: {
properties: { title },
},
})),
{ deleteSheet: { sheetId: 0 } },
],
},
});
// Put values to each sheet.
const data = week.map((game, i) => {
game.teamA.data.forEach((player) => {
player.push(game.teamA.name);
});
game.teamB.data.forEach((player) => {
player.push(game.teamB.name);
});
return [
{
range: "'" + sheetNames[i] + "'!A1:I1",
values: [
[
"First",
"Last",
"Email",
"Position",
"Number",
"Team",
"Goals",
"Assists",
"Penalties",
],
],
},
{
range: "'" + sheetNames[i] + "'!K1:L1",
values: [["Team", "Shots"]],
},
{
range: "'" + sheetNames[i] + "'!K2:L3",
values: [
[game.teamA.name, ""],
[game.teamB.name, ""],
],
},
{
range: "'" + sheetNames[i] + "'!A2:F12",
values: game.teamA.data,
},
{
range: "'" + sheetNames[i] + "'!A14:F24",
values: game.teamB.data,
},
];
});
await sheets.spreadsheets.values.batchUpdate({
spreadsheetId: sheetId,
resource: { valueInputOption: "RAW", data },
});
}
In your situation, at Method: spreadsheets.batchUpdate, all requests for adding sheets and deleting a sheet can be included in one request.
In your situation, at Method: spreadsheets.values.batchUpdate, all requests for putting values to each sheet can be included in one request.
Note:
When Sheets API is used in a loop, an error might occur because of the continuous requests. When this script is run, such error occurs, please put the script for waiting in the loop.
If you want to reduce the number of requests of Drive API, the batch requests might be able to be used. Ref
References:
Method: spreadsheets.batchUpdate
Method: spreadsheets.values.batchUpdate

Related

Add information to a spreadsheet from the Google API

I have a Typescript and Node project in which I am trying to insert the information I get from the database into the spreadsheet using the Google API V4
https://sheets.googleapis.com/v4/spreadsheets/{spreadsheetId}:batchUpdate
This is the JSON object that I get from the database:
let sheetData = [
{
"country":null,
"age":25,
"fileName":"testName1"
},
{
"country":"Spain",
"age":null,
"fileName":"testName2"
}
]
I transform it with papaparse:
const papa = require("papaparse")
let result = papa.unparse(sheetData, {
header: false,
delimiter: ';',
encoding: 'UTF-8',
})
console.log(result)
This is what I get:
;25;testName1
Spain;;testName2
This is the xml that I use from the API to add the information:
{
"requests": [
{
"pasteData": {
"coordinate": {
"sheetId": 123,
"rowIndex": 2,
"columnIndex": 1
},
"delimiter": ";",
"type": "PASTE_VALUES",
"data": ";25;testName1Spain;;testName2"
}
}
]
}
I attach a screenshot with the result of the sheet:
My problem: All the information is put in the same row, how do I have to modify the array to include line breaks and be identified by the API?
This is the JSON that works from the API by adding \n:
{
"requests": [
{
"pasteData": {
"coordinate": {
"sheetId": 123,
"rowIndex": 2,
"columnIndex": 1
},
"delimiter": ";",
"type": "PASTE_VALUES",
"data": ";25;testName1\nSpain;;testName2"
}
}
]
}
This is the result I want to achieve, but I don't know how to treat the JSON with the information I get:
I thought that in your script, how about the following sample script?
Sample script:
const sheets = google.sheets({ version: "v4", auth }); // Please use your authorization script.
let sheetData = [
{
country: null,
age: 25,
fileName: "testName1",
},
{
country: "Spain",
age: null,
fileName: "testName2",
},
];
let result = papa.unparse(sheetData, {
header: false,
delimiter: ";",
encoding: "UTF-8",
});
console.log(result);
const requests = {
requests: [
{
pasteData: {
coordinate: {
sheetId: 123, // Please set your sheet ID.
rowIndex: 2,
columnIndex: 1,
},
delimiter: ";",
type: "PASTE_VALUES",
data: result,
},
},
],
};
sheets.spreadsheets.batchUpdate(
{
spreadsheetId: "###", // Please set your Spreadsheet ID.
resource: requests,
},
(err, result) => {
if (err) {
console.log(err);
return;
}
console.log(result.data);
}
);
In this modification, your value of result is used as the data of pasteData request.
When I tested this script, I confirmed that your expected result can be obtained.

Paypal window not showing multiple items

I'm creating my own cart and then using Paypal smart button for the payment in Angular.
For multiple items to handle, I'm uing items array in createOrder method at the backend in Express.
function arrayOfItems() {
art_details.forEach((item, index) => {
let sku = item.message;
let currency = priceDetails.collPriceL[index];
let tax = priceDetails.taxAmtL[index];
let quantity = item.quantity;
let items = [
{
name: "Collection",
sku: sku,
description: '' + item.collid,
unit_amount: { currency_code: "CAD", value: "" + currency },
tax: { currency_code: "CAD", value: "" + tax },
quantity: quantity,
},
];
return items;
});
}
I'm now using arrayOfItems() as items in createOrder:
const request = new checkoutNodeJssdk.orders.OrdersCreateRequest();
request.prefer("return=representation");
request.requestBody({
intent: "CAPTURE",
purchase_units: [
{
amount: {
currency_code: "CAD",
value: ...,
breakdown: {
...
},
},
soft_descriptor: orderkey,
items: arrayOfItems(),
shipping: {
...
},
},
],
});
Suppose I'm creating order for 2 items. art_details contains array of items I need to purchase. My order is creating successfully but the Paypal window doesn't show the items in right side. (it should appear as a dropdown of items).
What am I missing here?
Thanks

Payload Syntax for batchUpdate

I am using the googleapis module in a Node application. I was using version 21 but have just updated to version 52 due to a security vulnerability in the older version.
There are several breaking changes. I have overcome most except for formatting a date/time string. Is the following payload correct for formatting a date/time value in cell A11?
const formatDate = (next) => {
sheets.spreadsheets.batchUpdate({
auth: authClient,
spreadsheetId: sheetId,
requestBody: {
requests: [{
"repeatCell": {
range: { sheetId: 0, startRowIndex: 10, endRowIndex: 11, startColumnIndex: 0, endColumnIndex: 1},
cell: { userEnteredFormat: { numberFormat: { "type": "DATE_TIME", "pattern": "ddd yyyy-mm-dd hh:mm" } } },
fields: "userEnteredFormat.numberFormat"
}
}]
}
}, (err, response) => {
// ...
next();
}
);
}
No errors were returned with the above payload, but the formatting is not taking place. Is the key requestBody? Previously I was using resource.
I used async to perform authentication before formatting the date:
const authClient = new google.auth.JWT(client_email, null, private_key, SCOPES, null);
const sheetId = "1vgiEnV8fU_MrnIy31fbPAzhHz.......";
function authenticate(next) {
authClient.authorize((err) => {
next(err);
}
}
const tasks = [ authenticate, insertRow, formatdate ];
require("async").series(tasks);
Code for insertRow is not included here, but that works without problem.
I think that your script is correct. The cell format of "A11" is modified with the request body. And in this case, the request body can be used for both requestBody and resource.
But please confirm the following points, again.
sheetId of spreadsheetId: sheetId, is required to be the Spreadsheet ID.
In this case, when =now() is put to the cell "A11" and run the script, you can see the modified cell format.
By the following modification, you can check the returned values from Sheets API.
sheets.spreadsheets.batchUpdate(
{
auth: authClient,
spreadsheetId: "spreadsheetId", // <--- Please check this.
requestBody: {
requests: [
{
repeatCell: {
range: {
sheetId: 0,
startRowIndex: 10,
endRowIndex: 11,
startColumnIndex: 0,
endColumnIndex: 1,
},
cell: {
userEnteredFormat: {
numberFormat: {
type: "DATE_TIME",
pattern: "ddd yyyy-mm-dd hh:mm",
},
},
},
fields: "userEnteredFormat.numberFormat",
},
},
],
},
},
(err, res) => {
if (err) {
console.log(err);
return;
}
console.log(res.data);
}
);
Note:
In my environment, I tested your script with googleapis#52.1.0, and I could confirm the script worked.
This modified script supposes that your authorization process for using Sheets API has already been done.
References:
googleapis for Node.js
Method: spreadsheets.batchUpdate
RepeatCellRequest
Added:
Sample script for testing:
const client_email = "###"; // Please set here.
const private_key = "###"; // Please set here.
const spreadsheetId = "###"; // Please set here.
const { google } = require("googleapis");
let jwtClient = new google.auth.JWT(
client_email,
null,
private_key,
["https://www.googleapis.com/auth/spreadsheets"]
);
jwtClient.authorize((err) => {
if (err) console.log(err);
});
const sheets = google.sheets({ version: "v4", auth: jwtClient });
sheets.spreadsheets.batchUpdate(
{
spreadsheetId: spreadsheetId,
requestBody: {
requests: [
{
repeatCell: {
range: {
sheetId: 0,
startRowIndex: 10,
endRowIndex: 11,
startColumnIndex: 0,
endColumnIndex: 1,
},
cell: {
userEnteredFormat: {
numberFormat: {
type: "DATE_TIME",
pattern: "ddd yyyy-mm-dd hh:mm",
},
},
},
fields: "userEnteredFormat.numberFormat",
},
},
],
},
},
(err, res) => {
if (err) {
console.log(err);
return;
}
console.log(res.data);
}
);

Google Sheets API NodeJS - Update cell without changing format

Have written NodeJS code to Insert a Row in a Google Sheet using the API and updating the row values.
I insert the row with inheritFromBefore: true, which is working fine (I tested it by commenting out the updateOption). But while updating the cell the original format is not getting preserved; rather it is changing to default blank format.
function getSheetRow(jobData) {
const jobID = jobData.getJobID();
const jobName = jobData.getJobName();
const company = jobData.getCompany();
const pmName = jobData.getPMName();
let rowData = [
{ userEnteredValue: { stringValue: jobID } },
{ userEnteredValue: { stringValue: jobName + " (" + company + ", " + pmName + ")" } }
];
return rowData;
}
async function googleSheetInsertRow(workbookID, sheetNum, insertRowIndex, jobData) {
let sheetIdMap = await googleSheetsGetSheetIDTitle(workbookID);
let rowData = getSheetRow(jobData);
return new Promise((resolve, reject) => {
const insertOption = {
insertDimension: {
range: {
sheetId: sheetNum,
dimension: 'ROWS',
startIndex: insertRowIndex,
endIndex: insertRowIndex + 1
},
inheritFromBefore: true
}
};
const updateOption = {
updateCells: {
rows: [{
values: rowData
}],
fields: '*',
start: {
sheetId: sheetNum,
rowIndex: insertRowIndex,
columnIndex: 0
}
}
};
getGSheetsAPI().spreadsheets.batchUpdate({
spreadsheetId: workbookID,
resource: {
requests: [
insertOption, updateOption
]
}
},
(er, re) => {
if (er) {
console.log(`Unable to insert new row into ${googleSheetName} : https://docs.google.com/spreadsheets/d/${workbookID}`);
return reject(er);
}
else {
console.log(`New row inserted into ${googleSheetName} : https://docs.google.com/spreadsheets/d/${workbookID}`);
return resolve();
}
}
);
});
}
[In the picture below] I have inserted a row in between Row 6 & 8. And Row 7 is inheriting the format from the row before, but while updating the cell value it is not getting preserved.
Looking for help. Thanks.
I think that in your issue, when userEnteredValue or userEnteredValue.stringValue is used to fields, the issue can be resolved. When you want to use the values except for stringValue, I would like to recommend to use userEnteredValue as the fields.
From:
const updateOption = {
updateCells: {
rows: [{
values: rowData
}],
fields: '*',
start: {
sheetId: sheetNum,
rowIndex: insertRowIndex,
columnIndex: 0
}
}
};
To:
const updateOption = {
updateCells: {
rows: [
{
values: rowData,
},
],
fields: "userEnteredValue", // <--- Modified
start: {
sheetId: sheetNum,
rowIndex: insertRowIndex,
columnIndex: 0,
},
},
};
Reference:
UpdateCellsRequest

Google Sheets API: insert formatted text

I am trying to insert bolded text into Google Spreadsheet. I have the following NodeJS code that inserts the text successfully.
var body = {
data: [{
range: 'M2:M3',
values: [["value1"],["value2"]]
}],
valueInputOption: 'USER_ENTERED',
};
var sheets = google.sheets('v4');
sheets.spreadsheets.values.batchUpdate({
auth: auth,
spreadsheetId: SPREADSHEET_ID,
resource: body
}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
});
I looked up and found that in order to change the text format to bold, the following code needs to be used.
requests: [
{
"format": {
"textFormat": {
"bold": true
}
}
}
]
However, I cannot understand where this code should go. Tried putting requests as well as format separately inside data and batchUpdate but it did not work.
How about this sample script? If you want to update cell values and cell format, simultaneously, you can use spreadsheets.batchUpdate. The detail information is here.
The following sample imports [["value1"],["value2"]] to 'M2:M3' as shown in your script. As a sample, Sheet id is 0. The range is required to be inputted the GridRange.
Sample script :
var sheets = google.sheets('v4');
var request = {
spreadsheetId: SPREADSHEET_ID,
resource: {
"requests": [
{
"updateCells": {
"rows": [
{
"values": [
{
"userEnteredValue": {
"stringValue": "value1"
},
"userEnteredFormat": {
"textFormat": {
"bold": true
}
}
}
]
},
{
"values": [
{
"userEnteredValue": {
"stringValue": "value2"
},
"userEnteredFormat": {
"textFormat": {
"bold": true
}
}
}
]
}
],
"range": {
"sheetId": 0, // Sheet id
"startColumnIndex": 12,
"endColumnIndex": 13,
"startRowIndex": 1,
"endRowIndex": 3
},
"fields": "*"
}
}
]
},
auth: auth,
};
sheets.spreadsheets.batchUpdate(request, function(err, response) {
if (err) {
console.error(err);
return;
}
console.log(JSON.stringify(response, null, 2));
});
If I misunderstand your question, I'm sorry.

Resources