From b7979f4719ad029a66258caed81fbccf0b5c6422 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Fri, 18 Oct 2024 10:32:28 +0100 Subject: [PATCH] Refactor SearchFilterGroup type to be less verbose and more clearly named. --- .../src/api/routes/tests/viewV2.spec.ts | 73 +++++++------------ packages/server/src/sdk/app/views/utils.ts | 4 +- packages/shared-core/src/filters.ts | 54 ++++++-------- packages/shared-core/src/utils.ts | 24 +++--- packages/types/src/api/web/searchFilter.ts | 20 ++--- packages/types/src/documents/app/view.ts | 4 +- packages/types/src/sdk/search.ts | 2 +- 7 files changed, 73 insertions(+), 108 deletions(-) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index c2626155c9..fa1f1690f6 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -27,20 +27,18 @@ import { ViewV2Schema, ViewV2Type, JsonTypes, - FilterGroupLogicalOperator, + UILogicalOperator, EmptyFilterOption, JsonFieldSubType, - SearchFilterGroup, + UISearchFilter, LegacyFilter, SearchViewRowRequest, - SearchFilterChild, } from "@budibase/types" import { generator, mocks } from "@budibase/backend-core/tests" import { DatabaseName, getDatasource } from "../../../integrations/tests/utils" import merge from "lodash/merge" import { quotas } from "@budibase/pro" import { db, roles, features } from "@budibase/backend-core" -import { single } from "validate.js" describe.each([ ["lucene", undefined], @@ -158,11 +156,11 @@ describe.each([ tableId: table._id!, primaryDisplay: "id", queryUI: { - logicalOperator: FilterGroupLogicalOperator.ALL, + logicalOperator: UILogicalOperator.ALL, onEmptyFilter: EmptyFilterOption.RETURN_ALL, groups: [ { - logicalOperator: FilterGroupLogicalOperator.ALL, + logicalOperator: UILogicalOperator.ALL, filters: [ { operator: BasicOperator.EQUAL, @@ -2388,10 +2386,10 @@ describe.each([ name: generator.guid(), queryUI: { onEmptyFilter: EmptyFilterOption.RETURN_ALL, - logicalOperator: FilterGroupLogicalOperator.ALL, + logicalOperator: UILogicalOperator.ALL, groups: [ { - logicalOperator: FilterGroupLogicalOperator.ALL, + logicalOperator: UILogicalOperator.ALL, filters: [ { operator: BasicOperator.EQUAL, @@ -2452,10 +2450,10 @@ describe.each([ name: generator.guid(), queryUI: { onEmptyFilter: EmptyFilterOption.RETURN_ALL, - logicalOperator: FilterGroupLogicalOperator.ALL, + logicalOperator: UILogicalOperator.ALL, groups: [ { - logicalOperator: FilterGroupLogicalOperator.ALL, + logicalOperator: UILogicalOperator.ALL, filters: [ { operator: BasicOperator.EQUAL, @@ -2741,10 +2739,10 @@ describe.each([ name: generator.guid(), queryUI: { onEmptyFilter: EmptyFilterOption.RETURN_ALL, - logicalOperator: FilterGroupLogicalOperator.ALL, + logicalOperator: UILogicalOperator.ALL, groups: [ { - logicalOperator: FilterGroupLogicalOperator.ALL, + logicalOperator: UILogicalOperator.ALL, filters: [ { operator: BasicOperator.NOT_EQUAL, @@ -2799,10 +2797,10 @@ describe.each([ name: generator.guid(), queryUI: { onEmptyFilter: EmptyFilterOption.RETURN_ALL, - logicalOperator: FilterGroupLogicalOperator.ALL, + logicalOperator: UILogicalOperator.ALL, groups: [ { - logicalOperator: FilterGroupLogicalOperator.ALL, + logicalOperator: UILogicalOperator.ALL, filters: [ { operator: BasicOperator.NOT_EQUAL, @@ -2894,10 +2892,10 @@ describe.each([ name: generator.guid(), queryUI: { onEmptyFilter: EmptyFilterOption.RETURN_ALL, - logicalOperator: FilterGroupLogicalOperator.ALL, + logicalOperator: UILogicalOperator.ALL, groups: [ { - logicalOperator: FilterGroupLogicalOperator.ALL, + logicalOperator: UILogicalOperator.ALL, filters: [ { operator: BasicOperator.EQUAL, @@ -3338,10 +3336,10 @@ describe.each([ type: ViewV2Type.CALCULATION, queryUI: { onEmptyFilter: EmptyFilterOption.RETURN_ALL, - logicalOperator: FilterGroupLogicalOperator.ALL, + logicalOperator: UILogicalOperator.ALL, groups: [ { - logicalOperator: FilterGroupLogicalOperator.ALL, + logicalOperator: UILogicalOperator.ALL, filters: [ { operator: BasicOperator.EQUAL, @@ -3631,10 +3629,10 @@ describe.each([ name: generator.guid(), queryUI: { onEmptyFilter: EmptyFilterOption.RETURN_ALL, - logicalOperator: FilterGroupLogicalOperator.ALL, + logicalOperator: UILogicalOperator.ALL, groups: [ { - logicalOperator: FilterGroupLogicalOperator.ALL, + logicalOperator: UILogicalOperator.ALL, filters: [ { operator: BasicOperator.EQUAL, @@ -3711,52 +3709,31 @@ describe.each([ interface TestCase { name: string - query: SearchFilterGroup + query: UISearchFilter insert: Row[] expected: Row[] searchOpts?: SearchViewRowRequest } - function defaultQuery( - query: Partial - ): SearchFilterGroup { - return { - onEmptyFilter: EmptyFilterOption.RETURN_ALL, - logicalOperator: FilterGroupLogicalOperator.ALL, - groups: [], - ...query, - } - } - - function defaultGroup( - group: Partial - ): SearchFilterChild { - return { - logicalOperator: FilterGroupLogicalOperator.ALL, - filters: [], - ...group, - } - } - - function simpleQuery(...filters: LegacyFilter[]): SearchFilterGroup { - return defaultQuery({ groups: [defaultGroup({ filters })] }) + function simpleQuery(...filters: LegacyFilter[]): UISearchFilter { + return { groups: [{ filters }] } } const testCases: TestCase[] = [ { name: "empty query return all", insert: [{ string: "foo" }], - query: defaultQuery({ + query: { onEmptyFilter: EmptyFilterOption.RETURN_ALL, - }), + }, expected: [{ string: "foo" }], }, { name: "empty query return none", insert: [{ string: "foo" }], - query: defaultQuery({ + query: { onEmptyFilter: EmptyFilterOption.RETURN_NONE, - }), + }, expected: [], }, { diff --git a/packages/server/src/sdk/app/views/utils.ts b/packages/server/src/sdk/app/views/utils.ts index 1468328921..a110acf072 100644 --- a/packages/server/src/sdk/app/views/utils.ts +++ b/packages/server/src/sdk/app/views/utils.ts @@ -14,8 +14,8 @@ export function ensureQueryUISet(view: ViewV2) { // We're changing it in the change that this comment is part of to also // include SearchFilters objects. These are created when we receive an // update to a ViewV2 that contains a queryUI and not a query field. We - // can convert SearchFilterGroup (the type of queryUI) to SearchFilters, - // but not LegacyFilter[], they are incompatible due to SearchFilterGroup + // can convert UISearchFilter (the type of queryUI) to SearchFilters, + // but not LegacyFilter[], they are incompatible due to UISearchFilter // and SearchFilters being recursive types. // // So despite the type saying that `view.query` is a LegacyFilter[] | diff --git a/packages/shared-core/src/filters.ts b/packages/shared-core/src/filters.ts index 756a814710..932d5a8ca0 100644 --- a/packages/shared-core/src/filters.ts +++ b/packages/shared-core/src/filters.ts @@ -19,8 +19,8 @@ import { RangeOperator, LogicalOperator, isLogicalSearchOperator, - SearchFilterGroup, - FilterGroupLogicalOperator, + UISearchFilter, + UILogicalOperator, isBasicSearchOperator, isArraySearchOperator, isRangeSearchOperator, @@ -561,7 +561,7 @@ export const buildQueryLegacy = ( } /** - * Converts a **SearchFilterGroup** filter definition into a grouped + * Converts a **UISearchFilter** filter definition into a grouped * search query of type **SearchFilters** * * Legacy support remains for the old **SearchFilter[]** format. @@ -573,49 +573,41 @@ export const buildQueryLegacy = ( */ export function buildQuery(filter: undefined): undefined export function buildQuery( - filter: SearchFilterGroup | LegacyFilter[] + filter: UISearchFilter | LegacyFilter[] ): SearchFilters export function buildQuery( - filter?: SearchFilterGroup | LegacyFilter[] + filter?: UISearchFilter | LegacyFilter[] ): SearchFilters | undefined { if (!filter) { return } - let parsedFilter: SearchFilterGroup + let parsedFilter: UISearchFilter if (Array.isArray(filter)) { parsedFilter = processSearchFilters(filter) } else { parsedFilter = filter } - const operatorMap = { - [FilterGroupLogicalOperator.ALL]: LogicalOperator.AND, - [FilterGroupLogicalOperator.ANY]: LogicalOperator.OR, + const operator = logicalOperatorFromUI( + parsedFilter.logicalOperator || UILogicalOperator.ALL + ) + const groups = parsedFilter.groups || [] + const conditions: SearchFilters[] = groups.map(group => { + const filters = group.filters || [] + return { [operator]: { conditions: filters.map(x => buildCondition(x)) } } + }) + + return { + onEmptyFilter: parsedFilter.onEmptyFilter, + [operator]: { conditions }, } +} - const globalOnEmpty = parsedFilter.onEmptyFilter - ? parsedFilter.onEmptyFilter - : null - - const globalOperator = operatorMap[parsedFilter.logicalOperator] - - const ret = { - ...(globalOnEmpty ? { onEmptyFilter: globalOnEmpty } : {}), - [globalOperator]: { - conditions: parsedFilter.groups?.map(group => { - return { - [operatorMap[group.logicalOperator]]: { - conditions: group.filters - ?.map(x => buildCondition(x)) - .filter(filter => filter), - }, - } - }), - }, - } - - return ret +function logicalOperatorFromUI(operator: UILogicalOperator): LogicalOperator { + return operator === UILogicalOperator.ALL + ? LogicalOperator.AND + : LogicalOperator.OR } // The frontend can send single values for array fields sometimes, so to handle diff --git a/packages/shared-core/src/utils.ts b/packages/shared-core/src/utils.ts index 98f7c9b215..4f3c4f3deb 100644 --- a/packages/shared-core/src/utils.ts +++ b/packages/shared-core/src/utils.ts @@ -1,13 +1,13 @@ import { LegacyFilter, - SearchFilterGroup, - FilterGroupLogicalOperator, + UISearchFilter, + UILogicalOperator, SearchFilters, BasicOperator, ArrayOperator, isLogicalSearchOperator, EmptyFilterOption, - SearchFilterChild, + SearchFilterGroup, } from "@budibase/types" import * as Constants from "./constants" import { removeKeyNumbering } from "./filters" @@ -132,18 +132,18 @@ export function isSupportedUserSearch(query: SearchFilters) { /** * Processes the filter config. Filters are migrated from - * SearchFilter[] to SearchFilterGroup + * SearchFilter[] to UISearchFilter * * If filters is not an array, the migration is skipped * - * @param {LegacyFilter[] | SearchFilterGroup} filters + * @param {LegacyFilter[] | UISearchFilter} filters */ export const processSearchFilters = ( filters: LegacyFilter[] -): SearchFilterGroup => { +): UISearchFilter => { // Base search config. - const defaultCfg: SearchFilterGroup = { - logicalOperator: FilterGroupLogicalOperator.ALL, + const defaultCfg: UISearchFilter = { + logicalOperator: UILogicalOperator.ALL, onEmptyFilter: EmptyFilterOption.RETURN_ALL, groups: [], } @@ -159,11 +159,11 @@ export const processSearchFilters = ( "formulaType", ] - let baseGroup: SearchFilterChild = { - logicalOperator: FilterGroupLogicalOperator.ALL, + let baseGroup: SearchFilterGroup = { + logicalOperator: UILogicalOperator.ALL, } - return filters.reduce((acc: SearchFilterGroup, filter: LegacyFilter) => { + return filters.reduce((acc: UISearchFilter, filter: LegacyFilter) => { // Sort the properties for easier debugging const filterPropertyKeys = (Object.keys(filter) as (keyof LegacyFilter)[]) .sort((a, b) => { @@ -180,7 +180,7 @@ export const processSearchFilters = ( acc.onEmptyFilter = value } else if (key === "operator" && value === "allOr") { // Group 1 logical operator - baseGroup.logicalOperator = FilterGroupLogicalOperator.ANY + baseGroup.logicalOperator = UILogicalOperator.ANY } return acc diff --git a/packages/types/src/api/web/searchFilter.ts b/packages/types/src/api/web/searchFilter.ts index 305f2bab00..ef35217f9d 100644 --- a/packages/types/src/api/web/searchFilter.ts +++ b/packages/types/src/api/web/searchFilter.ts @@ -1,9 +1,5 @@ import { FieldType } from "../../documents" -import { - EmptyFilterOption, - FilterGroupLogicalOperator, - SearchFilters, -} from "../../sdk" +import { EmptyFilterOption, UILogicalOperator, SearchFilters } from "../../sdk" export type LegacyFilter = { operator: keyof SearchFilters | "rangeLow" | "rangeHigh" @@ -14,15 +10,15 @@ export type LegacyFilter = { externalType?: string } -export type SearchFilterChild = { - logicalOperator: FilterGroupLogicalOperator - groups?: SearchFilterChild[] +export type SearchFilterGroup = { + logicalOperator?: UILogicalOperator + groups?: SearchFilterGroup[] filters?: LegacyFilter[] } // this is a type purely used by the UI -export type SearchFilterGroup = { - logicalOperator: FilterGroupLogicalOperator - onEmptyFilter: EmptyFilterOption - groups: SearchFilterChild[] +export type UISearchFilter = { + logicalOperator?: UILogicalOperator + onEmptyFilter?: EmptyFilterOption + groups?: SearchFilterGroup[] } diff --git a/packages/types/src/documents/app/view.ts b/packages/types/src/documents/app/view.ts index 1b2372da85..3ae120fcca 100644 --- a/packages/types/src/documents/app/view.ts +++ b/packages/types/src/documents/app/view.ts @@ -1,4 +1,4 @@ -import { LegacyFilter, SearchFilterGroup, SortOrder, SortType } from "../../api" +import { LegacyFilter, UISearchFilter, SortOrder, SortType } from "../../api" import { UIFieldMetadata } from "./table" import { Document } from "../document" import { DBView, SearchFilters } from "../../sdk" @@ -92,7 +92,7 @@ export interface ViewV2 { tableId: string query?: LegacyFilter[] | SearchFilters // duplicate to store UI information about filters - queryUI?: SearchFilterGroup + queryUI?: UISearchFilter sort?: { field: string order?: SortOrder diff --git a/packages/types/src/sdk/search.ts b/packages/types/src/sdk/search.ts index d64e87d434..a2d4b4760f 100644 --- a/packages/types/src/sdk/search.ts +++ b/packages/types/src/sdk/search.ts @@ -203,7 +203,7 @@ export enum EmptyFilterOption { RETURN_NONE = "none", } -export enum FilterGroupLogicalOperator { +export enum UILogicalOperator { ALL = "all", ANY = "any", }