diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts index 6abfe0c681..caf85a7858 100644 --- a/packages/server/src/sdk/app/rows/search.ts +++ b/packages/server/src/sdk/app/rows/search.ts @@ -5,6 +5,7 @@ import { Row, RowSearchParams, SearchFilterKey, + SearchFilters, SearchResponse, SortOrder, Table, @@ -90,11 +91,16 @@ export async function search( if (options.viewId) { const view = source as ViewV2 + // 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 = await enrichSearchContext(view.query || {}, context) - viewQuery = dataFilters.buildQueryLegacy(viewQuery) || {} + let viewQuery = (await enrichSearchContext(view.query || {}, context)) as + | SearchFilters + | LegacyFilter[] + if (Array.isArray(viewQuery)) { + viewQuery = dataFilters.buildQuery(viewQuery) + } viewQuery = checkFilters(table, viewQuery) const sqsEnabled = await features.flags.isEnabled("SQS") diff --git a/packages/shared-core/src/filters.ts b/packages/shared-core/src/filters.ts index 932d5a8ca0..eda1139c5e 100644 --- a/packages/shared-core/src/filters.ts +++ b/packages/shared-core/src/filters.ts @@ -432,134 +432,6 @@ const buildCondition = (expression: LegacyFilter) => { return query } -export const buildQueryLegacy = ( - filter?: LegacyFilter[] | SearchFilters -): SearchFilters | undefined => { - // this is of type SearchFilters or is undefined - if (!Array.isArray(filter)) { - return filter - } - - let query: SearchFilters = { - string: {}, - fuzzy: {}, - range: {}, - equal: {}, - notEqual: {}, - empty: {}, - notEmpty: {}, - contains: {}, - notContains: {}, - oneOf: {}, - containsAny: {}, - } - - if (!Array.isArray(filter)) { - return query - } - - filter.forEach(expression => { - let { operator, field, type, value, externalType, onEmptyFilter } = - expression - const queryOperator = operator as SearchFilterOperator - const isHbs = - typeof value === "string" && (value.match(HBS_REGEX) || []).length > 0 - // Parse all values into correct types - if (operator === "allOr") { - query.allOr = true - return - } - if (onEmptyFilter) { - query.onEmptyFilter = onEmptyFilter - return - } - if ( - type === "datetime" && - !isHbs && - queryOperator !== "empty" && - queryOperator !== "notEmpty" - ) { - // Ensure date value is a valid date and parse into correct format - if (!value) { - return - } - try { - value = new Date(value).toISOString() - } catch (error) { - return - } - } - if (type === "number" && typeof value === "string" && !isHbs) { - if (queryOperator === "oneOf") { - value = value.split(",").map(item => parseFloat(item)) - } else { - value = parseFloat(value) - } - } - if (type === "boolean") { - value = `${value}`?.toLowerCase() === "true" - } - if ( - ["contains", "notContains", "containsAny"].includes( - operator.toLocaleString() - ) && - type === "array" && - typeof value === "string" - ) { - value = value.split(",") - } - if (operator.toLocaleString().startsWith("range") && query.range) { - const minint = - SqlNumberTypeRangeMap[ - externalType as keyof typeof SqlNumberTypeRangeMap - ]?.min || Number.MIN_SAFE_INTEGER - const maxint = - SqlNumberTypeRangeMap[ - externalType as keyof typeof SqlNumberTypeRangeMap - ]?.max || Number.MAX_SAFE_INTEGER - if (!query.range[field]) { - query.range[field] = { - low: type === "number" ? minint : "0000-00-00T00:00:00.000Z", - high: type === "number" ? maxint : "9999-00-00T00:00:00.000Z", - } - } - if (operator === "rangeLow" && value != null && value !== "") { - query.range[field] = { - ...query.range[field], - low: value, - } - } else if (operator === "rangeHigh" && value != null && value !== "") { - query.range[field] = { - ...query.range[field], - high: value, - } - } - } else if (isLogicalSearchOperator(queryOperator)) { - // ignore - } else if (query[queryOperator] && operator !== "onEmptyFilter") { - if (type === "boolean") { - // Transform boolean filters to cope with null. - // "equals false" needs to be "not equals true" - // "not equals false" needs to be "equals true" - if (queryOperator === "equal" && value === false) { - query.notEqual = query.notEqual || {} - query.notEqual[field] = true - } else if (queryOperator === "notEqual" && value === false) { - query.equal = query.equal || {} - query.equal[field] = true - } else { - query[queryOperator] ??= {} - query[queryOperator]![field] = value - } - } else { - query[queryOperator] ??= {} - query[queryOperator]![field] = value - } - } - }) - return query -} - /** * Converts a **UISearchFilter** filter definition into a grouped * search query of type **SearchFilters** diff --git a/packages/types/src/api/web/searchFilter.ts b/packages/types/src/api/web/searchFilter.ts index ef35217f9d..428d5d364a 100644 --- a/packages/types/src/api/web/searchFilter.ts +++ b/packages/types/src/api/web/searchFilter.ts @@ -1,6 +1,8 @@ import { FieldType } from "../../documents" import { EmptyFilterOption, UILogicalOperator, SearchFilters } from "../../sdk" +// Prior to v2, this is the type the frontend sent us when filters were +// involved. We convert this to a SearchFilters before use with the search SDK. export type LegacyFilter = { operator: keyof SearchFilters | "rangeLow" | "rangeHigh" onEmptyFilter?: EmptyFilterOption @@ -16,7 +18,14 @@ export type SearchFilterGroup = { filters?: LegacyFilter[] } -// this is a type purely used by the UI +// As of v3, this is the format that the frontend always sends when search +// filters are involved. We convert this to SearchFilters before use with the +// search SDK. +// +// The reason we migrated was that we started to support "logical operators" in +// tests and SearchFilters because a recursive data structure. LegacyFilter[] +// wasn't able to support these sorts of recursive structures, so we changed the +// format. export type UISearchFilter = { logicalOperator?: UILogicalOperator onEmptyFilter?: EmptyFilterOption