Getting relationship re-enrichment working, so that static formulas will update when the value of the relationship changes.
This commit is contained in:
parent
04934a544a
commit
faa82d8e0f
|
@ -14,7 +14,7 @@ const {
|
||||||
cleanupAttachments,
|
cleanupAttachments,
|
||||||
processFormulas,
|
processFormulas,
|
||||||
} = require("../../../utilities/rowProcessor")
|
} = require("../../../utilities/rowProcessor")
|
||||||
const { FieldTypes } = require("../../../constants")
|
const { FieldTypes, FormulaTypes } = require("../../../constants")
|
||||||
const { isEqual } = require("lodash")
|
const { isEqual } = require("lodash")
|
||||||
const { validate, findRow } = require("./utils")
|
const { validate, findRow } = require("./utils")
|
||||||
const { fullSearch, paginatedSearch } = require("./internalSearch")
|
const { fullSearch, paginatedSearch } = require("./internalSearch")
|
||||||
|
@ -35,11 +35,75 @@ const CALCULATION_TYPES = {
|
||||||
STATS: "stats",
|
STATS: "stats",
|
||||||
}
|
}
|
||||||
|
|
||||||
async function storeResponse(ctx, db, row, oldTable, table) {
|
/**
|
||||||
|
* This function runs through the enriched row, looks at the rows which
|
||||||
|
* are related and then checks if they need the state of their formulas
|
||||||
|
* updated.
|
||||||
|
*/
|
||||||
|
async function updateRelatedFormula(appId, db, table, enrichedRow) {
|
||||||
|
// no formula to update, we're done
|
||||||
|
if (!table.relatedFormula) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// the related rows by tableId
|
||||||
|
let relatedRows = {}
|
||||||
|
for (let [key, field] of Object.entries(enrichedRow)) {
|
||||||
|
const columnDefinition = table.schema[key]
|
||||||
|
if (columnDefinition && columnDefinition.type === FieldTypes.LINK) {
|
||||||
|
const relatedTableId = columnDefinition.tableId
|
||||||
|
if (!relatedRows[relatedTableId]) {
|
||||||
|
relatedRows[relatedTableId] = []
|
||||||
|
}
|
||||||
|
relatedRows[relatedTableId] = relatedRows[relatedTableId].concat(field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let promises = []
|
||||||
|
for (let tableId of table.relatedFormula) {
|
||||||
|
try {
|
||||||
|
// no rows to update, skip
|
||||||
|
if (!relatedRows[tableId] || relatedRows[tableId].length === 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const relatedTable = await db.get(tableId)
|
||||||
|
for (let column of Object.values(relatedTable.schema)) {
|
||||||
|
// needs updated in related rows
|
||||||
|
if (
|
||||||
|
column.type === FieldTypes.FORMULA &&
|
||||||
|
column.formulaType === FormulaTypes.STATIC
|
||||||
|
) {
|
||||||
|
// re-enrich rows for all the related, don't update the related formula for them
|
||||||
|
promises = promises.concat(
|
||||||
|
relatedRows[tableId].map(related =>
|
||||||
|
storeResponse(appId, db, relatedTable, related, {
|
||||||
|
updateFormula: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// no error scenario, table doesn't seem to exist anymore, ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.all(promises)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function runs at the end of the save/patch functions of the row controller, all this
|
||||||
|
* really does is enrich the row, handle any static formula processing, then return the enriched
|
||||||
|
* row. The reason we need to return the enriched row is that the automation row created trigger
|
||||||
|
* expects the row to be totally enriched/contain all relationships.
|
||||||
|
*/
|
||||||
|
async function storeResponse(
|
||||||
|
appId,
|
||||||
|
db,
|
||||||
|
table,
|
||||||
|
row,
|
||||||
|
{ oldTable, updateFormula } = { updateFormula: true }
|
||||||
|
) {
|
||||||
row.type = "row"
|
row.type = "row"
|
||||||
let rowToSave = cloneDeep(row)
|
|
||||||
// process the row before return, to include relationships
|
// process the row before return, to include relationships
|
||||||
let enrichedRow = await outputProcessing(ctx, table, cloneDeep(row), {
|
let enrichedRow = await outputProcessing({ appId }, table, cloneDeep(row), {
|
||||||
squash: false,
|
squash: false,
|
||||||
})
|
})
|
||||||
// use enriched row to generate formulas for saving, specifically only use as context
|
// use enriched row to generate formulas for saving, specifically only use as context
|
||||||
|
@ -51,13 +115,13 @@ async function storeResponse(ctx, db, row, oldTable, table) {
|
||||||
// don't worry about rev, tables handle rev/lastID updates
|
// don't worry about rev, tables handle rev/lastID updates
|
||||||
// if another row has been written since processing this will
|
// if another row has been written since processing this will
|
||||||
// handle the auto ID clash
|
// handle the auto ID clash
|
||||||
if (!isEqual(oldTable, table)) {
|
if (oldTable && !isEqual(oldTable, table)) {
|
||||||
try {
|
try {
|
||||||
await db.put(table)
|
await db.put(table)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.status === 409) {
|
if (err.status === 409) {
|
||||||
const updatedTable = await db.get(table._id)
|
const updatedTable = await db.get(table._id)
|
||||||
let response = processAutoColumn(null, updatedTable, rowToSave, {
|
let response = processAutoColumn(null, updatedTable, row, {
|
||||||
reprocessing: true,
|
reprocessing: true,
|
||||||
})
|
})
|
||||||
await db.put(response.table)
|
await db.put(response.table)
|
||||||
|
@ -71,6 +135,10 @@ async function storeResponse(ctx, db, row, oldTable, table) {
|
||||||
// for response, calculate the formulas for the enriched row
|
// for response, calculate the formulas for the enriched row
|
||||||
enrichedRow._rev = response.rev
|
enrichedRow._rev = response.rev
|
||||||
enrichedRow = await processFormulas(table, enrichedRow, { dynamic: false })
|
enrichedRow = await processFormulas(table, enrichedRow, { dynamic: false })
|
||||||
|
// this updates the related formulas in other rows based on the relations to this row
|
||||||
|
if (updateFormula) {
|
||||||
|
await updateRelatedFormula(appId, db, table, enrichedRow)
|
||||||
|
}
|
||||||
return { row: enrichedRow, table }
|
return { row: enrichedRow, table }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +242,10 @@ exports.patch = async ctx => {
|
||||||
return { row: ctx.body, table }
|
return { row: ctx.body, table }
|
||||||
}
|
}
|
||||||
|
|
||||||
return storeResponse(ctx, db, row, dbTable, table)
|
return storeResponse(ctx.appId, db, table, row, {
|
||||||
|
oldTable: dbTable,
|
||||||
|
updateFormula: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.save = async function (ctx) {
|
exports.save = async function (ctx) {
|
||||||
|
@ -208,7 +279,10 @@ exports.save = async function (ctx) {
|
||||||
table,
|
table,
|
||||||
})
|
})
|
||||||
|
|
||||||
return storeResponse(ctx, db, row, dbTable, table)
|
return storeResponse(ctx.appId, db, table, row, {
|
||||||
|
oldTable: dbTable,
|
||||||
|
updateFormula: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetchView = async ctx => {
|
exports.fetchView = async ctx => {
|
||||||
|
|
|
@ -20,7 +20,11 @@ const { isEqual } = require("lodash")
|
||||||
* can manage hydrating related rows formula fields. This is
|
* can manage hydrating related rows formula fields. This is
|
||||||
* specifically only for static formula.
|
* specifically only for static formula.
|
||||||
*/
|
*/
|
||||||
async function updateRelatedTablesForFormula(db, table) {
|
async function updateRelatedTablesForFormula(
|
||||||
|
db,
|
||||||
|
table,
|
||||||
|
{ deletion } = { deletion: false }
|
||||||
|
) {
|
||||||
// start by retrieving all tables, remove the current table from the list
|
// start by retrieving all tables, remove the current table from the list
|
||||||
const tables = (await getAllInternalTables({ db })).filter(
|
const tables = (await getAllInternalTables({ db })).filter(
|
||||||
tbl => tbl._id !== table._id
|
tbl => tbl._id !== table._id
|
||||||
|
@ -41,31 +45,34 @@ async function updateRelatedTablesForFormula(db, table) {
|
||||||
otherTable.relatedFormula.splice(index, 1)
|
otherTable.relatedFormula.splice(index, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let column of Object.values(table.schema)) {
|
// if deleting, just remove the table IDs, don't try add
|
||||||
// not a static formula, or doesn't contain a relationship
|
if (!deletion) {
|
||||||
if (
|
for (let column of Object.values(table.schema)) {
|
||||||
column.type !== FieldTypes.FORMULA ||
|
// not a static formula, or doesn't contain a relationship
|
||||||
column.formulaType !== FormulaTypes.STATIC
|
if (
|
||||||
) {
|
column.type !== FieldTypes.FORMULA ||
|
||||||
continue
|
column.formulaType !== FormulaTypes.STATIC
|
||||||
}
|
) {
|
||||||
// check to see if any relationship columns are used in formula
|
|
||||||
for (let relatedCol of relatedColumns) {
|
|
||||||
if (!doesContainString(column.formula, relatedCol.name)) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const relatedTable = tables.find(
|
// check to see if any relationship columns are used in formula
|
||||||
related => related._id === relatedCol.tableId
|
for (let relatedCol of relatedColumns) {
|
||||||
)
|
if (!doesContainString(column.formula, relatedCol.name)) {
|
||||||
// check if the table is already in the list of related formula, if it isn't, then add it
|
continue
|
||||||
if (
|
}
|
||||||
relatedTable &&
|
const relatedTable = tables.find(
|
||||||
(!relatedTable.relatedFormula ||
|
related => related._id === relatedCol.tableId
|
||||||
relatedTable.relatedFormula.indexOf(table._id) === -1)
|
)
|
||||||
) {
|
// check if the table is already in the list of related formula, if it isn't, then add it
|
||||||
relatedTable.relatedFormula = relatedTable.relatedFormula
|
if (
|
||||||
? [...relatedTable.relatedFormula, table._id]
|
relatedTable &&
|
||||||
: [table._id]
|
(!relatedTable.relatedFormula ||
|
||||||
|
relatedTable.relatedFormula.includes(table._id))
|
||||||
|
) {
|
||||||
|
relatedTable.relatedFormula = relatedTable.relatedFormula
|
||||||
|
? [...relatedTable.relatedFormula, table._id]
|
||||||
|
: [table._id]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,6 +218,8 @@ exports.destroy = async function (ctx) {
|
||||||
await db.deleteIndex(existingIndex)
|
await db.deleteIndex(existingIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// has to run after, make sure it has _id
|
||||||
|
await updateRelatedTablesForFormula(db, tableToDelete, { deletion: true })
|
||||||
return tableToDelete
|
return tableToDelete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -156,8 +156,6 @@ class LinkController {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRelatedFormula() {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given the link field of this table, and the link field of the linked table, this makes sure
|
* Given the link field of this table, and the link field of the linked table, this makes sure
|
||||||
* the state of relationship type is accurate on both.
|
* the state of relationship type is accurate on both.
|
||||||
|
|
|
@ -72,7 +72,7 @@ async function getLinksForRows(appId, rows) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getFullLinkedDocs(ctx, appId, links) {
|
async function getFullLinkedDocs(appId, links) {
|
||||||
// create DBs
|
// create DBs
|
||||||
const db = new CouchDB(appId)
|
const db = new CouchDB(appId)
|
||||||
const linkedRowIds = links.map(link => link.id)
|
const linkedRowIds = links.map(link => link.id)
|
||||||
|
@ -146,13 +146,12 @@ exports.updateLinks = async function (args) {
|
||||||
/**
|
/**
|
||||||
* Given a table and a list of rows this will retrieve all of the attached docs and enrich them into the row.
|
* Given a table and a list of rows this will retrieve all of the attached docs and enrich them into the row.
|
||||||
* This is required for formula fields, this may only be utilised internally (for now).
|
* This is required for formula fields, this may only be utilised internally (for now).
|
||||||
* @param {object} ctx The request which is looking for rows.
|
* @param {string} appId The ID of the app which this request is in the context of.
|
||||||
* @param {object} table The table from which the rows originated.
|
* @param {object} table The table from which the rows originated.
|
||||||
* @param {array<object>} rows The rows which are to be enriched.
|
* @param {array<object>} rows The rows which are to be enriched.
|
||||||
* @return {Promise<*>} returns the rows with all of the enriched relationships on it.
|
* @return {Promise<*>} returns the rows with all of the enriched relationships on it.
|
||||||
*/
|
*/
|
||||||
exports.attachFullLinkedDocs = async (ctx, table, rows) => {
|
exports.attachFullLinkedDocs = async (appId, table, rows) => {
|
||||||
const appId = ctx.appId
|
|
||||||
const linkedTableIds = getLinkedTableIDs(table)
|
const linkedTableIds = getLinkedTableIDs(table)
|
||||||
if (linkedTableIds.length === 0) {
|
if (linkedTableIds.length === 0) {
|
||||||
return rows
|
return rows
|
||||||
|
@ -166,7 +165,7 @@ exports.attachFullLinkedDocs = async (ctx, table, rows) => {
|
||||||
// clear any existing links that could be dupe'd
|
// clear any existing links that could be dupe'd
|
||||||
rows = clearRelationshipFields(table, rows)
|
rows = clearRelationshipFields(table, rows)
|
||||||
// now get the docs and combine into the rows
|
// now get the docs and combine into the rows
|
||||||
let linked = await getFullLinkedDocs(ctx, appId, links)
|
let linked = await getFullLinkedDocs(appId, links)
|
||||||
const linkedTables = []
|
const linkedTables = []
|
||||||
for (let row of rows) {
|
for (let row of rows) {
|
||||||
for (let link of links.filter(link => link.thisId === row._id)) {
|
for (let link of links.filter(link => link.thisId === row._id)) {
|
||||||
|
|
|
@ -253,7 +253,7 @@ exports.inputProcessing = (
|
||||||
/**
|
/**
|
||||||
* This function enriches the input rows with anything they are supposed to contain, for example
|
* This function enriches the input rows with anything they are supposed to contain, for example
|
||||||
* link records or attachment links.
|
* link records or attachment links.
|
||||||
* @param {object} ctx the request which is looking for enriched rows.
|
* @param {string} appId the app in which the request is looking for enriched rows.
|
||||||
* @param {object} table the table from which these rows came from originally, this is used to determine
|
* @param {object} table the table from which these rows came from originally, this is used to determine
|
||||||
* the schema of the rows and then enrich.
|
* the schema of the rows and then enrich.
|
||||||
* @param {object[]|object} rows the rows which are to be enriched.
|
* @param {object[]|object} rows the rows which are to be enriched.
|
||||||
|
@ -261,19 +261,18 @@ exports.inputProcessing = (
|
||||||
* @returns {object[]|object} the enriched rows will be returned.
|
* @returns {object[]|object} the enriched rows will be returned.
|
||||||
*/
|
*/
|
||||||
exports.outputProcessing = async (
|
exports.outputProcessing = async (
|
||||||
ctx,
|
{ appId },
|
||||||
table,
|
table,
|
||||||
rows,
|
rows,
|
||||||
opts = { squash: true }
|
opts = { squash: true }
|
||||||
) => {
|
) => {
|
||||||
const appId = ctx.appId
|
|
||||||
let wasArray = true
|
let wasArray = true
|
||||||
if (!(rows instanceof Array)) {
|
if (!(rows instanceof Array)) {
|
||||||
rows = [rows]
|
rows = [rows]
|
||||||
wasArray = false
|
wasArray = false
|
||||||
}
|
}
|
||||||
// attach any linked row information
|
// attach any linked row information
|
||||||
let enriched = await linkRows.attachFullLinkedDocs(ctx, table, rows)
|
let enriched = await linkRows.attachFullLinkedDocs(appId, table, rows)
|
||||||
|
|
||||||
// process formulas
|
// process formulas
|
||||||
enriched = processFormulas(table, enriched, { dynamic: true })
|
enriched = processFormulas(table, enriched, { dynamic: true })
|
||||||
|
|
Loading…
Reference in New Issue