fix(core): Bring back execution data on the executionFinished push message (#11821)
Some checks failed
Test Master / install-and-build (push) Has been cancelled
Test Master / Unit tests (18.x) (push) Has been cancelled
Test Master / Unit tests (20.x) (push) Has been cancelled
Test Master / Unit tests (22.4) (push) Has been cancelled
Test Master / Lint (push) Has been cancelled
Test Master / Notify Slack on failure (push) Has been cancelled

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2024-11-22 19:09:48 +01:00 committed by GitHub
parent 13cc5abb7f
commit 03135702f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 79 additions and 96 deletions

View file

@ -1,5 +1,6 @@
import { stringify } from 'flatted';
import type { IDataObject, IPinData, ITaskData, ITaskDataConnections } from 'n8n-workflow';
import type { IDataObject, ITaskData, ITaskDataConnections } from 'n8n-workflow';
import { nanoid } from 'nanoid';
import { clickExecuteWorkflowButton } from '../composables/workflow';
@ -39,38 +40,6 @@ export function createMockNodeExecutionData(
};
}
function createMockWorkflowExecutionData({
runData,
lastNodeExecuted,
}: {
runData: Record<string, ITaskData | ITaskData[]>;
pinData?: IPinData;
lastNodeExecuted: string;
}) {
return {
data: stringify({
startData: {},
resultData: {
runData,
pinData: {},
lastNodeExecuted,
},
executionData: {
contextData: {},
nodeExecutionStack: [],
metadata: {},
waitingExecution: {},
waitingExecutionSource: {},
},
}),
mode: 'manual',
startedAt: new Date().toISOString(),
stoppedAt: new Date().toISOString(),
status: 'success',
finished: true,
};
}
export function runMockWorkflowExecution({
trigger,
lastNodeExecuted,
@ -80,6 +49,7 @@ export function runMockWorkflowExecution({
lastNodeExecuted: string;
runData: Array<ReturnType<typeof createMockNodeExecutionData>>;
}) {
const workflowId = nanoid();
const executionId = Math.floor(Math.random() * 1_000_000).toString();
cy.intercept('POST', '/rest/workflows/**/run?**', {
@ -117,17 +87,24 @@ export function runMockWorkflowExecution({
resolvedRunData[nodeName] = nodeExecution[nodeName];
});
cy.intercept('GET', `/rest/executions/${executionId}`, {
statusCode: 200,
body: {
data: createMockWorkflowExecutionData({
cy.push('executionFinished', {
executionId,
workflowId,
status: 'success',
rawData: stringify({
startData: {},
resultData: {
runData,
pinData: {},
lastNodeExecuted,
runData: resolvedRunData,
}),
},
}).as('getExecution');
cy.push('executionFinished', { executionId });
cy.wait('@getExecution');
},
executionData: {
contextData: {},
nodeExecutionStack: [],
metadata: {},
waitingExecution: {},
waitingExecutionSource: {},
},
}),
});
}

View file

@ -1,4 +1,4 @@
import type { ITaskData, WorkflowExecuteMode } from 'n8n-workflow';
import type { ExecutionStatus, ITaskData, WorkflowExecuteMode } from 'n8n-workflow';
type ExecutionStarted = {
type: 'executionStarted';
@ -23,6 +23,10 @@ type ExecutionFinished = {
type: 'executionFinished';
data: {
executionId: string;
workflowId: string;
status: ExecutionStatus;
/** @deprecated: Please construct execution data in the frontend from the data pushed in previous messages, instead of depending on this additional payload serialization */
rawData?: string;
};
};

View file

@ -5,6 +5,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import type { PushType } from '@n8n/api-types';
import { GlobalConfig } from '@n8n/config';
import { stringify } from 'flatted';
import { WorkflowExecute } from 'n8n-core';
import {
ApplicationError,
@ -318,9 +319,17 @@ function hookFunctionsPush(): IWorkflowExecuteHooks {
workflowId,
});
const pushType =
fullRunData.status === 'waiting' ? 'executionWaiting' : 'executionFinished';
pushInstance.send(pushType, { executionId }, pushRef);
const { status } = fullRunData;
if (status === 'waiting') {
pushInstance.send('executionWaiting', { executionId }, pushRef);
} else {
const rawData = stringify(fullRunData.data);
pushInstance.send(
'executionFinished',
{ executionId, workflowId, status, rawData },
pushRef,
);
}
},
],
};

View file

@ -2,7 +2,6 @@ import { stringify } from 'flatted';
import { useRouter } from 'vue-router';
import { createPinia, setActivePinia } from 'pinia';
import type { PushMessage, PushPayload } from '@n8n/api-types';
import { mock } from 'vitest-mock-extended';
import type { ITaskData, WorkflowOperationError } from 'n8n-workflow';
import { usePushConnection } from '@/composables/usePushConnection';
@ -11,7 +10,6 @@ import { useOrchestrationStore } from '@/stores/orchestration.store';
import { useUIStore } from '@/stores/ui.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useToast } from '@/composables/useToast';
import type { IExecutionResponse } from '@/Interface';
vi.mock('vue-router', () => {
return {
@ -140,10 +138,7 @@ describe('usePushConnection()', () => {
describe('executionFinished', () => {
const executionId = '1';
const event: PushMessage = {
type: 'executionFinished',
data: { executionId: '1' },
};
const workflowId = 'abc';
beforeEach(() => {
workflowsStore.activeExecutionId = executionId;
@ -151,28 +146,23 @@ describe('usePushConnection()', () => {
});
it('should handle executionFinished event correctly', async () => {
const spy = vi.spyOn(workflowsStore, 'fetchExecutionDataById').mockResolvedValue(
mock<IExecutionResponse>({
id: executionId,
data: stringify({
const result = await pushConnection.pushMessageReceived({
type: 'executionFinished',
data: {
executionId,
workflowId,
status: 'success',
rawData: stringify({
resultData: {
runData: {},
},
}) as unknown as IExecutionResponse['data'],
finished: true,
mode: 'manual',
startedAt: new Date(),
stoppedAt: new Date(),
status: 'success',
}),
);
const result = await pushConnection.pushMessageReceived(event);
}),
},
});
expect(result).toBeTruthy();
expect(workflowsStore.workflowExecutionData).toBeDefined();
expect(uiStore.isActionActive['workflowRunning']).toBeTruthy();
expect(spy).toHaveBeenCalledWith(executionId);
expect(toast.showMessage).toHaveBeenCalledWith({
title: 'Workflow executed successfully',
@ -181,10 +171,13 @@ describe('usePushConnection()', () => {
});
it('should handle isManualExecutionCancelled correctly', async () => {
const spy = vi.spyOn(workflowsStore, 'fetchExecutionDataById').mockResolvedValue(
mock<IExecutionResponse>({
id: executionId,
data: stringify({
const result = await pushConnection.pushMessageReceived({
type: 'executionFinished',
data: {
executionId,
workflowId,
status: 'error',
rawData: stringify({
startData: {},
resultData: {
runData: {
@ -198,14 +191,9 @@ describe('usePushConnection()', () => {
node: 'Last Node',
} as unknown as WorkflowOperationError,
},
}) as unknown as IExecutionResponse['data'],
mode: 'manual',
startedAt: new Date(),
status: 'running',
}),
);
const result = await pushConnection.pushMessageReceived(event);
}),
},
});
expect(useToast().showMessage).toHaveBeenCalledWith({
message:
@ -219,7 +207,6 @@ describe('usePushConnection()', () => {
expect(result).toBeTruthy();
expect(workflowsStore.workflowExecutionData).toBeDefined();
expect(uiStore.isActionActive.workflowRunning).toBeTruthy();
expect(spy).toHaveBeenCalledWith(executionId);
});
});

View file

@ -35,6 +35,7 @@ import { useTelemetry } from '@/composables/useTelemetry';
import type { PushMessageQueueItem } from '@/types';
import { useAssistantStore } from '@/stores/assistant.store';
import NodeExecutionErrorMessage from '@/components/NodeExecutionErrorMessage.vue';
import type { IExecutionResponse } from '@/Interface';
export function usePushConnection({ router }: { router: ReturnType<typeof useRouter> }) {
const workflowHelpers = useWorkflowHelpers({ router });
@ -205,11 +206,19 @@ export function usePushConnection({ router }: { router: ReturnType<typeof useRou
return false;
}
// pull execution data for the execution from the server
const executionData = await workflowsStore.fetchExecutionDataById(executionId);
if (!executionData?.data) return false;
// data comes in as 'flatten' object, so we need to parse it
executionData.data = parse(executionData.data as unknown as string) as IRunExecutionData;
let executionData: Pick<IExecutionResponse, 'workflowId' | 'data' | 'status'>;
if (receivedData.type === 'executionFinished' && receivedData.data.rawData) {
const { workflowId, status, rawData } = receivedData.data;
executionData = { workflowId, data: parse(rawData), status };
} else {
const execution = await workflowsStore.fetchExecutionDataById(executionId);
if (!execution?.data) return false;
executionData = {
workflowId: execution.workflowId,
data: parse(execution.data as unknown as string),
status: execution.status,
};
}
const iRunExecutionData: IRunExecutionData = {
startData: executionData.data?.startData,
@ -265,7 +274,7 @@ export function usePushConnection({ router }: { router: ReturnType<typeof useRou
// Workflow did start but had been put to wait
workflowHelpers.setDocumentTitle(workflow.name as string, 'IDLE');
} else if (executionData.finished !== true) {
} else if (executionData.status === 'error' || executionData.status === 'canceled') {
workflowHelpers.setDocumentTitle(workflow.name as string, 'ERROR');
if (
@ -347,17 +356,14 @@ export function usePushConnection({ router }: { router: ReturnType<typeof useRou
duration: 0,
});
} else {
let title: string;
const isManualExecutionCancelled =
executionData.mode === 'manual' && executionData.status === 'canceled';
// Do not show the error message if the workflow got canceled manually
if (isManualExecutionCancelled) {
// Do not show the error message if the workflow got canceled
if (executionData.status === 'canceled') {
toast.showMessage({
title: i18n.baseText('nodeView.showMessage.stopExecutionTry.title'),
type: 'success',
});
} else {
let title: string;
if (iRunExecutionData.resultData.lastNodeExecuted) {
title = `Problem in node ${iRunExecutionData.resultData.lastNodeExecuted}`;
} else {
@ -421,7 +427,7 @@ export function usePushConnection({ router }: { router: ReturnType<typeof useRou
}
workflowsStore.executingNode.length = 0;
workflowsStore.setWorkflowExecutionData(executionData);
workflowsStore.setWorkflowExecutionData(executionData as IExecutionResponse);
uiStore.removeActiveAction('workflowRunning');
// Set the node execution issues on all the nodes which produced an error so that