From 6e1cd6eb01dba0d28e0da3ca5b209275a885ffe0 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 30 Sep 2024 15:18:42 +0200 Subject: [PATCH] Move query logic to sdk --- .../server/src/api/controllers/row/views.ts | 72 ++++---------- packages/server/src/sdk/app/rows/search.ts | 95 ++++++++++++++----- 2 files changed, 87 insertions(+), 80 deletions(-) diff --git a/packages/server/src/api/controllers/row/views.ts b/packages/server/src/api/controllers/row/views.ts index 68958da8e7..66c755b881 100644 --- a/packages/server/src/api/controllers/row/views.ts +++ b/packages/server/src/api/controllers/row/views.ts @@ -3,14 +3,9 @@ import { ViewV2, SearchRowResponse, SearchViewRowRequest, - SearchFilterKey, - LogicalOperator, } from "@budibase/types" -import { dataFilters } from "@budibase/shared-core" import sdk from "../../../sdk" -import { db, context, features } from "@budibase/backend-core" -import { enrichSearchContext } from "./utils" -import { isExternalTableID } from "../../../integrations/utils" +import { context } from "@budibase/backend-core" export async function searchView( ctx: UserCtx @@ -27,58 +22,23 @@ export async function searchView( const { body } = ctx.request - // Enrich saved query with ephemeral query params. - // We prevent searching on any fields that are saved as part of the query, as - // that could let users find rows they should not be allowed to access. - let query = dataFilters.buildQuery(view.query || []) - if (body.query) { - // Delete extraneous search params that cannot be overridden - delete body.query.onEmptyFilter - - if ( - !isExternalTableID(view.tableId) && - !(await features.flags.isEnabled("SQS")) - ) { - // Extract existing fields - const existingFields = - view.query - ?.filter(filter => filter.field) - .map(filter => db.removeKeyNumbering(filter.field)) || [] - - // Carry over filters for unused fields - Object.keys(body.query).forEach(key => { - const operator = key as Exclude - Object.keys(body.query[operator] || {}).forEach(field => { - if (!existingFields.includes(db.removeKeyNumbering(field))) { - query[operator]![field] = body.query[operator]![field] - } - }) - }) - } else { - query = { - $and: { - conditions: [query, body.query], - }, - } - } - } - await context.ensureSnippetContext(true) - const enrichedQuery = await enrichSearchContext(query, { - user: sdk.users.getUserContextBindings(ctx.user), - }) - - const result = await sdk.rows.search({ - viewId: view.id, - tableId: view.tableId, - query: enrichedQuery, - ...getSortOptions(body, view), - limit: body.limit, - bookmark: body.bookmark, - paginate: body.paginate, - countRows: body.countRows, - }) + const result = await sdk.rows.search( + { + viewId: view.id, + tableId: view.tableId, + query: body.query, + ...getSortOptions(body, view), + limit: body.limit, + bookmark: body.bookmark, + paginate: body.paginate, + countRows: body.countRows, + }, + { + user: sdk.users.getUserContextBindings(ctx.user), + } + ) result.rows.forEach(r => (r._viewId = view.id)) ctx.body = result diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts index 809bd73d1f..c09c625085 100644 --- a/packages/server/src/sdk/app/rows/search.ts +++ b/packages/server/src/sdk/app/rows/search.ts @@ -1,7 +1,10 @@ import { EmptyFilterOption, + LogicalOperator, Row, RowSearchParams, + SearchFilterKey, + SearchFilters, SearchResponse, SortOrder, Table, @@ -14,9 +17,10 @@ import { ExportRowsParams, ExportRowsResult } from "./search/types" import { dataFilters } from "@budibase/shared-core" import sdk from "../../index" import { searchInputMapping } from "./search/utils" -import { features } from "@budibase/backend-core" +import { db, features } from "@budibase/backend-core" import tracer from "dd-trace" import { getQueryableFields, removeInvalidFilters } from "./queryUtils" +import { enrichSearchContext } from "../../../api/controllers/row/utils" export { isValidFilter } from "../../../integrations/utils" @@ -34,7 +38,8 @@ function pickApi(tableId: any) { } export async function search( - options: RowSearchParams + options: RowSearchParams, + context?: Record ): Promise> { return await tracer.trace("search", async span => { span?.addTags({ @@ -51,6 +56,69 @@ export async function search( countRows: options.countRows, }) + let source: Table | ViewV2 + let table: Table + if (options.viewId) { + source = await sdk.views.get(options.viewId) + table = await sdk.views.getTable(source) + options = searchInputMapping(table, options) + } else if (options.tableId) { + source = await sdk.tables.getTable(options.tableId) + table = source + } else { + throw new Error(`Must supply either a view ID or a table ID`) + } + + if (options.query) { + const visibleFields = ( + options.fields || Object.keys(table.schema) + ).filter(field => table.schema[field].visible !== false) + + const queryableFields = await getQueryableFields(table, visibleFields) + options.query = removeInvalidFilters(options.query, queryableFields) + } + + if (options.viewId) { + const view = await sdk.views.get(options.viewId) + // Enrich saved query with ephemeral query params. + // We prevent searching on any fields that are saved as part of the query, as + // that could let users find rows they should not be allowed to access. + let viewQuery = dataFilters.buildQuery(view.query || []) + + if (!isExternalTable && !(await features.flags.isEnabled("SQS"))) { + // Lucene does not accept conditional filters, so we need to keep the old logic + const query: SearchFilters = {} + + // Extract existing fields + const existingFields = + view.query + ?.filter(filter => filter.field) + .map(filter => db.removeKeyNumbering(filter.field)) || [] + + // Carry over filters for unused fields + Object.keys(options.query).forEach(key => { + const operator = key as Exclude + Object.keys(options.query[operator] || {}).forEach(field => { + if (existingFields.includes(db.removeKeyNumbering(field))) { + query[operator]![field] = viewQuery[operator]![field] + } else { + query[operator]![field] = options.query[operator]![field] + } + }) + }) + } else { + options.query = { + $and: { + conditions: [viewQuery, options.query], + }, + } + } + } + + if (context) { + options.query = await enrichSearchContext(options.query, context) + } + options.query = dataFilters.cleanupQuery(options.query || {}) options.query = dataFilters.fixupFilterArrays(options.query) @@ -72,28 +140,7 @@ export async function search( options.sortOrder = options.sortOrder.toLowerCase() as SortOrder } - let source: Table | ViewV2 - let table: Table - if (options.viewId) { - source = await sdk.views.get(options.viewId) - table = await sdk.views.getTable(source) - options = searchInputMapping(table, options) - } else if (options.tableId) { - source = await sdk.tables.getTable(options.tableId) - table = source - options = searchInputMapping(table, options) - } else { - throw new Error(`Must supply either a view ID or a table ID`) - } - - if (options.query) { - const visibleFields = ( - options.fields || Object.keys(table.schema) - ).filter(field => table.schema[field].visible !== false) - - const queryableFields = await getQueryableFields(table, visibleFields) - options.query = removeInvalidFilters(options.query, queryableFields) - } + options = searchInputMapping(table, options) const isExternalTable = isExternalTableID(table._id!) let result: SearchResponse