Refactor SearchFilterGroup type to be less verbose and more clearly named.

This commit is contained in:
Sam Rose 2024-10-18 10:32:28 +01:00
parent 714029b9a0
commit b7979f4719
No known key found for this signature in database
7 changed files with 73 additions and 108 deletions

View File

@ -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>
): SearchFilterGroup {
return {
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
logicalOperator: FilterGroupLogicalOperator.ALL,
groups: [],
...query,
}
}
function defaultGroup(
group: Partial<SearchFilterChild>
): 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: [],
},
{

View File

@ -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[] |

View File

@ -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)) } }
})
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),
},
}
}),
},
onEmptyFilter: parsedFilter.onEmptyFilter,
[operator]: { conditions },
}
}
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

View File

@ -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

View File

@ -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[]
}

View File

@ -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

View File

@ -203,7 +203,7 @@ export enum EmptyFilterOption {
RETURN_NONE = "none",
}
export enum FilterGroupLogicalOperator {
export enum UILogicalOperator {
ALL = "all",
ANY = "any",
}