Skip to content

Commit

Permalink
feat(app): add dateFormat support to allow Sheets to render date-char…
Browse files Browse the repository at this point in the history
…ts naturally
  • Loading branch information
uglow committed Sep 18, 2018
1 parent 3dda212 commit 831ab4f
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 23 deletions.
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@ addUnitTestMetrics();
// See Authentication section for how to generate this information
const creds = require('./google-generated-creds.json');
// OR, if you cannot save the file locally (like on heroku)
const creds = {
const options = {
client_email: process.env.SMETRICS_GOOGLE_SERVICE_ACCOUNT_CLIENT_EMAIL,
private_key: process.env.SMETRICS_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY
private_key: process.env.SMETRICS_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY,
dateTimeFormat: 'googleDate', // defaults to 'milliseconds'
}
smetrics.commit('<spreadsheet key>', creds); // Async - returns a promise
smetrics.commit('<spreadsheet key>', options); // Async - returns a promise

```

Expand All @@ -54,6 +55,25 @@ smetrics.commit('<spreadsheet key>', creds); // Async - returns a promise
should open the corresponding Google Sheet and change the column-order to match your new metric-capturing order.


### The `options` parameter

#### `client_email` (string)

This value is available in the JSON file that you can download when setting up Authentication with a service account.

#### `private_key` (string)

This value is available in the JSON file that you can download when setting up Authentication with a service account.

#### `dateTimeFormat` (string, default = 'milliseconds')

The default format for the DateTime column is `milliseconds`, which is the number of milliseconds since the epoch (e.g. 1537165777561,
which is equivalent to Mon Sep 17 2018 16:29:37 GMT+1000 (Australian Eastern Standard Time)).

Alternately, you can specify the format as `googleDate`, which formats the date as `dd-mon-yyyy hh:mm:ss`.
Google sheets interprets this string as a date, and can be used correctly when the data is charted. You
may need to manually format the DateTime column as a 'Date Time' in the Google Sheet (once-only).

## How it works

Every time a metric is added using this module, a temporary file (`smetrics.json`, [example](fixtures/smetrics.json)) is created/updated in your
Expand Down
6 changes: 3 additions & 3 deletions fixtures/smetrics.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@
"sheet": "sheet a",
"metric": "Metric 1",
"value": 11,
"t": 1536627335859
"t": 1537229759962
},
{
"sheet": "sheet b",
"metric": "Objects of the future",
"value": "car",
"t": 1536627335862
"t": 1537229769962
},
{
"sheet": "sheet a",
"metric": "Metric 2",
"value": "503",
"t": 1536627335863
"t": 1537229779962
}
]
]
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"semantic-release": "15.9.14"
},
"engines": {
"node": ">=8.x",
"node": ">=8.2",
"npm": ">=6.1"
}
}
27 changes: 24 additions & 3 deletions src/__snapshots__/sheet.spec.js.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`SheetService .addData() should apply the dateFormat correctly when calling appendData() 1`] = `
Array [
"sheet a",
Array [
"18-Sep-2018 0:15:59",
11,
"503",
],
]
`;

exports[`SheetService .addData() should apply the dateFormat correctly when calling appendData() 2`] = `
Array [
"sheet b",
Array [
"18-Sep-2018 0:16:09",
"car",
],
]
`;

exports[`SheetService .addData() should be able to process two-or-more records at a time 1`] = `
Array [
"sheet a",
Expand All @@ -24,7 +45,7 @@ exports[`SheetService .addData() should call createSheet(), updateHeader() and a
Array [
"sheet a",
Array [
1536627335859,
1537229759962,
11,
"503",
],
Expand All @@ -35,7 +56,7 @@ exports[`SheetService .addData() should call createSheet(), updateHeader() and a
Array [
"sheet b",
Array [
1536627335862,
1537229769962,
"car",
],
]
Expand All @@ -57,7 +78,7 @@ Array [
],
},
"method": "post",
"url": "https://sheets.googleapis.com/v4/spreadsheets/foo/values/'Sheet 2'!A1:C1:append?access_token=token&valueInputOption=RAW&insertDataOption=INSERT_ROWS",
"url": "https://sheets.googleapis.com/v4/spreadsheets/foo/values/'Sheet 2'!A1:C1:append?access_token=token&valueInputOption=USER_ENTERED&insertDataOption=INSERT_ROWS",
},
],
]
Expand Down
8 changes: 4 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ function addMetric(category, metric, value, timeStamp = Date.now()) {

/**
* Attempts to write to Google sheets using the credentials supplied. Asynchronous (returns a Promise)
* @param spreadsheetKey
* @param credentials
* @param spreadsheetKey {string}
* @param options {Object}
*/
async function commit(spreadsheetKey, credentials, sheetService = new SheetService(spreadsheetKey)) {
async function commit(spreadsheetKey, options, sheetService = new SheetService(spreadsheetKey, options)) {
let data;
try {
data = readMetricFile();
Expand All @@ -45,7 +45,7 @@ async function commit(spreadsheetKey, credentials, sheetService = new SheetServi
return;
}

await sheetService.authorize(credentials);
await sheetService.authorize(options);

// Check if the first row has data.
try {
Expand Down
21 changes: 17 additions & 4 deletions src/sheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,24 @@ const {JWT} = require('google-auth-library');
const GOOGLE_AUTH_SCOPES = ['https://www.googleapis.com/auth/spreadsheets'];
const SHEETS_API = 'https://sheets.googleapis.com/v4/spreadsheets/';

const MONTH = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

const DATE_FORMATS = {
milliseconds: (val) => val,
googleDate: (val) => {
const d = new Date(val);
return [d.getUTCDate(), MONTH[d.getUTCMonth()], d.getUTCFullYear()].join('-') +
' ' + [d.getUTCHours(), String(d.getUTCMinutes()).padStart(2, '0'), String(d.getSeconds()).padStart(2, '0')].join(':')
},
};

class SheetService {
constructor(spreadsheetId) {
constructor(spreadsheetId, { dateFormat = 'milliseconds' } = {}) {
if (!spreadsheetId) throw new Error('Spreadsheet id must be supplied');

this.spreadsheetId = spreadsheetId;

if (!DATE_FORMATS[dateFormat]) throw new Error(`Invalid dateFormat. dateFormat must be one of: ${Object.keys(DATE_FORMATS).join(', ')}`);
this.dateFormat = dateFormat;
}

authorize(creds, authService = JWT) {
Expand Down Expand Up @@ -37,7 +50,7 @@ class SheetService {
}
// Update the header then add a new row
await this.updateHeader(sheet, ['DateTime'].concat(groupedItems[sheet].map(item => item.metric)))
.then(() => this.appendData(sheet, [groupedItems[sheet][0].t].concat(groupedItems[sheet].map(item => item.value))));
.then(() => this.appendData(sheet, [DATE_FORMATS[this.dateFormat](groupedItems[sheet][0].t)].concat(groupedItems[sheet].map(item => item.value))));
});
});
}
Expand All @@ -62,7 +75,7 @@ class SheetService {

async appendData(sheetName, values) {
const range = `'${sheetName}'!A1:${columnToLetter(values.length)}1`; // +1 is the DateTime generated-column
const url = `${SHEETS_API}${this.spreadsheetId}/values/${range}:append?access_token=${this.client.credentials.access_token}&valueInputOption=RAW&insertDataOption=INSERT_ROWS`;
const url = `${SHEETS_API}${this.spreadsheetId}/values/${range}:append?access_token=${this.client.credentials.access_token}&valueInputOption=USER_ENTERED&insertDataOption=INSERT_ROWS`;
const data = {
range,
majorDimension: 'ROWS',
Expand Down
34 changes: 33 additions & 1 deletion src/sheet.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,20 @@ describe('SheetService', () => {

it('should be createable using a non-blank spreadsheet id', () => {
expect(() => new SheetService()).toThrow('Spreadsheet id must be supplied');
expect(new SheetService('foo')).toBeDefined();

const service = new SheetService('foo');
expect(service).toBeDefined();
expect(service.dateFormat).toEqual('milliseconds');
});

it('should be possible to specify the dateFormat as "googleDate" or "milliseconds"', () => {
let service = new SheetService('foo', {dateFormat: 'googleDate'});
expect(service.dateFormat).toEqual('googleDate');

service = new SheetService('foo', {dateFormat: 'milliseconds'});
expect(service.dateFormat).toEqual('milliseconds');

expect(() => new SheetService('foo', {dateFormat: 'unknown'})).toThrow('Invalid dateFormat. dateFormat must be one of: milliseconds, googleDate');
});

describe('.authorize()', () => {
Expand Down Expand Up @@ -95,6 +108,25 @@ describe('SheetService', () => {
expect(service.appendData.mock.calls[1]).toMatchSnapshot();
});

it('should apply the dateFormat correctly when calling appendData()', async () => {
const service = new SheetService('foo', { dateFormat: 'googleDate' });
service.createSheet = jest.fn().mockResolvedValue();
service.updateHeader = jest.fn().mockResolvedValue();
service.appendData = jest.fn().mockResolvedValue();

// Well this is a bit weird! Thanks to Jest. In non-Jest context, you don't need to do this.
await await await await service.addData(require('../fixtures/smetrics.json'));

expect(service.createSheet.mock.calls[0]).toEqual(['sheet a']);
expect(service.createSheet.mock.calls[1]).toEqual(['sheet b']);

expect(service.updateHeader.mock.calls[0]).toEqual(['sheet a', ['DateTime', 'Metric 1', 'Metric 2']]);
expect(service.updateHeader.mock.calls[1]).toEqual(['sheet b', ['DateTime', 'Objects of the future']]);

expect(service.appendData.mock.calls[0]).toMatchSnapshot();
expect(service.appendData.mock.calls[1]).toMatchSnapshot();
});

it('should still add data even if createSheet() rejects (as happens when the sheet exists already)', async () => {
const service = new SheetService('foo');
service.createSheet = jest.fn().mockRejectedValue({ errors: [{ message: 'Invalid requests[0].addSheet: A sheet with the name "sheet a" already exists. Please enter another name.'}]});
Expand Down
9 changes: 5 additions & 4 deletions test/integration-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ require('dotenv-safe').config();

const smetrics = require('../src/index');

smetrics.addMetric('sheet a', 'Metric 1', 11);
smetrics.addMetric('sheet b', 'Objects of the future', 'car');
smetrics.addMetric('sheet a', 'Metric 2', '503');
smetrics.addMetric('sheet a', 'Metric 1', new Date().getMilliseconds());
smetrics.addMetric('sheet b', 'Objects of the future', new Date().getSeconds());
smetrics.addMetric('sheet a', 'Metric 2', new Date().getDay());

smetrics.commit(process.env.SMETRICS_SPREADSHEET_ID, {
client_email: process.env.SMETRICS_GOOGLE_SERVICE_ACCOUNT_CLIENT_EMAIL,
private_key: process.env.SMETRICS_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY
private_key: process.env.SMETRICS_GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY,
dateFormat: 'googleDate',
}).catch(err => {
console.log(err);
});

0 comments on commit 831ab4f

Please sign in to comment.