diff --git a/packages/builder/src/components/design/settings/controls/FilterEditor/FilterDrawer.svelte b/packages/builder/src/components/design/settings/controls/FilterEditor/FilterDrawer.svelte index 822442fcea..0fd30b82bb 100644 --- a/packages/builder/src/components/design/settings/controls/FilterEditor/FilterDrawer.svelte +++ b/packages/builder/src/components/design/settings/controls/FilterEditor/FilterDrawer.svelte @@ -191,7 +191,7 @@ } const getValidOperatorsForType = filter => { - if (!filter) { + if (!filter?.field) { return [] } diff --git a/packages/pro b/packages/pro index 7040ae5282..044bec6447 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 7040ae5282cc23d7ae56ac1be8a369d1c32aab2f +Subproject commit 044bec6447066b215932d6726c437e7ec5a9e42e diff --git a/packages/server/src/sdk/app/rows/search/external.ts b/packages/server/src/sdk/app/rows/search/external.ts index f908be0b3c..8dd141f8ef 100644 --- a/packages/server/src/sdk/app/rows/search/external.ts +++ b/packages/server/src/sdk/app/rows/search/external.ts @@ -16,6 +16,7 @@ import { cleanExportRows } from "../utils" import { utils } from "@budibase/shared-core" import { ExportRowsParams, ExportRowsResult } from "../search" import { HTTPError, db } from "@budibase/backend-core" +import { searchInputMapping } from "./utils" import pick from "lodash/pick" import { outputProcessing } from "../../../../utilities/rowProcessor" @@ -50,7 +51,10 @@ export async function search(options: SearchParams) { [params.sort]: { direction }, } } + try { + const table = await sdk.tables.getTable(tableId) + options = searchInputMapping(table, options) let rows = (await handleRequest(Operation.READ, tableId, { filters: query, sort, @@ -76,7 +80,6 @@ export async function search(options: SearchParams) { rows = rows.map((r: any) => pick(r, fields)) } - const table = await sdk.tables.getTable(tableId) rows = await outputProcessing(table, rows, { preserveLinks: true }) // need wrapper object for bookmarks etc when paginating diff --git a/packages/server/src/sdk/app/rows/search/internal.ts b/packages/server/src/sdk/app/rows/search/internal.ts index 4cdeca87f6..d78c0213b3 100644 --- a/packages/server/src/sdk/app/rows/search/internal.ts +++ b/packages/server/src/sdk/app/rows/search/internal.ts @@ -29,6 +29,7 @@ import { } from "../../../../api/controllers/view/utils" import sdk from "../../../../sdk" import { ExportRowsParams, ExportRowsResult } from "../search" +import { searchInputMapping } from "./utils" import pick from "lodash/pick" export async function search(options: SearchParams) { @@ -47,9 +48,9 @@ export async function search(options: SearchParams) { disableEscaping: options.disableEscaping, } - let table + let table = await sdk.tables.getTable(tableId) + options = searchInputMapping(table, options) if (params.sort && !params.sortType) { - table = await sdk.tables.getTable(tableId) const schema = table.schema const sortField = schema[params.sort] params.sortType = sortField.type === "number" ? "number" : "string" @@ -68,7 +69,6 @@ export async function search(options: SearchParams) { if (tableId === InternalTables.USER_METADATA) { response.rows = await getGlobalUsersFromMetadata(response.rows) } - table = table || (await sdk.tables.getTable(tableId)) if (options.fields) { const fields = [...options.fields, ...db.CONSTANT_INTERNAL_ROW_COLS] diff --git a/packages/server/src/sdk/app/rows/search/tests/utils.spec.ts b/packages/server/src/sdk/app/rows/search/tests/utils.spec.ts new file mode 100644 index 0000000000..08d1f1b1cb --- /dev/null +++ b/packages/server/src/sdk/app/rows/search/tests/utils.spec.ts @@ -0,0 +1,77 @@ +import { searchInputMapping } from "../utils" +import { db as dbCore } from "@budibase/backend-core" +import { + FieldType, + FieldTypeSubtypes, + Table, + SearchParams, +} from "@budibase/types" + +const tableId = "ta_a" +const tableWithUserCol: Table = { + _id: tableId, + name: "table", + schema: { + user: { + name: "user", + type: FieldType.BB_REFERENCE, + subtype: FieldTypeSubtypes.BB_REFERENCE.USER, + }, + }, +} + +describe("searchInputMapping", () => { + const globalUserId = dbCore.generateGlobalUserID() + const userMedataId = dbCore.generateUserMetadataID(globalUserId) + + it("should be able to map ro_ to global user IDs", () => { + const params: SearchParams = { + tableId, + query: { + equal: { + "1:user": userMedataId, + }, + }, + } + const output = searchInputMapping(tableWithUserCol, params) + expect(output.query.equal!["1:user"]).toBe(globalUserId) + }) + + it("should handle array of user IDs", () => { + const params: SearchParams = { + tableId, + query: { + oneOf: { + "1:user": [userMedataId, globalUserId], + }, + }, + } + const output = searchInputMapping(tableWithUserCol, params) + expect(output.query.oneOf!["1:user"]).toStrictEqual([ + globalUserId, + globalUserId, + ]) + }) + + it("shouldn't change any other input", () => { + const email = "test@test.com" + const params: SearchParams = { + tableId, + query: { + equal: { + "1:user": email, + }, + }, + } + const output = searchInputMapping(tableWithUserCol, params) + expect(output.query.equal!["1:user"]).toBe(email) + }) + + it("shouldn't error if no query supplied", () => { + const params: any = { + tableId, + } + const output = searchInputMapping(tableWithUserCol, params) + expect(output.query).toBeUndefined() + }) +}) diff --git a/packages/server/src/sdk/app/rows/search/utils.ts b/packages/server/src/sdk/app/rows/search/utils.ts new file mode 100644 index 0000000000..14f7907e4f --- /dev/null +++ b/packages/server/src/sdk/app/rows/search/utils.ts @@ -0,0 +1,76 @@ +import { + FieldType, + FieldTypeSubtypes, + SearchParams, + Table, + DocumentType, + SEPARATOR, +} from "@budibase/types" +import { db as dbCore } from "@budibase/backend-core" + +function findColumnInQueries( + column: string, + options: SearchParams, + callback: (filter: any) => any +) { + if (!options.query) { + return + } + for (let filterBlock of Object.values(options.query)) { + if (typeof filterBlock !== "object") { + continue + } + for (let [key, filter] of Object.entries(filterBlock)) { + if (key.endsWith(column)) { + filterBlock[key] = callback(filter) + } + } + } +} + +function userColumnMapping(column: string, options: SearchParams) { + findColumnInQueries(column, options, (filterValue: any): any => { + const isArray = Array.isArray(filterValue), + isString = typeof filterValue === "string" + if (!isString && !isArray) { + return filterValue + } + const processString = (input: string) => { + const rowPrefix = DocumentType.ROW + SEPARATOR + if (input.startsWith(rowPrefix)) { + return dbCore.getGlobalIDFromUserMetadataID(input) + } else { + return input + } + } + if (isArray) { + return filterValue.map(el => { + if (typeof el === "string") { + return processString(el) + } else { + return el + } + }) + } else { + return processString(filterValue) + } + }) +} + +// maps through the search parameters to check if any of the inputs are invalid +// based on the table schema, converts them to something that is valid. +export function searchInputMapping(table: Table, options: SearchParams) { + if (!table?.schema) { + return options + } + for (let [key, column] of Object.entries(table.schema)) { + switch (column.type) { + case FieldType.BB_REFERENCE: + if (column.subtype === FieldTypeSubtypes.BB_REFERENCE.USER) { + userColumnMapping(key, options) + } + break + } + } + return options +} diff --git a/packages/shared-core/src/filters.ts b/packages/shared-core/src/filters.ts index 6ab8ce623e..e443f35dbe 100644 --- a/packages/shared-core/src/filters.ts +++ b/packages/shared-core/src/filters.ts @@ -14,7 +14,6 @@ const HBS_REGEX = /{{([^{].*?)}}/g /** * Returns the valid operator options for a certain data type - * @param type the data type */ export const getValidOperatorsForType = ( type: FieldType,