feat: Add multi-branch workflow conditions (switch/case)
Add a `mode: 'multi'` variant to CONDITION steps that allows matching a field against multiple values, each routing to its own named branch. Branches are evaluated in order; first match wins. A `default` branch catches unmatched contacts. - Schema: z.union for legacy binary + multi-branch condition configs - Backend: executeCondition handles multi-branch evaluation with stable branch IDs, falls back to default when no branch matches - Builder: Dynamic branch rendering with per-branch colors/labels, dagre layout spreads branches horizontally - Config UI: Mode toggle (Simple If/Else vs Multi-branch Switch), dynamic branch list with add/remove - Tests: 8 new multi-branch tests covering matching, ordering, default fallback, mixed operators, and transition routing - Full backward compatibility: all changes gated on config.mode === 'multi' Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
@@ -727,7 +727,7 @@ export class WorkflowExecutionService {
|
||||
_stepExecution: WorkflowStepExecution,
|
||||
config: StepConfig,
|
||||
): Promise<StepResult> {
|
||||
const {field, operator, value} = WorkflowStepConfigSchemas.condition.parse(config);
|
||||
const parsed = WorkflowStepConfigSchemas.condition.parse(config);
|
||||
|
||||
// Get the value to evaluate
|
||||
const contact = execution.contact;
|
||||
@@ -743,7 +743,7 @@ export class WorkflowExecutionService {
|
||||
// - data.firstName, data.lastName, etc.
|
||||
// - workflow.* (execution context - alias for event data)
|
||||
// - event.* (event data that triggered the workflow)
|
||||
const actualValue = this.resolveField(field, {
|
||||
const fieldData = {
|
||||
contact: {
|
||||
email: contact.email,
|
||||
subscribed: contact.subscribed,
|
||||
@@ -751,9 +751,39 @@ export class WorkflowExecutionService {
|
||||
data: contactData,
|
||||
workflow: context,
|
||||
event: context, // Alias for easier access to event data
|
||||
});
|
||||
};
|
||||
|
||||
// Evaluate the condition
|
||||
// Multi-branch mode (switch/case)
|
||||
if ('mode' in parsed && parsed.mode === 'multi') {
|
||||
const actualValue = this.resolveField(parsed.field, fieldData);
|
||||
|
||||
for (const branch of parsed.branches) {
|
||||
if (this.evaluateCondition(actualValue, branch.operator, branch.value)) {
|
||||
return {
|
||||
field: parsed.field,
|
||||
mode: 'multi',
|
||||
matchedBranch: branch.name,
|
||||
actualValue,
|
||||
branch: branch.id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// No branch matched — use default
|
||||
return {
|
||||
field: parsed.field,
|
||||
mode: 'multi',
|
||||
matchedBranch: 'default',
|
||||
actualValue,
|
||||
branch: 'default',
|
||||
};
|
||||
}
|
||||
|
||||
// Legacy binary mode (if/else)
|
||||
const field = parsed.field;
|
||||
const operator = 'operator' in parsed ? parsed.operator : 'equals';
|
||||
const value = 'value' in parsed ? parsed.value : undefined;
|
||||
const actualValue = this.resolveField(field, fieldData);
|
||||
const result = this.evaluateCondition(actualValue, operator, value);
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user