Merge branch 'master' into grid-layout-expansion

This commit is contained in:
Andrew Kingston 2024-08-20 15:07:47 +01:00 committed by GitHub
commit d96cd60618
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 121 additions and 89 deletions

View File

@ -1,6 +1,6 @@
{ {
"$schema": "node_modules/lerna/schemas/lerna-schema.json", "$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "2.30.7", "version": "2.30.8",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*", "packages/*",

View File

@ -2762,6 +2762,57 @@ describe.each([
}) })
}) })
isSql &&
describe("primaryDisplay", () => {
beforeAll(async () => {
let toRelateTable = await createTable({
name: {
name: "name",
type: FieldType.STRING,
},
})
table = await config.api.table.save(
tableForDatasource(datasource, {
schema: {
name: {
name: "name",
type: FieldType.STRING,
},
link: {
name: "link",
type: FieldType.LINK,
relationshipType: RelationshipType.MANY_TO_ONE,
tableId: toRelateTable._id!,
fieldName: "link",
},
},
})
)
toRelateTable = await config.api.table.get(toRelateTable._id!)
await config.api.table.save({
...toRelateTable,
primaryDisplay: "link",
})
const relatedRows = await Promise.all([
config.api.row.save(toRelateTable._id!, { name: "test" }),
])
await Promise.all([
config.api.row.save(table._id!, {
name: "test",
link: relatedRows.map(row => row._id),
}),
])
})
it("should be able to query, primary display on related table shouldn't be used", async () => {
// this test makes sure that if a relationship has been specified as the primary display on a table
// it is ignored and another column is used instead
await expectQuery({}).toContain([
{ name: "test", link: [{ primaryDisplay: "test" }] },
])
})
})
!isLucene && !isLucene &&
describe("$and", () => { describe("$and", () => {
beforeAll(async () => { beforeAll(async () => {

View File

@ -1,10 +1,10 @@
import LinkController from "./LinkController" import LinkController from "./LinkController"
import { import {
getLinkDocuments, getLinkDocuments,
getUniqueByProp,
getRelatedTableForField,
getLinkedTableIDs,
getLinkedTable, getLinkedTable,
getLinkedTableIDs,
getRelatedTableForField,
getUniqueByProp,
} from "./linkUtils" } from "./linkUtils"
import flatten from "lodash/flatten" import flatten from "lodash/flatten"
import { USER_METDATA_PREFIX } from "../utils" import { USER_METDATA_PREFIX } from "../utils"
@ -13,16 +13,25 @@ import { getGlobalUsersFromMetadata } from "../../utilities/global"
import { processFormulas } from "../../utilities/rowProcessor" import { processFormulas } from "../../utilities/rowProcessor"
import { context } from "@budibase/backend-core" import { context } from "@budibase/backend-core"
import { import {
Table,
Row,
LinkDocumentValue,
FieldType,
ContextUser, ContextUser,
FieldType,
LinkDocumentValue,
Row,
Table,
} from "@budibase/types" } from "@budibase/types"
import sdk from "../../sdk" import sdk from "../../sdk"
export { IncludeDocs, getLinkDocuments, createLinkView } from "./linkUtils" export { IncludeDocs, getLinkDocuments, createLinkView } from "./linkUtils"
const INVALID_DISPLAY_COLUMN_TYPE = [
FieldType.LINK,
FieldType.ATTACHMENTS,
FieldType.ATTACHMENT_SINGLE,
FieldType.SIGNATURE_SINGLE,
FieldType.BB_REFERENCE,
FieldType.BB_REFERENCE_SINGLE,
]
/** /**
* This functionality makes sure that when rows with links are created, updated or deleted they are processed * This functionality makes sure that when rows with links are created, updated or deleted they are processed
* correctly - making sure that no stale links are left around and that all links have been made successfully. * correctly - making sure that no stale links are left around and that all links have been made successfully.
@ -206,6 +215,31 @@ export async function attachFullLinkedDocs(
return rows return rows
} }
/**
* Finds a valid value for the primary display, avoiding columns which break things
* like relationships (can be circular).
* @param row The row to lift a value from for the primary display.
* @param table The related table to attempt to work out the primary display column from.
*/
function getPrimaryDisplayValue(row: Row, table?: Table) {
const primaryDisplay = table?.primaryDisplay
let invalid = true
if (primaryDisplay) {
const primaryDisplaySchema = table?.schema[primaryDisplay]
invalid = INVALID_DISPLAY_COLUMN_TYPE.includes(primaryDisplaySchema.type)
}
if (invalid || !primaryDisplay) {
const validKey = Object.keys(table?.schema || {}).find(
key =>
table?.schema[key].type &&
!INVALID_DISPLAY_COLUMN_TYPE.includes(table?.schema[key].type)
)
return validKey ? row[validKey] : undefined
} else {
return row[primaryDisplay]
}
}
/** /**
* This function will take the given enriched rows and squash the links to only contain the primary display field. * This function will take the given enriched rows and squash the links to only contain the primary display field.
* @param table The table from which the rows originated. * @param table The table from which the rows originated.
@ -232,9 +266,7 @@ export async function squashLinksToPrimaryDisplay(
const linkTblId = link.tableId || getRelatedTableForField(table, column) const linkTblId = link.tableId || getRelatedTableForField(table, column)
const linkedTable = await getLinkedTable(linkTblId!, linkedTables) const linkedTable = await getLinkedTable(linkTblId!, linkedTables)
const obj: any = { _id: link._id } const obj: any = { _id: link._id }
if (linkedTable?.primaryDisplay && link[linkedTable.primaryDisplay]) { obj.primaryDisplay = getPrimaryDisplayValue(link, linkedTable)
obj.primaryDisplay = link[linkedTable.primaryDisplay]
}
newLinks.push(obj) newLinks.push(obj)
} }
row[column] = newLinks row[column] = newLinks

View File

@ -16,32 +16,32 @@ export const removeInvalidFilters = (
validFields = validFields.map(f => f.toLowerCase()) validFields = validFields.map(f => f.toLowerCase())
for (const filterKey of Object.keys(result) as (keyof SearchFilters)[]) { for (const filterKey of Object.keys(result) as (keyof SearchFilters)[]) {
if (typeof result[filterKey] !== "object") { const filter = result[filterKey]
if (!filter || typeof filter !== "object") {
continue continue
} }
if (isLogicalSearchOperator(filterKey)) { if (isLogicalSearchOperator(filterKey)) {
const resultingConditions: SearchFilters[] = [] const resultingConditions: SearchFilters[] = []
for (const condition of result[filterKey].conditions) { for (const condition of filter.conditions) {
const resultingCondition = removeInvalidFilters(condition, validFields) const resultingCondition = removeInvalidFilters(condition, validFields)
if (Object.keys(resultingCondition).length) { if (Object.keys(resultingCondition).length) {
resultingConditions.push(resultingCondition) resultingConditions.push(resultingCondition)
} }
} }
if (resultingConditions.length) { if (resultingConditions.length) {
result[filterKey].conditions = resultingConditions filter.conditions = resultingConditions
} else { } else {
delete result[filterKey] delete result[filterKey]
} }
continue continue
} }
const filter = result[filterKey]
for (const columnKey of Object.keys(filter)) { for (const columnKey of Object.keys(filter)) {
const possibleKeys = [columnKey, db.removeKeyNumbering(columnKey)].map( const possibleKeys = [columnKey, db.removeKeyNumbering(columnKey)].map(
c => c.toLowerCase() c => c.toLowerCase()
) )
if (!validFields.some(f => possibleKeys.includes(f.toLowerCase()))) { if (!validFields.some(f => possibleKeys.includes(f.toLowerCase()))) {
delete filter[columnKey] delete filter[columnKey as keyof typeof filter]
} }
} }
if (!Object.keys(filter).length) { if (!Object.keys(filter).length) {
@ -59,24 +59,31 @@ export const getQueryableFields = async (
const extractTableFields = async ( const extractTableFields = async (
table: Table, table: Table,
allowedFields: string[], allowedFields: string[],
fromTables: string[] fromTables: string[],
opts?: { noRelationships?: boolean }
): Promise<string[]> => { ): Promise<string[]> => {
const result = [] const result = []
for (const field of Object.keys(table.schema).filter( for (const field of Object.keys(table.schema).filter(
f => allowedFields.includes(f) && table.schema[f].visible !== false f => allowedFields.includes(f) && table.schema[f].visible !== false
)) { )) {
const subSchema = table.schema[field] const subSchema = table.schema[field]
if (subSchema.type === FieldType.LINK) { const isRelationship = subSchema.type === FieldType.LINK
if (fromTables.includes(subSchema.tableId)) { // avoid relationship loops
// avoid circular loops if (
continue isRelationship &&
} (opts?.noRelationships || fromTables.includes(subSchema.tableId))
) {
continue
}
if (isRelationship) {
try { try {
const relatedTable = await sdk.tables.getTable(subSchema.tableId) const relatedTable = await sdk.tables.getTable(subSchema.tableId)
const relatedFields = await extractTableFields( const relatedFields = await extractTableFields(
relatedTable, relatedTable,
Object.keys(relatedTable.schema), Object.keys(relatedTable.schema),
[...fromTables, subSchema.tableId] [...fromTables, subSchema.tableId],
// don't let it recurse back and forth between relationships
{ noRelationships: true }
) )
result.push( result.push(

View File

@ -386,35 +386,13 @@ describe("query utils", () => {
expect(result).toEqual([ expect(result).toEqual([
"_id", "_id",
"name", "name",
// deep 1 aux1 primitive props // aux1 primitive props
"aux1.name", "aux1.name",
"aux1Table.name", "aux1Table.name",
// deep 2 aux1 primitive props // aux2 primitive props
"aux1.aux2_1.title",
"aux1Table.aux2_1.title",
"aux1.aux2Table.title",
"aux1Table.aux2Table.title",
// deep 2 aux2 primitive props
"aux1.aux2_2.title",
"aux1Table.aux2_2.title",
"aux1.aux2Table.title",
"aux1Table.aux2Table.title",
// deep 1 aux2 primitive props
"aux2.title", "aux2.title",
"aux2Table.title", "aux2Table.title",
// deep 2 aux2 primitive props
"aux2.aux1_1.name",
"aux2Table.aux1_1.name",
"aux2.aux1Table.name",
"aux2Table.aux1Table.name",
"aux2.aux1_2.name",
"aux2Table.aux1_2.name",
"aux2.aux1Table.name",
"aux2Table.aux1Table.name",
]) ])
}) })
@ -426,35 +404,17 @@ describe("query utils", () => {
"_id", "_id",
"name", "name",
// deep 1 aux2_1 primitive props // aux2_1 primitive props
"aux2_1.title", "aux2_1.title",
"aux2Table.title", "aux2Table.title",
// deep 2 aux2_1 primitive props // aux2_2 primitive props
"aux2_1.table.name",
"aux2Table.table.name",
"aux2_1.TestTable.name",
"aux2Table.TestTable.name",
// deep 1 aux2_2 primitive props
"aux2_2.title", "aux2_2.title",
"aux2Table.title", "aux2Table.title",
// deep 2 aux2_2 primitive props // table primitive props
"aux2_2.table.name",
"aux2Table.table.name",
"aux2_2.TestTable.name",
"aux2Table.TestTable.name",
// deep 1 table primitive props
"table.name", "table.name",
"TestTable.name", "TestTable.name",
// deep 2 table primitive props
"table.aux2.title",
"TestTable.aux2.title",
"table.aux2Table.title",
"TestTable.aux2Table.title",
]) ])
}) })
@ -466,35 +426,17 @@ describe("query utils", () => {
"_id", "_id",
"title", "title",
// deep 1 aux1_1 primitive props // aux1_1 primitive props
"aux1_1.name", "aux1_1.name",
"aux1Table.name", "aux1Table.name",
// deep 2 aux1_1 primitive props // aux1_2 primitive props
"aux1_1.table.name",
"aux1Table.table.name",
"aux1_1.TestTable.name",
"aux1Table.TestTable.name",
// deep 1 aux1_2 primitive props
"aux1_2.name", "aux1_2.name",
"aux1Table.name", "aux1Table.name",
// deep 2 aux1_2 primitive props // table primitive props
"aux1_2.table.name",
"aux1Table.table.name",
"aux1_2.TestTable.name",
"aux1Table.TestTable.name",
// deep 1 table primitive props
"table.name", "table.name",
"TestTable.name", "TestTable.name",
// deep 2 table primitive props
"table.aux1.name",
"TestTable.aux1.name",
"table.aux1Table.name",
"TestTable.aux1Table.name",
]) ])
}) })
}) })