diff --git a/packages/pro b/packages/pro index cf3bef2aad..ecee8071eb 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit cf3bef2aad9c739111b306fd0712397adc363f81 +Subproject commit ecee8071ebe0f98a5bb19646954e373264be210d diff --git a/packages/server/src/api/controllers/row/views.ts b/packages/server/src/api/controllers/row/views.ts index 204cb6b4bc..c3b716ef88 100644 --- a/packages/server/src/api/controllers/row/views.ts +++ b/packages/server/src/api/controllers/row/views.ts @@ -7,6 +7,7 @@ import { RequiredKeys, SearchParams, } from "@budibase/types" +import { dataFilters } from "@budibase/shared-core" import sdk from "../../../sdk" export async function searchView( @@ -34,11 +35,12 @@ export async function searchView( ctx.status = 200 const { body } = ctx.request + const query = dataFilters.buildLuceneQuery(view.query || []) const searchOptions: RequiredKeys & RequiredKeys> = { tableId: view.tableId, - query: view.query || {}, + query, fields: viewFields, ...getSortOptions(body, view), limit: body.limit, diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index b9f584706e..3284d25de8 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -1072,7 +1072,7 @@ describe("/rows", () => { ) const createViewResponse = await config.api.viewV2.create({ - query: { equal: { age: 40 } }, + query: [{ operator: "equal", field: "age", value: 40 }], }) const response = await config.api.viewV2.search(createViewResponse.id) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index fc97e657c5..e30bc2c0b1 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -62,7 +62,7 @@ describe("/v2/views", () => { name: generator.name(), tableId: config.table!._id!, primaryDisplay: generator.word(), - query: { allOr: false, equal: { field: "value" } }, + query: [{ operator: "equal", field: "field", value: "value" }], sort: { field: "fieldToSort", order: SortOrder.DESCENDING, @@ -190,7 +190,7 @@ describe("/v2/views", () => { const tableId = config.table!._id! await config.api.viewV2.update({ ...view, - query: { equal: { newField: "thatValue" } }, + query: [{ operator: "equal", field: "newField", value: "thatValue" }], }) expect(await config.api.table.get(tableId)).toEqual({ @@ -198,7 +198,9 @@ describe("/v2/views", () => { views: { [view.name]: { ...view, - query: { equal: { newField: "thatValue" } }, + query: [ + { operator: "equal", field: "newField", value: "thatValue" }, + ], schema: expect.anything(), }, }, @@ -216,7 +218,13 @@ describe("/v2/views", () => { tableId, name: view.name, primaryDisplay: generator.word(), - query: { equal: { [generator.word()]: generator.word() } }, + query: [ + { + operator: "equal", + field: generator.word(), + value: generator.word(), + }, + ], sort: { field: generator.word(), order: SortOrder.DESCENDING, @@ -285,7 +293,7 @@ describe("/v2/views", () => { { ...view, tableId: generator.guid(), - query: { equal: { newField: "thatValue" } }, + query: [{ operator: "equal", field: "newField", value: "thatValue" }], }, { expectStatus: 404 } ) diff --git a/packages/shared-core/src/filters.ts b/packages/shared-core/src/filters.ts index 54e0df92df..8739db1b40 100644 --- a/packages/shared-core/src/filters.ts +++ b/packages/shared-core/src/filters.ts @@ -1,4 +1,12 @@ -import { Datasource, FieldType, SortDirection, SortType } from "@budibase/types" +import { + Datasource, + FieldType, + SearchFilter, + SearchQuery, + SearchQueryFields, + SortDirection, + SortType, +} from "@budibase/types" import { OperatorOptions, SqlNumberTypeRangeMap } from "./constants" import { deepGet } from "./helpers" @@ -73,13 +81,13 @@ export const NoEmptyFilterStrings = [ OperatorOptions.NotEquals.value, OperatorOptions.Contains.value, OperatorOptions.NotContains.value, -] as (keyof QueryFields)[] +] as (keyof SearchQueryFields)[] /** * Removes any fields that contain empty strings that would cause inconsistent * behaviour with how backend tables are filtered (no value means no filter). */ -const cleanupQuery = (query: Query) => { +const cleanupQuery = (query: SearchQuery) => { if (!query) { return query } @@ -110,66 +118,12 @@ const removeKeyNumbering = (key: string) => { } } -type Filter = { - operator: keyof Query - field: string - type: any - value: any - externalType: keyof typeof SqlNumberTypeRangeMap -} - -type Query = QueryFields & QueryConfig -type QueryFields = { - string?: { - [key: string]: string - } - fuzzy?: { - [key: string]: string - } - range?: { - [key: string]: { - high: number | string - low: number | string - } - } - equal?: { - [key: string]: any - } - notEqual?: { - [key: string]: any - } - empty?: { - [key: string]: any - } - notEmpty?: { - [key: string]: any - } - oneOf?: { - [key: string]: any[] - } - contains?: { - [key: string]: any[] - } - notContains?: { - [key: string]: any[] - } - containsAny?: { - [key: string]: any[] - } -} - -type QueryConfig = { - allOr?: boolean -} - -type QueryFieldsType = keyof QueryFields - /** * Builds a lucene JSON query from the filter structure generated in the builder * @param filter the builder filter structure */ -export const buildLuceneQuery = (filter: Filter[]) => { - let query: Query = { +export const buildLuceneQuery = (filter: SearchFilter[]) => { + let query: SearchQuery = { string: {}, fuzzy: {}, range: {}, @@ -227,9 +181,13 @@ export const buildLuceneQuery = (filter: Filter[]) => { } if (operator.startsWith("range") && query.range) { const minint = - SqlNumberTypeRangeMap[externalType]?.min || Number.MIN_SAFE_INTEGER + SqlNumberTypeRangeMap[ + externalType as keyof typeof SqlNumberTypeRangeMap + ]?.min || Number.MIN_SAFE_INTEGER const maxint = - SqlNumberTypeRangeMap[externalType]?.max || Number.MAX_SAFE_INTEGER + 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", @@ -275,7 +233,7 @@ export const buildLuceneQuery = (filter: Filter[]) => { * @param docs the data * @param query the JSON lucene query */ -export const runLuceneQuery = (docs: any[], query?: Query) => { +export const runLuceneQuery = (docs: any[], query?: SearchQuery) => { if (!docs || !Array.isArray(docs)) { return [] } @@ -289,7 +247,7 @@ export const runLuceneQuery = (docs: any[], query?: Query) => { // Iterates over a set of filters and evaluates a fail function against a doc const match = ( - type: QueryFieldsType, + type: keyof SearchQueryFields, failFn: (docValue: any, testValue: any) => boolean ) => (doc: any) => { @@ -456,7 +414,7 @@ export const luceneLimit = (docs: any[], limit: string) => { return docs.slice(0, numLimit) } -export const hasFilters = (query?: Query) => { +export const hasFilters = (query?: SearchQuery) => { if (!query) { return false } diff --git a/packages/types/src/api/web/index.ts b/packages/types/src/api/web/index.ts index 0e0527eb7f..cba1e04f9a 100644 --- a/packages/types/src/api/web/index.ts +++ b/packages/types/src/api/web/index.ts @@ -8,3 +8,4 @@ export * from "./system" export * from "./app" export * from "./global" export * from "./pagination" +export * from "./searchFilter" diff --git a/packages/types/src/api/web/searchFilter.ts b/packages/types/src/api/web/searchFilter.ts new file mode 100644 index 0000000000..1b5948e50c --- /dev/null +++ b/packages/types/src/api/web/searchFilter.ts @@ -0,0 +1,51 @@ +import { FieldType } from "../../documents" + +export type SearchFilter = { + operator: keyof SearchQuery + field: string + type?: FieldType + value: any + externalType?: string +} + +export type SearchQuery = { + allOr?: boolean + string?: { + [key: string]: string + } + fuzzy?: { + [key: string]: string + } + range?: { + [key: string]: { + high: number | string + low: number | string + } + } + equal?: { + [key: string]: any + } + notEqual?: { + [key: string]: any + } + empty?: { + [key: string]: any + } + notEmpty?: { + [key: string]: any + } + oneOf?: { + [key: string]: any[] + } + contains?: { + [key: string]: any[] + } + notContains?: { + [key: string]: any[] + } + containsAny?: { + [key: string]: any[] + } +} + +export type SearchQueryFields = Omit diff --git a/packages/types/src/documents/app/view.ts b/packages/types/src/documents/app/view.ts index 3fe8b4a500..204b995337 100644 --- a/packages/types/src/documents/app/view.ts +++ b/packages/types/src/documents/app/view.ts @@ -1,6 +1,5 @@ -import { SortOrder, SortType } from "../../api" -import { SearchFilters } from "../../sdk" -import { TableSchema, UIFieldMetadata } from "./table" +import { SearchFilter, SortOrder, SortType } from "../../api" +import { UIFieldMetadata } from "./table" export interface View { name: string @@ -20,7 +19,7 @@ export interface ViewV2 { name: string primaryDisplay?: string tableId: string - query?: SearchFilters + query?: SearchFilter[] sort?: { field: string order?: SortOrder