feat: Improvements to pairedItem

This commit is contained in:
Jan Oberhauser 2022-07-22 12:19:45 +02:00
parent 2f4f2cfb86
commit 1348349748
19 changed files with 161 additions and 90 deletions

View file

@ -8,6 +8,7 @@ import {
IDataObject, IDataObject,
IDeferredPromise, IDeferredPromise,
IExecuteResponsePromiseData, IExecuteResponsePromiseData,
IPinData,
IRun, IRun,
IRunData, IRunData,
IRunExecutionData, IRunExecutionData,
@ -15,7 +16,6 @@ import {
ITelemetrySettings, ITelemetrySettings,
ITelemetryTrackProperties, ITelemetryTrackProperties,
IWorkflowBase as IWorkflowBaseWorkflow, IWorkflowBase as IWorkflowBaseWorkflow,
PinData,
Workflow, Workflow,
WorkflowExecuteMode, WorkflowExecuteMode,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -689,7 +689,7 @@ export interface IWorkflowExecutionDataProcess {
executionMode: WorkflowExecuteMode; executionMode: WorkflowExecuteMode;
executionData?: IRunExecutionData; executionData?: IRunExecutionData;
runData?: IRunData; runData?: IRunData;
pinData?: PinData; pinData?: IPinData;
retryOf?: number | string; retryOf?: number | string;
sessionId?: string; sessionId?: string;
startNodes?: string[]; startNodes?: string[];

View file

@ -70,11 +70,11 @@ import {
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
INodeTypeNameVersion, INodeTypeNameVersion,
IPinData,
ITelemetrySettings, ITelemetrySettings,
IWorkflowBase, IWorkflowBase,
LoggerProxy, LoggerProxy,
NodeHelpers, NodeHelpers,
PinData,
WebhookHttpMethod, WebhookHttpMethod,
Workflow, Workflow,
WorkflowExecuteMode, WorkflowExecuteMode,
@ -2836,7 +2836,7 @@ const TRIGGER_NODE_SUFFIXES = ['trigger', 'webhook'];
const isTrigger = (str: string) => const isTrigger = (str: string) =>
TRIGGER_NODE_SUFFIXES.some((suffix) => str.toLowerCase().includes(suffix)); TRIGGER_NODE_SUFFIXES.some((suffix) => str.toLowerCase().includes(suffix));
function findFirstPinnedTrigger(workflow: IWorkflowDb, pinData?: PinData) { function findFirstPinnedTrigger(workflow: IWorkflowDb, pinData?: IPinData) {
if (!pinData) return; if (!pinData) return;
const firstPinnedTriggerName = Object.keys(pinData).find(isTrigger); const firstPinnedTriggerName = Object.keys(pinData).find(isTrigger);

View file

@ -230,6 +230,7 @@ export class WorkflowRunnerProcess {
nodeTypes, nodeTypes,
staticData: this.data.workflowData.staticData, staticData: this.data.workflowData.staticData,
settings: this.data.workflowData.settings, settings: this.data.workflowData.settings,
pinData: this.data.pinData,
}); });
await checkPermissionsForExecution(this.workflow, userId); await checkPermissionsForExecution(this.workflow, userId);
const additionalData = await WorkflowExecuteAdditionalData.getBase( const additionalData = await WorkflowExecuteAdditionalData.getBase(

View file

@ -2,7 +2,7 @@
/* eslint-disable import/no-cycle */ /* eslint-disable import/no-cycle */
import { Length } from 'class-validator'; import { Length } from 'class-validator';
import { IConnections, IDataObject, INode, IWorkflowSettings, PinData } from 'n8n-workflow'; import { IConnections, IDataObject, INode, IPinData, IWorkflowSettings } from 'n8n-workflow';
import { import {
BeforeUpdate, BeforeUpdate,
@ -122,7 +122,7 @@ export class WorkflowEntity implements IWorkflowDb {
nullable: true, nullable: true,
transformer: serializer, transformer: serializer,
}) })
pinData: PinData; pinData: IPinData;
@BeforeUpdate() @BeforeUpdate()
setUpdateDate() { setUpdateDate() {

View file

@ -6,9 +6,9 @@ import {
ICredentialNodeAccess, ICredentialNodeAccess,
INode, INode,
INodeCredentialTestRequest, INodeCredentialTestRequest,
IPinData,
IRunData, IRunData,
IWorkflowSettings, IWorkflowSettings,
PinData,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { User } from './databases/entities/User'; import { User } from './databases/entities/User';
@ -72,7 +72,7 @@ export declare namespace WorkflowRequest {
{ {
workflowData: IWorkflowDb; workflowData: IWorkflowDb;
runData: IRunData; runData: IRunData;
pinData: PinData; pinData: IPinData;
startNodes?: string[]; startNodes?: string[];
destinationNode?: string; destinationNode?: string;
} }

View file

@ -4,7 +4,7 @@ import * as utils from './shared/utils';
import * as testDb from './shared/testDb'; import * as testDb from './shared/testDb';
import { WorkflowEntity } from '../../src/databases/entities/WorkflowEntity'; import { WorkflowEntity } from '../../src/databases/entities/WorkflowEntity';
import type { Role } from '../../src/databases/entities/Role'; import type { Role } from '../../src/databases/entities/Role';
import { PinData } from 'n8n-workflow'; import { IPinData } from 'n8n-workflow';
jest.mock('../../src/telemetry'); 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); 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' }] }); 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); expect(response.statusCode).toBe(200);
const { pinData } = response.body.data as { pinData: PinData }; const { pinData } = response.body.data as { pinData: IPinData };
expect(pinData).toBeNull(); expect(pinData).toBeNull();
}); });
@ -78,7 +78,7 @@ test('GET /workflows/:id should return pin data', async () => {
expect(workflowRetrievalResponse.statusCode).toBe(200); 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' }] }); expect(pinData).toMatchObject({ Spotify: [{ myKey: 'myValue' }] });
}); });

View file

@ -19,6 +19,7 @@ import {
INode, INode,
INodeConnections, INodeConnections,
INodeExecutionData, INodeExecutionData,
IPinData,
IRun, IRun,
IRunData, IRunData,
IRunExecutionData, IRunExecutionData,
@ -32,7 +33,6 @@ import {
LoggerProxy as Logger, LoggerProxy as Logger,
NodeApiError, NodeApiError,
NodeOperationError, NodeOperationError,
PinData,
Workflow, Workflow,
WorkflowExecuteMode, WorkflowExecuteMode,
WorkflowOperationError, WorkflowOperationError,
@ -88,7 +88,7 @@ export class WorkflowExecute {
workflow: Workflow, workflow: Workflow,
startNode?: INode, startNode?: INode,
destinationNode?: string, destinationNode?: string,
pinData?: PinData, pinData?: IPinData,
): PCancelable<IRun> { ): PCancelable<IRun> {
// Get the nodes to start workflow execution from // Get the nodes to start workflow execution from
startNode = startNode || workflow.getStartNode(destinationNode); startNode = startNode || workflow.getStartNode(destinationNode);
@ -160,7 +160,7 @@ export class WorkflowExecute {
runData: IRunData, runData: IRunData,
startNodes: string[], startNodes: string[],
destinationNode: string, destinationNode: string,
pinData?: PinData, pinData?: IPinData,
// @ts-ignore // @ts-ignore
): PCancelable<IRun> { ): PCancelable<IRun> {
let incomingNodeConnections: INodeConnections | undefined; let incomingNodeConnections: INodeConnections | undefined;

View file

@ -14,6 +14,7 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeTypeDescription, INodeTypeDescription,
INodeTypeNameVersion, INodeTypeNameVersion,
IPinData,
IRunExecutionData, IRunExecutionData,
IRun, IRun,
IRunData, IRunData,
@ -21,15 +22,9 @@ import {
ITelemetrySettings, ITelemetrySettings,
IWorkflowSettings as IWorkflowSettingsWorkflow, IWorkflowSettings as IWorkflowSettingsWorkflow,
WorkflowExecuteMode, WorkflowExecuteMode,
PinData,
PublicInstalledPackage, PublicInstalledPackage,
} from 'n8n-workflow'; } from 'n8n-workflow';
import {
COMMUNITY_PACKAGE_MANAGE_ACTIONS,
} from './constants';
export * from 'n8n-design-system/src/types'; export * from 'n8n-design-system/src/types';
declare module 'jsplumb' { declare module 'jsplumb' {
@ -218,7 +213,7 @@ export interface IStartRunData {
startNodes?: string[]; startNodes?: string[];
destinationNode?: string; destinationNode?: string;
runData?: IRunData; runData?: IRunData;
pinData?: PinData; pinData?: IPinData;
} }
export interface IRunDataUi { export interface IRunDataUi {
@ -253,7 +248,7 @@ export interface IWorkflowData {
connections: IConnections; connections: IConnections;
settings?: IWorkflowSettings; settings?: IWorkflowSettings;
tags?: string[]; tags?: string[];
pinData?: PinData; pinData?: IPinData;
} }
export interface IWorkflowDataUpdate { export interface IWorkflowDataUpdate {
@ -264,7 +259,7 @@ export interface IWorkflowDataUpdate {
settings?: IWorkflowSettings; settings?: IWorkflowSettings;
active?: boolean; active?: boolean;
tags?: ITag[] | string[]; // string[] when store or requested, ITag[] from API response tags?: ITag[] | string[]; // string[] when store or requested, ITag[] from API response
pinData?: PinData; pinData?: IPinData;
} }
export interface IWorkflowTemplate { export interface IWorkflowTemplate {
@ -287,7 +282,7 @@ export interface IWorkflowDb {
connections: IConnections; connections: IConnections;
settings?: IWorkflowSettings; settings?: IWorkflowSettings;
tags?: ITag[] | string[]; // string[] when store or requested, ITag[] from API response tags?: ITag[] | string[]; // string[] when store or requested, ITag[] from API response
pinData?: PinData; pinData?: IPinData;
} }
// Identical to cli.Interfaces.ts // Identical to cli.Interfaces.ts

View file

@ -2,7 +2,7 @@
<div> <div>
<div class="error-header"> <div class="error-header">
<div class="error-message">{{ $locale.baseText('nodeErrorView.error') + ': ' + getErrorMessage() }}</div> <div class="error-message">{{ $locale.baseText('nodeErrorView.error') + ': ' + getErrorMessage() }}</div>
<div class="error-description" v-if="error.description">{{error.description}}</div> <div class="error-description" v-if="error.description">{{getErrorDescription()}}</div>
</div> </div>
<details> <details>
<summary class="error-details__summary"> <summary class="error-details__summary">
@ -139,6 +139,14 @@ export default mixins(
}, },
}, },
methods: { 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 { getErrorMessage (): string {
if (!this.error.context || !this.error.context.messageTemplate) { if (!this.error.context || !this.error.context.messageTemplate) {
return this.error.message; return this.error.message;

View file

@ -343,7 +343,6 @@ import {
INodeTypeDescription, INodeTypeDescription,
IRunData, IRunData,
IRunExecutionData, IRunExecutionData,
PinData,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {

View file

@ -21,10 +21,10 @@ import {
IContextObject, IContextObject,
IDataObject, IDataObject,
INodeExecutionData, INodeExecutionData,
IPinData,
IRunData, IRunData,
IRunExecutionData, IRunExecutionData,
IWorkflowDataProxyAdditionalKeys, IWorkflowDataProxyAdditionalKeys,
PinData,
Workflow, Workflow,
WorkflowDataProxy, WorkflowDataProxy,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -307,11 +307,11 @@ export default mixins(
* Get the node's output using pinData * Get the node's output using pinData
* *
* @param {string} nodeName The name of the node to get the data of * @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 {string} filterText Filter text for parameters
* @param {boolean} [useShort=false] Use short notation $json vs. $node[NodeName].json * @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]; const outputData = pinData.map((data) => ({ json: data } as INodeExecutionData))[0];
return this.getNodeOutput(nodeName, outputData, filterText, useShort); return this.getNodeOutput(nodeName, outputData, filterText, useShort);

View file

@ -1,17 +1,17 @@
import Vue from 'vue'; import Vue from 'vue';
import { INodeUi } from "@/Interface"; import { INodeUi } from '@/Interface';
import {IDataObject, PinData} from "n8n-workflow"; import { IPinData } from 'n8n-workflow';
import {stringSizeInBytes} from "@/components/helpers"; import { stringSizeInBytes } from '@/components/helpers';
import {MAX_WORKFLOW_PINNED_DATA_SIZE, PIN_DATA_NODE_TYPES_DENYLIST} from "@/constants"; import { MAX_WORKFLOW_PINNED_DATA_SIZE, PIN_DATA_NODE_TYPES_DENYLIST } from '@/constants';
interface PinDataContext { interface IPinDataContext {
node: INodeUi; node: INodeUi;
$showError(error: Error, title: string): void; $showError(error: Error, title: string): void;
} }
export const pinData = (Vue as Vue.VueConstructor<Vue & PinDataContext>).extend({ export const pinData = (Vue as Vue.VueConstructor<Vue & IPinDataContext>).extend({
computed: { computed: {
pinData (): PinData[string] | undefined { pinData (): IPinData[string] | undefined {
return this.node ? this.$store.getters['pinDataByNodeName'](this.node!.name) : undefined; return this.node ? this.$store.getters['pinDataByNodeName'](this.node!.name) : undefined;
}, },
hasPinData (): boolean { hasPinData (): boolean {

View file

@ -21,6 +21,7 @@ import {
INodeTypeData, INodeTypeData,
INodeTypeDescription, INodeTypeDescription,
INodeVersionedType, INodeVersionedType,
IPinData,
IRunData, IRunData,
IRunExecutionData, IRunExecutionData,
IWorfklowIssues, IWorfklowIssues,
@ -30,7 +31,6 @@ import {
IExecuteData, IExecuteData,
INodeConnection, INodeConnection,
IWebhookDescription, IWebhookDescription,
PinData,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -330,11 +330,16 @@ export const workflowHelpers = mixins(
const workflowName = this.$store.getters.workflowName; const workflowName = this.$store.getters.workflowName;
if (copyData === true) { return new Workflow({
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}); id: workflowId,
} else { name: workflowName,
return new Workflow({ id: workflowId, name: workflowName, nodes, connections, active: false, nodeTypes, settings: this.$store.getters.workflowSettings}); 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. // Returns the currently loaded workflow as JSON.
@ -526,7 +531,7 @@ export const workflowHelpers = mixins(
} }
parentNode.forEach((parentNodeName) => { parentNode.forEach((parentNodeName) => {
const pinData: PinData[string] = this.$store.getters['pinDataByNodeName'](parentNodeName); const pinData: IPinData[string] = this.$store.getters['pinDataByNodeName'](parentNodeName);
if (pinData) { if (pinData) {
runExecutionData = { runExecutionData = {

View file

@ -13,10 +13,10 @@ import {
INodeConnections, INodeConnections,
INodeIssueData, INodeIssueData,
INodeTypeDescription, INodeTypeDescription,
IPinData,
IRunData, IRunData,
ITaskData, ITaskData,
IWorkflowSettings, IWorkflowSettings,
PinData,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -213,7 +213,7 @@ export const store = new Vuex.Store({
}, },
// Pin data // Pin data
pinData(state, payload: { node: INodeUi, data: PinData[string] }) { pinData(state, payload: { node: INodeUi, data: IPinData[string] }) {
if (state.workflow.pinData) { if (state.workflow.pinData) {
Vue.set(state.workflow.pinData, payload.node.name, payload.data); 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); Vue.set(state.workflow, 'settings', workflowSettings);
}, },
setWorkflowPinData(state, pinData: PinData) { setWorkflowPinData(state, pinData: IPinData) {
Vue.set(state.workflow, 'pinData', pinData); Vue.set(state.workflow, 'pinData', pinData);
dataPinningEventBus.$emit('pin-data', pinData); dataPinningEventBus.$emit('pin-data', pinData);
@ -905,7 +905,7 @@ export const store = new Vuex.Store({
* Pin data * Pin data
*/ */
pinData: (state): PinData | undefined => { pinData: (state): IPinData | undefined => {
return state.workflow.pinData; return state.workflow.pinData;
}, },
pinDataByNodeName: (state) => (nodeName: string) => { pinDataByNodeName: (state) => (nodeName: string) => {

View file

@ -184,18 +184,18 @@ import {
IDataObject, IDataObject,
INode, INode,
INodeConnections, INodeConnections,
INodeCredentialsDetails,
INodeIssues, INodeIssues,
INodeTypeDescription, INodeTypeDescription,
INodeTypeNameVersion, INodeTypeNameVersion,
NodeHelpers, IPinData,
Workflow,
IRun, IRun,
ITaskData, ITaskData,
INodeCredentialsDetails,
TelemetryHelpers,
ITelemetryTrackProperties, ITelemetryTrackProperties,
IWorkflowBase, IWorkflowBase,
PinData, NodeHelpers,
TelemetryHelpers,
Workflow,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
ICredentialsResponse, ICredentialsResponse,
@ -2963,7 +2963,7 @@ export default mixins(
await this.importWorkflowData(workflowData); await this.importWorkflowData(workflowData);
} }
}, },
addPinDataConnections(pinData: PinData) { addPinDataConnections(pinData: IPinData) {
Object.keys(pinData).forEach((nodeName) => { Object.keys(pinData).forEach((nodeName) => {
// @ts-ignore // @ts-ignore
const connections = this.instance.getConnections({ const connections = this.instance.getConnections({
@ -2978,7 +2978,7 @@ export default mixins(
}); });
}); });
}, },
removePinDataConnections(pinData: PinData) { removePinDataConnections(pinData: IPinData) {
Object.keys(pinData).forEach((nodeName) => { Object.keys(pinData).forEach((nodeName) => {
// @ts-ignore // @ts-ignore
const connections = this.instance.getConnections({ const connections = this.instance.getConnections({

View file

@ -10,6 +10,7 @@ export class ExpressionError extends ExecutionBaseError {
options?: { options?: {
causeDetailed?: string; causeDetailed?: string;
description?: string; description?: string;
descriptionTemplate?: string;
runIndex?: number; runIndex?: number;
itemIndex?: number; itemIndex?: number;
messageTemplate?: string; messageTemplate?: string;
@ -23,6 +24,10 @@ export class ExpressionError extends ExecutionBaseError {
this.description = options.description; this.description = options.description;
} }
if (options?.descriptionTemplate !== undefined) {
this.context.descriptionTemplate = options.descriptionTemplate;
}
if (options?.causeDetailed !== undefined) { if (options?.causeDetailed !== undefined) {
this.context.causeDetailed = options.causeDetailed; this.context.causeDetailed = options.causeDetailed;
} }

View file

@ -839,10 +839,9 @@ export interface INode {
parameters: INodeParameters; parameters: INodeParameters;
credentials?: INodeCredentials; credentials?: INodeCredentials;
webhookId?: string; webhookId?: string;
pinData?: IDataObject;
} }
export interface PinData { export interface IPinData {
[nodeName: string]: IDataObject[]; [nodeName: string]: IDataObject[];
} }
@ -1328,7 +1327,7 @@ export interface IRunExecutionData {
resultData: { resultData: {
error?: ExecutionError; error?: ExecutionError;
runData: IRunData; runData: IRunData;
pinData?: PinData; pinData?: IPinData;
lastNodeExecuted?: string; lastNodeExecuted?: string;
}; };
executionData?: { executionData?: {
@ -1402,7 +1401,7 @@ export interface IWorkflowBase {
connections: IConnections; connections: IConnections;
settings?: IWorkflowSettings; settings?: IWorkflowSettings;
staticData?: IDataObject; staticData?: IDataObject;
pinData?: PinData; pinData?: IPinData;
} }
export interface IWorkflowCredentials { export interface IWorkflowCredentials {

View file

@ -28,6 +28,7 @@ import {
INodes, INodes,
INodeType, INodeType,
INodeTypes, INodeTypes,
IPinData,
IPollFunctions, IPollFunctions,
IRunExecutionData, IRunExecutionData,
ITaskDataConnections, ITaskDataConnections,
@ -84,6 +85,8 @@ export class Workflow {
// ids of registred webhooks of nodes // ids of registred webhooks of nodes
staticData: IDataObject; staticData: IDataObject;
pinData?: IPinData;
// constructor(id: string | undefined, nodes: INode[], connections: IConnections, active: boolean, nodeTypes: INodeTypes, staticData?: IDataObject, settings?: IWorkflowSettings) { // constructor(id: string | undefined, nodes: INode[], connections: IConnections, active: boolean, nodeTypes: INodeTypes, staticData?: IDataObject, settings?: IWorkflowSettings) {
constructor(parameters: { constructor(parameters: {
id?: string; id?: string;
@ -94,10 +97,12 @@ export class Workflow {
nodeTypes: INodeTypes; nodeTypes: INodeTypes;
staticData?: IDataObject; staticData?: IDataObject;
settings?: IWorkflowSettings; settings?: IWorkflowSettings;
pinData?: IPinData;
}) { }) {
this.id = parameters.id; this.id = parameters.id;
this.name = parameters.name; this.name = parameters.name;
this.nodeTypes = parameters.nodeTypes; this.nodeTypes = parameters.nodeTypes;
this.pinData = parameters.pinData;
// Save nodes in workflow as object to be able to get the // Save nodes in workflow as object to be able to get the
// nodes easily by its name. // nodes easily by its name.
@ -410,6 +415,17 @@ export class Workflow {
return null; 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 * Renames nodes in expressions
* *

View file

@ -532,11 +532,27 @@ export class WorkflowDataProxy {
const createExpressionError = ( const createExpressionError = (
message: string, message: string,
context?: { context?: {
messageTemplate?: string;
description?: string;
causeDetailed?: 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. <a>Unpin it</a>`;
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, { return new ExpressionError(message, {
runIndex: that.runIndex, runIndex: that.runIndex,
itemIndex: that.itemIndex, itemIndex: that.itemIndex,
@ -560,6 +576,7 @@ export class WorkflowDataProxy {
}; };
} }
let nodeBeforeLast: string | undefined;
while (sourceData !== null && destinationNodeName !== sourceData.previousNode) { while (sourceData !== null && destinationNodeName !== sourceData.previousNode) {
taskData = taskData =
that.runExecutionData!.resultData.runData[sourceData.previousNode][ that.runExecutionData!.resultData.runData[sourceData.previousNode][
@ -569,20 +586,29 @@ export class WorkflowDataProxy {
const previousNodeOutput = sourceData.previousNodeOutput || 0; const previousNodeOutput = sourceData.previousNodeOutput || 0;
if (previousNodeOutput >= taskData.data!.main.length) { if (previousNodeOutput >= taskData.data!.main.length) {
// `Could not resolve as the defined node-output is not valid on node '${sourceData.previousNode}'.` // `Could not resolve as the defined node-output is not valid on node '${sourceData.previousNode}'.`
throw createExpressionError('Cant get data for expression', { throw createExpressionError(
messageTemplate: 'Cant get data for expression under %%PARAMETER%%', 'Cant get data for expression',
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', messageTemplate: 'Cant 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) { if (pairedItem.item >= taskData.data!.main[previousNodeOutput]!.length) {
// `Could not resolve as the defined item index is not valid on node '${sourceData.previousNode}'. // `Could not resolve as the defined item index is not valid on node '${sourceData.previousNode}'.
throw createExpressionError('Cant get data for expression', { throw createExpressionError(
messageTemplate: `Cant get data for expression under %%PARAMETER%%`, 'Cant get data for expression',
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)`, messageTemplate: `Cant 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 = const itemPreviousNode: INodeExecutionData =
@ -590,11 +616,15 @@ export class WorkflowDataProxy {
if (itemPreviousNode.pairedItem === undefined) { if (itemPreviousNode.pairedItem === undefined) {
// `Could not resolve, as pairedItem data is missing on node '${sourceData.previousNode}'.`, // `Could not resolve, as pairedItem data is missing on node '${sourceData.previousNode}'.`,
throw createExpressionError('Cant get data for expression', { throw createExpressionError(
messageTemplate: `Cant get data for expression under %%PARAMETER%%`, 'Cant get data for expression',
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)`, messageTemplate: `Cant 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)) { 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}'.` // `Could not resolve pairedItem as the defined node input '${itemInput}' does not exist on node '${sourceData.previousNode}'.`
throw createExpressionError('Cant get data for expression', { throw createExpressionError(
messageTemplate: `Cant get data for expression under %%PARAMETER%%`, 'Cant get data for expression',
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)`, messageTemplate: `Cant 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; sourceData = taskData.source[pairedItem.input || 0] || null;
} }
if (sourceData === null) { if (sourceData === null) {
// 'Could not resolve, proably no pairedItem exists.' // 'Could not resolve, proably no pairedItem exists.'
throw createExpressionError('Cant get data for expression', { throw createExpressionError(
messageTemplate: `Cant get data for expression under %%PARAMETER%%`, 'Cant get data for expression',
description: `Could not resolve, proably no pairedItem exists`, {
}); messageTemplate: `Cant get data for expression under %%PARAMETER%%`,
description: `Could not resolve, proably no pairedItem exists`,
},
nodeBeforeLast,
);
} }
taskData = taskData =
@ -682,11 +721,15 @@ export class WorkflowDataProxy {
if (pairedItem.item >= taskData.data!.main[previousNodeOutput]!.length) { 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}'.` // `Could not resolve pairedItem as the item with the index '${pairedItem.item}' does not exist on node '${sourceData.previousNode}'.`
throw createExpressionError('Cant get data for expression', { throw createExpressionError(
messageTemplate: `Cant get data for expression under %%PARAMETER%%`, 'Cant get data for expression',
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)`, messageTemplate: `Cant 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]; return taskData.data!.main[previousNodeOutput]![pairedItem.item];