mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
refactor(core): Persist node execution order, and forward it to the frontend (#14455)
This commit is contained in:
committed by
GitHub
parent
707ecb63ae
commit
9ba58ca80b
@@ -16,7 +16,8 @@ export function createMockNodeExecutionData(
|
|||||||
): Record<string, ITaskData> {
|
): Record<string, ITaskData> {
|
||||||
return {
|
return {
|
||||||
[name]: {
|
[name]: {
|
||||||
startTime: new Date().getTime(),
|
startTime: Date.now(),
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 1,
|
executionTime: 1,
|
||||||
executionStatus,
|
executionStatus,
|
||||||
data: jsonData
|
data: jsonData
|
||||||
@@ -77,6 +78,7 @@ export function runMockWorkflowExecution({
|
|||||||
cy.push('nodeExecuteBefore', {
|
cy.push('nodeExecuteBefore', {
|
||||||
executionId,
|
executionId,
|
||||||
nodeName,
|
nodeName,
|
||||||
|
data: nodeRunData,
|
||||||
});
|
});
|
||||||
cy.push('nodeExecuteAfter', {
|
cy.push('nodeExecuteAfter', {
|
||||||
executionId,
|
executionId,
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import type { ExecutionStatus, ITaskData, WorkflowExecuteMode } from 'n8n-workflow';
|
import type {
|
||||||
|
ExecutionStatus,
|
||||||
|
ITaskData,
|
||||||
|
ITaskStartedData,
|
||||||
|
WorkflowExecuteMode,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
type ExecutionStarted = {
|
type ExecutionStarted = {
|
||||||
type: 'executionStarted';
|
type: 'executionStarted';
|
||||||
@@ -43,6 +48,7 @@ type NodeExecuteBefore = {
|
|||||||
data: {
|
data: {
|
||||||
executionId: string;
|
executionId: string;
|
||||||
nodeName: string;
|
nodeName: string;
|
||||||
|
data: ITaskStartedData;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export const newNode = (opts: Partial<INode> = {}): INode => ({
|
|||||||
export const newTaskData = (opts: Partial<ITaskData> & Pick<ITaskData, 'source'>): ITaskData => ({
|
export const newTaskData = (opts: Partial<ITaskData> & Pick<ITaskData, 'source'>): ITaskData => ({
|
||||||
startTime: Date.now(),
|
startTime: Date.now(),
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
...opts,
|
...opts,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import type {
|
|||||||
INode,
|
INode,
|
||||||
IWorkflowBase,
|
IWorkflowBase,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
|
ITaskStartedData,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
@@ -68,6 +69,7 @@ describe('Execution Lifecycle Hooks', () => {
|
|||||||
};
|
};
|
||||||
const workflow = mock<Workflow>();
|
const workflow = mock<Workflow>();
|
||||||
const staticData = mock<IDataObject>();
|
const staticData = mock<IDataObject>();
|
||||||
|
const taskStartedData = mock<ITaskStartedData>();
|
||||||
const taskData = mock<ITaskData>();
|
const taskData = mock<ITaskData>();
|
||||||
const runExecutionData = mock<IRunExecutionData>();
|
const runExecutionData = mock<IRunExecutionData>();
|
||||||
const successfulRun = mock<IRun>({
|
const successfulRun = mock<IRun>({
|
||||||
@@ -146,7 +148,7 @@ describe('Execution Lifecycle Hooks', () => {
|
|||||||
const nodeEventsTests = () => {
|
const nodeEventsTests = () => {
|
||||||
describe('nodeExecuteBefore', () => {
|
describe('nodeExecuteBefore', () => {
|
||||||
it('should emit node-pre-execute event', async () => {
|
it('should emit node-pre-execute event', async () => {
|
||||||
await lifecycleHooks.runHook('nodeExecuteBefore', [nodeName]);
|
await lifecycleHooks.runHook('nodeExecuteBefore', [nodeName, taskStartedData]);
|
||||||
|
|
||||||
expect(eventService.emit).toHaveBeenCalledWith('node-pre-execute', {
|
expect(eventService.emit).toHaveBeenCalledWith('node-pre-execute', {
|
||||||
executionId,
|
executionId,
|
||||||
@@ -246,10 +248,10 @@ describe('Execution Lifecycle Hooks', () => {
|
|||||||
|
|
||||||
describe('nodeExecuteBefore', () => {
|
describe('nodeExecuteBefore', () => {
|
||||||
it('should send nodeExecuteBefore push event', async () => {
|
it('should send nodeExecuteBefore push event', async () => {
|
||||||
await lifecycleHooks.runHook('nodeExecuteBefore', [nodeName]);
|
await lifecycleHooks.runHook('nodeExecuteBefore', [nodeName, taskStartedData]);
|
||||||
|
|
||||||
expect(push.send).toHaveBeenCalledWith(
|
expect(push.send).toHaveBeenCalledWith(
|
||||||
{ type: 'nodeExecuteBefore', data: { executionId, nodeName } },
|
{ type: 'nodeExecuteBefore', data: { executionId, nodeName, data: taskStartedData } },
|
||||||
pushRef,
|
pushRef,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -471,8 +473,9 @@ describe('Execution Lifecycle Hooks', () => {
|
|||||||
(successfulRun.data.resultData.runData = {
|
(successfulRun.data.resultData.runData = {
|
||||||
[nodeName]: [
|
[nodeName]: [
|
||||||
{
|
{
|
||||||
executionTime: 1,
|
|
||||||
startTime: 1,
|
startTime: 1,
|
||||||
|
executionIndex: 0,
|
||||||
|
executionTime: 1,
|
||||||
source: [],
|
source: [],
|
||||||
data: {
|
data: {
|
||||||
main: [
|
main: [
|
||||||
@@ -517,7 +520,7 @@ describe('Execution Lifecycle Hooks', () => {
|
|||||||
expect(handlers.workflowExecuteBefore).toHaveLength(2);
|
expect(handlers.workflowExecuteBefore).toHaveLength(2);
|
||||||
expect(handlers.workflowExecuteAfter).toHaveLength(4);
|
expect(handlers.workflowExecuteAfter).toHaveLength(4);
|
||||||
|
|
||||||
await lifecycleHooks.runHook('nodeExecuteBefore', [nodeName]);
|
await lifecycleHooks.runHook('nodeExecuteBefore', [nodeName, taskStartedData]);
|
||||||
await lifecycleHooks.runHook('nodeExecuteAfter', [nodeName, taskData, runExecutionData]);
|
await lifecycleHooks.runHook('nodeExecuteAfter', [nodeName, taskData, runExecutionData]);
|
||||||
await lifecycleHooks.runHook('workflowExecuteBefore', [workflow, runExecutionData]);
|
await lifecycleHooks.runHook('workflowExecuteBefore', [workflow, runExecutionData]);
|
||||||
await lifecycleHooks.runHook('workflowExecuteAfter', [successfulRun, {}]);
|
await lifecycleHooks.runHook('workflowExecuteAfter', [successfulRun, {}]);
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ function hookFunctionsPush(
|
|||||||
if (!pushRef) return;
|
if (!pushRef) return;
|
||||||
const logger = Container.get(Logger);
|
const logger = Container.get(Logger);
|
||||||
const pushInstance = Container.get(Push);
|
const pushInstance = Container.get(Push);
|
||||||
hooks.addHandler('nodeExecuteBefore', function (nodeName) {
|
hooks.addHandler('nodeExecuteBefore', function (nodeName, data) {
|
||||||
const { executionId } = this;
|
const { executionId } = this;
|
||||||
// Push data to session which started workflow before each
|
// Push data to session which started workflow before each
|
||||||
// node which starts rendering
|
// node which starts rendering
|
||||||
@@ -78,7 +78,10 @@ function hookFunctionsPush(
|
|||||||
workflowId: this.workflowData.id,
|
workflowId: this.workflowData.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
pushInstance.send({ type: 'nodeExecuteBefore', data: { executionId, nodeName } }, pushRef);
|
pushInstance.send(
|
||||||
|
{ type: 'nodeExecuteBefore', data: { executionId, nodeName, data } },
|
||||||
|
pushRef,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
hooks.addHandler('nodeExecuteAfter', function (nodeName, data) {
|
hooks.addHandler('nodeExecuteAfter', function (nodeName, data) {
|
||||||
const { executionId } = this;
|
const { executionId } = this;
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export class ExecutionDataService {
|
|||||||
returnData.data.resultData.runData[node.name] = [
|
returnData.data.resultData.runData[node.name] = [
|
||||||
{
|
{
|
||||||
startTime,
|
startTime,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
executionStatus: 'error',
|
executionStatus: 'error',
|
||||||
error: executionError,
|
error: executionError,
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ export class ExecutionRecoveryService {
|
|||||||
|
|
||||||
const taskData: ITaskData = {
|
const taskData: ITaskData = {
|
||||||
startTime: nodeStartedMessage.ts.toUnixInteger(),
|
startTime: nodeStartedMessage.ts.toUnixInteger(),
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: -1,
|
executionTime: -1,
|
||||||
source: [null],
|
source: [null],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -311,6 +311,7 @@ export class ExecutionService {
|
|||||||
[node.name]: [
|
[node.name]: [
|
||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
error,
|
error,
|
||||||
source: [],
|
source: [],
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ const taskData: DataRequestResponse = {
|
|||||||
{
|
{
|
||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1730313407328,
|
startTime: 1730313407328,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 1,
|
executionTime: 1,
|
||||||
source: [],
|
source: [],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
@@ -122,6 +123,7 @@ const taskData: DataRequestResponse = {
|
|||||||
{
|
{
|
||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1730313407330,
|
startTime: 1730313407330,
|
||||||
|
executionIndex: 1,
|
||||||
executionTime: 1,
|
executionTime: 1,
|
||||||
source: [
|
source: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -373,6 +373,7 @@ export async function getBase(
|
|||||||
const eventService = Container.get(EventService);
|
const eventService = Container.get(EventService);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
currentNodeExecutionIndex: 0,
|
||||||
credentialsHelper: Container.get(CredentialsHelper),
|
credentialsHelper: Container.get(CredentialsHelper),
|
||||||
executeWorkflow,
|
executeWorkflow,
|
||||||
restApiUrl: urlBaseWebhook + globalConfig.endpoints.rest,
|
restApiUrl: urlBaseWebhook + globalConfig.endpoints.rest,
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export function getDataLastExecutedNodeData(inputData: IRun): ITaskData | undefi
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
data: { main: [itemsPerRun] },
|
data: { main: [itemsPerRun] },
|
||||||
source: lastNodeRunData.source,
|
source: lastNodeRunData.source,
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ describe('JS TaskRunner execution on internal mode', () => {
|
|||||||
ManualTrigger: [
|
ManualTrigger: [
|
||||||
{
|
{
|
||||||
startTime: Date.now(),
|
startTime: Date.now(),
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
source: [],
|
source: [],
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type {
|
|||||||
IRun,
|
IRun,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
ITaskData,
|
ITaskData,
|
||||||
|
ITaskStartedData,
|
||||||
IWorkflowBase,
|
IWorkflowBase,
|
||||||
Workflow,
|
Workflow,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
@@ -52,7 +53,7 @@ describe('ExecutionLifecycleHooks', () => {
|
|||||||
hook: ExecutionLifecycleHookName;
|
hook: ExecutionLifecycleHookName;
|
||||||
args: Parameters<ExecutionLifecyleHookHandlers[keyof ExecutionLifecyleHookHandlers][number]>;
|
args: Parameters<ExecutionLifecyleHookHandlers[keyof ExecutionLifecyleHookHandlers][number]>;
|
||||||
}> = [
|
}> = [
|
||||||
{ hook: 'nodeExecuteBefore', args: ['testNode'] },
|
{ hook: 'nodeExecuteBefore', args: ['testNode', mock<ITaskStartedData>()] },
|
||||||
{
|
{
|
||||||
hook: 'nodeExecuteAfter',
|
hook: 'nodeExecuteAfter',
|
||||||
args: ['testNode', mock<ITaskData>(), mock<IRunExecutionData>()],
|
args: ['testNode', mock<ITaskData>(), mock<IRunExecutionData>()],
|
||||||
@@ -84,7 +85,7 @@ describe('ExecutionLifecycleHooks', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
hooks.addHandler('nodeExecuteBefore', hook1, hook2);
|
hooks.addHandler('nodeExecuteBefore', hook1, hook2);
|
||||||
await hooks.runHook('nodeExecuteBefore', ['testNode']);
|
await hooks.runHook('nodeExecuteBefore', ['testNode', mock()]);
|
||||||
|
|
||||||
expect(executionOrder).toEqual(['hook1', 'hook2']);
|
expect(executionOrder).toEqual(['hook1', 'hook2']);
|
||||||
expect(hook1).toHaveBeenCalled();
|
expect(hook1).toHaveBeenCalled();
|
||||||
@@ -98,7 +99,7 @@ describe('ExecutionLifecycleHooks', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
hooks.addHandler('nodeExecuteBefore', hook);
|
hooks.addHandler('nodeExecuteBefore', hook);
|
||||||
await hooks.runHook('nodeExecuteBefore', ['testNode']);
|
await hooks.runHook('nodeExecuteBefore', ['testNode', mock()]);
|
||||||
|
|
||||||
expect(hook).toHaveBeenCalled();
|
expect(hook).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@@ -107,7 +108,9 @@ describe('ExecutionLifecycleHooks', () => {
|
|||||||
const errorHook = jest.fn().mockRejectedValue(new Error('Hook failed'));
|
const errorHook = jest.fn().mockRejectedValue(new Error('Hook failed'));
|
||||||
hooks.addHandler('nodeExecuteBefore', errorHook);
|
hooks.addHandler('nodeExecuteBefore', errorHook);
|
||||||
|
|
||||||
await expect(hooks.runHook('nodeExecuteBefore', ['testNode'])).rejects.toThrow('Hook failed');
|
await expect(hooks.runHook('nodeExecuteBefore', ['testNode', mock()])).rejects.toThrow(
|
||||||
|
'Hook failed',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -77,11 +77,7 @@ describe('WorkflowExecute', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const waitPromise = createDeferredPromise<IRun>();
|
const waitPromise = createDeferredPromise<IRun>();
|
||||||
const nodeExecutionOrder: string[] = [];
|
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise);
|
||||||
const additionalData = Helpers.WorkflowExecuteAdditionalData(
|
|
||||||
waitPromise,
|
|
||||||
nodeExecutionOrder,
|
|
||||||
);
|
|
||||||
|
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, executionMode);
|
const workflowExecute = new WorkflowExecute(additionalData, executionMode);
|
||||||
|
|
||||||
@@ -110,6 +106,12 @@ describe('WorkflowExecute', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if the nodes did execute in the correct order
|
// Check if the nodes did execute in the correct order
|
||||||
|
const nodeExecutionOrder: string[] = [];
|
||||||
|
Object.entries(result.data.resultData.runData).forEach(([nodeName, taskDataArr]) => {
|
||||||
|
taskDataArr.forEach((taskData) => {
|
||||||
|
nodeExecutionOrder[taskData.executionIndex] = nodeName;
|
||||||
|
});
|
||||||
|
});
|
||||||
expect(nodeExecutionOrder).toEqual(testData.output.nodeExecutionOrder);
|
expect(nodeExecutionOrder).toEqual(testData.output.nodeExecutionOrder);
|
||||||
|
|
||||||
// Check if other data has correct value
|
// Check if other data has correct value
|
||||||
@@ -140,11 +142,7 @@ describe('WorkflowExecute', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const waitPromise = createDeferredPromise<IRun>();
|
const waitPromise = createDeferredPromise<IRun>();
|
||||||
const nodeExecutionOrder: string[] = [];
|
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise);
|
||||||
const additionalData = Helpers.WorkflowExecuteAdditionalData(
|
|
||||||
waitPromise,
|
|
||||||
nodeExecutionOrder,
|
|
||||||
);
|
|
||||||
|
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, executionMode);
|
const workflowExecute = new WorkflowExecute(additionalData, executionMode);
|
||||||
|
|
||||||
@@ -177,6 +175,12 @@ describe('WorkflowExecute', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if the nodes did execute in the correct order
|
// Check if the nodes did execute in the correct order
|
||||||
|
const nodeExecutionOrder: string[] = [];
|
||||||
|
Object.entries(result.data.resultData.runData).forEach(([nodeName, taskDataArr]) => {
|
||||||
|
taskDataArr.forEach((taskData) => {
|
||||||
|
nodeExecutionOrder[taskData.executionIndex] = nodeName;
|
||||||
|
});
|
||||||
|
});
|
||||||
expect(nodeExecutionOrder).toEqual(testData.output.nodeExecutionOrder);
|
expect(nodeExecutionOrder).toEqual(testData.output.nodeExecutionOrder);
|
||||||
|
|
||||||
// Check if other data has correct value
|
// Check if other data has correct value
|
||||||
@@ -207,11 +211,7 @@ describe('WorkflowExecute', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const waitPromise = createDeferredPromise<IRun>();
|
const waitPromise = createDeferredPromise<IRun>();
|
||||||
const nodeExecutionOrder: string[] = [];
|
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise);
|
||||||
const additionalData = Helpers.WorkflowExecuteAdditionalData(
|
|
||||||
waitPromise,
|
|
||||||
nodeExecutionOrder,
|
|
||||||
);
|
|
||||||
|
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, executionMode);
|
const workflowExecute = new WorkflowExecute(additionalData, executionMode);
|
||||||
|
|
||||||
@@ -259,8 +259,7 @@ describe('WorkflowExecute', () => {
|
|||||||
test("deletes dirty nodes' run data", async () => {
|
test("deletes dirty nodes' run data", async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const waitPromise = createDeferredPromise<IRun>();
|
const waitPromise = createDeferredPromise<IRun>();
|
||||||
const nodeExecutionOrder: string[] = [];
|
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise);
|
||||||
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise, nodeExecutionOrder);
|
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, 'manual');
|
const workflowExecute = new WorkflowExecute(additionalData, 'manual');
|
||||||
|
|
||||||
const trigger = createNodeData({ name: 'trigger', type: 'n8n-nodes-base.manualTrigger' });
|
const trigger = createNodeData({ name: 'trigger', type: 'n8n-nodes-base.manualTrigger' });
|
||||||
@@ -307,8 +306,7 @@ describe('WorkflowExecute', () => {
|
|||||||
test('deletes run data of children of dirty nodes as well', async () => {
|
test('deletes run data of children of dirty nodes as well', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const waitPromise = createDeferredPromise<IRun>();
|
const waitPromise = createDeferredPromise<IRun>();
|
||||||
const nodeExecutionOrder: string[] = [];
|
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise);
|
||||||
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise, nodeExecutionOrder);
|
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, 'manual');
|
const workflowExecute = new WorkflowExecute(additionalData, 'manual');
|
||||||
jest.spyOn(workflowExecute, 'processRunExecutionData').mockImplementationOnce(jest.fn());
|
jest.spyOn(workflowExecute, 'processRunExecutionData').mockImplementationOnce(jest.fn());
|
||||||
|
|
||||||
@@ -369,8 +367,7 @@ describe('WorkflowExecute', () => {
|
|||||||
test('removes disabled nodes from the workflow', async () => {
|
test('removes disabled nodes from the workflow', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const waitPromise = createDeferredPromise<IRun>();
|
const waitPromise = createDeferredPromise<IRun>();
|
||||||
const nodeExecutionOrder: string[] = [];
|
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise);
|
||||||
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise, nodeExecutionOrder);
|
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, 'manual');
|
const workflowExecute = new WorkflowExecute(additionalData, 'manual');
|
||||||
|
|
||||||
const trigger = createNodeData({ name: 'trigger', type: 'n8n-nodes-base.manualTrigger' });
|
const trigger = createNodeData({ name: 'trigger', type: 'n8n-nodes-base.manualTrigger' });
|
||||||
@@ -423,8 +420,7 @@ describe('WorkflowExecute', () => {
|
|||||||
test('passes filtered run data to `recreateNodeExecutionStack`', async () => {
|
test('passes filtered run data to `recreateNodeExecutionStack`', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const waitPromise = createDeferredPromise<IRun>();
|
const waitPromise = createDeferredPromise<IRun>();
|
||||||
const nodeExecutionOrder: string[] = [];
|
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise);
|
||||||
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise, nodeExecutionOrder);
|
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, 'manual');
|
const workflowExecute = new WorkflowExecute(additionalData, 'manual');
|
||||||
|
|
||||||
const trigger = createNodeData({ name: 'trigger', type: 'n8n-nodes-base.manualTrigger' });
|
const trigger = createNodeData({ name: 'trigger', type: 'n8n-nodes-base.manualTrigger' });
|
||||||
@@ -486,8 +482,7 @@ describe('WorkflowExecute', () => {
|
|||||||
test('passes subgraph to `cleanRunData`', async () => {
|
test('passes subgraph to `cleanRunData`', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const waitPromise = createDeferredPromise<IRun>();
|
const waitPromise = createDeferredPromise<IRun>();
|
||||||
const nodeExecutionOrder: string[] = [];
|
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise);
|
||||||
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise, nodeExecutionOrder);
|
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, 'manual');
|
const workflowExecute = new WorkflowExecute(additionalData, 'manual');
|
||||||
|
|
||||||
const trigger = createNodeData({ name: 'trigger', type: 'n8n-nodes-base.manualTrigger' });
|
const trigger = createNodeData({ name: 'trigger', type: 'n8n-nodes-base.manualTrigger' });
|
||||||
@@ -547,8 +542,7 @@ describe('WorkflowExecute', () => {
|
|||||||
test('passes pruned dirty nodes to `cleanRunData`', async () => {
|
test('passes pruned dirty nodes to `cleanRunData`', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const waitPromise = createDeferredPromise<IRun>();
|
const waitPromise = createDeferredPromise<IRun>();
|
||||||
const nodeExecutionOrder: string[] = [];
|
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise);
|
||||||
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise, nodeExecutionOrder);
|
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, 'manual');
|
const workflowExecute = new WorkflowExecute(additionalData, 'manual');
|
||||||
|
|
||||||
const trigger = createNodeData({ name: 'trigger', type: 'n8n-nodes-base.manualTrigger' });
|
const trigger = createNodeData({ name: 'trigger', type: 'n8n-nodes-base.manualTrigger' });
|
||||||
@@ -599,8 +593,7 @@ describe('WorkflowExecute', () => {
|
|||||||
test('works with a single node', async () => {
|
test('works with a single node', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const waitPromise = createDeferredPromise<IRun>();
|
const waitPromise = createDeferredPromise<IRun>();
|
||||||
const nodeExecutionOrder: string[] = [];
|
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise);
|
||||||
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise, nodeExecutionOrder);
|
|
||||||
const workflowExecute = new WorkflowExecute(additionalData, 'manual');
|
const workflowExecute = new WorkflowExecute(additionalData, 'manual');
|
||||||
|
|
||||||
const trigger = createNodeData({ name: 'trigger' });
|
const trigger = createNodeData({ name: 'trigger' });
|
||||||
@@ -856,6 +849,7 @@ describe('WorkflowExecute', () => {
|
|||||||
},
|
},
|
||||||
source: [],
|
source: [],
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1132,6 +1126,7 @@ describe('WorkflowExecute', () => {
|
|||||||
source: [],
|
source: [],
|
||||||
data: { main: [[], []] },
|
data: { main: [[], []] },
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1165,6 +1160,7 @@ describe('WorkflowExecute', () => {
|
|||||||
main: [[{ json: { data: 'test' } }], []],
|
main: [[{ json: { data: 'test' } }], []],
|
||||||
},
|
},
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1188,6 +1184,7 @@ describe('WorkflowExecute', () => {
|
|||||||
main: [[]],
|
main: [[]],
|
||||||
},
|
},
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1196,6 +1193,7 @@ describe('WorkflowExecute', () => {
|
|||||||
main: [[{ json: { data: 'test' } }]],
|
main: [[{ json: { data: 'test' } }]],
|
||||||
},
|
},
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
|
executionIndex: 1,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1215,6 +1213,7 @@ describe('WorkflowExecute', () => {
|
|||||||
{
|
{
|
||||||
source: [],
|
source: [],
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1254,7 +1253,7 @@ describe('WorkflowExecute', () => {
|
|||||||
|
|
||||||
test('should do nothing when there is no metadata', () => {
|
test('should do nothing when there is no metadata', () => {
|
||||||
runExecutionData.resultData.runData = {
|
runExecutionData.resultData.runData = {
|
||||||
node1: [{ startTime: 0, executionTime: 0, source: [] }],
|
node1: [{ startTime: 0, executionTime: 0, source: [], executionIndex: 0 }],
|
||||||
};
|
};
|
||||||
|
|
||||||
workflowExecute.moveNodeMetadata();
|
workflowExecute.moveNodeMetadata();
|
||||||
@@ -1264,7 +1263,7 @@ describe('WorkflowExecute', () => {
|
|||||||
|
|
||||||
test('should merge metadata into runData for single node', () => {
|
test('should merge metadata into runData for single node', () => {
|
||||||
runExecutionData.resultData.runData = {
|
runExecutionData.resultData.runData = {
|
||||||
node1: [{ startTime: 0, executionTime: 0, source: [] }],
|
node1: [{ startTime: 0, executionTime: 0, source: [], executionIndex: 0 }],
|
||||||
};
|
};
|
||||||
runExecutionData.executionData!.metadata = {
|
runExecutionData.executionData!.metadata = {
|
||||||
node1: [{ parentExecution }],
|
node1: [{ parentExecution }],
|
||||||
@@ -1277,8 +1276,8 @@ describe('WorkflowExecute', () => {
|
|||||||
|
|
||||||
test('should merge metadata into runData for multiple nodes', () => {
|
test('should merge metadata into runData for multiple nodes', () => {
|
||||||
runExecutionData.resultData.runData = {
|
runExecutionData.resultData.runData = {
|
||||||
node1: [{ startTime: 0, executionTime: 0, source: [] }],
|
node1: [{ startTime: 0, executionTime: 0, source: [], executionIndex: 0 }],
|
||||||
node2: [{ startTime: 0, executionTime: 0, source: [] }],
|
node2: [{ startTime: 0, executionTime: 0, source: [], executionIndex: 1 }],
|
||||||
};
|
};
|
||||||
runExecutionData.executionData!.metadata = {
|
runExecutionData.executionData!.metadata = {
|
||||||
node1: [{ parentExecution }],
|
node1: [{ parentExecution }],
|
||||||
@@ -1297,6 +1296,7 @@ describe('WorkflowExecute', () => {
|
|||||||
node1: [
|
node1: [
|
||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
source: [],
|
source: [],
|
||||||
metadata: { subExecutionsCount: 4 },
|
metadata: { subExecutionsCount: 4 },
|
||||||
@@ -1318,8 +1318,8 @@ describe('WorkflowExecute', () => {
|
|||||||
test('should handle multiple run indices', () => {
|
test('should handle multiple run indices', () => {
|
||||||
runExecutionData.resultData.runData = {
|
runExecutionData.resultData.runData = {
|
||||||
node1: [
|
node1: [
|
||||||
{ startTime: 0, executionTime: 0, source: [] },
|
{ startTime: 0, executionTime: 0, source: [], executionIndex: 0 },
|
||||||
{ startTime: 0, executionTime: 0, source: [] },
|
{ startTime: 0, executionTime: 0, source: [], executionIndex: 1 },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
runExecutionData.executionData!.metadata = {
|
runExecutionData.executionData!.metadata = {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type {
|
|||||||
IRun,
|
IRun,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
ITaskData,
|
ITaskData,
|
||||||
|
ITaskStartedData,
|
||||||
IWorkflowBase,
|
IWorkflowBase,
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
@@ -12,7 +13,11 @@ import type {
|
|||||||
|
|
||||||
export type ExecutionLifecyleHookHandlers = {
|
export type ExecutionLifecyleHookHandlers = {
|
||||||
nodeExecuteBefore: Array<
|
nodeExecuteBefore: Array<
|
||||||
(this: ExecutionLifecycleHooks, nodeName: string) => Promise<void> | void
|
(
|
||||||
|
this: ExecutionLifecycleHooks,
|
||||||
|
nodeName: string,
|
||||||
|
data: ITaskStartedData,
|
||||||
|
) => Promise<void> | void
|
||||||
>;
|
>;
|
||||||
|
|
||||||
nodeExecuteAfter: Array<
|
nodeExecuteAfter: Array<
|
||||||
|
|||||||
@@ -232,8 +232,9 @@ export class SupplyDataContext extends BaseExecuteContext implements ISupplyData
|
|||||||
let taskData: ITaskData | undefined;
|
let taskData: ITaskData | undefined;
|
||||||
if (type === 'input') {
|
if (type === 'input') {
|
||||||
taskData = {
|
taskData = {
|
||||||
startTime: new Date().getTime(),
|
startTime: Date.now(),
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: additionalData.currentNodeExecutionIndex++,
|
||||||
executionStatus: 'running',
|
executionStatus: 'running',
|
||||||
source: [null],
|
source: [null],
|
||||||
};
|
};
|
||||||
@@ -277,10 +278,10 @@ export class SupplyDataContext extends BaseExecuteContext implements ISupplyData
|
|||||||
}
|
}
|
||||||
|
|
||||||
runExecutionData.resultData.runData[nodeName][currentNodeRunIndex] = taskData;
|
runExecutionData.resultData.runData[nodeName][currentNodeRunIndex] = taskData;
|
||||||
await additionalData.hooks?.runHook('nodeExecuteBefore', [nodeName]);
|
await additionalData.hooks?.runHook('nodeExecuteBefore', [nodeName, taskData]);
|
||||||
} else {
|
} else {
|
||||||
// Outputs
|
// Outputs
|
||||||
taskData.executionTime = new Date().getTime() - taskData.startTime;
|
taskData.executionTime = Date.now() - taskData.startTime;
|
||||||
|
|
||||||
await additionalData.hooks?.runHook('nodeExecuteAfter', [
|
await additionalData.hooks?.runHook('nodeExecuteAfter', [
|
||||||
nodeName,
|
nodeName,
|
||||||
|
|||||||
@@ -557,6 +557,7 @@ describe('findStartNodes', () => {
|
|||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
data: { main: [[], [{ json: { name: 'loop' } }]] },
|
data: { main: [[], [{ json: { name: 'loop' } }]] },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export function toITaskData(taskData: TaskData[]): ITaskData {
|
|||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
data: {},
|
data: {},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ test('toITaskData', function () {
|
|||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
source: [],
|
source: [],
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
data: {
|
data: {
|
||||||
main: [[{ json: { value: 1 } }]],
|
main: [[{ json: { value: 1 } }]],
|
||||||
},
|
},
|
||||||
@@ -18,6 +19,7 @@ test('toITaskData', function () {
|
|||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
source: [],
|
source: [],
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
data: {
|
data: {
|
||||||
main: [null, [{ json: { value: 1 } }]],
|
main: [null, [{ json: { value: 1 } }]],
|
||||||
},
|
},
|
||||||
@@ -32,6 +34,7 @@ test('toITaskData', function () {
|
|||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
source: [],
|
source: [],
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
data: {
|
data: {
|
||||||
[NodeConnectionTypes.AiAgent]: [null, [{ json: { value: 1 } }]],
|
[NodeConnectionTypes.AiAgent]: [null, [{ json: { value: 1 } }]],
|
||||||
},
|
},
|
||||||
@@ -46,6 +49,7 @@ test('toITaskData', function () {
|
|||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
data: {
|
data: {
|
||||||
main: [
|
main: [
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ import type {
|
|||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
CloseFunction,
|
CloseFunction,
|
||||||
StartNodeData,
|
StartNodeData,
|
||||||
NodeExecutionHint,
|
|
||||||
IRunNodeResponse,
|
IRunNodeResponse,
|
||||||
IWorkflowIssues,
|
IWorkflowIssues,
|
||||||
INodeIssues,
|
INodeIssues,
|
||||||
INodeType,
|
INodeType,
|
||||||
|
ITaskStartedData,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import {
|
import {
|
||||||
LoggerProxy as Logger,
|
LoggerProxy as Logger,
|
||||||
@@ -1304,11 +1304,8 @@ export class WorkflowExecute {
|
|||||||
// Variables which hold temporary data for each node-execution
|
// Variables which hold temporary data for each node-execution
|
||||||
let executionData: IExecuteData;
|
let executionData: IExecuteData;
|
||||||
let executionError: ExecutionBaseError | undefined;
|
let executionError: ExecutionBaseError | undefined;
|
||||||
let executionHints: NodeExecutionHint[] = [];
|
|
||||||
let executionNode: INode;
|
let executionNode: INode;
|
||||||
let nodeSuccessData: INodeExecutionData[][] | null | undefined;
|
|
||||||
let runIndex: number;
|
let runIndex: number;
|
||||||
let startTime: number;
|
|
||||||
|
|
||||||
if (this.runExecutionData.startData === undefined) {
|
if (this.runExecutionData.startData === undefined) {
|
||||||
this.runExecutionData.startData = {};
|
this.runExecutionData.startData = {};
|
||||||
@@ -1356,19 +1353,20 @@ export class WorkflowExecute {
|
|||||||
// Set the incoming data of the node that it can be saved correctly
|
// Set the incoming data of the node that it can be saved correctly
|
||||||
|
|
||||||
executionData = this.runExecutionData.executionData!.nodeExecutionStack[0];
|
executionData = this.runExecutionData.executionData!.nodeExecutionStack[0];
|
||||||
|
const taskData: ITaskData = {
|
||||||
|
startTime: Date.now(),
|
||||||
|
executionIndex: 0,
|
||||||
|
executionTime: 0,
|
||||||
|
data: {
|
||||||
|
main: executionData.data.main,
|
||||||
|
},
|
||||||
|
source: [],
|
||||||
|
executionStatus: 'error',
|
||||||
|
hints: [],
|
||||||
|
};
|
||||||
this.runExecutionData.resultData = {
|
this.runExecutionData.resultData = {
|
||||||
runData: {
|
runData: {
|
||||||
[executionData.node.name]: [
|
[executionData.node.name]: [taskData],
|
||||||
{
|
|
||||||
startTime,
|
|
||||||
executionTime: new Date().getTime() - startTime,
|
|
||||||
data: {
|
|
||||||
main: executionData.data.main,
|
|
||||||
} as ITaskDataConnections,
|
|
||||||
source: [],
|
|
||||||
executionStatus: 'error',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
lastNodeExecuted: executionData.node.name,
|
lastNodeExecuted: executionData.node.name,
|
||||||
error: executionError,
|
error: executionError,
|
||||||
@@ -1391,13 +1389,19 @@ export class WorkflowExecute {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeSuccessData = null;
|
let nodeSuccessData: INodeExecutionData[][] | null | undefined = null;
|
||||||
executionError = undefined;
|
executionError = undefined;
|
||||||
executionHints = [];
|
|
||||||
executionData =
|
executionData =
|
||||||
this.runExecutionData.executionData!.nodeExecutionStack.shift() as IExecuteData;
|
this.runExecutionData.executionData!.nodeExecutionStack.shift() as IExecuteData;
|
||||||
executionNode = executionData.node;
|
executionNode = executionData.node;
|
||||||
|
|
||||||
|
const taskStartedData: ITaskStartedData = {
|
||||||
|
startTime: Date.now(),
|
||||||
|
executionIndex: this.additionalData.currentNodeExecutionIndex++,
|
||||||
|
source: !executionData.source ? [] : executionData.source.main,
|
||||||
|
hints: [],
|
||||||
|
};
|
||||||
|
|
||||||
// Update the pairedItem information on items
|
// Update the pairedItem information on items
|
||||||
const newTaskDataConnections: ITaskDataConnections = {};
|
const newTaskDataConnections: ITaskDataConnections = {};
|
||||||
for (const connectionType of Object.keys(executionData.data)) {
|
for (const connectionType of Object.keys(executionData.data)) {
|
||||||
@@ -1425,7 +1429,7 @@ export class WorkflowExecute {
|
|||||||
node: executionNode.name,
|
node: executionNode.name,
|
||||||
workflowId: workflow.id,
|
workflowId: workflow.id,
|
||||||
});
|
});
|
||||||
await hooks.runHook('nodeExecuteBefore', [executionNode.name]);
|
await hooks.runHook('nodeExecuteBefore', [executionNode.name, taskStartedData]);
|
||||||
|
|
||||||
// Get the index of the current run
|
// Get the index of the current run
|
||||||
runIndex = 0;
|
runIndex = 0;
|
||||||
@@ -1457,8 +1461,6 @@ export class WorkflowExecute {
|
|||||||
continue executionLoop;
|
continue executionLoop;
|
||||||
}
|
}
|
||||||
|
|
||||||
startTime = new Date().getTime();
|
|
||||||
|
|
||||||
let maxTries = 1;
|
let maxTries = 1;
|
||||||
if (executionData.node.retryOnFail === true) {
|
if (executionData.node.retryOnFail === true) {
|
||||||
// TODO: Remove the hardcoded default-values here and also in NodeSettings.vue
|
// TODO: Remove the hardcoded default-values here and also in NodeSettings.vue
|
||||||
@@ -1534,7 +1536,7 @@ export class WorkflowExecute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (runNodeData.hints?.length) {
|
if (runNodeData.hints?.length) {
|
||||||
executionHints.push(...runNodeData.hints);
|
taskStartedData.hints!.push(...runNodeData.hints);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodeSuccessData && executionData.node.onError === 'continueErrorOutput') {
|
if (nodeSuccessData && executionData.node.onError === 'continueErrorOutput') {
|
||||||
@@ -1631,10 +1633,8 @@ export class WorkflowExecute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const taskData: ITaskData = {
|
const taskData: ITaskData = {
|
||||||
hints: executionHints,
|
...taskStartedData,
|
||||||
startTime,
|
executionTime: Date.now() - taskStartedData.startTime,
|
||||||
executionTime: new Date().getTime() - startTime,
|
|
||||||
source: !executionData.source ? [] : executionData.source.main,
|
|
||||||
metadata: executionData.metadata,
|
metadata: executionData.metadata,
|
||||||
executionStatus: this.runExecutionData.waitTill ? 'waiting' : 'success',
|
executionStatus: this.runExecutionData.waitTill ? 'waiting' : 'success',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -51,14 +51,10 @@ export function NodeTypes(nodeTypes: INodeTypeData = predefinedNodesTypes): INod
|
|||||||
|
|
||||||
export function WorkflowExecuteAdditionalData(
|
export function WorkflowExecuteAdditionalData(
|
||||||
waitPromise: IDeferredPromise<IRun>,
|
waitPromise: IDeferredPromise<IRun>,
|
||||||
nodeExecutionOrder: string[],
|
|
||||||
): IWorkflowExecuteAdditionalData {
|
): IWorkflowExecuteAdditionalData {
|
||||||
const hooks = new ExecutionLifecycleHooks('trigger', '1', mock());
|
const hooks = new ExecutionLifecycleHooks('trigger', '1', mock());
|
||||||
hooks.addHandler('nodeExecuteAfter', (nodeName) => {
|
|
||||||
nodeExecutionOrder.push(nodeName);
|
|
||||||
});
|
|
||||||
hooks.addHandler('workflowExecuteAfter', (fullRunData) => waitPromise.resolve(fullRunData));
|
hooks.addHandler('workflowExecuteAfter', (fullRunData) => waitPromise.resolve(fullRunData));
|
||||||
return mock<IWorkflowExecuteAdditionalData>({ hooks });
|
return mock<IWorkflowExecuteAdditionalData>({ hooks, currentNodeExecutionIndex: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const preparePinData = (pinData: IDataObject) => {
|
const preparePinData = (pinData: IDataObject) => {
|
||||||
|
|||||||
@@ -256,6 +256,7 @@ describe('CanvasChat', () => {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
executionIndex: 0,
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
source: [null],
|
source: [null],
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ export const aiChatExecutionResponse: IExecutionResponse = {
|
|||||||
'AI Agent': [
|
'AI Agent': [
|
||||||
{
|
{
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
startTime: +new Date('2025-03-26T00:00:00.002Z'),
|
startTime: Date.parse('2025-03-26T00:00:00.002Z'),
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 1778,
|
executionTime: 1778,
|
||||||
source: [],
|
source: [],
|
||||||
data: {},
|
data: {},
|
||||||
@@ -80,7 +81,8 @@ export const aiChatExecutionResponse: IExecutionResponse = {
|
|||||||
'AI Model': [
|
'AI Model': [
|
||||||
{
|
{
|
||||||
executionStatus: 'error',
|
executionStatus: 'error',
|
||||||
startTime: +new Date('2025-03-26T00:00:00.003Z'),
|
startTime: Date.parse('2025-03-26T00:00:00.003Z'),
|
||||||
|
executionIndex: 1,
|
||||||
executionTime: 1777,
|
executionTime: 1777,
|
||||||
source: [],
|
source: [],
|
||||||
error: new WorkflowOperationError('Test error', aiModelNode, 'Test error description'),
|
error: new WorkflowOperationError('Test error', aiModelNode, 'Test error description'),
|
||||||
@@ -121,7 +123,8 @@ export const aiManualExecutionResponse: IExecutionResponse = {
|
|||||||
'AI Agent': [
|
'AI Agent': [
|
||||||
{
|
{
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
startTime: +new Date('2025-03-30T00:00:00.002Z'),
|
startTime: Date.parse('2025-03-30T00:00:00.002Z'),
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 12,
|
executionTime: 12,
|
||||||
source: [],
|
source: [],
|
||||||
data: {},
|
data: {},
|
||||||
@@ -130,7 +133,8 @@ export const aiManualExecutionResponse: IExecutionResponse = {
|
|||||||
'AI Model': [
|
'AI Model': [
|
||||||
{
|
{
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
startTime: +new Date('2025-03-30T00:00:00.003Z'),
|
startTime: Date.parse('2025-03-30T00:00:00.003Z'),
|
||||||
|
executionIndex: 1,
|
||||||
executionTime: 3456,
|
executionTime: 3456,
|
||||||
source: [],
|
source: [],
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -130,8 +130,9 @@ export function useChatMessaging({
|
|||||||
inputPayload.binary = binaryData;
|
inputPayload.binary = binaryData;
|
||||||
}
|
}
|
||||||
const nodeData: ITaskData = {
|
const nodeData: ITaskData = {
|
||||||
startTime: new Date().getTime(),
|
startTime: Date.now(),
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
data: {
|
data: {
|
||||||
main: [[inputPayload]],
|
main: [[inputPayload]],
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ describe('InputPanel', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
data: {},
|
data: {},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -359,8 +359,9 @@ describe('RunData', () => {
|
|||||||
const { getByTestId, queryByTestId } = render({
|
const { getByTestId, queryByTestId } = render({
|
||||||
runs: [
|
runs: [
|
||||||
{
|
{
|
||||||
startTime: new Date().getTime(),
|
startTime: Date.now(),
|
||||||
executionTime: new Date().getTime(),
|
executionIndex: 0,
|
||||||
|
executionTime: 1,
|
||||||
data: {
|
data: {
|
||||||
main: [[{ json: {} }]],
|
main: [[{ json: {} }]],
|
||||||
},
|
},
|
||||||
@@ -368,8 +369,9 @@ describe('RunData', () => {
|
|||||||
metadata,
|
metadata,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
startTime: new Date().getTime(),
|
startTime: Date.now(),
|
||||||
executionTime: new Date().getTime(),
|
executionIndex: 1,
|
||||||
|
executionTime: 1,
|
||||||
data: {
|
data: {
|
||||||
main: [[{ json: {} }]],
|
main: [[{ json: {} }]],
|
||||||
},
|
},
|
||||||
@@ -413,6 +415,7 @@ describe('RunData', () => {
|
|||||||
{
|
{
|
||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1737643696893,
|
startTime: 1737643696893,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 2,
|
executionTime: 2,
|
||||||
source: [
|
source: [
|
||||||
{
|
{
|
||||||
@@ -598,8 +601,9 @@ describe('RunData', () => {
|
|||||||
runs?: ITaskData[];
|
runs?: ITaskData[];
|
||||||
}) => {
|
}) => {
|
||||||
const defaultRun: ITaskData = {
|
const defaultRun: ITaskData = {
|
||||||
startTime: new Date().getTime(),
|
startTime: Date.now(),
|
||||||
executionTime: new Date().getTime(),
|
executionIndex: 0,
|
||||||
|
executionTime: 1,
|
||||||
data: {
|
data: {
|
||||||
main: [defaultRunItems ?? [{ json: {} }]],
|
main: [defaultRunItems ?? [{ json: {} }]],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ describe(getTreeNodeData, () => {
|
|||||||
function createTaskData(partialData: Partial<ITaskData>): ITaskData {
|
function createTaskData(partialData: Partial<ITaskData>): ITaskData {
|
||||||
return {
|
return {
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 1,
|
executionTime: 1,
|
||||||
source: [],
|
source: [],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
@@ -29,10 +30,10 @@ describe(getTreeNodeData, () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const taskDataByNodeName: Record<string, ITaskData[]> = {
|
const taskDataByNodeName: Record<string, ITaskData[]> = {
|
||||||
A: [createTaskData({ startTime: +new Date('2025-02-26T00:00:00.000Z') })],
|
A: [createTaskData({ startTime: Date.parse('2025-02-26T00:00:00.000Z') })],
|
||||||
B: [
|
B: [
|
||||||
createTaskData({
|
createTaskData({
|
||||||
startTime: +new Date('2025-02-26T00:00:01.000Z'),
|
startTime: Date.parse('2025-02-26T00:00:01.000Z'),
|
||||||
data: {
|
data: {
|
||||||
main: [
|
main: [
|
||||||
[
|
[
|
||||||
@@ -50,7 +51,7 @@ describe(getTreeNodeData, () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
createTaskData({
|
createTaskData({
|
||||||
startTime: +new Date('2025-02-26T00:00:03.000Z'),
|
startTime: Date.parse('2025-02-26T00:00:03.000Z'),
|
||||||
data: {
|
data: {
|
||||||
main: [
|
main: [
|
||||||
[
|
[
|
||||||
@@ -70,7 +71,7 @@ describe(getTreeNodeData, () => {
|
|||||||
],
|
],
|
||||||
C: [
|
C: [
|
||||||
createTaskData({
|
createTaskData({
|
||||||
startTime: +new Date('2025-02-26T00:00:02.000Z'),
|
startTime: Date.parse('2025-02-26T00:00:02.000Z'),
|
||||||
data: {
|
data: {
|
||||||
main: [
|
main: [
|
||||||
[
|
[
|
||||||
@@ -87,7 +88,7 @@ describe(getTreeNodeData, () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
createTaskData({ startTime: +new Date('2025-02-26T00:00:04.000Z') }),
|
createTaskData({ startTime: Date.parse('2025-02-26T00:00:04.000Z') }),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -117,7 +118,7 @@ describe(getTreeNodeData, () => {
|
|||||||
id: 'B',
|
id: 'B',
|
||||||
node: 'B',
|
node: 'B',
|
||||||
runIndex: 0,
|
runIndex: 0,
|
||||||
startTime: +new Date('2025-02-26T00:00:01.000Z'),
|
startTime: Date.parse('2025-02-26T00:00:01.000Z'),
|
||||||
parent: expect.objectContaining({ node: 'A' }),
|
parent: expect.objectContaining({ node: 'A' }),
|
||||||
consumedTokens: {
|
consumedTokens: {
|
||||||
completionTokens: 1,
|
completionTokens: 1,
|
||||||
@@ -132,7 +133,7 @@ describe(getTreeNodeData, () => {
|
|||||||
id: 'C',
|
id: 'C',
|
||||||
node: 'C',
|
node: 'C',
|
||||||
runIndex: 0,
|
runIndex: 0,
|
||||||
startTime: +new Date('2025-02-26T00:00:02.000Z'),
|
startTime: Date.parse('2025-02-26T00:00:02.000Z'),
|
||||||
parent: expect.objectContaining({ node: 'B' }),
|
parent: expect.objectContaining({ node: 'B' }),
|
||||||
consumedTokens: {
|
consumedTokens: {
|
||||||
completionTokens: 7,
|
completionTokens: 7,
|
||||||
@@ -148,7 +149,7 @@ describe(getTreeNodeData, () => {
|
|||||||
id: 'B',
|
id: 'B',
|
||||||
node: 'B',
|
node: 'B',
|
||||||
runIndex: 1,
|
runIndex: 1,
|
||||||
startTime: +new Date('2025-02-26T00:00:03.000Z'),
|
startTime: Date.parse('2025-02-26T00:00:03.000Z'),
|
||||||
parent: expect.objectContaining({ node: 'A' }),
|
parent: expect.objectContaining({ node: 'A' }),
|
||||||
consumedTokens: {
|
consumedTokens: {
|
||||||
completionTokens: 4,
|
completionTokens: 4,
|
||||||
@@ -163,7 +164,7 @@ describe(getTreeNodeData, () => {
|
|||||||
id: 'C',
|
id: 'C',
|
||||||
node: 'C',
|
node: 'C',
|
||||||
runIndex: 1,
|
runIndex: 1,
|
||||||
startTime: +new Date('2025-02-26T00:00:04.000Z'),
|
startTime: Date.parse('2025-02-26T00:00:04.000Z'),
|
||||||
parent: expect.objectContaining({ node: 'B' }),
|
parent: expect.objectContaining({ node: 'B' }),
|
||||||
consumedTokens: {
|
consumedTokens: {
|
||||||
completionTokens: 0,
|
completionTokens: 0,
|
||||||
|
|||||||
@@ -68,8 +68,9 @@ async function createPiniaWithActiveNode() {
|
|||||||
runData: {
|
runData: {
|
||||||
[node.name]: [
|
[node.name]: [
|
||||||
{
|
{
|
||||||
startTime: new Date().getTime(),
|
startTime: Date.now(),
|
||||||
executionTime: new Date().getTime(),
|
executionIndex: 0,
|
||||||
|
executionTime: 1,
|
||||||
data: {
|
data: {
|
||||||
main: [
|
main: [
|
||||||
[
|
[
|
||||||
@@ -91,8 +92,9 @@ async function createPiniaWithActiveNode() {
|
|||||||
source: [null],
|
source: [null],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
startTime: new Date().getTime(),
|
startTime: Date.now(),
|
||||||
executionTime: new Date().getTime(),
|
executionIndex: 1,
|
||||||
|
executionTime: 1,
|
||||||
data: {
|
data: {
|
||||||
main: [
|
main: [
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -278,6 +278,7 @@ export const SUPPORT_CHAT_TEST_PAYLOAD: ChatRequest.RequestPayload = {
|
|||||||
{
|
{
|
||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1737540693122,
|
startTime: 1737540693122,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 1,
|
executionTime: 1,
|
||||||
source: [],
|
source: [],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
@@ -287,6 +288,7 @@ export const SUPPORT_CHAT_TEST_PAYLOAD: ChatRequest.RequestPayload = {
|
|||||||
{
|
{
|
||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1737540693124,
|
startTime: 1737540693124,
|
||||||
|
executionIndex: 1,
|
||||||
executionTime: 2,
|
executionTime: 2,
|
||||||
source: [
|
source: [
|
||||||
{
|
{
|
||||||
@@ -300,6 +302,7 @@ export const SUPPORT_CHAT_TEST_PAYLOAD: ChatRequest.RequestPayload = {
|
|||||||
{
|
{
|
||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1737540693126,
|
startTime: 1737540693126,
|
||||||
|
executionIndex: 2,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
source: [
|
source: [
|
||||||
{
|
{
|
||||||
@@ -313,6 +316,7 @@ export const SUPPORT_CHAT_TEST_PAYLOAD: ChatRequest.RequestPayload = {
|
|||||||
{
|
{
|
||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1737540693127,
|
startTime: 1737540693127,
|
||||||
|
executionIndex: 3,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
source: [
|
source: [
|
||||||
{
|
{
|
||||||
@@ -326,6 +330,7 @@ export const SUPPORT_CHAT_TEST_PAYLOAD: ChatRequest.RequestPayload = {
|
|||||||
{
|
{
|
||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1737540693127,
|
startTime: 1737540693127,
|
||||||
|
executionIndex: 4,
|
||||||
executionTime: 28,
|
executionTime: 28,
|
||||||
source: [
|
source: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -460,6 +460,7 @@ const testExecutionData: IRunExecutionData['resultData'] = {
|
|||||||
{
|
{
|
||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1732882780588,
|
startTime: 1732882780588,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 4,
|
executionTime: 4,
|
||||||
source: [],
|
source: [],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
@@ -481,6 +482,7 @@ const testExecutionData: IRunExecutionData['resultData'] = {
|
|||||||
{
|
{
|
||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1732882780593,
|
startTime: 1732882780593,
|
||||||
|
executionIndex: 1,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
source: [
|
source: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -400,6 +400,7 @@ describe('useCanvasMapping', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
data: {
|
data: {
|
||||||
[NodeConnectionTypes.Main]: [[{ json: {} }, { json: {} }]],
|
[NodeConnectionTypes.Main]: [[{ json: {} }, { json: {} }]],
|
||||||
@@ -443,6 +444,7 @@ describe('useCanvasMapping', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
data: {
|
data: {
|
||||||
[NodeConnectionTypes.Main]: [[{ json: {} }]],
|
[NodeConnectionTypes.Main]: [[{ json: {} }]],
|
||||||
@@ -455,6 +457,7 @@ describe('useCanvasMapping', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
data: {
|
data: {
|
||||||
[NodeConnectionTypes.Main]: [[{ json: {} }, { json: {} }, { json: {} }]],
|
[NodeConnectionTypes.Main]: [[{ json: {} }, { json: {} }, { json: {} }]],
|
||||||
@@ -511,6 +514,7 @@ describe('useCanvasMapping', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
data: {
|
data: {
|
||||||
[NodeConnectionTypes.Main]: [[{ json: {} }]],
|
[NodeConnectionTypes.Main]: [[{ json: {} }]],
|
||||||
@@ -519,6 +523,7 @@ describe('useCanvasMapping', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 1,
|
||||||
source: [],
|
source: [],
|
||||||
data: {
|
data: {
|
||||||
[NodeConnectionTypes.Main]: [[{ json: {} }, { json: {} }, { json: {} }]],
|
[NodeConnectionTypes.Main]: [[{ json: {} }, { json: {} }, { json: {} }]],
|
||||||
@@ -527,6 +532,7 @@ describe('useCanvasMapping', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 2,
|
||||||
source: [],
|
source: [],
|
||||||
data: {
|
data: {
|
||||||
[NodeConnectionTypes.Main]: [[{ json: {} }, { json: {} }]],
|
[NodeConnectionTypes.Main]: [[{ json: {} }, { json: {} }]],
|
||||||
@@ -722,6 +728,7 @@ describe('useCanvasMapping', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
error: mock<NodeApiError>({
|
error: mock<NodeApiError>({
|
||||||
message: errorMessage,
|
message: errorMessage,
|
||||||
@@ -753,6 +760,7 @@ describe('useCanvasMapping', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
error: mock<NodeApiError>({
|
error: mock<NodeApiError>({
|
||||||
message: errorMessage,
|
message: errorMessage,
|
||||||
@@ -783,6 +791,7 @@ describe('useCanvasMapping', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
error: mock<NodeApiError>({
|
error: mock<NodeApiError>({
|
||||||
message: 'Error 1',
|
message: 'Error 1',
|
||||||
@@ -792,6 +801,7 @@ describe('useCanvasMapping', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 1,
|
||||||
source: [],
|
source: [],
|
||||||
error: mock<NodeApiError>({
|
error: mock<NodeApiError>({
|
||||||
message: 'Error 2',
|
message: 'Error 2',
|
||||||
@@ -855,6 +865,7 @@ describe('useCanvasMapping', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
error: mock<NodeApiError>({
|
error: mock<NodeApiError>({
|
||||||
message: 'Execution error',
|
message: 'Execution error',
|
||||||
@@ -894,6 +905,7 @@ describe('useCanvasMapping', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
error: mock<NodeApiError>({
|
error: mock<NodeApiError>({
|
||||||
message: 'Execution error',
|
message: 'Execution error',
|
||||||
@@ -948,6 +960,7 @@ describe('useCanvasMapping', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
executionStatus: 'crashed',
|
executionStatus: 'crashed',
|
||||||
},
|
},
|
||||||
@@ -976,6 +989,7 @@ describe('useCanvasMapping', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
executionStatus: 'error',
|
executionStatus: 'error',
|
||||||
},
|
},
|
||||||
@@ -1057,6 +1071,7 @@ describe('useCanvasMapping', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
executionStatus: 'error',
|
executionStatus: 'error',
|
||||||
error: mock<NodeApiError>({
|
error: mock<NodeApiError>({
|
||||||
@@ -1096,6 +1111,7 @@ describe('useCanvasMapping', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
executionStatus: 'error',
|
executionStatus: 'error',
|
||||||
},
|
},
|
||||||
@@ -1104,6 +1120,7 @@ describe('useCanvasMapping', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -553,7 +553,9 @@ describe('useDataSchema', () => {
|
|||||||
data: {
|
data: {
|
||||||
resultData: {
|
resultData: {
|
||||||
runData: {
|
runData: {
|
||||||
[runDataKey ?? name]: [{ data, startTime: 0, executionTime: 0, source: [] }],
|
[runDataKey ?? name]: [
|
||||||
|
{ data, startTime: 0, executionTime: 0, executionIndex: 0, source: [] },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -619,17 +621,20 @@ describe('useDataSchema', () => {
|
|||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 1,
|
||||||
source: [],
|
source: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
data: { [Main]: [null, mockExecutionDataMarker] },
|
data: { [Main]: [null, mockExecutionDataMarker] },
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 2,
|
||||||
source: [],
|
source: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ describe(useNodeDirtiness, () => {
|
|||||||
{
|
{
|
||||||
startTime: +runAt,
|
startTime: +runAt,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
source: [],
|
source: [],
|
||||||
},
|
},
|
||||||
@@ -423,6 +424,7 @@ describe(useNodeDirtiness, () => {
|
|||||||
{
|
{
|
||||||
startTime: +NODE_RUN_AT,
|
startTime: +NODE_RUN_AT,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
source: [],
|
source: [],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -354,6 +354,7 @@ describe('useRunWorkflow({ router })', () => {
|
|||||||
[parentName]: [
|
[parentName]: [
|
||||||
{
|
{
|
||||||
startTime: 1,
|
startTime: 1,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
source: [],
|
source: [],
|
||||||
},
|
},
|
||||||
@@ -361,6 +362,7 @@ describe('useRunWorkflow({ router })', () => {
|
|||||||
[executeName]: [
|
[executeName]: [
|
||||||
{
|
{
|
||||||
startTime: 1,
|
startTime: 1,
|
||||||
|
executionIndex: 1,
|
||||||
executionTime: 8,
|
executionTime: 8,
|
||||||
source: [
|
source: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const runExecutionData: IRunExecutionData = {
|
|||||||
Start: [
|
Start: [
|
||||||
{
|
{
|
||||||
startTime: 1,
|
startTime: 1,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 1,
|
executionTime: 1,
|
||||||
data: {
|
data: {
|
||||||
main: [
|
main: [
|
||||||
@@ -25,6 +26,7 @@ const runExecutionData: IRunExecutionData = {
|
|||||||
Function: [
|
Function: [
|
||||||
{
|
{
|
||||||
startTime: 1,
|
startTime: 1,
|
||||||
|
executionIndex: 1,
|
||||||
executionTime: 1,
|
executionTime: 1,
|
||||||
data: {
|
data: {
|
||||||
main: [
|
main: [
|
||||||
@@ -62,6 +64,7 @@ const runExecutionData: IRunExecutionData = {
|
|||||||
Rename: [
|
Rename: [
|
||||||
{
|
{
|
||||||
startTime: 1,
|
startTime: 1,
|
||||||
|
executionIndex: 2,
|
||||||
executionTime: 1,
|
executionTime: 1,
|
||||||
data: {
|
data: {
|
||||||
main: [
|
main: [
|
||||||
@@ -99,6 +102,7 @@ const runExecutionData: IRunExecutionData = {
|
|||||||
End: [
|
End: [
|
||||||
{
|
{
|
||||||
startTime: 1,
|
startTime: 1,
|
||||||
|
executionIndex: 3,
|
||||||
executionTime: 1,
|
executionTime: 1,
|
||||||
data: {
|
data: {
|
||||||
main: [
|
main: [
|
||||||
|
|||||||
@@ -849,6 +849,7 @@ function generateMockExecutionEvents() {
|
|||||||
data: {
|
data: {
|
||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1727867966633,
|
startTime: 1727867966633,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 1,
|
executionTime: 1,
|
||||||
source: [],
|
source: [],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
@@ -873,6 +874,7 @@ function generateMockExecutionEvents() {
|
|||||||
data: {
|
data: {
|
||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1727869043441,
|
startTime: 1727869043441,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 2,
|
executionTime: 2,
|
||||||
source: [
|
source: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const MOCK_EXECUTION: Partial<IExecutionResponse> = {
|
|||||||
'When clicking ‘Test workflow’': [
|
'When clicking ‘Test workflow’': [
|
||||||
{
|
{
|
||||||
startTime: 1706027170005,
|
startTime: 1706027170005,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
source: [],
|
source: [],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
@@ -24,6 +25,7 @@ const MOCK_EXECUTION: Partial<IExecutionResponse> = {
|
|||||||
DebugHelper: [
|
DebugHelper: [
|
||||||
{
|
{
|
||||||
startTime: 1706027170005,
|
startTime: 1706027170005,
|
||||||
|
executionIndex: 1,
|
||||||
executionTime: 1,
|
executionTime: 1,
|
||||||
source: [{ previousNode: 'When clicking ‘Test workflow’' }],
|
source: [{ previousNode: 'When clicking ‘Test workflow’' }],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
@@ -58,6 +60,7 @@ const MOCK_EXECUTION: Partial<IExecutionResponse> = {
|
|||||||
If: [
|
If: [
|
||||||
{
|
{
|
||||||
startTime: 1706027170006,
|
startTime: 1706027170006,
|
||||||
|
executionIndex: 2,
|
||||||
executionTime: 1,
|
executionTime: 1,
|
||||||
source: [{ previousNode: 'DebugHelper' }],
|
source: [{ previousNode: 'DebugHelper' }],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
@@ -94,6 +97,7 @@ const MOCK_EXECUTION: Partial<IExecutionResponse> = {
|
|||||||
'Edit Fields': [
|
'Edit Fields': [
|
||||||
{
|
{
|
||||||
startTime: 1706027170008,
|
startTime: 1706027170008,
|
||||||
|
executionIndex: 3,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
source: [{ previousNode: 'If', previousNodeOutput: 1 }],
|
source: [{ previousNode: 'If', previousNodeOutput: 1 }],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
@@ -116,6 +120,7 @@ const MOCK_EXECUTION: Partial<IExecutionResponse> = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
startTime: 1706027170009,
|
startTime: 1706027170009,
|
||||||
|
executionIndex: 3,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
source: [{ previousNode: 'If' }],
|
source: [{ previousNode: 'If' }],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
@@ -140,6 +145,7 @@ const MOCK_EXECUTION: Partial<IExecutionResponse> = {
|
|||||||
'Edit Fields1': [
|
'Edit Fields1': [
|
||||||
{
|
{
|
||||||
startTime: 1706027170008,
|
startTime: 1706027170008,
|
||||||
|
executionIndex: 4,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
source: [{ previousNode: 'Edit Fields' }],
|
source: [{ previousNode: 'Edit Fields' }],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
@@ -162,6 +168,7 @@ const MOCK_EXECUTION: Partial<IExecutionResponse> = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
startTime: 1706027170010,
|
startTime: 1706027170010,
|
||||||
|
executionIndex: 5,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
source: [{ previousNode: 'Edit Fields', previousNodeRun: 1 }],
|
source: [{ previousNode: 'Edit Fields', previousNodeRun: 1 }],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
@@ -329,6 +336,7 @@ describe('pairedItemUtils', () => {
|
|||||||
Start: [
|
Start: [
|
||||||
{
|
{
|
||||||
startTime: 1706027170005,
|
startTime: 1706027170005,
|
||||||
|
executionIndex: 0,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
source: [],
|
source: [],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
@@ -340,6 +348,7 @@ describe('pairedItemUtils', () => {
|
|||||||
DebugHelper: [
|
DebugHelper: [
|
||||||
{
|
{
|
||||||
startTime: 1706027170005,
|
startTime: 1706027170005,
|
||||||
|
executionIndex: 1,
|
||||||
executionTime: 1,
|
executionTime: 1,
|
||||||
source: [{ previousNode: 'Start' }],
|
source: [{ previousNode: 'Start' }],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
@@ -409,8 +418,9 @@ describe('pairedItemUtils', () => {
|
|||||||
runData: Object.fromEntries(
|
runData: Object.fromEntries(
|
||||||
Array.from({ length: nodeCount }).map<[string, ITaskData[]]>((_, j) => [
|
Array.from({ length: nodeCount }).map<[string, ITaskData[]]>((_, j) => [
|
||||||
`node_${j}`,
|
`node_${j}`,
|
||||||
Array.from({ length: runCount }).map(() => ({
|
Array.from({ length: runCount }).map((_, executionIndex) => ({
|
||||||
startTime: 1706027170005,
|
startTime: 1706027170005,
|
||||||
|
executionIndex,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
source: j === 0 ? [] : [{ previousNode: `node_${j - 1}` }],
|
source: j === 0 ? [] : [{ previousNode: `node_${j - 1}` }],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
|
|||||||
@@ -2180,16 +2180,22 @@ export interface ITaskMetadata {
|
|||||||
subExecutionsCount?: number;
|
subExecutionsCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The data that gets returned when a node runs
|
/** The data that gets returned when a node execution starts */
|
||||||
export interface ITaskData {
|
export interface ITaskStartedData {
|
||||||
startTime: number;
|
startTime: number;
|
||||||
|
/** This index tracks the order in which nodes are executed */
|
||||||
|
executionIndex: number;
|
||||||
|
source: Array<ISourceData | null>; // Is an array as nodes have multiple inputs
|
||||||
|
hints?: NodeExecutionHint[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The data that gets returned when a node execution ends */
|
||||||
|
export interface ITaskData extends ITaskStartedData {
|
||||||
executionTime: number;
|
executionTime: number;
|
||||||
executionStatus?: ExecutionStatus;
|
executionStatus?: ExecutionStatus;
|
||||||
data?: ITaskDataConnections;
|
data?: ITaskDataConnections;
|
||||||
inputOverride?: ITaskDataConnections;
|
inputOverride?: ITaskDataConnections;
|
||||||
error?: ExecutionError;
|
error?: ExecutionError;
|
||||||
hints?: NodeExecutionHint[];
|
|
||||||
source: Array<ISourceData | null>; // Is an array as nodes have multiple inputs
|
|
||||||
metadata?: ITaskMetadata;
|
metadata?: ITaskMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2336,6 +2342,7 @@ export interface IWorkflowExecuteAdditionalData {
|
|||||||
) => Promise<ExecuteWorkflowData>;
|
) => Promise<ExecuteWorkflowData>;
|
||||||
executionId?: string;
|
executionId?: string;
|
||||||
restartExecutionId?: string;
|
restartExecutionId?: string;
|
||||||
|
currentNodeExecutionIndex: number;
|
||||||
httpResponse?: express.Response;
|
httpResponse?: express.Response;
|
||||||
httpRequest?: express.Request;
|
httpRequest?: express.Request;
|
||||||
restApiUrl: string;
|
restApiUrl: string;
|
||||||
|
|||||||
@@ -916,7 +916,15 @@ export class WorkflowDataProxy {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (pinData) {
|
if (pinData) {
|
||||||
taskData = { data: { main: [pinData] }, startTime: 0, executionTime: 0, source: [] };
|
taskData = {
|
||||||
|
data: {
|
||||||
|
main: [pinData],
|
||||||
|
},
|
||||||
|
startTime: 0,
|
||||||
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
|
source: [],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1512,6 +1512,7 @@ function generateTestWorkflowAndRunData(): { workflow: Partial<IWorkflowBase>; r
|
|||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1727793340927,
|
startTime: 1727793340927,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 0,
|
||||||
source: [],
|
source: [],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
data: { main: [[{ json: {}, pairedItem: { item: 0 } }]] },
|
data: { main: [[{ json: {}, pairedItem: { item: 0 } }]] },
|
||||||
@@ -1522,6 +1523,7 @@ function generateTestWorkflowAndRunData(): { workflow: Partial<IWorkflowBase>; r
|
|||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1727793340928,
|
startTime: 1727793340928,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 1,
|
||||||
source: [{ previousNode: 'Execute Workflow Trigger' }],
|
source: [{ previousNode: 'Execute Workflow Trigger' }],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
data: {
|
data: {
|
||||||
@@ -1555,6 +1557,7 @@ function generateTestWorkflowAndRunData(): { workflow: Partial<IWorkflowBase>; r
|
|||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1727793340928,
|
startTime: 1727793340928,
|
||||||
executionTime: 1,
|
executionTime: 1,
|
||||||
|
executionIndex: 2,
|
||||||
source: [{ previousNode: 'DebugHelper' }],
|
source: [{ previousNode: 'DebugHelper' }],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
data: {
|
data: {
|
||||||
@@ -1586,6 +1589,7 @@ function generateTestWorkflowAndRunData(): { workflow: Partial<IWorkflowBase>; r
|
|||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1727793340931,
|
startTime: 1727793340931,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 3,
|
||||||
source: [{ previousNode: 'Execute Workflow Trigger' }],
|
source: [{ previousNode: 'Execute Workflow Trigger' }],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
data: { main: [[{ json: {}, pairedItem: { item: 0 } }]] },
|
data: { main: [[{ json: {}, pairedItem: { item: 0 } }]] },
|
||||||
@@ -1596,6 +1600,7 @@ function generateTestWorkflowAndRunData(): { workflow: Partial<IWorkflowBase>; r
|
|||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1727793340929,
|
startTime: 1727793340929,
|
||||||
executionTime: 1,
|
executionTime: 1,
|
||||||
|
executionIndex: 4,
|
||||||
source: [{ previousNode: 'Edit Fields' }],
|
source: [{ previousNode: 'Edit Fields' }],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
data: {
|
data: {
|
||||||
@@ -1630,6 +1635,7 @@ function generateTestWorkflowAndRunData(): { workflow: Partial<IWorkflowBase>; r
|
|||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1727793340931,
|
startTime: 1727793340931,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 5,
|
||||||
source: [{ previousNode: 'Edit Fields', previousNodeRun: 1 }],
|
source: [{ previousNode: 'Edit Fields', previousNodeRun: 1 }],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
data: { main: [[], [], [{ json: {}, pairedItem: { item: 0 } }], []] },
|
data: { main: [[], [], [{ json: {}, pairedItem: { item: 0 } }], []] },
|
||||||
@@ -1640,6 +1646,7 @@ function generateTestWorkflowAndRunData(): { workflow: Partial<IWorkflowBase>; r
|
|||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1727793340930,
|
startTime: 1727793340930,
|
||||||
executionTime: 0,
|
executionTime: 0,
|
||||||
|
executionIndex: 6,
|
||||||
source: [{ previousNode: 'Switch', previousNodeOutput: 2 }],
|
source: [{ previousNode: 'Switch', previousNodeOutput: 2 }],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
data: {
|
data: {
|
||||||
@@ -1656,6 +1663,7 @@ function generateTestWorkflowAndRunData(): { workflow: Partial<IWorkflowBase>; r
|
|||||||
hints: [],
|
hints: [],
|
||||||
startTime: 1727793340932,
|
startTime: 1727793340932,
|
||||||
executionTime: 1,
|
executionTime: 1,
|
||||||
|
executionIndex: 7,
|
||||||
source: [{ previousNode: 'Switch', previousNodeOutput: 2, previousNodeRun: 1 }],
|
source: [{ previousNode: 'Switch', previousNodeOutput: 2, previousNodeRun: 1 }],
|
||||||
executionStatus: 'success',
|
executionStatus: 'success',
|
||||||
data: { main: [[{ json: {}, pairedItem: { item: 0 } }]] },
|
data: { main: [[{ json: {}, pairedItem: { item: 0 } }]] },
|
||||||
|
|||||||
@@ -1603,6 +1603,7 @@ describe('Workflow', () => {
|
|||||||
],
|
],
|
||||||
startTime: 1,
|
startTime: 1,
|
||||||
executionTime: 1,
|
executionTime: 1,
|
||||||
|
executionIndex: 0,
|
||||||
data: {
|
data: {
|
||||||
main: [
|
main: [
|
||||||
[
|
[
|
||||||
@@ -1681,6 +1682,7 @@ describe('Workflow', () => {
|
|||||||
{
|
{
|
||||||
startTime: 1,
|
startTime: 1,
|
||||||
executionTime: 1,
|
executionTime: 1,
|
||||||
|
executionIndex: 0,
|
||||||
data: {
|
data: {
|
||||||
main: [
|
main: [
|
||||||
[
|
[
|
||||||
|
|||||||
Reference in New Issue
Block a user