2022-11-22 14:56:01 +01:00
|
|
|
import { clearColumns } from "./utils"
|
|
|
|
import { doesContainStrings } from "@budibase/string-templates"
|
|
|
|
import { cloneDeep } from "lodash/fp"
|
2023-07-27 21:36:32 +02:00
|
|
|
import isEqual from "lodash/isEqual"
|
|
|
|
import uniq from "lodash/uniq"
|
2022-11-22 14:56:01 +01:00
|
|
|
import { updateAllFormulasInTable } from "../row/staticFormula"
|
|
|
|
import { context } from "@budibase/backend-core"
|
2023-10-11 13:27:03 +02:00
|
|
|
import {
|
|
|
|
FieldSchema,
|
|
|
|
FieldType,
|
|
|
|
FormulaFieldMetadata,
|
2024-10-01 17:39:09 +02:00
|
|
|
FormulaType,
|
2023-10-11 13:27:03 +02:00
|
|
|
Table,
|
|
|
|
} from "@budibase/types"
|
2022-11-22 14:56:01 +01:00
|
|
|
import sdk from "../../../sdk"
|
2023-10-05 16:29:27 +02:00
|
|
|
import { isRelationshipColumn } from "../../../db/utils"
|
2022-01-25 17:01:04 +01:00
|
|
|
|
2023-10-05 16:22:01 +02:00
|
|
|
function isStaticFormula(
|
|
|
|
column: FieldSchema
|
2024-01-24 17:58:13 +01:00
|
|
|
): column is FormulaFieldMetadata & { formulaType: FormulaType.STATIC } {
|
2022-01-25 17:01:04 +01:00
|
|
|
return (
|
2023-10-11 13:27:03 +02:00
|
|
|
column.type === FieldType.FORMULA &&
|
2024-01-24 17:58:13 +01:00
|
|
|
column.formulaType === FormulaType.STATIC
|
2022-01-25 17:01:04 +01:00
|
|
|
)
|
|
|
|
}
|
2022-01-24 18:06:45 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This retrieves the formula columns from a table schema that use a specified column name
|
|
|
|
* in the formula.
|
|
|
|
*/
|
2022-11-22 14:56:01 +01:00
|
|
|
function getFormulaThatUseColumn(table: Table, columnNames: string[] | string) {
|
|
|
|
let formula: string[] = []
|
2022-01-24 19:22:59 +01:00
|
|
|
columnNames = Array.isArray(columnNames) ? columnNames : [columnNames]
|
2022-01-24 18:06:45 +01:00
|
|
|
for (let column of Object.values(table.schema)) {
|
|
|
|
// not a static formula, or doesn't contain a relationship
|
2022-01-25 17:01:04 +01:00
|
|
|
if (!isStaticFormula(column)) {
|
2022-01-24 18:06:45 +01:00
|
|
|
continue
|
|
|
|
}
|
2022-04-07 18:13:08 +02:00
|
|
|
if (!doesContainStrings(column?.formula ?? "", columnNames)) {
|
2022-01-24 18:06:45 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
formula.push(column.name)
|
|
|
|
}
|
|
|
|
return formula
|
|
|
|
}
|
|
|
|
|
2022-01-24 19:22:59 +01:00
|
|
|
/**
|
2022-01-25 17:01:04 +01:00
|
|
|
* This functions checks for when a related table, column or related column is deleted, if any
|
2022-01-24 19:22:59 +01:00
|
|
|
* tables need to have the formula column removed.
|
|
|
|
*/
|
2022-11-22 14:56:01 +01:00
|
|
|
async function checkIfFormulaNeedsCleared(
|
|
|
|
table: Table,
|
|
|
|
{ oldTable, deletion }: { oldTable?: Table; deletion?: boolean }
|
|
|
|
) {
|
2022-01-24 19:22:59 +01:00
|
|
|
// start by retrieving all tables, remove the current table from the list
|
2022-10-12 18:02:23 +02:00
|
|
|
const tables = (await sdk.tables.getAllInternalTables()).filter(
|
2022-01-24 19:22:59 +01:00
|
|
|
tbl => tbl._id !== table._id
|
|
|
|
)
|
|
|
|
const schemaToUse = oldTable ? oldTable.schema : table.schema
|
|
|
|
let removedColumns = Object.values(schemaToUse).filter(
|
|
|
|
column => deletion || !table.schema[column.name]
|
|
|
|
)
|
|
|
|
// remove any formula columns that used related columns
|
|
|
|
for (let removed of removedColumns) {
|
2022-11-22 14:56:01 +01:00
|
|
|
let tableToUse: Table | undefined = table
|
2022-01-24 19:22:59 +01:00
|
|
|
// if relationship, get the related table
|
2023-10-11 13:27:03 +02:00
|
|
|
if (removed.type === FieldType.LINK) {
|
2023-10-05 16:22:01 +02:00
|
|
|
const removedTableId = removed.tableId
|
|
|
|
tableToUse = tables.find(table => table._id === removedTableId)
|
2022-01-24 19:22:59 +01:00
|
|
|
}
|
2022-11-22 14:56:01 +01:00
|
|
|
if (!tableToUse) {
|
|
|
|
continue
|
|
|
|
}
|
2022-01-24 19:22:59 +01:00
|
|
|
const columnsToDelete = getFormulaThatUseColumn(tableToUse, removed.name)
|
|
|
|
if (columnsToDelete.length > 0) {
|
2022-01-31 18:00:22 +01:00
|
|
|
await clearColumns(table, columnsToDelete)
|
2022-01-24 19:22:59 +01:00
|
|
|
}
|
|
|
|
// need a special case, where a column has been removed from this table, but was used
|
|
|
|
// in a different, related tables formula
|
2022-01-25 17:01:04 +01:00
|
|
|
if (!table.relatedFormula) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for (let relatedTableId of table.relatedFormula) {
|
|
|
|
const relatedColumns = Object.values(table.schema).filter(
|
2023-10-11 13:27:03 +02:00
|
|
|
column =>
|
|
|
|
column.type === FieldType.LINK && column.tableId === relatedTableId
|
2022-01-25 17:01:04 +01:00
|
|
|
)
|
|
|
|
const relatedTable = tables.find(table => table._id === relatedTableId)
|
|
|
|
// look to see if the column was used in a relationship formula,
|
|
|
|
// relationships won't be used for this
|
2023-10-11 13:27:03 +02:00
|
|
|
if (relatedTable && relatedColumns && removed.type !== FieldType.LINK) {
|
2022-11-22 14:56:01 +01:00
|
|
|
let relatedFormulaToRemove: string[] = []
|
2022-01-25 17:01:04 +01:00
|
|
|
for (let column of relatedColumns) {
|
|
|
|
relatedFormulaToRemove = relatedFormulaToRemove.concat(
|
|
|
|
getFormulaThatUseColumn(relatedTable, [
|
2023-10-05 16:22:01 +02:00
|
|
|
(column as any).fieldName!,
|
2022-01-25 17:01:04 +01:00
|
|
|
removed.name,
|
|
|
|
])
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if (relatedFormulaToRemove.length > 0) {
|
2022-01-31 18:00:22 +01:00
|
|
|
await clearColumns(relatedTable, uniq(relatedFormulaToRemove))
|
2022-01-24 19:22:59 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-24 18:06:45 +01:00
|
|
|
/**
|
|
|
|
* This function adds a note to related tables that they are
|
|
|
|
* used in a static formula - so that the link controller
|
|
|
|
* can manage hydrating related rows formula fields. This is
|
|
|
|
* specifically only for static formula.
|
|
|
|
*/
|
2022-01-24 19:22:59 +01:00
|
|
|
async function updateRelatedFormulaLinksOnTables(
|
2022-11-22 14:56:01 +01:00
|
|
|
table: Table,
|
|
|
|
{ deletion }: { deletion?: boolean } = {}
|
2022-01-24 19:22:59 +01:00
|
|
|
) {
|
2022-11-22 14:56:01 +01:00
|
|
|
const tableId: string = table._id!
|
|
|
|
const db = context.getAppDB()
|
2022-01-24 18:06:45 +01:00
|
|
|
// start by retrieving all tables, remove the current table from the list
|
2022-10-12 18:02:23 +02:00
|
|
|
const tables = (await sdk.tables.getAllInternalTables()).filter(
|
2022-11-22 14:56:01 +01:00
|
|
|
tbl => tbl._id !== tableId
|
2022-01-24 18:06:45 +01:00
|
|
|
)
|
|
|
|
// clone the tables, so we can compare at end
|
|
|
|
const initialTables = cloneDeep(tables)
|
|
|
|
// first find the related column names
|
2023-10-05 16:29:27 +02:00
|
|
|
const relatedColumns = Object.values(table.schema).filter(
|
|
|
|
isRelationshipColumn
|
|
|
|
)
|
2022-01-24 18:06:45 +01:00
|
|
|
// we start by removing the formula field from all tables
|
|
|
|
for (let otherTable of tables) {
|
|
|
|
if (!otherTable.relatedFormula) {
|
|
|
|
continue
|
|
|
|
}
|
2022-11-22 14:56:01 +01:00
|
|
|
const index = otherTable.relatedFormula.indexOf(tableId)
|
2022-01-24 18:06:45 +01:00
|
|
|
if (index !== -1) {
|
|
|
|
otherTable.relatedFormula.splice(index, 1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if deleting, just remove the table IDs, don't try add
|
|
|
|
if (!deletion) {
|
|
|
|
for (let relatedCol of relatedColumns) {
|
|
|
|
let columns = getFormulaThatUseColumn(table, relatedCol.name)
|
|
|
|
if (!columns || columns.length === 0) {
|
|
|
|
continue
|
|
|
|
}
|
2023-10-05 16:22:01 +02:00
|
|
|
|
2022-01-24 18:06:45 +01:00
|
|
|
const relatedTable = tables.find(
|
|
|
|
related => related._id === relatedCol.tableId
|
|
|
|
)
|
|
|
|
// check if the table is already in the list of related formula, if it isn't, then add it
|
|
|
|
if (
|
|
|
|
relatedTable &&
|
|
|
|
(!relatedTable.relatedFormula ||
|
2022-11-22 14:56:01 +01:00
|
|
|
!relatedTable.relatedFormula.includes(tableId))
|
2022-01-24 18:06:45 +01:00
|
|
|
) {
|
|
|
|
relatedTable.relatedFormula = relatedTable.relatedFormula
|
2022-11-22 14:56:01 +01:00
|
|
|
? [...relatedTable.relatedFormula, tableId]
|
|
|
|
: [tableId]
|
2022-01-24 18:06:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// now we just need to compare all the tables and see if any need saved
|
|
|
|
for (let initial of initialTables) {
|
|
|
|
const found = tables.find(tbl => initial._id === tbl._id)
|
|
|
|
if (found && !isEqual(initial, found)) {
|
|
|
|
await db.put(found)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-01-24 19:22:59 +01:00
|
|
|
|
2022-11-22 14:56:01 +01:00
|
|
|
async function checkIfFormulaUpdated(
|
|
|
|
table: Table,
|
|
|
|
{ oldTable }: { oldTable?: Table }
|
|
|
|
) {
|
2022-01-25 17:01:04 +01:00
|
|
|
// look to see if any formula values have changed
|
|
|
|
const shouldUpdate = Object.values(table.schema).find(
|
|
|
|
column =>
|
|
|
|
isStaticFormula(column) &&
|
|
|
|
(!oldTable ||
|
|
|
|
!oldTable.schema[column.name] ||
|
|
|
|
!isEqual(oldTable.schema[column.name], column))
|
|
|
|
)
|
|
|
|
// if a static formula column has updated, then need to run the update
|
|
|
|
if (shouldUpdate != null) {
|
2022-01-31 18:00:22 +01:00
|
|
|
await updateAllFormulasInTable(table)
|
2022-01-25 17:01:04 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-22 14:56:01 +01:00
|
|
|
export async function runStaticFormulaChecks(
|
|
|
|
table: Table,
|
|
|
|
{ oldTable, deletion }: { oldTable?: Table; deletion?: boolean }
|
|
|
|
) {
|
2022-01-31 18:00:22 +01:00
|
|
|
await updateRelatedFormulaLinksOnTables(table, { deletion })
|
|
|
|
await checkIfFormulaNeedsCleared(table, { oldTable, deletion })
|
2022-01-25 17:01:04 +01:00
|
|
|
if (!deletion) {
|
2022-01-31 18:00:22 +01:00
|
|
|
await checkIfFormulaUpdated(table, { oldTable })
|
2022-01-25 17:01:04 +01:00
|
|
|
}
|
2022-01-24 19:22:59 +01:00
|
|
|
}
|