From 7aad0d6e92e835aff0418ca45462448105c900f0 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 30 Oct 2024 16:45:21 +0100 Subject: [PATCH 1/3] Type branch filters --- packages/server/src/threads/automation.ts | 26 ++++++++++++++++--- packages/shared-core/src/filters.ts | 19 -------------- .../app/automation/StepInputsOutputs.ts | 17 ++++++++++-- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/packages/server/src/threads/automation.ts b/packages/server/src/threads/automation.ts index 37a72ba334..e83b4efa35 100644 --- a/packages/server/src/threads/automation.ts +++ b/packages/server/src/threads/automation.ts @@ -23,9 +23,10 @@ import { AutomationStatus, AutomationStep, AutomationStepStatus, + BranchSearchFilters, BranchStep, + LogicalOperator, LoopStep, - SearchFilters, UserBindings, } from "@budibase/types" import { AutomationContext, TriggerOutput } from "../definitions/automations" @@ -546,12 +547,31 @@ class Orchestrator { ) } + private recurseSearchFilters( + filters: BranchSearchFilters, + processFn: (filter: BranchSearchFilters) => BranchSearchFilters + ): BranchSearchFilters { + // Process the current level + filters = processFn(filters) + + // Recurse through logical operators + for (const logical of Object.values(LogicalOperator)) { + if (filters[logical]) { + filters[logical]!.conditions = filters[logical]!.conditions.map( + condition => this.recurseSearchFilters(condition, processFn) + ) + } + } + + return filters + } + private async evaluateBranchCondition( - conditions: SearchFilters + conditions: BranchSearchFilters ): Promise { const toFilter: Record = {} - const processedConditions = dataFilters.recurseSearchFilters( + const processedConditions = this.recurseSearchFilters( conditions, filter => { Object.entries(filter).forEach(([_, value]) => { diff --git a/packages/shared-core/src/filters.ts b/packages/shared-core/src/filters.ts index 7720fe2b8d..15c30800a1 100644 --- a/packages/shared-core/src/filters.ts +++ b/packages/shared-core/src/filters.ts @@ -142,25 +142,6 @@ export function recurseLogicalOperators( return filters } -export function recurseSearchFilters( - filters: SearchFilters, - processFn: (filter: SearchFilters) => SearchFilters -): SearchFilters { - // Process the current level - filters = processFn(filters) - - // Recurse through logical operators - for (const logical of LOGICAL_OPERATORS) { - if (filters[logical]) { - filters[logical]!.conditions = filters[logical]!.conditions.map( - condition => recurseSearchFilters(condition, processFn) - ) - } - } - - return filters -} - /** * Removes any fields that contain empty strings that would cause inconsistent * behaviour with how backend tables are filtered (no value means no filter). diff --git a/packages/types/src/documents/app/automation/StepInputsOutputs.ts b/packages/types/src/documents/app/automation/StepInputsOutputs.ts index 1b734c589c..3aadb77108 100644 --- a/packages/types/src/documents/app/automation/StepInputsOutputs.ts +++ b/packages/types/src/documents/app/automation/StepInputsOutputs.ts @@ -1,5 +1,10 @@ import { SortOrder } from "../../../api" -import { SearchFilters, EmptyFilterOption } from "../../../sdk" +import { + SearchFilters, + EmptyFilterOption, + BasicOperator, + LogicalOperator, +} from "../../../sdk" import { HttpMethod } from "../query" import { Row } from "../row" import { LoopStepType, EmailAttachment, AutomationResults } from "./automation" @@ -118,9 +123,17 @@ export type BranchStepInputs = { export type Branch = { id: any name: string - condition: SearchFilters + condition: BranchSearchFilters } +export type BranchSearchFilters = Pick< + SearchFilters, + | BasicOperator.EQUAL + | BasicOperator.NOT_EQUAL + | LogicalOperator.AND + | LogicalOperator.OR +> + export type MakeIntegrationInputs = { url: string body: any From b510d04129cc7970c0c5fa0a84179651abd67ba8 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 30 Oct 2024 16:59:40 +0100 Subject: [PATCH 2/3] Simplify --- packages/server/src/threads/automation.ts | 59 +++++++++++------------ 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/packages/server/src/threads/automation.ts b/packages/server/src/threads/automation.ts index e83b4efa35..2071115472 100644 --- a/packages/server/src/threads/automation.ts +++ b/packages/server/src/threads/automation.ts @@ -25,7 +25,7 @@ import { AutomationStepStatus, BranchSearchFilters, BranchStep, - LogicalOperator, + isLogicalSearchOperator, LoopStep, UserBindings, } from "@budibase/types" @@ -547,54 +547,49 @@ class Orchestrator { ) } - private recurseSearchFilters( - filters: BranchSearchFilters, - processFn: (filter: BranchSearchFilters) => BranchSearchFilters - ): BranchSearchFilters { - // Process the current level - filters = processFn(filters) - - // Recurse through logical operators - for (const logical of Object.values(LogicalOperator)) { - if (filters[logical]) { - filters[logical]!.conditions = filters[logical]!.conditions.map( - condition => this.recurseSearchFilters(condition, processFn) - ) - } - } - - return filters - } - private async evaluateBranchCondition( conditions: BranchSearchFilters ): Promise { const toFilter: Record = {} - const processedConditions = this.recurseSearchFilters( - conditions, - filter => { - Object.entries(filter).forEach(([_, value]) => { - Object.entries(value).forEach(([field, val]) => { + const recurseSearchFilters = ( + filters: BranchSearchFilters + ): BranchSearchFilters => { + for (const filterKey of Object.keys( + filters + ) as (keyof typeof filters)[]) { + if (!filters[filterKey]) { + continue + } + + if (isLogicalSearchOperator(filterKey)) { + filters[filterKey].conditions = filters[filterKey].conditions.map( + condition => recurseSearchFilters(condition) + ) + } else { + for (const [field, value] of Object.entries(filters[filterKey])) { const fromContext = processStringSync( field, this.processContext(this.context) ) toFilter[field] = fromContext - if (typeof val === "string" && findHBSBlocks(val).length > 0) { + if (typeof value === "string" && findHBSBlocks(value).length > 0) { const processedVal = processStringSync( - val, + value, this.processContext(this.context) ) - value[field] = processedVal + filters[filterKey][field] = processedVal } - }) - }) - return filter + } + } } - ) + + return filters + } + + const processedConditions = recurseSearchFilters(conditions) const result = dataFilters.runQuery([toFilter], processedConditions) return result.length > 0 From 3779c7f6c7657a3068391632fcdc0a0ed0884cfd Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 30 Oct 2024 17:13:03 +0100 Subject: [PATCH 3/3] Typing --- packages/server/src/threads/automation.ts | 8 ++++++-- packages/shared-core/src/utils.ts | 11 +++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/server/src/threads/automation.ts b/packages/server/src/threads/automation.ts index 2071115472..0e87b39f6a 100644 --- a/packages/server/src/threads/automation.ts +++ b/packages/server/src/threads/automation.ts @@ -8,7 +8,7 @@ import { import * as actions from "../automations/actions" import * as automationUtils from "../automations/automationUtils" import { replaceFakeBindings } from "../automations/loopUtils" -import { dataFilters, helpers } from "@budibase/shared-core" +import { dataFilters, helpers, utils } from "@budibase/shared-core" import { default as AutomationEmitter } from "../events/AutomationEmitter" import { generateAutomationMetadataID, isProdAppID } from "../db/utils" import { definitions as triggerDefs } from "../automations/triggerInfo" @@ -28,6 +28,7 @@ import { isLogicalSearchOperator, LoopStep, UserBindings, + isBasicSearchOperator, } from "@budibase/types" import { AutomationContext, TriggerOutput } from "../definitions/automations" import { WorkerCallback } from "./definitions" @@ -566,7 +567,7 @@ class Orchestrator { filters[filterKey].conditions = filters[filterKey].conditions.map( condition => recurseSearchFilters(condition) ) - } else { + } else if (isBasicSearchOperator(filterKey)) { for (const [field, value] of Object.entries(filters[filterKey])) { const fromContext = processStringSync( field, @@ -583,6 +584,9 @@ class Orchestrator { filters[filterKey][field] = processedVal } } + } else { + // We want to types to complain if we extend BranchSearchFilters, but not to throw if the request comes with some extra data. It will just be ignored + utils.unreachable(filterKey, { doNotThrow: true }) } } diff --git a/packages/shared-core/src/utils.ts b/packages/shared-core/src/utils.ts index 0e49db9c7c..e2c40a8849 100644 --- a/packages/shared-core/src/utils.ts +++ b/packages/shared-core/src/utils.ts @@ -26,9 +26,16 @@ const FILTER_ALLOWED_KEYS: (keyof SearchFilter)[] = [ export function unreachable( value: never, - message = `No such case in exhaustive switch: ${value}` + opts?: { + message?: string + doNotThrow?: boolean + } ) { - throw new Error(message) + const message = opts?.message || `No such case in exhaustive switch: ${value}` + const doNotThrow = !!opts?.doNotThrow + if (!doNotThrow) { + throw new Error(message) + } } export async function parallelForeach(