fix(editor): Show warning toast when executed node was not reached
When executing to a specific node that sits on a conditional branch (e.g. after an If or Switch node), the node may not be reached if the condition routes data down a different path. Previously the toast incorrectly showed "Node executed successfully". Now it shows a warning explaining the execution took a different path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2341,6 +2341,8 @@
|
||||
"personalizationModal.registerEmailForTrial.success.button": "Start using n8n",
|
||||
"personalizationModal.registerEmailForTrial.error": "Error while registering for enterprise trial",
|
||||
"pushConnection.nodeExecutedSuccessfully": "Node executed successfully",
|
||||
"pushConnection.nodeNotExecuted": "Node was not executed",
|
||||
"pushConnection.nodeNotExecuted.message": "The execution took a different path and didn't reach this node. This typically happens with conditional nodes like If or Switch.",
|
||||
"pushConnection.workflowExecutedSuccessfully": "Workflow executed successfully",
|
||||
"pushConnection.error.message": "Connection lost",
|
||||
"pushConnection.error.tooltip": "You have a connection issue or the server is down. <br />n8n should reconnect automatically once the issue is resolved.",
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
handleExecutionFinishedWithErrorOrCanceled,
|
||||
type SimplifiedExecution,
|
||||
} from './executionFinished';
|
||||
import type { IRunExecutionData, ITaskData } from 'n8n-workflow';
|
||||
import type { IRunExecutionData, ITaskData, INodeTypeDescription } from 'n8n-workflow';
|
||||
import { EVALUATION_TRIGGER_NODE_TYPE } from 'n8n-workflow';
|
||||
import type { INodeUi, IWorkflowDb } from '@/Interface';
|
||||
import type { Router } from 'vue-router';
|
||||
@@ -21,12 +21,26 @@ import { useUIStore } from '@/app/stores/ui.store';
|
||||
import { mockedStore } from '@/__tests__/utils';
|
||||
import { useReadyToRunStore } from '@/features/workflows/readyToRun/stores/readyToRun.store';
|
||||
import { useBuilderStore } from '@/features/ai/assistant/builder.store';
|
||||
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
|
||||
|
||||
const opts = {
|
||||
workflowState: mock<WorkflowState>(),
|
||||
router: mock<Router>(),
|
||||
};
|
||||
|
||||
const mockShowMessage = vi.fn();
|
||||
vi.mock('@/app/composables/useToast', () => ({
|
||||
useToast: () => ({
|
||||
showMessage: mockShowMessage,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/app/composables/useDocumentTitle', () => ({
|
||||
useDocumentTitle: () => ({
|
||||
setDocumentTitle: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
const runWorkflow = vi.fn();
|
||||
|
||||
vi.mock('@/app/composables/useRunWorkflow', () => ({
|
||||
@@ -578,6 +592,98 @@ describe('manual execution stats tracking', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('toast messages', () => {
|
||||
beforeEach(() => {
|
||||
mockShowMessage.mockClear();
|
||||
});
|
||||
|
||||
it('shows success toast when executed node has run data', () => {
|
||||
const pinia = createTestingPinia();
|
||||
setActivePinia(pinia);
|
||||
|
||||
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||
const nodeTypesStore = mockedStore(useNodeTypesStore);
|
||||
|
||||
const nodeName = 'Send Telegram';
|
||||
vi.spyOn(workflowsStore, 'getWorkflowExecution', 'get').mockReturnValue({
|
||||
executedNode: nodeName,
|
||||
data: {
|
||||
resultData: {
|
||||
runData: {
|
||||
[nodeName]: [mock<ITaskData>()],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as ReturnType<typeof workflowsStore.getWorkflowExecution>);
|
||||
|
||||
vi.spyOn(workflowsStore, 'getNodeByName').mockReturnValue(
|
||||
mock<INodeUi>({ type: 'n8n-nodes-base.telegram', typeVersion: 1 }),
|
||||
);
|
||||
|
||||
nodeTypesStore.getNodeType = () => mock<INodeTypeDescription>({ polling: undefined });
|
||||
|
||||
handleExecutionFinishedWithSuccessOrOther(mock<WorkflowState>(), 'success', false);
|
||||
|
||||
expect(mockShowMessage).toHaveBeenCalledWith(expect.objectContaining({ type: 'success' }));
|
||||
});
|
||||
|
||||
it('shows warning toast when executed node was not reached', () => {
|
||||
const pinia = createTestingPinia();
|
||||
setActivePinia(pinia);
|
||||
|
||||
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||
const nodeTypesStore = mockedStore(useNodeTypesStore);
|
||||
|
||||
const nodeName = 'Send a text message';
|
||||
vi.spyOn(workflowsStore, 'getWorkflowExecution', 'get').mockReturnValue({
|
||||
executedNode: nodeName,
|
||||
data: {
|
||||
resultData: {
|
||||
runData: {},
|
||||
},
|
||||
},
|
||||
} as unknown as ReturnType<typeof workflowsStore.getWorkflowExecution>);
|
||||
|
||||
vi.spyOn(workflowsStore, 'getNodeByName').mockReturnValue(
|
||||
mock<INodeUi>({ type: 'n8n-nodes-base.vonage', typeVersion: 1 }),
|
||||
);
|
||||
|
||||
nodeTypesStore.getNodeType = () => mock<INodeTypeDescription>({ polling: undefined });
|
||||
|
||||
handleExecutionFinishedWithSuccessOrOther(mock<WorkflowState>(), 'success', false);
|
||||
|
||||
expect(mockShowMessage).toHaveBeenCalledWith(expect.objectContaining({ type: 'warning' }));
|
||||
});
|
||||
|
||||
it('does not show warning toast when successToastAlreadyShown is true', () => {
|
||||
const pinia = createTestingPinia();
|
||||
setActivePinia(pinia);
|
||||
|
||||
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||
const nodeTypesStore = mockedStore(useNodeTypesStore);
|
||||
|
||||
const nodeName = 'Send a text message';
|
||||
vi.spyOn(workflowsStore, 'getWorkflowExecution', 'get').mockReturnValue({
|
||||
executedNode: nodeName,
|
||||
data: {
|
||||
resultData: {
|
||||
runData: {},
|
||||
},
|
||||
},
|
||||
} as unknown as ReturnType<typeof workflowsStore.getWorkflowExecution>);
|
||||
|
||||
vi.spyOn(workflowsStore, 'getNodeByName').mockReturnValue(
|
||||
mock<INodeUi>({ type: 'n8n-nodes-base.vonage', typeVersion: 1 }),
|
||||
);
|
||||
|
||||
nodeTypesStore.getNodeType = () => mock<INodeTypeDescription>({ polling: undefined });
|
||||
|
||||
handleExecutionFinishedWithSuccessOrOther(mock<WorkflowState>(), 'success', true);
|
||||
|
||||
expect(mockShowMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleExecutionFinishedWithErrorOrCanceled', () => {
|
||||
it('increments error stats on execution error', () => {
|
||||
const pinia = createTestingPinia();
|
||||
|
||||
@@ -439,6 +439,12 @@ export function handleExecutionFinishedWithSuccessOrOther(
|
||||
}),
|
||||
type: 'success',
|
||||
});
|
||||
} else if (!nodeOutput && !successToastAlreadyShown) {
|
||||
toast.showMessage({
|
||||
title: i18n.baseText('pushConnection.nodeNotExecuted'),
|
||||
message: i18n.baseText('pushConnection.nodeNotExecuted.message'),
|
||||
type: 'warning',
|
||||
});
|
||||
} else if (!successToastAlreadyShown) {
|
||||
handleExecutionFinishedSuccessfully(
|
||||
workflowName,
|
||||
|
||||
Reference in New Issue
Block a user