fix: Prevent switching if nodes are attached to multi-branch

This commit is contained in:
Dries Augustyns
2026-03-11 15:30:00 +01:00
parent 2c3e8970cb
commit c32b54f588
+80 -16
View File
@@ -301,9 +301,7 @@ export default function WorkflowEditorPage() {
const branch = (config as any).branches?.find((b: any) => b.id === id); const branch = (config as any).branches?.find((b: any) => b.id === id);
return branch?.name || id; return branch?.name || id;
}); });
errors.push( errors.push(`"${step.name}" condition step is missing connections for: ${branchNames.join(', ')}`);
`"${step.name}" condition step is missing connections for: ${branchNames.join(', ')}`,
);
} else { } else {
errors.push(`"${step.name}" condition step must have both YES and NO branches connected`); errors.push(`"${step.name}" condition step must have both YES and NO branches connected`);
} }
@@ -1871,6 +1869,56 @@ function EditStepDialog({step, workflowId, open, onOpenChange, onSuccess}: EditS
const [conditionMode, setConditionMode] = useState<'binary' | 'multi'>(() => { const [conditionMode, setConditionMode] = useState<'binary' | 'multi'>(() => {
return config?.mode === 'multi' ? 'multi' : 'binary'; return config?.mode === 'multi' ? 'multi' : 'binary';
}); });
// Helper to check if switching from multi to binary is safe
const hasMultiBranchConnections = () => {
if (step.type !== 'CONDITION') return false;
if (config?.mode !== 'multi') return false;
const transitions = (step as any).outgoingTransitions || [];
// Check if any transition has a branch that's not 'yes' or 'no' (multi-branch specific)
return transitions.some((t: any) => {
const condition = t.condition;
if (condition && typeof condition === 'object' && 'branch' in condition) {
const branch = condition.branch as string;
// Multi-branch specific branches (not the simple if/else branches)
return branch !== 'yes' && branch !== 'no';
}
return false;
});
};
// Helper to check if switching from binary to multi is safe
const hasBinaryConnections = () => {
if (step.type !== 'CONDITION') return false;
if (config?.mode === 'multi') return false;
const transitions = (step as any).outgoingTransitions || [];
// Check if any transition has 'yes' or 'no' branches (binary-specific)
return transitions.some((t: any) => {
const condition = t.condition;
if (condition && typeof condition === 'object' && 'branch' in condition) {
const branch = condition.branch as string;
return branch === 'yes' || branch === 'no';
}
return false;
});
};
// Safe mode change handler
const handleModeChange = (newMode: 'binary' | 'multi') => {
// If switching from multi to binary, check for multi-branch connections
if (conditionMode === 'multi' && newMode === 'binary' && hasMultiBranchConnections()) {
// Don't allow the switch - user needs to disconnect branches first
return;
}
// If switching from binary to multi, check for binary connections
if (conditionMode === 'binary' && newMode === 'multi' && hasBinaryConnections()) {
// Don't allow the switch - user needs to disconnect branches first
return;
}
setConditionMode(newMode);
};
const [conditionField, setConditionField] = useState(() => { const [conditionField, setConditionField] = useState(() => {
if (!config?.field) return ''; if (!config?.field) return '';
// Handle case where field is an object like {field: 'email', type: 'string'} (legacy format) // Handle case where field is an object like {field: 'email', type: 'string'} (legacy format)
@@ -2402,22 +2450,28 @@ function EditStepDialog({step, workflowId, open, onOpenChange, onSuccess}: EditS
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
type="button" type="button"
onClick={() => setConditionMode('binary')} onClick={() => handleModeChange('binary')}
disabled={conditionMode === 'multi' && hasMultiBranchConnections()}
className={`flex-1 px-3 py-2 rounded-lg border-2 text-sm font-medium transition-all ${ className={`flex-1 px-3 py-2 rounded-lg border-2 text-sm font-medium transition-all ${
conditionMode === 'binary' conditionMode === 'binary'
? 'border-purple-500 bg-purple-50 text-purple-700' ? 'border-purple-500 bg-purple-50 text-purple-700'
: 'border-neutral-200 text-neutral-600 hover:border-neutral-300' : conditionMode === 'multi' && hasMultiBranchConnections()
? 'border-neutral-200 text-neutral-400 bg-neutral-50 cursor-not-allowed opacity-50'
: 'border-neutral-200 text-neutral-600 hover:border-neutral-300'
}`} }`}
> >
Simple (If/Else) Simple (If/Else)
</button> </button>
<button <button
type="button" type="button"
onClick={() => setConditionMode('multi')} onClick={() => handleModeChange('multi')}
disabled={conditionMode === 'binary' && hasBinaryConnections()}
className={`flex-1 px-3 py-2 rounded-lg border-2 text-sm font-medium transition-all ${ className={`flex-1 px-3 py-2 rounded-lg border-2 text-sm font-medium transition-all ${
conditionMode === 'multi' conditionMode === 'multi'
? 'border-purple-500 bg-purple-50 text-purple-700' ? 'border-purple-500 bg-purple-50 text-purple-700'
: 'border-neutral-200 text-neutral-600 hover:border-neutral-300' : conditionMode === 'binary' && hasBinaryConnections()
? 'border-neutral-200 text-neutral-400 bg-neutral-50 cursor-not-allowed opacity-50'
: 'border-neutral-200 text-neutral-600 hover:border-neutral-300'
}`} }`}
> >
Multi-branch (Switch) Multi-branch (Switch)
@@ -2428,7 +2482,23 @@ function EditStepDialog({step, workflowId, open, onOpenChange, onSuccess}: EditS
? 'Evaluates a single condition with Yes/No paths' ? 'Evaluates a single condition with Yes/No paths'
: 'Match a field against multiple values, each routing to its own branch'} : 'Match a field against multiple values, each routing to its own branch'}
</p> </p>
{hasMultiBranchConnections() && (
<div className="mt-2 p-2 bg-red-50 border border-red-200 rounded-lg text-xs text-red-800">
<AlertTriangle className="h-3 w-3 inline mr-1" />
Cannot switch to simple mode: branches have connected nodes. Disconnect all branch connections
first.
</div>
)}
{hasBinaryConnections() && (
<div className="mt-2 p-2 bg-red-50 border border-red-200 rounded-lg text-xs text-red-800">
<AlertTriangle className="h-3 w-3 inline mr-1" />
Cannot switch to multi-branch mode: Yes/No branches have connected nodes. Disconnect all branch
connections first.
</div>
)}
{conditionMode !== (config?.mode === 'multi' ? 'multi' : 'binary') && {conditionMode !== (config?.mode === 'multi' ? 'multi' : 'binary') &&
!hasMultiBranchConnections() &&
!hasBinaryConnections() &&
(step as any).outgoingTransitions && (step as any).outgoingTransitions &&
(step as any).outgoingTransitions.length > 0 && ( (step as any).outgoingTransitions.length > 0 && (
<div className="mt-2 p-2 bg-amber-50 border border-amber-200 rounded-lg text-xs text-amber-800"> <div className="mt-2 p-2 bg-amber-50 border border-amber-200 rounded-lg text-xs text-amber-800">
@@ -2574,9 +2644,7 @@ function EditStepDialog({step, workflowId, open, onOpenChange, onSuccess}: EditS
required required
className="mt-1.5" className="mt-1.5"
/> />
<p className="text-xs text-neutral-500 mt-1.5"> <p className="text-xs text-neutral-500 mt-1.5">Select a date and time to compare against</p>
Select a date and time to compare against
</p>
</> </>
) : ( ) : (
<> <>
@@ -2589,9 +2657,7 @@ function EditStepDialog({step, workflowId, open, onOpenChange, onSuccess}: EditS
placeholder="e.g., premium, active" placeholder="e.g., premium, active"
className="mt-1.5" className="mt-1.5"
/> />
<p className="text-xs text-neutral-500 mt-1.5"> <p className="text-xs text-neutral-500 mt-1.5">Enter the text value to compare against</p>
Enter the text value to compare against
</p>
</> </>
)} )}
</div> </div>
@@ -2618,9 +2684,7 @@ function EditStepDialog({step, workflowId, open, onOpenChange, onSuccess}: EditS
{conditionBranches.length > 1 && ( {conditionBranches.length > 1 && (
<button <button
type="button" type="button"
onClick={() => onClick={() => setConditionBranches(prev => prev.filter(b => b.id !== branch.id))}
setConditionBranches(prev => prev.filter(b => b.id !== branch.id))
}
className="text-xs text-red-500 hover:text-red-700" className="text-xs text-red-500 hover:text-red-700"
> >
<Trash2 className="h-3.5 w-3.5" /> <Trash2 className="h-3.5 w-3.5" />