Signal drop!
Relay (operand.online) is unreachable.
Usually, a dropped signal means an upgrade is happening. Hold on!
Sorry, no connección.
Hang in there while we get back on track
gram: card
> ./src/core/getFinalTable.ts
Lenses
(coming soon!)
// This file contains the logic for assembling a final table to display:
//
// * Join together various tables -- app data, user data
// * Join together attribute lists -- app, user
// * Sort / filter the final output
// These are implemented as reselect selectors because they're derived state;
// no need to store in the redux store; just a pure function of the various
// tables and attributes.
// Whatever gets outputted by these selectors is *exactly* what gets displayed
// in the final table view.
import { createSelector } from 'reselect';
import sortBy from 'lodash/sortBy';
import keyBy from 'lodash/keyBy';
import { RootState } from './reducer';
import { Attribute, Record, SortConfig } from './types';
const getAppRecords = (state:RootState):Record[] => state.appTable.records
const getAppAttributes = (state:RootState): Attribute[] => state.appTable.attributes
const getUserRecords = (state:RootState):Record[] => state.userTable.records
const getUserAttributes = (state:RootState):Attribute[] => state.userTable.attributes
const getSortConfig = (state:RootState):SortConfig => state.query.sortConfig
const getFormulaResults = (state:RootState):any => state.formulaResults
export const getFinalAttributes = createSelector(
[getAppAttributes, getUserAttributes, getFormulaResults],
(appAttributes, userAttributes, formulaResults) => {
// annotate attrs with a table id
appAttributes = (appAttributes || []).map( a => ({ ...a, tableId: "app" }))
userAttributes = (userAttributes || []).map(a => ({ ...a, tableId: "user" }))
const attributes = appAttributes.concat(userAttributes)
// set column type for formulas based on first row
const formulaResultsKeys = Object.keys(formulaResults);
if (formulaResultsKeys.length > 0) {
attributes.forEach(attr => {
if(attr.formula) {
const sampleValue = formulaResults[formulaResultsKeys[0]][attr.name]
if(typeof sampleValue === 'number') {
attr.type = "numeric"
} else if (typeof sampleValue === 'boolean') {
attr.type = "checkbox"
} else if (sampleValue instanceof HTMLElement) {
// hmm.. is this sensible...
attr.type = "element"
} else {
attr.type = "text"
}
} else {
attr.type = "text"
}
})
}
return attributes
}
)
// todo: this selector is just cached on the whole state --
// probably pointless to use this selector concept here?
export const getFinalRecords = createSelector(
[getAppRecords, getUserRecords, getAppAttributes, getUserAttributes, getSortConfig, getFormulaResults, getFinalAttributes],
(appRecords, userRecords, appAttributes, userAttributes, sortConfig, formulaResults, finalAttributes) => {
const userRecordsById = keyBy(userRecords, r => r.id);
let finalRecords = appRecords.slice().map(r => {
// join app records to user records
const finalRecord = {
id: r.id,
values: {
...r.values,
...(userRecordsById[r.id] || {}).values
}
}
// add formula results to the table, where available.
// (any missing results are still in process of being computed,
// and we'll re-run the reducer once they are available)
finalAttributes.filter(attr => attr.formula).forEach(attr => {
const result = formulaResults?.[finalRecord.id]?.[attr.name]
if(result !== undefined) {
finalRecord.values[attr.name] = result
}
})
return finalRecord
})
// sort
if (sortConfig) {
finalRecords = sortBy(finalRecords, r => r.values[sortConfig.attribute])
if (sortConfig.direction === "desc") {
finalRecords = finalRecords.reverse()
}
}
return finalRecords;
}
)