feat(core): Add optional Error-Output (#7460)

Add an additional optional error output to which all items get sent that
could not be processed.
![Screenshot from 2023-10-18
17-29-15](https://github.com/n8n-io/n8n/assets/6249596/e9732807-ab2b-4662-a5f6-bdff24f7ad55)

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:
Jan Oberhauser
2023-10-30 18:42:47 +01:00
committed by GitHub
parent 442c73e63b
commit 655efeaf66
20 changed files with 1090 additions and 61 deletions

View File

@@ -920,6 +920,7 @@ export interface INodeCredentials {
[key: string]: INodeCredentialsDetails;
}
export type OnError = 'continueErrorOutput' | 'continueRegularOutput' | 'stopWorkflow';
export interface INode {
id: string;
name: string;
@@ -934,6 +935,7 @@ export interface INode {
waitBetweenTries?: number;
alwaysOutputData?: boolean;
executeOnce?: boolean;
onError?: OnError;
continueOnFail?: boolean;
parameters: INodeParameters;
credentials?: INodeCredentials;
@@ -1546,6 +1548,7 @@ export interface INodeInputConfiguration {
}
export interface INodeOutputConfiguration {
category?: string;
displayName?: string;
required?: boolean;
type: ConnectionTypes;
@@ -1653,6 +1656,11 @@ export interface IWorkflowDataProxyData {
$thisItemIndex: number;
$now: any;
$today: any;
$getPairedItem: (
destinationNodeName: string,
incomingSourceData: ISourceData | null,
pairedItem: IPairedItemData,
) => INodeExecutionData | null;
constructor: any;
}

View File

@@ -1045,21 +1045,48 @@ export function getNodeOutputs(
node: INode,
nodeTypeData: INodeTypeDescription,
): Array<ConnectionTypes | INodeOutputConfiguration> {
let outputs: Array<ConnectionTypes | INodeOutputConfiguration> = [];
if (Array.isArray(nodeTypeData.outputs)) {
return nodeTypeData.outputs;
outputs = nodeTypeData.outputs;
} else {
// Calculate the outputs dynamically
try {
outputs = (workflow.expression.getSimpleParameterValue(
node,
nodeTypeData.outputs,
'internal',
{},
) || []) as ConnectionTypes[];
} catch (e) {
throw new Error(`Could not calculate outputs dynamically for node "${node.name}"`);
}
}
// Calculate the outputs dynamically
try {
return (workflow.expression.getSimpleParameterValue(
node,
nodeTypeData.outputs,
'internal',
{},
) || []) as ConnectionTypes[];
} catch (e) {
throw new Error(`Could not calculate outputs dynamically for node "${node.name}"`);
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;
}
/**

View File

@@ -121,8 +121,9 @@ export class RoutingNode {
// TODO: Think about how batching could be handled for REST APIs which support it
for (let i = 0; i < items.length; i++) {
let thisArgs: IExecuteSingleFunctions | undefined;
try {
const thisArgs = nodeExecuteFunctions.getExecuteSingleFunctions(
thisArgs = nodeExecuteFunctions.getExecuteSingleFunctions(
this.workflow,
this.runExecutionData,
runIndex,
@@ -209,7 +210,7 @@ export class RoutingNode {
returnData.push(...responseData);
} catch (error) {
if (get(this.node, 'continueOnFail', false)) {
if (thisArgs !== undefined && thisArgs.continueOnFail()) {
returnData.push({ json: {}, error: error.message });
continue;
}

View File

@@ -1211,6 +1211,7 @@ export class WorkflowDataProxy {
// eslint-disable-next-line @typescript-eslint/naming-convention
Duration,
...that.additionalKeys,
$getPairedItem: getPairedItem,
// deprecated
$jmespath: jmespathWrapper,