feat: No expression error when node hasn’t executed (#8448)

Co-authored-by: Giulio Andreini <andreini@netseven.it>
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
Elias Meire
2024-02-27 10:29:16 +01:00
committed by GitHub
parent 737170893d
commit f9a99ec029
29 changed files with 2818 additions and 558 deletions

View File

@@ -22,7 +22,7 @@ import type {
ProxyInput,
} from './Interfaces';
import * as NodeHelpers from './NodeHelpers';
import { ExpressionError } from './errors/expression.error';
import { ExpressionError, type ExpressionErrorOptions } from './errors/expression.error';
import type { Workflow } from './Workflow';
import { augmentArray, augmentObject } from './AugmentObject';
import { deepCopy } from './utils';
@@ -104,6 +104,7 @@ export class WorkflowDataProxy {
{
runIndex: that.runIndex,
itemIndex: that.itemIndex,
type: 'no_execution_data',
},
);
}
@@ -274,19 +275,25 @@ export class WorkflowDataProxy {
);
}
if (!that.runExecutionData.resultData.runData.hasOwnProperty(nodeName)) {
if (that.workflow.getNode(nodeName)) {
throw new ExpressionError(`no data, execute "${nodeName}" node first`, {
runIndex: that.runIndex,
itemIndex: that.itemIndex,
});
}
if (!that.workflow.getNode(nodeName)) {
throw new ExpressionError(`"${nodeName}" node doesn't exist`, {
runIndex: that.runIndex,
itemIndex: that.itemIndex,
});
}
if (
!that.runExecutionData.resultData.runData.hasOwnProperty(nodeName) &&
!that.workflow.getPinDataOfNode(nodeName)
) {
throw new ExpressionError(`no data, execute "${nodeName}" node first`, {
runIndex: that.runIndex,
itemIndex: that.itemIndex,
type: 'no_node_execution_data',
nodeCause: nodeName,
});
}
runIndex = runIndex === undefined ? that.defaultReturnRunIndex : runIndex;
runIndex =
runIndex === -1 ? that.runExecutionData.resultData.runData[nodeName].length - 1 : runIndex;
@@ -372,6 +379,28 @@ export class WorkflowDataProxy {
if (['binary', 'data', 'json'].includes(name)) {
const executionData = that.getNodeExecutionData(nodeName, shortSyntax, undefined);
if (executionData.length === 0) {
if (that.workflow.getParentNodes(nodeName).length === 0) {
throw new ExpressionError('No execution data available', {
messageTemplate:
'No execution data available to expression under %%PARAMETER%%',
description:
'This node has no input data. Please make sure this node is connected to another node.',
nodeCause: nodeName,
runIndex: that.runIndex,
itemIndex: that.itemIndex,
type: 'no_input_connection',
});
}
throw new ExpressionError('No execution data available', {
runIndex: that.runIndex,
itemIndex: that.itemIndex,
type: 'no_execution_data',
});
}
if (executionData.length <= that.itemIndex) {
throw new ExpressionError(`No data found for item-index: "${that.itemIndex}"`, {
runIndex: that.runIndex,
@@ -615,22 +644,13 @@ export class WorkflowDataProxy {
const createExpressionError = (
message: string,
context?: {
causeDetailed?: string;
description?: string;
descriptionTemplate?: string;
functionality?: 'pairedItem';
context?: ExpressionErrorOptions & {
moreInfoLink?: boolean;
functionOverrides?: {
// Custom data to display for Function-Nodes
message?: string;
description?: string;
};
itemIndex?: number;
messageTemplate?: string;
moreInfoLink?: boolean;
nodeCause?: string;
runIndex?: number;
type?: string;
},
) => {
if (isScriptingNode(that.activeNodeName, that.workflow) && context?.functionOverrides) {
@@ -678,7 +698,7 @@ export class WorkflowDataProxy {
incomingSourceData: ISourceData | null,
pairedItem: IPairedItemData,
): INodeExecutionData | null => {
let taskData: ITaskData;
let taskData: ITaskData | undefined;
let sourceData: ISourceData | null = incomingSourceData;
@@ -697,13 +717,12 @@ export class WorkflowDataProxy {
let nodeBeforeLast: string | undefined;
while (sourceData !== null && destinationNodeName !== sourceData.previousNode) {
taskData =
that.runExecutionData!.resultData.runData[sourceData.previousNode][
sourceData?.previousNodeRun || 0
];
const runIndex = sourceData?.previousNodeRun || 0;
const previousNodeOutput = sourceData.previousNodeOutput || 0;
if (previousNodeOutput >= taskData.data!.main.length) {
taskData =
that.runExecutionData?.resultData?.runData?.[sourceData.previousNode]?.[runIndex];
if (taskData?.data?.main && previousNodeOutput >= taskData.data.main.length) {
throw createExpressionError('Cant get data for expression', {
messageTemplate: 'Cant get data for expression under %%PARAMETER%% field',
functionOverrides: {
@@ -716,7 +735,12 @@ export class WorkflowDataProxy {
});
}
if (pairedItem.item >= taskData.data!.main[previousNodeOutput]!.length) {
const previousNodeOutputData =
taskData?.data?.main?.[previousNodeOutput] ??
(that.workflow.getPinDataOfNode(sourceData.previousNode) as INodeExecutionData[]);
const source = taskData?.source ?? [];
if (pairedItem.item >= previousNodeOutputData.length) {
throw createExpressionError('Cant get data for expression', {
messageTemplate: 'Cant get data for expression under %%PARAMETER%% field',
functionality: 'pairedItem',
@@ -733,13 +757,12 @@ export class WorkflowDataProxy {
}points to an input item on node <strong>${
sourceData.previousNode
}</strong> that doesnt exist.`,
type: 'invalid pairing info',
type: 'paired_item_invalid_info',
moreInfoLink: true,
});
}
const itemPreviousNode: INodeExecutionData =
taskData.data!.main[previousNodeOutput]![pairedItem.item];
const itemPreviousNode: INodeExecutionData = previousNodeOutputData[pairedItem.item];
if (itemPreviousNode.pairedItem === undefined) {
throw createExpressionError('Cant get data for expression', {
@@ -751,7 +774,7 @@ export class WorkflowDataProxy {
nodeCause: sourceData.previousNode,
description: `To fetch the data from other nodes that this expression needs, more information is needed from the node <strong>${sourceData.previousNode}</strong>`,
causeDetailed: `Missing pairedItem data (node ${sourceData.previousNode} probably didnt supply it)`,
type: 'no pairing info',
type: 'paired_item_no_info',
moreInfoLink: true,
});
}
@@ -763,13 +786,13 @@ export class WorkflowDataProxy {
.map((item) => {
try {
const itemInput = item.input || 0;
if (itemInput >= taskData.source.length) {
if (itemInput >= source.length) {
// `Could not resolve pairedItem as the defined node input '${itemInput}' does not exist on node '${sourceData!.previousNode}'.`
// Actual error does not matter as it gets caught below and `null` will be returned
throw new ApplicationError('Not found');
}
return getPairedItem(destinationNodeName, taskData.source[itemInput], item);
return getPairedItem(destinationNodeName, source[itemInput], item);
} catch (error) {
// Means pairedItem could not be found
return null;
@@ -793,7 +816,7 @@ export class WorkflowDataProxy {
message: 'Invalid code',
},
description: `The expression uses data in the node <strong>${destinationNodeName}</strong> but there is more than one matching item in that node`,
type: 'multiple matches',
type: 'paired_item_multiple_matches',
});
}
@@ -812,8 +835,8 @@ export class WorkflowDataProxy {
}
const itemInput = pairedItem.input || 0;
if (itemInput >= taskData.source.length) {
if (taskData.source.length === 0) {
if (itemInput >= source.length) {
if (source.length === 0) {
// A trigger node got reached, so looks like that that item can not be resolved
throw createExpressionError('Invalid expression', {
messageTemplate: 'Invalid expression under %%PARAMETER%%',
@@ -823,7 +846,7 @@ export class WorkflowDataProxy {
message: 'Invalid code',
},
description: `The expression uses data in the node <strong>${destinationNodeName}</strong> but there is no path back to it. Please check this node is connected to it (there can be other nodes in between).`,
type: 'no connection',
type: 'paired_item_no_connection',
moreInfoLink: true,
});
}
@@ -841,12 +864,12 @@ export class WorkflowDataProxy {
? `of run ${(sourceData.previousNodeRun || 0).toString()} `
: ''
}points to a branch that doesnt exist.`,
type: 'invalid pairing info',
type: 'paired_item_invalid_info',
});
}
nodeBeforeLast = sourceData.previousNode;
sourceData = taskData.source[pairedItem.input || 0] || null;
sourceData = source[pairedItem.input || 0] || null;
if (pairedItem.sourceOverwrite) {
sourceData = pairedItem.sourceOverwrite;
@@ -862,7 +885,7 @@ export class WorkflowDataProxy {
},
nodeCause: nodeBeforeLast,
description: 'Could not resolve, probably no pairedItem exists',
type: 'no pairing info',
type: 'paired_item_no_info',
moreInfoLink: true,
});
}
@@ -882,7 +905,7 @@ export class WorkflowDataProxy {
},
description: 'Item points to a node output which does not exist',
causeDetailed: `The sourceData points to a node output ${previousNodeOutput} which does not exist on node ${sourceData.previousNode} (output node did probably supply a wrong one)`,
type: 'invalid pairing info',
type: 'paired_item_invalid_info',
});
}
@@ -903,7 +926,7 @@ export class WorkflowDataProxy {
}points to an input item on node <strong>${
sourceData.previousNode
}</strong> that doesnt exist.`,
type: 'invalid pairing info',
type: 'paired_item_invalid_info',
moreInfoLink: true,
});
}
@@ -922,6 +945,18 @@ export class WorkflowDataProxy {
throw createExpressionError(`"${nodeName}" node doesn't exist`);
}
const ensureNodeExecutionData = () => {
if (
!that?.runExecutionData?.resultData?.runData.hasOwnProperty(nodeName) &&
!that.workflow.getPinDataOfNode(nodeName)
) {
throw createExpressionError(`no data, execute "${nodeName}" node first`, {
type: 'no_node_execution_data',
nodeCause: nodeName,
});
}
};
return new Proxy(
{},
{
@@ -942,13 +977,38 @@ export class WorkflowDataProxy {
get(target, property, receiver) {
if (property === 'isProxy') return true;
if (!that?.runExecutionData?.resultData?.runData.hasOwnProperty(nodeName)) {
if (property === 'isExecuted') return false;
throw createExpressionError(`no data, execute "${nodeName}" node first`);
if (property === 'isExecuted') {
return (
that?.runExecutionData?.resultData?.runData.hasOwnProperty(nodeName) ?? false
);
}
if (property === 'isExecuted') return true;
if (['pairedItem', 'itemMatching', 'item'].includes(property as string)) {
// Before resolving the pairedItem make sure that the requested node comes in the
// graph before the current one
const activeNode = that.workflow.getNode(that.activeNodeName);
let contextNode = that.contextNodeName;
if (activeNode) {
const parentMainInputNode = that.workflow.getParentMainInputNode(activeNode);
contextNode = parentMainInputNode.name ?? contextNode;
}
const parentNodes = that.workflow.getParentNodes(contextNode);
if (!parentNodes.includes(nodeName)) {
throw createExpressionError('Invalid expression', {
messageTemplate: 'Invalid expression under %%PARAMETER%%',
functionality: 'pairedItem',
functionOverrides: {
description: `The code uses data in the node <strong>${nodeName}</strong> but there is no path back to it. Please check this node is connected to it (there can be other nodes in between).`,
message: `No path back to node ${nodeName}`,
},
description: `The expression uses data in the node <strong>${nodeName}</strong> but there is no path back to it. Please check this node is connected to it (there can be other nodes in between).`,
nodeCause: nodeName,
type: 'paired_item_no_connection',
});
}
ensureNodeExecutionData();
const pairedItemMethod = (itemIndex?: number) => {
if (itemIndex === undefined) {
if (property === 'itemMatching') {
@@ -960,11 +1020,26 @@ export class WorkflowDataProxy {
}
const executionData = that.connectionInputData;
const input = executionData[itemIndex];
if (!input) {
throw createExpressionError('Cant get data for expression', {
messageTemplate: 'Cant get data for expression under %%PARAMETER%% field',
functionality: 'pairedItem',
functionOverrides: {
description: `Some intermediate nodes between <strong>${nodeName}</strong> and <strong>${that.activeNodeName}</strong> have not executed yet.`,
message: 'Cant get data',
},
description: `Some intermediate nodes between <strong>${nodeName}</strong> and <strong>${that.activeNodeName}</strong> have not executed yet.`,
causeDetailed: `pairedItem can\'t be found when intermediate nodes between <strong>${nodeName}</strong> and <strong>${that.activeNodeName}</strong> have not executed yet.`,
itemIndex,
type: 'paired_item_intermediate_nodes',
});
}
// As we operate on the incoming item we can be sure that pairedItem is not an
// array. After all can it only come from exactly one previous node via a certain
// input. For that reason do we not have to consider the array case.
const pairedItem = executionData[itemIndex].pairedItem as IPairedItemData;
const pairedItem = input.pairedItem as IPairedItemData;
if (pairedItem === undefined) {
throw createExpressionError('Cant get data for expression', {
@@ -994,28 +1069,6 @@ export class WorkflowDataProxy {
});
}
// Before resolving the pairedItem make sure that the requested node comes in the
// graph before the current one
const activeNode = that.workflow.getNode(that.activeNodeName);
let contextNode = that.contextNodeName;
if (activeNode) {
const parentMainInputNode = that.workflow.getParentMainInputNode(activeNode);
contextNode = parentMainInputNode.name ?? contextNode;
}
const parentNodes = that.workflow.getParentNodes(contextNode);
if (!parentNodes.includes(nodeName)) {
throw createExpressionError('Invalid expression', {
messageTemplate: 'Invalid expression under %%PARAMETER%%',
functionality: 'pairedItem',
functionOverrides: {
description: `The code uses data in the node <strong>${nodeName}</strong> but there is no path back to it. Please check this node is connected to it (there can be other nodes in between).`,
message: `No path back to node ${nodeName}`,
},
description: `The expression uses data in the node <strong>${nodeName}</strong> but there is no path back to it. Please check this node is connected to it (there can be other nodes in between).`,
itemIndex,
});
}
const sourceData: ISourceData | null =
that.executeData.source.main[pairedItem.input || 0] ??
that.executeData.source.main[0];
@@ -1029,6 +1082,7 @@ export class WorkflowDataProxy {
return pairedItemMethod;
}
if (property === 'first') {
ensureNodeExecutionData();
return (branchIndex?: number, runIndex?: number) => {
const executionData = getNodeOutput(nodeName, branchIndex, runIndex);
if (executionData[0]) return executionData[0];
@@ -1036,6 +1090,7 @@ export class WorkflowDataProxy {
};
}
if (property === 'last') {
ensureNodeExecutionData();
return (branchIndex?: number, runIndex?: number) => {
const executionData = getNodeOutput(nodeName, branchIndex, runIndex);
if (!executionData.length) return undefined;
@@ -1046,6 +1101,7 @@ export class WorkflowDataProxy {
};
}
if (property === 'all') {
ensureNodeExecutionData();
return (branchIndex?: number, runIndex?: number) =>
getNodeOutput(nodeName, branchIndex, runIndex);
}
@@ -1075,6 +1131,14 @@ export class WorkflowDataProxy {
get(target, property, receiver) {
if (property === 'isProxy') return true;
if (that.connectionInputData.length === 0) {
throw createExpressionError('No execution data available', {
runIndex: that.runIndex,
itemIndex: that.itemIndex,
type: 'no_execution_data',
});
}
if (property === 'item') {
return that.connectionInputData[that.itemIndex];
}