diff --git a/packages/editor-ui/src/composables/useRunWorkflow.test.ts b/packages/editor-ui/src/composables/useRunWorkflow.test.ts index 2908e50820dbd..043747dae480c 100644 --- a/packages/editor-ui/src/composables/useRunWorkflow.test.ts +++ b/packages/editor-ui/src/composables/useRunWorkflow.test.ts @@ -9,7 +9,7 @@ import { useToast } from '@/composables/useToast'; import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers'; import { useNodeHelpers } from '@/composables/useNodeHelpers'; import { useRouter } from 'vue-router'; -import type { IPinData, IRunData, Workflow } from 'n8n-workflow'; +import { ExpressionError, type IPinData, type IRunData, type Workflow } from 'n8n-workflow'; vi.mock('@/stores/n8nRoot.store', () => ({ useRootStore: vi.fn().mockReturnValue({ pushConnectionActive: true }), @@ -285,5 +285,34 @@ describe('useRunWorkflow({ router })', () => { expect(result.startNodeNames).toContain('node1'); expect(result.runData).toBeUndefined(); }); + + it('should rerun failed parent nodes, adding them to the returned list of start nodes and not adding their result to runData', () => { + const { consolidateRunDataAndStartNodes } = useRunWorkflow({ router }); + const directParentNodes = ['node1']; + const runData = { + node1: [ + { + error: new ExpressionError('error'), + }, + ], + } as unknown as IRunData; + const workflowMock = { + getParentNodes: vi.fn().mockReturnValue([]), + nodes: { + node1: { disabled: false }, + node2: { disabled: false }, + }, + } as unknown as Workflow; + + const result = consolidateRunDataAndStartNodes( + directParentNodes, + runData, + undefined, + workflowMock, + ); + + expect(result.startNodeNames).toContain('node1'); + expect(result.runData).toEqual(undefined); + }); }); }); diff --git a/packages/editor-ui/src/composables/useRunWorkflow.ts b/packages/editor-ui/src/composables/useRunWorkflow.ts index 6d642b3436a89..65eb1a79187bb 100644 --- a/packages/editor-ui/src/composables/useRunWorkflow.ts +++ b/packages/editor-ui/src/composables/useRunWorkflow.ts @@ -426,14 +426,19 @@ export function useRunWorkflow(options: { router: ReturnType } parentNodes.push(directParentNode); for (const parentNode of parentNodes) { - if (!runData[parentNode]?.length && !pinData?.[parentNode]?.length) { + // We want to execute nodes that don't have run data neither pin data + // in addition, if a node failed we want to execute it again + if ( + (!runData[parentNode]?.length && !pinData?.[parentNode]?.length) || + runData[parentNode]?.[0]?.error !== undefined + ) { // When we hit a node which has no data we stop and set it // as a start node the execution from and then go on with other // direct input nodes startNodeNames.push(parentNode); break; } - if (runData[parentNode]) { + if (runData[parentNode] && !runData[parentNode]?.[0]?.error) { newRunData[parentNode] = runData[parentNode]?.slice(0, 1); } }