Fixing issue #2412 - making sure full enriched records are passed along to automations.

This commit is contained in:
mike12345567 2021-09-13 16:28:52 +01:00
parent 3eeb7c27b8
commit a92530f170
4 changed files with 65 additions and 66 deletions

View File

@ -23,6 +23,19 @@ const CALCULATION_TYPES = {
STATS: "stats", STATS: "stats",
} }
async function storeResponse(ctx, db, row, oldTable, table) {
row.type = "row"
const response = await db.put(row)
// don't worry about rev, tables handle rev/lastID updates
if (!isEqual(oldTable, table)) {
await db.put(table)
}
row._rev = response.rev
// process the row before return, to include relationships
row = await outputProcessing(ctx, table, row, { squash: false })
return { row, table }
}
exports.patch = async ctx => { exports.patch = async ctx => {
const appId = ctx.appId const appId = ctx.appId
const db = new CouchDB(appId) const db = new CouchDB(appId)
@ -77,14 +90,7 @@ exports.patch = async ctx => {
return { row: ctx.body, table } return { row: ctx.body, table }
} }
const response = await db.put(row) return storeResponse(ctx, db, row, dbTable, table)
// don't worry about rev, tables handle rev/lastID updates
if (!isEqual(dbTable, table)) {
await db.put(table)
}
row._rev = response.rev
row.type = "row"
return { row, table }
} }
exports.save = async function (ctx) { exports.save = async function (ctx) {
@ -118,14 +124,7 @@ exports.save = async function (ctx) {
table, table,
}) })
row.type = "row" return storeResponse(ctx, db, row, dbTable, table)
const response = await db.put(row)
// don't worry about rev, tables handle rev/lastID updates
if (!isEqual(dbTable, table)) {
await db.put(table)
}
row._rev = response.rev
return { row, table }
} }
exports.fetchView = async ctx => { exports.fetchView = async ctx => {
@ -221,34 +220,47 @@ exports.destroy = async function (ctx) {
const appId = ctx.appId const appId = ctx.appId
const db = new CouchDB(appId) const db = new CouchDB(appId)
const { _id, _rev } = ctx.request.body const { _id, _rev } = ctx.request.body
const row = await db.get(_id) let row = await db.get(_id)
if (row.tableId !== ctx.params.tableId) { if (row.tableId !== ctx.params.tableId) {
throw "Supplied tableId doesn't match the row's tableId" throw "Supplied tableId doesn't match the row's tableId"
} }
const table = await db.get(row.tableId)
// update the row to include full relationships before deleting them
row = await outputProcessing(ctx, table, row, { squash: false })
// now remove the relationships
await linkRows.updateLinks({ await linkRows.updateLinks({
appId, appId,
eventType: linkRows.EventType.ROW_DELETE, eventType: linkRows.EventType.ROW_DELETE,
row, row,
tableId: row.tableId, tableId: row.tableId,
}) })
let response
if (ctx.params.tableId === InternalTables.USER_METADATA) { if (ctx.params.tableId === InternalTables.USER_METADATA) {
ctx.params = { ctx.params = {
id: _id, id: _id,
} }
await userController.destroyMetadata(ctx) await userController.destroyMetadata(ctx)
return { response: ctx.body, row } response = ctx.body
} else { } else {
const response = await db.remove(_id, _rev) response = await db.remove(_id, _rev)
return { response, row }
} }
return { response, row }
} }
exports.bulkDestroy = async ctx => { exports.bulkDestroy = async ctx => {
const appId = ctx.appId const appId = ctx.appId
const { rows } = ctx.request.body
const db = new CouchDB(appId) const db = new CouchDB(appId)
const tableId = ctx.params.tableId
const table = await db.get(tableId)
let { rows } = ctx.request.body
// before carrying out any updates, make sure the rows are ready to be returned
// they need to be the full rows (including previous relationships) for automations
rows = await outputProcessing(ctx, table, rows, { squash: false })
// remove the relationships first
let updates = rows.map(row => let updates = rows.map(row =>
linkRows.updateLinks({ linkRows.updateLinks({
appId, appId,
@ -257,8 +269,7 @@ exports.bulkDestroy = async ctx => {
tableId: row.tableId, tableId: row.tableId,
}) })
) )
// TODO remove special user case in future if (tableId === InternalTables.USER_METADATA) {
if (ctx.params.tableId === InternalTables.USER_METADATA) {
updates = updates.concat( updates = updates.concat(
rows.map(row => { rows.map(row => {
ctx.params = { ctx.params = {

View File

@ -36,6 +36,18 @@ exports.IncludeDocs = IncludeDocs
exports.getLinkDocuments = getLinkDocuments exports.getLinkDocuments = getLinkDocuments
exports.createLinkView = createLinkView exports.createLinkView = createLinkView
function clearRelationshipFields(table, rows) {
for (let [key, field] of Object.entries(table.schema)) {
if (field.type === FieldTypes.LINK) {
rows = rows.map(row => {
delete row[key]
return row
})
}
}
return rows
}
async function getLinksForRows(appId, rows) { async function getLinksForRows(appId, rows) {
const tableIds = [...new Set(rows.map(el => el.tableId))] const tableIds = [...new Set(rows.map(el => el.tableId))]
// start by getting all the link values for performance reasons // start by getting all the link values for performance reasons
@ -126,33 +138,6 @@ exports.updateLinks = async function (args) {
} }
} }
/**
* Update a row with information about the links that pertain to it.
* @param {string} appId The instance in which this row has been created.
* @param {object} rows The row(s) themselves which is to be updated with info (if applicable). This can be
* a single row object or an array of rows - both will be handled.
* @returns {Promise<object>} The updated row (this may be the same if no links were found). If an array was input
* then an array will be output, object input -> object output.
*/
exports.attachLinkIDs = async (appId, rows) => {
const links = await getLinksForRows(appId, rows)
// now iterate through the rows and all field information
for (let row of rows) {
// find anything that matches the row's ID we are searching for and join it
links
.filter(el => el.thisId === row._id)
.forEach(link => {
if (row[link.fieldName] == null) {
row[link.fieldName] = []
}
row[link.fieldName].push(link.id)
})
}
// if it was an array when it came in then handle it as an array in response
// otherwise return the first element as there was only one input
return rows
}
/** /**
* 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).
@ -173,6 +158,9 @@ exports.attachFullLinkedDocs = async (ctx, table, rows) => {
const links = (await getLinksForRows(appId, rows)).filter(link => const links = (await getLinksForRows(appId, rows)).filter(link =>
rows.some(row => row._id === link.thisId) rows.some(row => row._id === link.thisId)
) )
// clear any existing links that could be dupe'd
rows = clearRelationshipFields(table, rows)
// now get the docs and combine into the rows
let linked = await getFullLinkedDocs(ctx, appId, links) let linked = await getFullLinkedDocs(ctx, appId, links)
const linkedTables = [] const linkedTables = []
for (let row of rows) { for (let row of rows) {

View File

@ -59,16 +59,4 @@ describe("test link functionality", () => {
expect(Array.isArray(output)).toBe(true) expect(Array.isArray(output)).toBe(true)
}) })
}) })
describe("attachLinkIDs", () => {
it("should be able to attach linkIDs", async () => {
await config.init()
await config.createTable()
const table = await config.createLinkedTable()
const row = await config.createRow()
const linkRow = await config.createRow(basicLinkedRow(table._id, row._id))
const attached = await links.attachLinkIDs(config.getAppId(), [linkRow])
expect(attached[0].link[0]).toBe(row._id)
})
})
}) })

View File

@ -185,10 +185,16 @@ exports.inputProcessing = (user = {}, table, row) => {
* @param {object} ctx the request which is looking for enriched rows. * @param {object} ctx the request which 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[]} rows the rows which are to be enriched. * @param {object[]|object} rows the rows which are to be enriched.
* @returns {object[]} the enriched rows will be returned. * @param {object} opts used to set some options for the output, such as disabling relationship squashing.
* @returns {object[]|object} the enriched rows will be returned.
*/ */
exports.outputProcessing = async (ctx, table, rows) => { exports.outputProcessing = async (
ctx,
table,
rows,
opts = { squash: true }
) => {
const appId = ctx.appId const appId = ctx.appId
let wasArray = true let wasArray = true
if (!(rows instanceof Array)) { if (!(rows instanceof Array)) {
@ -214,6 +220,12 @@ exports.outputProcessing = async (ctx, table, rows) => {
} }
} }
} }
enriched = await linkRows.squashLinksToPrimaryDisplay(appId, table, enriched) if (opts.squash) {
enriched = await linkRows.squashLinksToPrimaryDisplay(
appId,
table,
enriched
)
}
return wasArray ? enriched : enriched[0] return wasArray ? enriched : enriched[0]
} }