mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-15 17:16:45 +00:00
refactor(editor): Remove part of getCurrentWorkflow usages (#16148)
This commit is contained in:
@@ -123,6 +123,7 @@
|
||||
"ics": "patches/ics.patch",
|
||||
"minifaker": "patches/minifaker.patch",
|
||||
"z-vue-scan": "patches/z-vue-scan.patch",
|
||||
"@lezer/highlight": "patches/@lezer__highlight.patch",
|
||||
"v-code-diff": "patches/v-code-diff.patch"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,8 +174,7 @@ function useJsonFieldCompletions() {
|
||||
try {
|
||||
const activeNode = ndvStore.activeNode;
|
||||
if (activeNode) {
|
||||
const workflow = workflowsStore.getCurrentWorkflow();
|
||||
const input = workflow.connectionsByDestinationNode[activeNode.name];
|
||||
const input = workflowsStore.connectionsByDestinationNode[activeNode.name];
|
||||
return input.main[0] ? input.main[0][0].node : null;
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -84,7 +84,7 @@ function getMultipleNodesText(nodeName: string): string {
|
||||
return '';
|
||||
|
||||
const activeNodeConnections =
|
||||
props.workflow.connectionsByDestinationNode[activeNode.value.name].main || [];
|
||||
workflowsStore.connectionsByDestinationNode[activeNode.value.name].main || [];
|
||||
// Collect indexes of connected nodes
|
||||
const connectedInputIndexes = activeNodeConnections.reduce((acc: number[], node, index) => {
|
||||
if (node?.[0] && node[0].node === nodeName) return [...acc, index];
|
||||
|
||||
@@ -804,7 +804,8 @@ function getNodeHints(): NodeHint[] {
|
||||
node: node.value,
|
||||
nodeType: nodeType.value,
|
||||
nodeOutputData,
|
||||
workflow: props.workflow,
|
||||
nodes: workflowsStore.allNodes,
|
||||
connections: workflowsStore.connectionsBySourceNode,
|
||||
hasNodeRun: hasNodeRun.value,
|
||||
hasMultipleInputItems,
|
||||
});
|
||||
|
||||
@@ -114,6 +114,7 @@ const expressionResolveCtx = computed<ExpressionLocalResolveContext | undefined>
|
||||
nodeName,
|
||||
additionalKeys: {},
|
||||
inputNode: findInputNode(),
|
||||
connections: workflowsStore.connectionsBySourceNode,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -216,6 +216,8 @@ describe('useCanvasMapping', () => {
|
||||
});
|
||||
|
||||
it('should handle input and output connections', () => {
|
||||
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||
|
||||
const [manualTriggerNode, setNode] = mockNodes.slice(0, 2);
|
||||
const nodes = [manualTriggerNode, setNode];
|
||||
const connections = {
|
||||
@@ -225,6 +227,9 @@ describe('useCanvasMapping', () => {
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
workflowsStore.workflow.connections = connections;
|
||||
|
||||
const workflowObject = createTestWorkflowObject({
|
||||
nodes,
|
||||
connections,
|
||||
|
||||
@@ -59,6 +59,7 @@ import { useNodeHelpers } from './useNodeHelpers';
|
||||
import { getTriggerNodeServiceName } from '@/utils/nodeTypesUtils';
|
||||
import { useNodeDirtiness } from '@/composables/useNodeDirtiness';
|
||||
import { getNodeIconSource } from '../utils/nodeIcon';
|
||||
import * as workflowUtils from 'n8n-workflow/common';
|
||||
|
||||
export function useCanvasMapping({
|
||||
nodes,
|
||||
@@ -571,56 +572,62 @@ export function useCanvasMapping({
|
||||
}, {});
|
||||
});
|
||||
|
||||
const mappedNodes = computed<CanvasNode[]>(() => [
|
||||
...nodes.value.map<CanvasNode>((node) => {
|
||||
const inputConnections = workflowObject.value.connectionsByDestinationNode[node.name] ?? {};
|
||||
const outputConnections = workflowObject.value.connectionsBySourceNode[node.name] ?? {};
|
||||
const mappedNodes = computed<CanvasNode[]>(() => {
|
||||
const connectionsBySourceNode = connections.value;
|
||||
const connectionsByDestinationNode =
|
||||
workflowUtils.mapConnectionsByDestination(connectionsBySourceNode);
|
||||
|
||||
const data: CanvasNodeData = {
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
subtitle: nodeSubtitleById.value[node.id] ?? '',
|
||||
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 [
|
||||
...nodes.value.map<CanvasNode>((node) => {
|
||||
const outputConnections = connectionsBySourceNode[node.name] ?? {};
|
||||
const inputConnections = connectionsByDestinationNode[node.name] ?? {};
|
||||
|
||||
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 data: CanvasNodeData = {
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
subtitle: nodeSubtitleById.value[node.id] ?? '',
|
||||
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 {
|
||||
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[]>(() => {
|
||||
return mapLegacyConnectionsToCanvasConnections(connections.value ?? [], nodes.value ?? []).map(
|
||||
|
||||
@@ -253,6 +253,7 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
|
||||
if (sourceData === null) {
|
||||
const parentNodes = workflow.getParentNodes(name, NodeConnectionTypes.Main, 1);
|
||||
const executeData = workflowHelpers.executeData(
|
||||
workflow.connectionsBySourceNode,
|
||||
parentNodes,
|
||||
name,
|
||||
NodeConnectionTypes.Main,
|
||||
|
||||
@@ -14,11 +14,8 @@ import {
|
||||
createTestWorkflowExecutionResponse,
|
||||
createTestWorkflowObject,
|
||||
} from '@/__tests__/mocks';
|
||||
import {
|
||||
NodeConnectionTypes,
|
||||
WEBHOOK_NODE_TYPE,
|
||||
type AssignmentCollectionValue,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionTypes, WEBHOOK_NODE_TYPE } from 'n8n-workflow';
|
||||
import type { AssignmentCollectionValue, IConnections } from 'n8n-workflow';
|
||||
import * as apiWebhooks from '@n8n/rest-api-client/api/webhooks';
|
||||
import { mockedStore } from '@/__tests__/utils';
|
||||
|
||||
@@ -519,7 +516,7 @@ describe('useWorkflowHelpers', () => {
|
||||
const inputName = 'main';
|
||||
const runIndex = 0;
|
||||
|
||||
const result = executeData(parentNodes, currentNode, inputName, runIndex);
|
||||
const result = executeData({}, parentNodes, currentNode, inputName, runIndex);
|
||||
|
||||
expect(result).toEqual({
|
||||
node: {},
|
||||
@@ -538,18 +535,15 @@ describe('useWorkflowHelpers', () => {
|
||||
const jsonData = {
|
||||
name: 'Test',
|
||||
};
|
||||
workflowsStore.getCurrentWorkflow.mockReturnValue({
|
||||
connectionsByDestinationNode: {
|
||||
Set: {
|
||||
main: [
|
||||
[
|
||||
{ node: 'Start', index: 0, type: 'main' },
|
||||
{ node: 'Set', index: 0, type: 'main' },
|
||||
],
|
||||
],
|
||||
},
|
||||
|
||||
const connectionsBySourceNode: IConnections = {
|
||||
Start: {
|
||||
main: [[{ node: 'Set', index: 0, type: 'main' }]],
|
||||
},
|
||||
} as never);
|
||||
Set: {
|
||||
main: [[{ node: 'Start', index: 0, type: 'main' }]],
|
||||
},
|
||||
};
|
||||
|
||||
workflowsStore.workflowExecutionData = {
|
||||
data: {
|
||||
@@ -575,7 +569,13 @@ describe('useWorkflowHelpers', () => {
|
||||
},
|
||||
} as unknown as IExecutionResponse;
|
||||
|
||||
const result = executeData(parentNodes, currentNode, inputName, runIndex);
|
||||
const result = executeData(
|
||||
connectionsBySourceNode,
|
||||
parentNodes,
|
||||
currentNode,
|
||||
inputName,
|
||||
runIndex,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
node: {},
|
||||
@@ -609,18 +609,15 @@ describe('useWorkflowHelpers', () => {
|
||||
const jsonData = {
|
||||
name: 'Test',
|
||||
};
|
||||
workflowsStore.getCurrentWorkflow.mockReturnValue({
|
||||
connectionsByDestinationNode: {
|
||||
Set: {
|
||||
main: [
|
||||
[
|
||||
{ node: 'Start', index: 0, type: 'main' },
|
||||
{ node: 'Set', index: 0, type: 'main' },
|
||||
],
|
||||
],
|
||||
},
|
||||
|
||||
const connectionsBySourceNode: IConnections = {
|
||||
Start: {
|
||||
main: [[{ node: 'Set', index: 0, type: 'main' }]],
|
||||
},
|
||||
} as never);
|
||||
Set: {
|
||||
main: [[{ node: 'Start', index: 0, type: 'main' }]],
|
||||
},
|
||||
};
|
||||
|
||||
workflowsStore.workflowExecutionData = {
|
||||
data: {
|
||||
@@ -646,7 +643,13 @@ describe('useWorkflowHelpers', () => {
|
||||
},
|
||||
} as unknown as IExecutionResponse;
|
||||
|
||||
const result = executeData(parentNodes, currentNode, inputName, runIndex);
|
||||
const result = executeData(
|
||||
connectionsBySourceNode,
|
||||
parentNodes,
|
||||
currentNode,
|
||||
inputName,
|
||||
runIndex,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
node: {},
|
||||
@@ -686,22 +689,20 @@ describe('useWorkflowHelpers', () => {
|
||||
name: 'Test B',
|
||||
};
|
||||
|
||||
workflowsStore.getCurrentWorkflow.mockReturnValue({
|
||||
connectionsByDestinationNode: {
|
||||
Set: {
|
||||
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' },
|
||||
],
|
||||
],
|
||||
},
|
||||
const connectionsBySourceNode: IConnections = {
|
||||
'Parent A': {
|
||||
main: [[{ node: 'Set', type: 'main', index: 0 }]],
|
||||
},
|
||||
} 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 = {
|
||||
data: {
|
||||
@@ -742,7 +743,13 @@ describe('useWorkflowHelpers', () => {
|
||||
},
|
||||
} as unknown as IExecutionResponse;
|
||||
|
||||
const result = executeData(parentNodes, currentNode, inputName, runIndex);
|
||||
const result = executeData(
|
||||
connectionsBySourceNode,
|
||||
parentNodes,
|
||||
currentNode,
|
||||
inputName,
|
||||
runIndex,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
node: {},
|
||||
@@ -779,7 +786,7 @@ describe('useWorkflowHelpers', () => {
|
||||
};
|
||||
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.source).toEqual({ main: [{ previousNode: 'ParentNode' }] });
|
||||
@@ -802,20 +809,23 @@ describe('useWorkflowHelpers', () => {
|
||||
} 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.source).toEqual({
|
||||
@@ -841,20 +851,24 @@ describe('useWorkflowHelpers', () => {
|
||||
} 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.source).toEqual({
|
||||
@@ -874,7 +888,7 @@ describe('useWorkflowHelpers', () => {
|
||||
workflowsStore.shouldReplaceInputDataWithPinData = false;
|
||||
workflowsStore.getWorkflowRunData = null;
|
||||
|
||||
const result = executeData(parentNodes, currentNode, inputName, runIndex);
|
||||
const result = executeData({}, parentNodes, currentNode, inputName, runIndex);
|
||||
|
||||
expect(result.data).toEqual({});
|
||||
expect(result.source).toBeNull();
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
NodeHelpers,
|
||||
WEBHOOK_NODE_TYPE,
|
||||
} from 'n8n-workflow';
|
||||
import * as workflowUtils from 'n8n-workflow/common';
|
||||
|
||||
import type {
|
||||
ICredentialsResponse,
|
||||
@@ -71,6 +72,7 @@ export type ResolveParameterOptions = {
|
||||
additionalKeys?: IWorkflowDataProxyAdditionalKeys;
|
||||
isForCredential?: boolean;
|
||||
contextNodeName?: string;
|
||||
connections?: IConnections;
|
||||
};
|
||||
|
||||
export function resolveParameter<T = IDataObject>(
|
||||
@@ -81,6 +83,7 @@ export function resolveParameter<T = IDataObject>(
|
||||
return resolveParameterImpl(
|
||||
parameter,
|
||||
() => opts.workflow,
|
||||
opts.connections,
|
||||
opts.envVars,
|
||||
opts.workflow.getNode(opts.nodeName),
|
||||
opts.execution,
|
||||
@@ -100,6 +103,7 @@ export function resolveParameter<T = IDataObject>(
|
||||
return resolveParameterImpl(
|
||||
parameter,
|
||||
workflowsStore.getCurrentWorkflow,
|
||||
workflowsStore.connectionsBySourceNode,
|
||||
useEnvironmentsStore().variablesAsObject,
|
||||
useNDVStore().activeNode,
|
||||
workflowsStore.workflowExecutionData,
|
||||
@@ -113,6 +117,7 @@ export function resolveParameter<T = IDataObject>(
|
||||
function resolveParameterImpl<T = IDataObject>(
|
||||
parameter: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
||||
getContextWorkflow: () => Workflow,
|
||||
connections: IConnections,
|
||||
envVars: Record<string, string | boolean | number>,
|
||||
ndvActiveNode: INodeUi | null,
|
||||
executionData: IExecutionResponse | null,
|
||||
@@ -200,11 +205,11 @@ function resolveParameterImpl<T = IDataObject>(
|
||||
}
|
||||
|
||||
let _connectionInputData = connectionInputData(
|
||||
connections,
|
||||
parentNode,
|
||||
contextNode!.name,
|
||||
inputName,
|
||||
runIndexParent,
|
||||
getContextWorkflow,
|
||||
shouldReplaceInputDataWithPinData,
|
||||
pinData,
|
||||
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
|
||||
// (Gets for example used by the Memory connected to the Chat-Trigger-Node)
|
||||
const _executeData = executeDataImpl(
|
||||
connections,
|
||||
[contextNode.name],
|
||||
contextNode.name,
|
||||
inputName,
|
||||
0,
|
||||
getContextWorkflow,
|
||||
shouldReplaceInputDataWithPinData,
|
||||
pinData,
|
||||
executionData?.data?.resultData.runData ?? null,
|
||||
@@ -265,11 +270,11 @@ function resolveParameterImpl<T = IDataObject>(
|
||||
runIndexCurrent = workflowRunData[contextNode!.name].length - 1;
|
||||
}
|
||||
let _executeData = executeDataImpl(
|
||||
connections,
|
||||
parentNode,
|
||||
contextNode!.name,
|
||||
inputName,
|
||||
runIndexCurrent,
|
||||
getContextWorkflow,
|
||||
shouldReplaceInputDataWithPinData,
|
||||
pinData,
|
||||
executionData?.data?.resultData.runData ?? null,
|
||||
@@ -279,11 +284,11 @@ function resolveParameterImpl<T = IDataObject>(
|
||||
if (!_executeData.source) {
|
||||
// fallback to parent's run index for multi-output case
|
||||
_executeData = executeDataImpl(
|
||||
connections,
|
||||
parentNode,
|
||||
contextNode!.name,
|
||||
inputName,
|
||||
runIndexParent,
|
||||
getContextWorkflow,
|
||||
shouldReplaceInputDataWithPinData,
|
||||
pinData,
|
||||
executionData?.data?.resultData.runData ?? null,
|
||||
@@ -310,6 +315,7 @@ export function resolveRequiredParameters(
|
||||
currentParameter: INodeProperties,
|
||||
parameters: INodeParameters,
|
||||
opts: {
|
||||
connections?: IConnections;
|
||||
targetItem?: TargetItem;
|
||||
inputNodeName?: string;
|
||||
inputRunIndex?: number;
|
||||
@@ -382,11 +388,11 @@ function getNodeTypes(): INodeTypes {
|
||||
// TODO: move to separate file
|
||||
// Returns connectionInputData to be able to execute an expression.
|
||||
function connectionInputData(
|
||||
connections: IConnections,
|
||||
parentNode: string[],
|
||||
currentNode: string,
|
||||
inputName: string,
|
||||
runIndex: number,
|
||||
getContextWorkflow: () => Workflow,
|
||||
shouldReplaceInputDataWithPinData: boolean,
|
||||
pinData: IPinData | undefined,
|
||||
workflowRunData: IRunData | null,
|
||||
@@ -394,11 +400,11 @@ function connectionInputData(
|
||||
): INodeExecutionData[] | null {
|
||||
let connectionInputData: INodeExecutionData[] | null = null;
|
||||
const _executeData = executeDataImpl(
|
||||
connections,
|
||||
parentNode,
|
||||
currentNode,
|
||||
inputName,
|
||||
runIndex,
|
||||
getContextWorkflow,
|
||||
shouldReplaceInputDataWithPinData,
|
||||
pinData,
|
||||
workflowRunData,
|
||||
@@ -431,6 +437,7 @@ function connectionInputData(
|
||||
}
|
||||
|
||||
export function executeData(
|
||||
connections: IConnections,
|
||||
parentNodes: string[],
|
||||
currentNode: string,
|
||||
inputName: string,
|
||||
@@ -440,11 +447,11 @@ export function executeData(
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
|
||||
return executeDataImpl(
|
||||
connections,
|
||||
parentNodes,
|
||||
currentNode,
|
||||
inputName,
|
||||
runIndex,
|
||||
workflowsStore.getCurrentWorkflow,
|
||||
workflowsStore.shouldReplaceInputDataWithPinData,
|
||||
workflowsStore.pinnedWorkflowData,
|
||||
workflowsStore.getWorkflowRunData,
|
||||
@@ -454,16 +461,18 @@ export function executeData(
|
||||
|
||||
// TODO: move to separate file
|
||||
function executeDataImpl(
|
||||
connections: IConnections,
|
||||
parentNodes: string[],
|
||||
currentNode: string,
|
||||
inputName: string,
|
||||
runIndex: number,
|
||||
getContextWorkflow: () => Workflow,
|
||||
shouldReplaceInputDataWithPinData: boolean,
|
||||
pinData: IPinData | undefined,
|
||||
workflowRunData: IRunData | null,
|
||||
parentRunIndex?: number,
|
||||
): IExecuteData {
|
||||
const connectionsByDestinationNode = workflowUtils.mapConnectionsByDestination(connections);
|
||||
|
||||
const executeData = {
|
||||
node: {},
|
||||
data: {},
|
||||
@@ -507,15 +516,12 @@ function executeDataImpl(
|
||||
[inputName]: workflowRunData[currentNode][runIndex].source,
|
||||
};
|
||||
} else {
|
||||
const workflow = getContextWorkflow();
|
||||
|
||||
let previousNodeOutput: number | undefined;
|
||||
// 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
|
||||
if (workflow.connectionsByDestinationNode[currentNode]?.main) {
|
||||
mainConnections: for (const mainConnections of workflow.connectionsByDestinationNode[
|
||||
currentNode
|
||||
].main) {
|
||||
if (connectionsByDestinationNode[currentNode]?.main) {
|
||||
mainConnections: for (const mainConnections of connectionsByDestinationNode[currentNode]
|
||||
.main) {
|
||||
for (const connection of mainConnections ?? []) {
|
||||
if (
|
||||
connection.type === NodeConnectionTypes.Main &&
|
||||
|
||||
@@ -149,9 +149,8 @@ export const useNDVStore = defineStore(STORES.NDV, () => {
|
||||
|
||||
const ndvNodeInputNumber = computed(() => {
|
||||
const returnData: { [nodeName: string]: number[] } = {};
|
||||
const workflow = workflowsStore.getCurrentWorkflow();
|
||||
const activeNodeConections = (
|
||||
workflow.connectionsByDestinationNode[activeNode.value?.name || ''] ?? {}
|
||||
workflowsStore.connectionsByDestinationNode[activeNode.value?.name || ''] ?? {}
|
||||
).main;
|
||||
|
||||
if (!activeNodeConections || activeNodeConections.length < 2) return returnData;
|
||||
|
||||
@@ -66,6 +66,7 @@ import {
|
||||
Workflow,
|
||||
TelemetryHelpers,
|
||||
} from 'n8n-workflow';
|
||||
import * as workflowUtils from 'n8n-workflow/common';
|
||||
import findLast from 'lodash/findLast';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import pick from 'lodash/pick';
|
||||
@@ -294,10 +295,17 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
||||
|
||||
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(() =>
|
||||
Workflow.getConnectionsByDestination(workflow.value.connections),
|
||||
workflowUtils.mapConnectionsByDestination(workflow.value.connections),
|
||||
);
|
||||
|
||||
// End section
|
||||
|
||||
const selectableTriggerNodes = computed(() =>
|
||||
workflowTriggerNodes.value.filter((node) => !node.disabled && !isChatNode(node)),
|
||||
);
|
||||
@@ -384,7 +392,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
||||
}
|
||||
|
||||
function getNodeByName(nodeName: string): INodeUi | null {
|
||||
return nodesByName.value[nodeName] || null;
|
||||
return workflowUtils.getNodeByName(nodesByName.value, nodeName);
|
||||
}
|
||||
|
||||
function getNodeById(nodeId: string): INodeUi | undefined {
|
||||
@@ -1955,6 +1963,8 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
||||
getWorkflowResultDataByNodeName,
|
||||
allConnections,
|
||||
allNodes,
|
||||
connectionsBySourceNode,
|
||||
connectionsByDestinationNode,
|
||||
isWaitingExecution,
|
||||
isWorkflowRunning,
|
||||
canvasNames,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 };
|
||||
|
||||
@@ -40,6 +40,7 @@ export interface ExpressionLocalResolveContext {
|
||||
envVars: Record<string, Basic>;
|
||||
additionalKeys: IWorkflowDataProxyAdditionalKeys;
|
||||
workflow: Workflow;
|
||||
connections: IConnections;
|
||||
execution: IExecutionResponse | null;
|
||||
nodeName: string;
|
||||
/**
|
||||
|
||||
@@ -23,6 +23,8 @@ import { SET_NODE_TYPE, STICKY_NODE_TYPE } from '@/constants';
|
||||
import { createTestNode } from '@/__tests__/mocks';
|
||||
import type { GraphNode } from '@vue-flow/core';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { setActivePinia } from 'pinia';
|
||||
|
||||
describe('getGenericHints', () => {
|
||||
let mockWorkflowNode: MockProxy<INode>;
|
||||
@@ -34,6 +36,9 @@ describe('getGenericHints', () => {
|
||||
let hasNodeRun: boolean;
|
||||
|
||||
beforeEach(() => {
|
||||
const pinia = createTestingPinia({});
|
||||
setActivePinia(pinia);
|
||||
|
||||
mockWorkflowNode = mock<INode>();
|
||||
mockNode = mock<INodeUi>({ type: 'test' });
|
||||
mockNodeType = mock<INodeTypeDescription>();
|
||||
@@ -55,8 +60,9 @@ describe('getGenericHints', () => {
|
||||
nodeType: mockNodeType,
|
||||
nodeOutputData: mockNodeOutputData,
|
||||
hasMultipleInputItems,
|
||||
workflow: mockWorkflow,
|
||||
hasNodeRun,
|
||||
nodes: [],
|
||||
connections: {},
|
||||
});
|
||||
|
||||
expect(hints).toEqual([
|
||||
@@ -80,8 +86,9 @@ describe('getGenericHints', () => {
|
||||
nodeType: mockNodeType,
|
||||
nodeOutputData: mockNodeOutputData,
|
||||
hasMultipleInputItems,
|
||||
workflow: mockWorkflow,
|
||||
hasNodeRun,
|
||||
nodes: [],
|
||||
connections: {},
|
||||
});
|
||||
|
||||
expect(hints).toEqual([
|
||||
@@ -118,8 +125,9 @@ describe('getGenericHints', () => {
|
||||
nodeType: mockNodeType,
|
||||
nodeOutputData: mockNodeOutputData,
|
||||
hasMultipleInputItems,
|
||||
workflow: mockWorkflow,
|
||||
hasNodeRun,
|
||||
nodes: [],
|
||||
connections: {},
|
||||
});
|
||||
|
||||
expect(hints).toEqual([
|
||||
@@ -142,8 +150,9 @@ describe('getGenericHints', () => {
|
||||
nodeType: mockNodeType,
|
||||
nodeOutputData: mockNodeOutputData,
|
||||
hasMultipleInputItems,
|
||||
workflow: mockWorkflow,
|
||||
hasNodeRun,
|
||||
nodes: [],
|
||||
connections: {},
|
||||
});
|
||||
|
||||
expect(hints).toEqual([
|
||||
@@ -166,8 +175,9 @@ describe('getGenericHints', () => {
|
||||
nodeType: mockNodeType,
|
||||
nodeOutputData: mockNodeOutputData,
|
||||
hasMultipleInputItems,
|
||||
workflow: mockWorkflow,
|
||||
hasNodeRun,
|
||||
nodes: [],
|
||||
connections: {},
|
||||
});
|
||||
|
||||
expect(hints).toEqual([
|
||||
@@ -191,8 +201,9 @@ describe('getGenericHints', () => {
|
||||
nodeType: mockNodeType,
|
||||
nodeOutputData: mockNodeOutputData,
|
||||
hasMultipleInputItems,
|
||||
workflow: mockWorkflow,
|
||||
hasNodeRun,
|
||||
nodes: [],
|
||||
connections: {},
|
||||
});
|
||||
|
||||
expect(hints).toEqual([
|
||||
|
||||
@@ -11,11 +11,11 @@ import {
|
||||
import type { INodeUi, XYPosition } from '@/Interface';
|
||||
import type {
|
||||
AssignmentCollectionValue,
|
||||
IConnections,
|
||||
INode,
|
||||
INodeExecutionData,
|
||||
INodeTypeDescription,
|
||||
NodeHint,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeHelpers, SEND_AND_WAIT_OPERATION } from 'n8n-workflow';
|
||||
import type { RouteLocation } from 'vue-router';
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
type Rect,
|
||||
type ViewportTransform,
|
||||
} from '@vue-flow/core';
|
||||
import * as workflowUtils from 'n8n-workflow/common';
|
||||
|
||||
/*
|
||||
* Canvas constants and functions
|
||||
@@ -371,7 +372,8 @@ export function getGenericHints({
|
||||
nodeType,
|
||||
nodeOutputData,
|
||||
hasMultipleInputItems,
|
||||
workflow,
|
||||
nodes,
|
||||
connections,
|
||||
hasNodeRun,
|
||||
}: {
|
||||
workflowNode: INode;
|
||||
@@ -379,7 +381,8 @@ export function getGenericHints({
|
||||
nodeType: INodeTypeDescription;
|
||||
nodeOutputData: INodeExecutionData[];
|
||||
hasMultipleInputItems: boolean;
|
||||
workflow: Workflow;
|
||||
nodes: INode[];
|
||||
connections: IConnections;
|
||||
hasNodeRun: boolean;
|
||||
}) {
|
||||
const nodeHints: NodeHint[] = [];
|
||||
@@ -417,7 +420,7 @@ export function getGenericHints({
|
||||
hasMultipleInputItems &&
|
||||
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) {
|
||||
nodeHints.push({
|
||||
message:
|
||||
@@ -429,7 +432,7 @@ export function getGenericHints({
|
||||
|
||||
// add sendAndWait hint
|
||||
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) {
|
||||
nodeHints.push({
|
||||
message: 'This action will run only once, for the first input item',
|
||||
@@ -470,9 +473,8 @@ export function getGenericHints({
|
||||
|
||||
// Split In Batches setup hints
|
||||
if (node.type === SPLIT_IN_BATCHES_NODE_TYPE) {
|
||||
const { connectionsBySourceNode } = workflow;
|
||||
|
||||
const firstNodesInLoop = connectionsBySourceNode[node.name]?.main[1] || [];
|
||||
const firstNodesInLoop =
|
||||
workflowUtils.mapConnectionsByDestination(connections)[node.name]?.main[1] || [];
|
||||
|
||||
if (!firstNodesInLoop.length) {
|
||||
nodeHints.push({
|
||||
@@ -482,7 +484,7 @@ export function getGenericHints({
|
||||
});
|
||||
} else {
|
||||
for (const nodeInConnection of firstNodesInLoop || []) {
|
||||
const nodeChilds = workflow.getChildNodes(nodeInConnection.node) || [];
|
||||
const nodeChilds = workflowUtils.getChildNodes(connections, nodeInConnection.node) || [];
|
||||
if (!nodeChilds.includes(node.name)) {
|
||||
nodeHints.push({
|
||||
message:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"extends": "@n8n/typescript-config/tsconfig.frontend.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"moduleResolution": "bundler",
|
||||
"rootDirs": [
|
||||
".",
|
||||
"../@n8n/rest-api-client/src",
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
"import": "./dist/esm/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": {
|
||||
|
||||
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 './constants';
|
||||
export * from './common';
|
||||
export * from './cron';
|
||||
export * from './deferred-promise';
|
||||
export * from './global-state';
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-for-in-array */
|
||||
import {
|
||||
getNodeByName,
|
||||
getConnectedNodes,
|
||||
getChildNodes,
|
||||
getParentNodes,
|
||||
mapConnectionsByDestination,
|
||||
} from './common';
|
||||
|
||||
import {
|
||||
MANUAL_CHAT_TRIGGER_LANGCHAIN_NODE_TYPE,
|
||||
NODES_WITH_RENAMABLE_CONTENT,
|
||||
@@ -123,9 +131,7 @@ export class Workflow {
|
||||
this.connectionsBySourceNode = parameters.connections;
|
||||
|
||||
// Save also the connections by the destination nodes
|
||||
this.connectionsByDestinationNode = Workflow.getConnectionsByDestination(
|
||||
parameters.connections,
|
||||
);
|
||||
this.connectionsByDestinationNode = mapConnectionsByDestination(parameters.connections);
|
||||
|
||||
this.active = parameters.active || false;
|
||||
|
||||
@@ -146,11 +152,6 @@ export class Workflow {
|
||||
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 {
|
||||
const returnConnection: IConnections = {};
|
||||
|
||||
@@ -288,11 +289,7 @@ export class Workflow {
|
||||
* @param {string} nodeName Name of the node to return
|
||||
*/
|
||||
getNode(nodeName: string): INode | null {
|
||||
if (this.nodes.hasOwnProperty(nodeName)) {
|
||||
return this.nodes[nodeName];
|
||||
}
|
||||
|
||||
return null;
|
||||
return getNodeByName(this.nodes, nodeName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -477,9 +474,7 @@ export class Workflow {
|
||||
}
|
||||
|
||||
// Use the updated connections to create updated connections by destination nodes
|
||||
this.connectionsByDestinationNode = Workflow.getConnectionsByDestination(
|
||||
this.connectionsBySourceNode,
|
||||
);
|
||||
this.connectionsByDestinationNode = mapConnectionsByDestination(this.connectionsBySourceNode);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -576,7 +571,7 @@ export class Workflow {
|
||||
type: NodeConnectionType | 'ALL' | 'ALL_NON_MAIN' = NodeConnectionTypes.Main,
|
||||
depth = -1,
|
||||
): 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,
|
||||
depth = -1,
|
||||
): 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,
|
||||
checkedNodesIncoming?: string[],
|
||||
): string[] {
|
||||
depth = depth === -1 ? -1 : depth;
|
||||
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;
|
||||
return getConnectedNodes(connections, nodeName, connectionType, depth, checkedNodesIncoming);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
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', () => {
|
||||
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
|
||||
|
||||
patchedDependencies:
|
||||
'@lezer/highlight':
|
||||
hash: 97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c
|
||||
path: patches/@lezer__highlight.patch
|
||||
'@types/express-serve-static-core@5.0.6':
|
||||
hash: d602248fcd302cf5a794d1e85a411633ba9635ea5d566d6f2e0429c7ae0fa3eb
|
||||
path: patches/@types__express-serve-static-core@5.0.6.patch
|
||||
@@ -590,7 +593,7 @@ importers:
|
||||
version: 6.9.3
|
||||
'@lezer/highlight':
|
||||
specifier: '*'
|
||||
version: 1.1.1
|
||||
version: 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||
'@lezer/lr':
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
@@ -18284,7 +18287,7 @@ snapshots:
|
||||
'@codemirror/state': 6.4.1
|
||||
'@codemirror/view': 6.26.3
|
||||
'@lezer/common': 1.1.0
|
||||
'@lezer/highlight': 1.1.1
|
||||
'@lezer/highlight': 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||
'@lezer/lr': 1.4.0
|
||||
style-mod: 4.1.0
|
||||
|
||||
@@ -18293,7 +18296,7 @@ snapshots:
|
||||
'@codemirror/state': 6.3.3
|
||||
'@codemirror/view': 6.22.3
|
||||
'@lezer/common': 1.1.0
|
||||
'@lezer/highlight': 1.1.1
|
||||
'@lezer/highlight': 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||
'@lezer/lr': 1.4.0
|
||||
style-mod: 4.1.0
|
||||
|
||||
@@ -19467,7 +19470,7 @@ snapshots:
|
||||
|
||||
'@lezer/css@1.1.1':
|
||||
dependencies:
|
||||
'@lezer/highlight': 1.1.1
|
||||
'@lezer/highlight': 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||
'@lezer/lr': 1.4.0
|
||||
|
||||
'@lezer/generator@1.7.0':
|
||||
@@ -19475,24 +19478,24 @@ snapshots:
|
||||
'@lezer/common': 1.1.0
|
||||
'@lezer/lr': 1.4.0
|
||||
|
||||
'@lezer/highlight@1.1.1':
|
||||
'@lezer/highlight@1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)':
|
||||
dependencies:
|
||||
'@lezer/common': 1.1.0
|
||||
|
||||
'@lezer/html@1.3.0':
|
||||
dependencies:
|
||||
'@lezer/common': 1.1.0
|
||||
'@lezer/highlight': 1.1.1
|
||||
'@lezer/highlight': 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||
'@lezer/lr': 1.4.0
|
||||
|
||||
'@lezer/javascript@1.0.2':
|
||||
dependencies:
|
||||
'@lezer/highlight': 1.1.1
|
||||
'@lezer/highlight': 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||
'@lezer/lr': 1.4.0
|
||||
|
||||
'@lezer/json@1.0.0':
|
||||
dependencies:
|
||||
'@lezer/highlight': 1.1.1
|
||||
'@lezer/highlight': 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||
'@lezer/lr': 1.4.0
|
||||
|
||||
'@lezer/lr@1.4.0':
|
||||
@@ -19501,7 +19504,7 @@ snapshots:
|
||||
|
||||
'@lezer/python@1.1.5':
|
||||
dependencies:
|
||||
'@lezer/highlight': 1.1.1
|
||||
'@lezer/highlight': 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||
'@lezer/lr': 1.4.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/language': 6.10.1
|
||||
'@codemirror/state': 6.4.1
|
||||
'@lezer/highlight': 1.1.1
|
||||
'@lezer/highlight': 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||
'@lezer/lr': 1.4.0
|
||||
transitivePeerDependencies:
|
||||
- '@codemirror/view'
|
||||
@@ -23724,7 +23727,7 @@ snapshots:
|
||||
'@codemirror/view': 6.26.3
|
||||
'@lezer/common': 1.1.0
|
||||
'@lezer/css': 1.1.1
|
||||
'@lezer/highlight': 1.1.1
|
||||
'@lezer/highlight': 1.1.1(patch_hash=97f85e6fe46f23015ea0dd420e33d584bc2dc71633910cf131321da31b27ca8c)
|
||||
'@lezer/html': 1.3.0
|
||||
'@lezer/lr': 1.4.0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user