Skip to content

Commit

Permalink
fix loadCells when using an API key only
Browse files Browse the repository at this point in the history
  • Loading branch information
theoephraim committed Apr 12, 2020
1 parent abb8217 commit 15a3619
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 9 deletions.
4 changes: 2 additions & 2 deletions docs/classes/google-spreadsheet-worksheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,9 @@ Param|Type|Required|Description
#### `loadCells(filters)` (async) :id=fn-loadCells
> Fetch cells from google
You can filter the cells you want to fetch in several ways.
You can filter the cells you want to fetch in several ways. See [Data Filters](https://developers.google.com/sheets/api/reference/rest/v4/DataFilter) for more info. Strings are treated as A1 ranges, objects are detected to be a [GridRange](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#GridRange) with sheetId not required.

See [Data Filters](https://developers.google.com/sheets/api/reference/rest/v4/DataFilter) for more info. Strings are treated as A1 ranges, objects are detected to be a [GridRange](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#GridRange) with sheetId not required.
**NOTE - if using an API key (read-only access), only A1 ranges are supported**

```javascript
await sheet.loadCells(); // no filter - will load ALL cells in the sheet
Expand Down
47 changes: 42 additions & 5 deletions lib/GoogleSpreadsheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ class GoogleSpreadsheet {
// create an axios instance with sheet root URL and interceptors to handle auth
this.axios = Axios.create({
baseURL: `https://sheets.googleapis.com/v4/spreadsheets/${sheetId}`,
// send arrays in params with duplicate keys - ie `?thing=1&thing=2` vs `?thing[]=1...`
// solution taken from https://github.com/axios/axios/issues/604
paramsSerializer(params) {
let options = '';
_.keys(params).forEach((key) => {
const isParamTypeObject = typeof params[key] === 'object';
const isParamTypeArray = isParamTypeObject && (params[key].length >= 0);
if (!isParamTypeObject) options += `${key}=${encodeURIComponent(params[key])}&`;
if (isParamTypeObject && isParamTypeArray) {
_.each(params[key], (val) => {
options += `${key}=${encodeURIComponent(val)}&`;
});
}
});
return options ? options.slice(0, -1) : options;
},
});
// have to use bind here or the functions dont have access to `this` :(
this.axios.interceptors.request.use(this._setAxiosRequestAuth.bind(this));
Expand Down Expand Up @@ -292,20 +308,41 @@ class GoogleSpreadsheet {
// objects are treated as GridRange objects
// TODO: make it support DeveloperMetadataLookup objects

// TODO: switch to this mode if using a read-only auth token?
const readOnlyMode = this.authMode === AUTH_MODES.API_KEY;

const filtersArray = _.isArray(filters) ? filters : [filters];
const dataFilters = _.map(filtersArray, (filter) => {
if (_.isString(filter)) return { a1Range: filter };
if (_.isString(filter)) {
return readOnlyMode ? filter : { a1Range: filter };
}
if (_.isObject(filter)) {
if (readOnlyMode) {
throw new Error('Only A1 ranges are supported when fetching cells with read-only access (using only an API key)');
}
// TODO: make this support Developer Metadata filters
return { gridRange: filter };
}
throw new Error('Each filter must be an A1 range string or a gridrange object');
});

const result = await this.axios.post(':getByDataFilter', {
includeGridData: true,
dataFilters,
});
let result;
// when using an API key only, we must use the regular get endpoint
// because :getByDataFilter requires higher access
if (this.authMode === AUTH_MODES.API_KEY) {
result = await this.axios.get('/', {
params: {
includeGridData: true,
ranges: dataFilters,
},
});
// otherwise we use the getByDataFilter endpoint because it is more flexible
} else {
result = await this.axios.post(':getByDataFilter', {
includeGridData: true,
dataFilters,
});
}

const { sheets } = result.data;
_.each(sheets, (sheet) => { this._updateOrCreateSheet(sheet); });
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"author": "Theo Ephraim <[email protected]> (https://theoephraim.com)",
"name": "google-spreadsheet",
"description": "Google Sheets API (v4) -- simple interface to read/write data and manage sheets",
"version": "3.0.10",
"version": "3.0.11",
"license": "Unlicense",
"keywords": [
"google spreadsheets",
Expand Down
8 changes: 7 additions & 1 deletion test/auth.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const _ = require('lodash');
const delay = require('delay');

const GoogleSpreadsheetCell = require('../lib/GoogleSpreadsheetCell');

const docs = require('./load-test-docs')();
const creds = require('./service-account-creds.json');
const apiKey = require('./api-key');
Expand All @@ -16,10 +18,14 @@ function checkDocAccess(docType, spec) {
expect(doc.title).toBeTruthy();
sheet = doc.sheetsByIndex[0];
});
it('reading data should succeed', async () => {
it('reading row data should succeed', async () => {
const rows = await sheet.getRows();
expect(rows).toBeInstanceOf(Array);
});
it('reading cell data should succeed', async () => {
await sheet.loadCells('A1');
expect(sheet.getCell(0, 0)).toBeInstanceOf(GoogleSpreadsheetCell);
});
} else {
it('reading info should fail', async () => {
await expect(doc.loadInfo()).rejects.toThrow(spec.readError);
Expand Down

0 comments on commit 15a3619

Please sign in to comment.