Getting external rows to be more consistent with the internal API - the formulas should be processed using the outputProcessing as they were being processed, but was causing an overwrite. The problem was that the external system internally processed the formulas/relationships, then squashed the relationships. Once it got to the external API, it would go through normal output processing, which would run over the squashed rows, causing an inconsistent behaviour.
This commit is contained in:
parent
ce6de27714
commit
635af0f76a
|
@ -106,7 +106,6 @@ export async function fetchDeployments(ctx: any) {
|
|||
}
|
||||
ctx.body = Object.values(deployments.history).reverse()
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
ctx.body = []
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import {
|
|||
breakRowIdField,
|
||||
convertRowId,
|
||||
generateRowIdField,
|
||||
getPrimaryDisplay,
|
||||
isRowId,
|
||||
isSQL,
|
||||
} from "../../../integrations/utils"
|
||||
|
@ -237,7 +236,7 @@ function basicProcessing({
|
|||
thisRow._id = generateIdForRow(row, table, isLinked)
|
||||
thisRow.tableId = table._id
|
||||
thisRow._rev = "rev"
|
||||
return processFormulas(table, thisRow)
|
||||
return thisRow
|
||||
}
|
||||
|
||||
function fixArrayTypes(row: Row, table: Table) {
|
||||
|
@ -392,7 +391,7 @@ export class ExternalRequest<T extends Operation> {
|
|||
return { row: newRow, manyRelationships }
|
||||
}
|
||||
|
||||
squashRelationshipColumns(
|
||||
processRelationshipFields(
|
||||
table: Table,
|
||||
row: Row,
|
||||
relationships: RelationshipsJson[]
|
||||
|
@ -402,7 +401,6 @@ export class ExternalRequest<T extends Operation> {
|
|||
if (!linkedTable || !row[relationship.column]) {
|
||||
continue
|
||||
}
|
||||
const display = linkedTable.primaryDisplay
|
||||
for (let key of Object.keys(row[relationship.column])) {
|
||||
let relatedRow: Row = row[relationship.column][key]
|
||||
// add this row as context for the relationship
|
||||
|
@ -411,15 +409,10 @@ export class ExternalRequest<T extends Operation> {
|
|||
relatedRow[col.name] = [row]
|
||||
}
|
||||
}
|
||||
// process additional types
|
||||
relatedRow = processDates(table, relatedRow)
|
||||
relatedRow = processFormulas(linkedTable, relatedRow)
|
||||
let relatedDisplay
|
||||
if (display) {
|
||||
relatedDisplay = getPrimaryDisplay(relatedRow[display])
|
||||
}
|
||||
row[relationship.column][key] = {
|
||||
primaryDisplay: relatedDisplay || "Invalid display column",
|
||||
_id: relatedRow._id,
|
||||
}
|
||||
row[relationship.column][key] = relatedRow
|
||||
}
|
||||
}
|
||||
return row
|
||||
|
@ -521,14 +514,14 @@ export class ExternalRequest<T extends Operation> {
|
|||
)
|
||||
}
|
||||
|
||||
// Process some additional data types
|
||||
let finalRowArray = Object.values(finalRows)
|
||||
finalRowArray = processDates(table, finalRowArray)
|
||||
finalRowArray = processFormulas(table, finalRowArray) as Row[]
|
||||
|
||||
return finalRowArray.map((row: Row) =>
|
||||
this.squashRelationshipColumns(table, row, relationships)
|
||||
// make sure all related rows are correct
|
||||
let finalRowArray = Object.values(finalRows).map(row =>
|
||||
this.processRelationshipFields(table, row, relationships)
|
||||
)
|
||||
|
||||
// process some additional types
|
||||
finalRowArray = processDates(table, finalRowArray)
|
||||
return finalRowArray
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -663,7 +656,7 @@ export class ExternalRequest<T extends Operation> {
|
|||
linkPrimary,
|
||||
linkSecondary,
|
||||
}: {
|
||||
row: { [key: string]: any }
|
||||
row: Row
|
||||
linkPrimary: string
|
||||
linkSecondary?: string
|
||||
}) {
|
||||
|
|
|
@ -76,6 +76,7 @@ export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
|
|||
relationships: true,
|
||||
})
|
||||
const enrichedRow = await outputProcessing(table, row, {
|
||||
squash: true,
|
||||
preserveLinks: true,
|
||||
})
|
||||
return {
|
||||
|
@ -119,7 +120,10 @@ export async function save(ctx: UserCtx) {
|
|||
})
|
||||
return {
|
||||
...response,
|
||||
row: await outputProcessing(table, row, { preserveLinks: true }),
|
||||
row: await outputProcessing(table, row, {
|
||||
preserveLinks: true,
|
||||
squash: true,
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
return response
|
||||
|
@ -140,7 +144,7 @@ export async function find(ctx: UserCtx): Promise<Row> {
|
|||
const table = await sdk.tables.getTable(tableId)
|
||||
// Preserving links, as the outputProcessing does not support external rows yet and we don't need it in this use case
|
||||
return await outputProcessing(table, row, {
|
||||
squash: false,
|
||||
squash: true,
|
||||
preserveLinks: true,
|
||||
})
|
||||
}
|
||||
|
@ -207,7 +211,7 @@ export async function fetchEnrichedRow(ctx: UserCtx) {
|
|||
// don't support composite keys right now
|
||||
const linkedIds = links.map((link: Row) => breakRowIdField(link._id!)[0])
|
||||
const primaryLink = linkedTable.primary?.[0] as string
|
||||
row[fieldName] = await handleRequest(Operation.READ, linkedTableId!, {
|
||||
const relatedRows = await handleRequest(Operation.READ, linkedTableId!, {
|
||||
tables,
|
||||
filters: {
|
||||
oneOf: {
|
||||
|
@ -216,6 +220,10 @@ export async function fetchEnrichedRow(ctx: UserCtx) {
|
|||
},
|
||||
includeSqlRelationships: IncludeRelationship.INCLUDE,
|
||||
})
|
||||
row[fieldName] = await outputProcessing(linkedTable, relatedRows, {
|
||||
squash: true,
|
||||
preserveLinks: true,
|
||||
})
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
|
|
@ -86,12 +86,12 @@ export async function updateAllFormulasInTable(table: Table) {
|
|||
const db = context.getAppDB()
|
||||
// start by getting the raw rows (which will be written back to DB after update)
|
||||
let rows = (
|
||||
await db.allDocs(
|
||||
await db.allDocs<Row>(
|
||||
getRowParams(table._id, null, {
|
||||
include_docs: true,
|
||||
})
|
||||
)
|
||||
).rows.map(row => row.doc)
|
||||
).rows.map(row => row.doc as Row)
|
||||
// now enrich the rows, note the clone so that we have the base state of the
|
||||
// rows so that we don't write any of the enriched information back
|
||||
let enrichedRows = await outputProcessing(table, cloneDeep(rows), {
|
||||
|
@ -101,12 +101,12 @@ export async function updateAllFormulasInTable(table: Table) {
|
|||
for (let row of rows) {
|
||||
// find the enriched row, if found process the formulas
|
||||
const enrichedRow = enrichedRows.find(
|
||||
(enriched: any) => enriched._id === row._id
|
||||
(enriched: Row) => enriched._id === row._id
|
||||
)
|
||||
if (enrichedRow) {
|
||||
const processed = processFormulas(table, cloneDeep(row), {
|
||||
dynamic: false,
|
||||
contextRows: enrichedRow,
|
||||
contextRows: [enrichedRow],
|
||||
})
|
||||
// values have changed, need to add to bulk docs to update
|
||||
if (!isEqual(processed, row)) {
|
||||
|
@ -139,7 +139,7 @@ export async function finaliseRow(
|
|||
// use enriched row to generate formulas for saving, specifically only use as context
|
||||
row = processFormulas(table, row, {
|
||||
dynamic: false,
|
||||
contextRows: enrichedRow,
|
||||
contextRows: [enrichedRow],
|
||||
})
|
||||
// don't worry about rev, tables handle rev/lastID updates
|
||||
// if another row has been written since processing this will
|
||||
|
@ -163,7 +163,9 @@ export async function finaliseRow(
|
|||
const response = await db.put(row)
|
||||
// for response, calculate the formulas for the enriched row
|
||||
enrichedRow._rev = response.rev
|
||||
enrichedRow = await processFormulas(table, enrichedRow, { dynamic: false })
|
||||
enrichedRow = processFormulas(table, enrichedRow, {
|
||||
dynamic: false,
|
||||
})
|
||||
// this updates the related formulas in other rows based on the relations to this row
|
||||
if (updateFormula) {
|
||||
await updateRelatedFormula(table, enrichedRow)
|
||||
|
|
|
@ -2,7 +2,6 @@ import LinkController from "./LinkController"
|
|||
import {
|
||||
IncludeDocs,
|
||||
getLinkDocuments,
|
||||
createLinkView,
|
||||
getUniqueByProp,
|
||||
getRelatedTableForField,
|
||||
getLinkedTableIDs,
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
LinkDocumentValue,
|
||||
Table,
|
||||
} from "@budibase/types"
|
||||
import sdk from "../../sdk"
|
||||
|
||||
export { createLinkView } from "../views/staticViews"
|
||||
|
||||
|
@ -110,12 +111,11 @@ export function getLinkedTableIDs(table: Table): string[] {
|
|||
}
|
||||
|
||||
export async function getLinkedTable(id: string, tables: Table[]) {
|
||||
const db = context.getAppDB()
|
||||
let linkedTable = tables.find(table => table._id === id)
|
||||
if (linkedTable) {
|
||||
return linkedTable
|
||||
}
|
||||
linkedTable = await db.get(id)
|
||||
linkedTable = await sdk.tables.getTable(id)
|
||||
if (linkedTable) {
|
||||
tables.push(linkedTable)
|
||||
}
|
||||
|
|
|
@ -80,7 +80,10 @@ export async function search(options: SearchParams) {
|
|||
rows = rows.map((r: any) => pick(r, fields))
|
||||
}
|
||||
|
||||
rows = await outputProcessing(table, rows, { preserveLinks: true })
|
||||
rows = await outputProcessing(table, rows, {
|
||||
preserveLinks: true,
|
||||
squash: true,
|
||||
})
|
||||
|
||||
// need wrapper object for bookmarks etc when paginating
|
||||
return { rows, hasNextPage, bookmark: bookmark && bookmark + 1 }
|
||||
|
|
|
@ -258,7 +258,7 @@ export async function outputProcessing<T extends Row[] | Row>(
|
|||
}
|
||||
|
||||
// process formulas after the complex types had been processed
|
||||
enriched = processFormulas(table, enriched, { dynamic: true }) as Row[]
|
||||
enriched = processFormulas(table, enriched, { dynamic: true })
|
||||
|
||||
if (opts.squash) {
|
||||
enriched = (await linkRows.squashLinksToPrimaryDisplay(
|
||||
|
|
|
@ -12,6 +12,11 @@ import {
|
|||
Table,
|
||||
} from "@budibase/types"
|
||||
|
||||
interface FormulaOpts {
|
||||
dynamic?: boolean
|
||||
contextRows?: Row[]
|
||||
}
|
||||
|
||||
/**
|
||||
* If the subtype has been lost for any reason this works out what
|
||||
* subtype the auto column should be.
|
||||
|
@ -40,52 +45,50 @@ export function fixAutoColumnSubType(
|
|||
/**
|
||||
* Looks through the rows provided and finds formulas - which it then processes.
|
||||
*/
|
||||
export function processFormulas(
|
||||
export function processFormulas<T extends Row | Row[]>(
|
||||
table: Table,
|
||||
rows: Row[] | Row,
|
||||
{ dynamic, contextRows }: any = { dynamic: true }
|
||||
) {
|
||||
const single = !Array.isArray(rows)
|
||||
let rowArray: Row[]
|
||||
if (single) {
|
||||
rowArray = [rows]
|
||||
contextRows = contextRows ? [contextRows] : contextRows
|
||||
} else {
|
||||
rowArray = rows
|
||||
}
|
||||
for (let [column, schema] of Object.entries(table.schema)) {
|
||||
if (schema.type !== FieldTypes.FORMULA) {
|
||||
continue
|
||||
}
|
||||
inputRows: T,
|
||||
{ dynamic, contextRows }: FormulaOpts = { dynamic: true }
|
||||
): Promise<T> {
|
||||
const rows = Array.isArray(inputRows) ? inputRows : [inputRows]
|
||||
if (rows)
|
||||
for (let [column, schema] of Object.entries(table.schema)) {
|
||||
if (schema.type !== FieldTypes.FORMULA) {
|
||||
continue
|
||||
}
|
||||
|
||||
const isStatic = schema.formulaType === FormulaTypes.STATIC
|
||||
const isStatic = schema.formulaType === FormulaTypes.STATIC
|
||||
|
||||
if (
|
||||
schema.formula == null ||
|
||||
(dynamic && isStatic) ||
|
||||
(!dynamic && !isStatic)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
// iterate through rows and process formula
|
||||
for (let i = 0; i < rowArray.length; i++) {
|
||||
let row = rowArray[i]
|
||||
let context = contextRows ? contextRows[i] : row
|
||||
rowArray[i] = {
|
||||
...row,
|
||||
[column]: processStringSync(schema.formula, context),
|
||||
if (
|
||||
schema.formula == null ||
|
||||
(dynamic && isStatic) ||
|
||||
(!dynamic && !isStatic)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
// iterate through rows and process formula
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
let row = rows[i]
|
||||
let context = contextRows ? contextRows[i] : row
|
||||
rows[i] = {
|
||||
...row,
|
||||
[column]: processStringSync(schema.formula, context),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return single ? rowArray[0] : rowArray
|
||||
return Array.isArray(inputRows) ? rows : rows[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes any date columns and ensures that those without the ignoreTimezones
|
||||
* flag set are parsed as UTC rather than local time.
|
||||
*/
|
||||
export function processDates(table: Table, rows: Row[]) {
|
||||
let datesWithTZ = []
|
||||
export function processDates<T extends Row | Row[]>(
|
||||
table: Table,
|
||||
inputRows: T
|
||||
): T {
|
||||
let rows = Array.isArray(inputRows) ? inputRows : [inputRows]
|
||||
let datesWithTZ: string[] = []
|
||||
for (let [column, schema] of Object.entries(table.schema)) {
|
||||
if (schema.type !== FieldTypes.DATETIME) {
|
||||
continue
|
||||
|
@ -102,5 +105,6 @@ export function processDates(table: Table, rows: Row[]) {
|
|||
}
|
||||
}
|
||||
}
|
||||
return rows
|
||||
|
||||
return Array.isArray(inputRows) ? rows : rows[0]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue