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
*/