diff --git a/packages/shared-core/src/filters.ts b/packages/shared-core/src/filters.ts index f7c7aa0922..34e107562a 100644 --- a/packages/shared-core/src/filters.ts +++ b/packages/shared-core/src/filters.ts @@ -6,6 +6,7 @@ import { SearchFilter, SearchFilters, SearchQueryFields, + ArrayOperator, SearchFilterOperator, SortType, FieldConstraints, @@ -14,11 +15,13 @@ import { EmptyFilterOption, SearchResponse, Table, + BasicOperator, + RangeOperator, } from "@budibase/types" import dayjs from "dayjs" import { OperatorOptions, SqlNumberTypeRangeMap } from "./constants" import { deepGet, schema } from "./helpers" -import { isPlainObject, isEmpty, isArray } from "lodash" +import { isPlainObject, isEmpty } from "lodash" const HBS_REGEX = /{{([^{].*?)}}/g @@ -327,13 +330,7 @@ export const buildQuery = (filter: SearchFilter[]) => { // this we convert them to arrays at the controller level so that nothing below // this has to worry about the non-array values. export function fixupFilterArrays(filters: SearchFilters) { - const arrayFields = [ - SearchFilterOperator.ONE_OF, - SearchFilterOperator.CONTAINS, - SearchFilterOperator.NOT_CONTAINS, - SearchFilterOperator.CONTAINS_ANY, - ] - for (const searchField of arrayFields) { + for (const searchField of Object.values(ArrayOperator)) { const field = filters[searchField] if (field == null || !isPlainObject(field)) { continue @@ -341,10 +338,12 @@ export function fixupFilterArrays(filters: SearchFilters) { for (const key of Object.keys(field)) { if (!Array.isArray(field[key])) { - if (typeof field[key] !== "string") { - field[key] = [field[key]] + if (typeof field[key] === "string") { + field[key] = (field[key] as string) + .split(",") + .map((x: string) => x.trim()) } else { - field[key] = field[key].split(",").map((x: string) => x.trim()) + field[key] = [field[key]] } } } @@ -412,7 +411,7 @@ export const runQuery = (docs: Record[], query: SearchFilters) => { } const stringMatch = match( - SearchFilterOperator.STRING, + BasicOperator.STRING, (docValue: any, testValue: any) => { if (!(typeof docValue === "string")) { return false @@ -425,7 +424,7 @@ export const runQuery = (docs: Record[], query: SearchFilters) => { ) const fuzzyMatch = match( - SearchFilterOperator.FUZZY, + BasicOperator.FUZZY, (docValue: any, testValue: any) => { if (!(typeof docValue === "string")) { return false @@ -438,7 +437,7 @@ export const runQuery = (docs: Record[], query: SearchFilters) => { ) const rangeMatch = match( - SearchFilterOperator.RANGE, + RangeOperator.RANGE, (docValue: any, testValue: any) => { if (docValue == null || docValue === "") { return false @@ -527,11 +526,8 @@ export const runQuery = (docs: Record[], query: SearchFilters) => { (...args: T): boolean => !f(...args) - const equalMatch = match(SearchFilterOperator.EQUAL, _valueMatches) - const notEqualMatch = match( - SearchFilterOperator.NOT_EQUAL, - not(_valueMatches) - ) + const equalMatch = match(BasicOperator.EQUAL, _valueMatches) + const notEqualMatch = match(BasicOperator.NOT_EQUAL, not(_valueMatches)) const _empty = (docValue: any) => { if (typeof docValue === "string") { @@ -546,27 +542,24 @@ export const runQuery = (docs: Record[], query: SearchFilters) => { return docValue == null } - const emptyMatch = match(SearchFilterOperator.EMPTY, _empty) - const notEmptyMatch = match(SearchFilterOperator.NOT_EMPTY, not(_empty)) + const emptyMatch = match(BasicOperator.EMPTY, _empty) + const notEmptyMatch = match(BasicOperator.NOT_EMPTY, not(_empty)) - const oneOf = match( - SearchFilterOperator.ONE_OF, - (docValue: any, testValue: any) => { - if (typeof testValue === "string") { - testValue = testValue.split(",") - } - - if (typeof docValue === "number") { - testValue = testValue.map((item: string) => parseFloat(item)) - } - - if (!Array.isArray(testValue)) { - return false - } - - return testValue.some(item => _valueMatches(docValue, item)) + const oneOf = match(ArrayOperator.ONE_OF, (docValue: any, testValue: any) => { + if (typeof testValue === "string") { + testValue = testValue.split(",") } - ) + + if (typeof docValue === "number") { + testValue = testValue.map((item: string) => parseFloat(item)) + } + + if (!Array.isArray(testValue)) { + return false + } + + return testValue.some(item => _valueMatches(docValue, item)) + }) const _contains = (f: "some" | "every") => (docValue: any, testValue: any) => { @@ -593,7 +586,7 @@ export const runQuery = (docs: Record[], query: SearchFilters) => { } const contains = match( - SearchFilterOperator.CONTAINS, + ArrayOperator.CONTAINS, (docValue: any, testValue: any) => { if (Array.isArray(testValue) && testValue.length === 0) { return true @@ -602,7 +595,7 @@ export const runQuery = (docs: Record[], query: SearchFilters) => { } ) const notContains = match( - SearchFilterOperator.NOT_CONTAINS, + ArrayOperator.NOT_CONTAINS, (docValue: any, testValue: any) => { // Not sure if this is logically correct, but at the time this code was // written the search endpoint behaved this way and we wanted to make this @@ -613,10 +606,7 @@ export const runQuery = (docs: Record[], query: SearchFilters) => { return not(_contains("every"))(docValue, testValue) } ) - const containsAny = match( - SearchFilterOperator.CONTAINS_ANY, - _contains("some") - ) + const containsAny = match(ArrayOperator.CONTAINS_ANY, _contains("some")) const docMatch = (doc: Record) => { const filterFunctions = { diff --git a/packages/types/src/sdk/search.ts b/packages/types/src/sdk/search.ts index ccb73a7fba..5607efece8 100644 --- a/packages/types/src/sdk/search.ts +++ b/packages/types/src/sdk/search.ts @@ -3,20 +3,28 @@ import { Row, Table, DocumentType } from "../documents" import { SortOrder, SortType } from "../api" import { Knex } from "knex" -export enum SearchFilterOperator { - STRING = "string", - FUZZY = "fuzzy", - RANGE = "range", +export enum BasicOperator { EQUAL = "equal", NOT_EQUAL = "notEqual", EMPTY = "empty", NOT_EMPTY = "notEmpty", - ONE_OF = "oneOf", + FUZZY = "fuzzy", + STRING = "string", +} + +export enum ArrayOperator { CONTAINS = "contains", NOT_CONTAINS = "notContains", CONTAINS_ANY = "containsAny", + ONE_OF = "oneOf", } +export enum RangeOperator { + RANGE = "range", +} + +export type SearchFilterOperator = BasicOperator | ArrayOperator | RangeOperator + export enum InternalSearchFilterOperator { COMPLEX_ID_OPERATOR = "_complexIdOperator", } @@ -52,17 +60,17 @@ export interface SearchFilters { // allows just fuzzy to be or - all the fuzzy/like parameters fuzzyOr?: boolean onEmptyFilter?: EmptyFilterOption - [SearchFilterOperator.STRING]?: BasicFilter - [SearchFilterOperator.FUZZY]?: BasicFilter - [SearchFilterOperator.RANGE]?: RangeFilter - [SearchFilterOperator.EQUAL]?: BasicFilter - [SearchFilterOperator.NOT_EQUAL]?: BasicFilter - [SearchFilterOperator.EMPTY]?: BasicFilter - [SearchFilterOperator.NOT_EMPTY]?: BasicFilter - [SearchFilterOperator.ONE_OF]?: ArrayFilter - [SearchFilterOperator.CONTAINS]?: ArrayFilter - [SearchFilterOperator.NOT_CONTAINS]?: ArrayFilter - [SearchFilterOperator.CONTAINS_ANY]?: ArrayFilter + [BasicOperator.STRING]?: BasicFilter + [BasicOperator.FUZZY]?: BasicFilter + [RangeOperator.RANGE]?: RangeFilter + [BasicOperator.EQUAL]?: BasicFilter + [BasicOperator.NOT_EQUAL]?: BasicFilter + [BasicOperator.EMPTY]?: BasicFilter + [BasicOperator.NOT_EMPTY]?: BasicFilter + [ArrayOperator.ONE_OF]?: ArrayFilter + [ArrayOperator.CONTAINS]?: ArrayFilter + [ArrayOperator.NOT_CONTAINS]?: ArrayFilter + [ArrayOperator.CONTAINS_ANY]?: ArrayFilter // specific to SQS/SQLite search on internal tables this can be used // to make sure the documents returned are always filtered down to a // specific document type (such as just rows)