From 13483497484e205975ef71091e3892f757f608e1 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 22 Jul 2022 12:19:45 +0200 Subject: [PATCH] feat: Improvements to pairedItem --- packages/cli/src/Interfaces.ts | 4 +- packages/cli/src/Server.ts | 4 +- packages/cli/src/WorkflowRunnerProcess.ts | 1 + .../src/databases/entities/WorkflowEntity.ts | 4 +- packages/cli/src/requests.d.ts | 4 +- .../test/integration/workflows.api.test.ts | 8 +- packages/core/src/WorkflowExecute.ts | 6 +- packages/editor-ui/src/Interface.ts | 15 +-- .../src/components/Error/NodeErrorView.vue | 10 +- packages/editor-ui/src/components/RunData.vue | 1 - .../src/components/VariableSelector.vue | 6 +- .../src/components/mixins/pinData.ts | 14 +-- .../src/components/mixins/workflowHelpers.ts | 19 ++-- packages/editor-ui/src/store.ts | 8 +- packages/editor-ui/src/views/NodeView.vue | 14 +-- packages/workflow/src/ExpressionError.ts | 5 + packages/workflow/src/Interfaces.ts | 7 +- packages/workflow/src/Workflow.ts | 16 +++ packages/workflow/src/WorkflowDataProxy.ts | 105 ++++++++++++------ 19 files changed, 161 insertions(+), 90 deletions(-) diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index fea69be905..e59cd84238 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -8,6 +8,7 @@ import { IDataObject, IDeferredPromise, IExecuteResponsePromiseData, + IPinData, IRun, IRunData, IRunExecutionData, @@ -15,7 +16,6 @@ import { ITelemetrySettings, ITelemetryTrackProperties, IWorkflowBase as IWorkflowBaseWorkflow, - PinData, Workflow, WorkflowExecuteMode, } from 'n8n-workflow'; @@ -689,7 +689,7 @@ export interface IWorkflowExecutionDataProcess { executionMode: WorkflowExecuteMode; executionData?: IRunExecutionData; runData?: IRunData; - pinData?: PinData; + pinData?: IPinData; retryOf?: number | string; sessionId?: string; startNodes?: string[]; diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index d74a83b21f..1ca0d81d12 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -70,11 +70,11 @@ import { INodeType, INodeTypeDescription, INodeTypeNameVersion, + IPinData, ITelemetrySettings, IWorkflowBase, LoggerProxy, NodeHelpers, - PinData, WebhookHttpMethod, Workflow, WorkflowExecuteMode, @@ -2836,7 +2836,7 @@ const TRIGGER_NODE_SUFFIXES = ['trigger', 'webhook']; const isTrigger = (str: string) => TRIGGER_NODE_SUFFIXES.some((suffix) => str.toLowerCase().includes(suffix)); -function findFirstPinnedTrigger(workflow: IWorkflowDb, pinData?: PinData) { +function findFirstPinnedTrigger(workflow: IWorkflowDb, pinData?: IPinData) { if (!pinData) return; const firstPinnedTriggerName = Object.keys(pinData).find(isTrigger); diff --git a/packages/cli/src/WorkflowRunnerProcess.ts b/packages/cli/src/WorkflowRunnerProcess.ts index 468ef57383..02e364e048 100644 --- a/packages/cli/src/WorkflowRunnerProcess.ts +++ b/packages/cli/src/WorkflowRunnerProcess.ts @@ -230,6 +230,7 @@ export class WorkflowRunnerProcess { nodeTypes, staticData: this.data.workflowData.staticData, settings: this.data.workflowData.settings, + pinData: this.data.pinData, }); await checkPermissionsForExecution(this.workflow, userId); const additionalData = await WorkflowExecuteAdditionalData.getBase( diff --git a/packages/cli/src/databases/entities/WorkflowEntity.ts b/packages/cli/src/databases/entities/WorkflowEntity.ts index 31b2b5fe70..b3e0427ee2 100644 --- a/packages/cli/src/databases/entities/WorkflowEntity.ts +++ b/packages/cli/src/databases/entities/WorkflowEntity.ts @@ -2,7 +2,7 @@ /* eslint-disable import/no-cycle */ import { Length } from 'class-validator'; -import { IConnections, IDataObject, INode, IWorkflowSettings, PinData } from 'n8n-workflow'; +import { IConnections, IDataObject, INode, IPinData, IWorkflowSettings } from 'n8n-workflow'; import { BeforeUpdate, @@ -122,7 +122,7 @@ export class WorkflowEntity implements IWorkflowDb { nullable: true, transformer: serializer, }) - pinData: PinData; + pinData: IPinData; @BeforeUpdate() setUpdateDate() { diff --git a/packages/cli/src/requests.d.ts b/packages/cli/src/requests.d.ts index d355edeab5..a6909f7e54 100644 --- a/packages/cli/src/requests.d.ts +++ b/packages/cli/src/requests.d.ts @@ -6,9 +6,9 @@ import { ICredentialNodeAccess, INode, INodeCredentialTestRequest, + IPinData, IRunData, IWorkflowSettings, - PinData, } from 'n8n-workflow'; import { User } from './databases/entities/User'; @@ -72,7 +72,7 @@ export declare namespace WorkflowRequest { { workflowData: IWorkflowDb; runData: IRunData; - pinData: PinData; + pinData: IPinData; startNodes?: string[]; destinationNode?: string; } diff --git a/packages/cli/test/integration/workflows.api.test.ts b/packages/cli/test/integration/workflows.api.test.ts index 0ff64b0655..ac49f0107e 100644 --- a/packages/cli/test/integration/workflows.api.test.ts +++ b/packages/cli/test/integration/workflows.api.test.ts @@ -4,7 +4,7 @@ import * as utils from './shared/utils'; import * as testDb from './shared/testDb'; import { WorkflowEntity } from '../../src/databases/entities/WorkflowEntity'; import type { Role } from '../../src/databases/entities/Role'; -import { PinData } from 'n8n-workflow'; +import { IPinData } from 'n8n-workflow'; jest.mock('../../src/telemetry'); @@ -44,7 +44,7 @@ test('POST /workflows should store pin data for node in workflow', async () => { expect(response.statusCode).toBe(200); - const { pinData } = response.body.data as { pinData: PinData }; + const { pinData } = response.body.data as { pinData: IPinData }; expect(pinData).toMatchObject({ Spotify: [{ myKey: 'myValue' }] }); }); @@ -59,7 +59,7 @@ test('POST /workflows should set pin data to null if no pin data', async () => { expect(response.statusCode).toBe(200); - const { pinData } = response.body.data as { pinData: PinData }; + const { pinData } = response.body.data as { pinData: IPinData }; expect(pinData).toBeNull(); }); @@ -78,7 +78,7 @@ test('GET /workflows/:id should return pin data', async () => { expect(workflowRetrievalResponse.statusCode).toBe(200); - const { pinData } = workflowRetrievalResponse.body.data as { pinData: PinData }; + const { pinData } = workflowRetrievalResponse.body.data as { pinData: IPinData }; expect(pinData).toMatchObject({ Spotify: [{ myKey: 'myValue' }] }); }); diff --git a/packages/core/src/WorkflowExecute.ts b/packages/core/src/WorkflowExecute.ts index f404891890..0c7b5712af 100644 --- a/packages/core/src/WorkflowExecute.ts +++ b/packages/core/src/WorkflowExecute.ts @@ -19,6 +19,7 @@ import { INode, INodeConnections, INodeExecutionData, + IPinData, IRun, IRunData, IRunExecutionData, @@ -32,7 +33,6 @@ import { LoggerProxy as Logger, NodeApiError, NodeOperationError, - PinData, Workflow, WorkflowExecuteMode, WorkflowOperationError, @@ -88,7 +88,7 @@ export class WorkflowExecute { workflow: Workflow, startNode?: INode, destinationNode?: string, - pinData?: PinData, + pinData?: IPinData, ): PCancelable { // Get the nodes to start workflow execution from startNode = startNode || workflow.getStartNode(destinationNode); @@ -160,7 +160,7 @@ export class WorkflowExecute { runData: IRunData, startNodes: string[], destinationNode: string, - pinData?: PinData, + pinData?: IPinData, // @ts-ignore ): PCancelable { let incomingNodeConnections: INodeConnections | undefined; diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index d9e5ae880c..917ee23457 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -14,6 +14,7 @@ import { INodePropertyOptions, INodeTypeDescription, INodeTypeNameVersion, + IPinData, IRunExecutionData, IRun, IRunData, @@ -21,15 +22,9 @@ import { ITelemetrySettings, IWorkflowSettings as IWorkflowSettingsWorkflow, WorkflowExecuteMode, - PinData, PublicInstalledPackage, } from 'n8n-workflow'; -import { - COMMUNITY_PACKAGE_MANAGE_ACTIONS, -} from './constants'; - - export * from 'n8n-design-system/src/types'; declare module 'jsplumb' { @@ -218,7 +213,7 @@ export interface IStartRunData { startNodes?: string[]; destinationNode?: string; runData?: IRunData; - pinData?: PinData; + pinData?: IPinData; } export interface IRunDataUi { @@ -253,7 +248,7 @@ export interface IWorkflowData { connections: IConnections; settings?: IWorkflowSettings; tags?: string[]; - pinData?: PinData; + pinData?: IPinData; } export interface IWorkflowDataUpdate { @@ -264,7 +259,7 @@ export interface IWorkflowDataUpdate { settings?: IWorkflowSettings; active?: boolean; tags?: ITag[] | string[]; // string[] when store or requested, ITag[] from API response - pinData?: PinData; + pinData?: IPinData; } export interface IWorkflowTemplate { @@ -287,7 +282,7 @@ export interface IWorkflowDb { connections: IConnections; settings?: IWorkflowSettings; tags?: ITag[] | string[]; // string[] when store or requested, ITag[] from API response - pinData?: PinData; + pinData?: IPinData; } // Identical to cli.Interfaces.ts diff --git a/packages/editor-ui/src/components/Error/NodeErrorView.vue b/packages/editor-ui/src/components/Error/NodeErrorView.vue index 248a505fc4..b36fdeafbf 100644 --- a/packages/editor-ui/src/components/Error/NodeErrorView.vue +++ b/packages/editor-ui/src/components/Error/NodeErrorView.vue @@ -2,7 +2,7 @@
{{ $locale.baseText('nodeErrorView.error') + ': ' + getErrorMessage() }}
-
{{error.description}}
+
{{getErrorDescription()}}
@@ -139,6 +139,14 @@ export default mixins( }, }, methods: { + getErrorDescription (): string { + if (!this.error.context || !this.error.context.descriptionTemplate) { + return this.error.description; + } + + const parameterName = this.parameterDisplayName(this.error.context.parameter); + return this.error.context.descriptionTemplate.replace(/%%PARAMETER%%/g, parameterName); + }, getErrorMessage (): string { if (!this.error.context || !this.error.context.messageTemplate) { return this.error.message; diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index f67fea6a9d..1851d79354 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -343,7 +343,6 @@ import { INodeTypeDescription, IRunData, IRunExecutionData, - PinData, } from 'n8n-workflow'; import { diff --git a/packages/editor-ui/src/components/VariableSelector.vue b/packages/editor-ui/src/components/VariableSelector.vue index ec5e53e4b5..2a16954dd9 100644 --- a/packages/editor-ui/src/components/VariableSelector.vue +++ b/packages/editor-ui/src/components/VariableSelector.vue @@ -21,10 +21,10 @@ import { IContextObject, IDataObject, INodeExecutionData, + IPinData, IRunData, IRunExecutionData, IWorkflowDataProxyAdditionalKeys, - PinData, Workflow, WorkflowDataProxy, } from 'n8n-workflow'; @@ -307,11 +307,11 @@ export default mixins( * Get the node's output using pinData * * @param {string} nodeName The name of the node to get the data of - * @param {PinData[string]} pinData The node's pin data + * @param {IPinData[string]} pinData The node's pin data * @param {string} filterText Filter text for parameters * @param {boolean} [useShort=false] Use short notation $json vs. $node[NodeName].json */ - getNodePinDataOutput(nodeName: string, pinData: PinData[string], filterText: string, useShort = false): IVariableSelectorOption[] | null { + getNodePinDataOutput(nodeName: string, pinData: IPinData[string], filterText: string, useShort = false): IVariableSelectorOption[] | null { const outputData = pinData.map((data) => ({ json: data } as INodeExecutionData))[0]; return this.getNodeOutput(nodeName, outputData, filterText, useShort); diff --git a/packages/editor-ui/src/components/mixins/pinData.ts b/packages/editor-ui/src/components/mixins/pinData.ts index d558bd7d84..34440ee25b 100644 --- a/packages/editor-ui/src/components/mixins/pinData.ts +++ b/packages/editor-ui/src/components/mixins/pinData.ts @@ -1,17 +1,17 @@ import Vue from 'vue'; -import { INodeUi } from "@/Interface"; -import {IDataObject, PinData} from "n8n-workflow"; -import {stringSizeInBytes} from "@/components/helpers"; -import {MAX_WORKFLOW_PINNED_DATA_SIZE, PIN_DATA_NODE_TYPES_DENYLIST} from "@/constants"; +import { INodeUi } from '@/Interface'; +import { IPinData } from 'n8n-workflow'; +import { stringSizeInBytes } from '@/components/helpers'; +import { MAX_WORKFLOW_PINNED_DATA_SIZE, PIN_DATA_NODE_TYPES_DENYLIST } from '@/constants'; -interface PinDataContext { +interface IPinDataContext { node: INodeUi; $showError(error: Error, title: string): void; } -export const pinData = (Vue as Vue.VueConstructor).extend({ +export const pinData = (Vue as Vue.VueConstructor).extend({ computed: { - pinData (): PinData[string] | undefined { + pinData (): IPinData[string] | undefined { return this.node ? this.$store.getters['pinDataByNodeName'](this.node!.name) : undefined; }, hasPinData (): boolean { diff --git a/packages/editor-ui/src/components/mixins/workflowHelpers.ts b/packages/editor-ui/src/components/mixins/workflowHelpers.ts index 0b1844da47..86f6503c72 100644 --- a/packages/editor-ui/src/components/mixins/workflowHelpers.ts +++ b/packages/editor-ui/src/components/mixins/workflowHelpers.ts @@ -21,6 +21,7 @@ import { INodeTypeData, INodeTypeDescription, INodeVersionedType, + IPinData, IRunData, IRunExecutionData, IWorfklowIssues, @@ -30,7 +31,6 @@ import { IExecuteData, INodeConnection, IWebhookDescription, - PinData, } from 'n8n-workflow'; import { @@ -330,11 +330,16 @@ export const workflowHelpers = mixins( const workflowName = this.$store.getters.workflowName; - if (copyData === true) { - return new Workflow({ id: workflowId, name: workflowName, nodes: JSON.parse(JSON.stringify(nodes)), connections: JSON.parse(JSON.stringify(connections)), active: false, nodeTypes, settings: this.$store.getters.workflowSettings}); - } else { - return new Workflow({ id: workflowId, name: workflowName, nodes, connections, active: false, nodeTypes, settings: this.$store.getters.workflowSettings}); - } + return new Workflow({ + id: workflowId, + name: workflowName, + nodes: copyData ? JSON.parse(JSON.stringify(nodes)) : nodes, + connections: copyData? JSON.parse(JSON.stringify(connections)): connections, + active: false, + nodeTypes, + settings: this.$store.getters.workflowSettings, + pinData: this.$store.getters.pinData, + }); }, // Returns the currently loaded workflow as JSON. @@ -526,7 +531,7 @@ export const workflowHelpers = mixins( } parentNode.forEach((parentNodeName) => { - const pinData: PinData[string] = this.$store.getters['pinDataByNodeName'](parentNodeName); + const pinData: IPinData[string] = this.$store.getters['pinDataByNodeName'](parentNodeName); if (pinData) { runExecutionData = { diff --git a/packages/editor-ui/src/store.ts b/packages/editor-ui/src/store.ts index 6bf8ba14d9..db8da97750 100644 --- a/packages/editor-ui/src/store.ts +++ b/packages/editor-ui/src/store.ts @@ -13,10 +13,10 @@ import { INodeConnections, INodeIssueData, INodeTypeDescription, + IPinData, IRunData, ITaskData, IWorkflowSettings, - PinData, } from 'n8n-workflow'; import { @@ -213,7 +213,7 @@ export const store = new Vuex.Store({ }, // Pin data - pinData(state, payload: { node: INodeUi, data: PinData[string] }) { + pinData(state, payload: { node: INodeUi, data: IPinData[string] }) { if (state.workflow.pinData) { Vue.set(state.workflow.pinData, payload.node.name, payload.data); } @@ -651,7 +651,7 @@ export const store = new Vuex.Store({ Vue.set(state.workflow, 'settings', workflowSettings); }, - setWorkflowPinData(state, pinData: PinData) { + setWorkflowPinData(state, pinData: IPinData) { Vue.set(state.workflow, 'pinData', pinData); dataPinningEventBus.$emit('pin-data', pinData); @@ -905,7 +905,7 @@ export const store = new Vuex.Store({ * Pin data */ - pinData: (state): PinData | undefined => { + pinData: (state): IPinData | undefined => { return state.workflow.pinData; }, pinDataByNodeName: (state) => (nodeName: string) => { diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index f7983cd9e0..a013387d91 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -184,18 +184,18 @@ import { IDataObject, INode, INodeConnections, + INodeCredentialsDetails, INodeIssues, INodeTypeDescription, INodeTypeNameVersion, - NodeHelpers, - Workflow, + IPinData, IRun, ITaskData, - INodeCredentialsDetails, - TelemetryHelpers, ITelemetryTrackProperties, IWorkflowBase, - PinData, + NodeHelpers, + TelemetryHelpers, + Workflow, } from 'n8n-workflow'; import { ICredentialsResponse, @@ -2963,7 +2963,7 @@ export default mixins( await this.importWorkflowData(workflowData); } }, - addPinDataConnections(pinData: PinData) { + addPinDataConnections(pinData: IPinData) { Object.keys(pinData).forEach((nodeName) => { // @ts-ignore const connections = this.instance.getConnections({ @@ -2978,7 +2978,7 @@ export default mixins( }); }); }, - removePinDataConnections(pinData: PinData) { + removePinDataConnections(pinData: IPinData) { Object.keys(pinData).forEach((nodeName) => { // @ts-ignore const connections = this.instance.getConnections({ diff --git a/packages/workflow/src/ExpressionError.ts b/packages/workflow/src/ExpressionError.ts index 310934450d..413a7e22f9 100644 --- a/packages/workflow/src/ExpressionError.ts +++ b/packages/workflow/src/ExpressionError.ts @@ -10,6 +10,7 @@ export class ExpressionError extends ExecutionBaseError { options?: { causeDetailed?: string; description?: string; + descriptionTemplate?: string; runIndex?: number; itemIndex?: number; messageTemplate?: string; @@ -23,6 +24,10 @@ export class ExpressionError extends ExecutionBaseError { this.description = options.description; } + if (options?.descriptionTemplate !== undefined) { + this.context.descriptionTemplate = options.descriptionTemplate; + } + if (options?.causeDetailed !== undefined) { this.context.causeDetailed = options.causeDetailed; } diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index fc9c791f91..6f5fc6c71f 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -839,10 +839,9 @@ export interface INode { parameters: INodeParameters; credentials?: INodeCredentials; webhookId?: string; - pinData?: IDataObject; } -export interface PinData { +export interface IPinData { [nodeName: string]: IDataObject[]; } @@ -1328,7 +1327,7 @@ export interface IRunExecutionData { resultData: { error?: ExecutionError; runData: IRunData; - pinData?: PinData; + pinData?: IPinData; lastNodeExecuted?: string; }; executionData?: { @@ -1402,7 +1401,7 @@ export interface IWorkflowBase { connections: IConnections; settings?: IWorkflowSettings; staticData?: IDataObject; - pinData?: PinData; + pinData?: IPinData; } export interface IWorkflowCredentials { diff --git a/packages/workflow/src/Workflow.ts b/packages/workflow/src/Workflow.ts index 508f8b2598..d5f56a623b 100644 --- a/packages/workflow/src/Workflow.ts +++ b/packages/workflow/src/Workflow.ts @@ -28,6 +28,7 @@ import { INodes, INodeType, INodeTypes, + IPinData, IPollFunctions, IRunExecutionData, ITaskDataConnections, @@ -84,6 +85,8 @@ export class Workflow { // ids of registred webhooks of nodes staticData: IDataObject; + pinData?: IPinData; + // constructor(id: string | undefined, nodes: INode[], connections: IConnections, active: boolean, nodeTypes: INodeTypes, staticData?: IDataObject, settings?: IWorkflowSettings) { constructor(parameters: { id?: string; @@ -94,10 +97,12 @@ export class Workflow { nodeTypes: INodeTypes; staticData?: IDataObject; settings?: IWorkflowSettings; + pinData?: IPinData; }) { this.id = parameters.id; this.name = parameters.name; this.nodeTypes = parameters.nodeTypes; + this.pinData = parameters.pinData; // Save nodes in workflow as object to be able to get the // nodes easily by its name. @@ -410,6 +415,17 @@ export class Workflow { return null; } + /** + * Returns the pinData of the node with the given name if it exists + * + * @param {string} nodeName Name of the node to return the pinData of + * @returns {(IDataObject[] | undefined)} + * @memberof Workflow + */ + getPinDataOfNode(nodeName: string): IDataObject[] | undefined { + return this.pinData ? this.pinData[nodeName] : undefined; + } + /** * Renames nodes in expressions * diff --git a/packages/workflow/src/WorkflowDataProxy.ts b/packages/workflow/src/WorkflowDataProxy.ts index bf24381561..a1be9771d1 100644 --- a/packages/workflow/src/WorkflowDataProxy.ts +++ b/packages/workflow/src/WorkflowDataProxy.ts @@ -532,11 +532,27 @@ export class WorkflowDataProxy { const createExpressionError = ( message: string, context?: { - messageTemplate?: string; - description?: string; causeDetailed?: string; + description?: string; + descriptionTemplate?: string; + messageTemplate?: string; }, + nodeName?: string, ) => { + if (nodeName) { + const pinData = this.workflow.getPinDataOfNode(nodeName); + + if (pinData) { + if (!context) { + context = {}; + } + message = `‘${nodeName}‘ must be unpinned to execute`; + context.description = `To fetch the data the expression needs, The node ‘${nodeName}’ needs to execute without being pinned. Unpin it`; + context.description = `To fetch the data for the expression, you must unpin the node '${nodeName}' and execute the workflow again.`; + context.descriptionTemplate = `To fetch the data for the expression under '%%PARAMETER%%', you must unpin the node '${nodeName}' and execute the workflow again.`; + } + } + return new ExpressionError(message, { runIndex: that.runIndex, itemIndex: that.itemIndex, @@ -560,6 +576,7 @@ export class WorkflowDataProxy { }; } + let nodeBeforeLast: string | undefined; while (sourceData !== null && destinationNodeName !== sourceData.previousNode) { taskData = that.runExecutionData!.resultData.runData[sourceData.previousNode][ @@ -569,20 +586,29 @@ export class WorkflowDataProxy { const previousNodeOutput = sourceData.previousNodeOutput || 0; if (previousNodeOutput >= taskData.data!.main.length) { // `Could not resolve as the defined node-output is not valid on node '${sourceData.previousNode}'.` - throw createExpressionError('Can’t get data for expression', { - messageTemplate: 'Can’t get data for expression under ‘%%PARAMETER%%’', - description: `Apologies, this is an internal error. See details for more information`, - causeDetailed: 'Referencing a non-existent output on a node, problem with source data', - }); + throw createExpressionError( + 'Can’t get data for expression', + { + messageTemplate: 'Can’t get data for expression under ‘%%PARAMETER%%’', + description: `Apologies, this is an internal error. See details for more information`, + causeDetailed: + 'Referencing a non-existent output on a node, problem with source data', + }, + nodeBeforeLast, + ); } if (pairedItem.item >= taskData.data!.main[previousNodeOutput]!.length) { // `Could not resolve as the defined item index is not valid on node '${sourceData.previousNode}'. - throw createExpressionError('Can’t get data for expression', { - messageTemplate: `Can’t get data for expression under ‘%%PARAMETER%%’`, - description: `Item points to an item which does not exist`, - causeDetailed: `The pairedItem data points to an item ‘${pairedItem.item}‘ which does not exist on node ‘${sourceData.previousNode}‘ (output node did probably supply a wrong one)`, - }); + throw createExpressionError( + 'Can’t get data for expression', + { + messageTemplate: `Can’t get data for expression under ‘%%PARAMETER%%’`, + description: `Item points to an item which does not exist`, + causeDetailed: `The pairedItem data points to an item ‘${pairedItem.item}‘ which does not exist on node ‘${sourceData.previousNode}‘ (output node did probably supply a wrong one)`, + }, + nodeBeforeLast, + ); } const itemPreviousNode: INodeExecutionData = @@ -590,11 +616,15 @@ export class WorkflowDataProxy { if (itemPreviousNode.pairedItem === undefined) { // `Could not resolve, as pairedItem data is missing on node '${sourceData.previousNode}'.`, - throw createExpressionError('Can’t get data for expression', { - messageTemplate: `Can’t get data for expression under ‘%%PARAMETER%%’`, - description: `To fetch the data from other nodes that this expression needs, more information is needed from the node ‘${sourceData.previousNode}’`, - causeDetailed: `Missing pairedItem data (node ‘${sourceData.previousNode}’ did probably not supply it)`, - }); + throw createExpressionError( + 'Can’t get data for expression', + { + messageTemplate: `Can’t get data for expression under ‘%%PARAMETER%%’`, + description: `To fetch the data from other nodes that this expression needs, more information is needed from the node ‘${sourceData.previousNode}’`, + causeDetailed: `Missing pairedItem data (node ‘${sourceData.previousNode}’ did probably not supply it)`, + }, + sourceData.previousNode, + ); } if (Array.isArray(itemPreviousNode.pairedItem)) { @@ -647,22 +677,31 @@ export class WorkflowDataProxy { }); } // `Could not resolve pairedItem as the defined node input '${itemInput}' does not exist on node '${sourceData.previousNode}'.` - throw createExpressionError('Can’t get data for expression', { - messageTemplate: `Can’t get data for expression under ‘%%PARAMETER%%’`, - description: `Item points to a node input which does not exist`, - causeDetailed: `The pairedItem data points to a node input ‘${itemInput}‘ which does not exist on node ‘${sourceData.previousNode}‘ (node did probably supply a wrong one)`, - }); + throw createExpressionError( + 'Can’t get data for expression', + { + messageTemplate: `Can’t get data for expression under ‘%%PARAMETER%%’`, + description: `Item points to a node input which does not exist`, + causeDetailed: `The pairedItem data points to a node input ‘${itemInput}‘ which does not exist on node ‘${sourceData.previousNode}‘ (node did probably supply a wrong one)`, + }, + nodeBeforeLast, + ); } + nodeBeforeLast = sourceData.previousNode; sourceData = taskData.source[pairedItem.input || 0] || null; } if (sourceData === null) { // 'Could not resolve, proably no pairedItem exists.' - throw createExpressionError('Can’t get data for expression', { - messageTemplate: `Can’t get data for expression under ‘%%PARAMETER%%’`, - description: `Could not resolve, proably no pairedItem exists`, - }); + throw createExpressionError( + 'Can’t get data for expression', + { + messageTemplate: `Can’t get data for expression under ‘%%PARAMETER%%’`, + description: `Could not resolve, proably no pairedItem exists`, + }, + nodeBeforeLast, + ); } taskData = @@ -682,11 +721,15 @@ export class WorkflowDataProxy { if (pairedItem.item >= taskData.data!.main[previousNodeOutput]!.length) { // `Could not resolve pairedItem as the item with the index '${pairedItem.item}' does not exist on node '${sourceData.previousNode}'.` - throw createExpressionError('Can’t get data for expression', { - messageTemplate: `Can’t get data for expression under ‘%%PARAMETER%%’`, - description: `Item points to an item which does not exist`, - causeDetailed: `The pairedItem data points to an item ‘${pairedItem.item}‘ which does not exist on node ‘${sourceData.previousNode}‘ (output node did probably supply a wrong one)`, - }); + throw createExpressionError( + 'Can’t get data for expression', + { + messageTemplate: `Can’t get data for expression under ‘%%PARAMETER%%’`, + description: `Item points to an item which does not exist`, + causeDetailed: `The pairedItem data points to an item ‘${pairedItem.item}‘ which does not exist on node ‘${sourceData.previousNode}‘ (output node did probably supply a wrong one)`, + }, + nodeBeforeLast, + ); } return taskData.data!.main[previousNodeOutput]![pairedItem.item];