mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 01:26:44 +00:00
refactor(editor): Remove part of getCurrentWorkflow usages (#16148)
This commit is contained in:
@@ -123,6 +123,7 @@
|
|||||||
"ics": "patches/ics.patch",
|
"ics": "patches/ics.patch",
|
||||||
"minifaker": "patches/minifaker.patch",
|
"minifaker": "patches/minifaker.patch",
|
||||||
"z-vue-scan": "patches/z-vue-scan.patch",
|
"z-vue-scan": "patches/z-vue-scan.patch",
|
||||||
|
"@lezer/highlight": "patches/@lezer__highlight.patch",
|
||||||
"v-code-diff": "patches/v-code-diff.patch"
|
"v-code-diff": "patches/v-code-diff.patch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,8 +174,7 @@ function useJsonFieldCompletions() {
|
|||||||
try {
|
try {
|
||||||
const activeNode = ndvStore.activeNode;
|
const activeNode = ndvStore.activeNode;
|
||||||
if (activeNode) {
|
if (activeNode) {
|
||||||
const workflow = workflowsStore.getCurrentWorkflow();
|
const input = workflowsStore.connectionsByDestinationNode[activeNode.name];
|
||||||
const input = workflow.connectionsByDestinationNode[activeNode.name];
|
|
||||||
return input.main[0] ? input.main[0][0].node : null;
|
return input.main[0] ? input.main[0][0].node : null;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ function getMultipleNodesText(nodeName: string): string {
|
|||||||
return '';
|
return '';
|
||||||
|
|
||||||
const activeNodeConnections =
|
const activeNodeConnections =
|
||||||
props.workflow.connectionsByDestinationNode[activeNode.value.name].main || [];
|
workflowsStore.connectionsByDestinationNode[activeNode.value.name].main || [];
|
||||||
// Collect indexes of connected nodes
|
// Collect indexes of connected nodes
|
||||||
const connectedInputIndexes = activeNodeConnections.reduce((acc: number[], node, index) => {
|
const connectedInputIndexes = activeNodeConnections.reduce((acc: number[], node, index) => {
|
||||||
if (node?.[0] && node[0].node === nodeName) return [...acc, index];
|
if (node?.[0] && node[0].node === nodeName) return [...acc, index];
|
||||||
|
|||||||
@@ -804,7 +804,8 @@ function getNodeHints(): NodeHint[] {
|
|||||||
node: node.value,
|
node: node.value,
|
||||||
nodeType: nodeType.value,
|
nodeType: nodeType.value,
|
||||||
nodeOutputData,
|
nodeOutputData,
|
||||||
workflow: props.workflow,
|
nodes: workflowsStore.allNodes,
|
||||||
|
connections: workflowsStore.connectionsBySourceNode,
|
||||||
hasNodeRun: hasNodeRun.value,
|
hasNodeRun: hasNodeRun.value,
|
||||||
hasMultipleInputItems,
|
hasMultipleInputItems,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ const expressionResolveCtx = computed<ExpressionLocalResolveContext | undefined>
|
|||||||
nodeName,
|
nodeName,
|
||||||
additionalKeys: {},
|
additionalKeys: {},
|
||||||
inputNode: findInputNode(),
|
inputNode: findInputNode(),
|
||||||
|
connections: workflowsStore.connectionsBySourceNode,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -216,6 +216,8 @@ describe('useCanvasMapping', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should handle input and output connections', () => {
|
it('should handle input and output connections', () => {
|
||||||
|
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||||
|
|
||||||
const [manualTriggerNode, setNode] = mockNodes.slice(0, 2);
|
const [manualTriggerNode, setNode] = mockNodes.slice(0, 2);
|
||||||
const nodes = [manualTriggerNode, setNode];
|
const nodes = [manualTriggerNode, setNode];
|
||||||
const connections = {
|
const connections = {
|
||||||
@@ -225,6 +227,9 @@ describe('useCanvasMapping', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
workflowsStore.workflow.connections = connections;
|
||||||
|
|
||||||
const workflowObject = createTestWorkflowObject({
|
const workflowObject = createTestWorkflowObject({
|
||||||
nodes,
|
nodes,
|
||||||
connections,
|
connections,
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ import { useNodeHelpers } from './useNodeHelpers';
|
|||||||
import { getTriggerNodeServiceName } from '@/utils/nodeTypesUtils';
|
import { getTriggerNodeServiceName } from '@/utils/nodeTypesUtils';
|
||||||
import { useNodeDirtiness } from '@/composables/useNodeDirtiness';
|
import { useNodeDirtiness } from '@/composables/useNodeDirtiness';
|
||||||
import { getNodeIconSource } from '../utils/nodeIcon';
|
import { getNodeIconSource } from '../utils/nodeIcon';
|
||||||
|
import * as workflowUtils from 'n8n-workflow/common';
|
||||||
|
|
||||||
export function useCanvasMapping({
|
export function useCanvasMapping({
|
||||||
nodes,
|
nodes,
|
||||||
@@ -571,56 +572,62 @@ export function useCanvasMapping({
|
|||||||
}, {});
|
}, {});
|
||||||
});
|
});
|
||||||
|
|
||||||
const mappedNodes = computed<CanvasNode[]>(() => [
|
const mappedNodes = computed<CanvasNode[]>(() => {
|
||||||
...nodes.value.map<CanvasNode>((node) => {
|
const connectionsBySourceNode = connections.value;
|
||||||
const inputConnections = workflowObject.value.connectionsByDestinationNode[node.name] ?? {};
|
const connectionsByDestinationNode =
|
||||||
const outputConnections = workflowObject.value.connectionsBySourceNode[node.name] ?? {};
|
workflowUtils.mapConnectionsByDestination(connectionsBySourceNode);
|
||||||
|
|
||||||
const data: CanvasNodeData = {
|
return [
|
||||||
id: node.id,
|
...nodes.value.map<CanvasNode>((node) => {
|
||||||
name: node.name,
|
const outputConnections = connectionsBySourceNode[node.name] ?? {};
|
||||||
subtitle: nodeSubtitleById.value[node.id] ?? '',
|
const inputConnections = connectionsByDestinationNode[node.name] ?? {};
|
||||||
type: node.type,
|
|
||||||
typeVersion: node.typeVersion,
|
|
||||||
disabled: node.disabled,
|
|
||||||
inputs: nodeInputsById.value[node.id] ?? [],
|
|
||||||
outputs: nodeOutputsById.value[node.id] ?? [],
|
|
||||||
connections: {
|
|
||||||
[CanvasConnectionMode.Input]: inputConnections,
|
|
||||||
[CanvasConnectionMode.Output]: outputConnections,
|
|
||||||
},
|
|
||||||
issues: {
|
|
||||||
items: nodeIssuesById.value[node.id],
|
|
||||||
visible: nodeHasIssuesById.value[node.id],
|
|
||||||
},
|
|
||||||
pinnedData: {
|
|
||||||
count: nodePinnedDataById.value[node.id]?.length ?? 0,
|
|
||||||
visible: !!nodePinnedDataById.value[node.id],
|
|
||||||
},
|
|
||||||
execution: {
|
|
||||||
status: nodeExecutionStatusById.value[node.id],
|
|
||||||
waiting: nodeExecutionWaitingById.value[node.id],
|
|
||||||
waitingForNext: nodeExecutionWaitingForNextById.value[node.id],
|
|
||||||
running: nodeExecutionRunningById.value[node.id],
|
|
||||||
},
|
|
||||||
runData: {
|
|
||||||
outputMap: nodeExecutionRunDataOutputMapById.value[node.id],
|
|
||||||
iterations: nodeExecutionRunDataById.value[node.id]?.length ?? 0,
|
|
||||||
visible: !!nodeExecutionRunDataById.value[node.id],
|
|
||||||
},
|
|
||||||
render: renderTypeByNodeId.value[node.id] ?? { type: 'default', options: {} },
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
const data: CanvasNodeData = {
|
||||||
id: node.id,
|
id: node.id,
|
||||||
label: node.name,
|
name: node.name,
|
||||||
type: 'canvas-node',
|
subtitle: nodeSubtitleById.value[node.id] ?? '',
|
||||||
position: { x: node.position[0], y: node.position[1] },
|
type: node.type,
|
||||||
data,
|
typeVersion: node.typeVersion,
|
||||||
...additionalNodePropertiesById.value[node.id],
|
disabled: node.disabled,
|
||||||
};
|
inputs: nodeInputsById.value[node.id] ?? [],
|
||||||
}),
|
outputs: nodeOutputsById.value[node.id] ?? [],
|
||||||
]);
|
connections: {
|
||||||
|
[CanvasConnectionMode.Input]: inputConnections,
|
||||||
|
[CanvasConnectionMode.Output]: outputConnections,
|
||||||
|
},
|
||||||
|
issues: {
|
||||||
|
items: nodeIssuesById.value[node.id],
|
||||||
|
visible: nodeHasIssuesById.value[node.id],
|
||||||
|
},
|
||||||
|
pinnedData: {
|
||||||
|
count: nodePinnedDataById.value[node.id]?.length ?? 0,
|
||||||
|
visible: !!nodePinnedDataById.value[node.id],
|
||||||
|
},
|
||||||
|
execution: {
|
||||||
|
status: nodeExecutionStatusById.value[node.id],
|
||||||
|
waiting: nodeExecutionWaitingById.value[node.id],
|
||||||
|
waitingForNext: nodeExecutionWaitingForNextById.value[node.id],
|
||||||
|
running: nodeExecutionRunningById.value[node.id],
|
||||||
|
},
|
||||||
|
runData: {
|
||||||
|
outputMap: nodeExecutionRunDataOutputMapById.value[node.id],
|
||||||
|
iterations: nodeExecutionRunDataById.value[node.id]?.length ?? 0,
|
||||||
|
visible: !!nodeExecutionRunDataById.value[node.id],
|
||||||
|
},
|
||||||
|
render: renderTypeByNodeId.value[node.id] ?? { type: 'default', options: {} },
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: node.id,
|
||||||
|
label: node.name,
|
||||||
|
type: 'canvas-node',
|
||||||
|
position: { x: node.position[0], y: node.position[1] },
|
||||||
|
data,
|
||||||
|
...additionalNodePropertiesById.value[node.id],
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
const mappedConnections = computed<CanvasConnection[]>(() => {
|
const mappedConnections = computed<CanvasConnection[]>(() => {
|
||||||
return mapLegacyConnectionsToCanvasConnections(connections.value ?? [], nodes.value ?? []).map(
|
return mapLegacyConnectionsToCanvasConnections(connections.value ?? [], nodes.value ?? []).map(
|
||||||
|
|||||||
@@ -253,6 +253,7 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
|
|||||||
if (sourceData === null) {
|
if (sourceData === null) {
|
||||||
const parentNodes = workflow.getParentNodes(name, NodeConnectionTypes.Main, 1);
|
const parentNodes = workflow.getParentNodes(name, NodeConnectionTypes.Main, 1);
|
||||||
const executeData = workflowHelpers.executeData(
|
const executeData = workflowHelpers.executeData(
|
||||||
|
workflow.connectionsBySourceNode,
|
||||||
parentNodes,
|
parentNodes,
|
||||||
name,
|
name,
|
||||||
NodeConnectionTypes.Main,
|
NodeConnectionTypes.Main,
|
||||||
|
|||||||
@@ -14,11 +14,8 @@ import {
|
|||||||
createTestWorkflowExecutionResponse,
|
createTestWorkflowExecutionResponse,
|
||||||
createTestWorkflowObject,
|
createTestWorkflowObject,
|
||||||
} from '@/__tests__/mocks';
|
} from '@/__tests__/mocks';
|
||||||
import {
|
import { NodeConnectionTypes, WEBHOOK_NODE_TYPE } from 'n8n-workflow';
|
||||||
NodeConnectionTypes,
|
import type { AssignmentCollectionValue, IConnections } from 'n8n-workflow';
|
||||||
WEBHOOK_NODE_TYPE,
|
|
||||||
type AssignmentCollectionValue,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
import * as apiWebhooks from '@n8n/rest-api-client/api/webhooks';
|
import * as apiWebhooks from '@n8n/rest-api-client/api/webhooks';
|
||||||
import { mockedStore } from '@/__tests__/utils';
|
import { mockedStore } from '@/__tests__/utils';
|
||||||
|
|
||||||
@@ -519,7 +516,7 @@ describe('useWorkflowHelpers', () => {
|
|||||||
const inputName = 'main';
|
const inputName = 'main';
|
||||||
const runIndex = 0;
|
const runIndex = 0;
|
||||||
|
|
||||||
const result = executeData(parentNodes, currentNode, inputName, runIndex);
|
const result = executeData({}, parentNodes, currentNode, inputName, runIndex);
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
node: {},
|
node: {},
|
||||||
@@ -538,18 +535,15 @@ describe('useWorkflowHelpers', () => {
|
|||||||
const jsonData = {
|
const jsonData = {
|
||||||
name: 'Test',
|
name: 'Test',
|
||||||
};
|
};
|
||||||
workflowsStore.getCurrentWorkflow.mockReturnValue({
|
|
||||||
connectionsByDestinationNode: {
|
const connectionsBySourceNode: IConnections = {
|
||||||
Set: {
|
Start: {
|
||||||
main: [
|
main: [[{ node: 'Set', index: 0, type: 'main' }]],
|
||||||
[
|
|
||||||
{ node: 'Start', index: 0, type: 'main' },
|
|
||||||
{ node: 'Set', index: 0, type: 'main' },
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
} as never);
|
Set: {
|
||||||
|
main: [[{ node: 'Start', index: 0, type: 'main' }]],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
workflowsStore.workflowExecutionData = {
|
workflowsStore.workflowExecutionData = {
|
||||||
data: {
|
data: {
|
||||||
@@ -575,7 +569,13 @@ describe('useWorkflowHelpers', () => {
|
|||||||
},
|
},
|
||||||
} as unknown as IExecutionResponse;
|
} as unknown as IExecutionResponse;
|
||||||
|
|
||||||
const result = executeData(parentNodes, currentNode, inputName, runIndex);
|
const result = executeData(
|
||||||
|
connectionsBySourceNode,
|
||||||
|
parentNodes,
|
||||||
|
currentNode,
|
||||||
|
inputName,
|
||||||
|
runIndex,
|
||||||
|
);
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
node: {},
|
node: {},
|
||||||
@@ -609,18 +609,15 @@ describe('useWorkflowHelpers', () => {
|
|||||||
const jsonData = {
|
const jsonData = {
|
||||||
name: 'Test',
|
name: 'Test',
|
||||||
};
|
};
|
||||||
workflowsStore.getCurrentWorkflow.mockReturnValue({
|
|
||||||
connectionsByDestinationNode: {
|
const connectionsBySourceNode: IConnections = {
|
||||||
Set: {
|
Start: {
|
||||||
main: [
|
main: [[{ node: 'Set', index: 0, type: 'main' }]],
|
||||||
[
|
|
||||||
{ node: 'Start', index: 0, type: 'main' },
|
|
||||||
{ node: 'Set', index: 0, type: 'main' },
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
} as never);
|
Set: {
|
||||||
|
main: [[{ node: 'Start', index: 0, type: 'main' }]],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
workflowsStore.workflowExecutionData = {
|
workflowsStore.workflowExecutionData = {
|
||||||
data: {
|
data: {
|
||||||
@@ -646,7 +643,13 @@ describe('useWorkflowHelpers', () => {
|
|||||||
},
|
},
|
||||||
} as unknown as IExecutionResponse;
|
} as unknown as IExecutionResponse;
|
||||||
|
|
||||||
const result = executeData(parentNodes, currentNode, inputName, runIndex);
|
const result = executeData(
|
||||||
|
connectionsBySourceNode,
|
||||||
|
parentNodes,
|
||||||
|
currentNode,
|
||||||
|
inputName,
|
||||||
|
runIndex,
|
||||||
|
);
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
node: {},
|
node: {},
|
||||||
@@ -686,22 +689,20 @@ describe('useWorkflowHelpers', () => {
|
|||||||
name: 'Test B',
|
name: 'Test B',
|
||||||
};
|
};
|
||||||
|
|
||||||
workflowsStore.getCurrentWorkflow.mockReturnValue({
|
const connectionsBySourceNode: IConnections = {
|
||||||
connectionsByDestinationNode: {
|
'Parent A': {
|
||||||
Set: {
|
main: [[{ node: 'Set', type: 'main', index: 0 }]],
|
||||||
main: [
|
|
||||||
[
|
|
||||||
{ node: 'Parent A', index: 0, type: 'main' },
|
|
||||||
{ node: 'Set', index: 0, type: 'main' },
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{ node: 'Parent B', index: 0, type: 'main' },
|
|
||||||
{ node: 'Set', index: 0, type: 'main' },
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
} as never);
|
'Parent B': {
|
||||||
|
main: [[{ node: 'Set', type: 'main', index: 1 }]],
|
||||||
|
},
|
||||||
|
Set: {
|
||||||
|
main: [
|
||||||
|
[{ node: 'Set', type: 'main', index: 0 }],
|
||||||
|
[{ node: 'Set', type: 'main', index: 1 }],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
workflowsStore.workflowExecutionData = {
|
workflowsStore.workflowExecutionData = {
|
||||||
data: {
|
data: {
|
||||||
@@ -742,7 +743,13 @@ describe('useWorkflowHelpers', () => {
|
|||||||
},
|
},
|
||||||
} as unknown as IExecutionResponse;
|
} as unknown as IExecutionResponse;
|
||||||
|
|
||||||
const result = executeData(parentNodes, currentNode, inputName, runIndex);
|
const result = executeData(
|
||||||
|
connectionsBySourceNode,
|
||||||
|
parentNodes,
|
||||||
|
currentNode,
|
||||||
|
inputName,
|
||||||
|
runIndex,
|
||||||
|
);
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
node: {},
|
node: {},
|
||||||
@@ -779,7 +786,7 @@ describe('useWorkflowHelpers', () => {
|
|||||||
};
|
};
|
||||||
workflowsStore.shouldReplaceInputDataWithPinData = true;
|
workflowsStore.shouldReplaceInputDataWithPinData = true;
|
||||||
|
|
||||||
const result = executeData(parentNodes, currentNode, inputName, runIndex);
|
const result = executeData({}, parentNodes, currentNode, inputName, runIndex);
|
||||||
|
|
||||||
expect(result.data).toEqual({ main: [[{ json: { key: 'value' } }]] });
|
expect(result.data).toEqual({ main: [[{ json: { key: 'value' } }]] });
|
||||||
expect(result.source).toEqual({ main: [{ previousNode: 'ParentNode' }] });
|
expect(result.source).toEqual({ main: [{ previousNode: 'ParentNode' }] });
|
||||||
@@ -802,20 +809,23 @@ describe('useWorkflowHelpers', () => {
|
|||||||
} as never,
|
} as never,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
workflowsStore.getCurrentWorkflow.mockReturnValue({
|
|
||||||
connectionsByDestinationNode: {
|
|
||||||
CurrentNode: {
|
|
||||||
main: [
|
|
||||||
[
|
|
||||||
{ node: 'ParentNode', index: 0, type: 'main' },
|
|
||||||
{ node: 'CurrentNode', index: 0, type: 'main' },
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as never);
|
|
||||||
|
|
||||||
const result = executeData(parentNodes, currentNode, inputName, runIndex);
|
const connectionsBySourceNode: IConnections = {
|
||||||
|
CurrentNode: {
|
||||||
|
main: [[{ node: 'CurrentNode', index: 0, type: 'main' }]],
|
||||||
|
},
|
||||||
|
ParentNode: {
|
||||||
|
main: [[{ node: 'CurrentNode', index: 0, type: 'main' }]],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = executeData(
|
||||||
|
connectionsBySourceNode,
|
||||||
|
parentNodes,
|
||||||
|
currentNode,
|
||||||
|
inputName,
|
||||||
|
runIndex,
|
||||||
|
);
|
||||||
|
|
||||||
expect(result.data).toEqual({ main: [[{ json: { key: 'valueFromRunData' } }]] });
|
expect(result.data).toEqual({ main: [[{ json: { key: 'valueFromRunData' } }]] });
|
||||||
expect(result.source).toEqual({
|
expect(result.source).toEqual({
|
||||||
@@ -841,20 +851,24 @@ describe('useWorkflowHelpers', () => {
|
|||||||
} as never,
|
} as never,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
workflowsStore.getCurrentWorkflow.mockReturnValue({
|
|
||||||
connectionsByDestinationNode: {
|
|
||||||
CurrentNode: {
|
|
||||||
main: [
|
|
||||||
[
|
|
||||||
{ node: 'ParentNode', index: 1, type: 'main' },
|
|
||||||
{ node: 'CurrentNode', index: 0, type: 'main' },
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as never);
|
|
||||||
|
|
||||||
const result = executeData(parentNodes, currentNode, inputName, runIndex, parentRunIndex);
|
const connectionsBySourceNode: IConnections = {
|
||||||
|
CurrentNode: {
|
||||||
|
main: [[{ node: 'CurrentNode', index: 0, type: 'main' }]],
|
||||||
|
},
|
||||||
|
ParentNode: {
|
||||||
|
main: [[], [{ node: 'CurrentNode', index: 1, type: 'main' }]],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = executeData(
|
||||||
|
connectionsBySourceNode,
|
||||||
|
parentNodes,
|
||||||
|
currentNode,
|
||||||
|
inputName,
|
||||||
|
runIndex,
|
||||||
|
parentRunIndex,
|
||||||
|
);
|
||||||
|
|
||||||
expect(result.data).toEqual({ main: [[{ json: { key: 'valueFromRunData' } }]] });
|
expect(result.data).toEqual({ main: [[{ json: { key: 'valueFromRunData' } }]] });
|
||||||
expect(result.source).toEqual({
|
expect(result.source).toEqual({
|
||||||
@@ -874,7 +888,7 @@ describe('useWorkflowHelpers', () => {
|
|||||||
workflowsStore.shouldReplaceInputDataWithPinData = false;
|
workflowsStore.shouldReplaceInputDataWithPinData = false;
|
||||||
workflowsStore.getWorkflowRunData = null;
|
workflowsStore.getWorkflowRunData = null;
|
||||||
|
|
||||||
const result = executeData(parentNodes, currentNode, inputName, runIndex);
|
const result = executeData({}, parentNodes, currentNode, inputName, runIndex);
|
||||||
|
|
||||||
expect(result.data).toEqual({});
|
expect(result.data).toEqual({});
|
||||||
expect(result.source).toBeNull();
|
expect(result.source).toBeNull();
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
NodeHelpers,
|
NodeHelpers,
|
||||||
WEBHOOK_NODE_TYPE,
|
WEBHOOK_NODE_TYPE,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
import * as workflowUtils from 'n8n-workflow/common';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ICredentialsResponse,
|
ICredentialsResponse,
|
||||||
@@ -71,6 +72,7 @@ export type ResolveParameterOptions = {
|
|||||||
additionalKeys?: IWorkflowDataProxyAdditionalKeys;
|
additionalKeys?: IWorkflowDataProxyAdditionalKeys;
|
||||||
isForCredential?: boolean;
|
isForCredential?: boolean;
|
||||||
contextNodeName?: string;
|
contextNodeName?: string;
|
||||||
|
connections?: IConnections;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function resolveParameter<T = IDataObject>(
|
export function resolveParameter<T = IDataObject>(
|
||||||
@@ -81,6 +83,7 @@ export function resolveParameter<T = IDataObject>(
|
|||||||
return resolveParameterImpl(
|
return resolveParameterImpl(
|
||||||
parameter,
|
parameter,
|
||||||
() => opts.workflow,
|
() => opts.workflow,
|
||||||
|
opts.connections,
|
||||||
opts.envVars,
|
opts.envVars,
|
||||||
opts.workflow.getNode(opts.nodeName),
|
opts.workflow.getNode(opts.nodeName),
|
||||||
opts.execution,
|
opts.execution,
|
||||||
@@ -100,6 +103,7 @@ export function resolveParameter<T = IDataObject>(
|
|||||||
return resolveParameterImpl(
|
return resolveParameterImpl(
|
||||||
parameter,
|
parameter,
|
||||||
workflowsStore.getCurrentWorkflow,
|
workflowsStore.getCurrentWorkflow,
|
||||||
|
workflowsStore.connectionsBySourceNode,
|
||||||
useEnvironmentsStore().variablesAsObject,
|
useEnvironmentsStore().variablesAsObject,
|
||||||
useNDVStore().activeNode,
|
useNDVStore().activeNode,
|
||||||
workflowsStore.workflowExecutionData,
|
workflowsStore.workflowExecutionData,
|
||||||
@@ -113,6 +117,7 @@ export function resolveParameter<T = IDataObject>(
|
|||||||
function resolveParameterImpl<T = IDataObject>(
|
function resolveParameterImpl<T = IDataObject>(
|
||||||
parameter: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
parameter: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
||||||
getContextWorkflow: () => Workflow,
|
getContextWorkflow: () => Workflow,
|
||||||
|
connections: IConnections,
|
||||||
envVars: Record<string, string | boolean | number>,
|
envVars: Record<string, string | boolean | number>,
|
||||||
ndvActiveNode: INodeUi | null,
|
ndvActiveNode: INodeUi | null,
|
||||||
executionData: IExecutionResponse | null,
|
executionData: IExecutionResponse | null,
|
||||||
@@ -200,11 +205,11 @@ function resolveParameterImpl<T = IDataObject>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let _connectionInputData = connectionInputData(
|
let _connectionInputData = connectionInputData(
|
||||||
|
connections,
|
||||||
parentNode,
|
parentNode,
|
||||||
contextNode!.name,
|
contextNode!.name,
|
||||||
inputName,
|
inputName,
|
||||||
runIndexParent,
|
runIndexParent,
|
||||||
getContextWorkflow,
|
|
||||||
shouldReplaceInputDataWithPinData,
|
shouldReplaceInputDataWithPinData,
|
||||||
pinData,
|
pinData,
|
||||||
executionData?.data?.resultData.runData ?? null,
|
executionData?.data?.resultData.runData ?? null,
|
||||||
@@ -215,11 +220,11 @@ function resolveParameterImpl<T = IDataObject>(
|
|||||||
// For Sub-Nodes connected to Trigger-Nodes use the data of the root-node
|
// For Sub-Nodes connected to Trigger-Nodes use the data of the root-node
|
||||||
// (Gets for example used by the Memory connected to the Chat-Trigger-Node)
|
// (Gets for example used by the Memory connected to the Chat-Trigger-Node)
|
||||||
const _executeData = executeDataImpl(
|
const _executeData = executeDataImpl(
|
||||||
|
connections,
|
||||||
[contextNode.name],
|
[contextNode.name],
|
||||||
contextNode.name,
|
contextNode.name,
|
||||||
inputName,
|
inputName,
|
||||||
0,
|
0,
|
||||||
getContextWorkflow,
|
|
||||||
shouldReplaceInputDataWithPinData,
|
shouldReplaceInputDataWithPinData,
|
||||||
pinData,
|
pinData,
|
||||||
executionData?.data?.resultData.runData ?? null,
|
executionData?.data?.resultData.runData ?? null,
|
||||||
@@ -265,11 +270,11 @@ function resolveParameterImpl<T = IDataObject>(
|
|||||||
runIndexCurrent = workflowRunData[contextNode!.name].length - 1;
|
runIndexCurrent = workflowRunData[contextNode!.name].length - 1;
|
||||||
}
|
}
|
||||||
let _executeData = executeDataImpl(
|
let _executeData = executeDataImpl(
|
||||||
|
connections,
|
||||||
parentNode,
|
parentNode,
|
||||||
contextNode!.name,
|
contextNode!.name,
|
||||||
inputName,
|
inputName,
|
||||||
runIndexCurrent,
|
runIndexCurrent,
|
||||||
getContextWorkflow,
|
|
||||||
shouldReplaceInputDataWithPinData,
|
shouldReplaceInputDataWithPinData,
|
||||||
pinData,
|
pinData,
|
||||||
executionData?.data?.resultData.runData ?? null,
|
executionData?.data?.resultData.runData ?? null,
|
||||||
@@ -279,11 +284,11 @@ function resolveParameterImpl<T = IDataObject>(
|
|||||||
if (!_executeData.source) {
|
if (!_executeData.source) {
|
||||||
// fallback to parent's run index for multi-output case
|
// fallback to parent's run index for multi-output case
|
||||||
_executeData = executeDataImpl(
|
_executeData = executeDataImpl(
|
||||||
|
connections,
|
||||||
parentNode,
|
parentNode,
|
||||||
contextNode!.name,
|
contextNode!.name,
|
||||||
inputName,
|
inputName,
|
||||||
runIndexParent,
|
runIndexParent,
|
||||||
getContextWorkflow,
|
|
||||||
shouldReplaceInputDataWithPinData,
|
shouldReplaceInputDataWithPinData,
|
||||||
pinData,
|
pinData,
|
||||||
executionData?.data?.resultData.runData ?? null,
|
executionData?.data?.resultData.runData ?? null,
|
||||||
@@ -310,6 +315,7 @@ export function resolveRequiredParameters(
|
|||||||
currentParameter: INodeProperties,
|
currentParameter: INodeProperties,
|
||||||
parameters: INodeParameters,
|
parameters: INodeParameters,
|
||||||
opts: {
|
opts: {
|
||||||
|
connections?: IConnections;
|
||||||
targetItem?: TargetItem;
|
targetItem?: TargetItem;
|
||||||
inputNodeName?: string;
|
inputNodeName?: string;
|
||||||
inputRunIndex?: number;
|
inputRunIndex?: number;
|
||||||
@@ -382,11 +388,11 @@ function getNodeTypes(): INodeTypes {
|
|||||||
// TODO: move to separate file
|
// TODO: move to separate file
|
||||||
// Returns connectionInputData to be able to execute an expression.
|
// Returns connectionInputData to be able to execute an expression.
|
||||||
function connectionInputData(
|
function connectionInputData(
|
||||||
|
connections: IConnections,
|
||||||
parentNode: string[],
|
parentNode: string[],
|
||||||
currentNode: string,
|
currentNode: string,
|
||||||
inputName: string,
|
inputName: string,
|
||||||
runIndex: number,
|
runIndex: number,
|
||||||
getContextWorkflow: () => Workflow,
|
|
||||||
shouldReplaceInputDataWithPinData: boolean,
|
shouldReplaceInputDataWithPinData: boolean,
|
||||||
pinData: IPinData | undefined,
|
pinData: IPinData | undefined,
|
||||||
workflowRunData: IRunData | null,
|
workflowRunData: IRunData | null,
|
||||||
@@ -394,11 +400,11 @@ function connectionInputData(
|
|||||||
): INodeExecutionData[] | null {
|
): INodeExecutionData[] | null {
|
||||||
let connectionInputData: INodeExecutionData[] | null = null;
|
let connectionInputData: INodeExecutionData[] | null = null;
|
||||||
const _executeData = executeDataImpl(
|
const _executeData = executeDataImpl(
|
||||||
|
connections,
|
||||||
parentNode,
|
parentNode,
|
||||||
currentNode,
|
currentNode,
|
||||||
inputName,
|
inputName,
|
||||||
runIndex,
|
runIndex,
|
||||||
getContextWorkflow,
|
|
||||||
shouldReplaceInputDataWithPinData,
|
shouldReplaceInputDataWithPinData,
|
||||||
pinData,
|
pinData,
|
||||||
workflowRunData,
|
workflowRunData,
|
||||||
@@ -431,6 +437,7 @@ function connectionInputData(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function executeData(
|
export function executeData(
|
||||||
|
connections: IConnections,
|
||||||
parentNodes: string[],
|
parentNodes: string[],
|
||||||
currentNode: string,
|
currentNode: string,
|
||||||
inputName: string,
|
inputName: string,
|
||||||
@@ -440,11 +447,11 @@ export function executeData(
|
|||||||
const workflowsStore = useWorkflowsStore();
|
const workflowsStore = useWorkflowsStore();
|
||||||
|
|
||||||
return executeDataImpl(
|
return executeDataImpl(
|
||||||
|
connections,
|
||||||
parentNodes,
|
parentNodes,
|
||||||
currentNode,
|
currentNode,
|
||||||
inputName,
|
inputName,
|
||||||
runIndex,
|
runIndex,
|
||||||
workflowsStore.getCurrentWorkflow,
|
|
||||||
workflowsStore.shouldReplaceInputDataWithPinData,
|
workflowsStore.shouldReplaceInputDataWithPinData,
|
||||||
workflowsStore.pinnedWorkflowData,
|
workflowsStore.pinnedWorkflowData,
|
||||||
workflowsStore.getWorkflowRunData,
|
workflowsStore.getWorkflowRunData,
|
||||||
@@ -454,16 +461,18 @@ export function executeData(
|
|||||||
|
|
||||||
// TODO: move to separate file
|
// TODO: move to separate file
|
||||||
function executeDataImpl(
|
function executeDataImpl(
|
||||||
|
connections: IConnections,
|
||||||
parentNodes: string[],
|
parentNodes: string[],
|
||||||
currentNode: string,
|
currentNode: string,
|
||||||
inputName: string,
|
inputName: string,
|
||||||
runIndex: number,
|
runIndex: number,
|
||||||
getContextWorkflow: () => Workflow,
|
|
||||||
shouldReplaceInputDataWithPinData: boolean,
|
shouldReplaceInputDataWithPinData: boolean,
|
||||||
pinData: IPinData | undefined,
|
pinData: IPinData | undefined,
|
||||||
workflowRunData: IRunData | null,
|
workflowRunData: IRunData | null,
|
||||||
parentRunIndex?: number,
|
parentRunIndex?: number,
|
||||||
): IExecuteData {
|
): IExecuteData {
|
||||||
|
const connectionsByDestinationNode = workflowUtils.mapConnectionsByDestination(connections);
|
||||||
|
|
||||||
const executeData = {
|
const executeData = {
|
||||||
node: {},
|
node: {},
|
||||||
data: {},
|
data: {},
|
||||||
@@ -507,15 +516,12 @@ function executeDataImpl(
|
|||||||
[inputName]: workflowRunData[currentNode][runIndex].source,
|
[inputName]: workflowRunData[currentNode][runIndex].source,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const workflow = getContextWorkflow();
|
|
||||||
|
|
||||||
let previousNodeOutput: number | undefined;
|
let previousNodeOutput: number | undefined;
|
||||||
// As the node can be connected through either of the outputs find the correct one
|
// As the node can be connected through either of the outputs find the correct one
|
||||||
// and set it to make pairedItem work on not executed nodes
|
// and set it to make pairedItem work on not executed nodes
|
||||||
if (workflow.connectionsByDestinationNode[currentNode]?.main) {
|
if (connectionsByDestinationNode[currentNode]?.main) {
|
||||||
mainConnections: for (const mainConnections of workflow.connectionsByDestinationNode[
|
mainConnections: for (const mainConnections of connectionsByDestinationNode[currentNode]
|
||||||
currentNode
|
.main) {
|
||||||
].main) {
|
|
||||||
for (const connection of mainConnections ?? []) {
|
for (const connection of mainConnections ?? []) {
|
||||||
if (
|
if (
|
||||||
connection.type === NodeConnectionTypes.Main &&
|
connection.type === NodeConnectionTypes.Main &&
|
||||||
|
|||||||
@@ -149,9 +149,8 @@ export const useNDVStore = defineStore(STORES.NDV, () => {
|
|||||||
|
|
||||||
const ndvNodeInputNumber = computed(() => {
|
const ndvNodeInputNumber = computed(() => {
|
||||||
const returnData: { [nodeName: string]: number[] } = {};
|
const returnData: { [nodeName: string]: number[] } = {};
|
||||||
const workflow = workflowsStore.getCurrentWorkflow();
|
|
||||||
const activeNodeConections = (
|
const activeNodeConections = (
|
||||||
workflow.connectionsByDestinationNode[activeNode.value?.name || ''] ?? {}
|
workflowsStore.connectionsByDestinationNode[activeNode.value?.name || ''] ?? {}
|
||||||
).main;
|
).main;
|
||||||
|
|
||||||
if (!activeNodeConections || activeNodeConections.length < 2) return returnData;
|
if (!activeNodeConections || activeNodeConections.length < 2) return returnData;
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ import {
|
|||||||
Workflow,
|
Workflow,
|
||||||
TelemetryHelpers,
|
TelemetryHelpers,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
import * as workflowUtils from 'n8n-workflow/common';
|
||||||
import findLast from 'lodash/findLast';
|
import findLast from 'lodash/findLast';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
import pick from 'lodash/pick';
|
import pick from 'lodash/pick';
|
||||||
@@ -294,10 +295,17 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||||||
|
|
||||||
const getPastChatMessages = computed(() => Array.from(new Set(chatMessages.value)));
|
const getPastChatMessages = computed(() => Array.from(new Set(chatMessages.value)));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This section contains functions migrated from the workflow class
|
||||||
|
*/
|
||||||
|
|
||||||
|
const connectionsBySourceNode = computed(() => workflow.value.connections);
|
||||||
const connectionsByDestinationNode = computed(() =>
|
const connectionsByDestinationNode = computed(() =>
|
||||||
Workflow.getConnectionsByDestination(workflow.value.connections),
|
workflowUtils.mapConnectionsByDestination(workflow.value.connections),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// End section
|
||||||
|
|
||||||
const selectableTriggerNodes = computed(() =>
|
const selectableTriggerNodes = computed(() =>
|
||||||
workflowTriggerNodes.value.filter((node) => !node.disabled && !isChatNode(node)),
|
workflowTriggerNodes.value.filter((node) => !node.disabled && !isChatNode(node)),
|
||||||
);
|
);
|
||||||
@@ -384,7 +392,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getNodeByName(nodeName: string): INodeUi | null {
|
function getNodeByName(nodeName: string): INodeUi | null {
|
||||||
return nodesByName.value[nodeName] || null;
|
return workflowUtils.getNodeByName(nodesByName.value, nodeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNodeById(nodeId: string): INodeUi | undefined {
|
function getNodeById(nodeId: string): INodeUi | undefined {
|
||||||
@@ -1955,6 +1963,8 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||||||
getWorkflowResultDataByNodeName,
|
getWorkflowResultDataByNodeName,
|
||||||
allConnections,
|
allConnections,
|
||||||
allNodes,
|
allNodes,
|
||||||
|
connectionsBySourceNode,
|
||||||
|
connectionsByDestinationNode,
|
||||||
isWaitingExecution,
|
isWaitingExecution,
|
||||||
isWorkflowRunning,
|
isWorkflowRunning,
|
||||||
canvasNames,
|
canvasNames,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Basic, IExecutionResponse } from '@/Interface';
|
import type { Basic, IExecutionResponse } from '@/Interface';
|
||||||
import type { IWorkflowDataProxyAdditionalKeys, Workflow } from 'n8n-workflow';
|
import type { IConnections, IWorkflowDataProxyAdditionalKeys, Workflow } from 'n8n-workflow';
|
||||||
|
|
||||||
type Range = { from: number; to: number };
|
type Range = { from: number; to: number };
|
||||||
|
|
||||||
@@ -40,6 +40,7 @@ export interface ExpressionLocalResolveContext {
|
|||||||
envVars: Record<string, Basic>;
|
envVars: Record<string, Basic>;
|
||||||
additionalKeys: IWorkflowDataProxyAdditionalKeys;
|
additionalKeys: IWorkflowDataProxyAdditionalKeys;
|
||||||
workflow: Workflow;
|
workflow: Workflow;
|
||||||
|
connections: IConnections;
|
||||||
execution: IExecutionResponse | null;
|
execution: IExecutionResponse | null;
|
||||||
nodeName: string;
|
nodeName: string;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import { SET_NODE_TYPE, STICKY_NODE_TYPE } from '@/constants';
|
|||||||
import { createTestNode } from '@/__tests__/mocks';
|
import { createTestNode } from '@/__tests__/mocks';
|
||||||
import type { GraphNode } from '@vue-flow/core';
|
import type { GraphNode } from '@vue-flow/core';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
|
import { setActivePinia } from 'pinia';
|
||||||
|
|
||||||
describe('getGenericHints', () => {
|
describe('getGenericHints', () => {
|
||||||
let mockWorkflowNode: MockProxy<INode>;
|
let mockWorkflowNode: MockProxy<INode>;
|
||||||
@@ -34,6 +36,9 @@ describe('getGenericHints', () => {
|
|||||||
let hasNodeRun: boolean;
|
let hasNodeRun: boolean;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
const pinia = createTestingPinia({});
|
||||||
|
setActivePinia(pinia);
|
||||||
|
|
||||||
mockWorkflowNode = mock<INode>();
|
mockWorkflowNode = mock<INode>();
|
||||||
mockNode = mock<INodeUi>({ type: 'test' });
|
mockNode = mock<INodeUi>({ type: 'test' });
|
||||||
mockNodeType = mock<INodeTypeDescription>();
|
mockNodeType = mock<INodeTypeDescription>();
|
||||||
@@ -55,8 +60,9 @@ describe('getGenericHints', () => {
|
|||||||
nodeType: mockNodeType,
|
nodeType: mockNodeType,
|
||||||
nodeOutputData: mockNodeOutputData,
|
nodeOutputData: mockNodeOutputData,
|
||||||
hasMultipleInputItems,
|
hasMultipleInputItems,
|
||||||
workflow: mockWorkflow,
|
|
||||||
hasNodeRun,
|
hasNodeRun,
|
||||||
|
nodes: [],
|
||||||
|
connections: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(hints).toEqual([
|
expect(hints).toEqual([
|
||||||
@@ -80,8 +86,9 @@ describe('getGenericHints', () => {
|
|||||||
nodeType: mockNodeType,
|
nodeType: mockNodeType,
|
||||||
nodeOutputData: mockNodeOutputData,
|
nodeOutputData: mockNodeOutputData,
|
||||||
hasMultipleInputItems,
|
hasMultipleInputItems,
|
||||||
workflow: mockWorkflow,
|
|
||||||
hasNodeRun,
|
hasNodeRun,
|
||||||
|
nodes: [],
|
||||||
|
connections: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(hints).toEqual([
|
expect(hints).toEqual([
|
||||||
@@ -118,8 +125,9 @@ describe('getGenericHints', () => {
|
|||||||
nodeType: mockNodeType,
|
nodeType: mockNodeType,
|
||||||
nodeOutputData: mockNodeOutputData,
|
nodeOutputData: mockNodeOutputData,
|
||||||
hasMultipleInputItems,
|
hasMultipleInputItems,
|
||||||
workflow: mockWorkflow,
|
|
||||||
hasNodeRun,
|
hasNodeRun,
|
||||||
|
nodes: [],
|
||||||
|
connections: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(hints).toEqual([
|
expect(hints).toEqual([
|
||||||
@@ -142,8 +150,9 @@ describe('getGenericHints', () => {
|
|||||||
nodeType: mockNodeType,
|
nodeType: mockNodeType,
|
||||||
nodeOutputData: mockNodeOutputData,
|
nodeOutputData: mockNodeOutputData,
|
||||||
hasMultipleInputItems,
|
hasMultipleInputItems,
|
||||||
workflow: mockWorkflow,
|
|
||||||
hasNodeRun,
|
hasNodeRun,
|
||||||
|
nodes: [],
|
||||||
|
connections: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(hints).toEqual([
|
expect(hints).toEqual([
|
||||||
@@ -166,8 +175,9 @@ describe('getGenericHints', () => {
|
|||||||
nodeType: mockNodeType,
|
nodeType: mockNodeType,
|
||||||
nodeOutputData: mockNodeOutputData,
|
nodeOutputData: mockNodeOutputData,
|
||||||
hasMultipleInputItems,
|
hasMultipleInputItems,
|
||||||
workflow: mockWorkflow,
|
|
||||||
hasNodeRun,
|
hasNodeRun,
|
||||||
|
nodes: [],
|
||||||
|
connections: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(hints).toEqual([
|
expect(hints).toEqual([
|
||||||
@@ -191,8 +201,9 @@ describe('getGenericHints', () => {
|
|||||||
nodeType: mockNodeType,
|
nodeType: mockNodeType,
|
||||||
nodeOutputData: mockNodeOutputData,
|
nodeOutputData: mockNodeOutputData,
|
||||||
hasMultipleInputItems,
|
hasMultipleInputItems,
|
||||||
workflow: mockWorkflow,
|
|
||||||
hasNodeRun,
|
hasNodeRun,
|
||||||
|
nodes: [],
|
||||||
|
connections: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(hints).toEqual([
|
expect(hints).toEqual([
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ import {
|
|||||||
import type { INodeUi, XYPosition } from '@/Interface';
|
import type { INodeUi, XYPosition } from '@/Interface';
|
||||||
import type {
|
import type {
|
||||||
AssignmentCollectionValue,
|
AssignmentCollectionValue,
|
||||||
|
IConnections,
|
||||||
INode,
|
INode,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
NodeHint,
|
NodeHint,
|
||||||
Workflow,
|
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeHelpers, SEND_AND_WAIT_OPERATION } from 'n8n-workflow';
|
import { NodeHelpers, SEND_AND_WAIT_OPERATION } from 'n8n-workflow';
|
||||||
import type { RouteLocation } from 'vue-router';
|
import type { RouteLocation } from 'vue-router';
|
||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
type Rect,
|
type Rect,
|
||||||
type ViewportTransform,
|
type ViewportTransform,
|
||||||
} from '@vue-flow/core';
|
} from '@vue-flow/core';
|
||||||
|
import * as workflowUtils from 'n8n-workflow/common';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Canvas constants and functions
|
* Canvas constants and functions
|
||||||
@@ -371,7 +372,8 @@ export function getGenericHints({
|
|||||||
nodeType,
|
nodeType,
|
||||||
nodeOutputData,
|
nodeOutputData,
|
||||||
hasMultipleInputItems,
|
hasMultipleInputItems,
|
||||||
workflow,
|
nodes,
|
||||||
|
connections,
|
||||||
hasNodeRun,
|
hasNodeRun,
|
||||||
}: {
|
}: {
|
||||||
workflowNode: INode;
|
workflowNode: INode;
|
||||||
@@ -379,7 +381,8 @@ export function getGenericHints({
|
|||||||
nodeType: INodeTypeDescription;
|
nodeType: INodeTypeDescription;
|
||||||
nodeOutputData: INodeExecutionData[];
|
nodeOutputData: INodeExecutionData[];
|
||||||
hasMultipleInputItems: boolean;
|
hasMultipleInputItems: boolean;
|
||||||
workflow: Workflow;
|
nodes: INode[];
|
||||||
|
connections: IConnections;
|
||||||
hasNodeRun: boolean;
|
hasNodeRun: boolean;
|
||||||
}) {
|
}) {
|
||||||
const nodeHints: NodeHint[] = [];
|
const nodeHints: NodeHint[] = [];
|
||||||
@@ -417,7 +420,7 @@ export function getGenericHints({
|
|||||||
hasMultipleInputItems &&
|
hasMultipleInputItems &&
|
||||||
LIST_LIKE_NODE_OPERATIONS.includes((workflowNode.parameters.operation as string) || '')
|
LIST_LIKE_NODE_OPERATIONS.includes((workflowNode.parameters.operation as string) || '')
|
||||||
) {
|
) {
|
||||||
const executeOnce = workflow.getNode(node.name)?.executeOnce;
|
const executeOnce = workflowUtils.getNodeByName(nodes, node.name)?.executeOnce;
|
||||||
if (!executeOnce) {
|
if (!executeOnce) {
|
||||||
nodeHints.push({
|
nodeHints.push({
|
||||||
message:
|
message:
|
||||||
@@ -429,7 +432,7 @@ export function getGenericHints({
|
|||||||
|
|
||||||
// add sendAndWait hint
|
// add sendAndWait hint
|
||||||
if (hasMultipleInputItems && workflowNode.parameters.operation === SEND_AND_WAIT_OPERATION) {
|
if (hasMultipleInputItems && workflowNode.parameters.operation === SEND_AND_WAIT_OPERATION) {
|
||||||
const executeOnce = workflow.getNode(node.name)?.executeOnce;
|
const executeOnce = workflowUtils.getNodeByName(nodes, node.name)?.executeOnce;
|
||||||
if (!executeOnce) {
|
if (!executeOnce) {
|
||||||
nodeHints.push({
|
nodeHints.push({
|
||||||
message: 'This action will run only once, for the first input item',
|
message: 'This action will run only once, for the first input item',
|
||||||
@@ -470,9 +473,8 @@ export function getGenericHints({
|
|||||||
|
|
||||||
// Split In Batches setup hints
|
// Split In Batches setup hints
|
||||||
if (node.type === SPLIT_IN_BATCHES_NODE_TYPE) {
|
if (node.type === SPLIT_IN_BATCHES_NODE_TYPE) {
|
||||||
const { connectionsBySourceNode } = workflow;
|
const firstNodesInLoop =
|
||||||
|
workflowUtils.mapConnectionsByDestination(connections)[node.name]?.main[1] || [];
|
||||||
const firstNodesInLoop = connectionsBySourceNode[node.name]?.main[1] || [];
|
|
||||||
|
|
||||||
if (!firstNodesInLoop.length) {
|
if (!firstNodesInLoop.length) {
|
||||||
nodeHints.push({
|
nodeHints.push({
|
||||||
@@ -482,7 +484,7 @@ export function getGenericHints({
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
for (const nodeInConnection of firstNodesInLoop || []) {
|
for (const nodeInConnection of firstNodesInLoop || []) {
|
||||||
const nodeChilds = workflow.getChildNodes(nodeInConnection.node) || [];
|
const nodeChilds = workflowUtils.getChildNodes(connections, nodeInConnection.node) || [];
|
||||||
if (!nodeChilds.includes(node.name)) {
|
if (!nodeChilds.includes(node.name)) {
|
||||||
nodeHints.push({
|
nodeHints.push({
|
||||||
message:
|
message:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"extends": "@n8n/typescript-config/tsconfig.frontend.json",
|
"extends": "@n8n/typescript-config/tsconfig.frontend.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
"rootDirs": [
|
"rootDirs": [
|
||||||
".",
|
".",
|
||||||
"../@n8n/rest-api-client/src",
|
"../@n8n/rest-api-client/src",
|
||||||
|
|||||||
@@ -11,6 +11,11 @@
|
|||||||
"import": "./dist/esm/index.js",
|
"import": "./dist/esm/index.js",
|
||||||
"require": "./dist/cjs/index.js"
|
"require": "./dist/cjs/index.js"
|
||||||
},
|
},
|
||||||
|
"./common": {
|
||||||
|
"types": "./dist/esm/common/index.d.ts",
|
||||||
|
"import": "./dist/esm/common/index.js",
|
||||||
|
"require": "./dist/cjs/common/index.js"
|
||||||
|
},
|
||||||
"./*": "./*"
|
"./*": "./*"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
12
packages/workflow/src/common/get-child-nodes.ts
Normal file
12
packages/workflow/src/common/get-child-nodes.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { getConnectedNodes } from './get-connected-nodes';
|
||||||
|
import { NodeConnectionTypes } from '../interfaces';
|
||||||
|
import type { IConnections, NodeConnectionType } from '../interfaces';
|
||||||
|
|
||||||
|
export function getChildNodes(
|
||||||
|
connectionsBySourceNode: IConnections,
|
||||||
|
nodeName: string,
|
||||||
|
type: NodeConnectionType | 'ALL' | 'ALL_NON_MAIN' = NodeConnectionTypes.Main,
|
||||||
|
depth = -1,
|
||||||
|
): string[] {
|
||||||
|
return getConnectedNodes(connectionsBySourceNode, nodeName, type, depth);
|
||||||
|
}
|
||||||
98
packages/workflow/src/common/get-connected-nodes.ts
Normal file
98
packages/workflow/src/common/get-connected-nodes.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import { NodeConnectionTypes } from '../interfaces';
|
||||||
|
import type { IConnections, NodeConnectionType } from '../interfaces';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the nodes which are connected nodes starting from
|
||||||
|
* the given one
|
||||||
|
*
|
||||||
|
* @param {NodeConnectionType} [type='main']
|
||||||
|
* @param {*} [depth=-1]
|
||||||
|
*/
|
||||||
|
export function getConnectedNodes(
|
||||||
|
connections: IConnections,
|
||||||
|
nodeName: string,
|
||||||
|
connectionType: NodeConnectionType | 'ALL' | 'ALL_NON_MAIN' = NodeConnectionTypes.Main,
|
||||||
|
depth = -1,
|
||||||
|
checkedNodesIncoming?: string[],
|
||||||
|
): string[] {
|
||||||
|
const newDepth = depth === -1 ? depth : depth - 1;
|
||||||
|
if (depth === 0) {
|
||||||
|
// Reached max depth
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!connections.hasOwnProperty(nodeName)) {
|
||||||
|
// Node does not have incoming connections
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let types: NodeConnectionType[];
|
||||||
|
if (connectionType === 'ALL') {
|
||||||
|
types = Object.keys(connections[nodeName]) as NodeConnectionType[];
|
||||||
|
} else if (connectionType === 'ALL_NON_MAIN') {
|
||||||
|
types = Object.keys(connections[nodeName]).filter(
|
||||||
|
(type) => type !== 'main',
|
||||||
|
) as NodeConnectionType[];
|
||||||
|
} else {
|
||||||
|
types = [connectionType];
|
||||||
|
}
|
||||||
|
|
||||||
|
let addNodes: string[];
|
||||||
|
let nodeIndex: number;
|
||||||
|
let i: number;
|
||||||
|
let parentNodeName: string;
|
||||||
|
const returnNodes: string[] = [];
|
||||||
|
|
||||||
|
types.forEach((type) => {
|
||||||
|
if (!connections[nodeName].hasOwnProperty(type)) {
|
||||||
|
// Node does not have incoming connections of given type
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkedNodes = checkedNodesIncoming ? [...checkedNodesIncoming] : [];
|
||||||
|
|
||||||
|
if (checkedNodes.includes(nodeName)) {
|
||||||
|
// Node got checked already before
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkedNodes.push(nodeName);
|
||||||
|
|
||||||
|
connections[nodeName][type].forEach((connectionsByIndex) => {
|
||||||
|
connectionsByIndex?.forEach((connection) => {
|
||||||
|
if (checkedNodes.includes(connection.node)) {
|
||||||
|
// Node got checked already before
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
returnNodes.unshift(connection.node);
|
||||||
|
|
||||||
|
addNodes = getConnectedNodes(
|
||||||
|
connections,
|
||||||
|
connection.node,
|
||||||
|
connectionType,
|
||||||
|
newDepth,
|
||||||
|
checkedNodes,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (i = addNodes.length; i--; i > 0) {
|
||||||
|
// Because nodes can have multiple parents it is possible that
|
||||||
|
// parts of the tree is parent of both and to not add nodes
|
||||||
|
// twice check first if they already got added before.
|
||||||
|
parentNodeName = addNodes[i];
|
||||||
|
nodeIndex = returnNodes.indexOf(parentNodeName);
|
||||||
|
|
||||||
|
if (nodeIndex !== -1) {
|
||||||
|
// Node got found before so remove it from current location
|
||||||
|
// that node-order stays correct
|
||||||
|
returnNodes.splice(nodeIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
returnNodes.unshift(parentNodeName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return returnNodes;
|
||||||
|
}
|
||||||
19
packages/workflow/src/common/get-node-by-name.ts
Normal file
19
packages/workflow/src/common/get-node-by-name.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { INode, INodes } from '../interfaces';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the node with the given name if it exists else null
|
||||||
|
*
|
||||||
|
* @param {INodes} nodes Nodes to search in
|
||||||
|
* @param {string} name Name of the node to return
|
||||||
|
*/
|
||||||
|
export function getNodeByName(nodes: INodes | INode[], name: string) {
|
||||||
|
if (Array.isArray(nodes)) {
|
||||||
|
return nodes.find((node) => node.name === name) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodes.hasOwnProperty(name)) {
|
||||||
|
return nodes[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
18
packages/workflow/src/common/get-parent-nodes.ts
Normal file
18
packages/workflow/src/common/get-parent-nodes.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { getConnectedNodes } from './get-connected-nodes';
|
||||||
|
import { NodeConnectionTypes } from '../interfaces';
|
||||||
|
import type { IConnections, NodeConnectionType } from '../interfaces';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all the nodes before the given one
|
||||||
|
*
|
||||||
|
* @param {NodeConnectionType} [type='main']
|
||||||
|
* @param {*} [depth=-1]
|
||||||
|
*/
|
||||||
|
export function getParentNodes(
|
||||||
|
connectionsByDestinationNode: IConnections,
|
||||||
|
nodeName: string,
|
||||||
|
type: NodeConnectionType | 'ALL' | 'ALL_NON_MAIN' = NodeConnectionTypes.Main,
|
||||||
|
depth = -1,
|
||||||
|
): string[] {
|
||||||
|
return getConnectedNodes(connectionsByDestinationNode, nodeName, type, depth);
|
||||||
|
}
|
||||||
5
packages/workflow/src/common/index.ts
Normal file
5
packages/workflow/src/common/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export * from './get-child-nodes';
|
||||||
|
export * from './get-connected-nodes';
|
||||||
|
export * from './get-node-by-name';
|
||||||
|
export * from './get-parent-nodes';
|
||||||
|
export * from './map-connections-by-destination';
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-for-in-array */
|
||||||
|
|
||||||
|
import type { IConnections, NodeConnectionType } from '../interfaces';
|
||||||
|
|
||||||
|
export function mapConnectionsByDestination(connections: IConnections) {
|
||||||
|
const returnConnection: IConnections = {};
|
||||||
|
|
||||||
|
let connectionInfo;
|
||||||
|
let maxIndex: number;
|
||||||
|
for (const sourceNode in connections) {
|
||||||
|
if (!connections.hasOwnProperty(sourceNode)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const type of Object.keys(connections[sourceNode]) as NodeConnectionType[]) {
|
||||||
|
if (!connections[sourceNode].hasOwnProperty(type)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const inputIndex in connections[sourceNode][type]) {
|
||||||
|
if (!connections[sourceNode][type].hasOwnProperty(inputIndex)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (connectionInfo of connections[sourceNode][type][inputIndex] ?? []) {
|
||||||
|
if (!returnConnection.hasOwnProperty(connectionInfo.node)) {
|
||||||
|
returnConnection[connectionInfo.node] = {};
|
||||||
|
}
|
||||||
|
if (!returnConnection[connectionInfo.node].hasOwnProperty(connectionInfo.type)) {
|
||||||
|
returnConnection[connectionInfo.node][connectionInfo.type] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
maxIndex = returnConnection[connectionInfo.node][connectionInfo.type].length - 1;
|
||||||
|
for (let j = maxIndex; j < connectionInfo.index; j++) {
|
||||||
|
returnConnection[connectionInfo.node][connectionInfo.type].push([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
returnConnection[connectionInfo.node][connectionInfo.type][connectionInfo.index]?.push({
|
||||||
|
node: sourceNode,
|
||||||
|
type,
|
||||||
|
index: parseInt(inputIndex, 10),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnConnection;
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import * as TelemetryHelpers from './telemetry-helpers';
|
|||||||
|
|
||||||
export * from './errors';
|
export * from './errors';
|
||||||
export * from './constants';
|
export * from './constants';
|
||||||
|
export * from './common';
|
||||||
export * from './cron';
|
export * from './cron';
|
||||||
export * from './deferred-promise';
|
export * from './deferred-promise';
|
||||||
export * from './global-state';
|
export * from './global-state';
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
/* eslint-disable @typescript-eslint/no-for-in-array */
|
/* eslint-disable @typescript-eslint/no-for-in-array */
|
||||||
|
import {
|
||||||
|
getNodeByName,
|
||||||
|
getConnectedNodes,
|
||||||
|
getChildNodes,
|
||||||
|
getParentNodes,
|
||||||
|
mapConnectionsByDestination,
|
||||||
|
} from './common';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MANUAL_CHAT_TRIGGER_LANGCHAIN_NODE_TYPE,
|
MANUAL_CHAT_TRIGGER_LANGCHAIN_NODE_TYPE,
|
||||||
NODES_WITH_RENAMABLE_CONTENT,
|
NODES_WITH_RENAMABLE_CONTENT,
|
||||||
@@ -123,9 +131,7 @@ export class Workflow {
|
|||||||
this.connectionsBySourceNode = parameters.connections;
|
this.connectionsBySourceNode = parameters.connections;
|
||||||
|
|
||||||
// Save also the connections by the destination nodes
|
// Save also the connections by the destination nodes
|
||||||
this.connectionsByDestinationNode = Workflow.getConnectionsByDestination(
|
this.connectionsByDestinationNode = mapConnectionsByDestination(parameters.connections);
|
||||||
parameters.connections,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.active = parameters.active || false;
|
this.active = parameters.active || false;
|
||||||
|
|
||||||
@@ -146,11 +152,6 @@ export class Workflow {
|
|||||||
this.staticData.__dataChanged = true;
|
this.staticData.__dataChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The default connections are by source node. This function rewrites them by destination nodes
|
|
||||||
* to easily find parent nodes.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
static getConnectionsByDestination(connections: IConnections): IConnections {
|
static getConnectionsByDestination(connections: IConnections): IConnections {
|
||||||
const returnConnection: IConnections = {};
|
const returnConnection: IConnections = {};
|
||||||
|
|
||||||
@@ -288,11 +289,7 @@ export class Workflow {
|
|||||||
* @param {string} nodeName Name of the node to return
|
* @param {string} nodeName Name of the node to return
|
||||||
*/
|
*/
|
||||||
getNode(nodeName: string): INode | null {
|
getNode(nodeName: string): INode | null {
|
||||||
if (this.nodes.hasOwnProperty(nodeName)) {
|
return getNodeByName(this.nodes, nodeName);
|
||||||
return this.nodes[nodeName];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -477,9 +474,7 @@ export class Workflow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use the updated connections to create updated connections by destination nodes
|
// Use the updated connections to create updated connections by destination nodes
|
||||||
this.connectionsByDestinationNode = Workflow.getConnectionsByDestination(
|
this.connectionsByDestinationNode = mapConnectionsByDestination(this.connectionsBySourceNode);
|
||||||
this.connectionsBySourceNode,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -576,7 +571,7 @@ export class Workflow {
|
|||||||
type: NodeConnectionType | 'ALL' | 'ALL_NON_MAIN' = NodeConnectionTypes.Main,
|
type: NodeConnectionType | 'ALL' | 'ALL_NON_MAIN' = NodeConnectionTypes.Main,
|
||||||
depth = -1,
|
depth = -1,
|
||||||
): string[] {
|
): string[] {
|
||||||
return this.getConnectedNodes(this.connectionsBySourceNode, nodeName, type, depth);
|
return getChildNodes(this.connectionsBySourceNode, nodeName, type, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -590,7 +585,7 @@ export class Workflow {
|
|||||||
type: NodeConnectionType | 'ALL' | 'ALL_NON_MAIN' = NodeConnectionTypes.Main,
|
type: NodeConnectionType | 'ALL' | 'ALL_NON_MAIN' = NodeConnectionTypes.Main,
|
||||||
depth = -1,
|
depth = -1,
|
||||||
): string[] {
|
): string[] {
|
||||||
return this.getConnectedNodes(this.connectionsByDestinationNode, nodeName, type, depth);
|
return getParentNodes(this.connectionsByDestinationNode, nodeName, type, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -607,87 +602,7 @@ export class Workflow {
|
|||||||
depth = -1,
|
depth = -1,
|
||||||
checkedNodesIncoming?: string[],
|
checkedNodesIncoming?: string[],
|
||||||
): string[] {
|
): string[] {
|
||||||
depth = depth === -1 ? -1 : depth;
|
return getConnectedNodes(connections, nodeName, connectionType, depth, checkedNodesIncoming);
|
||||||
const newDepth = depth === -1 ? depth : depth - 1;
|
|
||||||
if (depth === 0) {
|
|
||||||
// Reached max depth
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!connections.hasOwnProperty(nodeName)) {
|
|
||||||
// Node does not have incoming connections
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
let types: NodeConnectionType[];
|
|
||||||
if (connectionType === 'ALL') {
|
|
||||||
types = Object.keys(connections[nodeName]) as NodeConnectionType[];
|
|
||||||
} else if (connectionType === 'ALL_NON_MAIN') {
|
|
||||||
types = Object.keys(connections[nodeName]).filter(
|
|
||||||
(type) => type !== 'main',
|
|
||||||
) as NodeConnectionType[];
|
|
||||||
} else {
|
|
||||||
types = [connectionType];
|
|
||||||
}
|
|
||||||
|
|
||||||
let addNodes: string[];
|
|
||||||
let nodeIndex: number;
|
|
||||||
let i: number;
|
|
||||||
let parentNodeName: string;
|
|
||||||
const returnNodes: string[] = [];
|
|
||||||
|
|
||||||
types.forEach((type) => {
|
|
||||||
if (!connections[nodeName].hasOwnProperty(type)) {
|
|
||||||
// Node does not have incoming connections of given type
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkedNodes = checkedNodesIncoming ? [...checkedNodesIncoming] : [];
|
|
||||||
|
|
||||||
if (checkedNodes.includes(nodeName)) {
|
|
||||||
// Node got checked already before
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkedNodes.push(nodeName);
|
|
||||||
|
|
||||||
connections[nodeName][type].forEach((connectionsByIndex) => {
|
|
||||||
connectionsByIndex?.forEach((connection) => {
|
|
||||||
if (checkedNodes.includes(connection.node)) {
|
|
||||||
// Node got checked already before
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
returnNodes.unshift(connection.node);
|
|
||||||
|
|
||||||
addNodes = this.getConnectedNodes(
|
|
||||||
connections,
|
|
||||||
connection.node,
|
|
||||||
connectionType,
|
|
||||||
newDepth,
|
|
||||||
checkedNodes,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (i = addNodes.length; i--; i > 0) {
|
|
||||||
// Because nodes can have multiple parents it is possible that
|
|
||||||
// parts of the tree is parent of both and to not add nodes
|
|
||||||
// twice check first if they already got added before.
|
|
||||||
parentNodeName = addNodes[i];
|
|
||||||
nodeIndex = returnNodes.indexOf(parentNodeName);
|
|
||||||
|
|
||||||
if (nodeIndex !== -1) {
|
|
||||||
// Node got found before so remove it from current location
|
|
||||||
// that node-order stays correct
|
|
||||||
returnNodes.splice(nodeIndex, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
returnNodes.unshift(parentNodeName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return returnNodes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
112
packages/workflow/test/common.test.ts
Normal file
112
packages/workflow/test/common.test.ts
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import type { IConnections, IConnection } from '../src/interfaces';
|
||||||
|
import { NodeConnectionTypes } from '../src/interfaces';
|
||||||
|
import { mapConnectionsByDestination } from '../src/common';
|
||||||
|
|
||||||
|
describe('getConnectionsByDestination', () => {
|
||||||
|
it('should return empty object when there are no connections', () => {
|
||||||
|
const result = mapConnectionsByDestination({});
|
||||||
|
|
||||||
|
expect(result).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return connections by destination node', () => {
|
||||||
|
const connections: IConnections = {
|
||||||
|
Node1: {
|
||||||
|
[NodeConnectionTypes.Main]: [
|
||||||
|
[
|
||||||
|
{ node: 'Node2', type: NodeConnectionTypes.Main, index: 0 },
|
||||||
|
{ node: 'Node3', type: NodeConnectionTypes.Main, index: 1 },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result = mapConnectionsByDestination(connections);
|
||||||
|
expect(result).toEqual({
|
||||||
|
Node2: {
|
||||||
|
[NodeConnectionTypes.Main]: [[{ node: 'Node1', type: NodeConnectionTypes.Main, index: 0 }]],
|
||||||
|
},
|
||||||
|
Node3: {
|
||||||
|
[NodeConnectionTypes.Main]: [
|
||||||
|
[],
|
||||||
|
[{ node: 'Node1', type: NodeConnectionTypes.Main, index: 0 }],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle multiple connection types', () => {
|
||||||
|
const connections: IConnections = {
|
||||||
|
Node1: {
|
||||||
|
[NodeConnectionTypes.Main]: [[{ node: 'Node2', type: NodeConnectionTypes.Main, index: 0 }]],
|
||||||
|
[NodeConnectionTypes.AiAgent]: [
|
||||||
|
[{ node: 'Node3', type: NodeConnectionTypes.AiAgent, index: 0 }],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = mapConnectionsByDestination(connections);
|
||||||
|
expect(result).toEqual({
|
||||||
|
Node2: {
|
||||||
|
[NodeConnectionTypes.Main]: [[{ node: 'Node1', type: NodeConnectionTypes.Main, index: 0 }]],
|
||||||
|
},
|
||||||
|
Node3: {
|
||||||
|
[NodeConnectionTypes.AiAgent]: [
|
||||||
|
[{ node: 'Node1', type: NodeConnectionTypes.AiAgent, index: 0 }],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle nodes with no connections', () => {
|
||||||
|
const connections: IConnections = {
|
||||||
|
Node1: {
|
||||||
|
[NodeConnectionTypes.Main]: [[]],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = mapConnectionsByDestination(connections);
|
||||||
|
expect(result).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
// @issue https://linear.app/n8n/issue/N8N-7880/cannot-load-some-templates
|
||||||
|
it('should handle nodes with null connections', () => {
|
||||||
|
const connections: IConnections = {
|
||||||
|
Node1: {
|
||||||
|
[NodeConnectionTypes.Main]: [
|
||||||
|
null as unknown as IConnection[],
|
||||||
|
[{ node: 'Node2', type: NodeConnectionTypes.Main, index: 0 }],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = mapConnectionsByDestination(connections);
|
||||||
|
expect(result).toEqual({
|
||||||
|
Node2: {
|
||||||
|
[NodeConnectionTypes.Main]: [[{ node: 'Node1', type: NodeConnectionTypes.Main, index: 1 }]],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle nodes with multiple input connections', () => {
|
||||||
|
const connections: IConnections = {
|
||||||
|
Node1: {
|
||||||
|
[NodeConnectionTypes.Main]: [[{ node: 'Node2', type: NodeConnectionTypes.Main, index: 0 }]],
|
||||||
|
},
|
||||||
|
Node3: {
|
||||||
|
[NodeConnectionTypes.Main]: [[{ node: 'Node2', type: NodeConnectionTypes.Main, index: 0 }]],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = mapConnectionsByDestination(connections);
|
||||||
|
expect(result).toEqual({
|
||||||
|
Node2: {
|
||||||
|
[NodeConnectionTypes.Main]: [
|
||||||
|
[
|
||||||
|
{ node: 'Node1', type: NodeConnectionTypes.Main, index: 0 },
|
||||||
|
{ node: 'Node3', type: NodeConnectionTypes.Main, index: 0 },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2106,127 +2106,6 @@ describe('Workflow', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getConnectionsByDestination', () => {
|
|
||||||
it('should return empty object when there are no connections', () => {
|
|
||||||
const result = Workflow.getConnectionsByDestination({});
|
|
||||||
|
|
||||||
expect(result).toEqual({});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return connections by destination node', () => {
|
|
||||||
const connections: IConnections = {
|
|
||||||
Node1: {
|
|
||||||
[NodeConnectionTypes.Main]: [
|
|
||||||
[
|
|
||||||
{ node: 'Node2', type: NodeConnectionTypes.Main, index: 0 },
|
|
||||||
{ node: 'Node3', type: NodeConnectionTypes.Main, index: 1 },
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const result = Workflow.getConnectionsByDestination(connections);
|
|
||||||
expect(result).toEqual({
|
|
||||||
Node2: {
|
|
||||||
[NodeConnectionTypes.Main]: [
|
|
||||||
[{ node: 'Node1', type: NodeConnectionTypes.Main, index: 0 }],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
Node3: {
|
|
||||||
[NodeConnectionTypes.Main]: [
|
|
||||||
[],
|
|
||||||
[{ node: 'Node1', type: NodeConnectionTypes.Main, index: 0 }],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle multiple connection types', () => {
|
|
||||||
const connections: IConnections = {
|
|
||||||
Node1: {
|
|
||||||
[NodeConnectionTypes.Main]: [
|
|
||||||
[{ node: 'Node2', type: NodeConnectionTypes.Main, index: 0 }],
|
|
||||||
],
|
|
||||||
[NodeConnectionTypes.AiAgent]: [
|
|
||||||
[{ node: 'Node3', type: NodeConnectionTypes.AiAgent, index: 0 }],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = Workflow.getConnectionsByDestination(connections);
|
|
||||||
expect(result).toEqual({
|
|
||||||
Node2: {
|
|
||||||
[NodeConnectionTypes.Main]: [
|
|
||||||
[{ node: 'Node1', type: NodeConnectionTypes.Main, index: 0 }],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
Node3: {
|
|
||||||
[NodeConnectionTypes.AiAgent]: [
|
|
||||||
[{ node: 'Node1', type: NodeConnectionTypes.AiAgent, index: 0 }],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle nodes with no connections', () => {
|
|
||||||
const connections: IConnections = {
|
|
||||||
Node1: {
|
|
||||||
[NodeConnectionTypes.Main]: [[]],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = Workflow.getConnectionsByDestination(connections);
|
|
||||||
expect(result).toEqual({});
|
|
||||||
});
|
|
||||||
|
|
||||||
// @issue https://linear.app/n8n/issue/N8N-7880/cannot-load-some-templates
|
|
||||||
it('should handle nodes with null connections', () => {
|
|
||||||
const connections: IConnections = {
|
|
||||||
Node1: {
|
|
||||||
[NodeConnectionTypes.Main]: [
|
|
||||||
null as unknown as IConnection[],
|
|
||||||
[{ node: 'Node2', type: NodeConnectionTypes.Main, index: 0 }],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = Workflow.getConnectionsByDestination(connections);
|
|
||||||
expect(result).toEqual({
|
|
||||||
Node2: {
|
|
||||||
[NodeConnectionTypes.Main]: [
|
|
||||||
[{ node: 'Node1', type: NodeConnectionTypes.Main, index: 1 }],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle nodes with multiple input connections', () => {
|
|
||||||
const connections: IConnections = {
|
|
||||||
Node1: {
|
|
||||||
[NodeConnectionTypes.Main]: [
|
|
||||||
[{ node: 'Node2', type: NodeConnectionTypes.Main, index: 0 }],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
Node3: {
|
|
||||||
[NodeConnectionTypes.Main]: [
|
|
||||||
[{ node: 'Node2', type: NodeConnectionTypes.Main, index: 0 }],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = Workflow.getConnectionsByDestination(connections);
|
|
||||||
expect(result).toEqual({
|
|
||||||
Node2: {
|
|
||||||
[NodeConnectionTypes.Main]: [
|
|
||||||
[
|
|
||||||
{ node: 'Node1', type: NodeConnectionTypes.Main, index: 0 },
|
|
||||||
{ node: 'Node3', type: NodeConnectionTypes.Main, index: 0 },
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getHighestNode', () => {
|
describe('getHighestNode', () => {
|
||||||
const createNode = (name: string, disabled = false) =>
|
const createNode = (name: string, disabled = false) =>
|
||||||
({
|
({
|
||||||
|
|||||||
12
patches/@lezer__highlight.patch
Normal file
12
patches/@lezer__highlight.patch
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
diff --git a/package.json b/package.json
|
||||||
|
index 2d52edb23f0c7defdfbf5f95ad1ee1fa75672b41..8d9dcbdc07cb5c73e629e22696235b19db98940d 100644
|
||||||
|
--- a/package.json
|
||||||
|
+++ b/package.json
|
||||||
|
@@ -5,6 +5,7 @@
|
||||||
|
"main": "dist/index.cjs",
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
+ "types": "./dist/highlight.d.ts",
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"require": "./dist/index.cjs"
|
||||||
|
},
|
||||||
25
pnpm-lock.yaml
generated
25
pnpm-lock.yaml
generated
@@ -204,6 +204,9 @@ overrides:
|
|||||||
date-fns-tz: 2.0.0
|
date-fns-tz: 2.0.0
|
||||||
|
|
||||||
patchedDependencies:
|
patchedDependencies:
|
||||||
|
'@lezer/highlight':
|
||||||
|
hash: 97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c
|
||||||
|
path: patches/@lezer__highlight.patch
|
||||||
'@types/express-serve-static-core@5.0.6':
|
'@types/express-serve-static-core@5.0.6':
|
||||||
hash: d602248fcd302cf5a794d1e85a411633ba9635ea5d566d6f2e0429c7ae0fa3eb
|
hash: d602248fcd302cf5a794d1e85a411633ba9635ea5d566d6f2e0429c7ae0fa3eb
|
||||||
path: patches/@types__express-serve-static-core@5.0.6.patch
|
path: patches/@types__express-serve-static-core@5.0.6.patch
|
||||||
@@ -590,7 +593,7 @@ importers:
|
|||||||
version: 6.9.3
|
version: 6.9.3
|
||||||
'@lezer/highlight':
|
'@lezer/highlight':
|
||||||
specifier: '*'
|
specifier: '*'
|
||||||
version: 1.1.1
|
version: 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||||
'@lezer/lr':
|
'@lezer/lr':
|
||||||
specifier: ^1.4.0
|
specifier: ^1.4.0
|
||||||
version: 1.4.0
|
version: 1.4.0
|
||||||
@@ -18284,7 +18287,7 @@ snapshots:
|
|||||||
'@codemirror/state': 6.4.1
|
'@codemirror/state': 6.4.1
|
||||||
'@codemirror/view': 6.26.3
|
'@codemirror/view': 6.26.3
|
||||||
'@lezer/common': 1.1.0
|
'@lezer/common': 1.1.0
|
||||||
'@lezer/highlight': 1.1.1
|
'@lezer/highlight': 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||||
'@lezer/lr': 1.4.0
|
'@lezer/lr': 1.4.0
|
||||||
style-mod: 4.1.0
|
style-mod: 4.1.0
|
||||||
|
|
||||||
@@ -18293,7 +18296,7 @@ snapshots:
|
|||||||
'@codemirror/state': 6.3.3
|
'@codemirror/state': 6.3.3
|
||||||
'@codemirror/view': 6.22.3
|
'@codemirror/view': 6.22.3
|
||||||
'@lezer/common': 1.1.0
|
'@lezer/common': 1.1.0
|
||||||
'@lezer/highlight': 1.1.1
|
'@lezer/highlight': 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||||
'@lezer/lr': 1.4.0
|
'@lezer/lr': 1.4.0
|
||||||
style-mod: 4.1.0
|
style-mod: 4.1.0
|
||||||
|
|
||||||
@@ -19467,7 +19470,7 @@ snapshots:
|
|||||||
|
|
||||||
'@lezer/css@1.1.1':
|
'@lezer/css@1.1.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@lezer/highlight': 1.1.1
|
'@lezer/highlight': 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||||
'@lezer/lr': 1.4.0
|
'@lezer/lr': 1.4.0
|
||||||
|
|
||||||
'@lezer/generator@1.7.0':
|
'@lezer/generator@1.7.0':
|
||||||
@@ -19475,24 +19478,24 @@ snapshots:
|
|||||||
'@lezer/common': 1.1.0
|
'@lezer/common': 1.1.0
|
||||||
'@lezer/lr': 1.4.0
|
'@lezer/lr': 1.4.0
|
||||||
|
|
||||||
'@lezer/highlight@1.1.1':
|
'@lezer/highlight@1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@lezer/common': 1.1.0
|
'@lezer/common': 1.1.0
|
||||||
|
|
||||||
'@lezer/html@1.3.0':
|
'@lezer/html@1.3.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@lezer/common': 1.1.0
|
'@lezer/common': 1.1.0
|
||||||
'@lezer/highlight': 1.1.1
|
'@lezer/highlight': 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||||
'@lezer/lr': 1.4.0
|
'@lezer/lr': 1.4.0
|
||||||
|
|
||||||
'@lezer/javascript@1.0.2':
|
'@lezer/javascript@1.0.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@lezer/highlight': 1.1.1
|
'@lezer/highlight': 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||||
'@lezer/lr': 1.4.0
|
'@lezer/lr': 1.4.0
|
||||||
|
|
||||||
'@lezer/json@1.0.0':
|
'@lezer/json@1.0.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@lezer/highlight': 1.1.1
|
'@lezer/highlight': 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||||
'@lezer/lr': 1.4.0
|
'@lezer/lr': 1.4.0
|
||||||
|
|
||||||
'@lezer/lr@1.4.0':
|
'@lezer/lr@1.4.0':
|
||||||
@@ -19501,7 +19504,7 @@ snapshots:
|
|||||||
|
|
||||||
'@lezer/python@1.1.5':
|
'@lezer/python@1.1.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@lezer/highlight': 1.1.1
|
'@lezer/highlight': 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||||
'@lezer/lr': 1.4.0
|
'@lezer/lr': 1.4.0
|
||||||
|
|
||||||
'@mdx-js/react@3.0.1(@types/react@18.0.27)(react@18.2.0)':
|
'@mdx-js/react@3.0.1(@types/react@18.0.27)(react@18.2.0)':
|
||||||
@@ -19605,7 +19608,7 @@ snapshots:
|
|||||||
'@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.1.0)
|
'@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.1.0)
|
||||||
'@codemirror/language': 6.10.1
|
'@codemirror/language': 6.10.1
|
||||||
'@codemirror/state': 6.4.1
|
'@codemirror/state': 6.4.1
|
||||||
'@lezer/highlight': 1.1.1
|
'@lezer/highlight': 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||||
'@lezer/lr': 1.4.0
|
'@lezer/lr': 1.4.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@codemirror/view'
|
- '@codemirror/view'
|
||||||
@@ -23724,7 +23727,7 @@ snapshots:
|
|||||||
'@codemirror/view': 6.26.3
|
'@codemirror/view': 6.26.3
|
||||||
'@lezer/common': 1.1.0
|
'@lezer/common': 1.1.0
|
||||||
'@lezer/css': 1.1.1
|
'@lezer/css': 1.1.1
|
||||||
'@lezer/highlight': 1.1.1
|
'@lezer/highlight': 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||||
'@lezer/html': 1.3.0
|
'@lezer/html': 1.3.0
|
||||||
'@lezer/lr': 1.4.0
|
'@lezer/lr': 1.4.0
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user