feat(core): Add support for building LLM applications (#7235)
This extracts all core and editor changes from #7246 and #7137, so that we can get these changes merged first. ADO-1120 [DB Tests](https://github.com/n8n-io/n8n/actions/runs/6379749011) [E2E Tests](https://github.com/n8n-io/n8n/actions/runs/6379751480) [Workflow Tests](https://github.com/n8n-io/n8n/actions/runs/6379752828) --------- Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com> Co-authored-by: Oleg Ivaniv <me@olegivaniv.com> Co-authored-by: Alex Grozav <alex@grozav.com> Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
committed by
GitHub
parent
04dfcd73be
commit
00a4b8b0c6
@@ -4,9 +4,20 @@ import { mapStores } from 'pinia';
|
||||
|
||||
import type { INodeUi } from '@/Interface';
|
||||
import { deviceSupportHelpers } from '@/mixins/deviceSupportHelpers';
|
||||
import { NO_OP_NODE_TYPE } from '@/constants';
|
||||
import {
|
||||
NO_OP_NODE_TYPE,
|
||||
NODE_CONNECTION_TYPE_ALLOW_MULTIPLE,
|
||||
NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS,
|
||||
NODE_MIN_INPUT_ITEMS_COUNT,
|
||||
} from '@/constants';
|
||||
|
||||
import type { INodeTypeDescription } from 'n8n-workflow';
|
||||
import { NodeHelpers, NodeConnectionType } from 'n8n-workflow';
|
||||
import type {
|
||||
ConnectionTypes,
|
||||
INodeInputConfiguration,
|
||||
INodeTypeDescription,
|
||||
INodeOutputConfiguration,
|
||||
} from 'n8n-workflow';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
@@ -15,6 +26,33 @@ import type { Endpoint, EndpointOptions } from '@jsplumb/core';
|
||||
import * as NodeViewUtils from '@/utils/nodeViewUtils';
|
||||
import { useHistoryStore } from '@/stores/history.store';
|
||||
import { useCanvasStore } from '@/stores/canvas.store';
|
||||
import type { EndpointSpec } from '@jsplumb/common';
|
||||
|
||||
const createAddInputEndpointSpec = (
|
||||
connectionName: NodeConnectionType,
|
||||
color: string,
|
||||
): EndpointSpec => {
|
||||
const multiple = NODE_CONNECTION_TYPE_ALLOW_MULTIPLE.includes(connectionName);
|
||||
|
||||
return {
|
||||
type: 'N8nAddInput',
|
||||
options: {
|
||||
width: 24,
|
||||
height: 72,
|
||||
color,
|
||||
multiple,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const createDiamondOutputEndpointSpec = (): EndpointSpec => ({
|
||||
type: 'Rectangle',
|
||||
options: {
|
||||
height: 10,
|
||||
width: 10,
|
||||
cssClass: 'diamond-output-endpoint',
|
||||
},
|
||||
});
|
||||
|
||||
export const nodeBase = defineComponent({
|
||||
mixins: [deviceSupportHelpers],
|
||||
@@ -29,6 +67,12 @@ export const nodeBase = defineComponent({
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputs: [] as Array<ConnectionTypes | INodeInputConfiguration>,
|
||||
outputs: [] as Array<ConnectionTypes | INodeOutputConfiguration>,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useNodeTypesStore, useUIStore, useCanvasStore, useWorkflowsStore, useHistoryStore),
|
||||
data(): INodeUi | null {
|
||||
@@ -72,59 +116,151 @@ export const nodeBase = defineComponent({
|
||||
},
|
||||
__addInputEndpoints(node: INodeUi, nodeTypeData: INodeTypeDescription) {
|
||||
// Add Inputs
|
||||
let index;
|
||||
const indexData: {
|
||||
const rootTypeIndexData: {
|
||||
[key: string]: number;
|
||||
} = {};
|
||||
const typeIndexData: {
|
||||
[key: string]: number;
|
||||
} = {};
|
||||
|
||||
nodeTypeData.inputs.forEach((inputName: string, i: number) => {
|
||||
// Increment the index for inputs with current name
|
||||
if (indexData.hasOwnProperty(inputName)) {
|
||||
indexData[inputName]++;
|
||||
} else {
|
||||
indexData[inputName] = 0;
|
||||
const workflow = this.workflowsStore.getCurrentWorkflow();
|
||||
const inputs: Array<ConnectionTypes | INodeInputConfiguration> =
|
||||
NodeHelpers.getNodeInputs(workflow, this.data!, nodeTypeData) || [];
|
||||
this.inputs = inputs;
|
||||
|
||||
const sortedInputs = [...inputs];
|
||||
sortedInputs.sort((a, b) => {
|
||||
if (typeof a === 'string') {
|
||||
return 1;
|
||||
} else if (typeof b === 'string') {
|
||||
return -1;
|
||||
}
|
||||
index = indexData[inputName];
|
||||
|
||||
if (a.required && !b.required) {
|
||||
return -1;
|
||||
} else if (!a.required && b.required) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
sortedInputs.forEach((value, i) => {
|
||||
let inputConfiguration: INodeInputConfiguration;
|
||||
if (typeof value === 'string') {
|
||||
inputConfiguration = {
|
||||
type: value,
|
||||
};
|
||||
} else {
|
||||
inputConfiguration = value;
|
||||
}
|
||||
|
||||
const inputName: ConnectionTypes = inputConfiguration.type;
|
||||
|
||||
const rootCategoryInputName =
|
||||
inputName === NodeConnectionType.Main ? NodeConnectionType.Main : 'other';
|
||||
|
||||
// Increment the index for inputs with current name
|
||||
if (rootTypeIndexData.hasOwnProperty(rootCategoryInputName)) {
|
||||
rootTypeIndexData[rootCategoryInputName]++;
|
||||
} else {
|
||||
rootTypeIndexData[rootCategoryInputName] = 0;
|
||||
}
|
||||
|
||||
if (typeIndexData.hasOwnProperty(inputName)) {
|
||||
typeIndexData[inputName]++;
|
||||
} else {
|
||||
typeIndexData[inputName] = 0;
|
||||
}
|
||||
|
||||
const rootTypeIndex = rootTypeIndexData[rootCategoryInputName];
|
||||
const typeIndex = typeIndexData[inputName];
|
||||
|
||||
const inputsOfSameRootType = inputs.filter((inputData) => {
|
||||
const thisInputName: string = typeof inputData === 'string' ? inputData : inputData.type;
|
||||
return inputName === NodeConnectionType.Main
|
||||
? thisInputName === NodeConnectionType.Main
|
||||
: thisInputName !== NodeConnectionType.Main;
|
||||
});
|
||||
|
||||
const nonMainInputs = inputsOfSameRootType.filter((inputData) => {
|
||||
return inputData !== NodeConnectionType.Main;
|
||||
});
|
||||
const requiredNonMainInputs = nonMainInputs.filter((inputData) => {
|
||||
return typeof inputData !== 'string' && inputData.required;
|
||||
});
|
||||
const optionalNonMainInputs = nonMainInputs.filter((inputData) => {
|
||||
return typeof inputData !== 'string' && !inputData.required;
|
||||
});
|
||||
const spacerIndexes = this.getSpacerIndexes(
|
||||
requiredNonMainInputs.length,
|
||||
optionalNonMainInputs.length,
|
||||
);
|
||||
|
||||
// Get the position of the anchor depending on how many it has
|
||||
const anchorPosition =
|
||||
NodeViewUtils.ANCHOR_POSITIONS.input[nodeTypeData.inputs.length][index];
|
||||
const anchorPosition = NodeViewUtils.getAnchorPosition(
|
||||
inputName,
|
||||
'input',
|
||||
inputsOfSameRootType.length,
|
||||
spacerIndexes,
|
||||
)[rootTypeIndex];
|
||||
|
||||
const scope = NodeViewUtils.getEndpointScope(inputName as NodeConnectionType);
|
||||
|
||||
const newEndpointData: EndpointOptions = {
|
||||
uuid: NodeViewUtils.getInputEndpointUUID(this.nodeId, index),
|
||||
uuid: NodeViewUtils.getInputEndpointUUID(this.nodeId, inputName, typeIndex),
|
||||
anchor: anchorPosition,
|
||||
maxConnections: -1,
|
||||
// We potentially want to change that in the future to allow people to dynamically
|
||||
// activate and deactivate connected nodes
|
||||
maxConnections: inputConfiguration.maxConnections ?? -1,
|
||||
endpoint: 'Rectangle',
|
||||
paintStyle: NodeViewUtils.getInputEndpointStyle(nodeTypeData, '--color-foreground-xdark'),
|
||||
hoverPaintStyle: NodeViewUtils.getInputEndpointStyle(nodeTypeData, '--color-primary'),
|
||||
source: false,
|
||||
target: !this.isReadOnly && nodeTypeData.inputs.length > 1, // only enabled for nodes with multiple inputs.. otherwise attachment handled by connectionDrag event in NodeView,
|
||||
paintStyle: NodeViewUtils.getInputEndpointStyle(
|
||||
nodeTypeData,
|
||||
'--color-foreground-xdark',
|
||||
inputName,
|
||||
),
|
||||
hoverPaintStyle: NodeViewUtils.getInputEndpointStyle(
|
||||
nodeTypeData,
|
||||
'--color-primary',
|
||||
inputName,
|
||||
),
|
||||
scope: NodeViewUtils.getScope(scope),
|
||||
source: inputName !== NodeConnectionType.Main,
|
||||
target: !this.isReadOnly && inputs.length > 1, // only enabled for nodes with multiple inputs.. otherwise attachment handled by connectionDrag event in NodeView,
|
||||
parameters: {
|
||||
connection: 'target',
|
||||
nodeId: this.nodeId,
|
||||
type: inputName,
|
||||
index,
|
||||
index: typeIndex,
|
||||
},
|
||||
enabled: !this.isReadOnly, // enabled in default case to allow dragging
|
||||
cssClass: 'rect-input-endpoint',
|
||||
dragAllowedWhenFull: true,
|
||||
hoverClass: 'dropHover',
|
||||
hoverClass: 'rect-input-endpoint-hover',
|
||||
...this.__getInputConnectionStyle(inputName, nodeTypeData),
|
||||
};
|
||||
|
||||
const endpoint = this.instance?.addEndpoint(
|
||||
this.$refs[this.data.name] as Element,
|
||||
newEndpointData,
|
||||
);
|
||||
this.__addEndpointTestingData(endpoint, 'input', index);
|
||||
if (nodeTypeData.inputNames) {
|
||||
) as Endpoint;
|
||||
this.__addEndpointTestingData(endpoint, 'input', typeIndex);
|
||||
if (inputConfiguration.displayName || nodeTypeData.inputNames?.[i]) {
|
||||
// Apply input names if they got set
|
||||
endpoint.addOverlay(NodeViewUtils.getInputNameOverlay(nodeTypeData.inputNames[index]));
|
||||
endpoint.addOverlay(
|
||||
NodeViewUtils.getInputNameOverlay(
|
||||
inputConfiguration.displayName || nodeTypeData.inputNames[i],
|
||||
inputName,
|
||||
inputConfiguration.required,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (!Array.isArray(endpoint)) {
|
||||
endpoint.__meta = {
|
||||
nodeName: node.name,
|
||||
nodeId: this.nodeId,
|
||||
index: i,
|
||||
totalEndpoints: nodeTypeData.inputs.length,
|
||||
index: typeIndex,
|
||||
totalEndpoints: inputsOfSameRootType.length,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -134,71 +270,166 @@ export const nodeBase = defineComponent({
|
||||
// different to the regular one (have different ids). So that seems to make
|
||||
// problems when hiding the input-name.
|
||||
|
||||
// if (index === 0 && inputName === 'main') {
|
||||
// if (index === 0 && inputName === NodeConnectionType.Main) {
|
||||
// // Make the first main-input the default one to connect to when connection gets dropped on node
|
||||
// this.instance.makeTarget(this.nodeId, newEndpointData);
|
||||
// }
|
||||
});
|
||||
if (nodeTypeData.inputs.length === 0) {
|
||||
if (sortedInputs.length === 0) {
|
||||
this.instance.manage(this.$refs[this.data.name] as Element);
|
||||
}
|
||||
},
|
||||
getSpacerIndexes(
|
||||
leftGroupItemsCount: number,
|
||||
rightGroupItemsCount: number,
|
||||
insertSpacerBetweenGroups = NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS,
|
||||
minItemsCount = NODE_MIN_INPUT_ITEMS_COUNT,
|
||||
): number[] {
|
||||
const spacerIndexes = [];
|
||||
|
||||
if (leftGroupItemsCount > 0 && rightGroupItemsCount > 0) {
|
||||
if (insertSpacerBetweenGroups) {
|
||||
spacerIndexes.push(leftGroupItemsCount);
|
||||
} else if (leftGroupItemsCount + rightGroupItemsCount < minItemsCount) {
|
||||
for (
|
||||
let spacerIndex = leftGroupItemsCount;
|
||||
spacerIndex < minItemsCount - rightGroupItemsCount;
|
||||
spacerIndex++
|
||||
) {
|
||||
spacerIndexes.push(spacerIndex);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
leftGroupItemsCount > 0 &&
|
||||
leftGroupItemsCount < minItemsCount &&
|
||||
rightGroupItemsCount === 0
|
||||
) {
|
||||
for (
|
||||
let spacerIndex = 0;
|
||||
spacerIndex < minItemsCount - leftGroupItemsCount;
|
||||
spacerIndex++
|
||||
) {
|
||||
spacerIndexes.push(spacerIndex + leftGroupItemsCount);
|
||||
}
|
||||
} else if (
|
||||
leftGroupItemsCount === 0 &&
|
||||
rightGroupItemsCount > 0 &&
|
||||
rightGroupItemsCount < minItemsCount
|
||||
) {
|
||||
for (
|
||||
let spacerIndex = 0;
|
||||
spacerIndex < minItemsCount - rightGroupItemsCount;
|
||||
spacerIndex++
|
||||
) {
|
||||
spacerIndexes.push(spacerIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return spacerIndexes;
|
||||
},
|
||||
__addOutputEndpoints(node: INodeUi, nodeTypeData: INodeTypeDescription) {
|
||||
let index;
|
||||
const indexData: {
|
||||
const rootTypeIndexData: {
|
||||
[key: string]: number;
|
||||
} = {};
|
||||
const typeIndexData: {
|
||||
[key: string]: number;
|
||||
} = {};
|
||||
|
||||
nodeTypeData.outputs.forEach((inputName: string, i: number) => {
|
||||
// Increment the index for outputs with current name
|
||||
if (indexData.hasOwnProperty(inputName)) {
|
||||
indexData[inputName]++;
|
||||
const workflow = this.workflowsStore.getCurrentWorkflow();
|
||||
const outputs = NodeHelpers.getNodeOutputs(workflow, this.data, nodeTypeData) || [];
|
||||
this.outputs = outputs;
|
||||
|
||||
// TODO: There are still a lot of references of "main" in NodesView and
|
||||
// other locations. So assume there will be more problems
|
||||
|
||||
outputs.forEach((value, i) => {
|
||||
let outputConfiguration: INodeOutputConfiguration;
|
||||
if (typeof value === 'string') {
|
||||
outputConfiguration = {
|
||||
type: value,
|
||||
};
|
||||
} else {
|
||||
indexData[inputName] = 0;
|
||||
outputConfiguration = value;
|
||||
}
|
||||
index = indexData[inputName];
|
||||
|
||||
const outputName: ConnectionTypes = outputConfiguration.type;
|
||||
|
||||
const rootCategoryOutputName =
|
||||
outputName === NodeConnectionType.Main ? NodeConnectionType.Main : 'other';
|
||||
|
||||
// Increment the index for outputs with current name
|
||||
if (rootTypeIndexData.hasOwnProperty(rootCategoryOutputName)) {
|
||||
rootTypeIndexData[rootCategoryOutputName]++;
|
||||
} else {
|
||||
rootTypeIndexData[rootCategoryOutputName] = 0;
|
||||
}
|
||||
|
||||
if (typeIndexData.hasOwnProperty(outputName)) {
|
||||
typeIndexData[outputName]++;
|
||||
} else {
|
||||
typeIndexData[outputName] = 0;
|
||||
}
|
||||
|
||||
const rootTypeIndex = rootTypeIndexData[rootCategoryOutputName];
|
||||
const typeIndex = typeIndexData[outputName];
|
||||
|
||||
const outputsOfSameRootType = outputs.filter((outputData) => {
|
||||
const thisOutputName: string =
|
||||
typeof outputData === 'string' ? outputData : outputData.type;
|
||||
return outputName === NodeConnectionType.Main
|
||||
? thisOutputName === NodeConnectionType.Main
|
||||
: thisOutputName !== NodeConnectionType.Main;
|
||||
});
|
||||
|
||||
// Get the position of the anchor depending on how many it has
|
||||
const anchorPosition =
|
||||
NodeViewUtils.ANCHOR_POSITIONS.output[nodeTypeData.outputs.length][index];
|
||||
const anchorPosition = NodeViewUtils.getAnchorPosition(
|
||||
outputName,
|
||||
'output',
|
||||
outputsOfSameRootType.length,
|
||||
)[rootTypeIndex];
|
||||
|
||||
const scope = NodeViewUtils.getEndpointScope(outputName as NodeConnectionType);
|
||||
|
||||
const newEndpointData: EndpointOptions = {
|
||||
uuid: NodeViewUtils.getOutputEndpointUUID(this.nodeId, index),
|
||||
uuid: NodeViewUtils.getOutputEndpointUUID(this.nodeId, outputName, typeIndex),
|
||||
anchor: anchorPosition,
|
||||
maxConnections: -1,
|
||||
endpoint: {
|
||||
type: 'Dot',
|
||||
options: {
|
||||
radius: nodeTypeData && nodeTypeData.outputs.length > 2 ? 7 : 9,
|
||||
radius: nodeTypeData && outputsOfSameRootType.length > 2 ? 7 : 9,
|
||||
},
|
||||
},
|
||||
paintStyle: NodeViewUtils.getOutputEndpointStyle(
|
||||
nodeTypeData,
|
||||
'--color-foreground-xdark',
|
||||
),
|
||||
hoverPaintStyle: NodeViewUtils.getOutputEndpointStyle(nodeTypeData, '--color-primary'),
|
||||
scope,
|
||||
source: true,
|
||||
target: false,
|
||||
target: outputName !== NodeConnectionType.Main,
|
||||
enabled: !this.isReadOnly,
|
||||
parameters: {
|
||||
connection: 'source',
|
||||
nodeId: this.nodeId,
|
||||
type: inputName,
|
||||
index,
|
||||
type: outputName,
|
||||
index: typeIndex,
|
||||
},
|
||||
hoverClass: 'dot-output-endpoint-hover',
|
||||
connectionsDirected: true,
|
||||
cssClass: 'dot-output-endpoint',
|
||||
dragAllowedWhenFull: false,
|
||||
...this.__getOutputConnectionStyle(outputName, nodeTypeData),
|
||||
};
|
||||
|
||||
const endpoint = this.instance.addEndpoint(
|
||||
this.$refs[this.data.name] as Element,
|
||||
newEndpointData,
|
||||
);
|
||||
this.__addEndpointTestingData(endpoint, 'output', index);
|
||||
if (nodeTypeData.outputNames) {
|
||||
this.__addEndpointTestingData(endpoint, 'output', typeIndex);
|
||||
if (outputConfiguration.displayName || nodeTypeData.outputNames?.[i]) {
|
||||
// Apply output names if they got set
|
||||
const overlaySpec = NodeViewUtils.getOutputNameOverlay(nodeTypeData.outputNames[index]);
|
||||
const overlaySpec = NodeViewUtils.getOutputNameOverlay(
|
||||
outputConfiguration.displayName || nodeTypeData.outputNames[i],
|
||||
outputName,
|
||||
);
|
||||
endpoint.addOverlay(overlaySpec);
|
||||
}
|
||||
|
||||
@@ -206,14 +437,14 @@ export const nodeBase = defineComponent({
|
||||
endpoint.__meta = {
|
||||
nodeName: node.name,
|
||||
nodeId: this.nodeId,
|
||||
index: i,
|
||||
totalEndpoints: nodeTypeData.outputs.length,
|
||||
index: typeIndex,
|
||||
totalEndpoints: outputsOfSameRootType.length,
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.isReadOnly) {
|
||||
if (!this.isReadOnly && outputName === NodeConnectionType.Main) {
|
||||
const plusEndpointData: EndpointOptions = {
|
||||
uuid: NodeViewUtils.getOutputEndpointUUID(this.nodeId, index),
|
||||
uuid: NodeViewUtils.getOutputEndpointUUID(this.nodeId, outputName, typeIndex),
|
||||
anchor: anchorPosition,
|
||||
maxConnections: -1,
|
||||
endpoint: {
|
||||
@@ -221,8 +452,8 @@ export const nodeBase = defineComponent({
|
||||
options: {
|
||||
dimensions: 24,
|
||||
connectedEndpoint: endpoint,
|
||||
showOutputLabel: nodeTypeData.outputs.length === 1,
|
||||
size: nodeTypeData.outputs.length >= 3 ? 'small' : 'medium',
|
||||
showOutputLabel: outputs.length === 1,
|
||||
size: outputs.length >= 3 ? 'small' : 'medium',
|
||||
hoverMessage: this.$locale.baseText('nodeBase.clickToAddNodeOrDragToConnect'),
|
||||
},
|
||||
},
|
||||
@@ -236,9 +467,10 @@ export const nodeBase = defineComponent({
|
||||
outlineStroke: 'none',
|
||||
},
|
||||
parameters: {
|
||||
connection: 'source',
|
||||
nodeId: this.nodeId,
|
||||
type: inputName,
|
||||
index,
|
||||
type: outputName,
|
||||
index: typeIndex,
|
||||
},
|
||||
cssClass: 'plus-draggable-endpoint',
|
||||
dragAllowedWhenFull: false,
|
||||
@@ -247,14 +479,14 @@ export const nodeBase = defineComponent({
|
||||
this.$refs[this.data.name] as Element,
|
||||
plusEndpointData,
|
||||
);
|
||||
this.__addEndpointTestingData(plusEndpoint, 'plus', index);
|
||||
this.__addEndpointTestingData(plusEndpoint, 'plus', typeIndex);
|
||||
|
||||
if (!Array.isArray(plusEndpoint)) {
|
||||
plusEndpoint.__meta = {
|
||||
nodeName: node.name,
|
||||
nodeId: this.nodeId,
|
||||
index: i,
|
||||
totalEndpoints: nodeTypeData.outputs.length,
|
||||
index: typeIndex,
|
||||
totalEndpoints: outputsOfSameRootType.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -267,6 +499,74 @@ export const nodeBase = defineComponent({
|
||||
this.__addInputEndpoints(node, nodeTypeData);
|
||||
this.__addOutputEndpoints(node, nodeTypeData);
|
||||
},
|
||||
__getEndpointColor(connectionType: ConnectionTypes) {
|
||||
return `--node-type-${connectionType}-color`;
|
||||
},
|
||||
__getInputConnectionStyle(
|
||||
connectionType: ConnectionTypes,
|
||||
nodeTypeData: INodeTypeDescription,
|
||||
): EndpointOptions {
|
||||
if (connectionType === NodeConnectionType.Main) {
|
||||
return {
|
||||
paintStyle: NodeViewUtils.getInputEndpointStyle(
|
||||
nodeTypeData,
|
||||
this.__getEndpointColor(NodeConnectionType.Main),
|
||||
connectionType,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (!Object.values(NodeConnectionType).includes(connectionType as NodeConnectionType)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const createSupplementalConnectionType = (
|
||||
connectionName: ConnectionTypes,
|
||||
): EndpointOptions => ({
|
||||
endpoint: createAddInputEndpointSpec(
|
||||
connectionName as NodeConnectionType,
|
||||
this.__getEndpointColor(connectionName),
|
||||
),
|
||||
});
|
||||
|
||||
return createSupplementalConnectionType(connectionType);
|
||||
},
|
||||
__getOutputConnectionStyle(
|
||||
connectionType: ConnectionTypes,
|
||||
nodeTypeData: INodeTypeDescription,
|
||||
): EndpointOptions {
|
||||
const type = 'output';
|
||||
|
||||
const createSupplementalConnectionType = (
|
||||
connectionName: ConnectionTypes,
|
||||
): EndpointOptions => ({
|
||||
endpoint: createDiamondOutputEndpointSpec(),
|
||||
paintStyle: NodeViewUtils.getOutputEndpointStyle(
|
||||
nodeTypeData,
|
||||
this.__getEndpointColor(connectionName),
|
||||
),
|
||||
hoverPaintStyle: NodeViewUtils.getOutputEndpointStyle(
|
||||
nodeTypeData,
|
||||
this.__getEndpointColor(connectionName),
|
||||
),
|
||||
});
|
||||
|
||||
if (connectionType === NodeConnectionType.Main) {
|
||||
return {
|
||||
paintStyle: NodeViewUtils.getOutputEndpointStyle(
|
||||
nodeTypeData,
|
||||
this.__getEndpointColor(NodeConnectionType.Main),
|
||||
),
|
||||
cssClass: `dot-${type}-endpoint`,
|
||||
};
|
||||
}
|
||||
|
||||
if (!Object.values(NodeConnectionType).includes(connectionType as NodeConnectionType)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return createSupplementalConnectionType(connectionType);
|
||||
},
|
||||
touchEnd(e: MouseEvent) {
|
||||
if (this.isTouchDevice) {
|
||||
if (this.uiStore.isActionActive('dragActive')) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useHistoryStore } from '@/stores/history.store';
|
||||
import { PLACEHOLDER_FILLED_AT_EXECUTION_TIME, CUSTOM_API_CALL_KEY } from '@/constants';
|
||||
|
||||
import type {
|
||||
ConnectionTypes,
|
||||
IBinaryKeyData,
|
||||
ICredentialType,
|
||||
INodeCredentialDescription,
|
||||
@@ -18,14 +19,17 @@ import type {
|
||||
INode,
|
||||
INodePropertyOptions,
|
||||
IDataObject,
|
||||
Workflow,
|
||||
INodeInputConfiguration,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeHelpers } from 'n8n-workflow';
|
||||
import { NodeHelpers, ExpressionEvaluatorProxy, NodeConnectionType } from 'n8n-workflow';
|
||||
|
||||
import type {
|
||||
ICredentialsResponse,
|
||||
INodeUi,
|
||||
INodeUpdatePropertiesInformation,
|
||||
IUser,
|
||||
NodePanelType,
|
||||
} from '@/Interface';
|
||||
|
||||
import { get } from 'lodash-es';
|
||||
@@ -84,6 +88,24 @@ export const nodeHelpers = defineComponent({
|
||||
return NodeHelpers.displayParameterPath(nodeValues, parameter, path, node);
|
||||
},
|
||||
|
||||
// Updates all the issues on all the nodes
|
||||
refreshNodeIssues(): void {
|
||||
const nodes = this.workflowsStore.allNodes;
|
||||
let nodeType: INodeTypeDescription | null;
|
||||
let foundNodeIssues: INodeIssues | null;
|
||||
|
||||
nodes.forEach((node) => {
|
||||
if (node.disabled === true) {
|
||||
return;
|
||||
}
|
||||
nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||
foundNodeIssues = this.getNodeIssues(nodeType, node);
|
||||
if (foundNodeIssues !== null) {
|
||||
node.issues = foundNodeIssues;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Returns all the issues of the node
|
||||
getNodeIssues(
|
||||
nodeType: INodeTypeDescription | null,
|
||||
@@ -124,6 +146,14 @@ export const nodeHelpers = defineComponent({
|
||||
NodeHelpers.mergeIssues(nodeIssues, nodeCredentialIssues);
|
||||
}
|
||||
}
|
||||
|
||||
const workflow = this.workflowsStore.getCurrentWorkflow();
|
||||
const nodeInputIssues = this.getNodeInputIssues(workflow, node, nodeType);
|
||||
if (nodeIssues === null) {
|
||||
nodeIssues = nodeInputIssues;
|
||||
} else {
|
||||
NodeHelpers.mergeIssues(nodeIssues, nodeInputIssues);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.hasNodeExecutionIssues(node) && !ignoreIssues.includes('execution')) {
|
||||
@@ -168,6 +198,25 @@ export const nodeHelpers = defineComponent({
|
||||
};
|
||||
},
|
||||
|
||||
updateNodesInputIssues() {
|
||||
const nodes = this.workflowsStore.allNodes;
|
||||
const workflow = this.workflowsStore.getCurrentWorkflow();
|
||||
|
||||
for (const node of nodes) {
|
||||
const nodeType = this.nodeTypesStore.getNodeType(node.type, node.typeVersion);
|
||||
if (!nodeType) {
|
||||
return;
|
||||
}
|
||||
const nodeInputIssues = this.getNodeInputIssues(workflow, node, nodeType);
|
||||
|
||||
this.workflowsStore.setNodeIssue({
|
||||
node: node.name,
|
||||
type: 'input',
|
||||
value: nodeInputIssues?.input ? nodeInputIssues.input : null,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Updates the execution issues.
|
||||
updateNodesExecutionIssues() {
|
||||
const nodes = this.workflowsStore.allNodes;
|
||||
@@ -242,6 +291,45 @@ export const nodeHelpers = defineComponent({
|
||||
});
|
||||
},
|
||||
|
||||
// Returns all the input-issues of the node
|
||||
getNodeInputIssues(
|
||||
workflow: Workflow,
|
||||
node: INodeUi,
|
||||
nodeType?: INodeTypeDescription,
|
||||
): INodeIssues | null {
|
||||
const foundIssues: INodeIssueObjectProperty = {};
|
||||
|
||||
const workflowNode = workflow.getNode(node.name);
|
||||
let inputs: Array<ConnectionTypes | INodeInputConfiguration> = [];
|
||||
if (nodeType && workflowNode) {
|
||||
inputs = NodeHelpers.getNodeInputs(workflow, workflowNode, nodeType);
|
||||
}
|
||||
|
||||
inputs.forEach((input) => {
|
||||
if (typeof input === 'string' || input.required !== true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parentNodes = workflow.getParentNodes(node.name, input.type, 1);
|
||||
|
||||
if (parentNodes.length === 0) {
|
||||
foundIssues[input.type] = [
|
||||
this.$locale.baseText('nodeIssues.input.missing', {
|
||||
interpolate: { inputName: input.displayName || input.type },
|
||||
}),
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(foundIssues).length) {
|
||||
return {
|
||||
input: foundIssues,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
// Returns all the credential-issues of the node
|
||||
getNodeCredentialIssues(node: INodeUi, nodeType?: INodeTypeDescription): INodeIssues | null {
|
||||
if (node.disabled) {
|
||||
@@ -414,14 +502,20 @@ export const nodeHelpers = defineComponent({
|
||||
}
|
||||
},
|
||||
|
||||
getNodeInputData(node: INodeUi | null, runIndex = 0, outputIndex = 0): INodeExecutionData[] {
|
||||
getNodeInputData(
|
||||
node: INodeUi | null,
|
||||
runIndex = 0,
|
||||
outputIndex = 0,
|
||||
paneType: NodePanelType = 'output',
|
||||
connectionType: ConnectionTypes = NodeConnectionType.Main,
|
||||
): INodeExecutionData[] {
|
||||
if (node === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (this.workflowsStore.getWorkflowExecution === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const executionData = this.workflowsStore.getWorkflowExecution.data;
|
||||
if (!executionData?.resultData) {
|
||||
// unknown status
|
||||
@@ -429,31 +523,39 @@ export const nodeHelpers = defineComponent({
|
||||
}
|
||||
const runData = executionData.resultData.runData;
|
||||
|
||||
if (
|
||||
!runData?.[node.name]?.[runIndex].data ||
|
||||
runData[node.name][runIndex].data === undefined
|
||||
) {
|
||||
const taskData = get(runData, `[${node.name}][${runIndex}]`);
|
||||
if (!taskData) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.getMainInputData(runData[node.name][runIndex].data!, outputIndex);
|
||||
let data: ITaskDataConnections | undefined = taskData.data!;
|
||||
if (paneType === 'input' && taskData.inputOverride) {
|
||||
data = taskData.inputOverride!;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.getInputData(data, outputIndex, connectionType);
|
||||
},
|
||||
|
||||
// Returns the data of the main input
|
||||
getMainInputData(
|
||||
getInputData(
|
||||
connectionsData: ITaskDataConnections,
|
||||
outputIndex: number,
|
||||
connectionType: ConnectionTypes = NodeConnectionType.Main,
|
||||
): INodeExecutionData[] {
|
||||
if (
|
||||
!connectionsData ||
|
||||
!connectionsData.hasOwnProperty('main') ||
|
||||
connectionsData.main === undefined ||
|
||||
connectionsData.main.length < outputIndex ||
|
||||
connectionsData.main[outputIndex] === null
|
||||
!connectionsData.hasOwnProperty(connectionType) ||
|
||||
connectionsData[connectionType] === undefined ||
|
||||
connectionsData[connectionType].length < outputIndex ||
|
||||
connectionsData[connectionType][outputIndex] === null
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
return connectionsData.main[outputIndex] as INodeExecutionData[];
|
||||
return connectionsData[connectionType][outputIndex] as INodeExecutionData[];
|
||||
},
|
||||
|
||||
// Returns all the binary data of all the entries
|
||||
@@ -462,6 +564,7 @@ export const nodeHelpers = defineComponent({
|
||||
node: string | null,
|
||||
runIndex: number,
|
||||
outputIndex: number,
|
||||
connectionType: ConnectionTypes = NodeConnectionType.Main,
|
||||
): IBinaryKeyData[] {
|
||||
if (node === null) {
|
||||
return [];
|
||||
@@ -473,7 +576,11 @@ export const nodeHelpers = defineComponent({
|
||||
return [];
|
||||
}
|
||||
|
||||
const inputData = this.getMainInputData(runData[node][runIndex].data!, outputIndex);
|
||||
const inputData = this.getInputData(
|
||||
runData[node][runIndex].data!,
|
||||
outputIndex,
|
||||
connectionType,
|
||||
);
|
||||
|
||||
const returnData: IBinaryKeyData[] = [];
|
||||
for (let i = 0; i < inputData.length; i++) {
|
||||
@@ -509,6 +616,7 @@ export const nodeHelpers = defineComponent({
|
||||
this.workflowsStore.clearNodeExecutionData(node.name);
|
||||
this.updateNodeParameterIssues(node);
|
||||
this.updateNodeCredentialIssues(node);
|
||||
this.updateNodesInputIssues();
|
||||
if (trackHistory) {
|
||||
this.historyStore.pushCommandToUndo(
|
||||
new EnableNodeToggleCommand(node.name, oldState === true, node.disabled === true),
|
||||
@@ -531,6 +639,9 @@ export const nodeHelpers = defineComponent({
|
||||
|
||||
if (nodeType !== null && nodeType.subtitle !== undefined) {
|
||||
try {
|
||||
ExpressionEvaluatorProxy.setEvaluator(
|
||||
useSettingsStore().settings.expressions?.evaluator ?? 'tmpl',
|
||||
);
|
||||
return workflow.expression.getSimpleParameterValue(
|
||||
data as INode,
|
||||
nodeType.subtitle,
|
||||
|
||||
@@ -494,7 +494,7 @@ export const pushConnection = defineComponent({
|
||||
runDataExecuted.data.resultData.runData = this.workflowsStore.getWorkflowRunData;
|
||||
}
|
||||
|
||||
this.workflowsStore.executingNode = null;
|
||||
this.workflowsStore.executingNode.length = 0;
|
||||
this.workflowsStore.setWorkflowExecutionData(runDataExecuted as IExecutionResponse);
|
||||
this.uiStore.removeActiveAction('workflowRunning');
|
||||
|
||||
@@ -543,10 +543,11 @@ export const pushConnection = defineComponent({
|
||||
// A node finished to execute. Add its data
|
||||
const pushData = receivedData.data;
|
||||
this.workflowsStore.addNodeExecutionData(pushData);
|
||||
this.workflowsStore.removeExecutingNode(pushData.nodeName);
|
||||
} else if (receivedData.type === 'nodeExecuteBefore') {
|
||||
// A node started to be executed. Set it as executing.
|
||||
const pushData = receivedData.data;
|
||||
this.workflowsStore.executingNode = pushData.nodeName;
|
||||
this.workflowsStore.addExecutingNode(pushData.nodeName);
|
||||
} else if (receivedData.type === 'testWebhookDeleted') {
|
||||
// A test-webhook was deleted
|
||||
const pushData = receivedData.data;
|
||||
|
||||
@@ -30,7 +30,7 @@ import type {
|
||||
INodeProperties,
|
||||
IWorkflowSettings,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeHelpers } from 'n8n-workflow';
|
||||
import { NodeConnectionType, ExpressionEvaluatorProxy, NodeHelpers } from 'n8n-workflow';
|
||||
|
||||
import type {
|
||||
INodeTypesMaxCount,
|
||||
@@ -62,9 +62,45 @@ import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { useWorkflowsEEStore } from '@/stores/workflows.ee.store';
|
||||
import { useEnvironmentsStore } from '@/stores/environments.ee.store';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { getWorkflowPermissions } from '@/permissions';
|
||||
import type { IPermissions } from '@/permissions';
|
||||
|
||||
export function getParentMainInputNode(workflow: Workflow, node: INode): INode {
|
||||
const nodeType = useNodeTypesStore().getNodeType(node.type);
|
||||
if (nodeType) {
|
||||
const outputs = NodeHelpers.getNodeOutputs(workflow, node, nodeType);
|
||||
|
||||
if (!!outputs.find((output) => output !== NodeConnectionType.Main)) {
|
||||
// Get the first node which is connected to a non-main output
|
||||
const nonMainNodesConnected = outputs?.reduce((acc, outputName) => {
|
||||
const parentNodes = workflow.getChildNodes(node.name, outputName);
|
||||
if (parentNodes.length > 0) {
|
||||
acc.push(...parentNodes);
|
||||
}
|
||||
return acc;
|
||||
}, [] as string[]);
|
||||
|
||||
if (nonMainNodesConnected.length) {
|
||||
const returnNode = workflow.getNode(nonMainNodesConnected[0]);
|
||||
if (returnNode === null) {
|
||||
// This should theoretically never happen as the node is connected
|
||||
// but who knows and it makes TS happy
|
||||
throw new Error(
|
||||
`The node "${nonMainNodesConnected[0]}" which is a connection of "${node.name}" could not be found!`,
|
||||
);
|
||||
}
|
||||
|
||||
// The chain of non-main nodes is potentially not finished yet so
|
||||
// keep on going
|
||||
return getParentMainInputNode(workflow, returnNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
export function resolveParameter(
|
||||
parameter: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
||||
opts: {
|
||||
@@ -77,10 +113,16 @@ export function resolveParameter(
|
||||
): IDataObject | null {
|
||||
let itemIndex = opts?.targetItem?.itemIndex || 0;
|
||||
|
||||
const inputName = 'main';
|
||||
const activeNode = useNDVStore().activeNode;
|
||||
const inputName = NodeConnectionType.Main;
|
||||
let activeNode = useNDVStore().activeNode;
|
||||
|
||||
const workflow = getCurrentWorkflow();
|
||||
|
||||
// Should actually just do that for incoming data and not things like parameters
|
||||
if (activeNode) {
|
||||
activeNode = getParentMainInputNode(workflow, activeNode);
|
||||
}
|
||||
|
||||
const workflowRunData = useWorkflowsStore().getWorkflowRunData;
|
||||
let parentNode = workflow.getParentNodes(activeNode!.name, inputName, 1);
|
||||
const executionData = useWorkflowsStore().getWorkflowExecution;
|
||||
@@ -162,6 +204,10 @@ export function resolveParameter(
|
||||
}
|
||||
const _executeData = executeData(parentNode, activeNode!.name, inputName, runIndexCurrent);
|
||||
|
||||
ExpressionEvaluatorProxy.setEvaluator(
|
||||
useSettingsStore().settings.expressions?.evaluator ?? 'tmpl',
|
||||
);
|
||||
|
||||
return workflow.expression.getParameterValue(
|
||||
parameter,
|
||||
runExecutionData,
|
||||
@@ -222,6 +268,34 @@ function getCurrentWorkflow(copyData?: boolean): Workflow {
|
||||
return useWorkflowsStore().getCurrentWorkflow(copyData);
|
||||
}
|
||||
|
||||
function getConnectedNodes(
|
||||
direction: 'upstream' | 'downstream',
|
||||
workflow: Workflow,
|
||||
nodeName: string,
|
||||
): string[] {
|
||||
let checkNodes: string[];
|
||||
if (direction === 'downstream') {
|
||||
checkNodes = workflow.getChildNodes(nodeName);
|
||||
} else if (direction === 'upstream') {
|
||||
checkNodes = workflow.getParentNodes(nodeName);
|
||||
} else {
|
||||
throw new Error(`The direction "${direction}" is not supported!`);
|
||||
}
|
||||
|
||||
// Find also all nodes which are connected to the child nodes via a non-main input
|
||||
let connectedNodes: string[] = [];
|
||||
checkNodes.forEach((checkNode) => {
|
||||
connectedNodes = [
|
||||
...connectedNodes,
|
||||
checkNode,
|
||||
...workflow.getParentNodes(checkNode, 'ALL_NON_MAIN'),
|
||||
];
|
||||
});
|
||||
|
||||
// Remove duplicates
|
||||
return [...new Set(connectedNodes)];
|
||||
}
|
||||
|
||||
function getNodes(): INodeUi[] {
|
||||
return useWorkflowsStore().getNodes();
|
||||
}
|
||||
@@ -356,11 +430,33 @@ export function executeData(
|
||||
[inputName]: workflowRunData[currentNode][runIndex].source,
|
||||
};
|
||||
} else {
|
||||
const workflow = getCurrentWorkflow();
|
||||
|
||||
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) {
|
||||
for (const connection of mainConnections) {
|
||||
if (
|
||||
connection.type === NodeConnectionType.Main &&
|
||||
connection.node === parentNodeName
|
||||
) {
|
||||
previousNodeOutput = connection.index;
|
||||
break mainConnections;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The current node did not get executed in UI yet so build data manually
|
||||
executeData.source = {
|
||||
[inputName]: [
|
||||
{
|
||||
previousNode: parentNodeName,
|
||||
previousNodeOutput,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -399,7 +495,9 @@ export const workflowHelpers = defineComponent({
|
||||
resolveParameter,
|
||||
resolveRequiredParameters,
|
||||
getCurrentWorkflow,
|
||||
getConnectedNodes,
|
||||
getNodes,
|
||||
getParentMainInputNode,
|
||||
getWorkflow,
|
||||
getNodeTypes,
|
||||
connectionInputData,
|
||||
|
||||
@@ -2,8 +2,8 @@ import { defineComponent } from 'vue';
|
||||
import { mapStores } from 'pinia';
|
||||
import type { IExecutionPushResponse, IExecutionResponse, IStartRunData } from '@/Interface';
|
||||
|
||||
import type { IRunData, IRunExecutionData, IWorkflowBase } from 'n8n-workflow';
|
||||
import { NodeHelpers, TelemetryHelpers } from 'n8n-workflow';
|
||||
import type { IRunData, IRunExecutionData, ITaskData, IWorkflowBase } from 'n8n-workflow';
|
||||
import { NodeHelpers, NodeConnectionType, TelemetryHelpers } from 'n8n-workflow';
|
||||
|
||||
import { externalHooks } from '@/mixins/externalHooks';
|
||||
import { workflowHelpers } from '@/mixins/workflowHelpers';
|
||||
@@ -28,7 +28,7 @@ export const workflowRun = defineComponent({
|
||||
methods: {
|
||||
// Starts to executes a workflow on server.
|
||||
async runWorkflowApi(runData: IStartRunData): Promise<IExecutionPushResponse> {
|
||||
if (this.rootStore.pushConnectionActive === false) {
|
||||
if (!this.rootStore.pushConnectionActive) {
|
||||
// Do not start if the connection to server is not active
|
||||
// because then it can not receive the data as it executes.
|
||||
throw new Error(this.$locale.baseText('workflowRun.noActiveConnectionToTheServer'));
|
||||
@@ -57,9 +57,12 @@ export const workflowRun = defineComponent({
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
async runWorkflow(
|
||||
nodeName?: string,
|
||||
source?: string,
|
||||
options:
|
||||
| { destinationNode: string; source?: string }
|
||||
| { triggerNode: string; nodeData: ITaskData; source?: string }
|
||||
| { source?: string },
|
||||
): Promise<IExecutionPushResponse | undefined> {
|
||||
const workflow = this.getCurrentWorkflow();
|
||||
|
||||
@@ -74,9 +77,9 @@ export const workflowRun = defineComponent({
|
||||
try {
|
||||
// Check first if the workflow has any issues before execute it
|
||||
const issuesExist = this.workflowsStore.nodesIssuesExist;
|
||||
if (issuesExist === true) {
|
||||
if (issuesExist) {
|
||||
// If issues exist get all of the issues of all nodes
|
||||
const workflowIssues = this.checkReadyForExecution(workflow, nodeName);
|
||||
const workflowIssues = this.checkReadyForExecution(workflow, options.destinationNode);
|
||||
if (workflowIssues !== null) {
|
||||
const errorMessages = [];
|
||||
let nodeIssues: string[];
|
||||
@@ -115,13 +118,17 @@ export const workflowRun = defineComponent({
|
||||
duration: 0,
|
||||
});
|
||||
this.titleSet(workflow.name as string, 'ERROR');
|
||||
void this.$externalHooks().run('workflowRun.runError', { errorMessages, nodeName });
|
||||
void this.$externalHooks().run('workflowRun.runError', {
|
||||
errorMessages,
|
||||
nodeName: options.destinationNode,
|
||||
});
|
||||
|
||||
await this.getWorkflowDataToSave().then((workflowData) => {
|
||||
this.$telemetry.track('Workflow execution preflight failed', {
|
||||
workflow_id: workflow.id,
|
||||
workflow_name: workflow.name,
|
||||
execution_type: nodeName ? 'node' : 'workflow',
|
||||
execution_type:
|
||||
options.destinationNode || options.triggerNode ? 'node' : 'workflow',
|
||||
node_graph_string: JSON.stringify(
|
||||
TelemetryHelpers.generateNodesGraph(
|
||||
workflowData as IWorkflowBase,
|
||||
@@ -138,8 +145,12 @@ export const workflowRun = defineComponent({
|
||||
|
||||
// Get the direct parents of the node
|
||||
let directParentNodes: string[] = [];
|
||||
if (nodeName !== undefined) {
|
||||
directParentNodes = workflow.getParentNodes(nodeName, 'main', 1);
|
||||
if (options.destinationNode !== undefined) {
|
||||
directParentNodes = workflow.getParentNodes(
|
||||
options.destinationNode,
|
||||
NodeConnectionType.Main,
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
const runData = this.workflowsStore.getWorkflowRunData;
|
||||
@@ -155,7 +166,7 @@ export const workflowRun = defineComponent({
|
||||
for (const directParentNode of directParentNodes) {
|
||||
// Go over the parents of that node so that we can get a start
|
||||
// node for each of the branches
|
||||
const parentNodes = workflow.getParentNodes(directParentNode, 'main');
|
||||
const parentNodes = workflow.getParentNodes(directParentNode, NodeConnectionType.Main);
|
||||
|
||||
// Add also the enabled direct parent to be checked
|
||||
if (workflow.nodes[directParentNode].disabled) continue;
|
||||
@@ -181,8 +192,22 @@ export const workflowRun = defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
if (startNodes.length === 0 && nodeName !== undefined) {
|
||||
startNodes.push(nodeName);
|
||||
let executedNode: string | undefined;
|
||||
if (
|
||||
startNodes.length === 0 &&
|
||||
'destinationNode' in options &&
|
||||
options.destinationNode !== undefined
|
||||
) {
|
||||
executedNode = options.destinationNode;
|
||||
startNodes.push(options.destinationNode);
|
||||
} else if ('triggerNode' in options && 'nodeData' in options) {
|
||||
startNodes.push(
|
||||
...workflow.getChildNodes(options.triggerNode, NodeConnectionType.Main, 1),
|
||||
);
|
||||
newRunData = {
|
||||
[options.triggerNode]: [options.nodeData],
|
||||
};
|
||||
executedNode = options.triggerNode;
|
||||
}
|
||||
|
||||
if (this.workflowsStore.isNewWorkflow) {
|
||||
@@ -197,8 +222,8 @@ export const workflowRun = defineComponent({
|
||||
pinData: workflowData.pinData,
|
||||
startNodes,
|
||||
};
|
||||
if (nodeName) {
|
||||
startRunData.destinationNode = nodeName;
|
||||
if ('destinationNode' in options) {
|
||||
startRunData.destinationNode = options.destinationNode;
|
||||
}
|
||||
|
||||
// Init the execution data to represent the start of the execution
|
||||
@@ -211,7 +236,7 @@ export const workflowRun = defineComponent({
|
||||
startedAt: new Date(),
|
||||
stoppedAt: undefined,
|
||||
workflowId: workflow.id,
|
||||
executedNode: nodeName,
|
||||
executedNode,
|
||||
data: {
|
||||
resultData: {
|
||||
runData: newRunData || {},
|
||||
@@ -234,7 +259,10 @@ export const workflowRun = defineComponent({
|
||||
|
||||
const runWorkflowApiResponse = await this.runWorkflowApi(startRunData);
|
||||
|
||||
await this.$externalHooks().run('workflowRun.runWorkflow', { nodeName, source });
|
||||
await this.$externalHooks().run('workflowRun.runWorkflow', {
|
||||
nodeName: options.destinationNode,
|
||||
source: options.source,
|
||||
});
|
||||
|
||||
return runWorkflowApiResponse;
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user