mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
feat(core): Add support for pairedItem (beta) (#3012)
* ✨ Add pairedItem support * 👕 Fix lint issue * 🐛 Fix resolution in frontend * 🐛 Fix resolution issue * 🐛 Fix resolution in frontend * 🐛 Fix another resolution issue in frontend * ⚡ Try to automatically add pairedItem data if possible * ⚡ Cleanup * ⚡ Display expression errors in editor UI * 🐛 Fix issue that it did not display errors in production * 🐛 Fix auto-fix of missing pairedItem data * 🐛 Fix frontend resolution for not executed nodes * ⚡ Fail execution on pairedItem resolve issue and display information about itemIndex and runIndex * ⚡ Allow that pairedItem is only set to number if runIndex is 0 * ✨ Improve Expression Errors * ⚡ Remove no longer needed code * ⚡ Make errors more helpful * ⚡ Add additional errors * 👕 Fix lint issue * ⚡ Add pairedItem support to core nodes * ⚡ Improve support in Merge-Node * ⚡ Fix issue with not correctly converted incoming pairedItem data * 🐛 Fix frontend resolve issue * 🐛 Fix frontend parameter name display issue * ⚡ Improve errors * 👕 Fix lint issue * ⚡ Improve errors * ⚡ Make it possible to display parameter name in error messages * ⚡ Improve error messages * ⚡ Fix error message * ⚡ Improve error messages * ⚡ Add another error message * ⚡ Simplify
This commit is contained in:
@@ -591,6 +591,7 @@ export class ActiveWorkflowRunner {
|
|||||||
data: {
|
data: {
|
||||||
main: data,
|
main: data,
|
||||||
},
|
},
|
||||||
|
source: null,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -603,6 +604,7 @@ export class ActiveWorkflowRunner {
|
|||||||
contextData: {},
|
contextData: {},
|
||||||
nodeExecutionStack,
|
nodeExecutionStack,
|
||||||
waitingExecution: {},
|
waitingExecution: {},
|
||||||
|
waitingExecutionSource: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||||||
'internal',
|
'internal',
|
||||||
defaultTimezone,
|
defaultTimezone,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
|
undefined,
|
||||||
'',
|
'',
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -366,6 +367,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||||||
mode,
|
mode,
|
||||||
timezone,
|
timezone,
|
||||||
{},
|
{},
|
||||||
|
undefined,
|
||||||
false,
|
false,
|
||||||
decryptedData,
|
decryptedData,
|
||||||
) as ICredentialDataDecryptedObject;
|
) as ICredentialDataDecryptedObject;
|
||||||
@@ -398,6 +400,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||||||
defaultTimezone,
|
defaultTimezone,
|
||||||
{},
|
{},
|
||||||
undefined,
|
undefined,
|
||||||
|
undefined,
|
||||||
decryptedData,
|
decryptedData,
|
||||||
) as ICredentialDataDecryptedObject;
|
) as ICredentialDataDecryptedObject;
|
||||||
}
|
}
|
||||||
@@ -642,6 +645,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||||||
inputData,
|
inputData,
|
||||||
runIndex,
|
runIndex,
|
||||||
nodeTypeCopy,
|
nodeTypeCopy,
|
||||||
|
{ node, data: {}, source: null },
|
||||||
NodeExecuteFunctions,
|
NodeExecuteFunctions,
|
||||||
credentialsDecrypted,
|
credentialsDecrypted,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ export async function executeWebhook(
|
|||||||
executionMode,
|
executionMode,
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
|
undefined,
|
||||||
'onReceived',
|
'onReceived',
|
||||||
);
|
);
|
||||||
const responseCode = workflow.expression.getSimpleParameterValue(
|
const responseCode = workflow.expression.getSimpleParameterValue(
|
||||||
@@ -206,6 +207,7 @@ export async function executeWebhook(
|
|||||||
executionMode,
|
executionMode,
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
|
undefined,
|
||||||
200,
|
200,
|
||||||
) as number;
|
) as number;
|
||||||
|
|
||||||
@@ -215,6 +217,7 @@ export async function executeWebhook(
|
|||||||
executionMode,
|
executionMode,
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
|
undefined,
|
||||||
'firstEntryJson',
|
'firstEntryJson',
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -288,6 +291,7 @@ export async function executeWebhook(
|
|||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
undefined,
|
undefined,
|
||||||
|
undefined,
|
||||||
) as {
|
) as {
|
||||||
entries?:
|
entries?:
|
||||||
| Array<{
|
| Array<{
|
||||||
@@ -373,6 +377,7 @@ export async function executeWebhook(
|
|||||||
data: {
|
data: {
|
||||||
main: webhookResultData.workflowData,
|
main: webhookResultData.workflowData,
|
||||||
},
|
},
|
||||||
|
source: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
runExecutionData =
|
runExecutionData =
|
||||||
@@ -546,6 +551,7 @@ export async function executeWebhook(
|
|||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
undefined,
|
undefined,
|
||||||
|
undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (responsePropertyName !== undefined) {
|
if (responsePropertyName !== undefined) {
|
||||||
@@ -559,6 +565,7 @@ export async function executeWebhook(
|
|||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
undefined,
|
undefined,
|
||||||
|
undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (responseContentType !== undefined) {
|
if (responseContentType !== undefined) {
|
||||||
@@ -603,6 +610,7 @@ export async function executeWebhook(
|
|||||||
executionMode,
|
executionMode,
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
|
undefined,
|
||||||
'data',
|
'data',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -397,6 +397,7 @@ export function hookFunctionsPreExecute(parentProcessMode?: string): IWorkflowEx
|
|||||||
contextData: {},
|
contextData: {},
|
||||||
nodeExecutionStack: [],
|
nodeExecutionStack: [],
|
||||||
waitingExecution: {},
|
waitingExecution: {},
|
||||||
|
waitingExecutionSource: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -752,6 +753,7 @@ export async function getRunData(
|
|||||||
data: {
|
data: {
|
||||||
main: [inputData],
|
main: [inputData],
|
||||||
},
|
},
|
||||||
|
source: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const runExecutionData: IRunExecutionData = {
|
const runExecutionData: IRunExecutionData = {
|
||||||
@@ -763,6 +765,7 @@ export async function getRunData(
|
|||||||
contextData: {},
|
contextData: {},
|
||||||
nodeExecutionStack,
|
nodeExecutionStack,
|
||||||
waitingExecution: {},
|
waitingExecution: {},
|
||||||
|
waitingExecutionSource: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ export async function executeErrorWorkflow(
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
source: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const runExecutionData: IRunExecutionData = {
|
const runExecutionData: IRunExecutionData = {
|
||||||
@@ -200,6 +201,7 @@ export async function executeErrorWorkflow(
|
|||||||
contextData: {},
|
contextData: {},
|
||||||
nodeExecutionStack,
|
nodeExecutionStack,
|
||||||
waitingExecution: {},
|
waitingExecution: {},
|
||||||
|
waitingExecutionSource: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ export class LoadNodeParameterOptions {
|
|||||||
inputData,
|
inputData,
|
||||||
runIndex,
|
runIndex,
|
||||||
tempNode,
|
tempNode,
|
||||||
|
{ node: node!, source: null, data: {} },
|
||||||
NodeExecuteFunctions,
|
NodeExecuteFunctions,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import {
|
|||||||
WorkflowDataProxy,
|
WorkflowDataProxy,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
LoggerProxy as Logger,
|
LoggerProxy as Logger,
|
||||||
|
IExecuteData,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import { Agent } from 'https';
|
import { Agent } from 'https';
|
||||||
@@ -1447,6 +1448,7 @@ export function getNodeParameter(
|
|||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
timezone: string,
|
timezone: string,
|
||||||
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
||||||
|
executeData?: IExecuteData,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object {
|
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object {
|
||||||
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
||||||
@@ -1472,11 +1474,13 @@ export function getNodeParameter(
|
|||||||
mode,
|
mode,
|
||||||
timezone,
|
timezone,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
|
executeData,
|
||||||
);
|
);
|
||||||
|
|
||||||
returnData = cleanupParameterData(returnData);
|
returnData = cleanupParameterData(returnData);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
e.message += ` [Error in parameter: "${parameterName}"]`;
|
if (e.context) e.context.parameter = parameterName;
|
||||||
|
e.cause = value;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1543,6 +1547,7 @@ export function getNodeWebhookUrl(
|
|||||||
mode,
|
mode,
|
||||||
timezone,
|
timezone,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
|
undefined,
|
||||||
false,
|
false,
|
||||||
) as boolean;
|
) as boolean;
|
||||||
return NodeHelpers.getNodeWebhookUrl(baseUrl, workflow.id!, node, path.toString(), isFullPath);
|
return NodeHelpers.getNodeWebhookUrl(baseUrl, workflow.id!, node, path.toString(), isFullPath);
|
||||||
@@ -1673,6 +1678,7 @@ export function getExecutePollFunctions(
|
|||||||
mode,
|
mode,
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
getAdditionalKeys(additionalData),
|
getAdditionalKeys(additionalData),
|
||||||
|
undefined,
|
||||||
fallbackValue,
|
fallbackValue,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -1827,6 +1833,7 @@ export function getExecuteTriggerFunctions(
|
|||||||
mode,
|
mode,
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
getAdditionalKeys(additionalData),
|
getAdditionalKeys(additionalData),
|
||||||
|
undefined,
|
||||||
fallbackValue,
|
fallbackValue,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -1940,6 +1947,7 @@ export function getExecuteFunctions(
|
|||||||
inputData: ITaskDataConnections,
|
inputData: ITaskDataConnections,
|
||||||
node: INode,
|
node: INode,
|
||||||
additionalData: IWorkflowExecuteAdditionalData,
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
|
executeData: IExecuteData,
|
||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
): IExecuteFunctions {
|
): IExecuteFunctions {
|
||||||
return ((workflow, runExecutionData, connectionInputData, inputData, node) => {
|
return ((workflow, runExecutionData, connectionInputData, inputData, node) => {
|
||||||
@@ -1959,6 +1967,7 @@ export function getExecuteFunctions(
|
|||||||
mode,
|
mode,
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
getAdditionalKeys(additionalData),
|
getAdditionalKeys(additionalData),
|
||||||
|
executeData,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
async executeWorkflow(
|
async executeWorkflow(
|
||||||
@@ -2035,6 +2044,7 @@ export function getExecuteFunctions(
|
|||||||
mode,
|
mode,
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
getAdditionalKeys(additionalData),
|
getAdditionalKeys(additionalData),
|
||||||
|
executeData,
|
||||||
fallbackValue,
|
fallbackValue,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -2050,6 +2060,9 @@ export function getExecuteFunctions(
|
|||||||
getTimezone: (): string => {
|
getTimezone: (): string => {
|
||||||
return getTimezone(workflow, additionalData);
|
return getTimezone(workflow, additionalData);
|
||||||
},
|
},
|
||||||
|
getExecuteData: (): IExecuteData => {
|
||||||
|
return executeData;
|
||||||
|
},
|
||||||
getWorkflow: () => {
|
getWorkflow: () => {
|
||||||
return getWorkflowMetadata(workflow);
|
return getWorkflowMetadata(workflow);
|
||||||
},
|
},
|
||||||
@@ -2065,6 +2078,7 @@ export function getExecuteFunctions(
|
|||||||
mode,
|
mode,
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
getAdditionalKeys(additionalData),
|
getAdditionalKeys(additionalData),
|
||||||
|
executeData,
|
||||||
);
|
);
|
||||||
return dataProxy.getDataProxy();
|
return dataProxy.getDataProxy();
|
||||||
},
|
},
|
||||||
@@ -2199,6 +2213,7 @@ export function getExecuteSingleFunctions(
|
|||||||
node: INode,
|
node: INode,
|
||||||
itemIndex: number,
|
itemIndex: number,
|
||||||
additionalData: IWorkflowExecuteAdditionalData,
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
|
executeData: IExecuteData,
|
||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
): IExecuteSingleFunctions {
|
): IExecuteSingleFunctions {
|
||||||
return ((workflow, runExecutionData, connectionInputData, inputData, node, itemIndex) => {
|
return ((workflow, runExecutionData, connectionInputData, inputData, node, itemIndex) => {
|
||||||
@@ -2219,6 +2234,7 @@ export function getExecuteSingleFunctions(
|
|||||||
mode,
|
mode,
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
getAdditionalKeys(additionalData),
|
getAdditionalKeys(additionalData),
|
||||||
|
executeData,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getContext(type: string): IContextObject {
|
getContext(type: string): IContextObject {
|
||||||
@@ -2276,6 +2292,9 @@ export function getExecuteSingleFunctions(
|
|||||||
getTimezone: (): string => {
|
getTimezone: (): string => {
|
||||||
return getTimezone(workflow, additionalData);
|
return getTimezone(workflow, additionalData);
|
||||||
},
|
},
|
||||||
|
getExecuteData: (): IExecuteData => {
|
||||||
|
return executeData;
|
||||||
|
},
|
||||||
getNodeParameter: (
|
getNodeParameter: (
|
||||||
parameterName: string,
|
parameterName: string,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
@@ -2296,6 +2315,7 @@ export function getExecuteSingleFunctions(
|
|||||||
mode,
|
mode,
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
getAdditionalKeys(additionalData),
|
getAdditionalKeys(additionalData),
|
||||||
|
executeData,
|
||||||
fallbackValue,
|
fallbackValue,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -2314,6 +2334,7 @@ export function getExecuteSingleFunctions(
|
|||||||
mode,
|
mode,
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
getAdditionalKeys(additionalData),
|
getAdditionalKeys(additionalData),
|
||||||
|
executeData,
|
||||||
);
|
);
|
||||||
return dataProxy.getDataProxy();
|
return dataProxy.getDataProxy();
|
||||||
},
|
},
|
||||||
@@ -2471,6 +2492,7 @@ export function getLoadOptionsFunctions(
|
|||||||
'internal' as WorkflowExecuteMode,
|
'internal' as WorkflowExecuteMode,
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
getAdditionalKeys(additionalData),
|
getAdditionalKeys(additionalData),
|
||||||
|
undefined,
|
||||||
fallbackValue,
|
fallbackValue,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -2601,6 +2623,7 @@ export function getExecuteHookFunctions(
|
|||||||
mode,
|
mode,
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
getAdditionalKeys(additionalData),
|
getAdditionalKeys(additionalData),
|
||||||
|
undefined,
|
||||||
fallbackValue,
|
fallbackValue,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -2763,6 +2786,7 @@ export function getExecuteWebhookFunctions(
|
|||||||
mode,
|
mode,
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
getAdditionalKeys(additionalData),
|
getAdditionalKeys(additionalData),
|
||||||
|
undefined,
|
||||||
fallbackValue,
|
fallbackValue,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,9 +22,12 @@ import {
|
|||||||
IRun,
|
IRun,
|
||||||
IRunData,
|
IRunData,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
|
ISourceData,
|
||||||
ITaskData,
|
ITaskData,
|
||||||
ITaskDataConnections,
|
ITaskDataConnections,
|
||||||
|
ITaskDataConnectionsSource,
|
||||||
IWaitingForExecution,
|
IWaitingForExecution,
|
||||||
|
IWaitingForExecutionSource,
|
||||||
IWorkflowExecuteAdditionalData,
|
IWorkflowExecuteAdditionalData,
|
||||||
LoggerProxy as Logger,
|
LoggerProxy as Logger,
|
||||||
NodeApiError,
|
NodeApiError,
|
||||||
@@ -61,6 +64,7 @@ export class WorkflowExecute {
|
|||||||
contextData: {},
|
contextData: {},
|
||||||
nodeExecutionStack: [],
|
nodeExecutionStack: [],
|
||||||
waitingExecution: {},
|
waitingExecution: {},
|
||||||
|
waitingExecutionSource: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -106,6 +110,7 @@ export class WorkflowExecute {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
source: null,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -121,6 +126,7 @@ export class WorkflowExecute {
|
|||||||
contextData: {},
|
contextData: {},
|
||||||
nodeExecutionStack,
|
nodeExecutionStack,
|
||||||
waitingExecution: {},
|
waitingExecution: {},
|
||||||
|
waitingExecutionSource: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -157,10 +163,12 @@ export class WorkflowExecute {
|
|||||||
// the data from runData
|
// the data from runData
|
||||||
const nodeExecutionStack: IExecuteData[] = [];
|
const nodeExecutionStack: IExecuteData[] = [];
|
||||||
const waitingExecution: IWaitingForExecution = {};
|
const waitingExecution: IWaitingForExecution = {};
|
||||||
|
const waitingExecutionSource: IWaitingForExecutionSource = {};
|
||||||
for (const startNode of startNodes) {
|
for (const startNode of startNodes) {
|
||||||
incomingNodeConnections = workflow.connectionsByDestinationNode[startNode];
|
incomingNodeConnections = workflow.connectionsByDestinationNode[startNode];
|
||||||
|
|
||||||
const incomingData: INodeExecutionData[][] = [];
|
const incomingData: INodeExecutionData[][] = [];
|
||||||
|
let incomingSourceData: ITaskDataConnectionsSource | null = null;
|
||||||
|
|
||||||
if (incomingNodeConnections === undefined) {
|
if (incomingNodeConnections === undefined) {
|
||||||
// If it has no incoming data add the default empty data
|
// If it has no incoming data add the default empty data
|
||||||
@@ -171,6 +179,7 @@ export class WorkflowExecute {
|
|||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
// Get the data of the incoming connections
|
// Get the data of the incoming connections
|
||||||
|
incomingSourceData = { main: [] };
|
||||||
for (const connections of incomingNodeConnections.main) {
|
for (const connections of incomingNodeConnections.main) {
|
||||||
for (let inputIndex = 0; inputIndex < connections.length; inputIndex++) {
|
for (let inputIndex = 0; inputIndex < connections.length; inputIndex++) {
|
||||||
connection = connections[inputIndex];
|
connection = connections[inputIndex];
|
||||||
@@ -178,6 +187,9 @@ export class WorkflowExecute {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
runData[connection.node][runIndex].data![connection.type][connection.index]!,
|
runData[connection.node][runIndex].data![connection.type][connection.index]!,
|
||||||
);
|
);
|
||||||
|
incomingSourceData.main.push({
|
||||||
|
previousNode: connection.node,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,6 +199,7 @@ export class WorkflowExecute {
|
|||||||
data: {
|
data: {
|
||||||
main: incomingData,
|
main: incomingData,
|
||||||
},
|
},
|
||||||
|
source: incomingSourceData,
|
||||||
};
|
};
|
||||||
|
|
||||||
nodeExecutionStack.push(executeData);
|
nodeExecutionStack.push(executeData);
|
||||||
@@ -201,12 +214,15 @@ export class WorkflowExecute {
|
|||||||
|
|
||||||
if (waitingExecution[destinationNode] === undefined) {
|
if (waitingExecution[destinationNode] === undefined) {
|
||||||
waitingExecution[destinationNode] = {};
|
waitingExecution[destinationNode] = {};
|
||||||
|
waitingExecutionSource[destinationNode] = {};
|
||||||
}
|
}
|
||||||
if (waitingExecution[destinationNode][runIndex] === undefined) {
|
if (waitingExecution[destinationNode][runIndex] === undefined) {
|
||||||
waitingExecution[destinationNode][runIndex] = {};
|
waitingExecution[destinationNode][runIndex] = {};
|
||||||
|
waitingExecutionSource[destinationNode][runIndex] = {};
|
||||||
}
|
}
|
||||||
if (waitingExecution[destinationNode][runIndex][connection.type] === undefined) {
|
if (waitingExecution[destinationNode][runIndex][connection.type] === undefined) {
|
||||||
waitingExecution[destinationNode][runIndex][connection.type] = [];
|
waitingExecution[destinationNode][runIndex][connection.type] = [];
|
||||||
|
waitingExecutionSource[destinationNode][runIndex][connection.type] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (runData[connection.node] !== undefined) {
|
if (runData[connection.node] !== undefined) {
|
||||||
@@ -215,8 +231,14 @@ export class WorkflowExecute {
|
|||||||
waitingExecution[destinationNode][runIndex][connection.type].push(
|
waitingExecution[destinationNode][runIndex][connection.type].push(
|
||||||
runData[connection.node][runIndex].data![connection.type][connection.index],
|
runData[connection.node][runIndex].data![connection.type][connection.index],
|
||||||
);
|
);
|
||||||
|
waitingExecutionSource[destinationNode][runIndex][connection.type].push({
|
||||||
|
previousNode: connection.node,
|
||||||
|
previousNodeOutput: connection.index || undefined,
|
||||||
|
previousNodeRun: runIndex || undefined,
|
||||||
|
} as ISourceData);
|
||||||
} else {
|
} else {
|
||||||
waitingExecution[destinationNode][runIndex][connection.type].push(null);
|
waitingExecution[destinationNode][runIndex][connection.type].push(null);
|
||||||
|
waitingExecutionSource[destinationNode][runIndex][connection.type].push(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,6 +263,7 @@ export class WorkflowExecute {
|
|||||||
contextData: {},
|
contextData: {},
|
||||||
nodeExecutionStack,
|
nodeExecutionStack,
|
||||||
waitingExecution,
|
waitingExecution,
|
||||||
|
waitingExecutionSource,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -303,12 +326,17 @@ export class WorkflowExecute {
|
|||||||
// Node has multiple inputs
|
// Node has multiple inputs
|
||||||
let nodeWasWaiting = true;
|
let nodeWasWaiting = true;
|
||||||
|
|
||||||
|
if (this.runExecutionData.executionData!.waitingExecutionSource === null) {
|
||||||
|
this.runExecutionData.executionData!.waitingExecutionSource = {};
|
||||||
|
}
|
||||||
|
|
||||||
// Check if there is already data for the node
|
// Check if there is already data for the node
|
||||||
if (
|
if (
|
||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node] === undefined
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node] === undefined
|
||||||
) {
|
) {
|
||||||
// Node does not have data yet so create a new empty one
|
// Node does not have data yet so create a new empty one
|
||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node] = {};
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node] = {};
|
||||||
|
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node] = {};
|
||||||
nodeWasWaiting = false;
|
nodeWasWaiting = false;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -319,6 +347,10 @@ export class WorkflowExecute {
|
|||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex] = {
|
||||||
main: [],
|
main: [],
|
||||||
};
|
};
|
||||||
|
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][runIndex] =
|
||||||
|
{
|
||||||
|
main: [],
|
||||||
|
};
|
||||||
for (
|
for (
|
||||||
let i = 0;
|
let i = 0;
|
||||||
i < workflow.connectionsByDestinationNode[connectionData.node].main.length;
|
i < workflow.connectionsByDestinationNode[connectionData.node].main.length;
|
||||||
@@ -327,6 +359,10 @@ export class WorkflowExecute {
|
|||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node][
|
||||||
runIndex
|
runIndex
|
||||||
].main.push(null);
|
].main.push(null);
|
||||||
|
|
||||||
|
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][
|
||||||
|
runIndex
|
||||||
|
].main.push(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,10 +371,20 @@ export class WorkflowExecute {
|
|||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[
|
||||||
connectionData.index
|
connectionData.index
|
||||||
] = null;
|
] = null;
|
||||||
|
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][
|
||||||
|
runIndex
|
||||||
|
].main[connectionData.index] = null;
|
||||||
} else {
|
} else {
|
||||||
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[
|
this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex].main[
|
||||||
connectionData.index
|
connectionData.index
|
||||||
] = nodeSuccessData[outputIndex];
|
] = nodeSuccessData[outputIndex];
|
||||||
|
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][
|
||||||
|
runIndex
|
||||||
|
].main[connectionData.index] = {
|
||||||
|
previousNode: parentNodeName,
|
||||||
|
previousNodeOutput: outputIndex || undefined,
|
||||||
|
previousNodeRun: runIndex || undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if all data exists now
|
// Check if all data exists now
|
||||||
@@ -364,15 +410,32 @@ export class WorkflowExecute {
|
|||||||
if (allDataFound) {
|
if (allDataFound) {
|
||||||
// All data exists for node to be executed
|
// All data exists for node to be executed
|
||||||
// So add it to the execution stack
|
// So add it to the execution stack
|
||||||
this.runExecutionData.executionData!.nodeExecutionStack.push({
|
|
||||||
|
const executionStackItem = {
|
||||||
node: workflow.nodes[connectionData.node],
|
node: workflow.nodes[connectionData.node],
|
||||||
data: this.runExecutionData.executionData!.waitingExecution[connectionData.node][
|
data: this.runExecutionData.executionData!.waitingExecution[connectionData.node][
|
||||||
runIndex
|
runIndex
|
||||||
],
|
],
|
||||||
});
|
source:
|
||||||
|
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][
|
||||||
|
runIndex
|
||||||
|
],
|
||||||
|
} as IExecuteData;
|
||||||
|
|
||||||
|
if (this.runExecutionData.executionData!.waitingExecutionSource !== null) {
|
||||||
|
executionStackItem.source =
|
||||||
|
this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][
|
||||||
|
runIndex
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.runExecutionData.executionData!.nodeExecutionStack.push(executionStackItem);
|
||||||
|
|
||||||
// Remove the data from waiting
|
// Remove the data from waiting
|
||||||
delete this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex];
|
delete this.runExecutionData.executionData!.waitingExecution[connectionData.node][runIndex];
|
||||||
|
delete this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node][
|
||||||
|
runIndex
|
||||||
|
];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
Object.keys(this.runExecutionData.executionData!.waitingExecution[connectionData.node])
|
Object.keys(this.runExecutionData.executionData!.waitingExecution[connectionData.node])
|
||||||
@@ -380,6 +443,7 @@ export class WorkflowExecute {
|
|||||||
) {
|
) {
|
||||||
// No more data left for the node so also delete that one
|
// No more data left for the node so also delete that one
|
||||||
delete this.runExecutionData.executionData!.waitingExecution[connectionData.node];
|
delete this.runExecutionData.executionData!.waitingExecution[connectionData.node];
|
||||||
|
delete this.runExecutionData.executionData!.waitingExecutionSource[connectionData.node];
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -534,6 +598,15 @@ export class WorkflowExecute {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
source: {
|
||||||
|
main: [
|
||||||
|
{
|
||||||
|
previousNode: parentNodeName,
|
||||||
|
previousNodeOutput: outputIndex || undefined,
|
||||||
|
previousNodeRun: runIndex || undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -571,6 +644,15 @@ export class WorkflowExecute {
|
|||||||
data: {
|
data: {
|
||||||
main: connectionDataArray,
|
main: connectionDataArray,
|
||||||
},
|
},
|
||||||
|
source: {
|
||||||
|
main: [
|
||||||
|
{
|
||||||
|
previousNode: parentNodeName,
|
||||||
|
previousNodeOutput: outputIndex || undefined,
|
||||||
|
previousNodeRun: runIndex || undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -660,6 +742,7 @@ export class WorkflowExecute {
|
|||||||
data: {
|
data: {
|
||||||
main: executionData.data.main,
|
main: executionData.data.main,
|
||||||
} as ITaskDataConnections,
|
} as ITaskDataConnections,
|
||||||
|
source: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -691,6 +774,29 @@ export class WorkflowExecute {
|
|||||||
this.runExecutionData.executionData!.nodeExecutionStack.shift() as IExecuteData;
|
this.runExecutionData.executionData!.nodeExecutionStack.shift() as IExecuteData;
|
||||||
executionNode = executionData.node;
|
executionNode = executionData.node;
|
||||||
|
|
||||||
|
// Update the pairedItem information on items
|
||||||
|
const newTaskDataConnections: ITaskDataConnections = {};
|
||||||
|
for (const inputName of Object.keys(executionData.data)) {
|
||||||
|
newTaskDataConnections[inputName] = executionData.data[inputName].map(
|
||||||
|
(input, inputIndex) => {
|
||||||
|
if (input === null) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
return input.map((item, itemIndex) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
input: inputIndex || undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
executionData.data = newTaskDataConnections;
|
||||||
|
|
||||||
Logger.debug(`Start processing node "${executionNode.name}"`, {
|
Logger.debug(`Start processing node "${executionNode.name}"`, {
|
||||||
node: executionNode.name,
|
node: executionNode.name,
|
||||||
workflowId: workflow.id,
|
workflowId: workflow.id,
|
||||||
@@ -767,9 +873,6 @@ export class WorkflowExecute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone input data that nodes can not mess up data of parallel nodes which receive the same data
|
|
||||||
// TODO: Should only clone if multiple nodes get the same data or when it gets returned to frontned
|
|
||||||
// is very slow so only do if needed
|
|
||||||
startTime = new Date().getTime();
|
startTime = new Date().getTime();
|
||||||
|
|
||||||
let maxTries = 1;
|
let maxTries = 1;
|
||||||
@@ -813,8 +916,7 @@ export class WorkflowExecute {
|
|||||||
workflowId: workflow.id,
|
workflowId: workflow.id,
|
||||||
});
|
});
|
||||||
const runNodeData = await workflow.runNode(
|
const runNodeData = await workflow.runNode(
|
||||||
executionData.node,
|
executionData,
|
||||||
executionData.data,
|
|
||||||
this.runExecutionData,
|
this.runExecutionData,
|
||||||
runIndex,
|
runIndex,
|
||||||
this.additionalData,
|
this.additionalData,
|
||||||
@@ -834,6 +936,30 @@ export class WorkflowExecute {
|
|||||||
workflowId: workflow.id,
|
workflowId: workflow.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check if the output data contains pairedItem data
|
||||||
|
checkOutputData: for (const outputData of nodeSuccessData as INodeExecutionData[][]) {
|
||||||
|
if (outputData === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const item of outputData) {
|
||||||
|
if (!item.pairedItem) {
|
||||||
|
// The pairedItem is missing so check if it can get automatically fixed
|
||||||
|
if (
|
||||||
|
executionData.data.main.length !== 1 ||
|
||||||
|
executionData.data.main[0]?.length !== 1
|
||||||
|
) {
|
||||||
|
// Automatically fixing is only possible if there is only one
|
||||||
|
// input and one input item
|
||||||
|
break checkOutputData;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.pairedItem = {
|
||||||
|
item: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (nodeSuccessData === undefined) {
|
if (nodeSuccessData === undefined) {
|
||||||
// Node did not get executed
|
// Node did not get executed
|
||||||
nodeSuccessData = null;
|
nodeSuccessData = null;
|
||||||
@@ -885,6 +1011,7 @@ export class WorkflowExecute {
|
|||||||
taskData = {
|
taskData = {
|
||||||
startTime,
|
startTime,
|
||||||
executionTime: new Date().getTime() - startTime,
|
executionTime: new Date().getTime() - startTime,
|
||||||
|
source: executionData.source === null ? [] : executionData.source.main,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (executionError !== undefined) {
|
if (executionError !== undefined) {
|
||||||
|
|||||||
@@ -144,7 +144,10 @@ export default mixins(
|
|||||||
const workflow = this.getWorkflow();
|
const workflow = this.getWorkflow();
|
||||||
const activeNode: INodeUi | null = this.$store.getters.activeNode;
|
const activeNode: INodeUi | null = this.$store.getters.activeNode;
|
||||||
const parentNode = workflow.getParentNodes(activeNode!.name, inputName, 1);
|
const parentNode = workflow.getParentNodes(activeNode!.name, inputName, 1);
|
||||||
const inputIndex = workflow.getNodeConnectionOutputIndex(activeNode!.name, parentNode[0]) || 0;
|
const nodeConnection = workflow.getNodeConnectionIndexes(activeNode!.name, parentNode[0]) || {
|
||||||
|
sourceIndex: 0,
|
||||||
|
destinationIndex: 0,
|
||||||
|
};
|
||||||
|
|
||||||
const autocompleteData: string[] = [];
|
const autocompleteData: string[] = [];
|
||||||
|
|
||||||
@@ -164,7 +167,7 @@ export default mixins(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const connectionInputData = this.connectionInputData(parentNode, inputName, runIndex, inputIndex);
|
const connectionInputData = this.connectionInputData(parentNode, activeNode!.name, inputName, runIndex, nodeConnection);
|
||||||
|
|
||||||
const additionalProxyKeys: IWorkflowDataProxyAdditionalKeys = {
|
const additionalProxyKeys: IWorkflowDataProxyAdditionalKeys = {
|
||||||
$executionId: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
$executionId: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="error-header">
|
<div class="error-header">
|
||||||
<div class="error-message">{{ $locale.baseText('nodeErrorView.error') + ': ' + error.message }}</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">{{error.description}}</div>
|
||||||
</div>
|
</div>
|
||||||
<details>
|
<details>
|
||||||
@@ -9,6 +9,13 @@
|
|||||||
<font-awesome-icon class="error-details__icon" icon="angle-right" /> {{ $locale.baseText('nodeErrorView.details') }}
|
<font-awesome-icon class="error-details__icon" icon="angle-right" /> {{ $locale.baseText('nodeErrorView.details') }}
|
||||||
</summary>
|
</summary>
|
||||||
<div class="error-details__content">
|
<div class="error-details__content">
|
||||||
|
<div v-if="error.context.causeDetailed">
|
||||||
|
<el-card class="box-card" shadow="never">
|
||||||
|
<div>
|
||||||
|
{{error.context.causeDetailed}}
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
<div v-if="error.timestamp">
|
<div v-if="error.timestamp">
|
||||||
<el-card class="box-card" shadow="never">
|
<el-card class="box-card" shadow="never">
|
||||||
<div slot="header" class="clearfix box-card__title">
|
<div slot="header" class="clearfix box-card__title">
|
||||||
@@ -19,6 +26,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="error.context && error.context.itemIndex !== undefined" class="el-card box-card is-never-shadow el-card__body">
|
||||||
|
<span class="error-details__summary">{{ $locale.baseText('nodeErrorView.itemIndex') }}:</span>
|
||||||
|
{{error.context.itemIndex}}
|
||||||
|
<span v-if="error.context.runIndex">
|
||||||
|
| <span class="error-details__summary">{{ $locale.baseText('nodeErrorView.itemIndex') }}:</span>
|
||||||
|
{{error.context.runIndex}}
|
||||||
|
</span>
|
||||||
|
<span v-if="error.context.parameter">
|
||||||
|
| <span class="error-details__summary">{{ $locale.baseText('nodeErrorView.inParameter') }}:</span>
|
||||||
|
{{ parameterDisplayName(error.context.parameter) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div v-if="error.httpCode">
|
<div v-if="error.httpCode">
|
||||||
<el-card class="box-card" shadow="never">
|
<el-card class="box-card" shadow="never">
|
||||||
<div slot="header" class="clearfix box-card__title">
|
<div slot="header" class="clearfix box-card__title">
|
||||||
@@ -79,6 +98,16 @@ import mixins from 'vue-typed-mixins';
|
|||||||
import {
|
import {
|
||||||
MAX_DISPLAY_DATA_SIZE,
|
MAX_DISPLAY_DATA_SIZE,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
|
import {
|
||||||
|
INodeUi,
|
||||||
|
} from '@/Interface';
|
||||||
|
|
||||||
|
import {
|
||||||
|
INodeProperties,
|
||||||
|
INodePropertyCollection,
|
||||||
|
INodePropertyOptions,
|
||||||
|
INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
export default mixins(
|
export default mixins(
|
||||||
copyPaste,
|
copyPaste,
|
||||||
@@ -95,8 +124,72 @@ export default mixins(
|
|||||||
displayCause(): boolean {
|
displayCause(): boolean {
|
||||||
return JSON.stringify(this.error.cause).length < MAX_DISPLAY_DATA_SIZE;
|
return JSON.stringify(this.error.cause).length < MAX_DISPLAY_DATA_SIZE;
|
||||||
},
|
},
|
||||||
|
parameters (): INodeProperties[] {
|
||||||
|
const node = this.$store.getters.activeNode;
|
||||||
|
if (!node) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const nodeType = this.$store.getters.nodeType(node.type, node.typeVersion);
|
||||||
|
|
||||||
|
if (nodeType === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeType.properties;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getErrorMessage (): string {
|
||||||
|
if (!this.error.context.messageTemplate) {
|
||||||
|
return this.error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parameterName = this.parameterDisplayName(this.error.context.parameter);
|
||||||
|
return this.error.context.messageTemplate.replace(/%%PARAMETER%%/g, parameterName);
|
||||||
|
},
|
||||||
|
parameterDisplayName(path: string) {
|
||||||
|
try {
|
||||||
|
const parameters = this.parameterName(this.parameters, path.split('.'));
|
||||||
|
if (!parameters.length) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
return parameters.map(parameter => parameter.displayName).join(' > ');
|
||||||
|
} catch (error) {
|
||||||
|
return `Could not find parameter "${path}"`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parameterName(parameters: Array<(INodePropertyOptions | INodeProperties | INodePropertyCollection)>, pathParts: string[]): Array<(INodeProperties | INodePropertyCollection)> {
|
||||||
|
let currentParameterName = pathParts.shift();
|
||||||
|
|
||||||
|
if (currentParameterName === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrayMatch = currentParameterName.match(/(.*)\[([\d])\]$/);
|
||||||
|
if (arrayMatch !== null && arrayMatch.length > 0) {
|
||||||
|
currentParameterName = arrayMatch[1];
|
||||||
|
}
|
||||||
|
const currentParameter = parameters.find(parameter => parameter.name === currentParameterName) as unknown as INodeProperties | INodePropertyCollection;
|
||||||
|
|
||||||
|
if (currentParameter === undefined) {
|
||||||
|
throw new Error(`Could not find parameter "${currentParameterName}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathParts.length === 0) {
|
||||||
|
return [currentParameter];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentParameter.hasOwnProperty('options')) {
|
||||||
|
return [currentParameter, ...this.parameterName((currentParameter as INodeProperties).options!, pathParts)];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentParameter.hasOwnProperty('values')) {
|
||||||
|
return [currentParameter, ...this.parameterName((currentParameter as INodePropertyCollection).values, pathParts)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can not resolve any deeper so lets stop here and at least return hopefully something useful
|
||||||
|
return [currentParameter];
|
||||||
|
},
|
||||||
copyCause() {
|
copyCause() {
|
||||||
this.copyToClipboard(JSON.stringify(this.error.cause));
|
this.copyToClipboard(JSON.stringify(this.error.cause));
|
||||||
this.copySuccess();
|
this.copySuccess();
|
||||||
|
|||||||
@@ -366,6 +366,7 @@ export default mixins(
|
|||||||
|
|
||||||
// Get the resolved parameter values of the current node
|
// Get the resolved parameter values of the current node
|
||||||
const currentNodeParameters = this.$store.getters.activeNode.parameters;
|
const currentNodeParameters = this.$store.getters.activeNode.parameters;
|
||||||
|
try {
|
||||||
const resolvedNodeParameters = this.resolveParameter(currentNodeParameters);
|
const resolvedNodeParameters = this.resolveParameter(currentNodeParameters);
|
||||||
|
|
||||||
const returnValues: string[] = [];
|
const returnValues: string[] = [];
|
||||||
@@ -374,6 +375,9 @@ export default mixins(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return returnValues.join('|');
|
return returnValues.join('|');
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
node (): INodeUi | null {
|
node (): INodeUi | null {
|
||||||
return this.$store.getters.activeNode;
|
return this.$store.getters.activeNode;
|
||||||
@@ -698,9 +702,9 @@ export default mixins(
|
|||||||
|
|
||||||
// Get the resolved parameter values of the current node
|
// Get the resolved parameter values of the current node
|
||||||
const currentNodeParameters = this.$store.getters.activeNode.parameters;
|
const currentNodeParameters = this.$store.getters.activeNode.parameters;
|
||||||
const resolvedNodeParameters = this.resolveParameter(currentNodeParameters) as INodeParameters;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const resolvedNodeParameters = this.resolveParameter(currentNodeParameters) as INodeParameters;
|
||||||
const loadOptionsMethod = this.getArgument('loadOptionsMethod') as string | undefined;
|
const loadOptionsMethod = this.getArgument('loadOptionsMethod') as string | undefined;
|
||||||
const loadOptions = this.getArgument('loadOptions') as ILoadOptions | undefined;
|
const loadOptions = this.getArgument('loadOptions') as ILoadOptions | undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -374,13 +374,19 @@ export default mixins(
|
|||||||
return returnData;
|
return returnData;
|
||||||
},
|
},
|
||||||
getNodeContext (workflow: Workflow, runExecutionData: IRunExecutionData | null, parentNode: string[], nodeName: string, filterText: string): IVariableSelectorOption[] | null {
|
getNodeContext (workflow: Workflow, runExecutionData: IRunExecutionData | null, parentNode: string[], nodeName: string, filterText: string): IVariableSelectorOption[] | null {
|
||||||
const inputIndex = 0;
|
|
||||||
const itemIndex = 0;
|
const itemIndex = 0;
|
||||||
const inputName = 'main';
|
const inputName = 'main';
|
||||||
const runIndex = 0;
|
const runIndex = 0;
|
||||||
const returnData: IVariableSelectorOption[] = [];
|
const returnData: IVariableSelectorOption[] = [];
|
||||||
|
|
||||||
const connectionInputData = this.connectionInputData(parentNode, inputName, runIndex, inputIndex);
|
const activeNode: INodeUi | null = this.$store.getters.activeNode;
|
||||||
|
|
||||||
|
if (activeNode === null) {
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeConnection = this.workflow.getNodeConnectionIndexes(activeNode.name, parentNode[0], 'main');
|
||||||
|
const connectionInputData = this.connectionInputData(parentNode, nodeName, inputName, runIndex, nodeConnection);
|
||||||
|
|
||||||
if (connectionInputData === null) {
|
if (connectionInputData === null) {
|
||||||
return returnData;
|
return returnData;
|
||||||
@@ -493,7 +499,8 @@ export default mixins(
|
|||||||
// Check from which output to read the data.
|
// Check from which output to read the data.
|
||||||
// Depends on how the nodes are connected.
|
// Depends on how the nodes are connected.
|
||||||
// (example "IF" node. If node is connected to "true" or to "false" output)
|
// (example "IF" node. If node is connected to "true" or to "false" output)
|
||||||
const outputIndex = this.workflow.getNodeConnectionOutputIndex(activeNode.name, parentNode[0], 'main');
|
const nodeConnection = this.workflow.getNodeConnectionIndexes(activeNode.name, parentNode[0], 'main');
|
||||||
|
const outputIndex = nodeConnection === undefined ? 0: nodeConnection.sourceIndex;
|
||||||
|
|
||||||
tempOutputData = this.getNodeOutputData(runData, parentNode[0], filterText, itemIndex, 0, 'main', outputIndex, true) as IVariableSelectorOption[];
|
tempOutputData = this.getNodeOutputData(runData, parentNode[0], filterText, itemIndex, 0, 'main', outputIndex, true) as IVariableSelectorOption[];
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import {
|
|||||||
IWorkflowDataProxyAdditionalKeys,
|
IWorkflowDataProxyAdditionalKeys,
|
||||||
Workflow,
|
Workflow,
|
||||||
NodeHelpers,
|
NodeHelpers,
|
||||||
|
IExecuteData,
|
||||||
|
INodeConnection,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -59,9 +61,12 @@ export const workflowHelpers = mixins(
|
|||||||
)
|
)
|
||||||
.extend({
|
.extend({
|
||||||
methods: {
|
methods: {
|
||||||
// Returns connectionInputData to be able to execute an expression.
|
executeData(parentNode: string[], currentNode: string, inputName: string, runIndex: number): IExecuteData {
|
||||||
connectionInputData (parentNode: string[], inputName: string, runIndex: number, inputIndex: number): INodeExecutionData[] | null {
|
const executeData = {
|
||||||
let connectionInputData = null;
|
node: {},
|
||||||
|
data: {},
|
||||||
|
source: null,
|
||||||
|
} as IExecuteData;
|
||||||
|
|
||||||
if (parentNode.length) {
|
if (parentNode.length) {
|
||||||
// Add the input data to be able to also resolve the short expression format
|
// Add the input data to be able to also resolve the short expression format
|
||||||
@@ -70,18 +75,58 @@ export const workflowHelpers = mixins(
|
|||||||
|
|
||||||
const workflowRunData = this.$store.getters.getWorkflowRunData as IRunData | null;
|
const workflowRunData = this.$store.getters.getWorkflowRunData as IRunData | null;
|
||||||
if (workflowRunData === null) {
|
if (workflowRunData === null) {
|
||||||
return null;
|
return executeData;
|
||||||
}
|
}
|
||||||
if (!workflowRunData[parentNodeName] ||
|
if (!workflowRunData[parentNodeName] ||
|
||||||
workflowRunData[parentNodeName].length <= runIndex ||
|
workflowRunData[parentNodeName].length <= runIndex ||
|
||||||
!workflowRunData[parentNodeName][runIndex].hasOwnProperty('data') ||
|
!workflowRunData[parentNodeName][runIndex].hasOwnProperty('data') ||
|
||||||
workflowRunData[parentNodeName][runIndex].data === undefined ||
|
workflowRunData[parentNodeName][runIndex].data === undefined ||
|
||||||
!workflowRunData[parentNodeName][runIndex].data!.hasOwnProperty(inputName) ||
|
!workflowRunData[parentNodeName][runIndex].data!.hasOwnProperty(inputName)
|
||||||
workflowRunData[parentNodeName][runIndex].data![inputName].length <= inputIndex
|
|
||||||
) {
|
) {
|
||||||
|
executeData.data = {};
|
||||||
|
} else {
|
||||||
|
executeData.data = workflowRunData[parentNodeName][runIndex].data!;
|
||||||
|
if (workflowRunData[currentNode] && workflowRunData[currentNode][runIndex]) {
|
||||||
|
executeData.source = {
|
||||||
|
[inputName]: workflowRunData[currentNode][runIndex].source!,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// The curent node did not get executed in UI yet so build data manually
|
||||||
|
executeData.source = {
|
||||||
|
[inputName]: [
|
||||||
|
{
|
||||||
|
previousNode: parentNodeName,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return executeData;
|
||||||
|
},
|
||||||
|
// Returns connectionInputData to be able to execute an expression.
|
||||||
|
connectionInputData (parentNode: string[], currentNode: string, inputName: string, runIndex: number, nodeConnection: INodeConnection = { sourceIndex: 0, destinationIndex: 0 }): INodeExecutionData[] | null {
|
||||||
|
let connectionInputData = null;
|
||||||
|
const executeData = this.executeData(parentNode, currentNode, inputName, runIndex);
|
||||||
|
if (parentNode.length) {
|
||||||
|
if (!Object.keys(executeData.data).length || executeData.data[inputName].length <= nodeConnection.sourceIndex) {
|
||||||
connectionInputData = [];
|
connectionInputData = [];
|
||||||
} else {
|
} else {
|
||||||
connectionInputData = workflowRunData[parentNodeName][runIndex].data![inputName][inputIndex];
|
connectionInputData = executeData.data![inputName][nodeConnection.sourceIndex];
|
||||||
|
|
||||||
|
if (connectionInputData !== null) {
|
||||||
|
// Update the pairedItem information on items
|
||||||
|
connectionInputData = connectionInputData.map((item, itemIndex) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
input: nodeConnection.destinationIndex,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,14 +431,20 @@ export const workflowHelpers = mixins(
|
|||||||
|
|
||||||
resolveParameter(parameter: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[]) {
|
resolveParameter(parameter: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[]) {
|
||||||
const itemIndex = 0;
|
const itemIndex = 0;
|
||||||
const runIndex = 0;
|
|
||||||
const inputName = 'main';
|
const inputName = 'main';
|
||||||
const activeNode = this.$store.getters.activeNode;
|
const activeNode = this.$store.getters.activeNode;
|
||||||
const workflow = this.getWorkflow();
|
const workflow = this.getWorkflow();
|
||||||
const parentNode = workflow.getParentNodes(activeNode.name, inputName, 1);
|
const parentNode = workflow.getParentNodes(activeNode.name, inputName, 1);
|
||||||
const executionData = this.$store.getters.getWorkflowExecution as IExecutionResponse | null;
|
const executionData = this.$store.getters.getWorkflowExecution as IExecutionResponse | null;
|
||||||
const inputIndex = workflow.getNodeConnectionOutputIndex(activeNode!.name, parentNode[0]) || 0;
|
|
||||||
let connectionInputData = this.connectionInputData(parentNode, inputName, runIndex, inputIndex);
|
const workflowRunData = this.$store.getters.getWorkflowRunData as IRunData | null;
|
||||||
|
let runIndexParent = 0;
|
||||||
|
if (workflowRunData !== null && parentNode.length) {
|
||||||
|
runIndexParent = workflowRunData[parentNode[0]].length -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeConnection = workflow.getNodeConnectionIndexes(activeNode!.name, parentNode[0]);
|
||||||
|
let connectionInputData = this.connectionInputData(parentNode, activeNode.name, inputName, runIndexParent, nodeConnection);
|
||||||
|
|
||||||
let runExecutionData: IRunExecutionData;
|
let runExecutionData: IRunExecutionData;
|
||||||
if (executionData === null) {
|
if (executionData === null) {
|
||||||
@@ -415,7 +466,13 @@ export const workflowHelpers = mixins(
|
|||||||
$resumeWebhookUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
$resumeWebhookUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||||
};
|
};
|
||||||
|
|
||||||
return workflow.expression.getParameterValue(parameter, runExecutionData, runIndex, itemIndex, activeNode.name, connectionInputData, 'manual', this.$store.getters.timezone, additionalKeys, false) as IDataObject;
|
let runIndexCurrent = 0;
|
||||||
|
if (workflowRunData !== null && workflowRunData[activeNode.name]) {
|
||||||
|
runIndexCurrent = workflowRunData[activeNode.name].length -1;
|
||||||
|
}
|
||||||
|
const executeData = this.executeData(parentNode, activeNode.name, inputName, runIndexCurrent);
|
||||||
|
|
||||||
|
return workflow.expression.getParameterValue(parameter, runExecutionData, runIndexCurrent, itemIndex, activeNode.name, connectionInputData, 'manual', this.$store.getters.timezone, additionalKeys, executeData, false) as IDataObject;
|
||||||
},
|
},
|
||||||
|
|
||||||
resolveExpression(expression: string, siblingParameters: INodeParameters = {}) {
|
resolveExpression(expression: string, siblingParameters: INodeParameters = {}) {
|
||||||
|
|||||||
@@ -400,6 +400,9 @@
|
|||||||
"nodeErrorView.details": "Details",
|
"nodeErrorView.details": "Details",
|
||||||
"nodeErrorView.error": "ERROR",
|
"nodeErrorView.error": "ERROR",
|
||||||
"nodeErrorView.httpCode": "HTTP Code",
|
"nodeErrorView.httpCode": "HTTP Code",
|
||||||
|
"nodeErrorView.inParameter": "In or underneath Parameter",
|
||||||
|
"nodeErrorView.itemIndex": "Item Index",
|
||||||
|
"nodeErrorView.runIndex": "Run Index",
|
||||||
"nodeErrorView.showMessage.title": "Copied to clipboard",
|
"nodeErrorView.showMessage.title": "Copied to clipboard",
|
||||||
"nodeErrorView.stack": "Stack",
|
"nodeErrorView.stack": "Stack",
|
||||||
"nodeErrorView.theErrorCauseIsTooLargeToBeDisplayed": "The error cause is too large to be displayed",
|
"nodeErrorView.theErrorCauseIsTooLargeToBeDisplayed": "The error cause is too large to be displayed",
|
||||||
|
|||||||
@@ -257,6 +257,9 @@ export class Compression implements INodeType {
|
|||||||
returnData.push({
|
returnData.push({
|
||||||
json: items[i].json,
|
json: items[i].json,
|
||||||
binary: binaryObject,
|
binary: binaryObject,
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,6 +317,9 @@ export class Compression implements INodeType {
|
|||||||
binary: {
|
binary: {
|
||||||
[binaryPropertyOutput]: data,
|
[binaryPropertyOutput]: data,
|
||||||
},
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,13 +327,23 @@ export class Compression implements INodeType {
|
|||||||
returnData.push({
|
returnData.push({
|
||||||
json: items[i].json,
|
json: items[i].json,
|
||||||
binary: binaryObject,
|
binary: binaryObject,
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
returnData.push({ json: { error: error.message } });
|
returnData.push({
|
||||||
|
json: {
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -493,11 +493,17 @@ export class Crypto implements INodeType {
|
|||||||
// Uses dot notation so copy all data
|
// Uses dot notation so copy all data
|
||||||
newItem = {
|
newItem = {
|
||||||
json: JSON.parse(JSON.stringify(item.json)),
|
json: JSON.parse(JSON.stringify(item.json)),
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Does not use dot notation so shallow copy is enough
|
// Does not use dot notation so shallow copy is enough
|
||||||
newItem = {
|
newItem = {
|
||||||
json: { ...item.json },
|
json: { ...item.json },
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,7 +517,14 @@ export class Crypto implements INodeType {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
returnData.push({ json: { error: (error as JsonObject).message } });
|
returnData.push({
|
||||||
|
json: {
|
||||||
|
error: (error as JsonObject).message,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -446,11 +446,17 @@ export class DateTime implements INodeType {
|
|||||||
// Uses dot notation so copy all data
|
// Uses dot notation so copy all data
|
||||||
newItem = {
|
newItem = {
|
||||||
json: JSON.parse(JSON.stringify(item.json)),
|
json: JSON.parse(JSON.stringify(item.json)),
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Does not use dot notation so shallow copy is enough
|
// Does not use dot notation so shallow copy is enough
|
||||||
newItem = {
|
newItem = {
|
||||||
json: { ...item.json },
|
json: { ...item.json },
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,11 +491,17 @@ export class DateTime implements INodeType {
|
|||||||
// Uses dot notation so copy all data
|
// Uses dot notation so copy all data
|
||||||
newItem = {
|
newItem = {
|
||||||
json: JSON.parse(JSON.stringify(item.json)),
|
json: JSON.parse(JSON.stringify(item.json)),
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Does not use dot notation so shallow copy is enough
|
// Does not use dot notation so shallow copy is enough
|
||||||
newItem = {
|
newItem = {
|
||||||
json: { ...item.json },
|
json: { ...item.json },
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,7 +516,14 @@ export class DateTime implements INodeType {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
returnData.push({json:{ error: error.message }});
|
returnData.push({
|
||||||
|
json: {
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -1211,6 +1211,9 @@ export class EditImage implements INodeType {
|
|||||||
const newItem: INodeExecutionData = {
|
const newItem: INodeExecutionData = {
|
||||||
json: item.json,
|
json: item.json,
|
||||||
binary: {},
|
binary: {},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (operation === 'information') {
|
if (operation === 'information') {
|
||||||
@@ -1394,7 +1397,14 @@ export class EditImage implements INodeType {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
returnData.push({json:{ error: error.message }});
|
returnData.push({
|
||||||
|
json: {
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -204,11 +204,23 @@ export class EmailSend implements INodeType {
|
|||||||
// Send the email
|
// Send the email
|
||||||
const info = await transporter.sendMail(mailOptions);
|
const info = await transporter.sendMail(mailOptions);
|
||||||
|
|
||||||
returnData.push({ json: info as unknown as IDataObject });
|
returnData.push({
|
||||||
|
json: info as unknown as IDataObject,
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
returnData.push({json:{ error: error.message }});
|
returnData.push({
|
||||||
|
json: {
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -119,12 +119,22 @@ export class ExecuteCommand implements INodeType {
|
|||||||
stderr,
|
stderr,
|
||||||
stdout,
|
stdout,
|
||||||
},
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
returnItems.push({json:{ error: error.message }});
|
returnItems.push({
|
||||||
|
json: {
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -163,6 +163,9 @@ return item;`,
|
|||||||
|
|
||||||
const returnItem: INodeExecutionData = {
|
const returnItem: INodeExecutionData = {
|
||||||
json: cleanupData(jsonData),
|
json: cleanupData(jsonData),
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (item.binary) {
|
if (item.binary) {
|
||||||
@@ -172,7 +175,14 @@ return item;`,
|
|||||||
returnData.push(returnItem);
|
returnData.push(returnItem);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
returnData.push({json:{ error: error.message }});
|
returnData.push({
|
||||||
|
json: {
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -259,7 +259,14 @@ export class Git implements INodeType {
|
|||||||
|
|
||||||
await git.add(pathsToAdd.split(','));
|
await git.add(pathsToAdd.split(','));
|
||||||
|
|
||||||
returnItems.push({ json: { success: true } });
|
returnItems.push({
|
||||||
|
json: {
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
} else if (operation === 'addConfig') {
|
} else if (operation === 'addConfig') {
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
@@ -275,7 +282,14 @@ export class Git implements INodeType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await git.addConfig(key, value, append);
|
await git.addConfig(key, value, append);
|
||||||
returnItems.push({ json: { success: true } });
|
returnItems.push({
|
||||||
|
json: {
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
} else if (operation === 'clone') {
|
} else if (operation === 'clone') {
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
@@ -287,7 +301,14 @@ export class Git implements INodeType {
|
|||||||
|
|
||||||
await git.clone(sourceRepository, '.');
|
await git.clone(sourceRepository, '.');
|
||||||
|
|
||||||
returnItems.push({ json: { success: true } });
|
returnItems.push({
|
||||||
|
json: {
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
} else if (operation === 'commit') {
|
} else if (operation === 'commit') {
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
@@ -303,7 +324,14 @@ export class Git implements INodeType {
|
|||||||
|
|
||||||
await git.commit(message, pathsToAdd);
|
await git.commit(message, pathsToAdd);
|
||||||
|
|
||||||
returnItems.push({ json: { success: true } });
|
returnItems.push({
|
||||||
|
json: {
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
} else if (operation === 'fetch') {
|
} else if (operation === 'fetch') {
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
@@ -311,7 +339,14 @@ export class Git implements INodeType {
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
await git.fetch();
|
await git.fetch();
|
||||||
returnItems.push({ json: { success: true } });
|
returnItems.push({
|
||||||
|
json: {
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
} else if (operation === 'log') {
|
} else if (operation === 'log') {
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
@@ -331,7 +366,12 @@ export class Git implements INodeType {
|
|||||||
const log = await git.log(logOptions);
|
const log = await git.log(logOptions);
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
returnItems.push(...this.helpers.returnJsonArray(log.all));
|
returnItems.push(...this.helpers.returnJsonArray(log.all).map(item => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
pairedItem: { item: itemIndex },
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
} else if (operation === 'pull') {
|
} else if (operation === 'pull') {
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
@@ -339,7 +379,14 @@ export class Git implements INodeType {
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
await git.pull();
|
await git.pull();
|
||||||
returnItems.push({ json: { success: true } });
|
returnItems.push({
|
||||||
|
json: {
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
} else if (operation === 'push') {
|
} else if (operation === 'push') {
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
@@ -370,7 +417,14 @@ export class Git implements INodeType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
returnItems.push({ json: { success: true } });
|
returnItems.push({
|
||||||
|
json: {
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
} else if (operation === 'pushTags') {
|
} else if (operation === 'pushTags') {
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
@@ -378,7 +432,14 @@ export class Git implements INodeType {
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
await git.pushTags();
|
await git.pushTags();
|
||||||
returnItems.push({ json: { success: true } });
|
returnItems.push({
|
||||||
|
json: {
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
} else if (operation === 'listConfig') {
|
} else if (operation === 'listConfig') {
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
@@ -396,7 +457,12 @@ export class Git implements INodeType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
returnItems.push(...this.helpers.returnJsonArray(data));
|
returnItems.push(...this.helpers.returnJsonArray(data).map(item => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
pairedItem: { item: itemIndex },
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
} else if (operation === 'status') {
|
} else if (operation === 'status') {
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
@@ -406,7 +472,12 @@ export class Git implements INodeType {
|
|||||||
const status = await git.status();
|
const status = await git.status();
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
returnItems.push(...this.helpers.returnJsonArray([status]));
|
returnItems.push(...this.helpers.returnJsonArray([status]).map(item => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
pairedItem: { item: itemIndex },
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
} else if (operation === 'tag') {
|
} else if (operation === 'tag') {
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
@@ -416,14 +487,27 @@ export class Git implements INodeType {
|
|||||||
const name = this.getNodeParameter('name', itemIndex, '') as string;
|
const name = this.getNodeParameter('name', itemIndex, '') as string;
|
||||||
|
|
||||||
await git.addTag(name);
|
await git.addTag(name);
|
||||||
returnItems.push({ json: { success: true } });
|
returnItems.push({
|
||||||
|
json: {
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
returnItems.push({ json: { error: error.toString() } });
|
returnItems.push({
|
||||||
|
json: {
|
||||||
|
error: error.toString(),
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -254,6 +254,9 @@ export class HtmlExtract implements INodeType {
|
|||||||
|
|
||||||
const newItem: INodeExecutionData = {
|
const newItem: INodeExecutionData = {
|
||||||
json: {},
|
json: {},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Itterate over all the defined values which should be extracted
|
// Itterate over all the defined values which should be extracted
|
||||||
@@ -277,7 +280,14 @@ export class HtmlExtract implements INodeType {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
returnData.push({ json: { error: error.message } });
|
returnData.push({
|
||||||
|
json: {
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -1232,6 +1232,9 @@ export class HttpRequest implements INodeType {
|
|||||||
json: {
|
json: {
|
||||||
error: response.reason,
|
error: response.reason,
|
||||||
},
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
@@ -1251,6 +1254,9 @@ export class HttpRequest implements INodeType {
|
|||||||
const newItem: INodeExecutionData = {
|
const newItem: INodeExecutionData = {
|
||||||
json: {},
|
json: {},
|
||||||
binary: {},
|
binary: {},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (items[itemIndex].binary !== undefined) {
|
if (items[itemIndex].binary !== undefined) {
|
||||||
@@ -1295,12 +1301,20 @@ export class HttpRequest implements INodeType {
|
|||||||
|
|
||||||
returnItem[property] = response![property];
|
returnItem[property] = response![property];
|
||||||
}
|
}
|
||||||
returnItems.push({ json: returnItem });
|
returnItems.push({
|
||||||
|
json: returnItem,
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
returnItems.push({
|
returnItems.push({
|
||||||
json: {
|
json: {
|
||||||
[dataPropertyName]: response,
|
[dataPropertyName]: response,
|
||||||
},
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1319,7 +1333,12 @@ export class HttpRequest implements INodeType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
returnItems.push({ json: returnItem });
|
returnItems.push({
|
||||||
|
json: returnItem,
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
if (responseFormat === 'json' && typeof response === 'string') {
|
if (responseFormat === 'json' && typeof response === 'string') {
|
||||||
try {
|
try {
|
||||||
@@ -1330,9 +1349,19 @@ export class HttpRequest implements INodeType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.splitIntoItems === true && Array.isArray(response)) {
|
if (options.splitIntoItems === true && Array.isArray(response)) {
|
||||||
response.forEach(item => returnItems.push({ json: item }));
|
response.forEach(item => returnItems.push({
|
||||||
|
json: item,
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
}));
|
||||||
} else {
|
} else {
|
||||||
returnItems.push({ json: response });
|
returnItems.push({
|
||||||
|
json: response,
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -355,6 +355,9 @@ export class ICalendar implements INodeType {
|
|||||||
binary: {
|
binary: {
|
||||||
[binaryPropertyName]: binaryData,
|
[binaryPropertyName]: binaryData,
|
||||||
},
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -752,7 +752,12 @@ return 0;`,
|
|||||||
newItem = { ...newItem, [destinationFieldName as string || fieldToSplitOut as string]: element };
|
newItem = { ...newItem, [destinationFieldName as string || fieldToSplitOut as string]: element };
|
||||||
}
|
}
|
||||||
|
|
||||||
returnData.push({ json: newItem });
|
returnData.push({
|
||||||
|
json: newItem,
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -790,8 +795,17 @@ return 0;`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let newItem: INodeExecutionData;
|
let newItem: INodeExecutionData;
|
||||||
newItem = { json: {} };
|
newItem = {
|
||||||
|
json: {},
|
||||||
|
pairedItem: Array.from({length}, (_, i) => i).map(index => {
|
||||||
|
return {
|
||||||
|
item: index,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
// tslint:disable-next-line: no-any
|
// tslint:disable-next-line: no-any
|
||||||
const values: { [key: string]: any } = {};
|
const values: { [key: string]: any } = {};
|
||||||
const outputFields: string[] = [];
|
const outputFields: string[] = [];
|
||||||
@@ -899,9 +913,10 @@ return 0;`,
|
|||||||
}
|
}
|
||||||
keys = fieldsToCompare.map(key => (key.trim()));
|
keys = fieldsToCompare.map(key => (key.trim()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// This solution is O(nlogn)
|
// This solution is O(nlogn)
|
||||||
// add original index to the items
|
// add original index to the items
|
||||||
const newItems = items.map((item, index) => ({ json: { ...item['json'], __INDEX: index, }, } as INodeExecutionData));
|
const newItems = items.map((item, index) => ({ json: { ...item['json'], __INDEX: index, }, pairedItem: { item: index, } } as INodeExecutionData));
|
||||||
//sort items using the compare keys
|
//sort items using the compare keys
|
||||||
newItems.sort((a, b) => {
|
newItems.sort((a, b) => {
|
||||||
let result = 0;
|
let result = 0;
|
||||||
@@ -962,7 +977,7 @@ return 0;`,
|
|||||||
let data = items.filter((_, index) => !removedIndexes.includes(index));
|
let data = items.filter((_, index) => !removedIndexes.includes(index));
|
||||||
|
|
||||||
if (removeOtherFields) {
|
if (removeOtherFields) {
|
||||||
data = data.map(item => ({ json: pick(item.json, ...keys) }));
|
data = data.map((item, index) => ({ json: pick(item.json, ...keys), pairedItem: { item: index, } }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the filtered items
|
// return the filtered items
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export async function router(this: IExecuteFunctions): Promise<INodeExecutionDat
|
|||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
operationResult.push({json: this.getInputData(i)[0].json, error: err});
|
operationResult.push({json: this.getInputData(i)[0].json, error: err});
|
||||||
} else {
|
} else {
|
||||||
|
if (err.context) err.context.itemIndex = i;
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
INodeType,
|
INodeType,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
|
IPairedItemData,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
|
||||||
@@ -261,6 +262,10 @@ export class Merge implements INodeType {
|
|||||||
|
|
||||||
newItem = {
|
newItem = {
|
||||||
json: {},
|
json: {},
|
||||||
|
pairedItem: [
|
||||||
|
dataInput1[i].pairedItem as IPairedItemData,
|
||||||
|
dataInput2[i].pairedItem as IPairedItemData,
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (dataInput1[i].binary !== undefined) {
|
if (dataInput1[i].binary !== undefined) {
|
||||||
@@ -305,7 +310,15 @@ export class Merge implements INodeType {
|
|||||||
|
|
||||||
for (entry1 of dataInput1) {
|
for (entry1 of dataInput1) {
|
||||||
for (entry2 of dataInput2) {
|
for (entry2 of dataInput2) {
|
||||||
returnData.push({json: {...(entry1.json), ...(entry2.json)}});
|
returnData.push({
|
||||||
|
json: {
|
||||||
|
...(entry1.json), ...(entry2.json),
|
||||||
|
},
|
||||||
|
pairedItem: [
|
||||||
|
entry1.pairedItem as IPairedItemData,
|
||||||
|
entry2.pairedItem as IPairedItemData,
|
||||||
|
],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return [returnData];
|
return [returnData];
|
||||||
|
|||||||
@@ -380,6 +380,9 @@ export class MoveBinaryData implements INodeType {
|
|||||||
// Copy the whole JSON data as data on any level can be renamed
|
// Copy the whole JSON data as data on any level can be renamed
|
||||||
newItem = {
|
newItem = {
|
||||||
json: {},
|
json: {},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mode === 'binaryToJson') {
|
if (mode === 'binaryToJson') {
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ export class ReadBinaryFile implements INodeType {
|
|||||||
const newItem: INodeExecutionData = {
|
const newItem: INodeExecutionData = {
|
||||||
json: item.json,
|
json: item.json,
|
||||||
binary: {},
|
binary: {},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (item.binary !== undefined) {
|
if (item.binary !== undefined) {
|
||||||
@@ -90,7 +93,14 @@ export class ReadBinaryFile implements INodeType {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
returnData.push({json:{ error: error.message }});
|
returnData.push({
|
||||||
|
json: {
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ export class ReadBinaryFiles implements INodeType {
|
|||||||
[dataPropertyName]: await this.helpers.prepareBinaryData(data, filePath),
|
[dataPropertyName]: await this.helpers.prepareBinaryData(data, filePath),
|
||||||
},
|
},
|
||||||
json: {},
|
json: {},
|
||||||
|
pairedItem: {
|
||||||
|
item: 0,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
items.push(item);
|
items.push(item);
|
||||||
|
|||||||
@@ -60,7 +60,14 @@ export class ReadPdf implements INodeType {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
returnData.push({json:{ error: error.message }});
|
returnData.push({
|
||||||
|
json: {
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -88,6 +88,9 @@ export class RenameKeys implements INodeType {
|
|||||||
// Copy the whole JSON data as data on any level can be renamed
|
// Copy the whole JSON data as data on any level can be renamed
|
||||||
newItem = {
|
newItem = {
|
||||||
json: JSON.parse(JSON.stringify(item.json)),
|
json: JSON.parse(JSON.stringify(item.json)),
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (item.binary !== undefined) {
|
if (item.binary !== undefined) {
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ export class Set implements INodeType {
|
|||||||
|
|
||||||
const newItem: INodeExecutionData = {
|
const newItem: INodeExecutionData = {
|
||||||
json: {},
|
json: {},
|
||||||
|
pairedItem: item.pairedItem,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (keepOnlySet !== true) {
|
if (keepOnlySet !== true) {
|
||||||
|
|||||||
@@ -94,6 +94,12 @@ export class SplitInBatches implements INodeType {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
returnItems.map((item, index) => {
|
||||||
|
item.pairedItem = {
|
||||||
|
item: index,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return this.prepareOutputData(returnItems);
|
return this.prepareOutputData(returnItems);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -391,17 +391,36 @@ export class SpreadsheetFile implements INodeType {
|
|||||||
if (options.headerRow === false) {
|
if (options.headerRow === false) {
|
||||||
// Data was returned as an array - https://github.com/SheetJS/sheetjs#json
|
// Data was returned as an array - https://github.com/SheetJS/sheetjs#json
|
||||||
for (const rowData of sheetJson) {
|
for (const rowData of sheetJson) {
|
||||||
newItems.push({ json: { row: rowData } } as INodeExecutionData);
|
newItems.push({
|
||||||
|
json: {
|
||||||
|
row: rowData,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
|
} as INodeExecutionData);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const rowData of sheetJson) {
|
for (const rowData of sheetJson) {
|
||||||
newItems.push({ json: rowData } as INodeExecutionData);
|
newItems.push({
|
||||||
|
json: rowData,
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
|
} as INodeExecutionData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
newItems.push({json:{ error: error.message }});
|
newItems.push({
|
||||||
|
json: {
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
@@ -466,6 +485,9 @@ export class SpreadsheetFile implements INodeType {
|
|||||||
const newItem: INodeExecutionData = {
|
const newItem: INodeExecutionData = {
|
||||||
json: {},
|
json: {},
|
||||||
binary: {},
|
binary: {},
|
||||||
|
pairedItem: {
|
||||||
|
item: 0,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let fileName = `spreadsheet.${fileFormat}`;
|
let fileName = `spreadsheet.${fileFormat}`;
|
||||||
@@ -478,7 +500,14 @@ export class SpreadsheetFile implements INodeType {
|
|||||||
newItems.push(newItem);
|
newItems.push(newItem);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
newItems.push({json:{ error: error.message }});
|
newItems.push({
|
||||||
|
json: {
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ export class Ssh implements INodeType {
|
|||||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
const items = this.getInputData();
|
const items = this.getInputData();
|
||||||
|
|
||||||
const returnData: IDataObject[] = [];
|
const returnItems: INodeExecutionData[] = [];
|
||||||
|
|
||||||
const resource = this.getNodeParameter('resource', 0) as string;
|
const resource = this.getNodeParameter('resource', 0) as string;
|
||||||
const operation = this.getNodeParameter('operation', 0) as string;
|
const operation = this.getNodeParameter('operation', 0) as string;
|
||||||
@@ -333,7 +333,12 @@ export class Ssh implements INodeType {
|
|||||||
|
|
||||||
const command = this.getNodeParameter('command', i) as string;
|
const command = this.getNodeParameter('command', i) as string;
|
||||||
const cwd = this.getNodeParameter('cwd', i) as string;
|
const cwd = this.getNodeParameter('cwd', i) as string;
|
||||||
returnData.push(await ssh.execCommand(command, { cwd, }));
|
returnItems.push({
|
||||||
|
json: await ssh.execCommand(command, { cwd, }),
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,6 +357,9 @@ export class Ssh implements INodeType {
|
|||||||
const newItem: INodeExecutionData = {
|
const newItem: INodeExecutionData = {
|
||||||
json: items[i].json,
|
json: items[i].json,
|
||||||
binary: {},
|
binary: {},
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (items[i].binary !== undefined) {
|
if (items[i].binary !== undefined) {
|
||||||
@@ -395,7 +403,14 @@ export class Ssh implements INodeType {
|
|||||||
|
|
||||||
await ssh.putFile(path, `${parameterPath}${(parameterPath.charAt(parameterPath.length - 1) === '/') ? '' : '/'}${fileName || binaryData.fileName}`);
|
await ssh.putFile(path, `${parameterPath}${(parameterPath.charAt(parameterPath.length - 1) === '/') ? '' : '/'}${fileName || binaryData.fileName}`);
|
||||||
|
|
||||||
returnData.push({ success: true });
|
returnItems.push({
|
||||||
|
json: {
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -407,7 +422,14 @@ export class Ssh implements INodeType {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
returnData.push({ error: error.message });
|
returnItems.push({
|
||||||
|
json: {
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: i,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -428,7 +450,7 @@ export class Ssh implements INodeType {
|
|||||||
// For file downloads the files get attached to the existing items
|
// For file downloads the files get attached to the existing items
|
||||||
return this.prepareOutputData(items);
|
return this.prepareOutputData(items);
|
||||||
} else {
|
} else {
|
||||||
return [this.helpers.returnJsonArray(returnData)];
|
return this.prepareOutputData(returnItems);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ export class WriteBinaryFile implements INodeType {
|
|||||||
|
|
||||||
const newItem: INodeExecutionData = {
|
const newItem: INodeExecutionData = {
|
||||||
json: {},
|
json: {},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
Object.assign(newItem.json, item.json);
|
Object.assign(newItem.json, item.json);
|
||||||
|
|
||||||
@@ -100,7 +103,14 @@ export class WriteBinaryFile implements INodeType {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
returnData.push({ json: { error: error.message } });
|
returnData.push({
|
||||||
|
json: {
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -262,13 +262,23 @@ export class Xml implements INodeType {
|
|||||||
json: {
|
json: {
|
||||||
[dataPropertyName]: builder.buildObject(items[itemIndex].json),
|
[dataPropertyName]: builder.buildObject(items[itemIndex].json),
|
||||||
},
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new NodeOperationError(this.getNode(), `The operation "${mode}" is not known!`);
|
throw new NodeOperationError(this.getNode(), `The operation "${mode}" is not known!`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
items[itemIndex] = ({json:{ error: error.message }});
|
items[itemIndex] = ({
|
||||||
|
json: {
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
pairedItem: {
|
||||||
|
item: itemIndex,
|
||||||
|
},
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { DateTime, Duration, Interval } from 'luxon';
|
|||||||
|
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
import {
|
import {
|
||||||
|
ExpressionError,
|
||||||
|
IExecuteData,
|
||||||
INode,
|
INode,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
@@ -21,10 +23,11 @@ import {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||||
tmpl.brackets.set('{{ }}');
|
tmpl.brackets.set('{{ }}');
|
||||||
|
|
||||||
// Make sure that it does not always print an error when it could not resolve
|
// Make sure that error get forwarded
|
||||||
// a variable
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
tmpl.tmpl.errorHandler = () => {};
|
tmpl.tmpl.errorHandler = (error: Error) => {
|
||||||
|
throw error;
|
||||||
|
};
|
||||||
|
|
||||||
export class Expression {
|
export class Expression {
|
||||||
workflow: Workflow;
|
workflow: Workflow;
|
||||||
@@ -71,6 +74,7 @@ export class Expression {
|
|||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
timezone: string,
|
timezone: string,
|
||||||
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
||||||
|
executeData?: IExecuteData,
|
||||||
returnObjectAsString = false,
|
returnObjectAsString = false,
|
||||||
selfData = {},
|
selfData = {},
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] {
|
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] {
|
||||||
@@ -98,6 +102,7 @@ export class Expression {
|
|||||||
mode,
|
mode,
|
||||||
timezone,
|
timezone,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
|
executeData,
|
||||||
-1,
|
-1,
|
||||||
selfData,
|
selfData,
|
||||||
);
|
);
|
||||||
@@ -148,13 +153,24 @@ export class Expression {
|
|||||||
data.constructor = {};
|
data.constructor = {};
|
||||||
|
|
||||||
// Execute the expression
|
// Execute the expression
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
let returnValue;
|
||||||
try {
|
try {
|
||||||
if (/([^a-zA-Z0-9"']window[^a-zA-Z0-9"'])/g.test(parameterValue)) {
|
if (/([^a-zA-Z0-9"']window[^a-zA-Z0-9"'])/g.test(parameterValue)) {
|
||||||
throw new Error(`window is not allowed`);
|
throw new Error(`window is not allowed`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||||
const returnValue = tmpl.tmpl(parameterValue, data);
|
returnValue = tmpl.tmpl(parameterValue, data);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ExpressionError) {
|
||||||
|
// Ignore all errors except if they are ExpressionErrors and they are supposed
|
||||||
|
// to fail the execution
|
||||||
|
if (error.context.failExecution) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof returnValue === 'function') {
|
if (typeof returnValue === 'function') {
|
||||||
throw new Error('Expression resolved to a function. Please add "()"');
|
throw new Error('Expression resolved to a function. Please add "()"');
|
||||||
@@ -163,12 +179,9 @@ export class Expression {
|
|||||||
return this.convertObjectValueToString(returnValue);
|
return this.convertObjectValueToString(returnValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||||
return returnValue;
|
return returnValue;
|
||||||
} catch (e) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access
|
|
||||||
throw new Error(`Expression is not valid: ${e.message}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -186,6 +199,7 @@ export class Expression {
|
|||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
timezone: string,
|
timezone: string,
|
||||||
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
||||||
|
executeData?: IExecuteData,
|
||||||
defaultValue?: boolean | number | string,
|
defaultValue?: boolean | number | string,
|
||||||
): boolean | number | string | undefined {
|
): boolean | number | string | undefined {
|
||||||
if (parameterValue === undefined) {
|
if (parameterValue === undefined) {
|
||||||
@@ -213,6 +227,7 @@ export class Expression {
|
|||||||
mode,
|
mode,
|
||||||
timezone,
|
timezone,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
|
executeData,
|
||||||
) as boolean | number | string | undefined;
|
) as boolean | number | string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,6 +246,7 @@ export class Expression {
|
|||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
timezone: string,
|
timezone: string,
|
||||||
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
||||||
|
executeData?: IExecuteData,
|
||||||
defaultValue:
|
defaultValue:
|
||||||
| NodeParameterValue
|
| NodeParameterValue
|
||||||
| INodeParameters
|
| INodeParameters
|
||||||
@@ -265,6 +281,7 @@ export class Expression {
|
|||||||
mode,
|
mode,
|
||||||
timezone,
|
timezone,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
|
executeData,
|
||||||
false,
|
false,
|
||||||
selfData,
|
selfData,
|
||||||
);
|
);
|
||||||
@@ -280,6 +297,7 @@ export class Expression {
|
|||||||
mode,
|
mode,
|
||||||
timezone,
|
timezone,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
|
executeData,
|
||||||
false,
|
false,
|
||||||
selfData,
|
selfData,
|
||||||
);
|
);
|
||||||
@@ -310,6 +328,7 @@ export class Expression {
|
|||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
timezone: string,
|
timezone: string,
|
||||||
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
||||||
|
executeData?: IExecuteData,
|
||||||
returnObjectAsString = false,
|
returnObjectAsString = false,
|
||||||
selfData = {},
|
selfData = {},
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] {
|
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] {
|
||||||
@@ -336,6 +355,7 @@ export class Expression {
|
|||||||
mode,
|
mode,
|
||||||
timezone,
|
timezone,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
|
executeData,
|
||||||
returnObjectAsString,
|
returnObjectAsString,
|
||||||
selfData,
|
selfData,
|
||||||
);
|
);
|
||||||
@@ -351,6 +371,7 @@ export class Expression {
|
|||||||
mode,
|
mode,
|
||||||
timezone,
|
timezone,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
|
executeData,
|
||||||
returnObjectAsString,
|
returnObjectAsString,
|
||||||
selfData,
|
selfData,
|
||||||
);
|
);
|
||||||
@@ -369,6 +390,7 @@ export class Expression {
|
|||||||
mode,
|
mode,
|
||||||
timezone,
|
timezone,
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
|
executeData,
|
||||||
returnObjectAsString,
|
returnObjectAsString,
|
||||||
selfData,
|
selfData,
|
||||||
);
|
);
|
||||||
|
|||||||
48
packages/workflow/src/ExpressionError.ts
Normal file
48
packages/workflow/src/ExpressionError.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
|
import { ExecutionBaseError } from './NodeErrors';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for instantiating an expression error
|
||||||
|
*/
|
||||||
|
export class ExpressionError extends ExecutionBaseError {
|
||||||
|
constructor(
|
||||||
|
message: string,
|
||||||
|
options?: {
|
||||||
|
causeDetailed?: string;
|
||||||
|
description?: string;
|
||||||
|
runIndex?: number;
|
||||||
|
itemIndex?: number;
|
||||||
|
messageTemplate?: string;
|
||||||
|
parameter?: string;
|
||||||
|
failExecution?: boolean;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
super(new Error(message));
|
||||||
|
|
||||||
|
if (options?.description !== undefined) {
|
||||||
|
this.description = options.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.causeDetailed !== undefined) {
|
||||||
|
this.context.causeDetailed = options.causeDetailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.runIndex !== undefined) {
|
||||||
|
this.context.runIndex = options.runIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.itemIndex !== undefined) {
|
||||||
|
this.context.itemIndex = options.itemIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.parameter !== undefined) {
|
||||||
|
this.context.parameter = options.parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.messageTemplate !== undefined) {
|
||||||
|
this.context.messageTemplate = options.messageTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.context.failExecution = !!options?.failExecution;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -306,6 +306,11 @@ export interface ICredentialDataDecryptedObject {
|
|||||||
// Second array index: The different connections (if one node is connected to multiple nodes)
|
// Second array index: The different connections (if one node is connected to multiple nodes)
|
||||||
export type NodeInputConnections = IConnection[][];
|
export type NodeInputConnections = IConnection[][];
|
||||||
|
|
||||||
|
export interface INodeConnection {
|
||||||
|
sourceIndex: number;
|
||||||
|
destinationIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface INodeConnections {
|
export interface INodeConnections {
|
||||||
// Input name
|
// Input name
|
||||||
[key: string]: NodeInputConnections;
|
[key: string]: NodeInputConnections;
|
||||||
@@ -363,6 +368,7 @@ export interface IGetExecuteFunctions {
|
|||||||
inputData: ITaskDataConnections,
|
inputData: ITaskDataConnections,
|
||||||
node: INode,
|
node: INode,
|
||||||
additionalData: IWorkflowExecuteAdditionalData,
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
|
executeData: IExecuteData,
|
||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
): IExecuteFunctions;
|
): IExecuteFunctions;
|
||||||
}
|
}
|
||||||
@@ -377,6 +383,7 @@ export interface IGetExecuteSingleFunctions {
|
|||||||
node: INode,
|
node: INode,
|
||||||
itemIndex: number,
|
itemIndex: number,
|
||||||
additionalData: IWorkflowExecuteAdditionalData,
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
|
executeData: IExecuteData,
|
||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
): IExecuteSingleFunctions;
|
): IExecuteSingleFunctions;
|
||||||
}
|
}
|
||||||
@@ -403,9 +410,17 @@ export interface IGetExecuteWebhookFunctions {
|
|||||||
): IWebhookFunctions;
|
): IWebhookFunctions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ISourceDataConnections {
|
||||||
|
// Key for each input type and because there can be multiple inputs of the same type it is an array
|
||||||
|
// null is also allowed because if we still need data for a later while executing the workflow set teompoary to null
|
||||||
|
// the nodes get as input TaskDataConnections which is identical to this one except that no null is allowed.
|
||||||
|
[key: string]: Array<ISourceData[] | null>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IExecuteData {
|
export interface IExecuteData {
|
||||||
data: ITaskDataConnections;
|
data: ITaskDataConnections;
|
||||||
node: INode;
|
node: INode;
|
||||||
|
source: ITaskDataConnectionsSource | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IContextObject = {
|
export type IContextObject = {
|
||||||
@@ -514,6 +529,7 @@ export interface IExecuteFunctions {
|
|||||||
getWorkflowStaticData(type: string): IDataObject;
|
getWorkflowStaticData(type: string): IDataObject;
|
||||||
getRestApiUrl(): string;
|
getRestApiUrl(): string;
|
||||||
getTimezone(): string;
|
getTimezone(): string;
|
||||||
|
getExecuteData(): IExecuteData;
|
||||||
getWorkflow(): IWorkflowMetadata;
|
getWorkflow(): IWorkflowMetadata;
|
||||||
prepareOutputData(
|
prepareOutputData(
|
||||||
outputData: INodeExecutionData[],
|
outputData: INodeExecutionData[],
|
||||||
@@ -553,6 +569,7 @@ export interface IExecuteSingleFunctions {
|
|||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object;
|
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object;
|
||||||
getRestApiUrl(): string;
|
getRestApiUrl(): string;
|
||||||
getTimezone(): string;
|
getTimezone(): string;
|
||||||
|
getExecuteData(): IExecuteData;
|
||||||
getWorkflow(): IWorkflowMetadata;
|
getWorkflow(): IWorkflowMetadata;
|
||||||
getWorkflowDataProxy(): IWorkflowDataProxyData;
|
getWorkflowDataProxy(): IWorkflowDataProxyData;
|
||||||
getWorkflowStaticData(type: string): IDataObject;
|
getWorkflowStaticData(type: string): IDataObject;
|
||||||
@@ -801,11 +818,25 @@ export interface IBinaryKeyData {
|
|||||||
[key: string]: IBinaryData;
|
[key: string]: IBinaryData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IPairedItemData {
|
||||||
|
item: number;
|
||||||
|
input?: number; // If undefined "0" gets used
|
||||||
|
}
|
||||||
|
|
||||||
export interface INodeExecutionData {
|
export interface INodeExecutionData {
|
||||||
[key: string]: IDataObject | IBinaryKeyData | NodeApiError | NodeOperationError | undefined;
|
[key: string]:
|
||||||
|
| IDataObject
|
||||||
|
| IBinaryKeyData
|
||||||
|
| IPairedItemData
|
||||||
|
| IPairedItemData[]
|
||||||
|
| NodeApiError
|
||||||
|
| NodeOperationError
|
||||||
|
| number
|
||||||
|
| undefined;
|
||||||
json: IDataObject;
|
json: IDataObject;
|
||||||
binary?: IBinaryKeyData;
|
binary?: IBinaryKeyData;
|
||||||
error?: NodeApiError | NodeOperationError;
|
error?: NodeApiError | NodeOperationError;
|
||||||
|
pairedItem?: IPairedItemData | IPairedItemData[] | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INodeExecuteFunctions {
|
export interface INodeExecuteFunctions {
|
||||||
@@ -1262,6 +1293,7 @@ export interface IRunExecutionData {
|
|||||||
contextData: IExecuteContextData;
|
contextData: IExecuteContextData;
|
||||||
nodeExecutionStack: IExecuteData[];
|
nodeExecutionStack: IExecuteData[];
|
||||||
waitingExecution: IWaitingForExecution;
|
waitingExecution: IWaitingForExecution;
|
||||||
|
waitingExecutionSource: IWaitingForExecutionSource | null;
|
||||||
};
|
};
|
||||||
waitTill?: Date;
|
waitTill?: Date;
|
||||||
}
|
}
|
||||||
@@ -1277,9 +1309,16 @@ export interface ITaskData {
|
|||||||
executionTime: number;
|
executionTime: number;
|
||||||
data?: ITaskDataConnections;
|
data?: ITaskDataConnections;
|
||||||
error?: ExecutionError;
|
error?: ExecutionError;
|
||||||
|
source: Array<ISourceData | null>; // Is an array as nodes have multiple inputs
|
||||||
}
|
}
|
||||||
|
|
||||||
// The data for al the different kind of connectons (like main) and all the indexes
|
export interface ISourceData {
|
||||||
|
previousNode: string;
|
||||||
|
previousNodeOutput?: number; // If undefined "0" gets used
|
||||||
|
previousNodeRun?: number; // If undefined "0" gets used
|
||||||
|
}
|
||||||
|
|
||||||
|
// The data for all the different kind of connectons (like main) and all the indexes
|
||||||
export interface ITaskDataConnections {
|
export interface ITaskDataConnections {
|
||||||
// Key for each input type and because there can be multiple inputs of the same type it is an array
|
// Key for each input type and because there can be multiple inputs of the same type it is an array
|
||||||
// null is also allowed because if we still need data for a later while executing the workflow set teompoary to null
|
// null is also allowed because if we still need data for a later while executing the workflow set teompoary to null
|
||||||
@@ -1296,6 +1335,21 @@ export interface IWaitingForExecution {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ITaskDataConnectionsSource {
|
||||||
|
// Key for each input type and because there can be multiple inputs of the same type it is an array
|
||||||
|
// null is also allowed because if we still need data for a later while executing the workflow set teompoary to null
|
||||||
|
// the nodes get as input TaskDataConnections which is identical to this one except that no null is allowed.
|
||||||
|
[key: string]: Array<ISourceData | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IWaitingForExecutionSource {
|
||||||
|
// Node name
|
||||||
|
[key: string]: {
|
||||||
|
// Run index
|
||||||
|
[key: number]: ITaskDataConnectionsSource;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface IWorkflowBase {
|
export interface IWorkflowBase {
|
||||||
id?: number | string | any;
|
id?: number | string | any;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
// eslint-disable-next-line max-classes-per-file
|
// eslint-disable-next-line max-classes-per-file
|
||||||
import { parseString } from 'xml2js';
|
import { parseString } from 'xml2js';
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
import { INode, IStatusCodeMessages, JsonObject } from '.';
|
import { IDataObject, INode, IStatusCodeMessages, JsonObject } from '.';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Top-level properties where an error message can be found in an API response.
|
* Top-level properties where an error message can be found in an API response.
|
||||||
@@ -56,29 +56,42 @@ const ERROR_STATUS_PROPERTIES = [
|
|||||||
*/
|
*/
|
||||||
const ERROR_NESTING_PROPERTIES = ['error', 'err', 'response', 'body', 'data'];
|
const ERROR_NESTING_PROPERTIES = ['error', 'err', 'response', 'body', 'data'];
|
||||||
|
|
||||||
/**
|
export abstract class ExecutionBaseError extends Error {
|
||||||
* Base class for specific NodeError-types, with functionality for finding
|
|
||||||
* a value recursively inside an error object.
|
|
||||||
*/
|
|
||||||
abstract class NodeError extends Error {
|
|
||||||
description: string | null | undefined;
|
description: string | null | undefined;
|
||||||
|
|
||||||
cause: Error | JsonObject;
|
cause: Error | JsonObject;
|
||||||
|
|
||||||
node: INode;
|
|
||||||
|
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
|
||||||
constructor(node: INode, error: Error | JsonObject) {
|
context: IDataObject = {};
|
||||||
|
|
||||||
|
constructor(error: Error | ExecutionBaseError | JsonObject) {
|
||||||
super();
|
super();
|
||||||
this.name = this.constructor.name;
|
this.name = this.constructor.name;
|
||||||
this.cause = error;
|
this.cause = error;
|
||||||
this.node = node;
|
|
||||||
this.timestamp = Date.now();
|
this.timestamp = Date.now();
|
||||||
|
|
||||||
if (error.message) {
|
if (error.message) {
|
||||||
this.message = error.message as string;
|
this.message = error.message as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(error, 'context')) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
this.context = (error as any).context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for specific NodeError-types, with functionality for finding
|
||||||
|
* a value recursively inside an error object.
|
||||||
|
*/
|
||||||
|
abstract class NodeError extends ExecutionBaseError {
|
||||||
|
node: INode;
|
||||||
|
|
||||||
|
constructor(node: INode, error: Error | JsonObject) {
|
||||||
|
super(error);
|
||||||
|
this.node = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -203,7 +216,11 @@ abstract class NodeError extends Error {
|
|||||||
* Class for instantiating an operational error, e.g. an invalid credentials error.
|
* Class for instantiating an operational error, e.g. an invalid credentials error.
|
||||||
*/
|
*/
|
||||||
export class NodeOperationError extends NodeError {
|
export class NodeOperationError extends NodeError {
|
||||||
constructor(node: INode, error: Error | string, options?: { description: string }) {
|
constructor(
|
||||||
|
node: INode,
|
||||||
|
error: Error | string,
|
||||||
|
options?: { description?: string; runIndex?: number; itemIndex?: number },
|
||||||
|
) {
|
||||||
if (typeof error === 'string') {
|
if (typeof error === 'string') {
|
||||||
error = new Error(error);
|
error = new Error(error);
|
||||||
}
|
}
|
||||||
@@ -212,6 +229,14 @@ export class NodeOperationError extends NodeError {
|
|||||||
if (options?.description) {
|
if (options?.description) {
|
||||||
this.description = options.description;
|
this.description = options.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options?.runIndex !== undefined) {
|
||||||
|
this.context.runIndex = options.runIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.itemIndex !== undefined) {
|
||||||
|
this.context.itemIndex = options.itemIndex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,7 +274,16 @@ export class NodeApiError extends NodeError {
|
|||||||
description,
|
description,
|
||||||
httpCode,
|
httpCode,
|
||||||
parseXml,
|
parseXml,
|
||||||
}: { message?: string; description?: string; httpCode?: string; parseXml?: boolean } = {},
|
runIndex,
|
||||||
|
itemIndex,
|
||||||
|
}: {
|
||||||
|
message?: string;
|
||||||
|
description?: string;
|
||||||
|
httpCode?: string;
|
||||||
|
parseXml?: boolean;
|
||||||
|
runIndex?: number;
|
||||||
|
itemIndex?: number;
|
||||||
|
} = {},
|
||||||
) {
|
) {
|
||||||
super(node, error);
|
super(node, error);
|
||||||
if (error.error) {
|
if (error.error) {
|
||||||
@@ -272,6 +306,9 @@ export class NodeApiError extends NodeError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.description = this.findProperty(error, ERROR_MESSAGE_PROPERTIES, ERROR_NESTING_PROPERTIES);
|
this.description = this.findProperty(error, ERROR_MESSAGE_PROPERTIES, ERROR_NESTING_PROPERTIES);
|
||||||
|
|
||||||
|
if (runIndex !== undefined) this.context.runIndex = runIndex;
|
||||||
|
if (itemIndex !== undefined) this.context.itemIndex = itemIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
private setDescriptionFromXml(xml: string) {
|
private setDescriptionFromXml(xml: string) {
|
||||||
|
|||||||
@@ -939,6 +939,7 @@ export function getNodeWebhooks(
|
|||||||
'internal',
|
'internal',
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
{},
|
{},
|
||||||
|
undefined,
|
||||||
false,
|
false,
|
||||||
) as boolean;
|
) as boolean;
|
||||||
const restartWebhook: boolean = workflow.expression.getSimpleParameterValue(
|
const restartWebhook: boolean = workflow.expression.getSimpleParameterValue(
|
||||||
@@ -947,6 +948,7 @@ export function getNodeWebhooks(
|
|||||||
'internal',
|
'internal',
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
{},
|
{},
|
||||||
|
undefined,
|
||||||
false,
|
false,
|
||||||
) as boolean;
|
) as boolean;
|
||||||
const path = getNodeWebhookPath(workflowId, node, nodeWebhookPath, isFullPath, restartWebhook);
|
const path = getNodeWebhookPath(workflowId, node, nodeWebhookPath, isFullPath, restartWebhook);
|
||||||
@@ -957,6 +959,7 @@ export function getNodeWebhooks(
|
|||||||
mode,
|
mode,
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
{},
|
{},
|
||||||
|
undefined,
|
||||||
'GET',
|
'GET',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ import {
|
|||||||
ITaskDataConnections,
|
ITaskDataConnections,
|
||||||
IWorkflowDataProxyAdditionalKeys,
|
IWorkflowDataProxyAdditionalKeys,
|
||||||
IWorkflowExecuteAdditionalData,
|
IWorkflowExecuteAdditionalData,
|
||||||
|
NodeApiError,
|
||||||
NodeHelpers,
|
NodeHelpers,
|
||||||
|
NodeOperationError,
|
||||||
NodeParameterValue,
|
NodeParameterValue,
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
@@ -37,6 +39,7 @@ import {
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
IDataObject,
|
IDataObject,
|
||||||
|
IExecuteData,
|
||||||
IExecuteSingleFunctions,
|
IExecuteSingleFunctions,
|
||||||
IN8nRequestOperations,
|
IN8nRequestOperations,
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
@@ -77,6 +80,7 @@ export class RoutingNode {
|
|||||||
inputData: ITaskDataConnections,
|
inputData: ITaskDataConnections,
|
||||||
runIndex: number,
|
runIndex: number,
|
||||||
nodeType: INodeType,
|
nodeType: INodeType,
|
||||||
|
executeData: IExecuteData,
|
||||||
nodeExecuteFunctions: INodeExecuteFunctions,
|
nodeExecuteFunctions: INodeExecuteFunctions,
|
||||||
credentialsDecrypted?: ICredentialsDecrypted,
|
credentialsDecrypted?: ICredentialsDecrypted,
|
||||||
): Promise<INodeExecutionData[][] | null | undefined> {
|
): Promise<INodeExecutionData[][] | null | undefined> {
|
||||||
@@ -97,6 +101,7 @@ export class RoutingNode {
|
|||||||
inputData,
|
inputData,
|
||||||
this.node,
|
this.node,
|
||||||
this.additionalData,
|
this.additionalData,
|
||||||
|
executeData,
|
||||||
this.mode,
|
this.mode,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -119,6 +124,7 @@ export class RoutingNode {
|
|||||||
this.node,
|
this.node,
|
||||||
i,
|
i,
|
||||||
this.additionalData,
|
this.additionalData,
|
||||||
|
executeData,
|
||||||
this.mode,
|
this.mode,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -145,6 +151,7 @@ export class RoutingNode {
|
|||||||
value,
|
value,
|
||||||
i,
|
i,
|
||||||
runIndex,
|
runIndex,
|
||||||
|
executeData,
|
||||||
{ $credentials: credentials },
|
{ $credentials: credentials },
|
||||||
true,
|
true,
|
||||||
) as string;
|
) as string;
|
||||||
@@ -160,6 +167,7 @@ export class RoutingNode {
|
|||||||
value,
|
value,
|
||||||
i,
|
i,
|
||||||
runIndex,
|
runIndex,
|
||||||
|
executeData,
|
||||||
{ $credentials: credentials },
|
{ $credentials: credentials },
|
||||||
true,
|
true,
|
||||||
) as string | NodeParameterValue;
|
) as string | NodeParameterValue;
|
||||||
@@ -198,7 +206,7 @@ export class RoutingNode {
|
|||||||
returnData.push({ json: {}, error: error.message });
|
returnData.push({ json: {}, error: error.message });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw error;
|
throw new NodeApiError(this.node, error, { runIndex, itemIndex: i });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,9 +262,10 @@ export class RoutingNode {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(
|
throw new NodeOperationError(
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
this.node,
|
||||||
`The rootProperty "${action.properties.property}" could not be found on item.`,
|
`The rootProperty "${action.properties.property}" could not be found on item.`,
|
||||||
|
{ runIndex, itemIndex },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -269,6 +278,7 @@ export class RoutingNode {
|
|||||||
value,
|
value,
|
||||||
itemIndex,
|
itemIndex,
|
||||||
runIndex,
|
runIndex,
|
||||||
|
executeSingleFunctions.getExecuteData(),
|
||||||
{ $response: responseData, $value: parameterValue },
|
{ $response: responseData, $value: parameterValue },
|
||||||
false,
|
false,
|
||||||
) as IDataObject,
|
) as IDataObject,
|
||||||
@@ -315,6 +325,7 @@ export class RoutingNode {
|
|||||||
propertyValue,
|
propertyValue,
|
||||||
itemIndex,
|
itemIndex,
|
||||||
runIndex,
|
runIndex,
|
||||||
|
executeSingleFunctions.getExecuteData(),
|
||||||
{
|
{
|
||||||
$response: responseData,
|
$response: responseData,
|
||||||
$responseItem: item.json,
|
$responseItem: item.json,
|
||||||
@@ -338,6 +349,7 @@ export class RoutingNode {
|
|||||||
destinationProperty,
|
destinationProperty,
|
||||||
itemIndex,
|
itemIndex,
|
||||||
runIndex,
|
runIndex,
|
||||||
|
executeSingleFunctions.getExecuteData(),
|
||||||
{ $response: responseData, $value: parameterValue },
|
{ $response: responseData, $value: parameterValue },
|
||||||
false,
|
false,
|
||||||
) as string;
|
) as string;
|
||||||
@@ -512,8 +524,10 @@ export class RoutingNode {
|
|||||||
| IDataObject[]
|
| IDataObject[]
|
||||||
| undefined;
|
| undefined;
|
||||||
if (tempResponseValue === undefined) {
|
if (tempResponseValue === undefined) {
|
||||||
throw new Error(
|
throw new NodeOperationError(
|
||||||
|
this.node,
|
||||||
`The rootProperty "${properties.rootProperty}" could not be found on item.`,
|
`The rootProperty "${properties.rootProperty}" could not be found on item.`,
|
||||||
|
{ runIndex, itemIndex },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,6 +560,7 @@ export class RoutingNode {
|
|||||||
parameterValue: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
parameterValue: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
||||||
itemIndex: number,
|
itemIndex: number,
|
||||||
runIndex: number,
|
runIndex: number,
|
||||||
|
executeData: IExecuteData,
|
||||||
additionalKeys?: IWorkflowDataProxyAdditionalKeys,
|
additionalKeys?: IWorkflowDataProxyAdditionalKeys,
|
||||||
returnObjectAsString = false,
|
returnObjectAsString = false,
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | string {
|
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | string {
|
||||||
@@ -560,6 +575,7 @@ export class RoutingNode {
|
|||||||
this.mode,
|
this.mode,
|
||||||
this.additionalData.timezone,
|
this.additionalData.timezone,
|
||||||
additionalKeys ?? {},
|
additionalKeys ?? {},
|
||||||
|
executeData,
|
||||||
returnObjectAsString,
|
returnObjectAsString,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -617,6 +633,7 @@ export class RoutingNode {
|
|||||||
propertyValue,
|
propertyValue,
|
||||||
itemIndex,
|
itemIndex,
|
||||||
runIndex,
|
runIndex,
|
||||||
|
executeSingleFunctions.getExecuteData(),
|
||||||
{ ...additionalKeys, $value: parameterValue },
|
{ ...additionalKeys, $value: parameterValue },
|
||||||
true,
|
true,
|
||||||
) as string;
|
) as string;
|
||||||
@@ -633,6 +650,7 @@ export class RoutingNode {
|
|||||||
propertyName,
|
propertyName,
|
||||||
itemIndex,
|
itemIndex,
|
||||||
runIndex,
|
runIndex,
|
||||||
|
executeSingleFunctions.getExecuteData(),
|
||||||
additionalKeys,
|
additionalKeys,
|
||||||
true,
|
true,
|
||||||
) as string;
|
) as string;
|
||||||
@@ -647,6 +665,7 @@ export class RoutingNode {
|
|||||||
valueString,
|
valueString,
|
||||||
itemIndex,
|
itemIndex,
|
||||||
runIndex,
|
runIndex,
|
||||||
|
executeSingleFunctions.getExecuteData(),
|
||||||
{ ...additionalKeys, $value: value },
|
{ ...additionalKeys, $value: value },
|
||||||
true,
|
true,
|
||||||
) as string;
|
) as string;
|
||||||
@@ -680,6 +699,7 @@ export class RoutingNode {
|
|||||||
paginateValue,
|
paginateValue,
|
||||||
itemIndex,
|
itemIndex,
|
||||||
runIndex,
|
runIndex,
|
||||||
|
executeSingleFunctions.getExecuteData(),
|
||||||
{ ...additionalKeys, $value: parameterValue },
|
{ ...additionalKeys, $value: parameterValue },
|
||||||
true,
|
true,
|
||||||
) as string;
|
) as string;
|
||||||
@@ -701,6 +721,7 @@ export class RoutingNode {
|
|||||||
maxResultsValue,
|
maxResultsValue,
|
||||||
itemIndex,
|
itemIndex,
|
||||||
runIndex,
|
runIndex,
|
||||||
|
executeSingleFunctions.getExecuteData(),
|
||||||
{ ...additionalKeys, $value: parameterValue },
|
{ ...additionalKeys, $value: parameterValue },
|
||||||
true,
|
true,
|
||||||
) as string;
|
) as string;
|
||||||
|
|||||||
@@ -48,8 +48,10 @@ import {
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
IConnection,
|
IConnection,
|
||||||
IDataObject,
|
|
||||||
IConnectedNode,
|
IConnectedNode,
|
||||||
|
IDataObject,
|
||||||
|
IExecuteData,
|
||||||
|
INodeConnection,
|
||||||
IObservableObject,
|
IObservableObject,
|
||||||
IRun,
|
IRun,
|
||||||
IRunNodeResponse,
|
IRunNodeResponse,
|
||||||
@@ -805,34 +807,28 @@ export class Workflow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns via which output of the parent-node the node
|
* Returns via which output of the parent-node and index the current node
|
||||||
* is connected to.
|
* they are connected
|
||||||
*
|
*
|
||||||
* @param {string} nodeName The node to check how it is connected with parent node
|
* @param {string} nodeName The node to check how it is connected with parent node
|
||||||
* @param {string} parentNodeName The parent node to get the output index of
|
* @param {string} parentNodeName The parent node to get the output index of
|
||||||
* @param {string} [type='main']
|
* @param {string} [type='main']
|
||||||
* @param {*} [depth=-1]
|
* @param {*} [depth=-1]
|
||||||
* @param {string[]} [checkedNodes]
|
* @param {string[]} [checkedNodes]
|
||||||
* @returns {(number | undefined)}
|
* @returns {(INodeConnection | undefined)}
|
||||||
* @memberof Workflow
|
* @memberof Workflow
|
||||||
*/
|
*/
|
||||||
getNodeConnectionOutputIndex(
|
getNodeConnectionIndexes(
|
||||||
nodeName: string,
|
nodeName: string,
|
||||||
parentNodeName: string,
|
parentNodeName: string,
|
||||||
type = 'main',
|
type = 'main',
|
||||||
depth = -1,
|
depth = -1,
|
||||||
checkedNodes?: string[],
|
checkedNodes?: string[],
|
||||||
): number | undefined {
|
): INodeConnection | undefined {
|
||||||
const node = this.getNode(parentNodeName);
|
const node = this.getNode(parentNodeName);
|
||||||
if (node === null) {
|
if (node === null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion) as INodeType;
|
|
||||||
if (nodeType.description.outputs.length === 1) {
|
|
||||||
// If the parent node has only one output, it can only be connected
|
|
||||||
// to that one. So no further checking is required.
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
depth = depth === -1 ? -1 : depth;
|
depth = depth === -1 ? -1 : depth;
|
||||||
const newDepth = depth === -1 ? depth : depth - 1;
|
const newDepth = depth === -1 ? depth : depth - 1;
|
||||||
@@ -860,11 +856,19 @@ export class Workflow {
|
|||||||
|
|
||||||
checkedNodes.push(nodeName);
|
checkedNodes.push(nodeName);
|
||||||
|
|
||||||
let outputIndex: number | undefined;
|
let outputIndex: INodeConnection | undefined;
|
||||||
for (const connectionsByIndex of this.connectionsByDestinationNode[nodeName][type]) {
|
for (const connectionsByIndex of this.connectionsByDestinationNode[nodeName][type]) {
|
||||||
for (const connection of connectionsByIndex) {
|
for (
|
||||||
|
let destinationIndex = 0;
|
||||||
|
destinationIndex < connectionsByIndex.length;
|
||||||
|
destinationIndex++
|
||||||
|
) {
|
||||||
|
const connection = connectionsByIndex[destinationIndex];
|
||||||
if (parentNodeName === connection.node) {
|
if (parentNodeName === connection.node) {
|
||||||
return connection.index;
|
return {
|
||||||
|
sourceIndex: connection.index,
|
||||||
|
destinationIndex,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkedNodes.includes(connection.node)) {
|
if (checkedNodes.includes(connection.node)) {
|
||||||
@@ -872,7 +876,7 @@ export class Workflow {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
outputIndex = this.getNodeConnectionOutputIndex(
|
outputIndex = this.getNodeConnectionIndexes(
|
||||||
connection.node,
|
connection.node,
|
||||||
parentNodeName,
|
parentNodeName,
|
||||||
type,
|
type,
|
||||||
@@ -1157,8 +1161,7 @@ export class Workflow {
|
|||||||
/**
|
/**
|
||||||
* Executes the given node.
|
* Executes the given node.
|
||||||
*
|
*
|
||||||
* @param {INode} node
|
* @param {IExecuteData} executionData
|
||||||
* @param {ITaskDataConnections} inputData
|
|
||||||
* @param {IRunExecutionData} runExecutionData
|
* @param {IRunExecutionData} runExecutionData
|
||||||
* @param {number} runIndex
|
* @param {number} runIndex
|
||||||
* @param {IWorkflowExecuteAdditionalData} additionalData
|
* @param {IWorkflowExecuteAdditionalData} additionalData
|
||||||
@@ -1168,14 +1171,16 @@ export class Workflow {
|
|||||||
* @memberof Workflow
|
* @memberof Workflow
|
||||||
*/
|
*/
|
||||||
async runNode(
|
async runNode(
|
||||||
node: INode,
|
executionData: IExecuteData,
|
||||||
inputData: ITaskDataConnections,
|
|
||||||
runExecutionData: IRunExecutionData,
|
runExecutionData: IRunExecutionData,
|
||||||
runIndex: number,
|
runIndex: number,
|
||||||
additionalData: IWorkflowExecuteAdditionalData,
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
nodeExecuteFunctions: INodeExecuteFunctions,
|
nodeExecuteFunctions: INodeExecuteFunctions,
|
||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
): Promise<IRunNodeResponse> {
|
): Promise<IRunNodeResponse> {
|
||||||
|
const { node } = executionData;
|
||||||
|
let inputData = executionData.data;
|
||||||
|
|
||||||
if (node.disabled === true) {
|
if (node.disabled === true) {
|
||||||
// If node is disabled simply pass the data through
|
// If node is disabled simply pass the data through
|
||||||
// return NodeRunHelpers.
|
// return NodeRunHelpers.
|
||||||
@@ -1254,6 +1259,7 @@ export class Workflow {
|
|||||||
node,
|
node,
|
||||||
itemIndex,
|
itemIndex,
|
||||||
additionalData,
|
additionalData,
|
||||||
|
executionData,
|
||||||
mode,
|
mode,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1283,6 +1289,7 @@ export class Workflow {
|
|||||||
inputData,
|
inputData,
|
||||||
node,
|
node,
|
||||||
additionalData,
|
additionalData,
|
||||||
|
executionData,
|
||||||
mode,
|
mode,
|
||||||
);
|
);
|
||||||
return { data: await nodeType.execute.call(thisArgs) };
|
return { data: await nodeType.execute.call(thisArgs) };
|
||||||
@@ -1356,7 +1363,13 @@ export class Workflow {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: await routingNode.runNode(inputData, runIndex, nodeType, nodeExecuteFunctions),
|
data: await routingNode.runNode(
|
||||||
|
inputData,
|
||||||
|
runIndex,
|
||||||
|
nodeType,
|
||||||
|
executionData,
|
||||||
|
nodeExecuteFunctions,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,15 @@ import * as jmespath from 'jmespath';
|
|||||||
|
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
import {
|
import {
|
||||||
|
ExpressionError,
|
||||||
IDataObject,
|
IDataObject,
|
||||||
|
IExecuteData,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
|
IPairedItemData,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
|
ISourceData,
|
||||||
|
ITaskData,
|
||||||
IWorkflowDataProxyAdditionalKeys,
|
IWorkflowDataProxyAdditionalKeys,
|
||||||
IWorkflowDataProxyData,
|
IWorkflowDataProxyData,
|
||||||
NodeHelpers,
|
NodeHelpers,
|
||||||
@@ -47,6 +52,8 @@ export class WorkflowDataProxy {
|
|||||||
|
|
||||||
private additionalKeys: IWorkflowDataProxyAdditionalKeys;
|
private additionalKeys: IWorkflowDataProxyAdditionalKeys;
|
||||||
|
|
||||||
|
private executeData: IExecuteData | undefined;
|
||||||
|
|
||||||
private defaultTimezone: string;
|
private defaultTimezone: string;
|
||||||
|
|
||||||
private timezone: string;
|
private timezone: string;
|
||||||
@@ -62,6 +69,7 @@ export class WorkflowDataProxy {
|
|||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
defaultTimezone: string,
|
defaultTimezone: string,
|
||||||
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
||||||
|
executeData?: IExecuteData,
|
||||||
defaultReturnRunIndex = -1,
|
defaultReturnRunIndex = -1,
|
||||||
selfData = {},
|
selfData = {},
|
||||||
) {
|
) {
|
||||||
@@ -78,7 +86,7 @@ export class WorkflowDataProxy {
|
|||||||
this.timezone = (this.workflow.settings.timezone as string) || this.defaultTimezone;
|
this.timezone = (this.workflow.settings.timezone as string) || this.defaultTimezone;
|
||||||
this.selfData = selfData;
|
this.selfData = selfData;
|
||||||
this.additionalKeys = additionalKeys;
|
this.additionalKeys = additionalKeys;
|
||||||
|
this.executeData = executeData;
|
||||||
Settings.defaultZone = this.timezone;
|
Settings.defaultZone = this.timezone;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,6 +210,7 @@ export class WorkflowDataProxy {
|
|||||||
that.mode,
|
that.mode,
|
||||||
that.timezone,
|
that.timezone,
|
||||||
that.additionalKeys,
|
that.additionalKeys,
|
||||||
|
that.executeData,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,17 +243,26 @@ export class WorkflowDataProxy {
|
|||||||
// Long syntax got used to return data from node in path
|
// Long syntax got used to return data from node in path
|
||||||
|
|
||||||
if (that.runExecutionData === null) {
|
if (that.runExecutionData === null) {
|
||||||
throw new Error(`Workflow did not run so do not have any execution-data.`);
|
throw new ExpressionError(`Workflow did not run so do not have any execution-data.`, {
|
||||||
|
runIndex: that.runIndex,
|
||||||
|
itemIndex: that.itemIndex,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!that.runExecutionData.resultData.runData.hasOwnProperty(nodeName)) {
|
if (!that.runExecutionData.resultData.runData.hasOwnProperty(nodeName)) {
|
||||||
if (that.workflow.getNode(nodeName)) {
|
if (that.workflow.getNode(nodeName)) {
|
||||||
throw new Error(
|
throw new ExpressionError(
|
||||||
`The node "${nodeName}" hasn't been executed yet, so you can't reference its output data`,
|
`The node "${nodeName}" hasn't been executed yet, so you can't reference its output data`,
|
||||||
|
{
|
||||||
|
runIndex: that.runIndex,
|
||||||
|
itemIndex: that.itemIndex,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
throw new Error(`No node called "${nodeName}" in this workflow`);
|
|
||||||
}
|
}
|
||||||
|
throw new ExpressionError(`No node called "${nodeName}" in this workflow`, {
|
||||||
|
runIndex: that.runIndex,
|
||||||
|
itemIndex: that.itemIndex,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
runIndex = runIndex === undefined ? that.defaultReturnRunIndex : runIndex;
|
runIndex = runIndex === undefined ? that.defaultReturnRunIndex : runIndex;
|
||||||
@@ -252,32 +270,42 @@ export class WorkflowDataProxy {
|
|||||||
runIndex === -1 ? that.runExecutionData.resultData.runData[nodeName].length - 1 : runIndex;
|
runIndex === -1 ? that.runExecutionData.resultData.runData[nodeName].length - 1 : runIndex;
|
||||||
|
|
||||||
if (that.runExecutionData.resultData.runData[nodeName].length <= runIndex) {
|
if (that.runExecutionData.resultData.runData[nodeName].length <= runIndex) {
|
||||||
throw new Error(`Run ${runIndex} of node "${nodeName}" not found`);
|
throw new ExpressionError(`Run ${runIndex} of node "${nodeName}" not found`, {
|
||||||
|
runIndex: that.runIndex,
|
||||||
|
itemIndex: that.itemIndex,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const taskData = that.runExecutionData.resultData.runData[nodeName][runIndex].data!;
|
const taskData = that.runExecutionData.resultData.runData[nodeName][runIndex].data!;
|
||||||
|
|
||||||
if (taskData.main === null || !taskData.main.length || taskData.main[0] === null) {
|
if (taskData.main === null || !taskData.main.length || taskData.main[0] === null) {
|
||||||
// throw new Error(`No data found for item-index: "${itemIndex}"`);
|
// throw new Error(`No data found for item-index: "${itemIndex}"`);
|
||||||
throw new Error(`No data found from "main" input.`);
|
throw new ExpressionError(`No data found from "main" input.`, {
|
||||||
|
runIndex: that.runIndex,
|
||||||
|
itemIndex: that.itemIndex,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check from which output to read the data.
|
// Check from which output to read the data.
|
||||||
// Depends on how the nodes are connected.
|
// Depends on how the nodes are connected.
|
||||||
// (example "IF" node. If node is connected to "true" or to "false" output)
|
// (example "IF" node. If node is connected to "true" or to "false" output)
|
||||||
if (outputIndex === undefined) {
|
if (outputIndex === undefined) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
const nodeConnection = that.workflow.getNodeConnectionIndexes(
|
||||||
const outputIndex = that.workflow.getNodeConnectionOutputIndex(
|
|
||||||
that.activeNodeName,
|
that.activeNodeName,
|
||||||
nodeName,
|
nodeName,
|
||||||
'main',
|
'main',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (outputIndex === undefined) {
|
if (nodeConnection === undefined) {
|
||||||
throw new Error(
|
throw new ExpressionError(
|
||||||
`The node "${that.activeNodeName}" is not connected with node "${nodeName}" so no data can get returned from it.`,
|
`The node "${that.activeNodeName}" is not connected with node "${nodeName}" so no data can get returned from it.`,
|
||||||
|
{
|
||||||
|
runIndex: that.runIndex,
|
||||||
|
itemIndex: that.itemIndex,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
outputIndex = nodeConnection.sourceIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outputIndex === undefined) {
|
if (outputIndex === undefined) {
|
||||||
@@ -285,7 +313,10 @@ export class WorkflowDataProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (taskData.main.length <= outputIndex) {
|
if (taskData.main.length <= outputIndex) {
|
||||||
throw new Error(`Node "${nodeName}" has no branch with index ${outputIndex}.`);
|
throw new ExpressionError(`Node "${nodeName}" has no branch with index ${outputIndex}.`, {
|
||||||
|
runIndex: that.runIndex,
|
||||||
|
itemIndex: that.itemIndex,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
executionData = taskData.main[outputIndex] as INodeExecutionData[];
|
executionData = taskData.main[outputIndex] as INodeExecutionData[];
|
||||||
@@ -328,9 +359,11 @@ export class WorkflowDataProxy {
|
|||||||
|
|
||||||
if (['binary', 'data', 'json'].includes(name)) {
|
if (['binary', 'data', 'json'].includes(name)) {
|
||||||
const executionData = that.getNodeExecutionData(nodeName, shortSyntax, undefined);
|
const executionData = that.getNodeExecutionData(nodeName, shortSyntax, undefined);
|
||||||
|
|
||||||
if (executionData.length <= that.itemIndex) {
|
if (executionData.length <= that.itemIndex) {
|
||||||
throw new Error(`No data found for item-index: "${that.itemIndex}"`);
|
throw new ExpressionError(`No data found for item-index: "${that.itemIndex}"`, {
|
||||||
|
runIndex: that.runIndex,
|
||||||
|
itemIndex: that.itemIndex,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['data', 'json'].includes(name)) {
|
if (['data', 'json'].includes(name)) {
|
||||||
@@ -486,10 +519,177 @@ export class WorkflowDataProxy {
|
|||||||
return jmespath.search(data, query);
|
return jmespath.search(data, query);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createExpressionError = (
|
||||||
|
message: string,
|
||||||
|
context?: {
|
||||||
|
messageTemplate?: string;
|
||||||
|
description?: string;
|
||||||
|
causeDetailed?: string;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
return new ExpressionError(message, {
|
||||||
|
runIndex: that.runIndex,
|
||||||
|
itemIndex: that.itemIndex,
|
||||||
|
failExecution: true,
|
||||||
|
...context,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPairedItem = (
|
||||||
|
destinationNodeName: string,
|
||||||
|
incomingSourceData: ISourceData | null,
|
||||||
|
pairedItem: IPairedItemData,
|
||||||
|
): INodeExecutionData | null => {
|
||||||
|
let taskData: ITaskData;
|
||||||
|
|
||||||
|
let sourceData: ISourceData | null = incomingSourceData;
|
||||||
|
|
||||||
|
if (typeof pairedItem === 'number') {
|
||||||
|
pairedItem = {
|
||||||
|
item: pairedItem,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
while (sourceData !== null && destinationNodeName !== sourceData.previousNode) {
|
||||||
|
taskData =
|
||||||
|
that.runExecutionData!.resultData.runData[sourceData.previousNode][
|
||||||
|
sourceData?.previousNodeRun || 0
|
||||||
|
];
|
||||||
|
|
||||||
|
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',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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)`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemPreviousNode: INodeExecutionData =
|
||||||
|
taskData.data!.main[previousNodeOutput]![pairedItem.item];
|
||||||
|
|
||||||
|
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)`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(itemPreviousNode.pairedItem)) {
|
||||||
|
// Item is based on multiple items so check all of them
|
||||||
|
const results = itemPreviousNode.pairedItem
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||||
|
.map((item) => {
|
||||||
|
try {
|
||||||
|
const itemInput = item.input || 0;
|
||||||
|
if (itemInput >= taskData.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 Error('Not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return getPairedItem(destinationNodeName, taskData.source[itemInput], item);
|
||||||
|
} catch (error) {
|
||||||
|
// Means pairedItem could not be found
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((result) => result !== null);
|
||||||
|
|
||||||
|
if (results.length !== 1) {
|
||||||
|
throw createExpressionError('Invalid expression', {
|
||||||
|
messageTemplate: 'Invalid expression under ‘%%PARAMETER%%’',
|
||||||
|
description: `The expression uses data in node ‘${destinationNodeName}’ but there is more than one matching item in that node`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return results[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// pairedItem is not an array
|
||||||
|
if (typeof itemPreviousNode.pairedItem === 'number') {
|
||||||
|
pairedItem = {
|
||||||
|
item: itemPreviousNode.pairedItem,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
pairedItem = itemPreviousNode.pairedItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemInput = pairedItem.input || 0;
|
||||||
|
if (itemInput >= taskData.source.length) {
|
||||||
|
if (taskData.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%%’',
|
||||||
|
description: `The expression uses data in node ‘${destinationNodeName}’ but there is no path back to it. Please check this node is connected to node ‘${that.activeNodeName}’ (there can be other nodes in between).`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// `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)`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
taskData =
|
||||||
|
that.runExecutionData!.resultData.runData[sourceData.previousNode][
|
||||||
|
sourceData?.previousNodeRun || 0
|
||||||
|
];
|
||||||
|
|
||||||
|
const previousNodeOutput = sourceData.previousNodeOutput || 0;
|
||||||
|
if (previousNodeOutput >= taskData.data!.main.length) {
|
||||||
|
// `Could not resolve pairedItem as the node output '${previousNodeOutput}' 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 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)`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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)`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return taskData.data!.main[previousNodeOutput]![pairedItem.item];
|
||||||
|
};
|
||||||
|
|
||||||
const base = {
|
const base = {
|
||||||
$: (nodeName: string) => {
|
$: (nodeName: string) => {
|
||||||
if (!nodeName) {
|
if (!nodeName) {
|
||||||
throw new Error(`When calling $(), please specify a node`);
|
throw new ExpressionError('When calling $(), please specify a node', {
|
||||||
|
runIndex: that.runIndex,
|
||||||
|
itemIndex: that.itemIndex,
|
||||||
|
failExecution: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Proxy(
|
return new Proxy(
|
||||||
@@ -497,12 +697,58 @@ export class WorkflowDataProxy {
|
|||||||
{
|
{
|
||||||
get(target, property, receiver) {
|
get(target, property, receiver) {
|
||||||
if (property === 'pairedItem') {
|
if (property === 'pairedItem') {
|
||||||
return () => {
|
return (itemIndex?: number) => {
|
||||||
const executionData = getNodeOutput(nodeName, 0, that.runIndex);
|
if (itemIndex === undefined) {
|
||||||
if (executionData[that.itemIndex]) {
|
itemIndex = that.itemIndex;
|
||||||
return executionData[that.itemIndex];
|
|
||||||
}
|
}
|
||||||
return undefined;
|
|
||||||
|
const executionData = that.connectionInputData;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
if (pairedItem === undefined) {
|
||||||
|
throw new ExpressionError('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 ‘${that.activeNodeName}‘`,
|
||||||
|
causeDetailed: `Missing pairedItem data (node ‘${that.activeNodeName}‘ did probably not supply it)`,
|
||||||
|
runIndex: that.runIndex,
|
||||||
|
itemIndex,
|
||||||
|
failExecution: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!that.executeData?.source) {
|
||||||
|
throw new ExpressionError('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: `Missing sourceData (probably an internal error)`,
|
||||||
|
runIndex: that.runIndex,
|
||||||
|
itemIndex,
|
||||||
|
failExecution: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before resolving the pairedItem make sure that the requested node comes in the
|
||||||
|
// graph before the current one
|
||||||
|
const parentNodes = that.workflow.getParentNodes(that.activeNodeName);
|
||||||
|
if (!parentNodes.includes(nodeName)) {
|
||||||
|
throw new ExpressionError('Invalid expression', {
|
||||||
|
messageTemplate: 'Invalid expression under ‘%%PARAMETER%%’',
|
||||||
|
description: `The expression uses data in node ‘${nodeName}’ but there is no path back to it. Please check this node is connected to node ‘${that.activeNodeName}’ (there can be other nodes in between).`,
|
||||||
|
runIndex: that.runIndex,
|
||||||
|
itemIndex,
|
||||||
|
failExecution: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceData: ISourceData = that.executeData?.source.main![
|
||||||
|
pairedItem.input || 0
|
||||||
|
] as ISourceData;
|
||||||
|
|
||||||
|
return getPairedItem(nodeName, sourceData, pairedItem);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (property === 'item') {
|
if (property === 'item') {
|
||||||
@@ -513,6 +759,7 @@ export class WorkflowDataProxy {
|
|||||||
runIndex = that.runIndex;
|
runIndex = that.runIndex;
|
||||||
}
|
}
|
||||||
const executionData = getNodeOutput(nodeName, branchIndex, runIndex);
|
const executionData = getNodeOutput(nodeName, branchIndex, runIndex);
|
||||||
|
|
||||||
if (executionData[itemIndex]) {
|
if (executionData[itemIndex]) {
|
||||||
return executionData[itemIndex];
|
return executionData[itemIndex];
|
||||||
}
|
}
|
||||||
@@ -645,6 +892,7 @@ export class WorkflowDataProxy {
|
|||||||
that.mode,
|
that.mode,
|
||||||
that.timezone,
|
that.timezone,
|
||||||
that.additionalKeys,
|
that.additionalKeys,
|
||||||
|
that.executeData,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
$item: (itemIndex: number, runIndex?: number) => {
|
$item: (itemIndex: number, runIndex?: number) => {
|
||||||
@@ -660,6 +908,7 @@ export class WorkflowDataProxy {
|
|||||||
that.mode,
|
that.mode,
|
||||||
that.defaultTimezone,
|
that.defaultTimezone,
|
||||||
that.additionalKeys,
|
that.additionalKeys,
|
||||||
|
that.executeData,
|
||||||
defaultReturnRunIndex,
|
defaultReturnRunIndex,
|
||||||
);
|
);
|
||||||
return dataProxy.getDataProxy();
|
return dataProxy.getDataProxy();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import * as ObservableObject from './ObservableObject';
|
|||||||
export * from './DeferredPromise';
|
export * from './DeferredPromise';
|
||||||
export * from './Interfaces';
|
export * from './Interfaces';
|
||||||
export * from './Expression';
|
export * from './Expression';
|
||||||
|
export * from './ExpressionError';
|
||||||
export * from './NodeErrors';
|
export * from './NodeErrors';
|
||||||
export * as TelemetryHelpers from './TelemetryHelpers';
|
export * as TelemetryHelpers from './TelemetryHelpers';
|
||||||
export * from './RoutingNode';
|
export * from './RoutingNode';
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
ICredentialsEncrypted,
|
ICredentialsEncrypted,
|
||||||
ICredentialsHelper,
|
ICredentialsHelper,
|
||||||
IDataObject,
|
IDataObject,
|
||||||
|
IExecuteData,
|
||||||
IExecuteFunctions,
|
IExecuteFunctions,
|
||||||
IExecuteResponsePromiseData,
|
IExecuteResponsePromiseData,
|
||||||
IExecuteSingleFunctions,
|
IExecuteSingleFunctions,
|
||||||
@@ -146,6 +147,7 @@ export function getNodeParameter(
|
|||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
timezone: string,
|
timezone: string,
|
||||||
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
additionalKeys: IWorkflowDataProxyAdditionalKeys,
|
||||||
|
executeData: IExecuteData,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object {
|
): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object {
|
||||||
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
||||||
@@ -189,6 +191,7 @@ export function getExecuteFunctions(
|
|||||||
node: INode,
|
node: INode,
|
||||||
itemIndex: number,
|
itemIndex: number,
|
||||||
additionalData: IWorkflowExecuteAdditionalData,
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
|
executeData: IExecuteData,
|
||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
): IExecuteFunctions {
|
): IExecuteFunctions {
|
||||||
return ((workflow, runExecutionData, connectionInputData, inputData, node) => {
|
return ((workflow, runExecutionData, connectionInputData, inputData, node) => {
|
||||||
@@ -272,6 +275,9 @@ export function getExecuteFunctions(
|
|||||||
getTimezone: (): string => {
|
getTimezone: (): string => {
|
||||||
return additionalData.timezone;
|
return additionalData.timezone;
|
||||||
},
|
},
|
||||||
|
getExecuteData: (): IExecuteData => {
|
||||||
|
return executeData;
|
||||||
|
},
|
||||||
getWorkflow: () => {
|
getWorkflow: () => {
|
||||||
return {
|
return {
|
||||||
id: workflow.id,
|
id: workflow.id,
|
||||||
@@ -291,6 +297,7 @@ export function getExecuteFunctions(
|
|||||||
mode,
|
mode,
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
{},
|
{},
|
||||||
|
executeData,
|
||||||
);
|
);
|
||||||
return dataProxy.getDataProxy();
|
return dataProxy.getDataProxy();
|
||||||
},
|
},
|
||||||
@@ -375,6 +382,7 @@ export function getExecuteSingleFunctions(
|
|||||||
node: INode,
|
node: INode,
|
||||||
itemIndex: number,
|
itemIndex: number,
|
||||||
additionalData: IWorkflowExecuteAdditionalData,
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
|
executeData: IExecuteData,
|
||||||
mode: WorkflowExecuteMode,
|
mode: WorkflowExecuteMode,
|
||||||
): IExecuteSingleFunctions {
|
): IExecuteSingleFunctions {
|
||||||
return ((workflow, runExecutionData, connectionInputData, inputData, node, itemIndex) => {
|
return ((workflow, runExecutionData, connectionInputData, inputData, node, itemIndex) => {
|
||||||
@@ -431,6 +439,9 @@ export function getExecuteSingleFunctions(
|
|||||||
getTimezone: (): string => {
|
getTimezone: (): string => {
|
||||||
return additionalData.timezone;
|
return additionalData.timezone;
|
||||||
},
|
},
|
||||||
|
getExecuteData: (): IExecuteData => {
|
||||||
|
return executeData;
|
||||||
|
},
|
||||||
getNodeParameter: (
|
getNodeParameter: (
|
||||||
parameterName: string,
|
parameterName: string,
|
||||||
fallbackValue?: any,
|
fallbackValue?: any,
|
||||||
@@ -473,6 +484,7 @@ export function getExecuteSingleFunctions(
|
|||||||
mode,
|
mode,
|
||||||
additionalData.timezone,
|
additionalData.timezone,
|
||||||
{},
|
{},
|
||||||
|
executeData,
|
||||||
);
|
);
|
||||||
return dataProxy.getDataProxy();
|
return dataProxy.getDataProxy();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
INodeExecuteFunctions,
|
INodeExecuteFunctions,
|
||||||
IN8nRequestOperations,
|
IN8nRequestOperations,
|
||||||
INodeCredentialDescription,
|
INodeCredentialDescription,
|
||||||
|
IExecuteData,
|
||||||
} from '../src';
|
} from '../src';
|
||||||
|
|
||||||
import * as Helpers from './Helpers';
|
import * as Helpers from './Helpers';
|
||||||
@@ -657,6 +658,11 @@ describe('RoutingNode', () => {
|
|||||||
node,
|
node,
|
||||||
itemIndex,
|
itemIndex,
|
||||||
additionalData,
|
additionalData,
|
||||||
|
{
|
||||||
|
node,
|
||||||
|
data: {},
|
||||||
|
source: null,
|
||||||
|
},
|
||||||
mode,
|
mode,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1636,6 +1642,12 @@ describe('RoutingNode', () => {
|
|||||||
mode,
|
mode,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const executeData = {
|
||||||
|
data: {},
|
||||||
|
node,
|
||||||
|
source: null,
|
||||||
|
} as IExecuteData;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const nodeExecuteFunctions: INodeExecuteFunctions = {
|
const nodeExecuteFunctions: INodeExecuteFunctions = {
|
||||||
getExecuteFunctions: () => {
|
getExecuteFunctions: () => {
|
||||||
@@ -1648,6 +1660,7 @@ describe('RoutingNode', () => {
|
|||||||
node,
|
node,
|
||||||
itemIndex,
|
itemIndex,
|
||||||
additionalData,
|
additionalData,
|
||||||
|
executeData,
|
||||||
mode,
|
mode,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -1661,6 +1674,7 @@ describe('RoutingNode', () => {
|
|||||||
node,
|
node,
|
||||||
itemIndex,
|
itemIndex,
|
||||||
additionalData,
|
additionalData,
|
||||||
|
executeData,
|
||||||
mode,
|
mode,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -1670,6 +1684,7 @@ describe('RoutingNode', () => {
|
|||||||
inputData,
|
inputData,
|
||||||
runIndex,
|
runIndex,
|
||||||
nodeType,
|
nodeType,
|
||||||
|
executeData,
|
||||||
nodeExecuteFunctions,
|
nodeExecuteFunctions,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1243,6 +1243,7 @@ describe('Workflow', () => {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
source: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ describe('WorkflowDataProxy', () => {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
source: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
Rename: [
|
Rename: [
|
||||||
@@ -122,6 +123,7 @@ describe('WorkflowDataProxy', () => {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
source: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user