Merge branch 'master' into grid-layout-expansion
This commit is contained in:
commit
d96cd60618
|
@ -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/*",
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 (
|
||||||
|
isRelationship &&
|
||||||
|
(opts?.noRelationships || fromTables.includes(subSchema.tableId))
|
||||||
|
) {
|
||||||
continue
|
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(
|
||||||
|
|
|
@ -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",
|
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue