diff --git a/core/entities/nodes/JoinNode.js b/core/entities/nodes/JoinNode.js new file mode 100644 index 0000000..3029eea --- /dev/null +++ b/core/entities/nodes/JoinNode.js @@ -0,0 +1,80 @@ +import Node from '../Node.js' + +class JoinNode extends Node { + constructor (props) { + super(props) + this._assignProps(props) + } + + export = () => { + const baseTable = this.tables.find(t => t.label === this.baseTableLabel) + const baseTableRows = baseTable.getRows() + const tablesToJoin = this.tables.filter(t => { + return t.label !== this.baseTableLabel + }) + + const relatedTables = this.joinParams.map(joinParam => { + const foreignTable = tablesToJoin.find(t => { + return t.label === joinParam.foreignTable + }) + const foreignTableRows = foreignTable.getRows() + + const mergedRows = baseTableRows.map(baseRow => { + const matchingForeignRow = foreignTableRows.find(foreignRow => { + return baseRow[joinParam.primaryTableKey] === foreignRow[joinParam.matchingKey] + }) + let rowToMerge = {} + for (let key in matchingForeignRow) { + rowToMerge[`${joinParam.foreignTable}::${key}`] = matchingForeignRow[key] + } + + return {...baseRow, ...rowToMerge} + }) + return mergedRows + }) + + return relatedTables[0] + } + + setJoinBy = joinBy => { + const joinByValidation = this._validateJoinBy(joinBy) + if (joinByValidation.status === 'ERR') throw joinByValidation + else { + this.baseTableLabel = joinBy.baseTableLabel + this.joinParams = joinBy.joinParams + } + } + + _assignProps = props => { + if (props.joinBy) this.setJoinBy(props.joinBy) + } + + _validateJoinBy = joinBy => { + const err = { + status: 'ERR', + error: { + label: 'JoinBy Parameters are not valid', + messages: [] + } + } + + const { baseTableLabel, joinParams } = joinBy + + if (!baseTableLabel) err.error.messages.push('No baseTableLabel provided') + + if (!Array.isArray(joinParams)) { + const joinParamsType = typeof joinParams + err.error.messages.push(`Keys was of type ${joinParamsType} should be an array`) + return err + } + + for (let p = 0; p < joinParams.length; p++) { + if (typeof joinParams[p] !== 'object') err.error.messages.push(`joinParams[${p}] is not an object`) + } + + if (err.error.messages.length > 0) return err + else return { status: 'OK' } + } +} + +export default JoinNode diff --git a/tests/core/nodes/joinNodeTests.js b/tests/core/nodes/joinNodeTests.js new file mode 100644 index 0000000..6331383 --- /dev/null +++ b/tests/core/nodes/joinNodeTests.js @@ -0,0 +1,97 @@ +import JoinNode from '../../../core/entities/nodes/JoinNode.js' +import Table from '../../../core/entities/Table.js' + +const joinTables = () => { + const pickupTable = new Table({ + id: 'abc', + label: 'receipts', + rows: [ + { id: '2345676', contractor: 'AshBritt', type: 'row', lat: 54, long: 31 }, + { id: '2345676', contractor: 'Jefferson',type: 'lh', lat: 31, long: -71.34 }, + { id: '2345676', contractor: 'AshBritt', type: 'lh', lat: 80, long: -41 }, + ] + }) + + const contractorTable = new Table({ + id: 'XYZ', + label: 'contractors', + rows: [ + { id: '1WE3V6', name: 'AshBritt', employeeCount: 43, homeState: 'CA' }, + { id: 'FG4S67', name: 'Jefferson', employeeCount: 91, homeState: 'AL' } + ] + }) + + const expectedOutput = [ + { id: '2345676', contractor: 'AshBritt', type: 'row', lat: 54, long: 31, 'contractors::id': '1WE3V6', 'contractors::name': 'AshBritt', 'contractors::employeeCount': 43, 'contractors::homeState': 'CA' }, + { id: '2345676', contractor: 'Jefferson',type: 'lh', lat: 31, long: -71.34, 'contractors::id': 'FG4S67', 'contractors::name': 'Jefferson', 'contractors::employeeCount': 91, 'contractors::homeState': 'AL' }, + { id: '2345676', contractor: 'AshBritt', type: 'lh', lat: 80, long: -41, 'contractors::id': '1WE3V6', 'contractors::name': 'AshBritt', 'contractors::employeeCount': 43, 'contractors::homeState': 'CA' }, + ] + + const joinNode = new JoinNode({ + id: 'QWE', + label: 'Receipts with Contractors', + tables: [pickupTable, contractorTable], + joinBy: { + baseTableLabel: 'receipts', + joinParams: [ + { foreignTable: 'contractors', primaryTableKey: 'contractor', matchingKey: 'name' } + ] + } + }) + + + + const joinNodeProps = joinNode.export() + + if (JSON.stringify(joinNodeProps) === JSON.stringify(expectedOutput)) return true + else return false +} + +const setJoinBy = () => { + const pickupTable = new Table({ + id: 'abc', + label: 'receipts', + rows: [ + { id: '2345676', contractor: 'AshBritt', type: 'row', lat: 54, long: 31 }, + { id: '2345676', contractor: 'Jefferson',type: 'lh', lat: 31, long: -71.34 }, + { id: '2345676', contractor: 'AshBritt', type: 'lh', lat: 80, long: -41 }, + ] + }) + + const contractorTable = new Table({ + id: 'XYZ', + label: 'contractors', + rows: [ + { id: '1WE3V6', name: 'AshBritt', employeeCount: 43, homeState: 'CA' }, + { id: 'FG4S67', name: 'Jefferson', employeeCount: 91, homeState: 'AL' } + ] + }) + + const expectedOutput = [ + { id: '2345676', contractor: 'AshBritt', type: 'row', lat: 54, long: 31, 'contractors::id': '1WE3V6', 'contractors::name': 'AshBritt', 'contractors::employeeCount': 43, 'contractors::homeState': 'CA' }, + { id: '2345676', contractor: 'Jefferson',type: 'lh', lat: 31, long: -71.34, 'contractors::id': 'FG4S67', 'contractors::name': 'Jefferson', 'contractors::employeeCount': 91, 'contractors::homeState': 'AL' }, + { id: '2345676', contractor: 'AshBritt', type: 'lh', lat: 80, long: -41, 'contractors::id': '1WE3V6', 'contractors::name': 'AshBritt', 'contractors::employeeCount': 43, 'contractors::homeState': 'CA' }, + ] + + const joinNode = new JoinNode({ + id: 'QWE', + label: 'Receipts with Contractors', + tables: [pickupTable, contractorTable] + }) + + joinNode.setJoinBy({ + baseTableLabel: 'receipts', + joinParams: [ + { foreignTable: 'contractors', primaryTableKey: 'contractor', matchingKey: 'name' } + ] + }) + const joinNodeProps = joinNode.export() + + if (JSON.stringify(joinNodeProps) === JSON.stringify(expectedOutput)) return true + else return false +} + +export default [ + { name: 'Entity | Join Table Test', test: joinTables }, + { name: 'Entity | Join Table setJoinBy', test: setJoinBy }, +] \ No newline at end of file diff --git a/tests/index.js b/tests/index.js index 4736d5b..de4eb5d 100644 --- a/tests/index.js +++ b/tests/index.js @@ -3,6 +3,7 @@ import ProgressBar from 'progress' import tableTests from '../tests/core/tableTests.js' import nodeTests from '../tests/core/nodeTests.js' import filterNodeTests from '../tests/core/nodes/filterNodeTests.js' +import joinNodeTests from '../tests/core/nodes/joinNodeTests.js' function runTestsAndReturnFailures (tests) { const testTotalCount = tests.length @@ -39,7 +40,8 @@ function init (tests) { const testsArray = [ tableTests, nodeTests, - filterNodeTests + filterNodeTests, + joinNodeTests ] init (testsArray.flat()) \ No newline at end of file