mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(core): Add optional Error-Output (#7460)
Add an additional optional error output to which all items get sent that could not be processed.  Github issue / Community forum post (link here to close automatically): https://community.n8n.io/t/error-connector-for-nodes/3094 https://community.n8n.io/t/error-handling-at-node-level-detect-node-execution-status/26791 --------- Co-authored-by: OlegIvaniv <me@olegivaniv.com>
This commit is contained in:
parent
442c73e63b
commit
655efeaf66
|
@ -34,9 +34,9 @@ properties:
|
||||||
type: number
|
type: number
|
||||||
waitBetweenTries:
|
waitBetweenTries:
|
||||||
type: number
|
type: number
|
||||||
continueOnFail:
|
onError:
|
||||||
type: boolean
|
type: string
|
||||||
example: false
|
example: 'stopWorkflow'
|
||||||
position:
|
position:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
|
|
|
@ -2289,9 +2289,15 @@ export function getNodeParameter(
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export function continueOnFail(node: INode): boolean {
|
export function continueOnFail(node: INode): boolean {
|
||||||
|
const onError = get(node, 'onError', undefined);
|
||||||
|
|
||||||
|
if (onError === undefined) {
|
||||||
return get(node, 'continueOnFail', false);
|
return get(node, 'continueOnFail', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ['continueRegularOutput', 'continueErrorOutput'].includes(onError);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the webhook URL of the webhook with the given name
|
* Returns the webhook URL of the webhook with the given name
|
||||||
*
|
*
|
||||||
|
|
|
@ -9,6 +9,7 @@ import PCancelable from 'p-cancelable';
|
||||||
import type {
|
import type {
|
||||||
ExecutionError,
|
ExecutionError,
|
||||||
ExecutionStatus,
|
ExecutionStatus,
|
||||||
|
GenericValue,
|
||||||
IConnection,
|
IConnection,
|
||||||
IDataObject,
|
IDataObject,
|
||||||
IExecuteData,
|
IExecuteData,
|
||||||
|
@ -36,6 +37,8 @@ import {
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
IWorkflowExecuteAdditionalData,
|
IWorkflowExecuteAdditionalData,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
|
NodeHelpers,
|
||||||
|
NodeConnectionType,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import * as NodeExecuteFunctions from './NodeExecuteFunctions';
|
import * as NodeExecuteFunctions from './NodeExecuteFunctions';
|
||||||
|
@ -1041,6 +1044,7 @@ export class WorkflowExecute {
|
||||||
node: executionNode.name,
|
node: executionNode.name,
|
||||||
workflowId: workflow.id,
|
workflowId: workflow.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const runNodeData = await workflow.runNode(
|
const runNodeData = await workflow.runNode(
|
||||||
executionData,
|
executionData,
|
||||||
this.runExecutionData,
|
this.runExecutionData,
|
||||||
|
@ -1051,6 +1055,112 @@ export class WorkflowExecute {
|
||||||
);
|
);
|
||||||
nodeSuccessData = runNodeData.data;
|
nodeSuccessData = runNodeData.data;
|
||||||
|
|
||||||
|
if (nodeSuccessData && executionData.node.onError === 'continueErrorOutput') {
|
||||||
|
// If errorOutput is activated check all the output items for error data.
|
||||||
|
// If any is found, route them to the last output as that will be the
|
||||||
|
// error output.
|
||||||
|
|
||||||
|
const nodeType = workflow.nodeTypes.getByNameAndVersion(
|
||||||
|
executionData.node.type,
|
||||||
|
executionData.node.typeVersion,
|
||||||
|
);
|
||||||
|
const outputs = NodeHelpers.getNodeOutputs(
|
||||||
|
workflow,
|
||||||
|
executionData.node,
|
||||||
|
nodeType.description,
|
||||||
|
);
|
||||||
|
const outputTypes = NodeHelpers.getConnectionTypes(outputs);
|
||||||
|
const mainOutputTypes = outputTypes.filter(
|
||||||
|
(output) => output === NodeConnectionType.Main,
|
||||||
|
);
|
||||||
|
|
||||||
|
const errorItems: INodeExecutionData[] = [];
|
||||||
|
const successItems: INodeExecutionData[] = [];
|
||||||
|
|
||||||
|
// Create a WorkflowDataProxy instance that we can get the data of the
|
||||||
|
// item which did error
|
||||||
|
const executeFunctions = NodeExecuteFunctions.getExecuteFunctions(
|
||||||
|
workflow,
|
||||||
|
this.runExecutionData,
|
||||||
|
runIndex,
|
||||||
|
[],
|
||||||
|
executionData.data,
|
||||||
|
executionData.node,
|
||||||
|
this.additionalData,
|
||||||
|
executionData,
|
||||||
|
this.mode,
|
||||||
|
);
|
||||||
|
const dataProxy = executeFunctions.getWorkflowDataProxy(0);
|
||||||
|
|
||||||
|
// Loop over all outputs except the error output as it would not contain data by default
|
||||||
|
for (
|
||||||
|
let outputIndex = 0;
|
||||||
|
outputIndex < mainOutputTypes.length - 1;
|
||||||
|
outputIndex++
|
||||||
|
) {
|
||||||
|
successItems.length = 0;
|
||||||
|
const items = nodeSuccessData.length ? nodeSuccessData[0] : [];
|
||||||
|
|
||||||
|
while (items.length) {
|
||||||
|
const item = items.pop();
|
||||||
|
if (item === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let errorData: GenericValue | undefined;
|
||||||
|
if (item.error) {
|
||||||
|
errorData = item.error;
|
||||||
|
item.error = undefined;
|
||||||
|
} else if (item.json.error && Object.keys(item.json).length === 1) {
|
||||||
|
errorData = item.json.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorData) {
|
||||||
|
const pairedItemData =
|
||||||
|
item.pairedItem && typeof item.pairedItem === 'object'
|
||||||
|
? Array.isArray(item.pairedItem)
|
||||||
|
? item.pairedItem[0]
|
||||||
|
: item.pairedItem
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (executionData!.source === null || pairedItemData === undefined) {
|
||||||
|
// Source data is missing for some reason so we can not figure out the item
|
||||||
|
errorItems.push(item);
|
||||||
|
} else {
|
||||||
|
const pairedItemInputIndex = pairedItemData.input || 0;
|
||||||
|
|
||||||
|
const sourceData =
|
||||||
|
executionData!.source[NodeConnectionType.Main][pairedItemInputIndex];
|
||||||
|
|
||||||
|
const constPairedItem = dataProxy.$getPairedItem(
|
||||||
|
sourceData!.previousNode,
|
||||||
|
sourceData,
|
||||||
|
pairedItemData,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (constPairedItem === null) {
|
||||||
|
errorItems.push(item);
|
||||||
|
} else {
|
||||||
|
errorItems.push({
|
||||||
|
...item,
|
||||||
|
json: {
|
||||||
|
...constPairedItem.json,
|
||||||
|
...item.json,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
successItems.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeSuccessData[outputIndex] = successItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeSuccessData[mainOutputTypes.length - 1] = errorItems;
|
||||||
|
}
|
||||||
|
|
||||||
if (runNodeData.closeFunction) {
|
if (runNodeData.closeFunction) {
|
||||||
// Explanation why we do this can be found in n8n-workflow/Workflow.ts -> runNode
|
// Explanation why we do this can be found in n8n-workflow/Workflow.ts -> runNode
|
||||||
|
|
||||||
|
@ -1180,7 +1290,12 @@ export class WorkflowExecute {
|
||||||
taskData.error = executionError;
|
taskData.error = executionError;
|
||||||
taskData.executionStatus = 'error';
|
taskData.executionStatus = 'error';
|
||||||
|
|
||||||
if (executionData.node.continueOnFail === true) {
|
if (
|
||||||
|
executionData.node.continueOnFail === true ||
|
||||||
|
['continueRegularOutput', 'continueErrorOutput'].includes(
|
||||||
|
executionData.node.onError || '',
|
||||||
|
)
|
||||||
|
) {
|
||||||
// Workflow should continue running even if node errors
|
// Workflow should continue running even if node errors
|
||||||
if (executionData.data.hasOwnProperty('main') && executionData.data.main.length > 0) {
|
if (executionData.data.hasOwnProperty('main') && executionData.data.main.length > 0) {
|
||||||
// Simply get the input data of the node if it has any and pass it through
|
// Simply get the input data of the node if it has any and pass it through
|
||||||
|
|
734
packages/core/test/workflows/error_outputs.json
Normal file
734
packages/core/test/workflows/error_outputs.json
Normal file
|
@ -0,0 +1,734 @@
|
||||||
|
{
|
||||||
|
"name": "Error Output - Test Workflow",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "c41b46f0-3e76-4655-b5ea-4d15af58c138",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [-680, 460]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"fields": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"name": "originalName",
|
||||||
|
"stringValue": "={{ $('Mock Data').item.json.name }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "247f4118-d80f-49ab-8d9a-0cdbbb9271df",
|
||||||
|
"name": "Success",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 3.2,
|
||||||
|
"position": [200, 860]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"fields": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"name": "originalName",
|
||||||
|
"stringValue": "={{ $('Mock Data').item.json.name }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "311e3650-d89c-405a-9c8d-c238f48a8a5a",
|
||||||
|
"name": "Error",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 3.2,
|
||||||
|
"position": [200, 1040]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "return [\n {\n \"id\": \"23423532\",\n \"name\": \"Jay Gatsby\",\n \"email\": \"gatsby@west-egg.com\",\n \"notes\": \"Keeps asking about a green light??\",\n \"country\": \"US\",\n \"created\": \"1925-04-10\"\n },\n {\n \"id\": \"23423533\",\n \"name\": \"José Arcadio Buendía\",\n \"email\": \"jab@macondo.co\",\n \"notes\": \"Lots of people named after him. Very confusing\",\n \"country\": \"CO\",\n \"created\": \"1967-05-05\"\n },\n {\n \"id\": \"23423534\",\n \"name\": \"Max Sendak\",\n \"email\": \"info@in-and-out-of-weeks.org\",\n \"notes\": \"Keeps rolling his terrible eyes\",\n \"country\": \"US\",\n \"created\": \"1963-04-09\"\n },\n {\n \"id\": \"23423535\",\n \"name\": \"Zaphod Beeblebrox\",\n \"email\": \"captain@heartofgold.com\",\n \"notes\": \"Felt like I was talking to more than one person\",\n \"country\": null,\n \"created\": \"1979-10-12\"\n },\n {\n \"id\": \"23423536\",\n \"name\": \"Edmund Pevensie\",\n \"email\": \"edmund@narnia.gov\",\n \"notes\": \"Passionate sailor\",\n \"country\": \"UK\",\n \"created\": \"1950-10-16\"\n }\n]"
|
||||||
|
},
|
||||||
|
"id": "179d4fe7-1ae7-4957-a77d-12c3ca6d141b",
|
||||||
|
"name": "Mock Data",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [-460, 460]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"content": "## On Error: Continue (using error output)",
|
||||||
|
"height": 414,
|
||||||
|
"width": 564
|
||||||
|
},
|
||||||
|
"id": "1ec2a8b6-54e2-4319-90b3-30b387855b36",
|
||||||
|
"name": "Sticky Note",
|
||||||
|
"type": "n8n-nodes-base.stickyNote",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [-160, 780]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"content": "## Continue On Fail (deprecated)",
|
||||||
|
"height": 279,
|
||||||
|
"width": 564
|
||||||
|
},
|
||||||
|
"id": "49a2b7d9-8bd1-4cdf-9649-2d93668b0f8f",
|
||||||
|
"name": "Sticky Note1",
|
||||||
|
"type": "n8n-nodes-base.stickyNote",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [-160, 140]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"fields": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"name": "originalName",
|
||||||
|
"stringValue": "={{ $('Mock Data').item.json.name }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "9852f1d9-95b4-4ef7-bb18-8f0bab81a0bc",
|
||||||
|
"name": "Combined",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 3.2,
|
||||||
|
"position": [180, 240]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"mode": "runOnceForEachItem",
|
||||||
|
"jsCode": "// Add a new field called 'myNewField' to the JSON of the item\n$input.item.json.myNewField = 1;\n\nif ($input.item.json.country === 'US') {\n throw new Error('This is an error');\n}\n\nreturn $input.item;"
|
||||||
|
},
|
||||||
|
"id": "40d4dba3-3db7-4eb5-aa27-e76f955a5e09",
|
||||||
|
"name": "Throw Error",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [-140, 960],
|
||||||
|
"errorOutput": true,
|
||||||
|
"onError": "continueErrorOutput"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"content": "## On Error: Continue",
|
||||||
|
"height": 279,
|
||||||
|
"width": 564
|
||||||
|
},
|
||||||
|
"id": "8eb3dd54-c1dd-4167-abfa-c06d044c63f3",
|
||||||
|
"name": "Sticky Note2",
|
||||||
|
"type": "n8n-nodes-base.stickyNote",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [-160, 460]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"mode": "runOnceForEachItem",
|
||||||
|
"jsCode": "// Add a new field called 'myNewField' to the JSON of the item\n$input.item.json.myNewField = 1;\n\nif ($input.item.json.country === 'US') {\n throw new Error('This is an error');\n}\n\nreturn $input.item;"
|
||||||
|
},
|
||||||
|
"id": "19a3d6ac-e610-4296-9b7a-9ed19d242bdb",
|
||||||
|
"name": "Throw Error2",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [-120, 560],
|
||||||
|
"onError": "continueRegularOutput"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"fields": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"name": "originalName",
|
||||||
|
"stringValue": "={{ $('Mock Data').item.json.name }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "5f803fdc-7d88-4c12-8886-6092cfbc03c6",
|
||||||
|
"name": "Combined1",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 3.2,
|
||||||
|
"position": [180, 560]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"mode": "runOnceForEachItem",
|
||||||
|
"jsCode": "// Add a new field called 'myNewField' to the JSON of the item\n$input.item.json.myNewField = 1;\n\nif ($input.item.json.country === 'US') {\n throw new Error('This is an error');\n}\n\nreturn $input.item;"
|
||||||
|
},
|
||||||
|
"id": "c2696c1f-1abd-4549-9ad9-e62017dc14b8",
|
||||||
|
"name": "Throw Error1",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [-120, 240],
|
||||||
|
"continueOnFail": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"fields": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"name": "originalName",
|
||||||
|
"stringValue": "={{ $('Mock Data').item.json.name }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "01740d7e-e572-408a-9fae-729068803113",
|
||||||
|
"name": "Success1",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 3.2,
|
||||||
|
"position": [200, 1320]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"content": "## On Error: Continue (using error output) + Make sure error data gets removed",
|
||||||
|
"height": 509.71047006830065,
|
||||||
|
"width": 1183.725293692246
|
||||||
|
},
|
||||||
|
"id": "ed409181-4847-4d65-af45-f45078a6343e",
|
||||||
|
"name": "Sticky Note3",
|
||||||
|
"type": "n8n-nodes-base.stickyNote",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [-160, 1240]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"mode": "runOnceForEachItem",
|
||||||
|
"jsCode": "// Add a new field called 'myNewField' to the JSON of the item\n$input.item.json.myNewField = 1;\n\nif ($input.item.json.country === 'US') {\n throw new Error('This is an error');\n}\n\nreturn $input.item;"
|
||||||
|
},
|
||||||
|
"id": "93d03f38-b928-4b4b-832a-3f1a5deebb2d",
|
||||||
|
"name": "Throw Error3",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [-140, 1420],
|
||||||
|
"errorOutput": true,
|
||||||
|
"onError": "continueErrorOutput"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "c92a6ce5-41ea-4fb9-a07b-c4e98f905b12",
|
||||||
|
"name": "Edit Fields",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 3.2,
|
||||||
|
"position": [420, 1500],
|
||||||
|
"onError": "continueErrorOutput"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"fields": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"name": "originalName",
|
||||||
|
"stringValue": "={{ $('Mock Data').item.json.name }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "ab838cc1-0987-4b41-bdc5-fe17f38e0691",
|
||||||
|
"name": "Success2",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 3.2,
|
||||||
|
"position": [700, 1360]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"fields": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"name": "originalName",
|
||||||
|
"stringValue": "={{ $('Mock Data').item.json.name }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "22e04172-19b9-4735-9dd0-a3e2fa3bf000",
|
||||||
|
"name": "Error2",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 3.2,
|
||||||
|
"position": [700, 1580]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"fields": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"name": "originalName",
|
||||||
|
"stringValue": "={{ $('Mock Data').item.json.name }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "69e7257a-1ba8-46ba-9394-d38d65b2e567",
|
||||||
|
"name": "Error1",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"typeVersion": 3.2,
|
||||||
|
"position": [200, 1500]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"Error": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "23423534",
|
||||||
|
"name": "Max Sendak",
|
||||||
|
"email": "info@in-and-out-of-weeks.org",
|
||||||
|
"notes": "Keeps rolling his terrible eyes",
|
||||||
|
"country": "US",
|
||||||
|
"created": "1963-04-09",
|
||||||
|
"error": "This is an error [line 5, for item 2]",
|
||||||
|
"originalName": "Max Sendak"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "23423532",
|
||||||
|
"name": "Jay Gatsby",
|
||||||
|
"email": "gatsby@west-egg.com",
|
||||||
|
"notes": "Keeps asking about a green light??",
|
||||||
|
"country": "US",
|
||||||
|
"created": "1925-04-10",
|
||||||
|
"error": "This is an error [line 5, for item 0]",
|
||||||
|
"originalName": "Jay Gatsby"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Success": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "23423536",
|
||||||
|
"name": "Edmund Pevensie",
|
||||||
|
"email": "edmund@narnia.gov",
|
||||||
|
"notes": "Passionate sailor",
|
||||||
|
"country": "UK",
|
||||||
|
"created": "1950-10-16",
|
||||||
|
"myNewField": 1,
|
||||||
|
"originalName": "Edmund Pevensie"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "23423535",
|
||||||
|
"name": "Zaphod Beeblebrox",
|
||||||
|
"email": "captain@heartofgold.com",
|
||||||
|
"notes": "Felt like I was talking to more than one person",
|
||||||
|
"country": null,
|
||||||
|
"created": "1979-10-12",
|
||||||
|
"myNewField": 1,
|
||||||
|
"originalName": "Zaphod Beeblebrox"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "23423533",
|
||||||
|
"name": "José Arcadio Buendía",
|
||||||
|
"email": "jab@macondo.co",
|
||||||
|
"notes": "Lots of people named after him. Very confusing",
|
||||||
|
"country": "CO",
|
||||||
|
"created": "1967-05-05",
|
||||||
|
"myNewField": 1,
|
||||||
|
"originalName": "José Arcadio Buendía"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Combined": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"error": "This is an error [line 5, for item 0]",
|
||||||
|
"originalName": "Jay Gatsby"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "23423533",
|
||||||
|
"name": "José Arcadio Buendía",
|
||||||
|
"email": "jab@macondo.co",
|
||||||
|
"notes": "Lots of people named after him. Very confusing",
|
||||||
|
"country": "CO",
|
||||||
|
"created": "1967-05-05",
|
||||||
|
"myNewField": 1,
|
||||||
|
"originalName": "José Arcadio Buendía"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"error": "This is an error [line 5, for item 2]",
|
||||||
|
"originalName": "Max Sendak"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "23423535",
|
||||||
|
"name": "Zaphod Beeblebrox",
|
||||||
|
"email": "captain@heartofgold.com",
|
||||||
|
"notes": "Felt like I was talking to more than one person",
|
||||||
|
"country": null,
|
||||||
|
"created": "1979-10-12",
|
||||||
|
"myNewField": 1,
|
||||||
|
"originalName": "Zaphod Beeblebrox"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "23423536",
|
||||||
|
"name": "Edmund Pevensie",
|
||||||
|
"email": "edmund@narnia.gov",
|
||||||
|
"notes": "Passionate sailor",
|
||||||
|
"country": "UK",
|
||||||
|
"created": "1950-10-16",
|
||||||
|
"myNewField": 1,
|
||||||
|
"originalName": "Edmund Pevensie"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Combined1": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"error": "This is an error [line 5, for item 0]",
|
||||||
|
"originalName": "Jay Gatsby"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "23423533",
|
||||||
|
"name": "José Arcadio Buendía",
|
||||||
|
"email": "jab@macondo.co",
|
||||||
|
"notes": "Lots of people named after him. Very confusing",
|
||||||
|
"country": "CO",
|
||||||
|
"created": "1967-05-05",
|
||||||
|
"myNewField": 1,
|
||||||
|
"originalName": "José Arcadio Buendía"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"error": "This is an error [line 5, for item 2]",
|
||||||
|
"originalName": "Max Sendak"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "23423535",
|
||||||
|
"name": "Zaphod Beeblebrox",
|
||||||
|
"email": "captain@heartofgold.com",
|
||||||
|
"notes": "Felt like I was talking to more than one person",
|
||||||
|
"country": null,
|
||||||
|
"created": "1979-10-12",
|
||||||
|
"myNewField": 1,
|
||||||
|
"originalName": "Zaphod Beeblebrox"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "23423536",
|
||||||
|
"name": "Edmund Pevensie",
|
||||||
|
"email": "edmund@narnia.gov",
|
||||||
|
"notes": "Passionate sailor",
|
||||||
|
"country": "UK",
|
||||||
|
"created": "1950-10-16",
|
||||||
|
"myNewField": 1,
|
||||||
|
"originalName": "Edmund Pevensie"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Success1": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "23423536",
|
||||||
|
"name": "Edmund Pevensie",
|
||||||
|
"email": "edmund@narnia.gov",
|
||||||
|
"notes": "Passionate sailor",
|
||||||
|
"country": "UK",
|
||||||
|
"created": "1950-10-16",
|
||||||
|
"myNewField": 1,
|
||||||
|
"originalName": "Edmund Pevensie"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "23423535",
|
||||||
|
"name": "Zaphod Beeblebrox",
|
||||||
|
"email": "captain@heartofgold.com",
|
||||||
|
"notes": "Felt like I was talking to more than one person",
|
||||||
|
"country": null,
|
||||||
|
"created": "1979-10-12",
|
||||||
|
"myNewField": 1,
|
||||||
|
"originalName": "Zaphod Beeblebrox"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "23423533",
|
||||||
|
"name": "José Arcadio Buendía",
|
||||||
|
"email": "jab@macondo.co",
|
||||||
|
"notes": "Lots of people named after him. Very confusing",
|
||||||
|
"country": "CO",
|
||||||
|
"created": "1967-05-05",
|
||||||
|
"myNewField": 1,
|
||||||
|
"originalName": "José Arcadio Buendía"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Error1": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "23423534",
|
||||||
|
"name": "Max Sendak",
|
||||||
|
"email": "info@in-and-out-of-weeks.org",
|
||||||
|
"notes": "Keeps rolling his terrible eyes",
|
||||||
|
"country": "US",
|
||||||
|
"created": "1963-04-09",
|
||||||
|
"error": "This is an error [line 5, for item 2]",
|
||||||
|
"originalName": "Max Sendak"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "23423532",
|
||||||
|
"name": "Jay Gatsby",
|
||||||
|
"email": "gatsby@west-egg.com",
|
||||||
|
"notes": "Keeps asking about a green light??",
|
||||||
|
"country": "US",
|
||||||
|
"created": "1925-04-10",
|
||||||
|
"error": "This is an error [line 5, for item 0]",
|
||||||
|
"originalName": "Jay Gatsby"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Success2": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "23423532",
|
||||||
|
"name": "Jay Gatsby",
|
||||||
|
"email": "gatsby@west-egg.com",
|
||||||
|
"notes": "Keeps asking about a green light??",
|
||||||
|
"country": "US",
|
||||||
|
"created": "1925-04-10",
|
||||||
|
"error": "This is an error [line 5, for item 0]",
|
||||||
|
"originalName": "Jay Gatsby"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "23423534",
|
||||||
|
"name": "Max Sendak",
|
||||||
|
"email": "info@in-and-out-of-weeks.org",
|
||||||
|
"notes": "Keeps rolling his terrible eyes",
|
||||||
|
"country": "US",
|
||||||
|
"created": "1963-04-09",
|
||||||
|
"error": "This is an error [line 5, for item 2]",
|
||||||
|
"originalName": "Max Sendak"
|
||||||
|
},
|
||||||
|
"pairedItem": {
|
||||||
|
"item": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Mock Data",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Mock Data": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Throw Error",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "Throw Error2",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "Throw Error1",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "Throw Error3",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Throw Error": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Success",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Error",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Throw Error2": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Combined1",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Throw Error1": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Combined",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Throw Error3": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Success1",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Error1",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Edit Fields": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Success2",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Error2",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Error1": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Edit Fields",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "e73e1eda-293c-4ee2-87b9-923873241774",
|
||||||
|
"id": "UgoluWRMeg7fPLCB",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "021d3c82ba2d3bc090cbf4fc81c9312668bcc34297e022bb3438c5c88a43a5ff"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
|
@ -1297,6 +1297,15 @@ export default defineComponent({
|
||||||
stroke: var(--color-foreground-xdark);
|
stroke: var(--color-foreground-xdark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.error {
|
||||||
|
path {
|
||||||
|
fill: var(--node-error-output-color);
|
||||||
|
}
|
||||||
|
rect {
|
||||||
|
stroke: var(--node-error-output-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.small {
|
&.small {
|
||||||
margin-left: calc((var(--stalk-size) + var(--plus-endpoint-box-size-small) / 2));
|
margin-left: calc((var(--stalk-size) + var(--plus-endpoint-box-size-small) / 2));
|
||||||
g {
|
g {
|
||||||
|
@ -1427,6 +1436,10 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.node-output-endpoint-label.node-connection-category-error {
|
||||||
|
color: var(--node-error-output-color);
|
||||||
|
}
|
||||||
|
|
||||||
.node-output-endpoint-label {
|
.node-output-endpoint-label {
|
||||||
margin-left: calc(var(--endpoint-size-small) + var(--spacing-2xs));
|
margin-left: calc(var(--endpoint-size-small) + var(--spacing-2xs));
|
||||||
|
|
||||||
|
@ -1436,9 +1449,9 @@ export default defineComponent({
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch node allows for dynamic connection labels
|
// Some nodes allow for dynamic connection labels
|
||||||
// so we need to make sure the label does not overflow
|
// so we need to make sure the label does not overflow
|
||||||
&[data-endpoint-node-type='n8n-nodes-base.switch'] {
|
&[data-endpoint-label-length='medium'] {
|
||||||
max-width: calc(var(--stalk-size) - (var(--endpoint-size-small)));
|
max-width: calc(var(--stalk-size) - (var(--endpoint-size-small)));
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
@ -1495,7 +1508,8 @@ export default defineComponent({
|
||||||
.ep-success--without-label {
|
.ep-success--without-label {
|
||||||
--stalk-size: var(--stalk-success-size-without-label);
|
--stalk-size: var(--stalk-success-size-without-label);
|
||||||
}
|
}
|
||||||
[data-endpoint-node-type='n8n-nodes-base.switch'] {
|
|
||||||
|
[data-endpoint-label-length='medium'] {
|
||||||
--stalk-size: var(--stalk-switch-size);
|
--stalk-size: var(--stalk-switch-size);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -106,6 +106,7 @@
|
||||||
@valueChanged="valueChanged"
|
@valueChanged="valueChanged"
|
||||||
@execute="onNodeExecute"
|
@execute="onNodeExecute"
|
||||||
@stopExecution="onStopExecution"
|
@stopExecution="onStopExecution"
|
||||||
|
@redrawRequired="redrawRequired = true"
|
||||||
@activate="onWorkflowActivate"
|
@activate="onWorkflowActivate"
|
||||||
/>
|
/>
|
||||||
<a
|
<a
|
||||||
|
@ -199,6 +200,7 @@ export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
settingsEventBus: createEventBus(),
|
settingsEventBus: createEventBus(),
|
||||||
|
redrawRequired: false,
|
||||||
runInputIndex: -1,
|
runInputIndex: -1,
|
||||||
runOutputIndex: -1,
|
runOutputIndex: -1,
|
||||||
isLinkingEnabled: true,
|
isLinkingEnabled: true,
|
||||||
|
@ -633,7 +635,8 @@ export default defineComponent({
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof this.activeNodeType?.outputs === 'string' ||
|
typeof this.activeNodeType?.outputs === 'string' ||
|
||||||
typeof this.activeNodeType?.inputs === 'string'
|
typeof this.activeNodeType?.inputs === 'string' ||
|
||||||
|
this.redrawRequired
|
||||||
) {
|
) {
|
||||||
// TODO: We should keep track of if it actually changed and only do if required
|
// TODO: We should keep track of if it actually changed and only do if required
|
||||||
// Whenever a node with custom inputs and outputs gets closed redraw it in case
|
// Whenever a node with custom inputs and outputs gets closed redraw it in case
|
||||||
|
|
|
@ -371,7 +371,7 @@ export default defineComponent({
|
||||||
alwaysOutputData: false,
|
alwaysOutputData: false,
|
||||||
executeOnce: false,
|
executeOnce: false,
|
||||||
notesInFlow: false,
|
notesInFlow: false,
|
||||||
continueOnFail: false,
|
onError: 'stopWorkflow',
|
||||||
retryOnFail: false,
|
retryOnFail: false,
|
||||||
maxTries: 3,
|
maxTries: 3,
|
||||||
waitBetweenTries: 1000,
|
waitBetweenTries: 1000,
|
||||||
|
@ -440,12 +440,39 @@ export default defineComponent({
|
||||||
description: this.$locale.baseText('nodeSettings.waitBetweenTries.description'),
|
description: this.$locale.baseText('nodeSettings.waitBetweenTries.description'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: this.$locale.baseText('nodeSettings.continueOnFail.displayName'),
|
displayName: this.$locale.baseText('nodeSettings.onError.displayName'),
|
||||||
name: 'continueOnFail',
|
name: 'onError',
|
||||||
type: 'boolean',
|
type: 'options',
|
||||||
default: false,
|
options: [
|
||||||
|
{
|
||||||
|
name: this.$locale.baseText('nodeSettings.onError.options.stopWorkflow.displayName'),
|
||||||
|
value: 'stopWorkflow',
|
||||||
|
description: this.$locale.baseText(
|
||||||
|
'nodeSettings.onError.options.stopWorkflow.description',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: this.$locale.baseText(
|
||||||
|
'nodeSettings.onError.options.continueRegularOutput.displayName',
|
||||||
|
),
|
||||||
|
value: 'continueRegularOutput',
|
||||||
|
description: this.$locale.baseText(
|
||||||
|
'nodeSettings.onError.options.continueRegularOutput.description',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: this.$locale.baseText(
|
||||||
|
'nodeSettings.onError.options.continueErrorOutput.displayName',
|
||||||
|
),
|
||||||
|
value: 'continueErrorOutput',
|
||||||
|
description: this.$locale.baseText(
|
||||||
|
'nodeSettings.onError.options.continueErrorOutput.description',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'stopWorkflow',
|
||||||
noDataExpression: true,
|
noDataExpression: true,
|
||||||
description: this.$locale.baseText('nodeSettings.continueOnFail.description'),
|
description: this.$locale.baseText('nodeSettings.onError.description'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: this.$locale.baseText('nodeSettings.notes.displayName'),
|
displayName: this.$locale.baseText('nodeSettings.notes.displayName'),
|
||||||
|
@ -633,6 +660,11 @@ export default defineComponent({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parameterData.name === 'onError') {
|
||||||
|
// If that parameter changes, we need to redraw the connections, as the error output may need to be added or removed
|
||||||
|
this.$emit('redrawRequired');
|
||||||
|
}
|
||||||
|
|
||||||
if (parameterData.name === 'name') {
|
if (parameterData.name === 'name') {
|
||||||
// Name of node changed so we have to set also the new node name as active
|
// Name of node changed so we have to set also the new node name as active
|
||||||
|
|
||||||
|
@ -880,10 +912,18 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.node.continueOnFail) {
|
if (this.node.continueOnFail) {
|
||||||
foundNodeSettings.push('continueOnFail');
|
foundNodeSettings.push('onError');
|
||||||
this.nodeValues = {
|
this.nodeValues = {
|
||||||
...this.nodeValues,
|
...this.nodeValues,
|
||||||
continueOnFail: this.node.continueOnFail,
|
onError: 'continueRegularOutput',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.node.onError) {
|
||||||
|
foundNodeSettings.push('onError');
|
||||||
|
this.nodeValues = {
|
||||||
|
...this.nodeValues,
|
||||||
|
onError: this.node.onError,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import * as NodeViewUtils from '@/utils/nodeViewUtils';
|
||||||
import { useHistoryStore } from '@/stores/history.store';
|
import { useHistoryStore } from '@/stores/history.store';
|
||||||
import { useCanvasStore } from '@/stores/canvas.store';
|
import { useCanvasStore } from '@/stores/canvas.store';
|
||||||
import type { EndpointSpec } from '@jsplumb/common';
|
import type { EndpointSpec } from '@jsplumb/common';
|
||||||
|
import { getStyleTokenValue } from '@/utils';
|
||||||
|
|
||||||
const createAddInputEndpointSpec = (
|
const createAddInputEndpointSpec = (
|
||||||
connectionName: NodeConnectionType,
|
connectionName: NodeConnectionType,
|
||||||
|
@ -339,13 +340,13 @@ export const nodeBase = defineComponent({
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
const workflow = this.workflowsStore.getCurrentWorkflow();
|
const workflow = this.workflowsStore.getCurrentWorkflow();
|
||||||
const outputs = NodeHelpers.getNodeOutputs(workflow, this.data, nodeTypeData) || [];
|
this.outputs = NodeHelpers.getNodeOutputs(workflow, this.data, nodeTypeData) || [];
|
||||||
this.outputs = outputs;
|
|
||||||
|
|
||||||
// TODO: There are still a lot of references of "main" in NodesView and
|
// TODO: There are still a lot of references of "main" in NodesView and
|
||||||
// other locations. So assume there will be more problems
|
// other locations. So assume there will be more problems
|
||||||
|
let maxLabelLength = 0;
|
||||||
outputs.forEach((value, i) => {
|
const outputConfigurations: INodeOutputConfiguration[] = [];
|
||||||
|
this.outputs.forEach((value, i) => {
|
||||||
let outputConfiguration: INodeOutputConfiguration;
|
let outputConfiguration: INodeOutputConfiguration;
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
outputConfiguration = {
|
outputConfiguration = {
|
||||||
|
@ -354,6 +355,24 @@ export const nodeBase = defineComponent({
|
||||||
} else {
|
} else {
|
||||||
outputConfiguration = value;
|
outputConfiguration = value;
|
||||||
}
|
}
|
||||||
|
if (nodeTypeData.outputNames?.[i]) {
|
||||||
|
outputConfiguration.displayName = nodeTypeData.outputNames[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputConfiguration.displayName) {
|
||||||
|
maxLabelLength =
|
||||||
|
outputConfiguration.displayName.length > maxLabelLength
|
||||||
|
? outputConfiguration.displayName.length
|
||||||
|
: maxLabelLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputConfigurations.push(outputConfiguration);
|
||||||
|
});
|
||||||
|
|
||||||
|
const endpointLabelLength = maxLabelLength < 4 ? 'short' : 'medium';
|
||||||
|
|
||||||
|
this.outputs.forEach((value, i) => {
|
||||||
|
const outputConfiguration = outputConfigurations[i];
|
||||||
|
|
||||||
const outputName: ConnectionTypes = outputConfiguration.type;
|
const outputName: ConnectionTypes = outputConfiguration.type;
|
||||||
|
|
||||||
|
@ -376,7 +395,7 @@ export const nodeBase = defineComponent({
|
||||||
const rootTypeIndex = rootTypeIndexData[rootCategoryOutputName];
|
const rootTypeIndex = rootTypeIndexData[rootCategoryOutputName];
|
||||||
const typeIndex = typeIndexData[outputName];
|
const typeIndex = typeIndexData[outputName];
|
||||||
|
|
||||||
const outputsOfSameRootType = outputs.filter((outputData) => {
|
const outputsOfSameRootType = this.outputs.filter((outputData) => {
|
||||||
const thisOutputName: string =
|
const thisOutputName: string =
|
||||||
typeof outputData === 'string' ? outputData : outputData.type;
|
typeof outputData === 'string' ? outputData : outputData.type;
|
||||||
return outputName === NodeConnectionType.Main
|
return outputName === NodeConnectionType.Main
|
||||||
|
@ -417,7 +436,7 @@ export const nodeBase = defineComponent({
|
||||||
hoverClass: 'dot-output-endpoint-hover',
|
hoverClass: 'dot-output-endpoint-hover',
|
||||||
connectionsDirected: true,
|
connectionsDirected: true,
|
||||||
dragAllowedWhenFull: false,
|
dragAllowedWhenFull: false,
|
||||||
...this.__getOutputConnectionStyle(outputName, nodeTypeData),
|
...this.__getOutputConnectionStyle(outputName, outputConfiguration, nodeTypeData),
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = this.instance.addEndpoint(
|
const endpoint = this.instance.addEndpoint(
|
||||||
|
@ -426,11 +445,12 @@ export const nodeBase = defineComponent({
|
||||||
);
|
);
|
||||||
|
|
||||||
this.__addEndpointTestingData(endpoint, 'output', typeIndex);
|
this.__addEndpointTestingData(endpoint, 'output', typeIndex);
|
||||||
if (outputConfiguration.displayName || nodeTypeData.outputNames?.[i]) {
|
if (outputConfiguration.displayName) {
|
||||||
// Apply output names if they got set
|
// Apply output names if they got set
|
||||||
const overlaySpec = NodeViewUtils.getOutputNameOverlay(
|
const overlaySpec = NodeViewUtils.getOutputNameOverlay(
|
||||||
outputConfiguration.displayName || nodeTypeData.outputNames[i],
|
outputConfiguration.displayName,
|
||||||
outputName,
|
outputName,
|
||||||
|
outputConfiguration?.category,
|
||||||
);
|
);
|
||||||
endpoint.addOverlay(overlaySpec);
|
endpoint.addOverlay(overlaySpec);
|
||||||
}
|
}
|
||||||
|
@ -441,7 +461,7 @@ export const nodeBase = defineComponent({
|
||||||
nodeId: this.nodeId,
|
nodeId: this.nodeId,
|
||||||
index: typeIndex,
|
index: typeIndex,
|
||||||
totalEndpoints: outputsOfSameRootType.length,
|
totalEndpoints: outputsOfSameRootType.length,
|
||||||
nodeType: node.type,
|
endpointLabelLength,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -455,9 +475,9 @@ export const nodeBase = defineComponent({
|
||||||
options: {
|
options: {
|
||||||
dimensions: 24,
|
dimensions: 24,
|
||||||
connectedEndpoint: endpoint,
|
connectedEndpoint: endpoint,
|
||||||
showOutputLabel: outputs.length === 1,
|
showOutputLabel: this.outputs.length === 1,
|
||||||
size: outputs.length >= 3 ? 'small' : 'medium',
|
size: this.outputs.length >= 3 ? 'small' : 'medium',
|
||||||
nodeType: node.type,
|
endpointLabelLength,
|
||||||
hoverMessage: this.$locale.baseText('nodeBase.clickToAddNodeOrDragToConnect'),
|
hoverMessage: this.$locale.baseText('nodeBase.clickToAddNodeOrDragToConnect'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -475,10 +495,16 @@ export const nodeBase = defineComponent({
|
||||||
nodeId: this.nodeId,
|
nodeId: this.nodeId,
|
||||||
type: outputName,
|
type: outputName,
|
||||||
index: typeIndex,
|
index: typeIndex,
|
||||||
|
category: outputConfiguration?.category,
|
||||||
},
|
},
|
||||||
cssClass: 'plus-draggable-endpoint',
|
cssClass: 'plus-draggable-endpoint',
|
||||||
dragAllowedWhenFull: false,
|
dragAllowedWhenFull: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (outputConfiguration?.category) {
|
||||||
|
plusEndpointData.cssClass = `${plusEndpointData.cssClass} ${outputConfiguration?.category}`;
|
||||||
|
}
|
||||||
|
|
||||||
const plusEndpoint = this.instance.addEndpoint(
|
const plusEndpoint = this.instance.addEndpoint(
|
||||||
this.$refs[this.data.name] as Element,
|
this.$refs[this.data.name] as Element,
|
||||||
plusEndpointData,
|
plusEndpointData,
|
||||||
|
@ -538,6 +564,7 @@ export const nodeBase = defineComponent({
|
||||||
},
|
},
|
||||||
__getOutputConnectionStyle(
|
__getOutputConnectionStyle(
|
||||||
connectionType: ConnectionTypes,
|
connectionType: ConnectionTypes,
|
||||||
|
outputConfiguration: INodeOutputConfiguration,
|
||||||
nodeTypeData: INodeTypeDescription,
|
nodeTypeData: INodeTypeDescription,
|
||||||
): EndpointOptions {
|
): EndpointOptions {
|
||||||
const type = 'output';
|
const type = 'output';
|
||||||
|
@ -557,6 +584,18 @@ export const nodeBase = defineComponent({
|
||||||
});
|
});
|
||||||
|
|
||||||
if (connectionType === NodeConnectionType.Main) {
|
if (connectionType === NodeConnectionType.Main) {
|
||||||
|
if (outputConfiguration.category === 'error') {
|
||||||
|
return {
|
||||||
|
paintStyle: {
|
||||||
|
...NodeViewUtils.getOutputEndpointStyle(
|
||||||
|
nodeTypeData,
|
||||||
|
this.__getEndpointColor(NodeConnectionType.Main),
|
||||||
|
),
|
||||||
|
fill: getStyleTokenValue('--node-error-output-color', true),
|
||||||
|
},
|
||||||
|
cssClass: `dot-${type}-endpoint`,
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
paintStyle: NodeViewUtils.getOutputEndpointStyle(
|
paintStyle: NodeViewUtils.getOutputEndpointStyle(
|
||||||
nodeTypeData,
|
nodeTypeData,
|
||||||
|
|
|
@ -649,6 +649,7 @@ export const workflowHelpers = defineComponent({
|
||||||
'credentials',
|
'credentials',
|
||||||
'disabled',
|
'disabled',
|
||||||
'issues',
|
'issues',
|
||||||
|
'onError',
|
||||||
'notes',
|
'notes',
|
||||||
'parameters',
|
'parameters',
|
||||||
'status',
|
'status',
|
||||||
|
@ -725,14 +726,16 @@ export const workflowHelpers = defineComponent({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the disabled property and continueOnFail only when is set
|
// Save the disabled property, continueOnFail and onError only when is set
|
||||||
if (node.disabled === true) {
|
if (node.disabled === true) {
|
||||||
nodeData.disabled = true;
|
nodeData.disabled = true;
|
||||||
}
|
}
|
||||||
if (node.continueOnFail === true) {
|
if (node.continueOnFail === true) {
|
||||||
nodeData.continueOnFail = true;
|
nodeData.continueOnFail = true;
|
||||||
}
|
}
|
||||||
|
if (node.onError !== 'stopWorkflow') {
|
||||||
|
nodeData.onError = node.onError;
|
||||||
|
}
|
||||||
// Save the notes only if when they contain data
|
// Save the notes only if when they contain data
|
||||||
if (![undefined, ''].includes(node.notes)) {
|
if (![undefined, ''].includes(node.notes)) {
|
||||||
nodeData.notes = node.notes;
|
nodeData.notes = node.notes;
|
||||||
|
|
|
@ -174,6 +174,7 @@
|
||||||
var(--node-type-ai_vectorStore-color-s),
|
var(--node-type-ai_vectorStore-color-s),
|
||||||
var(--node-type-background-l)
|
var(--node-type-background-l)
|
||||||
);
|
);
|
||||||
|
--node-error-output-color: #991818;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clickable {
|
.clickable {
|
||||||
|
|
|
@ -941,8 +941,14 @@
|
||||||
"nodeSettings.alwaysOutputData.description": "If active, will output a single, empty item when the output would have been empty. Use to prevent the workflow finishing on this node.",
|
"nodeSettings.alwaysOutputData.description": "If active, will output a single, empty item when the output would have been empty. Use to prevent the workflow finishing on this node.",
|
||||||
"nodeSettings.alwaysOutputData.displayName": "Always Output Data",
|
"nodeSettings.alwaysOutputData.displayName": "Always Output Data",
|
||||||
"nodeSettings.clickOnTheQuestionMarkIcon": "Click the '?' icon to open this node on n8n.io",
|
"nodeSettings.clickOnTheQuestionMarkIcon": "Click the '?' icon to open this node on n8n.io",
|
||||||
"nodeSettings.continueOnFail.description": "If active, the workflow continues even if this node's execution fails. When this occurs, the node passes along input data from previous nodes - so your workflow should account for unexpected output data.",
|
"nodeSettings.onError.description": "Action to take when the node execution fails",
|
||||||
"nodeSettings.continueOnFail.displayName": "Continue On Fail",
|
"nodeSettings.onError.displayName": "On Error",
|
||||||
|
"nodeSettings.onError.options.continueRegularOutput.description": "Pass error message as item in regular output",
|
||||||
|
"nodeSettings.onError.options.continueRegularOutput.displayName": "Continue",
|
||||||
|
"nodeSettings.onError.options.continueErrorOutput.description": "Pass item to an extra `error` output",
|
||||||
|
"nodeSettings.onError.options.continueErrorOutput.displayName": "Continue (using error output)",
|
||||||
|
"nodeSettings.onError.options.stopWorkflow.description": "Halt execution and fail workflow",
|
||||||
|
"nodeSettings.onError.options.stopWorkflow.displayName": "Stop Workflow",
|
||||||
"nodeSettings.docs": "Docs",
|
"nodeSettings.docs": "Docs",
|
||||||
"nodeSettings.executeOnce.description": "If active, the node executes only once, with data from the first item it receives",
|
"nodeSettings.executeOnce.description": "If active, the node executes only once, with data from the first item it receives",
|
||||||
"nodeSettings.executeOnce.displayName": "Execute Once",
|
"nodeSettings.executeOnce.displayName": "Execute Once",
|
||||||
|
|
|
@ -14,7 +14,7 @@ interface N8nPlusEndpointParams extends EndpointRepresentationParams {
|
||||||
dimensions: number;
|
dimensions: number;
|
||||||
connectedEndpoint: Endpoint;
|
connectedEndpoint: Endpoint;
|
||||||
hoverMessage: string;
|
hoverMessage: string;
|
||||||
nodeType: string;
|
endpointLabelLength: 'small' | 'medium';
|
||||||
size: 'small' | 'medium';
|
size: 'small' | 'medium';
|
||||||
showOutputLabel: boolean;
|
showOutputLabel: boolean;
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ export class N8nPlusEndpoint extends EndpointRepresentation<ComputedN8nPlusEndpo
|
||||||
options: {
|
options: {
|
||||||
id: PlusStalkOverlay,
|
id: PlusStalkOverlay,
|
||||||
attributes: {
|
attributes: {
|
||||||
'data-endpoint-node-type': this.params.nodeType,
|
'data-endpoint-label-length': this.params.endpointLabelLength,
|
||||||
},
|
},
|
||||||
create: () => {
|
create: () => {
|
||||||
const stalk = createElement('div', {}, `${PlusStalkOverlay} ${this.params.size}`);
|
const stalk = createElement('div', {}, `${PlusStalkOverlay} ${this.params.size}`);
|
||||||
|
@ -69,7 +69,7 @@ export class N8nPlusEndpoint extends EndpointRepresentation<ComputedN8nPlusEndpo
|
||||||
id: HoverMessageOverlay,
|
id: HoverMessageOverlay,
|
||||||
location: 0.5,
|
location: 0.5,
|
||||||
attributes: {
|
attributes: {
|
||||||
'data-endpoint-node-type': this.params.nodeType,
|
'data-endpoint-label-length': this.params.endpointLabelLength,
|
||||||
},
|
},
|
||||||
create: () => {
|
create: () => {
|
||||||
const hoverMessage = createElement('p', {}, `${HoverMessageOverlay} ${this.params.size}`);
|
const hoverMessage = createElement('p', {}, `${HoverMessageOverlay} ${this.params.size}`);
|
||||||
|
@ -199,7 +199,8 @@ export const N8nPlusEndpointHandler: EndpointHandler<N8nPlusEndpoint, ComputedN8
|
||||||
ep.w = w;
|
ep.w = w;
|
||||||
ep.h = h;
|
ep.h = h;
|
||||||
|
|
||||||
ep.canvas?.setAttribute('data-endpoint-node-type', ep.params.nodeType);
|
ep.canvas?.setAttribute('data-endpoint-label-length', ep.params.endpointLabelLength);
|
||||||
|
|
||||||
ep.addClass('plus-endpoint');
|
ep.addClass('plus-endpoint');
|
||||||
return [x, y, w, h, ep.params.dimensions];
|
return [x, y, w, h, ep.params.dimensions];
|
||||||
},
|
},
|
||||||
|
|
|
@ -112,7 +112,11 @@ export const CONNECTOR_PAINT_STYLE_DATA: PaintStyle = {
|
||||||
stroke: getStyleTokenValue('--color-foreground-dark', true),
|
stroke: getStyleTokenValue('--color-foreground-dark', true),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getConnectorColor = (type: ConnectionTypes): string => {
|
export const getConnectorColor = (type: ConnectionTypes, category?: string): string => {
|
||||||
|
if (category === 'error') {
|
||||||
|
return '--node-error-output-color';
|
||||||
|
}
|
||||||
|
|
||||||
if (type === NodeConnectionType.Main) {
|
if (type === NodeConnectionType.Main) {
|
||||||
return '--node-type-main-color';
|
return '--node-type-main-color';
|
||||||
}
|
}
|
||||||
|
@ -121,7 +125,10 @@ export const getConnectorColor = (type: ConnectionTypes): string => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getConnectorPaintStylePull = (connection: Connection): PaintStyle => {
|
export const getConnectorPaintStylePull = (connection: Connection): PaintStyle => {
|
||||||
const connectorColor = getConnectorColor(connection.parameters.type as ConnectionTypes);
|
const connectorColor = getConnectorColor(
|
||||||
|
connection.parameters.type as ConnectionTypes,
|
||||||
|
connection.parameters.category,
|
||||||
|
);
|
||||||
const additionalStyles: PaintStyle = {};
|
const additionalStyles: PaintStyle = {};
|
||||||
if (connection.parameters.type !== NodeConnectionType.Main) {
|
if (connection.parameters.type !== NodeConnectionType.Main) {
|
||||||
additionalStyles.dashstyle = '5 3';
|
additionalStyles.dashstyle = '5 3';
|
||||||
|
@ -134,15 +141,21 @@ export const getConnectorPaintStylePull = (connection: Connection): PaintStyle =
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getConnectorPaintStyleDefault = (connection: Connection): PaintStyle => {
|
export const getConnectorPaintStyleDefault = (connection: Connection): PaintStyle => {
|
||||||
const connectorColor = getConnectorColor(connection.parameters.type as ConnectionTypes);
|
const connectorColor = getConnectorColor(
|
||||||
|
connection.parameters.type as ConnectionTypes,
|
||||||
|
connection.parameters.category,
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
...CONNECTOR_PAINT_STYLE_DEFAULT,
|
...CONNECTOR_PAINT_STYLE_DEFAULT,
|
||||||
...(connectorColor ? { stroke: getStyleTokenValue(connectorColor, true) } : {}),
|
...(connectorColor ? { stroke: getStyleTokenValue(connectorColor, true) } : {}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getConnectorPaintStyleData = (connection: Connection): PaintStyle => {
|
export const getConnectorPaintStyleData = (
|
||||||
const connectorColor = getConnectorColor(connection.parameters.type as ConnectionTypes);
|
connection: Connection,
|
||||||
|
category?: string,
|
||||||
|
): PaintStyle => {
|
||||||
|
const connectorColor = getConnectorColor(connection.parameters.type as ConnectionTypes, category);
|
||||||
return {
|
return {
|
||||||
...CONNECTOR_PAINT_STYLE_DATA,
|
...CONNECTOR_PAINT_STYLE_DATA,
|
||||||
...(connectorColor ? { stroke: getStyleTokenValue(connectorColor, true) } : {}),
|
...(connectorColor ? { stroke: getStyleTokenValue(connectorColor, true) } : {}),
|
||||||
|
@ -292,6 +305,7 @@ export const getOutputEndpointStyle = (
|
||||||
export const getOutputNameOverlay = (
|
export const getOutputNameOverlay = (
|
||||||
labelText: string,
|
labelText: string,
|
||||||
outputName: ConnectionTypes,
|
outputName: ConnectionTypes,
|
||||||
|
category?: string,
|
||||||
): OverlaySpec => ({
|
): OverlaySpec => ({
|
||||||
type: 'Custom',
|
type: 'Custom',
|
||||||
options: {
|
options: {
|
||||||
|
@ -302,11 +316,16 @@ export const getOutputNameOverlay = (
|
||||||
label.innerHTML = labelText;
|
label.innerHTML = labelText;
|
||||||
label.classList.add('node-output-endpoint-label');
|
label.classList.add('node-output-endpoint-label');
|
||||||
|
|
||||||
label.setAttribute('data-endpoint-node-type', ep?.__meta?.nodeType);
|
if (ep?.__meta?.endpointLabelLength) {
|
||||||
|
label.setAttribute('data-endpoint-label-length', ep?.__meta?.endpointLabelLength);
|
||||||
|
}
|
||||||
if (outputName !== NodeConnectionType.Main) {
|
if (outputName !== NodeConnectionType.Main) {
|
||||||
label.classList.add('node-output-endpoint-label--data');
|
label.classList.add('node-output-endpoint-label--data');
|
||||||
label.classList.add(`node-connection-type-${getScope(outputName)}`);
|
label.classList.add(`node-connection-type-${getScope(outputName)}`);
|
||||||
}
|
}
|
||||||
|
if (category) {
|
||||||
|
label.classList.add(`node-connection-category-${category}`);
|
||||||
|
}
|
||||||
return label;
|
return label;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1978,21 +1978,25 @@ export default defineComponent({
|
||||||
lastSelectedNode.type,
|
lastSelectedNode.type,
|
||||||
lastSelectedNode.typeVersion,
|
lastSelectedNode.typeVersion,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (sourceNodeType) {
|
if (sourceNodeType) {
|
||||||
const offsets = [
|
const offsets = [
|
||||||
[-100, 100],
|
[-100, 100],
|
||||||
[-140, 0, 140],
|
[-140, 0, 140],
|
||||||
[-240, -100, 100, 240],
|
[-240, -100, 100, 240],
|
||||||
];
|
];
|
||||||
|
|
||||||
const sourceNodeOutputs = NodeHelpers.getNodeOutputs(
|
const sourceNodeOutputs = NodeHelpers.getNodeOutputs(
|
||||||
workflow,
|
workflow,
|
||||||
lastSelectedNode,
|
lastSelectedNode,
|
||||||
sourceNodeType,
|
sourceNodeType,
|
||||||
);
|
);
|
||||||
const sourceNodeOutputTypes = NodeHelpers.getConnectionTypes(sourceNodeOutputs);
|
const sourceNodeOutputTypes = NodeHelpers.getConnectionTypes(sourceNodeOutputs);
|
||||||
|
|
||||||
const sourceNodeOutputMainOutputs = sourceNodeOutputTypes.filter(
|
const sourceNodeOutputMainOutputs = sourceNodeOutputTypes.filter(
|
||||||
(output) => output === NodeConnectionType.Main,
|
(output) => output === NodeConnectionType.Main,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (sourceNodeOutputMainOutputs.length > 1) {
|
if (sourceNodeOutputMainOutputs.length > 1) {
|
||||||
const offset = offsets[sourceNodeOutputMainOutputs.length - 2];
|
const offset = offsets[sourceNodeOutputMainOutputs.length - 2];
|
||||||
const sourceOutputIndex = lastSelectedConnection.__meta
|
const sourceOutputIndex = lastSelectedConnection.__meta
|
||||||
|
@ -2005,9 +2009,9 @@ export default defineComponent({
|
||||||
|
|
||||||
let outputs: Array<ConnectionTypes | INodeOutputConfiguration> = [];
|
let outputs: Array<ConnectionTypes | INodeOutputConfiguration> = [];
|
||||||
try {
|
try {
|
||||||
// It fails when the outputs are an expression. As those node have
|
// It fails when the outputs are an expression. As those nodes have
|
||||||
// normally no outputs by default and the only reason we need the
|
// normally no outputs by default and the only reason we need the
|
||||||
// outputs here is to calculate the position it is fine to assume
|
// outputs here is to calculate the position, it is fine to assume
|
||||||
// that they have no outputs and are so treated as a regular node
|
// that they have no outputs and are so treated as a regular node
|
||||||
// with only "main" outputs.
|
// with only "main" outputs.
|
||||||
outputs = NodeHelpers.getNodeOutputs(workflow, newNodeData, nodeTypeData);
|
outputs = NodeHelpers.getNodeOutputs(workflow, newNodeData, nodeTypeData);
|
||||||
|
@ -2571,7 +2575,9 @@ export default defineComponent({
|
||||||
);
|
);
|
||||||
if (sourceInfo.type !== NodeConnectionType.Main) {
|
if (sourceInfo.type !== NodeConnectionType.Main) {
|
||||||
// Not "main" connections get a different connection style
|
// Not "main" connections get a different connection style
|
||||||
info.connection.setPaintStyle(getConnectorPaintStyleData(info.connection));
|
info.connection.setPaintStyle(
|
||||||
|
getConnectorPaintStyleData(info.connection, info.sourceEndpoint.parameters.category),
|
||||||
|
);
|
||||||
endpointArrow?.setVisible(false);
|
endpointArrow?.setVisible(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,7 +160,12 @@ export class Code implements INodeType {
|
||||||
result = await sandbox.runCodeEachItem();
|
result = await sandbox.runCodeEachItem();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!this.continueOnFail()) throw error;
|
if (!this.continueOnFail()) throw error;
|
||||||
returnData.push({ json: { error: error.message } });
|
returnData.push({
|
||||||
|
json: { error: error.message },
|
||||||
|
pairedItem: {
|
||||||
|
item: index,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
|
|
|
@ -16,7 +16,7 @@ export interface SandboxContext extends IWorkflowDataProxyData {
|
||||||
helpers: IExecuteFunctions['helpers'];
|
helpers: IExecuteFunctions['helpers'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const REQUIRED_N8N_ITEM_KEYS = new Set(['json', 'binary', 'pairedItem']);
|
export const REQUIRED_N8N_ITEM_KEYS = new Set(['json', 'binary', 'pairedItem', 'error']);
|
||||||
|
|
||||||
export function getSandboxContext(this: IExecuteFunctions, index: number): SandboxContext {
|
export function getSandboxContext(this: IExecuteFunctions, index: number): SandboxContext {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -920,6 +920,7 @@ export interface INodeCredentials {
|
||||||
[key: string]: INodeCredentialsDetails;
|
[key: string]: INodeCredentialsDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type OnError = 'continueErrorOutput' | 'continueRegularOutput' | 'stopWorkflow';
|
||||||
export interface INode {
|
export interface INode {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -934,6 +935,7 @@ export interface INode {
|
||||||
waitBetweenTries?: number;
|
waitBetweenTries?: number;
|
||||||
alwaysOutputData?: boolean;
|
alwaysOutputData?: boolean;
|
||||||
executeOnce?: boolean;
|
executeOnce?: boolean;
|
||||||
|
onError?: OnError;
|
||||||
continueOnFail?: boolean;
|
continueOnFail?: boolean;
|
||||||
parameters: INodeParameters;
|
parameters: INodeParameters;
|
||||||
credentials?: INodeCredentials;
|
credentials?: INodeCredentials;
|
||||||
|
@ -1546,6 +1548,7 @@ export interface INodeInputConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INodeOutputConfiguration {
|
export interface INodeOutputConfiguration {
|
||||||
|
category?: string;
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
type: ConnectionTypes;
|
type: ConnectionTypes;
|
||||||
|
@ -1653,6 +1656,11 @@ export interface IWorkflowDataProxyData {
|
||||||
$thisItemIndex: number;
|
$thisItemIndex: number;
|
||||||
$now: any;
|
$now: any;
|
||||||
$today: any;
|
$today: any;
|
||||||
|
$getPairedItem: (
|
||||||
|
destinationNodeName: string,
|
||||||
|
incomingSourceData: ISourceData | null,
|
||||||
|
pairedItem: IPairedItemData,
|
||||||
|
) => INodeExecutionData | null;
|
||||||
constructor: any;
|
constructor: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1045,13 +1045,14 @@ export function getNodeOutputs(
|
||||||
node: INode,
|
node: INode,
|
||||||
nodeTypeData: INodeTypeDescription,
|
nodeTypeData: INodeTypeDescription,
|
||||||
): Array<ConnectionTypes | INodeOutputConfiguration> {
|
): Array<ConnectionTypes | INodeOutputConfiguration> {
|
||||||
if (Array.isArray(nodeTypeData.outputs)) {
|
let outputs: Array<ConnectionTypes | INodeOutputConfiguration> = [];
|
||||||
return nodeTypeData.outputs;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (Array.isArray(nodeTypeData.outputs)) {
|
||||||
|
outputs = nodeTypeData.outputs;
|
||||||
|
} else {
|
||||||
// Calculate the outputs dynamically
|
// Calculate the outputs dynamically
|
||||||
try {
|
try {
|
||||||
return (workflow.expression.getSimpleParameterValue(
|
outputs = (workflow.expression.getSimpleParameterValue(
|
||||||
node,
|
node,
|
||||||
nodeTypeData.outputs,
|
nodeTypeData.outputs,
|
||||||
'internal',
|
'internal',
|
||||||
|
@ -1062,6 +1063,32 @@ export function getNodeOutputs(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.onError === 'continueErrorOutput') {
|
||||||
|
// Copy the data to make sure that we do not change the data of the
|
||||||
|
// node type and so change the displayNames for all nodes in the flow
|
||||||
|
outputs = deepCopy(outputs);
|
||||||
|
if (outputs.length === 1) {
|
||||||
|
// Set the displayName to "Success"
|
||||||
|
if (typeof outputs[0] === 'string') {
|
||||||
|
outputs[0] = {
|
||||||
|
type: outputs[0],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
outputs[0].displayName = 'Success';
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
...outputs,
|
||||||
|
{
|
||||||
|
category: 'error',
|
||||||
|
type: 'main',
|
||||||
|
displayName: 'Error',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputs;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all the parameter-issues of the node
|
* Returns all the parameter-issues of the node
|
||||||
*
|
*
|
||||||
|
|
|
@ -121,8 +121,9 @@ export class RoutingNode {
|
||||||
|
|
||||||
// TODO: Think about how batching could be handled for REST APIs which support it
|
// TODO: Think about how batching could be handled for REST APIs which support it
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
let thisArgs: IExecuteSingleFunctions | undefined;
|
||||||
try {
|
try {
|
||||||
const thisArgs = nodeExecuteFunctions.getExecuteSingleFunctions(
|
thisArgs = nodeExecuteFunctions.getExecuteSingleFunctions(
|
||||||
this.workflow,
|
this.workflow,
|
||||||
this.runExecutionData,
|
this.runExecutionData,
|
||||||
runIndex,
|
runIndex,
|
||||||
|
@ -209,7 +210,7 @@ export class RoutingNode {
|
||||||
|
|
||||||
returnData.push(...responseData);
|
returnData.push(...responseData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (get(this.node, 'continueOnFail', false)) {
|
if (thisArgs !== undefined && thisArgs.continueOnFail()) {
|
||||||
returnData.push({ json: {}, error: error.message });
|
returnData.push({ json: {}, error: error.message });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1211,6 +1211,7 @@ export class WorkflowDataProxy {
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
Duration,
|
Duration,
|
||||||
...that.additionalKeys,
|
...that.additionalKeys,
|
||||||
|
$getPairedItem: getPairedItem,
|
||||||
|
|
||||||
// deprecated
|
// deprecated
|
||||||
$jmespath: jmespathWrapper,
|
$jmespath: jmespathWrapper,
|
||||||
|
|
Loading…
Reference in a new issue