From 6d4f74679fa43bed3e9fe2a3123788ce63efbc81 Mon Sep 17 00:00:00 2001 From: Joshua Shoemaker Date: Tue, 4 Aug 2020 00:08:11 -0500 Subject: [PATCH] feat: groupby nodule --- README.md | 6 ++ package.json | 2 +- src/entities/nodules/GroupByNodule.js | 65 ++++++++++++ tests/core/nodules/groupByNoduleTests.js | 123 +++++++++++++++++++++++ tests/index.js | 4 +- 5 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 src/entities/nodules/GroupByNodule.js create mode 100644 tests/core/nodules/groupByNoduleTests.js diff --git a/README.md b/README.md index d6576b6..129657b 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,12 @@ Only the columns specified in the `structure` object will be exported from the ` export() // get the joined data from the Nodule +## GroupByNodule + +The `GroupByNodule` class can take in `Table`s and a single value of `groupByValue` to group rows by a table header. On `export()` this will return an object with keys representing an array of objects (or `rows`) + +The `asTable()` method is overloaded and actually returns an array of new `Table` instances, operating differently than the `Nodule` base class `asTable()` + ## Constant Values A set of declared constant variables has been provided for safer typing. Although importing them is not essential, the values they represent are the only options for certain options. diff --git a/package.json b/package.json index 321ab2c..77d8895 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lovelacejs", - "version": "0.1.6", + "version": "0.2.0", "description": "Lovelace.js is a modern JavaScript Library to create objects that easily mutate data through relationships, filtering, and tranforming the shape of data.", "main": "index.js", "directories": { diff --git a/src/entities/nodules/GroupByNodule.js b/src/entities/nodules/GroupByNodule.js new file mode 100644 index 0000000..cd53641 --- /dev/null +++ b/src/entities/nodules/GroupByNodule.js @@ -0,0 +1,65 @@ +import Nodule from '../Nodule.js' +import Table from '../Table.js' + +class GroupByNodule extends Nodule { + constructor (props) { + super (props) + this._assignProps(props) + } + + /* Overload the Nodule Method + Returns an Array of Tables with modified ids */ + asTable = () => { + const exports = this.export() + const tables = [] + for (let key in exports) { + const newTableProps = { + id: `${this.id}-${key}`, + label: `${this.label} by ${key}`, + rows: exports[key] + } + const table = new Table(newTableProps) + tables.push(table) + } + return tables + } + + export = () => { + const { groupByValue } = this + const rows = this.tables.map(t => t.export() ).flat() + const groupedByRows = rows.reduce((groups, r) => { + const val = r[groupByValue] + groups[val] = groups[val] || [] + groups[val].push(r) + return groups + }, {}) + return groupedByRows + } + + setGroupByValue = value => { + const valueValidation = this._validateGroupByValue(value) + if(valueValidation.status === 'ERR') throw valueValidation + else this.groupByValue = value + } + + _assignProps = props => { + if (props.groupByValue) this.setGroupByValue(props.groupByValue) + } + + _validateGroupByValue = value => { + const err = { + status: 'ERR', + error: { + label: 'Filter Parameter are not valid', + messages: [] + } + } + + if (typeof value !== 'string') { + const valueType = typeof value + err.error.messages.push(`GroupBy value was of type ${valueType}, should be a string`) + } else return { status: 'OK' } + } +} + +export default GroupByNodule diff --git a/tests/core/nodules/groupByNoduleTests.js b/tests/core/nodules/groupByNoduleTests.js new file mode 100644 index 0000000..7ad6cfc --- /dev/null +++ b/tests/core/nodules/groupByNoduleTests.js @@ -0,0 +1,123 @@ +import GroupByNodule from '../../../src/entities/nodules/GroupByNodule.js' +import Table from '../../../src/entities/Table.js' + +const groupByTest = () => { + const expectedOutput = { + AshBritt: [ + { id: 'abc', data: 'row', contractor: 'AshBritt' }, + { id: 'qwe', data: 'lh', contractor: 'AshBritt' }, + { id: 'XYZ', data: 'row', contractor: 'AshBritt' } + ], + HeyDay: [ + { id: 'XYZ', data: 'row', contractor: 'HeyDay' } + ] + } + + let table = {} + try { + table = new Table({ + id: 'XYZ', + label: 'Test Table', + rows: [ + { id: 'abc', data: 'row', contractor: 'AshBritt' }, + { id: 'qwe', data: 'lh', contractor: 'AshBritt' }, + { id: 'XYZ', data: 'row', contractor: 'AshBritt' }, + { id: 'XYZ', data: 'row', contractor: 'HeyDay' }, + ] + }) + } catch (err) { + return false + } + + let groupByNodule = {} + try { + groupByNodule = new GroupByNodule({ + id: 'ABC', + label: 'Test Group By', + tables: [table], + groupByValue: 'contractor' + }) + } catch (err) { + console.log(err) + return false + } + + const groupedRows = groupByNodule.export() + if (JSON.stringify(groupedRows) === JSON.stringify(expectedOutput)) { + return true + } else { + return false + } +} + +const groupByAsTables = () => { + + const expectedOutput = [ + { + id: 'ABC-AshBritt', + label: 'Test Group by AshBritt', + rows: [ + { id: 'abc', data: 'row', contractor: 'AshBritt' }, + { id: 'qwe', data: 'lh', contractor: 'AshBritt' }, + { id: 'XYZ', data: 'row', contractor: 'AshBritt' } + ], + headers: [ 'id', 'data', 'contractor' ], + type: 'Table', + isValid: true + }, + { + id: 'ABC-HeyDay', + label: 'Test Group by HeyDay', + rows: [ + { id: 'XYZ', data: 'row', contractor: 'HeyDay' } + ], + headers: [ 'id', 'data', 'contractor' ], + type: 'Table', + isValid: true + } + ] + + let table = {} + try { + table = new Table({ + id: 'XYZ', + label: 'Test Table', + rows: [ + { id: 'abc', data: 'row', contractor: 'AshBritt' }, + { id: 'qwe', data: 'lh', contractor: 'AshBritt' }, + { id: 'XYZ', data: 'row', contractor: 'AshBritt' }, + { id: 'XYZ', data: 'row', contractor: 'HeyDay' }, + ] + }) + } catch (err) { + return false + } + + let groupByNodule = {} + try { + groupByNodule = new GroupByNodule({ + id: 'ABC', + label: 'Test Group', + tables: [table], + groupByValue: 'contractor' + }) + } catch (err) { + console.log(err) + return false + } + + // make checks here + const groupedTables = groupByNodule.asTable() + const groupedTablesProps = groupedTables.map(t => t.getProperties()) + if (JSON.stringify(groupedTablesProps) === JSON.stringify(expectedOutput)) { + return true + } else { + return false + } +} + + +export default [ + { name: 'Entity | GroupBy Value', test: groupByTest }, + { name: 'Entity | GroupBy As Table', test: groupByAsTables }, +] \ No newline at end of file diff --git a/tests/index.js b/tests/index.js index a922ba3..7b0e9eb 100644 --- a/tests/index.js +++ b/tests/index.js @@ -5,6 +5,7 @@ import noduleTests from '../tests/core/NoduleTests.js' import filterNoduleTests from '../tests/core/nodules/filterNoduleTests.js' import joinNoduleTests from '../tests/core/nodules/joinNoduleTests.js' import transformNoduleTests from '../tests/core/nodules/transformNoduleTests.js' +import groupByNoduleTests from '../tests/core/nodules/groupByNoduleTests.js' function runTestsAndReturnFailures (tests) { const testTotalCount = tests.length @@ -34,7 +35,8 @@ const testsArray = [ noduleTests, filterNoduleTests, joinNoduleTests, - transformNoduleTests + transformNoduleTests, + groupByNoduleTests ] init (testsArray.flat()) \ No newline at end of file