From f88d5a5606814c3fa338b27e2c5d3f035b343c82 Mon Sep 17 00:00:00 2001 From: Pascal <128643171+Pascal-Delange@users.noreply.github.com> Date: Thu, 16 Jan 2025 09:45:27 +0100 Subject: [PATCH] refactor: shuffle functions (#643) * refactor: shuffle functions * refactor continued * rename files * remove unused function (duplicated elsewhere) * some more renaming * remove get of operators * fix imports * update openapi * fix bug introduced in the PR * file rename * reintegrate changes from case name templating #647 * fix imports --- .../src/components/Cases/CaseDecisions.tsx | 14 +- .../src/components/Decisions/RulesDetail.tsx | 12 +- .../RulesExecutions/RuleExecutionDetail.tsx | 150 ---- .../Scenario/AstBuilder/AstBuilder.tsx | 6 +- .../AstBuilderNode/AstBuilderNode.tsx | 4 +- .../AstBuilder/AstBuilderNode/MainAstLine.tsx | 11 +- .../AstBuilderNode/Operand/Operand.tsx | 4 +- .../AggregationEdit/AggregationEdit.tsx | 8 +- .../AggregationEdit/EditFilters.tsx | 11 +- .../FuzzyMatchComparatorEdit.hook.tsx | 6 +- .../FuzzyMatchComparatorEdit.tsx | 4 +- .../IsMultipleOfEdit/IsMultipleOfEdit.hook.ts | 3 +- .../IsMultipleOfEdit/IsMultipleOfEdit.tsx | 6 +- .../OperandEditModal/OperandEditModal.tsx | 8 +- .../StringTemplateEdit.hook.ts | 7 +- .../StringTemplateEdit/StringTemplateEdit.tsx | 4 +- .../TimeAddEdit/TimeAddEdit.tsx | 8 +- .../TimeAddEdit/TimestampField.tsx | 4 +- .../TimestampExtract/TimestampExtract.tsx | 8 +- .../TimestampExtract/TimestampField.tsx | 4 +- .../Operand/OperandEditor/OperandEditor.tsx | 2 +- .../OperandEditor/OperandEditorProvider.tsx | 14 +- .../AstBuilderNode/Operand/OperandInfos.tsx | 22 +- .../AstBuilder/AstBuilderNode/Operator.tsx | 12 +- .../AstBuilder/RootAstBuilderNode/RootAnd.tsx | 7 +- .../RootAstBuilderNode/RootAstBuilderNode.tsx | 5 +- .../RootAstBuilderNode/RootOrWithAnd.tsx | 6 +- .../DetailPanel/CaseNameEditor.hook.ts | 6 +- .../Workflow/DetailPanel/CaseNameEditor.tsx | 4 +- .../Scenario/Workflow/models/nodes.ts | 2 +- packages/app-builder/src/models/ast-node.ts | 706 ------------------ .../src/models/astNode/aggregation.ts | 57 ++ .../src/models/astNode/ast-node.ts | 85 +++ .../astNode/builder-ast-node-node-operator.ts | 66 ++ .../src/models/astNode/builder-ast-node.ts | 192 +++++ .../src/models/astNode/constant.ts | 25 + .../src/models/astNode/custom-list.ts | 31 + .../src/models/astNode/data-accessor.ts | 46 ++ .../src/models/astNode/multiple-of.ts | 32 + .../app-builder/src/models/astNode/strings.ts | 169 +++++ .../app-builder/src/models/astNode/time.ts | 129 ++++ .../src/models/editable-operators.ts | 244 ------ .../src/models/get-operator-name.ts | 87 +++ packages/app-builder/src/models/index.ts | 2 +- .../src/models/modale-operators.ts | 54 ++ .../app-builder/src/models/node-evaluation.ts | 2 +- .../src/models/operator-options.ts | 36 + .../src/models/scenario-iteration-rule.ts | 2 +- .../src/models/scenario-iteration.ts | 2 +- packages/app-builder/src/models/scenario.ts | 5 +- .../src/repositories/EditorRepository.ts | 22 +- .../_builder+/cases+/$caseId._layout.tsx | 4 - .../_builder+/decisions+/$decisionId.tsx | 20 +- .../i+/$iterationId+/_edit-view+/trigger.tsx | 16 +- .../i+/$iterationId+/rules.$ruleId.tsx | 23 +- .../scenarios+/$scenarioId+/workflow.tsx | 6 +- .../services/ast-node/getAstNodeDataType.ts | 14 +- .../ast-node/getAstNodeDisplayName.ts | 31 +- .../ast-node/getAstNodeOperandType.ts | 20 +- .../ast-node/getCustomListAccessCustomList.ts | 2 +- .../ast-node/getDataAccessorAstNodeField.ts | 8 +- .../src/services/editor/ast-editor.tsx | 6 +- .../editor/coerceToConstantAstNode.spec.ts | 3 +- .../editor/coerceToConstantAstNode.ts | 5 +- .../editor/getEnumOptionsFromNeighbour.ts | 2 +- .../src/services/editor/options.tsx | 73 +- .../validation/ast-node-validation.ts | 3 +- .../marble-api/openapis/marblecore-api.yaml | 34 - .../src/generated/marblecore-api.ts | 22 - 69 files changed, 1215 insertions(+), 1433 deletions(-) delete mode 100644 packages/app-builder/src/components/Decisions/RulesExecutions/RuleExecutionDetail.tsx delete mode 100644 packages/app-builder/src/models/ast-node.ts create mode 100644 packages/app-builder/src/models/astNode/aggregation.ts create mode 100644 packages/app-builder/src/models/astNode/ast-node.ts create mode 100644 packages/app-builder/src/models/astNode/builder-ast-node-node-operator.ts create mode 100644 packages/app-builder/src/models/astNode/builder-ast-node.ts create mode 100644 packages/app-builder/src/models/astNode/constant.ts create mode 100644 packages/app-builder/src/models/astNode/custom-list.ts create mode 100644 packages/app-builder/src/models/astNode/data-accessor.ts create mode 100644 packages/app-builder/src/models/astNode/multiple-of.ts create mode 100644 packages/app-builder/src/models/astNode/strings.ts create mode 100644 packages/app-builder/src/models/astNode/time.ts delete mode 100644 packages/app-builder/src/models/editable-operators.ts create mode 100644 packages/app-builder/src/models/get-operator-name.ts create mode 100644 packages/app-builder/src/models/modale-operators.ts create mode 100644 packages/app-builder/src/models/operator-options.ts diff --git a/packages/app-builder/src/components/Cases/CaseDecisions.tsx b/packages/app-builder/src/components/Cases/CaseDecisions.tsx index f2d3cdc4b..396879f26 100644 --- a/packages/app-builder/src/components/Cases/CaseDecisions.tsx +++ b/packages/app-builder/src/components/Cases/CaseDecisions.tsx @@ -1,16 +1,14 @@ +import { type Pivot, type TableModel } from '@app-builder/models'; import { type DatabaseAccessAstNode, type PayloadAstNode, - type Pivot, - type TableModel, -} from '@app-builder/models'; +} from '@app-builder/models/astNode/data-accessor'; import { type CustomList } from '@app-builder/models/custom-list'; import { type Decision, type DecisionDetail, type RuleExecution, } from '@app-builder/models/decision'; -import { type OperatorFunction } from '@app-builder/models/editable-operators'; import { type LicenseEntitlements } from '@app-builder/models/license'; import { type RuleSnoozeWithRuleId } from '@app-builder/models/rule-snooze'; import { type ScenarioIterationRule } from '@app-builder/models/scenario-iteration-rule'; @@ -49,7 +47,7 @@ import { CasePivotValues } from './CasePivotValues'; import { casesI18n } from './cases-i18n'; import { RuleSnoozes } from './RuleSnoozes'; -interface DecisionsDetail { +interface DecisionsDetailWithContext { decisionId: string; ruleExecutions: RuleExecution[]; triggerObjectType: string; @@ -59,7 +57,6 @@ interface DecisionsDetail { databaseAccessors: DatabaseAccessAstNode[]; payloadAccessors: PayloadAstNode[]; }; - operators: OperatorFunction[]; ruleSnoozes: RuleSnoozeWithRuleId[]; } @@ -76,7 +73,7 @@ export function CaseDecisions({ }; entitlements: LicenseEntitlements; caseDecisionsPromise: Promise< - [TableModel[], CustomList[], DecisionsDetail[]] + [TableModel[], CustomList[], DecisionsDetailWithContext[]] >; }) { const { t } = useTranslation(casesI18n); @@ -273,7 +270,7 @@ function DecisionDetail({ featureAccess, }: { decision: Decision; - decisionsDetail: DecisionsDetail[]; + decisionsDetail: DecisionsDetailWithContext[]; dataModel: TableModel[]; customLists: CustomList[]; entitlements: LicenseEntitlements; @@ -358,7 +355,6 @@ function DecisionDetail({ decisionDetail.accessors.databaseAccessors, payloadAccessors: decisionDetail.accessors.payloadAccessors, - operators: decisionDetail.operators, rules: decisionDetail.rules, }} /> diff --git a/packages/app-builder/src/components/Decisions/RulesDetail.tsx b/packages/app-builder/src/components/Decisions/RulesDetail.tsx index e3a2bc434..3e96cc6a2 100644 --- a/packages/app-builder/src/components/Decisions/RulesDetail.tsx +++ b/packages/app-builder/src/components/Decisions/RulesDetail.tsx @@ -1,13 +1,11 @@ import { decisionsI18n, Paper } from '@app-builder/components'; +import { type AstNode, type DataModel } from '@app-builder/models'; import { - type AstNode, type DatabaseAccessAstNode, - type DataModel, type PayloadAstNode, -} from '@app-builder/models'; +} from '@app-builder/models/astNode/data-accessor'; import { type CustomList } from '@app-builder/models/custom-list'; import { type RuleExecution } from '@app-builder/models/decision'; -import { type OperatorFunction } from '@app-builder/models/editable-operators'; import { type NodeEvaluation } from '@app-builder/models/node-evaluation'; import { type ScenarioIterationRule } from '@app-builder/models/scenario-iteration-rule'; import { useAstNodeEditor } from '@app-builder/services/editor/ast-editor'; @@ -41,7 +39,6 @@ export function RulesDetail({ rules: ScenarioIterationRule[]; databaseAccessors: DatabaseAccessAstNode[]; payloadAccessors: PayloadAstNode[]; - operators: OperatorFunction[]; dataModel: DataModel; customLists: CustomList[]; }>; @@ -94,7 +91,6 @@ export function RuleExecutionDetail({ rules: ScenarioIterationRule[]; databaseAccessors: DatabaseAccessAstNode[]; payloadAccessors: PayloadAstNode[]; - operators: OperatorFunction[]; dataModel: DataModel; customLists: CustomList[]; }; @@ -140,7 +136,6 @@ export function RuleExecutionDetail({ evaluation={ruleExecution.evaluation} databaseAccessors={astRuleData.databaseAccessors} payloadAccessors={astRuleData.payloadAccessors} - operators={astRuleData.operators} dataModel={astRuleData.dataModel} customLists={astRuleData.customLists} triggerObjectType={triggerObjectType} @@ -175,7 +170,6 @@ function RuleFormula({ databaseAccessors, evaluation, payloadAccessors, - operators, dataModel, customLists, triggerObjectType, @@ -184,7 +178,6 @@ function RuleFormula({ evaluation?: NodeEvaluation; databaseAccessors: DatabaseAccessAstNode[]; payloadAccessors: PayloadAstNode[]; - operators: OperatorFunction[]; dataModel: DataModel; customLists: CustomList[]; triggerObjectType: string; @@ -199,7 +192,6 @@ function RuleFormula({ options={{ databaseAccessors, payloadAccessors, - operators, dataModel, customLists, triggerObjectType, diff --git a/packages/app-builder/src/components/Decisions/RulesExecutions/RuleExecutionDetail.tsx b/packages/app-builder/src/components/Decisions/RulesExecutions/RuleExecutionDetail.tsx deleted file mode 100644 index 7f2a6707a..000000000 --- a/packages/app-builder/src/components/Decisions/RulesExecutions/RuleExecutionDetail.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { decisionsI18n, Paper } from '@app-builder/components'; -import { AstBuilder } from '@app-builder/components/Scenario/AstBuilder/AstBuilder'; -import { - type AstNode, - type DatabaseAccessAstNode, - type DataModel, - type PayloadAstNode, -} from '@app-builder/models'; -import { type CustomList } from '@app-builder/models/custom-list'; -import { type RuleExecution } from '@app-builder/models/decision'; -import { type OperatorFunction } from '@app-builder/models/editable-operators'; -import { type NodeEvaluation } from '@app-builder/models/node-evaluation'; -import { type ScenarioIterationRule } from '@app-builder/models/scenario-iteration-rule'; -import { useAstNodeEditor } from '@app-builder/services/editor/ast-editor'; -import { - DisplayReturnValuesProvider, - useDisplayReturnValues, -} from '@app-builder/services/editor/return-value'; -import { formatNumber, useFormatLanguage } from '@app-builder/utils/format'; -import * as React from 'react'; -import { Trans, useTranslation } from 'react-i18next'; -import { Switch } from 'ui-design-system'; - -export function RuleExecutionDetail({ - ruleExecution, - triggerObjectType, - astRuleData, -}: { - ruleExecution: RuleExecution; - triggerObjectType: string; - astRuleData: { - rules: ScenarioIterationRule[]; - databaseAccessors: DatabaseAccessAstNode[]; - payloadAccessors: PayloadAstNode[]; - operators: OperatorFunction[]; - dataModel: DataModel; - customLists: CustomList[]; - }; -}) { - const { t } = useTranslation(decisionsI18n); - const language = useFormatLanguage(); - const currentRule = React.useMemo( - () => astRuleData.rules.find((rule) => rule.id === ruleExecution.ruleId), - [astRuleData.rules, ruleExecution.ruleId], - ); - - if (!currentRule || !currentRule.formula) { - return ( -

- {t('decisions:rules.error.not_found')} -

- ); - } - - return ( - -
-
- , - }} - values={{ - score: formatNumber(currentRule.scoreModifier, { - language, - signDisplay: 'always', - }), - }} - /> -
- {ruleExecution.evaluation ? : null} -
- - -
- ); -} - -function DisplayReturnValuesSwitch() { - const { t } = useTranslation(decisionsI18n); - const [displayReturnValues, setDisplayReturnValues] = - useDisplayReturnValues(); - - const id = React.useId(); - - return ( -
- - -
- ); -} - -function RuleFormula({ - formula, - databaseAccessors, - evaluation, - payloadAccessors, - operators, - dataModel, - customLists, - triggerObjectType, -}: { - formula: AstNode; - evaluation?: NodeEvaluation; - databaseAccessors: DatabaseAccessAstNode[]; - payloadAccessors: PayloadAstNode[]; - operators: OperatorFunction[]; - dataModel: DataModel; - customLists: CustomList[]; - triggerObjectType: string; -}) { - const astEditorStore = useAstNodeEditor({ - initialAstNode: formula, - initialEvaluation: evaluation, - }); - return ( - - - - ); -} diff --git a/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilder.tsx b/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilder.tsx index 3bea99a0a..e158be87b 100644 --- a/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilder.tsx +++ b/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilder.tsx @@ -1,10 +1,9 @@ +import { type DataModel } from '@app-builder/models'; import { type DatabaseAccessAstNode, - type DataModel, type PayloadAstNode, -} from '@app-builder/models'; +} from '@app-builder/models/astNode/data-accessor'; import { type CustomList } from '@app-builder/models/custom-list'; -import { type OperatorFunction } from '@app-builder/models/editable-operators'; import { type AstEditorStore, AstNodeEditorProvider, @@ -18,7 +17,6 @@ interface AstBuilderProps { options: { databaseAccessors: DatabaseAccessAstNode[]; payloadAccessors: PayloadAstNode[]; - operators: OperatorFunction[]; dataModel: DataModel; customLists: CustomList[]; triggerObjectType: string; diff --git a/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/AstBuilderNode.tsx b/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/AstBuilderNode.tsx index 0d0a3430e..2f0bcaed8 100644 --- a/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/AstBuilderNode.tsx +++ b/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/AstBuilderNode.tsx @@ -1,8 +1,8 @@ +import { type AstNode } from '@app-builder/models'; import { - type AstNode, isMainAstBinaryNode, isMainAstUnaryNode, -} from '@app-builder/models'; +} from '@app-builder/models/astNode/builder-ast-node'; import { useEnumValuesFromNeighbour, useEvaluation, diff --git a/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/MainAstLine.tsx b/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/MainAstLine.tsx index 951cf2446..df9e4215a 100644 --- a/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/MainAstLine.tsx +++ b/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/MainAstLine.tsx @@ -1,15 +1,14 @@ +import { type AstNode, NewUndefinedAstNode } from '@app-builder/models'; import { - type AstNode, isMainAstNode, type MainAstBinaryNode, type MainAstUnaryNode, - NewUndefinedAstNode, -} from '@app-builder/models'; +} from '@app-builder/models/astNode/builder-ast-node'; +import { allMainAstOperatorFunctions } from '@app-builder/models/astNode/builder-ast-node-node-operator'; import { useAstNodeEditorActions, useEvaluationErrors, } from '@app-builder/services/editor/ast-editor'; -import { useMainAstOperatorFunctions } from '@app-builder/services/editor/options'; import type * as React from 'react'; import { useTranslation } from 'react-i18next'; import { MenuButton, MenuItem, MenuPopover, MenuRoot } from 'ui-design-system'; @@ -63,7 +62,7 @@ export function MainAstBinaryOperatorLine({ ); } - const operators = useMainAstOperatorFunctions(); + const operators = allMainAstOperatorFunctions; const left = mainAstNode.children[0]; const leftPath = `${treePath}.children.0`; @@ -156,7 +155,7 @@ export function MainAstUnaryOperatorLine({ ); } - const operators = useMainAstOperatorFunctions(); + const operators = allMainAstOperatorFunctions; const left = mainAstNode.children[0]; const leftPath = `${treePath}.children.0`; diff --git a/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/Operand/Operand.tsx b/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/Operand/Operand.tsx index fbb2412bd..6c9ee11c0 100644 --- a/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/Operand/Operand.tsx +++ b/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/Operand/Operand.tsx @@ -1,10 +1,10 @@ import { type AstNode, - type ConstantAstNode, type ConstantType, type DataType, - isKnownOperandAstNode, } from '@app-builder/models'; +import { isKnownOperandAstNode } from '@app-builder/models/astNode/builder-ast-node'; +import { type ConstantAstNode } from '@app-builder/models/astNode/constant'; import { type OperandType } from '@app-builder/models/operand-type'; import { type AstNodeErrors, diff --git a/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/Operand/OperandEditor/OperandEditModal/AggregationEdit/AggregationEdit.tsx b/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/Operand/OperandEditor/OperandEditModal/AggregationEdit/AggregationEdit.tsx index de7889521..24c5cd9d5 100644 --- a/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/Operand/OperandEditor/OperandEditModal/AggregationEdit/AggregationEdit.tsx +++ b/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/Operand/OperandEditor/OperandEditModal/AggregationEdit/AggregationEdit.tsx @@ -1,16 +1,16 @@ import { Callout } from '@app-builder/components/Callout'; import { ExternalLink } from '@app-builder/components/ExternalLink'; import { EvaluationErrors } from '@app-builder/components/Scenario/ScenarioValidationError'; +import { type AstNode } from '@app-builder/models'; import { type AggregationAstNode, aggregationAstNodeName, - type AstNode, - NewConstantAstNode, -} from '@app-builder/models'; +} from '@app-builder/models/astNode/aggregation'; +import { NewConstantAstNode } from '@app-builder/models/astNode/constant'; import { type AggregatorOperator, aggregatorOperators, -} from '@app-builder/models/editable-operators'; +} from '@app-builder/models/modale-operators'; import { type EvaluationError } from '@app-builder/models/node-evaluation'; import { aggregationDocHref } from '@app-builder/services/documentation-href'; import { useDataModel } from '@app-builder/services/editor/options'; diff --git a/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/Operand/OperandEditor/OperandEditModal/AggregationEdit/EditFilters.tsx b/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/Operand/OperandEditor/OperandEditModal/AggregationEdit/EditFilters.tsx index 3562a1c98..6667a8c52 100644 --- a/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/Operand/OperandEditor/OperandEditModal/AggregationEdit/EditFilters.tsx +++ b/packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/Operand/OperandEditor/OperandEditModal/AggregationEdit/EditFilters.tsx @@ -5,9 +5,9 @@ import { LogicalOperatorLabel } from '@app-builder/components/Scenario/AstBuilde import { EvaluationErrors } from '@app-builder/components/Scenario/ScenarioValidationError'; import { type AstNode, NewUndefinedAstNode } from '@app-builder/models'; import { - filterOperators, - isFilterOperator, -} from '@app-builder/models/editable-operators'; + aggregationFilterOperators, + isAggregationFilterOperator, +} from '@app-builder/models/modale-operators'; import { useDefaultCoerceToConstant, useGetAstNodeOperandProps, @@ -139,7 +139,8 @@ export function EditFilters({ /> 0 ? 'error' : 'valid' } - operators={filterOperators} + operators={aggregationFilterOperators} /> ( }, ); -interface OperatorProps +interface OperatorProps extends VariantProps { value?: T; setValue: (operator: T) => void; @@ -62,7 +60,7 @@ interface OperatorProps * * For now, this is not possible due to the Radix Select component not allowing for a custom label component */ -export function Operator({ +export function Operator({ value, setValue, operators, diff --git a/packages/app-builder/src/components/Scenario/AstBuilder/RootAstBuilderNode/RootAnd.tsx b/packages/app-builder/src/components/Scenario/AstBuilder/RootAstBuilderNode/RootAnd.tsx index b8874c04b..3a2b6bbdb 100644 --- a/packages/app-builder/src/components/Scenario/AstBuilder/RootAstBuilderNode/RootAnd.tsx +++ b/packages/app-builder/src/components/Scenario/AstBuilder/RootAstBuilderNode/RootAnd.tsx @@ -1,9 +1,6 @@ import { LogicalOperatorLabel } from '@app-builder/components/Scenario/AstBuilder/RootAstBuilderNode/LogicalOperator'; -import { - type AndAstNode, - type AstNode, - NewUndefinedAstNode, -} from '@app-builder/models'; +import { type AstNode, NewUndefinedAstNode } from '@app-builder/models'; +import { type AndAstNode } from '@app-builder/models/astNode/builder-ast-node'; import { useAstNodeEditorActions, useRootOrAndChildValidation, diff --git a/packages/app-builder/src/components/Scenario/AstBuilder/RootAstBuilderNode/RootAstBuilderNode.tsx b/packages/app-builder/src/components/Scenario/AstBuilder/RootAstBuilderNode/RootAstBuilderNode.tsx index 47ccbb0a2..69b0b4994 100644 --- a/packages/app-builder/src/components/Scenario/AstBuilder/RootAstBuilderNode/RootAstBuilderNode.tsx +++ b/packages/app-builder/src/components/Scenario/AstBuilder/RootAstBuilderNode/RootAstBuilderNode.tsx @@ -1,4 +1,7 @@ -import { isAndAstNode, isOrWithAndAstNode } from '@app-builder/models'; +import { + isAndAstNode, + isOrWithAndAstNode, +} from '@app-builder/models/astNode/builder-ast-node'; import { useRootAstNode } from '@app-builder/services/editor/ast-editor'; import { AstBuilderNode } from '../AstBuilderNode'; diff --git a/packages/app-builder/src/components/Scenario/AstBuilder/RootAstBuilderNode/RootOrWithAnd.tsx b/packages/app-builder/src/components/Scenario/AstBuilder/RootAstBuilderNode/RootOrWithAnd.tsx index 4a3253263..a2c25cea9 100644 --- a/packages/app-builder/src/components/Scenario/AstBuilder/RootAstBuilderNode/RootOrWithAnd.tsx +++ b/packages/app-builder/src/components/Scenario/AstBuilder/RootAstBuilderNode/RootOrWithAnd.tsx @@ -1,11 +1,13 @@ import { LogicalOperatorLabel } from '@app-builder/components/Scenario/AstBuilder/RootAstBuilderNode/LogicalOperator'; import { - type AndAstNode, type AstNode, NewAstNode, NewUndefinedAstNode, - type OrWithAndAstNode, } from '@app-builder/models'; +import { + type AndAstNode, + type OrWithAndAstNode, +} from '@app-builder/models/astNode/builder-ast-node'; import { useAstNodeEditorActions, useEvaluation, diff --git a/packages/app-builder/src/components/Scenario/Workflow/DetailPanel/CaseNameEditor.hook.ts b/packages/app-builder/src/components/Scenario/Workflow/DetailPanel/CaseNameEditor.hook.ts index 0ed321640..58602f145 100644 --- a/packages/app-builder/src/components/Scenario/Workflow/DetailPanel/CaseNameEditor.hook.ts +++ b/packages/app-builder/src/components/Scenario/Workflow/DetailPanel/CaseNameEditor.hook.ts @@ -1,7 +1,5 @@ -import { - NewPayloadAstNode, - NewStringTemplateAstNode, -} from '@app-builder/models'; +import { NewPayloadAstNode } from '@app-builder/models/astNode/data-accessor'; +import { NewStringTemplateAstNode } from '@app-builder/models/astNode/strings'; import { useMemo } from 'react'; import { defaultCaseName } from './shared'; diff --git a/packages/app-builder/src/components/Scenario/Workflow/DetailPanel/CaseNameEditor.tsx b/packages/app-builder/src/components/Scenario/Workflow/DetailPanel/CaseNameEditor.tsx index e6f52c82a..1b92014a1 100644 --- a/packages/app-builder/src/components/Scenario/Workflow/DetailPanel/CaseNameEditor.tsx +++ b/packages/app-builder/src/components/Scenario/Workflow/DetailPanel/CaseNameEditor.tsx @@ -1,9 +1,9 @@ +import { type AstNode } from '@app-builder/models'; import { - type AstNode, isStringTemplateAstNode, NewStringTemplateAstNode, type StringTemplateAstNode, -} from '@app-builder/models'; +} from '@app-builder/models/astNode/strings'; import { useCurrentScenario } from '@app-builder/routes/_builder+/scenarios+/$scenarioId+/_layout'; import { useAstValidationFetcher } from '@app-builder/routes/ressources+/scenarios+/$scenarioId+/validate-ast'; import { useTriggerObjectTable } from '@app-builder/services/editor/options'; diff --git a/packages/app-builder/src/components/Scenario/Workflow/models/nodes.ts b/packages/app-builder/src/components/Scenario/Workflow/models/nodes.ts index c3be69f8c..f6a7abffc 100644 --- a/packages/app-builder/src/components/Scenario/Workflow/models/nodes.ts +++ b/packages/app-builder/src/components/Scenario/Workflow/models/nodes.ts @@ -1,4 +1,4 @@ -import { type StringTemplateAstNode } from '@app-builder/models'; +import { type StringTemplateAstNode } from '@app-builder/models/astNode/strings'; import { type Outcome } from '@app-builder/models/outcome'; import { nanoid } from 'nanoid'; import { useTranslation } from 'react-i18next'; diff --git a/packages/app-builder/src/models/ast-node.ts b/packages/app-builder/src/models/ast-node.ts deleted file mode 100644 index 956932252..000000000 --- a/packages/app-builder/src/models/ast-node.ts +++ /dev/null @@ -1,706 +0,0 @@ -import { type NodeDto } from 'marble-api'; -import * as R from 'remeda'; - -import { - type BinaryMainAstOperatorFunction, - isBinaryMainAstOperatorFunction, - isMainAstOperatorFunction, - isUnaryMainAstOperatorFunction, - type MainAstOperatorFunction, - type UnaryMainAstOperatorFunction, - undefinedAstNodeName, -} from './editable-operators'; -import { - defaultEditableFuzzyMatchAlgorithm, - defaultFuzzyMatchComparatorThreshold, - type FuzzyMatchAlgorithm, -} from './fuzzy-match'; - -export type AstNode = { - name: string | null; - constant?: ConstantType; - children: AstNode[]; - namedChildren: Record; -}; - -export type ConstantType = - | number - | string - | boolean - | null - | Array - | { [key: string]: ConstantType }; - -// helper -export function NewAstNode({ - name, - constant, - children, - namedChildren, -}: Partial = {}): AstNode { - return { - name: name ?? null, - constant: constant, - children: children ?? [], - namedChildren: namedChildren ?? {}, - }; -} - -export function NewUndefinedAstNode({ - children, - namedChildren, -}: Partial> = {}): UndefinedAstNode { - return { - name: undefinedAstNodeName, - children: children ?? [], - namedChildren: namedChildren ?? {}, - }; -} - -export function NewEmptyTriggerAstNode(): AstNode { - return NewAstNode({ - name: 'And', - }); -} - -export function NewEmptyRuleAstNode(): AstNode { - return NewAstNode({ - name: 'Or', - children: [], - }); -} - -export function adaptAstNode(nodeDto: NodeDto): AstNode { - return { - name: nodeDto.name === undefined ? null : nodeDto.name, - constant: nodeDto.constant, - children: (nodeDto.children ?? []).map(adaptAstNode), - namedChildren: R.mapValues(nodeDto.named_children ?? {}, adaptAstNode), - }; -} - -export function adaptNodeDto(astNode: AstNode): NodeDto { - return { - name: astNode.name ?? undefined, - constant: astNode.constant, - children: astNode.children.map(adaptNodeDto), - named_children: R.mapValues(astNode.namedChildren ?? {}, adaptNodeDto), - }; -} - -export interface UndefinedAstNode extends Omit { - name: typeof undefinedAstNodeName; -} - -export function isUndefinedAstNode(node: AstNode): node is UndefinedAstNode { - return node.name === undefinedAstNodeName; -} - -export interface ConstantAstNode { - name: null; - constant: T; - children: []; - namedChildren: Record; -} - -export function NewConstantAstNode({ - constant, -}: { - constant: T; -}): ConstantAstNode { - return { - name: null, - constant: constant, - children: [], - namedChildren: {}, - }; -} - -export function isConstant(node: AstNode): node is ConstantAstNode { - return !node.name && node.constant !== undefined; -} - -export const databaseAccessAstNodeName = 'DatabaseAccess'; -export interface DatabaseAccessAstNode { - name: typeof databaseAccessAstNodeName; - constant?: undefined; - children: []; - namedChildren: { - fieldName: ConstantAstNode; - path: ConstantAstNode; - tableName: ConstantAstNode; - }; -} - -export const aggregationAstNodeName = 'Aggregator'; -export interface AggregationAstNode { - name: typeof aggregationAstNodeName; - constant: undefined; - children: []; - namedChildren: { - aggregator: ConstantAstNode; - tableName: ConstantAstNode; - fieldName: ConstantAstNode; - label: ConstantAstNode; - filters: { - name: 'List'; - constant: undefined; - children: { - name: 'Filter'; - constant: undefined; - children: never[]; - namedChildren: { - tableName: ConstantAstNode; - fieldName: ConstantAstNode; - operator: ConstantAstNode; - value: AstNode; - }; - }[]; - namedChildren: Record; - }; - }; -} - -export function NewAggregatorAstNode( - aggregatorName: string, -): AggregationAstNode { - return { - name: aggregationAstNodeName, - constant: undefined, - children: [], - namedChildren: { - aggregator: NewConstantAstNode({ constant: aggregatorName }), - tableName: NewConstantAstNode({ constant: '' }), - fieldName: NewConstantAstNode({ constant: '' }), - label: NewConstantAstNode({ constant: '' }), - filters: { - name: 'List', - constant: undefined, - children: [], - namedChildren: {}, - }, - }, - }; -} - -export const payloadAstNodeName = 'Payload'; -export interface PayloadAstNode { - name: typeof payloadAstNodeName; - constant?: undefined; - children: [ConstantAstNode]; - namedChildren: Record; -} - -export function NewPayloadAstNode(field: string): PayloadAstNode { - return { - name: payloadAstNodeName, - children: [NewConstantAstNode({ constant: field })], - namedChildren: {}, - }; -} - -export const customListAccessAstNodeName = 'CustomListAccess'; -export interface CustomListAccessAstNode { - name: typeof customListAccessAstNodeName; - constant: undefined; - children: []; - namedChildren: { - customListId: ConstantAstNode; - }; -} - -export function NewCustomListAstNode( - customListId: string, -): CustomListAccessAstNode { - return { - name: customListAccessAstNodeName, - constant: undefined, - children: [], - namedChildren: { - customListId: NewConstantAstNode({ constant: customListId }), - }, - }; -} - -export type TimestampFieldAstNode = - | DataAccessorAstNode - | TimeNowAstNode - | TimeAddAstNode - | UndefinedAstNode; - -export function isTimestampFieldAstNode( - node: AstNode, -): node is TimestampFieldAstNode { - return ( - isDataAccessorAstNode(node) || - isTimeNow(node) || - isTimeAdd(node) || - isUndefinedAstNode(node) - ); -} - -export const timestampExtractAstNodeName = 'TimestampExtract'; -export const validTimestampExtractParts = [ - 'year', - 'month', - 'day_of_month', - 'day_of_week', - 'hour', -] as const; -export type ValidTimestampExtractParts = - (typeof validTimestampExtractParts)[number]; - -export interface TimestampExtractAstNode { - name: typeof timestampExtractAstNodeName; - constant?: undefined; - children: []; - namedChildren: { - timestamp: TimestampFieldAstNode; - part: ConstantAstNode; - }; -} -export function NewTimestampExtractAstNode( - timestampFieldAstNode: TimestampFieldAstNode = NewUndefinedAstNode(), - part: ConstantAstNode = NewConstantAstNode({ - constant: 'hour', - }), -): TimestampExtractAstNode { - return { - name: timestampExtractAstNodeName, - constant: undefined, - children: [], - namedChildren: { - timestamp: timestampFieldAstNode, - part, - }, - }; -} - -export const timeAddAstNodeName = 'TimeAdd'; -export interface TimeAddAstNode { - name: typeof timeAddAstNodeName; - constant?: undefined; - children: []; - namedChildren: { - timestampField: TimestampFieldAstNode; - sign: ConstantAstNode; - duration: ConstantAstNode; - }; -} - -export function NewTimeAddAstNode( - timestampFieldAstNode: TimestampFieldAstNode = NewUndefinedAstNode(), - signAstNode: ConstantAstNode = NewConstantAstNode({ - constant: '', - }), - durationAstNode: ConstantAstNode = NewConstantAstNode({ - constant: '', - }), -): TimeAddAstNode { - return { - name: timeAddAstNodeName, - constant: undefined, - children: [], - namedChildren: { - timestampField: timestampFieldAstNode, - sign: signAstNode, - duration: durationAstNode, - }, - }; -} - -export const timeNowAstNodeName = 'TimeNow'; -export interface TimeNowAstNode { - name: typeof timeNowAstNodeName; - constant?: undefined; - children: []; - namedChildren: Record; -} - -export function NewTimeNowAstNode(): TimeNowAstNode { - return { - name: timeNowAstNodeName, - constant: undefined, - children: [], - namedChildren: {}, - }; -} - -export const fuzzyMatchAstNodeName = 'FuzzyMatch'; -export interface FuzzyMatchAstNode { - name: typeof fuzzyMatchAstNodeName; - constant?: undefined; - children: [AstNode, AstNode]; - namedChildren: { - algorithm: ConstantAstNode; - }; -} - -export function NewFuzzyMatchAstNode({ - left = NewUndefinedAstNode(), - right = NewUndefinedAstNode(), - algorithm = defaultEditableFuzzyMatchAlgorithm, -}: { - left?: AstNode; - right?: AstNode; - algorithm?: FuzzyMatchAlgorithm; -}): FuzzyMatchAstNode { - return { - name: fuzzyMatchAstNodeName, - constant: undefined, - children: [left, right], - namedChildren: { - algorithm: NewConstantAstNode({ constant: algorithm }), - }, - }; -} - -export const fuzzyMatchAnyOfAstNodeName = 'FuzzyMatchAnyOf'; -export interface FuzzyMatchAnyOfAstNode { - name: typeof fuzzyMatchAnyOfAstNodeName; - constant?: undefined; - children: [AstNode, AstNode]; - namedChildren: { - algorithm: ConstantAstNode; - }; -} - -export function NewFuzzyMatchAnyOfAstNode({ - left = NewUndefinedAstNode(), - right = NewUndefinedAstNode(), - algorithm = defaultEditableFuzzyMatchAlgorithm, -}: { - left?: AstNode; - right?: AstNode; - algorithm?: FuzzyMatchAlgorithm; -}): FuzzyMatchAnyOfAstNode { - return { - name: fuzzyMatchAnyOfAstNodeName, - constant: undefined, - children: [left, right], - namedChildren: { - algorithm: NewConstantAstNode({ constant: algorithm }), - }, - }; -} - -export interface FuzzyMatchComparatorAstNode { - name: '>'; - constant?: undefined; - children: [ - FuzzyMatchAstNode | FuzzyMatchAnyOfAstNode, - ConstantAstNode, - ]; - namedChildren: Record; -} - -export function NewFuzzyMatchComparatorAstNode({ - funcName, - left, - right, - algorithm, - threshold = defaultFuzzyMatchComparatorThreshold, -}: { - funcName: typeof fuzzyMatchAnyOfAstNodeName | typeof fuzzyMatchAstNodeName; - left?: AstNode; - right?: AstNode; - algorithm?: FuzzyMatchAlgorithm; - threshold?: number; -}): FuzzyMatchComparatorAstNode { - const fuzzyMatch = - funcName === fuzzyMatchAstNodeName - ? NewFuzzyMatchAstNode({ - left, - right, - algorithm, - }) - : NewFuzzyMatchAnyOfAstNode({ - left, - right, - algorithm, - }); - - return { - name: '>', - constant: undefined, - children: [fuzzyMatch, NewConstantAstNode({ constant: threshold })], - namedChildren: {}, - }; -} - -export const isMultipleOfAstNodeName = 'IsMultipleOf'; -export interface IsMultipleOfAstNode { - name: typeof isMultipleOfAstNodeName; - constant?: undefined; - children: []; - namedChildren: { - value: AstNode; - divider: ConstantAstNode; - }; -} - -export function NewIsMultipleOfAstNode( - value: AstNode = NewUndefinedAstNode(), - divider: ConstantAstNode = NewConstantAstNode({ constant: 1 }), -): IsMultipleOfAstNode { - return { - name: isMultipleOfAstNodeName, - constant: undefined, - children: [], - namedChildren: { - value, - divider, - }, - }; -} - -export const stringTemplateAstNodeName = 'StringTemplate'; -export interface StringTemplateAstNode { - name: typeof stringTemplateAstNodeName; - constant?: undefined; - children: ConstantAstNode[]; - namedChildren: Record; -} - -export function NewStringTemplateAstNode( - template: string = '', - variables: Record = {}, -): StringTemplateAstNode { - return { - name: stringTemplateAstNodeName, - constant: undefined, - children: [NewConstantAstNode({ constant: template })], - namedChildren: variables, - }; -} - -export function isDatabaseAccess(node: AstNode): node is DatabaseAccessAstNode { - return node.name === databaseAccessAstNodeName; -} - -export function isAggregation(node: AstNode): node is AggregationAstNode { - return node.name === aggregationAstNodeName; -} - -export function isPayload(node: AstNode): node is PayloadAstNode { - return node.name === payloadAstNodeName; -} - -export function isCustomListAccess( - node: AstNode, -): node is CustomListAccessAstNode { - return node.name === customListAccessAstNodeName; -} - -export function isTimeAdd(node: AstNode): node is TimeAddAstNode { - return node.name === timeAddAstNodeName; -} - -export function isTimestampExtract( - node: AstNode, -): node is TimestampExtractAstNode { - return node.name === timestampExtractAstNodeName; -} - -export function isTimeNow(node: AstNode): node is TimeNowAstNode { - return node.name === timeNowAstNodeName; -} - -export function isFuzzyMatch(node: AstNode): node is FuzzyMatchAstNode { - return node.name === fuzzyMatchAstNodeName; -} - -export function isFuzzyMatchAnyOf( - node: AstNode, -): node is FuzzyMatchAnyOfAstNode { - return node.name === fuzzyMatchAnyOfAstNodeName; -} - -export function isFuzzyMatchComparator( - node: AstNode, -): node is FuzzyMatchComparatorAstNode { - if (node.name !== '>') { - return false; - } - if (node.children.length !== 2) { - return false; - } - const firstChild = node.children[0]; - if (firstChild === undefined) { - return false; - } - return isFuzzyMatch(firstChild) || isFuzzyMatchAnyOf(firstChild); -} - -export function isIsMultipleOf(node: AstNode): node is IsMultipleOfAstNode { - return node.name === isMultipleOfAstNodeName; -} - -export function isStringTemplateAstNode( - node: AstNode, -): node is StringTemplateAstNode { - return node.name === stringTemplateAstNodeName; -} - -export type EditableAstNode = - | AggregationAstNode - | TimeAddAstNode - | FuzzyMatchComparatorAstNode - | IsMultipleOfAstNode - | StringTemplateAstNode; - -/** - * Check if the node is editable in a dedicated modal - * @param node - * @returns - */ -export function isEditableAstNode(node: AstNode): node is EditableAstNode { - return ( - isAggregation(node) || - isTimeAdd(node) || - isFuzzyMatchComparator(node) || - isTimestampExtract(node) || - isIsMultipleOf(node) || - isStringTemplateAstNode(node) - ); -} - -type LeafOperandAstNode = EditableAstNode | TimeNowAstNode; - -/** - * Check if the node is considered as leaf operand - * @param node - * @returns - */ -export function isLeafOperandAstNode( - node: AstNode, -): node is LeafOperandAstNode { - return isEditableAstNode(node) || isTimeNow(node); -} - -export type DataAccessorAstNode = DatabaseAccessAstNode | PayloadAstNode; - -export function isDataAccessorAstNode( - node: AstNode, -): node is DataAccessorAstNode { - return isDatabaseAccess(node) || isPayload(node); -} - -export type KnownOperandAstNode = - | UndefinedAstNode - | ConstantAstNode - | CustomListAccessAstNode - | DatabaseAccessAstNode - | LeafOperandAstNode; - -/** - * Check if the node is handled in the Operand UI - * @param node - * @returns - */ -export function isKnownOperandAstNode( - node: AstNode, -): node is KnownOperandAstNode { - return ( - isUndefinedAstNode(node) || - isConstant(node) || - isCustomListAccess(node) || - isDataAccessorAstNode(node) || - isLeafOperandAstNode(node) - ); -} - -export interface AndAstNode { - name: 'And'; - constant: undefined; - children: AstNode[]; - namedChildren: Record; -} - -export function isAndAstNode(astNode: AstNode): astNode is AndAstNode { - if (astNode.name !== 'And') { - return false; - } - if (Object.keys(astNode.namedChildren).length > 0) return false; - return true; -} - -export interface OrWithAndAstNode { - name: 'Or'; - constant: undefined; - children: AndAstNode[]; - namedChildren: Record; -} - -export function isOrWithAndAstNode( - astNode: AstNode, -): astNode is OrWithAndAstNode { - if (astNode.name !== 'Or') { - return false; - } - for (const child of astNode.children) { - if (child.name !== 'And') { - return false; - } - } - if (Object.keys(astNode.namedChildren).length > 0) return false; - return true; -} - -export interface MainAstNode { - name: MainAstOperatorFunction; - constant: undefined; - children: AstNode[]; - namedChildren: Record; -} - -export interface MainAstBinaryNode { - name: BinaryMainAstOperatorFunction; - constant: undefined; - children: [AstNode, AstNode]; - namedChildren: Record; -} - -export interface MainAstUnaryNode { - name: UnaryMainAstOperatorFunction; - constant: undefined; - children: [AstNode]; - namedChildren: Record; -} - -export function isMainAstNode(astNode: AstNode): astNode is MainAstNode { - if (isLeafOperandAstNode(astNode)) { - return false; - } - - if (Object.keys(astNode.namedChildren).length > 0) { - return false; - } - if (astNode.name == null || !isMainAstOperatorFunction(astNode.name)) { - return false; - } - - return true; -} - -export function isMainAstUnaryNode( - astNode: AstNode, -): astNode is MainAstUnaryNode { - if (!isMainAstNode(astNode)) return false; - - return ( - astNode.children.length === 1 && - isUnaryMainAstOperatorFunction(astNode.name) - ); -} - -export function isMainAstBinaryNode( - astNode: AstNode, -): astNode is MainAstBinaryNode { - if (!isMainAstNode(astNode)) return false; - - return ( - astNode.children.length === 2 && - isBinaryMainAstOperatorFunction(astNode.name) - ); -} diff --git a/packages/app-builder/src/models/astNode/aggregation.ts b/packages/app-builder/src/models/astNode/aggregation.ts new file mode 100644 index 000000000..616a8238d --- /dev/null +++ b/packages/app-builder/src/models/astNode/aggregation.ts @@ -0,0 +1,57 @@ +import { type AstNode } from './ast-node'; +import { type ConstantAstNode, NewConstantAstNode } from './constant'; + +export const aggregationAstNodeName = 'Aggregator'; +export interface AggregationAstNode { + name: typeof aggregationAstNodeName; + constant: undefined; + children: []; + namedChildren: { + aggregator: ConstantAstNode; + tableName: ConstantAstNode; + fieldName: ConstantAstNode; + label: ConstantAstNode; + filters: { + name: 'List'; + constant: undefined; + children: { + name: 'Filter'; + constant: undefined; + children: never[]; + namedChildren: { + tableName: ConstantAstNode; + fieldName: ConstantAstNode; + operator: ConstantAstNode; + value: AstNode; + }; + }[]; + namedChildren: Record; + }; + }; +} + +export function NewAggregatorAstNode( + aggregatorName: string, +): AggregationAstNode { + return { + name: aggregationAstNodeName, + constant: undefined, + children: [], + namedChildren: { + aggregator: NewConstantAstNode({ constant: aggregatorName }), + tableName: NewConstantAstNode({ constant: '' }), + fieldName: NewConstantAstNode({ constant: '' }), + label: NewConstantAstNode({ constant: '' }), + filters: { + name: 'List', + constant: undefined, + children: [], + namedChildren: {}, + }, + }, + }; +} + +export function isAggregation(node: AstNode): node is AggregationAstNode { + return node.name === aggregationAstNodeName; +} diff --git a/packages/app-builder/src/models/astNode/ast-node.ts b/packages/app-builder/src/models/astNode/ast-node.ts new file mode 100644 index 000000000..00e6148d2 --- /dev/null +++ b/packages/app-builder/src/models/astNode/ast-node.ts @@ -0,0 +1,85 @@ +import { type NodeDto } from 'marble-api'; +import * as R from 'remeda'; + +// AST node general types +export type AstNode = { + name: string | null; + constant?: ConstantType; + children: AstNode[]; + namedChildren: Record; +}; + +export type ConstantType = + | number + | string + | boolean + | null + | Array + | { [key: string]: ConstantType }; + +export function NewAstNode({ + name, + constant, + children, + namedChildren, +}: Partial = {}): AstNode { + return { + name: name ?? null, + constant: constant, + children: children ?? [], + namedChildren: namedChildren ?? {}, + }; +} + +export const undefinedAstNodeName = 'Undefined'; +export interface UndefinedAstNode extends Omit { + name: typeof undefinedAstNodeName; +} + +export function NewUndefinedAstNode({ + children, + namedChildren, +}: Partial> = {}): UndefinedAstNode { + return { + name: undefinedAstNodeName, + children: children ?? [], + namedChildren: namedChildren ?? {}, + }; +} + +export function isUndefinedAstNode(node: AstNode): node is UndefinedAstNode { + return node.name === undefinedAstNodeName; +} + +// Helper functions to work with AST nodes +export function NewEmptyTriggerAstNode(): AstNode { + return NewAstNode({ + name: 'And', + }); +} + +export function NewEmptyRuleAstNode(): AstNode { + return NewAstNode({ + name: 'Or', + children: [], + }); +} + +// DTO adapter functions +export function adaptAstNode(nodeDto: NodeDto): AstNode { + return { + name: nodeDto.name === undefined ? null : nodeDto.name, + constant: nodeDto.constant, + children: (nodeDto.children ?? []).map(adaptAstNode), + namedChildren: R.mapValues(nodeDto.named_children ?? {}, adaptAstNode), + }; +} + +export function adaptNodeDto(astNode: AstNode): NodeDto { + return { + name: astNode.name ?? undefined, + constant: astNode.constant, + children: astNode.children.map(adaptNodeDto), + named_children: R.mapValues(astNode.namedChildren ?? {}, adaptNodeDto), + }; +} diff --git a/packages/app-builder/src/models/astNode/builder-ast-node-node-operator.ts b/packages/app-builder/src/models/astNode/builder-ast-node-node-operator.ts new file mode 100644 index 000000000..7f2f8c29a --- /dev/null +++ b/packages/app-builder/src/models/astNode/builder-ast-node-node-operator.ts @@ -0,0 +1,66 @@ +import { undefinedAstNodeName } from './ast-node'; + +// define a subset of MainAstOperatorFunction with only binary operators +const binaryMainAstOperatorFunctions = [ + '=', + '≠', + '<', + '<=', + '>', + '>=', + '+', + '-', + '*', + '/', + 'IsInList', + 'IsNotInList', + 'StringContains', + 'StringNotContain', + 'StringStartsWith', + 'StringEndsWith', + 'ContainsAnyOf', + 'ContainsNoneOf', +] as const; +export type BinaryMainAstOperatorFunction = + (typeof binaryMainAstOperatorFunctions)[number]; + +export function isBinaryMainAstOperatorFunction( + value: string, +): value is BinaryMainAstOperatorFunction { + return (binaryMainAstOperatorFunctions as ReadonlyArray).includes( + value, + ); +} + +// define a subset of MainAstOperatorFunction with only unary operators +const unaryMainAstOperatorFunctions = ['IsEmpty', 'IsNotEmpty'] as const; +export type UnaryMainAstOperatorFunction = + (typeof unaryMainAstOperatorFunctions)[number]; + +export function isUnaryMainAstOperatorFunction( + value: string, +): value is UnaryMainAstOperatorFunction { + return (unaryMainAstOperatorFunctions as ReadonlyArray).includes( + value, + ); +} + +// The order is important for sorting, it is the order in which the operators are displayed in the dropdown +export const allMainAstOperatorFunctions = [ + ...binaryMainAstOperatorFunctions, + ...unaryMainAstOperatorFunctions, +] as const; + +export function isMainAstOperatorFunction( + value: string, +): value is MainAstOperatorFunction { + return ( + value === undefinedAstNodeName || + isBinaryMainAstOperatorFunction(value) || + isUnaryMainAstOperatorFunction(value) + ); +} +export type MainAstOperatorFunction = + | typeof undefinedAstNodeName + | BinaryMainAstOperatorFunction + | UnaryMainAstOperatorFunction; diff --git a/packages/app-builder/src/models/astNode/builder-ast-node.ts b/packages/app-builder/src/models/astNode/builder-ast-node.ts new file mode 100644 index 000000000..66a5cf1e4 --- /dev/null +++ b/packages/app-builder/src/models/astNode/builder-ast-node.ts @@ -0,0 +1,192 @@ +import { type AggregationAstNode, isAggregation } from './aggregation'; +import { + type AstNode, + isUndefinedAstNode, + type UndefinedAstNode, +} from './ast-node'; +import { + type BinaryMainAstOperatorFunction, + isBinaryMainAstOperatorFunction, + isMainAstOperatorFunction, + isUnaryMainAstOperatorFunction, + type MainAstOperatorFunction, + type UnaryMainAstOperatorFunction, +} from './builder-ast-node-node-operator'; +import { type ConstantAstNode, isConstant } from './constant'; +import { + type CustomListAccessAstNode, + isCustomListAccess, +} from './custom-list'; +import { + type DatabaseAccessAstNode, + isDataAccessorAstNode, +} from './data-accessor'; +import { isIsMultipleOf, type IsMultipleOfAstNode } from './multiple-of'; +import { + type FuzzyMatchComparatorAstNode, + isFuzzyMatchComparator, + isStringTemplateAstNode, + type StringTemplateAstNode, +} from './strings'; +import { + isTimeAdd, + isTimeNow, + isTimestampExtract, + type TimeAddAstNode, + type TimeNowAstNode, +} from './time'; + +export type EditableAstNode = + | AggregationAstNode + | TimeAddAstNode + | FuzzyMatchComparatorAstNode + | IsMultipleOfAstNode + | StringTemplateAstNode; + +/** + * Check if the node is editable in a dedicated modal + * @param node + * @returns + */ +export function isEditableAstNode(node: AstNode): node is EditableAstNode { + return ( + isAggregation(node) || + isTimeAdd(node) || + isFuzzyMatchComparator(node) || + isTimestampExtract(node) || + isIsMultipleOf(node) || + isStringTemplateAstNode(node) + ); +} + +type LeafOperandAstNode = EditableAstNode | TimeNowAstNode; + +/** + * Check if the node is considered as leaf operand + * @param node + * @returns + */ +export function isLeafOperandAstNode( + node: AstNode, +): node is LeafOperandAstNode { + return isEditableAstNode(node) || isTimeNow(node); +} + +export type KnownOperandAstNode = + | UndefinedAstNode + | ConstantAstNode + | CustomListAccessAstNode + | DatabaseAccessAstNode + | LeafOperandAstNode; + +/** + * Check if the node is handled in the Operand UI + * @param node + * @returns + */ +export function isKnownOperandAstNode( + node: AstNode, +): node is KnownOperandAstNode { + return ( + isUndefinedAstNode(node) || + isConstant(node) || + isCustomListAccess(node) || + isDataAccessorAstNode(node) || + isLeafOperandAstNode(node) + ); +} + +export interface AndAstNode { + name: 'And'; + constant: undefined; + children: AstNode[]; + namedChildren: Record; +} + +export function isAndAstNode(astNode: AstNode): astNode is AndAstNode { + if (astNode.name !== 'And') { + return false; + } + if (Object.keys(astNode.namedChildren).length > 0) return false; + return true; +} + +export interface OrWithAndAstNode { + name: 'Or'; + constant: undefined; + children: AndAstNode[]; + namedChildren: Record; +} + +export function isOrWithAndAstNode( + astNode: AstNode, +): astNode is OrWithAndAstNode { + if (astNode.name !== 'Or') { + return false; + } + for (const child of astNode.children) { + if (child.name !== 'And') { + return false; + } + } + if (Object.keys(astNode.namedChildren).length > 0) return false; + return true; +} + +export interface MainAstNode { + name: MainAstOperatorFunction; + constant: undefined; + children: AstNode[]; + namedChildren: Record; +} + +export interface MainAstBinaryNode { + name: BinaryMainAstOperatorFunction; + constant: undefined; + children: [AstNode, AstNode]; + namedChildren: Record; +} + +export interface MainAstUnaryNode { + name: UnaryMainAstOperatorFunction; + constant: undefined; + children: [AstNode]; + namedChildren: Record; +} + +export function isMainAstNode(astNode: AstNode): astNode is MainAstNode { + if (isLeafOperandAstNode(astNode)) { + return false; + } + + if (astNode.name == null) { + return false; + } + + return isMainAstOperatorFunction(astNode.name) || isUndefinedAstNode(astNode); +} + +export function isMainAstUnaryNode( + astNode: AstNode, +): astNode is MainAstUnaryNode { + if (!isMainAstNode(astNode)) return false; + + return ( + isMainAstNode(astNode) && + astNode.children.length === 1 && + (isUnaryMainAstOperatorFunction(astNode.name) || + isUndefinedAstNode(astNode)) + ); +} + +export function isMainAstBinaryNode( + astNode: AstNode, +): astNode is MainAstBinaryNode { + if (!isMainAstNode(astNode)) return false; + + return ( + astNode.children.length === 2 && + (isBinaryMainAstOperatorFunction(astNode.name) || + isUndefinedAstNode(astNode)) + ); +} diff --git a/packages/app-builder/src/models/astNode/constant.ts b/packages/app-builder/src/models/astNode/constant.ts new file mode 100644 index 000000000..abf06a298 --- /dev/null +++ b/packages/app-builder/src/models/astNode/constant.ts @@ -0,0 +1,25 @@ +import { type AstNode, type ConstantType } from './ast-node'; + +export interface ConstantAstNode { + name: null; + constant: T; + children: []; + namedChildren: Record; +} + +export function NewConstantAstNode({ + constant, +}: { + constant: T; +}): ConstantAstNode { + return { + name: null, + constant: constant, + children: [], + namedChildren: {}, + }; +} + +export function isConstant(node: AstNode): node is ConstantAstNode { + return !node.name && node.constant !== undefined; +} diff --git a/packages/app-builder/src/models/astNode/custom-list.ts b/packages/app-builder/src/models/astNode/custom-list.ts new file mode 100644 index 000000000..a7e237fff --- /dev/null +++ b/packages/app-builder/src/models/astNode/custom-list.ts @@ -0,0 +1,31 @@ +import { type AstNode } from './ast-node'; +import { type ConstantAstNode, NewConstantAstNode } from './constant'; + +export const customListAccessAstNodeName = 'CustomListAccess'; +export interface CustomListAccessAstNode { + name: typeof customListAccessAstNodeName; + constant: undefined; + children: []; + namedChildren: { + customListId: ConstantAstNode; + }; +} + +export function NewCustomListAstNode( + customListId: string, +): CustomListAccessAstNode { + return { + name: customListAccessAstNodeName, + constant: undefined, + children: [], + namedChildren: { + customListId: NewConstantAstNode({ constant: customListId }), + }, + }; +} + +export function isCustomListAccess( + node: AstNode, +): node is CustomListAccessAstNode { + return node.name === customListAccessAstNodeName; +} diff --git a/packages/app-builder/src/models/astNode/data-accessor.ts b/packages/app-builder/src/models/astNode/data-accessor.ts new file mode 100644 index 000000000..801758e24 --- /dev/null +++ b/packages/app-builder/src/models/astNode/data-accessor.ts @@ -0,0 +1,46 @@ +import { type AstNode } from './ast-node'; +import { type ConstantAstNode, NewConstantAstNode } from './constant'; + +export const databaseAccessAstNodeName = 'DatabaseAccess'; +export interface DatabaseAccessAstNode { + name: typeof databaseAccessAstNodeName; + constant?: undefined; + children: []; + namedChildren: { + fieldName: ConstantAstNode; + path: ConstantAstNode; + tableName: ConstantAstNode; + }; +} + +export function isDatabaseAccess(node: AstNode): node is DatabaseAccessAstNode { + return node.name === databaseAccessAstNodeName; +} + +export const payloadAstNodeName = 'Payload'; +export interface PayloadAstNode { + name: typeof payloadAstNodeName; + constant?: undefined; + children: [ConstantAstNode]; + namedChildren: Record; +} + +export function NewPayloadAstNode(field: string): PayloadAstNode { + return { + name: payloadAstNodeName, + children: [NewConstantAstNode({ constant: field })], + namedChildren: {}, + }; +} + +export function isPayload(node: AstNode): node is PayloadAstNode { + return node.name === payloadAstNodeName; +} + +export type DataAccessorAstNode = DatabaseAccessAstNode | PayloadAstNode; + +export function isDataAccessorAstNode( + node: AstNode, +): node is DataAccessorAstNode { + return isDatabaseAccess(node) || isPayload(node); +} diff --git a/packages/app-builder/src/models/astNode/multiple-of.ts b/packages/app-builder/src/models/astNode/multiple-of.ts new file mode 100644 index 000000000..f4bad16e5 --- /dev/null +++ b/packages/app-builder/src/models/astNode/multiple-of.ts @@ -0,0 +1,32 @@ +import { type AstNode, NewUndefinedAstNode } from './ast-node'; +import { type ConstantAstNode, NewConstantAstNode } from './constant'; + +export const isMultipleOfAstNodeName = 'IsMultipleOf'; +export interface IsMultipleOfAstNode { + name: typeof isMultipleOfAstNodeName; + constant?: undefined; + children: []; + namedChildren: { + value: AstNode; + divider: ConstantAstNode; + }; +} + +export function NewIsMultipleOfAstNode( + value: AstNode = NewUndefinedAstNode(), + divider: ConstantAstNode = NewConstantAstNode({ constant: 1 }), +): IsMultipleOfAstNode { + return { + name: isMultipleOfAstNodeName, + constant: undefined, + children: [], + namedChildren: { + value, + divider, + }, + }; +} + +export function isIsMultipleOf(node: AstNode): node is IsMultipleOfAstNode { + return node.name === isMultipleOfAstNodeName; +} diff --git a/packages/app-builder/src/models/astNode/strings.ts b/packages/app-builder/src/models/astNode/strings.ts new file mode 100644 index 000000000..d288de541 --- /dev/null +++ b/packages/app-builder/src/models/astNode/strings.ts @@ -0,0 +1,169 @@ +import { + defaultEditableFuzzyMatchAlgorithm, + defaultFuzzyMatchComparatorThreshold, + type FuzzyMatchAlgorithm, +} from '../fuzzy-match'; +import { type AstNode, NewUndefinedAstNode } from './ast-node'; +import { type ConstantAstNode, NewConstantAstNode } from './constant'; + +//////////////////////// +// Fuzzy string matching +//////////////////////// + +export const fuzzyMatchAstNodeName = 'FuzzyMatch'; +export interface FuzzyMatchAstNode { + name: typeof fuzzyMatchAstNodeName; + constant?: undefined; + children: [AstNode, AstNode]; + namedChildren: { + algorithm: ConstantAstNode; + }; +} + +export function NewFuzzyMatchAstNode({ + left = NewUndefinedAstNode(), + right = NewUndefinedAstNode(), + algorithm = defaultEditableFuzzyMatchAlgorithm, +}: { + left?: AstNode; + right?: AstNode; + algorithm?: FuzzyMatchAlgorithm; +}): FuzzyMatchAstNode { + return { + name: fuzzyMatchAstNodeName, + constant: undefined, + children: [left, right], + namedChildren: { + algorithm: NewConstantAstNode({ constant: algorithm }), + }, + }; +} + +export const fuzzyMatchAnyOfAstNodeName = 'FuzzyMatchAnyOf'; +export interface FuzzyMatchAnyOfAstNode { + name: typeof fuzzyMatchAnyOfAstNodeName; + constant?: undefined; + children: [AstNode, AstNode]; + namedChildren: { + algorithm: ConstantAstNode; + }; +} + +export function NewFuzzyMatchAnyOfAstNode({ + left = NewUndefinedAstNode(), + right = NewUndefinedAstNode(), + algorithm = defaultEditableFuzzyMatchAlgorithm, +}: { + left?: AstNode; + right?: AstNode; + algorithm?: FuzzyMatchAlgorithm; +}): FuzzyMatchAnyOfAstNode { + return { + name: fuzzyMatchAnyOfAstNodeName, + constant: undefined, + children: [left, right], + namedChildren: { + algorithm: NewConstantAstNode({ constant: algorithm }), + }, + }; +} + +export interface FuzzyMatchComparatorAstNode { + name: '>'; + constant?: undefined; + children: [ + FuzzyMatchAstNode | FuzzyMatchAnyOfAstNode, + ConstantAstNode, + ]; + namedChildren: Record; +} + +export function NewFuzzyMatchComparatorAstNode({ + funcName, + left, + right, + algorithm, + threshold = defaultFuzzyMatchComparatorThreshold, +}: { + funcName: typeof fuzzyMatchAnyOfAstNodeName | typeof fuzzyMatchAstNodeName; + left?: AstNode; + right?: AstNode; + algorithm?: FuzzyMatchAlgorithm; + threshold?: number; +}): FuzzyMatchComparatorAstNode { + const fuzzyMatch = + funcName === fuzzyMatchAstNodeName + ? NewFuzzyMatchAstNode({ + left, + right, + algorithm, + }) + : NewFuzzyMatchAnyOfAstNode({ + left, + right, + algorithm, + }); + + return { + name: '>', + constant: undefined, + children: [fuzzyMatch, NewConstantAstNode({ constant: threshold })], + namedChildren: {}, + }; +} + +export function isFuzzyMatch(node: AstNode): node is FuzzyMatchAstNode { + return node.name === fuzzyMatchAstNodeName; +} + +export function isFuzzyMatchAnyOf( + node: AstNode, +): node is FuzzyMatchAnyOfAstNode { + return node.name === fuzzyMatchAnyOfAstNodeName; +} + +export function isFuzzyMatchComparator( + node: AstNode, +): node is FuzzyMatchComparatorAstNode { + if (node.name !== '>') { + return false; + } + if (node.children.length !== 2) { + return false; + } + const firstChild = node.children[0]; + if (firstChild === undefined) { + return false; + } + return isFuzzyMatch(firstChild) || isFuzzyMatchAnyOf(firstChild); +} + +//////////////////////// +// String templating /// +//////////////////////// + +export const stringTemplateAstNodeName = 'StringTemplate'; +export interface StringTemplateAstNode { + name: typeof stringTemplateAstNodeName; + constant?: undefined; + children: ConstantAstNode[]; + namedChildren: Record; +} + +export function NewStringTemplateAstNode( + template: string = '', + variables: Record = {}, +): StringTemplateAstNode { + return { + name: stringTemplateAstNodeName, + constant: undefined, + children: [NewConstantAstNode({ constant: template })], + namedChildren: variables, + }; +} + +export function isStringTemplateAstNode( + node: AstNode, +): node is StringTemplateAstNode { + return node.name === stringTemplateAstNodeName; +} diff --git a/packages/app-builder/src/models/astNode/time.ts b/packages/app-builder/src/models/astNode/time.ts new file mode 100644 index 000000000..c712f504a --- /dev/null +++ b/packages/app-builder/src/models/astNode/time.ts @@ -0,0 +1,129 @@ +import { + type AstNode, + isUndefinedAstNode, + NewUndefinedAstNode, + type UndefinedAstNode, +} from './ast-node'; +import { type ConstantAstNode, NewConstantAstNode } from './constant'; +import { + type DataAccessorAstNode, + isDataAccessorAstNode, +} from './data-accessor'; + +export const timeAddAstNodeName = 'TimeAdd'; +export interface TimeAddAstNode { + name: typeof timeAddAstNodeName; + constant?: undefined; + children: []; + namedChildren: { + timestampField: TimestampFieldAstNode; + sign: ConstantAstNode; + duration: ConstantAstNode; + }; +} + +export function NewTimeAddAstNode( + timestampFieldAstNode: TimestampFieldAstNode = NewUndefinedAstNode(), + signAstNode: ConstantAstNode = NewConstantAstNode({ + constant: '', + }), + durationAstNode: ConstantAstNode = NewConstantAstNode({ + constant: '', + }), +): TimeAddAstNode { + return { + name: timeAddAstNodeName, + constant: undefined, + children: [], + namedChildren: { + timestampField: timestampFieldAstNode, + sign: signAstNode, + duration: durationAstNode, + }, + }; +} + +export const timeNowAstNodeName = 'TimeNow'; +export interface TimeNowAstNode { + name: typeof timeNowAstNodeName; + constant?: undefined; + children: []; + namedChildren: Record; +} + +export function NewTimeNowAstNode(): TimeNowAstNode { + return { + name: timeNowAstNodeName, + constant: undefined, + children: [], + namedChildren: {}, + }; +} + +export type TimestampFieldAstNode = + | DataAccessorAstNode + | TimeNowAstNode + | TimeAddAstNode + | UndefinedAstNode; + +export function isTimestampFieldAstNode( + node: AstNode, +): node is TimestampFieldAstNode { + return ( + isDataAccessorAstNode(node) || + isTimeNow(node) || + isTimeAdd(node) || + isUndefinedAstNode(node) + ); +} + +export function isTimeAdd(node: AstNode): node is TimeAddAstNode { + return node.name === timeAddAstNodeName; +} + +export function isTimestampExtract( + node: AstNode, +): node is TimestampExtractAstNode { + return node.name === timestampExtractAstNodeName; +} + +export function isTimeNow(node: AstNode): node is TimeNowAstNode { + return node.name === timeNowAstNodeName; +} + +export const timestampExtractAstNodeName = 'TimestampExtract'; +export const validTimestampExtractParts = [ + 'year', + 'month', + 'day_of_month', + 'day_of_week', + 'hour', +] as const; +export type ValidTimestampExtractParts = + (typeof validTimestampExtractParts)[number]; + +export interface TimestampExtractAstNode { + name: typeof timestampExtractAstNodeName; + constant?: undefined; + children: []; + namedChildren: { + timestamp: TimestampFieldAstNode; + part: ConstantAstNode; + }; +} +export function NewTimestampExtractAstNode( + timestampFieldAstNode: TimestampFieldAstNode = NewUndefinedAstNode(), + part: ConstantAstNode = NewConstantAstNode({ + constant: 'hour', + }), +): TimestampExtractAstNode { + return { + name: timestampExtractAstNodeName, + constant: undefined, + children: [], + namedChildren: { + timestamp: timestampFieldAstNode, + part, + }, + }; +} diff --git a/packages/app-builder/src/models/editable-operators.ts b/packages/app-builder/src/models/editable-operators.ts deleted file mode 100644 index 540b9aa43..000000000 --- a/packages/app-builder/src/models/editable-operators.ts +++ /dev/null @@ -1,244 +0,0 @@ -import { type TFunction } from 'i18next'; -import { assertNever } from 'typescript-utils'; - -import { - type ValidTimestampExtractParts, - validTimestampExtractParts, -} from './ast-node'; - -export const undefinedAstNodeName = 'Undefined'; - -// order is important for sorting -const orderedMainAstOperatorFunctions = [ - '=', - '≠', - '<', - '<=', - '>', - '>=', - '+', - '-', - '*', - '/', - 'IsInList', - 'IsNotInList', - 'StringContains', - 'StringNotContain', - 'StringStartsWith', - 'StringEndsWith', - 'ContainsAnyOf', - 'ContainsNoneOf', - 'IsEmpty', - 'IsNotEmpty', - undefinedAstNodeName, -] as const; - -// define a subset of MainAstOperatorFunction with only unary operators -const unaryMainAstOperatorFunctions = ['IsEmpty', 'IsNotEmpty'] as const; -export type UnaryMainAstOperatorFunction = - (typeof unaryMainAstOperatorFunctions)[number]; - -export function isUnaryMainAstOperatorFunction( - value: string, -): value is UnaryMainAstOperatorFunction { - return (unaryMainAstOperatorFunctions as ReadonlyArray).includes( - value, - ); -} - -// define a subset of MainAstOperatorFunction with only binary operators -const binaryMainAstOperatorFunctions = [ - '=', - '≠', - '<', - '<=', - '>', - '>=', - '+', - '-', - '*', - '/', - 'IsInList', - 'IsNotInList', - 'StringContains', - 'StringNotContain', - 'StringStartsWith', - 'StringEndsWith', - 'ContainsAnyOf', - 'ContainsNoneOf', - undefinedAstNodeName, -] as const; -export type BinaryMainAstOperatorFunction = - (typeof binaryMainAstOperatorFunctions)[number]; - -export function isBinaryMainAstOperatorFunction( - value: string, -): value is BinaryMainAstOperatorFunction { - return (binaryMainAstOperatorFunctions as ReadonlyArray).includes( - value, - ); -} - -export function isMainAstOperatorFunction( - value: string, -): value is MainAstOperatorFunction { - return ( - isBinaryMainAstOperatorFunction(value) || - isUnaryMainAstOperatorFunction(value) - ); -} -export type MainAstOperatorFunction = - | BinaryMainAstOperatorFunction - | UnaryMainAstOperatorFunction; - -export function sortMainAstOperatorFunctions( - lhs: MainAstOperatorFunction, - rhs: MainAstOperatorFunction, -) { - const lhsIndex = orderedMainAstOperatorFunctions.indexOf(lhs); - const rhsIndex = orderedMainAstOperatorFunctions.indexOf(rhs); - return lhsIndex - rhsIndex; -} - -export const filterOperators = [ - '=', - '!=', - '>', - '<', - '>=', - '<=', - 'IsInList', - 'IsNotInList', -] as const; -export type FilterOperator = (typeof filterOperators)[number]; - -export function isFilterOperator(value: string): value is FilterOperator { - return (filterOperators as ReadonlyArray).includes(value); -} - -export const timeAddOperators = ['+', '-'] as const; -export type TimeAddOperator = (typeof timeAddOperators)[number]; - -export function isTimeAddOperator(value: string): value is TimeAddOperator { - return (timeAddOperators as ReadonlyArray).includes(value); -} - -export const aggregatorOperators = [ - 'AVG', - 'COUNT', - 'COUNT_DISTINCT', - 'MAX', - 'MIN', - 'SUM', -] as const; -export type AggregatorOperator = (typeof aggregatorOperators)[number]; - -export function isAggregatorOperator( - value: string, -): value is AggregatorOperator { - return (aggregatorOperators as ReadonlyArray).includes(value); -} - -export type OperatorFunction = - | MainAstOperatorFunction - | FilterOperator - | TimeAddOperator - | ValidTimestampExtractParts - | AggregatorOperator; -export function isOperatorFunction(value: string): value is OperatorFunction { - return ( - isMainAstOperatorFunction(value) || - isFilterOperator(value) || - isTimeAddOperator(value) || - isAggregatorOperator(value) || - isTimestampPart(value) - ); -} - -function isTimestampPart(value: string): value is ValidTimestampExtractParts { - return validTimestampExtractParts.includes( - value as ValidTimestampExtractParts, - ); -} - -export function getOperatorName( - t: TFunction<['common', 'scenarios'], undefined>, - operatorName: string, -) { - if (isOperatorFunction(operatorName)) { - switch (operatorName) { - case '+': - return '+'; - case '-': - return '-'; - case '<': - return '<'; - case '=': - return '='; - case '≠': - case '!=': - return '≠'; - case '>': - return '>'; - case '>=': - return '≥'; - case '<=': - return '≤'; - case '*': - return '×'; - case '/': - return '÷'; - case 'IsInList': - return t('scenarios:operator.is_in'); - case 'IsEmpty': - return t('scenarios:operator.is_empty'); - case 'IsNotEmpty': - return t('scenarios:operator.is_not_empty'); - case 'IsNotInList': - return t('scenarios:operator.is_not_in'); - case 'StringContains': - return t('scenarios:operator.contains'); - case 'StringNotContain': - return t('scenarios:operator.does_not_contain'); - case 'StringStartsWith': - return t('scenarios:operator.starts_with'); - case 'StringEndsWith': - return t('scenarios:operator.ends_with'); - case 'ContainsAnyOf': - return t('scenarios:operator.contains_any_of'); - case 'ContainsNoneOf': - return t('scenarios:operator.contains_none_of'); - case 'AVG': - return t('scenarios:aggregator.average'); - case 'COUNT': - return t('scenarios:aggregator.count'); - case 'COUNT_DISTINCT': - return t('scenarios:aggregator.count_distinct'); - case 'MAX': - return t('scenarios:aggregator.max'); - case 'MIN': - return t('scenarios:aggregator.min'); - case 'SUM': - return t('scenarios:aggregator.sum'); - case 'year': - return t('scenarios:timestamp_part.year'); - case 'month': - return t('scenarios:timestamp_part.month'); - case 'day_of_month': - return t('scenarios:timestamp_part.day_of_month'); - case 'day_of_week': - return t('scenarios:timestamp_part.day_of_week'); - case 'hour': - return t('scenarios:timestamp_part.hour'); - case undefinedAstNodeName: - return '...'; - default: - assertNever('Untranslated operator', operatorName); - } - } - - if (process.env.NODE_ENV === 'development') { - console.warn('Unhandled operator', operatorName); - } - return operatorName; -} diff --git a/packages/app-builder/src/models/get-operator-name.ts b/packages/app-builder/src/models/get-operator-name.ts new file mode 100644 index 000000000..034941028 --- /dev/null +++ b/packages/app-builder/src/models/get-operator-name.ts @@ -0,0 +1,87 @@ +import type { TFunction } from 'i18next'; +import { assertNever } from 'typescript-utils'; + +import { undefinedAstNodeName } from './astNode/ast-node'; +import { isOperatorOption } from './operator-options'; + +export function getOperatorName( + t: TFunction<['common', 'scenarios'], undefined>, + operatorName: string, +) { + if (isOperatorOption(operatorName)) { + switch (operatorName) { + case '+': + return '+'; + case '-': + return '-'; + case '<': + return '<'; + case '=': + return '='; + case '≠': + case '!=': + return '≠'; + case '>': + return '>'; + case '>=': + return '≥'; + case '<=': + return '≤'; + case '*': + return '×'; + case '/': + return '÷'; + case 'IsInList': + return t('scenarios:operator.is_in'); + case 'IsEmpty': + return t('scenarios:operator.is_empty'); + case 'IsNotEmpty': + return t('scenarios:operator.is_not_empty'); + case 'IsNotInList': + return t('scenarios:operator.is_not_in'); + case 'StringContains': + return t('scenarios:operator.contains'); + case 'StringNotContain': + return t('scenarios:operator.does_not_contain'); + case 'StringStartsWith': + return t('scenarios:operator.starts_with'); + case 'StringEndsWith': + return t('scenarios:operator.ends_with'); + case 'ContainsAnyOf': + return t('scenarios:operator.contains_any_of'); + case 'ContainsNoneOf': + return t('scenarios:operator.contains_none_of'); + case 'AVG': + return t('scenarios:aggregator.average'); + case 'COUNT': + return t('scenarios:aggregator.count'); + case 'COUNT_DISTINCT': + return t('scenarios:aggregator.count_distinct'); + case 'MAX': + return t('scenarios:aggregator.max'); + case 'MIN': + return t('scenarios:aggregator.min'); + case 'SUM': + return t('scenarios:aggregator.sum'); + case 'year': + return t('scenarios:timestamp_part.year'); + case 'month': + return t('scenarios:timestamp_part.month'); + case 'day_of_month': + return t('scenarios:timestamp_part.day_of_month'); + case 'day_of_week': + return t('scenarios:timestamp_part.day_of_week'); + case 'hour': + return t('scenarios:timestamp_part.hour'); + case undefinedAstNodeName: + return '...'; + default: + assertNever('Untranslated operator', operatorName); + } + } + + if (process.env.NODE_ENV === 'development') { + console.warn('Unhandled operator', operatorName); + } + return operatorName; +} diff --git a/packages/app-builder/src/models/index.ts b/packages/app-builder/src/models/index.ts index 1daa7fc75..8eb6fd331 100644 --- a/packages/app-builder/src/models/index.ts +++ b/packages/app-builder/src/models/index.ts @@ -1,4 +1,4 @@ -export * from './ast-node'; +export * from './astNode/ast-node'; export * from './auth-errors'; export * from './data-model'; export * from './http-errors'; diff --git a/packages/app-builder/src/models/modale-operators.ts b/packages/app-builder/src/models/modale-operators.ts new file mode 100644 index 000000000..4f11963b6 --- /dev/null +++ b/packages/app-builder/src/models/modale-operators.ts @@ -0,0 +1,54 @@ +import { + type ValidTimestampExtractParts, + validTimestampExtractParts, +} from './astNode/time'; + +export const aggregatorOperators = [ + 'AVG', + 'COUNT', + 'COUNT_DISTINCT', + 'MAX', + 'MIN', + 'SUM', +] as const; +export type AggregatorOperator = (typeof aggregatorOperators)[number]; + +export function isAggregatorOperator( + value: string, +): value is AggregatorOperator { + return (aggregatorOperators as ReadonlyArray).includes(value); +} + +export const aggregationFilterOperators = [ + '=', + '!=', + '>', + '<', + '>=', + '<=', + 'IsInList', + 'IsNotInList', +] as const; +export type AggregationFilterOperator = + (typeof aggregationFilterOperators)[number]; + +export function isAggregationFilterOperator( + value: string, +): value is AggregationFilterOperator { + return (aggregationFilterOperators as ReadonlyArray).includes(value); +} + +export const timeAddOperators = ['+', '-'] as const; +export type TimeAddOperator = (typeof timeAddOperators)[number]; + +export function isTimeAddOperator(value: string): value is TimeAddOperator { + return (timeAddOperators as ReadonlyArray).includes(value); +} + +export function isTimestampPart( + value: string, +): value is ValidTimestampExtractParts { + return validTimestampExtractParts.includes( + value as ValidTimestampExtractParts, + ); +} diff --git a/packages/app-builder/src/models/node-evaluation.ts b/packages/app-builder/src/models/node-evaluation.ts index 29a3b339b..83866cea1 100644 --- a/packages/app-builder/src/models/node-evaluation.ts +++ b/packages/app-builder/src/models/node-evaluation.ts @@ -5,7 +5,7 @@ import { } from 'marble-api'; import * as R from 'remeda'; -import { type ConstantType } from './ast-node'; +import { type ConstantType } from './astNode/ast-node'; export type ReturnValue = | { diff --git a/packages/app-builder/src/models/operator-options.ts b/packages/app-builder/src/models/operator-options.ts new file mode 100644 index 000000000..a11f0eb27 --- /dev/null +++ b/packages/app-builder/src/models/operator-options.ts @@ -0,0 +1,36 @@ +import type { undefinedAstNodeName } from './astNode/ast-node'; +import { + isMainAstOperatorFunction, + type MainAstOperatorFunction, +} from './astNode/builder-ast-node-node-operator'; +import type { ValidTimestampExtractParts } from './astNode/time'; +import { + type AggregationFilterOperator, + type AggregatorOperator, + isAggregationFilterOperator, + isAggregatorOperator, + isTimeAddOperator, + isTimestampPart, + type TimeAddOperator, +} from './modale-operators'; + +// This defines all the options that can be used in the operator dropdown. They include both actual AST node identifiers (in the main builder body), +// and options that are used as constant values in child nodes for complex nodes in the modales (e.g. filter, aggregator names for the aggregation node & modale). +export type OperatorOption = + | typeof undefinedAstNodeName + | MainAstOperatorFunction + | AggregationFilterOperator + | TimeAddOperator + | ValidTimestampExtractParts + | AggregatorOperator; + +export function isOperatorOption(value: string): value is OperatorOption { + return ( + value == 'Undefined' || + isMainAstOperatorFunction(value) || + isAggregationFilterOperator(value) || + isTimeAddOperator(value) || + isAggregatorOperator(value) || + isTimestampPart(value) + ); +} diff --git a/packages/app-builder/src/models/scenario-iteration-rule.ts b/packages/app-builder/src/models/scenario-iteration-rule.ts index e6def715a..63f939ebb 100644 --- a/packages/app-builder/src/models/scenario-iteration-rule.ts +++ b/packages/app-builder/src/models/scenario-iteration-rule.ts @@ -4,7 +4,7 @@ import { type UpdateScenarioIterationRuleBodyDto, } from 'marble-api'; -import { adaptAstNode, adaptNodeDto, type AstNode } from './ast-node'; +import { adaptAstNode, adaptNodeDto, type AstNode } from './astNode/ast-node'; export interface ScenarioIterationRule { id: string; diff --git a/packages/app-builder/src/models/scenario-iteration.ts b/packages/app-builder/src/models/scenario-iteration.ts index 6bc2dd4fd..a12b6fc70 100644 --- a/packages/app-builder/src/models/scenario-iteration.ts +++ b/packages/app-builder/src/models/scenario-iteration.ts @@ -4,7 +4,7 @@ import { type UpdateScenarioIterationBody as UpdateScenarioIterationBodyDto, } from 'marble-api'; -import { adaptAstNode, adaptNodeDto, type AstNode } from './ast-node'; +import { adaptAstNode, adaptNodeDto, type AstNode } from './astNode/ast-node'; import { adaptScenarioIterationRule, type ScenarioIterationRule, diff --git a/packages/app-builder/src/models/scenario.ts b/packages/app-builder/src/models/scenario.ts index 97724f0b9..17b5b0840 100644 --- a/packages/app-builder/src/models/scenario.ts +++ b/packages/app-builder/src/models/scenario.ts @@ -5,12 +5,11 @@ import { } from 'marble-api'; import * as z from 'zod'; +import { adaptAstNode, adaptNodeDto } from './astNode/ast-node'; import { - adaptAstNode, - adaptNodeDto, isStringTemplateAstNode, type StringTemplateAstNode, -} from './ast-node'; +} from './astNode/strings'; import { type Outcome, outcomes } from './outcome'; type DecisionToCaseWorkflowType = diff --git a/packages/app-builder/src/repositories/EditorRepository.ts b/packages/app-builder/src/repositories/EditorRepository.ts index 0256cc934..d68e680e9 100644 --- a/packages/app-builder/src/repositories/EditorRepository.ts +++ b/packages/app-builder/src/repositories/EditorRepository.ts @@ -1,23 +1,17 @@ import { type MarbleCoreApi } from '@app-builder/infra/marblecore-api'; +import { adaptAstNode } from '@app-builder/models'; import { - adaptAstNode, type DatabaseAccessAstNode, isDatabaseAccess, isPayload, type PayloadAstNode, -} from '@app-builder/models'; -import { - isOperatorFunction, - type OperatorFunction, -} from '@app-builder/models/editable-operators'; -import * as R from 'remeda'; +} from '@app-builder/models/astNode/data-accessor'; export interface EditorRepository { listAccessors(args: { scenarioId: string }): Promise<{ databaseAccessors: DatabaseAccessAstNode[]; payloadAccessors: PayloadAstNode[]; }>; - listOperators(args: { scenarioId: string }): Promise; } export function makeGetEditorRepository() { @@ -47,17 +41,5 @@ export function makeGetEditorRepository() { payloadAccessors, }; }, - listOperators: async ({ scenarioId }) => { - const { operators_accessors } = - await marbleCoreApiClient.listOperators(scenarioId); - - const operatorFunctions = R.pipe( - operators_accessors, - R.map(({ name }) => name), - R.filter(isOperatorFunction), - ); - - return operatorFunctions; - }, }); } diff --git a/packages/app-builder/src/routes/_builder+/cases+/$caseId._layout.tsx b/packages/app-builder/src/routes/_builder+/cases+/$caseId._layout.tsx index f900afd3a..92a903061 100644 --- a/packages/app-builder/src/routes/_builder+/cases+/$caseId._layout.tsx +++ b/packages/app-builder/src/routes/_builder+/cases+/$caseId._layout.tsx @@ -88,9 +88,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { const accessorsPromise = editor.listAccessors({ scenarioId: decisionDetail.scenario.id, }); - const operatorsPromise = editor.listOperators({ - scenarioId: decisionDetail.scenario.id, - }); const ruleSnoozesPromise = decision .getDecisionActiveSnoozes(id) @@ -103,7 +100,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { pivots: await pivotsPromise, rules: await rulesPromise, accessors: await accessorsPromise, - operators: await operatorsPromise, ruleSnoozes: await ruleSnoozesPromise, }; }), diff --git a/packages/app-builder/src/routes/_builder+/decisions+/$decisionId.tsx b/packages/app-builder/src/routes/_builder+/decisions+/$decisionId.tsx index 6ae904de8..f14a87d1c 100644 --- a/packages/app-builder/src/routes/_builder+/decisions+/$decisionId.tsx +++ b/packages/app-builder/src/routes/_builder+/decisions+/$decisionId.tsx @@ -67,24 +67,18 @@ export async function loader({ request, params }: LoaderFunctionArgs) { scenario.getScenarioIteration({ iterationId: currentDecision.scenario.scenarioIterationId, }), - editor.listOperators({ - scenarioId: currentDecision.scenario.id, - }), editor.listAccessors({ scenarioId: currentDecision.scenario.id, }), dataModelRepository.getDataModel(), customListsRepository.listCustomLists(), - ]).then( - ([scenarioIteration, operators, accessors, dataModel, customLists]) => ({ - rules: scenarioIteration.rules, - databaseAccessors: accessors.databaseAccessors, - payloadAccessors: accessors.payloadAccessors, - operators, - dataModel, - customLists, - }), - ); + ]).then(([scenarioIteration, accessors, dataModel, customLists]) => ({ + rules: scenarioIteration.rules, + databaseAccessors: accessors.databaseAccessors, + payloadAccessors: accessors.payloadAccessors, + dataModel, + customLists, + })); return defer({ decision: currentDecision, diff --git a/packages/app-builder/src/routes/_builder+/scenarios+/$scenarioId+/i+/$iterationId+/_edit-view+/trigger.tsx b/packages/app-builder/src/routes/_builder+/scenarios+/$scenarioId+/i+/$iterationId+/_edit-view+/trigger.tsx index 0e6900284..74c55d192 100644 --- a/packages/app-builder/src/routes/_builder+/scenarios+/$scenarioId+/i+/$iterationId+/_edit-view+/trigger.tsx +++ b/packages/app-builder/src/routes/_builder+/scenarios+/$scenarioId+/i+/$iterationId+/_edit-view+/trigger.tsx @@ -51,10 +51,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { const scenarioId = fromParams(params, 'scenarioId'); - const [operators, accessors, dataModel, customLists] = await Promise.all([ - editor.listOperators({ - scenarioId, - }), + const [accessors, dataModel, customLists] = await Promise.all([ editor.listAccessors({ scenarioId, }), @@ -65,7 +62,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { return json({ databaseAccessors: accessors.databaseAccessors, payloadAccessors: accessors.payloadAccessors, - operators, dataModel, customLists, }); @@ -130,13 +126,8 @@ export default function Trigger() { const scenarioIteration = useCurrentScenarioIteration(); const scenarioValidation = useCurrentScenarioValidation(); - const { - databaseAccessors, - payloadAccessors, - operators, - dataModel, - customLists, - } = useLoaderData(); + const { databaseAccessors, payloadAccessors, dataModel, customLists } = + useLoaderData(); const fetcher = useFetcher(); const editorMode = useEditorMode(); @@ -265,7 +256,6 @@ export default function Trigger() { options={{ databaseAccessors, payloadAccessors, - operators, dataModel, customLists, triggerObjectType: scenario.triggerObjectType, diff --git a/packages/app-builder/src/routes/_builder+/scenarios+/$scenarioId+/i+/$iterationId+/rules.$ruleId.tsx b/packages/app-builder/src/routes/_builder+/scenarios+/$scenarioId+/i+/$iterationId+/rules.$ruleId.tsx index bc2380bec..359ac1702 100644 --- a/packages/app-builder/src/routes/_builder+/scenarios+/$scenarioId+/i+/$iterationId+/rules.$ruleId.tsx +++ b/packages/app-builder/src/routes/_builder+/scenarios+/$scenarioId+/i+/$iterationId+/rules.$ruleId.tsx @@ -10,15 +10,13 @@ import { Highlight } from '@app-builder/components/Highlight'; import { setToastMessage } from '@app-builder/components/MarbleToaster'; import { AstBuilder } from '@app-builder/components/Scenario/AstBuilder'; import { EvaluationErrors } from '@app-builder/components/Scenario/ScenarioValidationError'; +import { type AstNode, NewEmptyRuleAstNode } from '@app-builder/models'; import { - type AstNode, type DatabaseAccessAstNode, - NewEmptyRuleAstNode, type PayloadAstNode, -} from '@app-builder/models'; +} from '@app-builder/models/astNode/data-accessor'; import { type CustomList } from '@app-builder/models/custom-list'; import { type DataModel } from '@app-builder/models/data-model'; -import { type OperatorFunction } from '@app-builder/models/editable-operators'; import { type ScenarioIterationRule } from '@app-builder/models/scenario-iteration-rule'; import { useCurrentScenario } from '@app-builder/routes/_builder+/scenarios+/$scenarioId+/_layout'; import { DeleteRule } from '@app-builder/routes/ressources+/scenarios+/$scenarioId+/$iterationId+/rules+/delete'; @@ -85,10 +83,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { const scenarioId = fromParams(params, 'scenarioId'); - const operatorsPromise = editor.listOperators({ - scenarioId, - }); - const accessorsPromise = editor.listAccessors({ scenarioId, }); @@ -99,7 +93,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { return json({ databaseAccessors: (await accessorsPromise).databaseAccessors, payloadAccessors: (await accessorsPromise).payloadAccessors, - operators: await operatorsPromise, dataModel: await dataModelPromise, customLists, }); @@ -184,13 +177,8 @@ export async function action({ request, params }: ActionFunctionArgs) { export default function RuleDetail() { const { t } = useTranslation(handle.i18n); - const { - databaseAccessors, - payloadAccessors, - operators, - dataModel, - customLists, - } = useLoaderData(); + const { databaseAccessors, payloadAccessors, dataModel, customLists } = + useLoaderData(); const iterationId = useParam('iterationId'); const scenarioId = useParam('scenarioId'); @@ -218,7 +206,6 @@ export default function RuleDetail() { const options = { databaseAccessors, payloadAccessors, - operators, dataModel, customLists, triggerObjectType: scenario.triggerObjectType, @@ -267,7 +254,6 @@ function RuleViewContent({ options: { databaseAccessors: DatabaseAccessAstNode[]; payloadAccessors: PayloadAstNode[]; - operators: OperatorFunction[]; dataModel: DataModel; customLists: CustomList[]; triggerObjectType: string; @@ -321,7 +307,6 @@ function RuleEditContent({ options: { databaseAccessors: DatabaseAccessAstNode[]; payloadAccessors: PayloadAstNode[]; - operators: OperatorFunction[]; dataModel: DataModel; customLists: CustomList[]; triggerObjectType: string; diff --git a/packages/app-builder/src/routes/_builder+/scenarios+/$scenarioId+/workflow.tsx b/packages/app-builder/src/routes/_builder+/scenarios+/$scenarioId+/workflow.tsx index f631a5592..6318979b3 100644 --- a/packages/app-builder/src/routes/_builder+/scenarios+/$scenarioId+/workflow.tsx +++ b/packages/app-builder/src/routes/_builder+/scenarios+/$scenarioId+/workflow.tsx @@ -80,10 +80,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { (pivot) => pivot.baseTable === currentScenario?.triggerObjectType, ); - const [operators, accessors, dataModel, customLists] = await Promise.all([ - editor.listOperators({ - scenarioId, - }), + const [accessors, dataModel, customLists] = await Promise.all([ editor.listAccessors({ scenarioId, }), @@ -101,7 +98,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { builderOptions: { databaseAccessors: accessors.databaseAccessors, payloadAccessors: accessors.payloadAccessors, - operators, dataModel, customLists, }, diff --git a/packages/app-builder/src/services/ast-node/getAstNodeDataType.ts b/packages/app-builder/src/services/ast-node/getAstNodeDataType.ts index 28ea16d4c..fdd487330 100644 --- a/packages/app-builder/src/services/ast-node/getAstNodeDataType.ts +++ b/packages/app-builder/src/services/ast-node/getAstNodeDataType.ts @@ -2,16 +2,20 @@ import { type AstNode, type DataModel, type DataType, - isConstant, - isDataAccessorAstNode, + type TableModel, +} from '@app-builder/models'; +import { isConstant } from '@app-builder/models/astNode/constant'; +import { isDataAccessorAstNode } from '@app-builder/models/astNode/data-accessor'; +import { isIsMultipleOf } from '@app-builder/models/astNode/multiple-of'; +import { isFuzzyMatchComparator, - isIsMultipleOf, isStringTemplateAstNode, +} from '@app-builder/models/astNode/strings'; +import { isTimeAdd, isTimeNow, isTimestampExtract, - type TableModel, -} from '@app-builder/models'; +} from '@app-builder/models/astNode/time'; import { dateTimeDataTypeSchema } from '@app-builder/utils/schema/dataTypeSchema'; import * as R from 'remeda'; diff --git a/packages/app-builder/src/services/ast-node/getAstNodeDisplayName.ts b/packages/app-builder/src/services/ast-node/getAstNodeDisplayName.ts index 05a2eebbf..3dcb3994e 100644 --- a/packages/app-builder/src/services/ast-node/getAstNodeDisplayName.ts +++ b/packages/app-builder/src/services/ast-node/getAstNodeDisplayName.ts @@ -1,29 +1,34 @@ +import { type AstNode, isUndefinedAstNode } from '@app-builder/models'; import { type AggregationAstNode, - type AstNode, - type FuzzyMatchComparatorAstNode, isAggregation, - isConstant, - isCustomListAccess, +} from '@app-builder/models/astNode/aggregation'; +import { isConstant } from '@app-builder/models/astNode/constant'; +import { isCustomListAccess } from '@app-builder/models/astNode/custom-list'; +import { isDatabaseAccess, - isFuzzyMatchComparator, + isPayload, +} from '@app-builder/models/astNode/data-accessor'; +import { isIsMultipleOf, type IsMultipleOfAstNode, - isPayload, +} from '@app-builder/models/astNode/multiple-of'; +import { + type FuzzyMatchComparatorAstNode, + isFuzzyMatchComparator, isStringTemplateAstNode, + type StringTemplateAstNode, +} from '@app-builder/models/astNode/strings'; +import { isTimeAdd, isTimeNow, isTimestampExtract, - isUndefinedAstNode, - type StringTemplateAstNode, type TimeAddAstNode, type TimestampExtractAstNode, -} from '@app-builder/models'; +} from '@app-builder/models/astNode/time'; import { type CustomList } from '@app-builder/models/custom-list'; -import { - getOperatorName, - isAggregatorOperator, -} from '@app-builder/models/editable-operators'; +import { getOperatorName } from '@app-builder/models/get-operator-name'; +import { isAggregatorOperator } from '@app-builder/models/modale-operators'; import { formatNumber } from '@app-builder/utils/format'; import { type TFunction } from 'i18next'; import * as R from 'remeda'; diff --git a/packages/app-builder/src/services/ast-node/getAstNodeOperandType.ts b/packages/app-builder/src/services/ast-node/getAstNodeOperandType.ts index 98df59173..bc29e3445 100644 --- a/packages/app-builder/src/services/ast-node/getAstNodeOperandType.ts +++ b/packages/app-builder/src/services/ast-node/getAstNodeOperandType.ts @@ -2,19 +2,23 @@ import { type AstNode, type DataModel, type EnumValue, - isAggregation, - isConstant, - isCustomListAccess, - isDataAccessorAstNode, + isUndefinedAstNode, + type TableModel, +} from '@app-builder/models'; +import { isAggregation } from '@app-builder/models/astNode/aggregation'; +import { isConstant } from '@app-builder/models/astNode/constant'; +import { isCustomListAccess } from '@app-builder/models/astNode/custom-list'; +import { isDataAccessorAstNode } from '@app-builder/models/astNode/data-accessor'; +import { isIsMultipleOf } from '@app-builder/models/astNode/multiple-of'; +import { isFuzzyMatchComparator, - isIsMultipleOf, isStringTemplateAstNode, +} from '@app-builder/models/astNode/strings'; +import { isTimeAdd, isTimeNow, isTimestampExtract, - isUndefinedAstNode, - type TableModel, -} from '@app-builder/models'; +} from '@app-builder/models/astNode/time'; import { type OperandType } from '@app-builder/models/operand-type'; import * as R from 'remeda'; diff --git a/packages/app-builder/src/services/ast-node/getCustomListAccessCustomList.ts b/packages/app-builder/src/services/ast-node/getCustomListAccessCustomList.ts index b715e4683..4b8aec685 100644 --- a/packages/app-builder/src/services/ast-node/getCustomListAccessCustomList.ts +++ b/packages/app-builder/src/services/ast-node/getCustomListAccessCustomList.ts @@ -1,4 +1,4 @@ -import { type CustomListAccessAstNode } from '@app-builder/models'; +import { type CustomListAccessAstNode } from '@app-builder/models/astNode/custom-list'; import { type CustomList } from '@app-builder/models/custom-list'; import * as R from 'remeda'; diff --git a/packages/app-builder/src/services/ast-node/getDataAccessorAstNodeField.ts b/packages/app-builder/src/services/ast-node/getDataAccessorAstNodeField.ts index 7e6c5c317..0f39f985a 100644 --- a/packages/app-builder/src/services/ast-node/getDataAccessorAstNodeField.ts +++ b/packages/app-builder/src/services/ast-node/getDataAccessorAstNodeField.ts @@ -1,13 +1,15 @@ import { - type DataAccessorAstNode, type DataModel, type DataModelField, findDataModelField, findDataModelTable, - isDatabaseAccess, - isPayload, type TableModel, } from '@app-builder/models'; +import { + type DataAccessorAstNode, + isDatabaseAccess, + isPayload, +} from '@app-builder/models/astNode/data-accessor'; import { assertNever } from 'typescript-utils'; export function getDataAccessorAstNodeField( diff --git a/packages/app-builder/src/services/editor/ast-editor.tsx b/packages/app-builder/src/services/editor/ast-editor.tsx index bc9c8e69c..4fc3d8781 100644 --- a/packages/app-builder/src/services/editor/ast-editor.tsx +++ b/packages/app-builder/src/services/editor/ast-editor.tsx @@ -1,8 +1,6 @@ import { type AstNode, NewUndefinedAstNode } from '@app-builder/models'; -import { - isBinaryMainAstOperatorFunction, - isUnaryMainAstOperatorFunction, -} from '@app-builder/models/editable-operators'; +import { isUnaryMainAstOperatorFunction } from '@app-builder/models/astNode/builder-ast-node-node-operator'; +import { isBinaryMainAstOperatorFunction } from '@app-builder/models/astNode/builder-ast-node-node-operator'; import { NewNodeEvaluation, type NodeEvaluation, diff --git a/packages/app-builder/src/services/editor/coerceToConstantAstNode.spec.ts b/packages/app-builder/src/services/editor/coerceToConstantAstNode.spec.ts index 87c34fbac..ca71a0c36 100644 --- a/packages/app-builder/src/services/editor/coerceToConstantAstNode.spec.ts +++ b/packages/app-builder/src/services/editor/coerceToConstantAstNode.spec.ts @@ -1,4 +1,5 @@ -import { NewAstNode, NewConstantAstNode } from '@app-builder/models'; +import { NewAstNode } from '@app-builder/models'; +import { NewConstantAstNode } from '@app-builder/models/astNode/constant'; import { coerceToConstantAstNode, diff --git a/packages/app-builder/src/services/editor/coerceToConstantAstNode.ts b/packages/app-builder/src/services/editor/coerceToConstantAstNode.ts index 579e05d70..8e926260d 100644 --- a/packages/app-builder/src/services/editor/coerceToConstantAstNode.ts +++ b/packages/app-builder/src/services/editor/coerceToConstantAstNode.ts @@ -1,4 +1,7 @@ -import { type ConstantAstNode, NewConstantAstNode } from '@app-builder/models'; +import { + type ConstantAstNode, + NewConstantAstNode, +} from '@app-builder/models/astNode/constant'; import * as R from 'remeda'; export interface CoerceToConstantAstNodeOptions { diff --git a/packages/app-builder/src/services/editor/getEnumOptionsFromNeighbour.ts b/packages/app-builder/src/services/editor/getEnumOptionsFromNeighbour.ts index d6b0f63c8..22d7dc349 100644 --- a/packages/app-builder/src/services/editor/getEnumOptionsFromNeighbour.ts +++ b/packages/app-builder/src/services/editor/getEnumOptionsFromNeighbour.ts @@ -2,9 +2,9 @@ import { type AstNode, type DataModel, type EnumValue, - isDataAccessorAstNode, type TableModel, } from '@app-builder/models'; +import { isDataAccessorAstNode } from '@app-builder/models/astNode/data-accessor'; import { getDataAccessorAstNodeField } from '../ast-node/getDataAccessorAstNodeField'; diff --git a/packages/app-builder/src/services/editor/options.tsx b/packages/app-builder/src/services/editor/options.tsx index 62a5b5c2b..3089883c4 100644 --- a/packages/app-builder/src/services/editor/options.tsx +++ b/packages/app-builder/src/services/editor/options.tsx @@ -1,19 +1,22 @@ +import { type AstNode, NewUndefinedAstNode } from '@app-builder/models'; +import { NewAggregatorAstNode } from '@app-builder/models/astNode/aggregation'; +import { NewConstantAstNode } from '@app-builder/models/astNode/constant'; import { - type AstNode, type CustomListAccessAstNode, + NewCustomListAstNode, +} from '@app-builder/models/astNode/custom-list'; +import { type DataAccessorAstNode, type DatabaseAccessAstNode, - NewAggregatorAstNode, - NewConstantAstNode, - NewCustomListAstNode, - NewFuzzyMatchComparatorAstNode, - NewIsMultipleOfAstNode, + type PayloadAstNode, +} from '@app-builder/models/astNode/data-accessor'; +import { NewIsMultipleOfAstNode } from '@app-builder/models/astNode/multiple-of'; +import { NewFuzzyMatchComparatorAstNode } from '@app-builder/models/astNode/strings'; +import { NewTimeAddAstNode, NewTimeNowAstNode, NewTimestampExtractAstNode, - NewUndefinedAstNode, - type PayloadAstNode, -} from '@app-builder/models/ast-node'; +} from '@app-builder/models/astNode/time'; import { type CustomList } from '@app-builder/models/custom-list'; import { type DataModel, @@ -23,12 +26,7 @@ import { findDataModelTableByName, type TableModel, } from '@app-builder/models/data-model'; -import { - aggregatorOperators, - isMainAstOperatorFunction, - type OperatorFunction, - sortMainAstOperatorFunctions, -} from '@app-builder/models/editable-operators'; +import { aggregatorOperators } from '@app-builder/models/modale-operators'; import { type OperandType } from '@app-builder/models/operand-type'; import { type OperandOption } from '@app-builder/types/operand-options'; import { createSimpleContext } from '@app-builder/utils/create-context'; @@ -54,9 +52,6 @@ const DataModelContext = createSimpleContext('DataModel'); const CustomLists = createSimpleContext('CustomLists'); -const OperatorFunctions = - createSimpleContext('OperatorFunctions'); - const TriggerObjectTable = createSimpleContext('TriggerObjectTable'); @@ -81,7 +76,6 @@ export const useDatabaseAccessors = DatabaseAccessors.useValue; export const usePayloadAccessors = PayloadAccessors.useValue; export const useDataModel = DataModelContext.useValue; export const useCustomLists = CustomLists.useValue; -export const useOperatorFunctions = OperatorFunctions.useValue; export const useTriggerObjectTable = TriggerObjectTable.useValue; export const useGetAstNodeDataType = GetAstNodeDataTypeContext.useValue; export const useGetAstNodeDisplayName = GetAstNodeDisplayNameContext.useValue; @@ -91,7 +85,6 @@ export function OptionsProvider({ children, databaseAccessors, payloadAccessors, - operators, dataModel, customLists, triggerObjectType, @@ -99,7 +92,6 @@ export function OptionsProvider({ children: React.ReactNode; databaseAccessors: DatabaseAccessAstNode[]; payloadAccessors: PayloadAstNode[]; - operators: OperatorFunction[]; dataModel: DataModel; customLists: CustomList[]; triggerObjectType: string; @@ -147,23 +139,21 @@ export function OptionsProvider({ - - - + + - - - {children} - - - - - + {children} + + + + @@ -285,17 +275,6 @@ export function useOperandOptions(enumValues?: EnumValue[]) { ]); } -export function useMainAstOperatorFunctions() { - const operators = useOperatorFunctions(); - return React.useMemo( - () => - operators - .filter(isMainAstOperatorFunction) - .sort(sortMainAstOperatorFunctions), - [operators], - ); -} - export function useCustomListAccessCustomList( astNode: CustomListAccessAstNode, ) { diff --git a/packages/app-builder/src/services/validation/ast-node-validation.ts b/packages/app-builder/src/services/validation/ast-node-validation.ts index 683207ef7..333c490e8 100644 --- a/packages/app-builder/src/services/validation/ast-node-validation.ts +++ b/packages/app-builder/src/services/validation/ast-node-validation.ts @@ -1,4 +1,5 @@ -import { type AstNode, isLeafOperandAstNode } from '@app-builder/models'; +import { type AstNode } from '@app-builder/models'; +import { isLeafOperandAstNode } from '@app-builder/models/astNode/builder-ast-node'; import { type EvaluationError } from '@app-builder/models/node-evaluation'; import { type PathSegment, type Tree } from '@app-builder/utils/tree'; import * as R from 'remeda'; diff --git a/packages/marble-api/openapis/marblecore-api.yaml b/packages/marble-api/openapis/marblecore-api.yaml index 84fe657fd..31058999b 100644 --- a/packages/marble-api/openapis/marblecore-api.yaml +++ b/packages/marble-api/openapis/marblecore-api.yaml @@ -2734,40 +2734,6 @@ paths: $ref: '#/components/responses/401' '403': $ref: '#/components/responses/403' - /editor/{scenarioId}/operators: - get: - tags: - - Editor - summary: List all operators - operationId: listOperators - security: - - bearerAuth: [] - parameters: - - name: scenarioId - description: ID of the scenario - in: path - required: true - schema: - type: string - format: uuid - responses: - '200': - description: The list of operators - content: - application/json: - schema: - type: object - required: - - operators_accessors - properties: - operators_accessors: - type: array - items: - $ref: '#/components/schemas/FuncAttributes' - '401': - $ref: '#/components/responses/401' - '403': - $ref: '#/components/responses/403' /inboxes: get: tags: diff --git a/packages/marble-api/src/generated/marblecore-api.ts b/packages/marble-api/src/generated/marblecore-api.ts index a37d0445b..05dc78bb5 100644 --- a/packages/marble-api/src/generated/marblecore-api.ts +++ b/packages/marble-api/src/generated/marblecore-api.ts @@ -623,9 +623,6 @@ export type CreateOrganizationBodyDto = { export type UpdateOrganizationBodyDto = { default_scenario_timezone?: string; }; -export type FuncAttributes = { - name: string; -}; export type InboxUserDto = { id: string; inbox_id: string; @@ -2497,25 +2494,6 @@ export function listIdentifiers(scenarioId: string, opts?: Oazapfts.RequestOpts) ...opts })); } -/** - * List all operators - */ -export function listOperators(scenarioId: string, opts?: Oazapfts.RequestOpts) { - return oazapfts.ok(oazapfts.fetchJson<{ - status: 200; - data: { - operators_accessors: FuncAttributes[]; - }; - } | { - status: 401; - data: string; - } | { - status: 403; - data: string; - }>(`/editor/${encodeURIComponent(scenarioId)}/operators`, { - ...opts - })); -} /** * List all inboxes */