feat: implement smart parameters (branch, case) for multi-output nodes (Phase 1, Task 2)

Add intuitive semantic parameters for working with IF and Switch nodes:
- branch='true'|'false' for IF nodes (maps to sourceOutput)
- case=N for Switch nodes (maps to sourceIndex)
- Smart parameters resolve to technical parameters automatically
- Explicit parameters always override smart parameters

Implementation:
- Added branch and case parameters to AddConnectionOperation and RewireConnectionOperation interfaces
- Created resolveSmartParameters() helper method to map semantic to technical parameters
- Updated applyAddConnection() to use smart parameter resolution
- Updated applyRewireConnection() to use smart parameter resolution
- Updated validateRewireConnection() to validate with resolved smart parameters

Tests:
- Added 8 comprehensive tests for smart parameters feature
- All 141 workflow diff engine tests passing
- Coverage: 91.7% overall

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
czlonkowski
2025-10-05 23:30:49 +02:00
parent f9194ee74c
commit ee125c52f8
3 changed files with 412 additions and 6 deletions

View File

@@ -523,8 +523,10 @@ export class WorkflowDiffEngine {
return `"To" node not found: "${operation.to}". Available nodes: ${availableNodes}. Tip: Use node ID for names with special characters.`;
}
// Resolve smart parameters (branch, case) before validating connections
const { sourceOutput } = this.resolveSmartParameters(workflow, operation);
// Validate that connection from source to "from" exists
const sourceOutput = operation.sourceOutput || 'main';
const connections = workflow.connections[sourceNode.name]?.[sourceOutput];
if (!connections) {
return `No connections found from "${sourceNode.name}" on output "${sourceOutput}"`;
@@ -631,16 +633,48 @@ export class WorkflowDiffEngine {
node.disabled = true;
}
/**
* Resolve smart parameters (branch, case) to technical parameters
* Phase 1 UX improvement: Semantic parameters for multi-output nodes
*/
private resolveSmartParameters(
workflow: Workflow,
operation: AddConnectionOperation | RewireConnectionOperation
): { sourceOutput: string; sourceIndex: number } {
const sourceNode = this.findNode(workflow, operation.source, operation.source);
// Start with explicit values or defaults
let sourceOutput = operation.sourceOutput ?? 'main';
let sourceIndex = operation.sourceIndex ?? 0;
// Smart parameter: branch (for IF nodes)
if (operation.branch && !operation.sourceOutput) {
// Only apply if sourceOutput not explicitly set
if (sourceNode?.type === 'n8n-nodes-base.if') {
sourceOutput = operation.branch; // 'true' or 'false'
}
}
// Smart parameter: case (for Switch nodes)
if (operation.case !== undefined && operation.sourceIndex === undefined) {
// Only apply if sourceIndex not explicitly set
sourceIndex = operation.case;
}
return { sourceOutput, sourceIndex };
}
// Connection operation appliers
private applyAddConnection(workflow: Workflow, operation: AddConnectionOperation): void {
const sourceNode = this.findNode(workflow, operation.source, operation.source);
const targetNode = this.findNode(workflow, operation.target, operation.target);
if (!sourceNode || !targetNode) return;
// Resolve smart parameters (branch, case) to technical parameters
const { sourceOutput, sourceIndex } = this.resolveSmartParameters(workflow, operation);
// Use nullish coalescing to properly handle explicit 0 values
const sourceOutput = operation.sourceOutput ?? 'main';
const targetInput = operation.targetInput ?? 'main';
const sourceIndex = operation.sourceIndex ?? 0;
const targetIndex = operation.targetIndex ?? 0;
// Initialize source node connections object
@@ -763,12 +797,15 @@ export class WorkflowDiffEngine {
* @param operation - Rewire operation specifying source, from, and to
*/
private applyRewireConnection(workflow: Workflow, operation: RewireConnectionOperation): void {
// Resolve smart parameters (branch, case) to technical parameters
const { sourceOutput, sourceIndex } = this.resolveSmartParameters(workflow, operation);
// First, remove the old connection (source → from)
this.applyRemoveConnection(workflow, {
type: 'removeConnection',
source: operation.source,
target: operation.from,
sourceOutput: operation.sourceOutput,
sourceOutput: sourceOutput,
targetInput: operation.targetInput
});
@@ -777,9 +814,9 @@ export class WorkflowDiffEngine {
type: 'addConnection',
source: operation.source,
target: operation.to,
sourceOutput: operation.sourceOutput,
sourceOutput: sourceOutput,
targetInput: operation.targetInput,
sourceIndex: operation.sourceIndex,
sourceIndex: sourceIndex,
targetIndex: 0 // Default target index for new connection
});
}