Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/new-things-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@openfn/language-googlesheets': minor
---

`batchUpdateValues()` now accepts a `data` array of `{ range, values }` objects, enabling multi-range updates in a single API call. The existing `range` + `values` params continue to work unchanged.
24 changes: 21 additions & 3 deletions packages/googlesheets/ast.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,13 @@
"tags": [
{
"title": "example",
"description": "batchUpdateValues({\n spreadsheetId: '1O-a4_RgPF_p8W3I6b5M9wobA3-CBW8hLClZfUik5sos',\n range: 'Sheet1!A1:E1',\n values: [\n ['From expression', '$15', '2', '3/15/2016'],\n ['Really now!', '$100', '1', '3/20/2016'],\n ],\n})"
"description": "batchUpdateValues({\n spreadsheetId: '1O-a4_RgPF_p8W3I6b5M9wobA3-CBW8hLClZfUik5sos',\n range: 'Sheet1!A1:E1',\n values: [\n ['From expression', '$15', '2', '3/15/2016'],\n ['Really now!', '$100', '1', '3/20/2016'],\n ],\n})",
"caption": "Update a single range"
},
{
"title": "example",
"description": "batchUpdateValues({\n spreadsheetId: '1O-a4_RgPF_p8W3I6b5M9wobA3-CBW8hLClZfUik5sos',\n data: [\n { range: 'Sheet1!A1', values: [['value1']] },\n { range: 'Sheet1!B5', values: [['value2']] },\n { range: 'Sheet1!D10:E11', values: [['a', 'b'], ['c', 'd']] },\n ],\n valueInputOption: 'RAW',\n})",
"caption": "Update multiple ranges"
},
{
"title": "function",
Expand Down Expand Up @@ -135,7 +141,7 @@
},
{
"title": "param",
"description": "The range of values to update.",
"description": "The range of values to update (single-range form).",
"type": {
"type": "OptionalType",
"expression": {
Expand All @@ -159,7 +165,7 @@
},
{
"title": "param",
"description": "A 2d array of values to update.",
"description": "A 2d array of values to update (single-range form).",
"type": {
"type": "OptionalType",
"expression": {
Expand All @@ -169,6 +175,18 @@
},
"name": "params.values"
},
{
"title": "param",
"description": "An array of ValueRange objects `({ range, values })` for updating multiple ranges at once.",
"type": {
"type": "OptionalType",
"expression": {
"type": "NameExpression",
"name": "array"
}
},
"name": "params.data"
},
{
"title": "param",
"description": "(Optional) callback function",
Expand Down
3 changes: 2 additions & 1 deletion packages/googlesheets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"chai": "4.3.6",
"deep-eql": "4.1.1",
"nock": "13.2.9",
"rimraf": "3.0.2"
"rimraf": "3.0.2",
"sinon": "^21.1.2"
},
"type": "module",
"types": "types/index.d.ts",
Expand Down
42 changes: 31 additions & 11 deletions packages/googlesheets/src/Adaptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export function appendValues(params, callback = s => s) {
/**
* Batch update values in a Spreadsheet.
* @example
* <caption>Update a single range</caption>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice for the single range API to take a single object, rather than an array of one. Just a little API convenience

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@josephjclark can you say more 🤔 , values are rows data do you mean we should pass values: [{columnHeader: value, colHeader: value}, {...}]?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So multiple ranges is:

batchUpdateValues(id, [set1, set2])

Where a set is { range, values }
And a single range so far is:

batchUpdateValues(id, [set1])

I'm saying allow a single range to be passed like this:

batchUpdateValues(id, set1)

BUT THEN AGAIN

That makes the API the same as the appendValues API, which only confuses the two.

Ok, so we should force data to be an array of ranges. But do not show an example with an array of just 1 range because it's wierd

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think function naming is what causing the confusion here. There are singular actions and plural actions
For example:

  • If we had appendValue(id, range, data, opt) this is clear, add new row to gsheet
  • For appendValues(id, [{set0, set1}], opt), this is clear adding multiple rows

The same could apply for batchUpdateValues()

  • batchUpdate(id, range, value,opt)
  • batchUpdate(id, [{set0}, {set1}])

Of course adding good docs that explain what append does, and what does batchUpdate do? because their different.

I will remove the single batch update example

* batchUpdateValues({
* spreadsheetId: '1O-a4_RgPF_p8W3I6b5M9wobA3-CBW8hLClZfUik5sos',
* range: 'Sheet1!A1:E1',
Expand All @@ -147,13 +148,25 @@ export function appendValues(params, callback = s => s) {
* ['Really now!', '$100', '1', '3/20/2016'],
* ],
* })
* @example
* <caption>Update multiple ranges</caption>
* batchUpdateValues({
Comment thread
hunterachieng marked this conversation as resolved.
Outdated
* spreadsheetId: '1O-a4_RgPF_p8W3I6b5M9wobA3-CBW8hLClZfUik5sos',
* data: [
* { range: 'Sheet1!A1', values: [['value1']] },
* { range: 'Sheet1!B5', values: [['value2']] },
* { range: 'Sheet1!D10:E11', values: [['a', 'b'], ['c', 'd']] },
* ],
* valueInputOption: 'RAW',
* })
* @function
* @public
* @param {Object} params - Data object to add to the spreadsheet.
* @param {string} [params.spreadsheetId] The spreadsheet ID.
* @param {string} [params.range] The range of values to update.
* @param {string} [params.range] The range of values to update (single-range form).
* @param {string} [params.valueInputOption] (Optional) Value update options. Defaults to 'USER_ENTERED'
* @param {array} [params.values] A 2d array of values to update.
* @param {array} [params.values] A 2d array of values to update (single-range form).
* @param {array} [params.data] An array of ValueRange objects `({ range, values })` for updating multiple ranges at once.
* @param {function} callback - (Optional) callback function
* @returns {Operation} spreadsheet information
*/
Expand All @@ -166,20 +179,27 @@ export function batchUpdateValues(params, callback = s => s) {
range,
valueInputOption = 'USER_ENTERED',
values,
data,
} = resolvedParams;

if (!values || values.length === 0) {
console.log('Warning: empty values array');
return state;
let rangeData;

if (data !== undefined) {
if (!data || data.length === 0) {
console.log('Warning: empty data array');
return state;
}
rangeData = data;
} else {
if (!values || values.length === 0) {
console.log('Warning: empty values array');
return state;
}
rangeData = [{ range, values }];
}

const resource = {
data: [
{
range,
values,
},
],
data: rangeData,
valueInputOption,
};
try {
Expand Down
83 changes: 81 additions & 2 deletions packages/googlesheets/test/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { expect } from 'chai';
import sinon from 'sinon';
import { google } from 'googleapis';

import { execute ,appendValues, batchUpdateValues } from '../src/index.js';
import { execute, appendValues, batchUpdateValues } from '../src/index.js';


describe('execute', () => {
Expand Down Expand Up @@ -55,7 +57,7 @@ describe('append', () =>{
});

describe('batchUpdateValues', () =>{
it('should return early if the values array is undefined or nullish', async() => {
it('should return early if the values array is undefined or nullish', async() => {
const state = {
data: [],
}
Expand All @@ -67,4 +69,81 @@ describe('batchUpdateValues', () =>{
})(state);
expect(result).to.eql(state);
});

it('should return early if the data array is empty', async() => {
const state = {
data: [],
};

const result = await batchUpdateValues({
spreadsheetId: '123-456-789',
data: [],
})(state);
expect(result).to.eql(state);
});

describe('with mocked Google Sheets client', () => {
Comment thread
mtuchi marked this conversation as resolved.
Outdated
let sandbox;
let mockBatchUpdate;

beforeEach(() => {
sandbox = sinon.createSandbox();

mockBatchUpdate = sandbox.stub().resolves({
data: { totalUpdatedCells: 3 },
});

sandbox.stub(google, 'sheets').returns({
spreadsheets: {
values: { batchUpdate: mockBatchUpdate },
},
});
});

afterEach(() => {
sandbox.restore();
});

it('should send multi-range data directly to the API', async () => {
const state = { configuration: { access_token: 'mock-token' }, data: {} };
const multiRangeData = [
{ range: 'Sheet1!A1', values: [['value1']] },
{ range: 'Sheet1!B5', values: [['value2']] },
{ range: 'Sheet1!D10:E11', values: [['a', 'b'], ['c', 'd']] },
];

await execute(
batchUpdateValues({
spreadsheetId: '123-456-789',
data: multiRangeData,
valueInputOption: 'RAW',
})
)(state);

expect(mockBatchUpdate.calledOnce).to.be.true;
const { resource } = mockBatchUpdate.firstCall.args[0];
expect(resource.data).to.deep.equal(multiRangeData);
expect(resource.valueInputOption).to.equal('RAW');
});

it('should wrap single range/values into a data array (fallback)', async () => {
const state = { configuration: { access_token: 'mock-token' }, data: {} };

await execute(
batchUpdateValues({
spreadsheetId: '123-456-789',
range: 'Sheet1!A1:B2',
values: [['a', 'b'], ['c', 'd']],
valueInputOption: 'USER_ENTERED',
})
)(state);

expect(mockBatchUpdate.calledOnce).to.be.true;
const { resource } = mockBatchUpdate.firstCall.args[0];
expect(resource.data).to.deep.equal([
{ range: 'Sheet1!A1:B2', values: [['a', 'b'], ['c', 'd']] },
]);
expect(resource.valueInputOption).to.equal('USER_ENTERED');
});
});
});
34 changes: 34 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading