From aff9b384d16b9c410791fbc991a24123ddc1147d Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 7 Dec 2018 12:02:40 +0000 Subject: [PATCH 1/8] better saving schema for model layer previously, the model layer had a confused interface, where it saved via blueprints, but loaded via a URL logic. This commit modifies Fetchers to save consistently via a URL --- src/lib/Controller.js | 7 +-- src/lib/Fetcher.js | 98 ++++++++++++++++++++++++++--------------- src/lib/util.js | 8 ++-- src/models/StoreJson.js | 21 +++++---- 4 files changed, 81 insertions(+), 53 deletions(-) diff --git a/src/lib/Controller.js b/src/lib/Controller.js index de37f97..0f97c35 100644 --- a/src/lib/Controller.js +++ b/src/lib/Controller.js @@ -9,7 +9,7 @@ class Controller { this.fetchers = fetchers } - sheetExists (sheet) { + _sheetExists (sheet) { return (Object.keys(this.fetchers).indexOf(sheet) >= 0) } @@ -25,6 +25,7 @@ class Controller { return this.fetchers[sheet].update() }) ).then(results => { + console.log(results) if (results.every(r => r)) { return copy.success.update } else { @@ -34,7 +35,7 @@ class Controller { } retrieve (sheet, tab, resource) { - if (this.sheetExists(sheet)) { + if (this._sheetExists(sheet)) { const fetcher = this.fetchers[sheet] return fetcher.retrieve(tab, resource) } else { @@ -43,7 +44,7 @@ class Controller { } retrieveFrag (sheet, tab, resource, frag) { - if (this.sheetExists(sheet)) { + if (this._sheetExists(sheet)) { const fetcher = this.fetchers[sheet] return fetcher.retrieveFrag(tab, resource, frag) } else { diff --git a/src/lib/Fetcher.js b/src/lib/Fetcher.js index 89477b4..b0a369c 100644 --- a/src/lib/Fetcher.js +++ b/src/lib/Fetcher.js @@ -1,12 +1,11 @@ // FetcherTwo class interfaces with Google Sheet, and saves to a specified db import { google } from 'googleapis' import { - fmtSheetTitle, + fmtName, fmtBlueprinterTitles, - bp, isFunction } from './util' -import { byRow } from './blueprinters' +import { createHash } from 'crypto' import R from 'ramda' class Fetcher { @@ -28,39 +27,62 @@ class Fetcher { */ this.sheetName = sheetName + /* + * A unique ID for the Fetcher to identify its elements in the model layer + */ + this.id = createHash('md5').update(sheetName).update(sheetId).digest('hex') + /* * These are the available tabs for storing and retrieving data. * Each blueprinter is a function that returns a Blueprint from a * list of lists (which will be retrieved from gsheets). */ this.blueprinters = fmtBlueprinterTitles(blueprinters) - this.blueprints = {} - Object.keys(this.blueprinters).forEach(key => { - this.blueprints[key] = null - }) + + /* + * This object is the canonical represenation for the data that a Fetcher + * proxies. When the fetcher is initialized, its model layer (db) is indexed, + * and this object populated accordingly. Whenever the fetcher updates, this + * data structure updates as well. It is the model layer that determines the + * performance of indexing the blueprints. + */ + this.blueprints = this._indexDbForBlueprints() + .then(res => res) /* * Google API setup */ - this.sheets = google.sheets('v4') + this.API = google.sheets('v4') this.auth = null - /** - * saveBp is a curried function that takes in a title and - * a blueprinter. NB: it sits here in the constructor as - * I am not sure how to curry a class method with Ramda. - */ - this._saveBp = R.curry((tab, title, data, blueprinter) => { - const saturatedBp = blueprinter( - tab, - this.sheetName, - this.sheetId, - data + /** curry to allow convenient syntax with map */ + this._saveViaBlueprinter = R.curry(this._saveViaBlueprinter) + } + + /** save data under a given tab name via its blueprinter, which generates + * its resource name. Note that this is curried in the constructor. + */ + _saveViaBlueprinter (tab, data, blueprinter) { + const saturatedBp = blueprinter( + tab, + this.sheetName, + this.sheetId, + data + ) + + return Promise.all( + Object.keys(saturatedBp.routes).map(route => + this.db.save(`${this.id}/${tab}/${route}`, saturatedBp.routes[route].data) ) - const blueprint = bp(saturatedBp) // TODO: come up with better semantics. - this.blueprints[title] = blueprint - return this.db.save(saturatedBp) - }) + ) + } + + /** index the db and produce appropriate blueprints structure **/ + _indexDbForBlueprints () { + return this.db.index() + .then(res => { + return res + }) } /** returns a Promise that resolves if access is granted to the account, and rejects otherwise. */ @@ -85,14 +107,14 @@ class Fetcher { update () { let tabTitles /* Retrieve all available routes on a given sheet, and store formatted copies of it where a formatter is available */ - return this.sheets.spreadsheets + return this.API.spreadsheets .get({ auth: this.auth, spreadsheetId: this.sheetId }) .then(response => { tabTitles = response.data.sheets.map(sheet => sheet.properties.title) - return this.sheets.spreadsheets.values.batchGet({ + return this.API.spreadsheets.values.batchGet({ auth: this.auth, spreadsheetId: this.sheetId, ranges: tabTitles @@ -100,12 +122,15 @@ class Fetcher { }) .then(results => { const tabData = results.data.valueRanges + return Promise.all( tabData.map((tab, idx) => { const { values } = tab + if (values === undefined) { return Promise.resolve({}) } + const name = tabTitles[idx] return this.save(name, values) }) @@ -115,31 +140,34 @@ class Fetcher { .catch(() => false) } - save (tab, data) { - const title = fmtSheetTitle(tab) - if (Object.keys(this.blueprinters).indexOf(title) > -1) { - const bpConfig = this.blueprinters[title] + save (_tab, data) { + const tab = fmtName(_tab) + if (Object.keys(this.blueprinters).indexOf(tab) > -1) { + const bpConfig = this.blueprinters[tab] if (isFunction(bpConfig)) { - return this._saveBp(tab, title, data, bpConfig) + // if bpConfig specifies a single blueprinter + return this._saveViaBlueprinter(tab, data, bpConfig) } else { - return bpConfig.map(this._saveBp(tab, title, data)) + // if bpConfig specifies an array of blueprinters + return bpConfig.map(this._saveViaBlueprinter(tab, data)) } } else { - // If it can't find a blueprinter for the tab title, default to byRow - return this.db.save(byRow(tab, this.sheetName, this.sheetId, data)) + // NB: if a blueprinter is not specified for a tab, + // just skip it. + return true } } // NB: could combine these functions by checking kwargs length retrieve (tab, resource) { - const title = fmtSheetTitle(tab) + const title = fmtName(tab) const url = `${this.sheetName}/${tab}/${resource}` return this.db.load(url, this.blueprints[title]) } retrieveFrag (tab, resource, frag) { - const title = fmtSheetTitle(tab) + const title = fmtName(tab) const url = `${this.sheetName}/${tab}/${resource}/${frag}` return this.db.load(url, this.blueprints[title]) } diff --git a/src/lib/util.js b/src/lib/util.js index 5e71f3a..f84bc59 100755 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -55,24 +55,24 @@ export const idxSearcher = R.curry((attrName, searchValue, myArray) => { /* more site specific functions. TODO: maybe move to another folder? */ -export function fmtSheetTitle (name) { +export function fmtName (name) { return name.replaceAll(' ', '-').toLowerCase() } export function fmtBlueprinterTitles (tabs) { const obj = {} Object.keys(tabs).forEach(tab => { - const name = fmtSheetTitle(tab) + const name = fmtName(tab) obj[name] = tabs[tab] }) return obj } export function deriveFilename (sheet, tab) { - return `${fmtSheetTitle(sheet)}-${fmtSheetTitle(tab)}.json` + return `${fmtName(sheet)}-${fmtName(tab)}.json` } -export function bp (full) { +export function desaturate (full) { const blueprint = { name: R.clone(full.name), sheet: R.clone(full.sheet), diff --git a/src/models/StoreJson.js b/src/models/StoreJson.js index e94e2f8..5f5ab28 100644 --- a/src/models/StoreJson.js +++ b/src/models/StoreJson.js @@ -1,20 +1,19 @@ import fs from 'mz/fs' -import { fmtSheetTitle } from '../lib/util' import copy from '../copy/en' const STORAGE_DIRNAME = 'temp' class StoreJson { - save (bp) { - return Promise.all( - Object.keys(bp.routes).map(route => - fs.writeFile( - `${STORAGE_DIRNAME}/${fmtSheetTitle( - bp.sheet.name - )}__${fmtSheetTitle(bp.name)}__${route}.json`, - JSON.stringify(bp.routes[route].data) - ) - ) + index () { + return Promise.resolve({}) + } + + save (url, data) { + const parts = url.split('/') + + return fs.writeFile( + `${STORAGE_DIRNAME}/${parts[0]}__${parts[1]}__${parts[2]}.json`, + JSON.stringify(data) ) } From 2be0f75362837b2e8639b1ed38c8be626b6d5797 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 7 Dec 2018 12:04:02 +0000 Subject: [PATCH 2/8] correct load URLs for model layer --- src/lib/Fetcher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Fetcher.js b/src/lib/Fetcher.js index b0a369c..c0fc510 100644 --- a/src/lib/Fetcher.js +++ b/src/lib/Fetcher.js @@ -162,7 +162,7 @@ class Fetcher { // NB: could combine these functions by checking kwargs length retrieve (tab, resource) { const title = fmtName(tab) - const url = `${this.sheetName}/${tab}/${resource}` + const url = `${this.id}/${tab}/${resource}` return this.db.load(url, this.blueprints[title]) } From 565a36083fbee4b1fd5fb34834537f162a62d2fe Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 7 Dec 2018 12:21:40 +0000 Subject: [PATCH 3/8] add index prototype for model layer as the model layer only speaks URL, it returns a list of the URLs that it supports --- src/models/StoreJson.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/models/StoreJson.js b/src/models/StoreJson.js index 5f5ab28..1124960 100644 --- a/src/models/StoreJson.js +++ b/src/models/StoreJson.js @@ -3,9 +3,18 @@ import copy from '../copy/en' const STORAGE_DIRNAME = 'temp' +function partsFromFilename (fname) { + const body = fname.slice(0, -5) + return body.split('__') +} + class StoreJson { index () { - return Promise.resolve({}) + return Promise.resolve() + .then(() => fs.readdir(STORAGE_DIRNAME)) + .then(files => files.filter(f => f.match(/.*\.json$/))) + .then(jsons => jsons.map(partsFromFilename)) + .then(parts => parts.map(p => `${p[0]}/${p[1]}/${p[2]}`)) } save (url, data) { From 8a5bce08423c5ba40dbea6017d03b27a3e7f8291 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 7 Dec 2018 12:32:32 +0000 Subject: [PATCH 4/8] rename 'routes' in blueprints to 'resources' routes was a confusing semantics that was not in order with the rest of the architecture --- src/blueprinters/byColumn.js | 12 ++++++++---- src/blueprinters/byGroup.js | 8 ++++---- src/blueprinters/byId.js | 8 ++++---- src/blueprinters/byRow.js | 8 ++++---- src/blueprinters/byTree.js | 8 ++++---- src/lib/Controller.js | 1 - src/lib/Fetcher.js | 11 +++++++---- src/lib/blueprinters.js | 2 +- src/lib/util.js | 8 ++++---- test/internals.js | 18 +++++++++--------- 10 files changed, 45 insertions(+), 39 deletions(-) diff --git a/src/blueprinters/byColumn.js b/src/blueprinters/byColumn.js index 68abb0e..d2144e2 100644 --- a/src/blueprinters/byColumn.js +++ b/src/blueprinters/byColumn.js @@ -9,7 +9,7 @@ import { defaultBlueprint, defaultRoute } from '../lib/blueprinters' * @return {type} Blueprint * generated. */ -export default function byColumn (tabName, sheetName, sheetId, data) { +function byColumn (tabName, sheetName, sheetId, data) { // Define Blueprint props const bp = R.clone(defaultBlueprint) bp.sheet = { @@ -18,18 +18,22 @@ export default function byColumn (tabName, sheetName, sheetId, data) { } bp.name = tabName - // column names define routes + // column names define resources const labels = data[0] labels.forEach(label => { - bp.routes[label] = R.clone(defaultRoute) + bp.resources[label] = R.clone(defaultRoute) }) // remaining rows as data data.forEach((row, idx) => { if (idx === 0) return labels.forEach((label, idx) => { - bp.routes[label].data.push(row[idx]) + bp.resources[label].data.push(row[idx]) }) }) return bp } + +byColumn.resourceName = 'columns' + +export default byColumn diff --git a/src/blueprinters/byGroup.js b/src/blueprinters/byGroup.js index 0dac039..3d1ce65 100644 --- a/src/blueprinters/byGroup.js +++ b/src/blueprinters/byGroup.js @@ -28,11 +28,11 @@ export default function byGroup ( } bp.name = tabName - // Column names define routes + // Column names define resources const itemLabels = data[0] const fmt = fmtObj(itemLabels) - bp.routes[label] = R.clone(defaultRoute) - bp.routes[label].data = [] + bp.resources[label] = R.clone(defaultRoute) + bp.resources[label].data = [] const dataGroups = {} @@ -46,7 +46,7 @@ export default function byGroup ( } }) Object.keys(dataGroups).forEach(groupKey => { - bp.routes[label].data.push({ + bp.resources[label].data.push({ group: groupKey, group_label: dataGroups[groupKey][0].group_label, data: dataGroups[groupKey] diff --git a/src/blueprinters/byId.js b/src/blueprinters/byId.js index 0f40a31..936957c 100644 --- a/src/blueprinters/byId.js +++ b/src/blueprinters/byId.js @@ -28,15 +28,15 @@ export default function byId ( } bp.name = tabName - // Column names define routes + // Column names define resources const itemLabels = data[0] const fmt = fmtObj(itemLabels) - bp.routes[label] = R.clone(defaultRoute) - bp.routes[label].data = [] + bp.resources[label] = R.clone(defaultRoute) + bp.resources[label].data = [] data.forEach((row, idx) => { if (idx === 0) return - bp.routes[label].data[fmt(row).id] = fmt(row) + bp.resources[label].data[fmt(row).id] = fmt(row) }) return bp } diff --git a/src/blueprinters/byRow.js b/src/blueprinters/byRow.js index 637e397..544d4f0 100644 --- a/src/blueprinters/byRow.js +++ b/src/blueprinters/byRow.js @@ -27,15 +27,15 @@ export default function byRow ( } bp.name = tabName - // Column names define routes + // Column names define resources const itemLabels = data[0] const fmt = fmtObj(itemLabels) - bp.routes[label] = R.clone(defaultRoute) - bp.routes[label].data = [] + bp.resources[label] = R.clone(defaultRoute) + bp.resources[label].data = [] data.forEach((row, idx) => { if (idx === 0) return - bp.routes[label].data.push(fmt(row)) + bp.resources[label].data.push(fmt(row)) }) return bp } diff --git a/src/blueprinters/byTree.js b/src/blueprinters/byTree.js index e483d3d..c43d266 100644 --- a/src/blueprinters/byTree.js +++ b/src/blueprinters/byTree.js @@ -27,9 +27,9 @@ export default function byTree ( } bp.name = tabName - // Column names define routes - bp.routes[label] = R.clone(defaultRoute) - bp.routes[label].data = {} + // Column names define resources + bp.resources[label] = R.clone(defaultRoute) + bp.resources[label].data = {} const tree = { key: 'tags', @@ -62,6 +62,6 @@ export default function byTree ( } }) - bp.routes[label].data = tree + bp.resources[label].data = tree return bp } diff --git a/src/lib/Controller.js b/src/lib/Controller.js index 0f97c35..b9e9351 100644 --- a/src/lib/Controller.js +++ b/src/lib/Controller.js @@ -25,7 +25,6 @@ class Controller { return this.fetchers[sheet].update() }) ).then(results => { - console.log(results) if (results.every(r => r)) { return copy.success.update } else { diff --git a/src/lib/Fetcher.js b/src/lib/Fetcher.js index c0fc510..2f3497c 100644 --- a/src/lib/Fetcher.js +++ b/src/lib/Fetcher.js @@ -47,7 +47,10 @@ class Fetcher { * performance of indexing the blueprints. */ this.blueprints = this._indexDbForBlueprints() - .then(res => res) + .then(allUrls => { + const supportedUrls = allUrls.filter(url => url.startsWith(this.id)) + return {} + }) /* * Google API setup @@ -71,8 +74,8 @@ class Fetcher { ) return Promise.all( - Object.keys(saturatedBp.routes).map(route => - this.db.save(`${this.id}/${tab}/${route}`, saturatedBp.routes[route].data) + Object.keys(saturatedBp.resources).map(route => + this.db.save(`${this.id}/${tab}/${route}`, saturatedBp.resources[route].data) ) ) } @@ -106,7 +109,7 @@ class Fetcher { update () { let tabTitles - /* Retrieve all available routes on a given sheet, and store formatted copies of it where a formatter is available */ + /* Retrieve all available resources on a given sheet, and store formatted copies of it where a formatter is available */ return this.API.spreadsheets .get({ auth: this.auth, diff --git a/src/lib/blueprinters.js b/src/lib/blueprinters.js index 6bb4c64..53f3980 100644 --- a/src/lib/blueprinters.js +++ b/src/lib/blueprinters.js @@ -5,7 +5,7 @@ export const defaultBlueprint = { name: null, id: null, dialects: ['rest'], // supported dialects, can (eventually) be multiple - routes: {} + resources: {} } export const defaultRoute = { diff --git a/src/lib/util.js b/src/lib/util.js index f84bc59..c6b29b8 100755 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -77,11 +77,11 @@ export function desaturate (full) { name: R.clone(full.name), sheet: R.clone(full.sheet), dialects: R.clone(full.dialects), - routes: {} + resources: {} } - Object.keys(full.routes).forEach(route => { - blueprint.routes[route] = { - options: R.clone(full.routes[route].options) + Object.keys(full.resources).forEach(route => { + blueprint.resources[route] = { + options: R.clone(full.resources[route].options) } }) return blueprint diff --git a/test/internals.js b/test/internals.js index c99f3ed..a2a00fa 100644 --- a/test/internals.js +++ b/test/internals.js @@ -18,7 +18,7 @@ test('defaultBlueprint exports', t => { name: null, id: null, dialects: ['rest'], - routes: {} + resources: {} } t.deepEqual(expected, defaultBlueprint) }) @@ -31,12 +31,12 @@ test('byColumn blueprinter generates expected output', t => { id: 'egSheetId', name: 'egSheetName' } - expected.routes['h1'] = R.clone(defaultRoute) - expected.routes['h1'].data = [1, 4] - expected.routes['h2'] = R.clone(defaultRoute) - expected.routes['h2'].data = [2, 5] - expected.routes['h3'] = R.clone(defaultRoute) - expected.routes['h3'].data = [3, 6] + expected.resources['h1'] = R.clone(defaultRoute) + expected.resources['h1'].data = [1, 4] + expected.resources['h2'] = R.clone(defaultRoute) + expected.resources['h2'].data = [2, 5] + expected.resources['h3'] = R.clone(defaultRoute) + expected.resources['h3'].data = [3, 6] t.deepEqual(expected, actual) }) @@ -48,8 +48,8 @@ test('byRow blueprinter generates expected output', t => { id: 'egSheetId', name: 'egSheetName' } - expected.routes['items'] = R.clone(defaultRoute) - expected.routes['items'].data = [{ + expected.resources['items'] = R.clone(defaultRoute) + expected.resources['items'].data = [{ h1: 1, h2: 2, h3: 3 From 41e2ba8299e9d8d609e8a26fdbd3e04a1cbf4b83 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 7 Dec 2018 13:02:50 +0000 Subject: [PATCH 5/8] defaultRoute -> defaultResource --- src/blueprinters/{byColumn.js => columns.js} | 10 ++++----- src/blueprinters/{byGroup.js => groups.js} | 8 +++---- src/blueprinters/{byId.js => ids.js} | 8 +++---- src/blueprinters/{byRow.js => rows.js} | 8 +++---- src/blueprinters/{byTree.js => tree.js} | 8 +++---- src/lib/Fetcher.js | 9 +++++++- src/lib/blueprinters.js | 9 ++++++-- test/internals.js | 22 ++++++++++---------- 8 files changed, 46 insertions(+), 36 deletions(-) rename src/blueprinters/{byColumn.js => columns.js} (75%) rename src/blueprinters/{byGroup.js => groups.js} (84%) rename src/blueprinters/{byId.js => ids.js} (80%) rename src/blueprinters/{byRow.js => rows.js} (79%) rename src/blueprinters/{byTree.js => tree.js} (85%) diff --git a/src/blueprinters/byColumn.js b/src/blueprinters/columns.js similarity index 75% rename from src/blueprinters/byColumn.js rename to src/blueprinters/columns.js index d2144e2..eee7bc8 100644 --- a/src/blueprinters/byColumn.js +++ b/src/blueprinters/columns.js @@ -1,5 +1,5 @@ import R from 'ramda' -import { defaultBlueprint, defaultRoute } from '../lib/blueprinters' +import { defaultBlueprint, defaultResource } from '../lib/blueprinters' /** * byColumn - generate a Blueprint from a data sheet by column. Each column @@ -9,7 +9,7 @@ import { defaultBlueprint, defaultRoute } from '../lib/blueprinters' * @return {type} Blueprint * generated. */ -function byColumn (tabName, sheetName, sheetId, data) { +function columns (tabName, sheetName, sheetId, data) { // Define Blueprint props const bp = R.clone(defaultBlueprint) bp.sheet = { @@ -21,7 +21,7 @@ function byColumn (tabName, sheetName, sheetId, data) { // column names define resources const labels = data[0] labels.forEach(label => { - bp.resources[label] = R.clone(defaultRoute) + bp.resources[label] = R.clone(defaultResource) }) // remaining rows as data @@ -34,6 +34,4 @@ function byColumn (tabName, sheetName, sheetId, data) { return bp } -byColumn.resourceName = 'columns' - -export default byColumn +export default columns diff --git a/src/blueprinters/byGroup.js b/src/blueprinters/groups.js similarity index 84% rename from src/blueprinters/byGroup.js rename to src/blueprinters/groups.js index 3d1ce65..30ef755 100644 --- a/src/blueprinters/byGroup.js +++ b/src/blueprinters/groups.js @@ -1,9 +1,9 @@ import R from 'ramda' import { fmtObj } from '../lib/util' -import { defaultBlueprint, defaultRoute } from '../lib/blueprinters' +import { defaultBlueprint, defaultResource } from '../lib/blueprinters' /** - * byGroup - generate a Blueprint from a data sheet grouped by a column called 'group' + * groups - generate a Blueprint from a data sheet grouped by a column called 'group' * The resource name defaults to 'groups', or a custom resource name can be passed. * Each resource item is an object with values labelled according to column * names. Items are inserted in the data list at idx = id. @@ -13,7 +13,7 @@ import { defaultBlueprint, defaultRoute } from '../lib/blueprinters' * @param {type} name="" name of blueprint. * @return {type} Blueprint */ -export default function byGroup ( +export default function groups ( tabName, sheetName, sheetId, @@ -31,7 +31,7 @@ export default function byGroup ( // Column names define resources const itemLabels = data[0] const fmt = fmtObj(itemLabels) - bp.resources[label] = R.clone(defaultRoute) + bp.resources[label] = R.clone(defaultResource) bp.resources[label].data = [] const dataGroups = {} diff --git a/src/blueprinters/byId.js b/src/blueprinters/ids.js similarity index 80% rename from src/blueprinters/byId.js rename to src/blueprinters/ids.js index 936957c..d027be1 100644 --- a/src/blueprinters/byId.js +++ b/src/blueprinters/ids.js @@ -1,9 +1,9 @@ import R from 'ramda' import { fmtObj } from '../lib/util' -import { defaultBlueprint, defaultRoute } from '../lib/blueprinters' +import { defaultBlueprint, defaultResource } from '../lib/blueprinters' /** - * byId - generate a Blueprint from a data sheet by id, which is an integer. + * ids - generate a Blueprint from a data sheet by id, which is an integer. * The resource name defaults to 'ids', or a custom resource name can be passed. * Each resource item is an object with values labelled according to column * names. Items are inserted in the data list at idx = id. @@ -13,7 +13,7 @@ import { defaultBlueprint, defaultRoute } from '../lib/blueprinters' * @param {type} name="" name of blueprint. * @return {type} Blueprint */ -export default function byId ( +export default function ids ( tabName, sheetName, sheetId, @@ -31,7 +31,7 @@ export default function byId ( // Column names define resources const itemLabels = data[0] const fmt = fmtObj(itemLabels) - bp.resources[label] = R.clone(defaultRoute) + bp.resources[label] = R.clone(defaultResource) bp.resources[label].data = [] data.forEach((row, idx) => { diff --git a/src/blueprinters/byRow.js b/src/blueprinters/rows.js similarity index 79% rename from src/blueprinters/byRow.js rename to src/blueprinters/rows.js index 544d4f0..6966480 100644 --- a/src/blueprinters/byRow.js +++ b/src/blueprinters/rows.js @@ -1,9 +1,9 @@ import R from 'ramda' import { fmtObj } from '../lib/util' -import { defaultBlueprint, defaultRoute } from '../lib/blueprinters' +import { defaultBlueprint, defaultResource } from '../lib/blueprinters' /** - * byRow - generate a Blueprint from a data sheet by row. The resource name + * rows - generate a Blueprint from a data sheet by row. The resource name * defaults to 'rows', or a custom resource name can be passed. Each resource * item is an object with values labelled according to column names. * @@ -12,7 +12,7 @@ import { defaultBlueprint, defaultRoute } from '../lib/blueprinters' * @param {type} name="" name of blueprint. * @return {type} Blueprint */ -export default function byRow ( +export default function rows ( tabName, sheetName, sheetId, @@ -30,7 +30,7 @@ export default function byRow ( // Column names define resources const itemLabels = data[0] const fmt = fmtObj(itemLabels) - bp.resources[label] = R.clone(defaultRoute) + bp.resources[label] = R.clone(defaultResource) bp.resources[label].data = [] data.forEach((row, idx) => { diff --git a/src/blueprinters/byTree.js b/src/blueprinters/tree.js similarity index 85% rename from src/blueprinters/byTree.js rename to src/blueprinters/tree.js index c43d266..143d419 100644 --- a/src/blueprinters/byTree.js +++ b/src/blueprinters/tree.js @@ -1,8 +1,8 @@ import R from 'ramda' -import { defaultBlueprint, defaultRoute } from '../lib/blueprinters' +import { defaultBlueprint, defaultResource } from '../lib/blueprinters' /** - * byTree - generate a Blueprint from a data sheet grouped by a column called 'group' + * tree - generate a Blueprint from a data sheet grouped by a column called 'group' * The resource name defaults to 'groups', or a custom resource name can be passed. * Each resource item is an object with values labelled according to column * names. Items are inserted in the data list at idx = id. @@ -12,7 +12,7 @@ import { defaultBlueprint, defaultRoute } from '../lib/blueprinters' * @param {type} name="" name of blueprint. * @return {type} Blueprint */ -export default function byTree ( +export default function tree ( tabName, sheetName, sheetId, @@ -28,7 +28,7 @@ export default function byTree ( bp.name = tabName // Column names define resources - bp.resources[label] = R.clone(defaultRoute) + bp.resources[label] = R.clone(defaultResource) bp.resources[label].data = {} const tree = { diff --git a/src/lib/Fetcher.js b/src/lib/Fetcher.js index 2f3497c..d7c3c6d 100644 --- a/src/lib/Fetcher.js +++ b/src/lib/Fetcher.js @@ -48,7 +48,14 @@ class Fetcher { */ this.blueprints = this._indexDbForBlueprints() .then(allUrls => { - const supportedUrls = allUrls.filter(url => url.startsWith(this.id)) + const allParts = allUrls.reduce((acc, url) => { + if (url.startsWith(this.id)) { + const parts = url.split('/') + acc.push([ parts[1], parts[2] ]) + return acc + } + }, []) + console.log(allParts) return {} }) diff --git a/src/lib/blueprinters.js b/src/lib/blueprinters.js index 53f3980..1d9d03a 100644 --- a/src/lib/blueprinters.js +++ b/src/lib/blueprinters.js @@ -1,5 +1,6 @@ import path from 'path' import fs from 'fs' +import R from 'ramda' export const defaultBlueprint = { name: null, @@ -8,13 +9,17 @@ export const defaultBlueprint = { resources: {} } -export const defaultRoute = { +export const defaultResource = { options: { fragment: true }, data: [] } +export function buildDesaturated (tab, resource) { + const bp = R.clone(defaultBlueprint) +} + // import all default exports from 'blueprinters' folder const allBps = {} const REL_PATH_TO_BPS = '../blueprinters' @@ -28,5 +33,5 @@ fs.readdirSync(normalizedPath).forEach(file => { // each file in blueprinters folder available for granular import from here. module.exports = Object.assign({ defaultBlueprint, - defaultRoute + defaultResource }, allBps) diff --git a/test/internals.js b/test/internals.js index a2a00fa..64ef814 100644 --- a/test/internals.js +++ b/test/internals.js @@ -2,9 +2,9 @@ import test from 'ava' import R from 'ramda' import { defaultBlueprint, - defaultRoute, - byColumn, - byRow + defaultResource, + columns, + rows } from '../src/lib/blueprinters' const egInput1 = [ @@ -23,32 +23,32 @@ test('defaultBlueprint exports', t => { t.deepEqual(expected, defaultBlueprint) }) -test('byColumn blueprinter generates expected output', t => { - const actual = byColumn('eg ColumnBlueprint', 'egSheetName', 'egSheetId', egInput1) +test('columns blueprinter generates expected output', t => { + const actual = columns('eg ColumnBlueprint', 'egSheetName', 'egSheetId', egInput1) const expected = R.clone(defaultBlueprint) expected.name = 'eg ColumnBlueprint' expected.sheet = { id: 'egSheetId', name: 'egSheetName' } - expected.resources['h1'] = R.clone(defaultRoute) + expected.resources['h1'] = R.clone(defaultResource) expected.resources['h1'].data = [1, 4] - expected.resources['h2'] = R.clone(defaultRoute) + expected.resources['h2'] = R.clone(defaultResource) expected.resources['h2'].data = [2, 5] - expected.resources['h3'] = R.clone(defaultRoute) + expected.resources['h3'] = R.clone(defaultResource) expected.resources['h3'].data = [3, 6] t.deepEqual(expected, actual) }) -test('byRow blueprinter generates expected output', t => { - const actual = byRow('egRowBlueprint', 'egSheetName', 'egSheetId', egInput1, 'items') +test('rows blueprinter generates expected output', t => { + const actual = rows('egRowBlueprint', 'egSheetName', 'egSheetId', egInput1, 'items') const expected = R.clone(defaultBlueprint) expected.name = 'egRowBlueprint' expected.sheet = { id: 'egSheetId', name: 'egSheetName' } - expected.resources['items'] = R.clone(defaultRoute) + expected.resources['items'] = R.clone(defaultResource) expected.resources['items'].data = [{ h1: 1, h2: 2, From 35b8bf4d9c812365d97769f7ed8e32fd3cb27dd8 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 7 Dec 2018 13:58:38 +0000 Subject: [PATCH 6/8] add _buildBlueprintsAsnc in Fetcher --- src/lib/Fetcher.js | 47 +++++++++++++++++++++++------------------ src/lib/blueprinters.js | 9 ++++++-- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/lib/Fetcher.js b/src/lib/Fetcher.js index d7c3c6d..890ba97 100644 --- a/src/lib/Fetcher.js +++ b/src/lib/Fetcher.js @@ -1,5 +1,6 @@ // FetcherTwo class interfaces with Google Sheet, and saves to a specified db import { google } from 'googleapis' +import { buildDesaturated } from './blueprinters' import { fmtName, fmtBlueprinterTitles, @@ -46,18 +47,8 @@ class Fetcher { * data structure updates as well. It is the model layer that determines the * performance of indexing the blueprints. */ - this.blueprints = this._indexDbForBlueprints() - .then(allUrls => { - const allParts = allUrls.reduce((acc, url) => { - if (url.startsWith(this.id)) { - const parts = url.split('/') - acc.push([ parts[1], parts[2] ]) - return acc - } - }, []) - console.log(allParts) - return {} - }) + this.blueprints = null + this._buildBlueprintsAsync() // NB: modifies this.blueprints on completion /* * Google API setup @@ -69,6 +60,29 @@ class Fetcher { this._saveViaBlueprinter = R.curry(this._saveViaBlueprinter) } + _buildBlueprintsAsync () { + return this.db.index() + .then(allUrls => { + const allParts = allUrls.reduce((acc, url) => { + if (url.startsWith(this.id)) { + const parts = url.split('/') + acc.push([ parts[1], parts[2] ]) + return acc + } + }, []) + return allParts + .map(parts => buildDesaturated( + this.sheetId, + this.sheetName, + parts[0], + parts[1] + )) + }) + .then(res => { + this.blueprints = res + }) + } + /** save data under a given tab name via its blueprinter, which generates * its resource name. Note that this is curried in the constructor. */ @@ -87,14 +101,6 @@ class Fetcher { ) } - /** index the db and produce appropriate blueprints structure **/ - _indexDbForBlueprints () { - return this.db.index() - .then(res => { - return res - }) - } - /** returns a Promise that resolves if access is granted to the account, and rejects otherwise. */ authenticate (clientEmail, privateKey) { const googleAuth = new google.auth.JWT(clientEmail, null, privateKey, [ @@ -155,6 +161,7 @@ class Fetcher { if (Object.keys(this.blueprinters).indexOf(tab) > -1) { const bpConfig = this.blueprinters[tab] + if (isFunction(bpConfig)) { // if bpConfig specifies a single blueprinter return this._saveViaBlueprinter(tab, data, bpConfig) diff --git a/src/lib/blueprinters.js b/src/lib/blueprinters.js index 1d9d03a..62b6fe9 100644 --- a/src/lib/blueprinters.js +++ b/src/lib/blueprinters.js @@ -16,8 +16,12 @@ export const defaultResource = { data: [] } -export function buildDesaturated (tab, resource) { +export function buildDesaturated (sheetId, sheetName, tab, resource) { const bp = R.clone(defaultBlueprint) + bp.name = sheetName + bp.id = sheetId + bp.resources[tab] = resource + return bp } // import all default exports from 'blueprinters' folder @@ -33,5 +37,6 @@ fs.readdirSync(normalizedPath).forEach(file => { // each file in blueprinters folder available for granular import from here. module.exports = Object.assign({ defaultBlueprint, - defaultResource + defaultResource, + buildDesaturated }, allBps) From 08dce0dbb9e0a03da43d29b553148153dc0f9778 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 7 Dec 2018 14:49:16 +0000 Subject: [PATCH 7/8] strip down blueprint structure to what is necessary --- src/lib/Fetcher.js | 3 +++ src/lib/blueprinters.js | 16 ++++++++-------- test/internals.js | 6 ++++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/lib/Fetcher.js b/src/lib/Fetcher.js index 890ba97..3103145 100644 --- a/src/lib/Fetcher.js +++ b/src/lib/Fetcher.js @@ -68,8 +68,11 @@ class Fetcher { const parts = url.split('/') acc.push([ parts[1], parts[2] ]) return acc + } else { + return acc } }, []) + return allParts .map(parts => buildDesaturated( this.sheetId, diff --git a/src/lib/blueprinters.js b/src/lib/blueprinters.js index 62b6fe9..3dba25d 100644 --- a/src/lib/blueprinters.js +++ b/src/lib/blueprinters.js @@ -4,23 +4,23 @@ import R from 'ramda' export const defaultBlueprint = { name: null, - id: null, - dialects: ['rest'], // supported dialects, can (eventually) be multiple + sheet: { + name: null, + id: null + }, resources: {} } export const defaultResource = { - options: { - fragment: true - }, data: [] } export function buildDesaturated (sheetId, sheetName, tab, resource) { const bp = R.clone(defaultBlueprint) - bp.name = sheetName - bp.id = sheetId - bp.resources[tab] = resource + bp.sheet.name = sheetName + bp.sheet.id = sheetId + bp.name = tab + bp.resources[resource] = null return bp } diff --git a/test/internals.js b/test/internals.js index 64ef814..49c6ba4 100644 --- a/test/internals.js +++ b/test/internals.js @@ -15,9 +15,11 @@ const egInput1 = [ test('defaultBlueprint exports', t => { const expected = { + sheet: { + name: null, + id: null + }, name: null, - id: null, - dialects: ['rest'], resources: {} } t.deepEqual(expected, defaultBlueprint) From e883406e4011f8253f8cf86d2900fce1144cdf66 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Fri, 7 Dec 2018 14:52:25 +0000 Subject: [PATCH 8/8] rm internal sheet id from copy error --- src/copy/en.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/copy/en.js b/src/copy/en.js index 24a9fbc..ef50d69 100644 --- a/src/copy/en.js +++ b/src/copy/en.js @@ -4,7 +4,7 @@ export default { onlySheet: 'You cannot query a sheet directly. The URL needs to be in the format /:sheet/:tab/:resource.', onlyTab: 'You cannot query a tab directly. The URL needs to be in the format /:sheet/:tab/:resource.', noSheet: sheet => `The sheet ${sheet} is not available in this server.`, - noResource: prts => `The resource '${prts[2]}' does not exists in the tab '${prts[1]}' of the sheet '${prts[0]}'.`, + noResource: prts => `The resource '${prts[2]}' does not exists in the tab '${prts[1]}' in this sheet.`, noFragment: prts => `Fragment index does not exist` }, success: {