mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-21 02:56:40 -08:00
make declarative nodes work more like regular nodes
This commit is contained in:
parent
1a915239c6
commit
57f6c33928
|
@ -2,6 +2,8 @@ import { Service } from '@n8n/di';
|
|||
import type { NeededNodeType } from '@n8n/task-runner';
|
||||
import type { Dirent } from 'fs';
|
||||
import { readdir } from 'fs/promises';
|
||||
import { RoutingNode } from 'n8n-core';
|
||||
import type { ExecuteContext } from 'n8n-core';
|
||||
import type { INodeType, INodeTypeDescription, INodeTypes, IVersionedNodeType } from 'n8n-workflow';
|
||||
import { ApplicationError, NodeHelpers } from 'n8n-workflow';
|
||||
import { join, dirname } from 'path';
|
||||
|
@ -60,6 +62,12 @@ export class NodeTypes implements INodeTypes {
|
|||
description: { value: clonedDescription },
|
||||
}) as INodeType;
|
||||
const tool = this.loadNodesAndCredentials.convertNodeToAiTool(clonedNode);
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
tool.execute ??= async function (this: ExecuteContext) {
|
||||
const routingNode = new RoutingNode(this, tool);
|
||||
const data = await routingNode.runNode();
|
||||
return data ?? [];
|
||||
};
|
||||
loadedNodes[nodeType + 'Tool'] = { sourcePath: '', type: tool };
|
||||
return tool;
|
||||
}
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
import { Service } from '@n8n/di';
|
||||
import get from 'lodash/get';
|
||||
import {
|
||||
CredentialTestContext,
|
||||
ErrorReporter,
|
||||
ExecuteContext,
|
||||
Logger,
|
||||
NodeExecuteFunctions,
|
||||
RoutingNode,
|
||||
isObjectLiteral,
|
||||
} from 'n8n-core';
|
||||
|
@ -29,6 +30,7 @@ import type {
|
|||
INodeTypes,
|
||||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
IExecuteData,
|
||||
} from 'n8n-workflow';
|
||||
import { VersionedNodeType, NodeHelpers, Workflow, ApplicationError } from 'n8n-workflow';
|
||||
|
||||
|
@ -205,9 +207,8 @@ export class CredentialsTester {
|
|||
|
||||
if (typeof credentialTestFunction === 'function') {
|
||||
// The credentials get tested via a function that is defined on the node
|
||||
const credentialTestFunctions = NodeExecuteFunctions.getCredentialTestFunctions();
|
||||
|
||||
return credentialTestFunction.call(credentialTestFunctions, credentialsDecrypted);
|
||||
const context = new CredentialTestContext();
|
||||
return credentialTestFunction.call(context, credentialsDecrypted);
|
||||
}
|
||||
|
||||
// Credentials get tested via request instructions
|
||||
|
@ -293,25 +294,25 @@ export class CredentialsTester {
|
|||
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(user.id, node.parameters);
|
||||
|
||||
const routingNode = new RoutingNode(
|
||||
// TODO: define `credentialTestFunction` for declarative nodes, to make this method smaller
|
||||
const executeData: IExecuteData = { node, data: {}, source: null };
|
||||
const executeFunctions = new ExecuteContext(
|
||||
workflow,
|
||||
node,
|
||||
connectionInputData,
|
||||
runExecutionData ?? null,
|
||||
additionalData,
|
||||
mode,
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
connectionInputData,
|
||||
inputData,
|
||||
executeData,
|
||||
[],
|
||||
);
|
||||
const routingNode = new RoutingNode(executeFunctions, nodeTypeCopy, credentialsDecrypted);
|
||||
|
||||
let response: INodeExecutionData[][] | null | undefined;
|
||||
|
||||
try {
|
||||
response = await routingNode.runNode(
|
||||
inputData,
|
||||
runIndex,
|
||||
nodeTypeCopy,
|
||||
{ node, data: {}, source: null },
|
||||
credentialsDecrypted,
|
||||
);
|
||||
response = await routingNode.runNode();
|
||||
} catch (error) {
|
||||
this.errorReporter.error(error);
|
||||
// Do not fail any requests to allow custom error messages and
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Service } from '@n8n/di';
|
||||
import { LoadOptionsContext, RoutingNode, LocalLoadOptionsContext } from 'n8n-core';
|
||||
import { LoadOptionsContext, RoutingNode, LocalLoadOptionsContext, ExecuteContext } from 'n8n-core';
|
||||
import type {
|
||||
ILoadOptions,
|
||||
ILoadOptionsFunctions,
|
||||
|
@ -19,6 +19,7 @@ import type {
|
|||
NodeParameterValueType,
|
||||
IDataObject,
|
||||
ILocalLoadOptionsFunctions,
|
||||
IExecuteData,
|
||||
} from 'n8n-workflow';
|
||||
import { Workflow, ApplicationError } from 'n8n-workflow';
|
||||
|
||||
|
@ -103,17 +104,8 @@ export class DynamicNodeParametersService {
|
|||
const workflow = this.getWorkflow(nodeTypeAndVersion, currentNodeParameters, credentials);
|
||||
const node = workflow.nodes['Temp-Node'];
|
||||
|
||||
const routingNode = new RoutingNode(
|
||||
workflow,
|
||||
node,
|
||||
connectionInputData,
|
||||
runExecutionData ?? null,
|
||||
additionalData,
|
||||
mode,
|
||||
);
|
||||
|
||||
// Create copy of node-type with the single property we want to get the data off
|
||||
const tempNode: INodeType = {
|
||||
const tempNodeType: INodeType = {
|
||||
...nodeType,
|
||||
...{
|
||||
description: {
|
||||
|
@ -135,11 +127,25 @@ export class DynamicNodeParametersService {
|
|||
main: [[{ json: {} }]],
|
||||
};
|
||||
|
||||
const optionsData = await routingNode.runNode(inputData, runIndex, tempNode, {
|
||||
const executeData: IExecuteData = {
|
||||
node,
|
||||
source: null,
|
||||
data: {},
|
||||
});
|
||||
};
|
||||
const executeFunctions = new ExecuteContext(
|
||||
workflow,
|
||||
node,
|
||||
additionalData,
|
||||
mode,
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
connectionInputData,
|
||||
inputData,
|
||||
executeData,
|
||||
[],
|
||||
);
|
||||
const routingNode = new RoutingNode(executeFunctions, tempNodeType);
|
||||
const optionsData = await routingNode.runNode();
|
||||
|
||||
if (optionsData?.length === 0) {
|
||||
return [];
|
||||
|
|
|
@ -744,14 +744,17 @@ describe('RoutingNode', () => {
|
|||
nodeTypes,
|
||||
});
|
||||
|
||||
const routingNode = new RoutingNode(
|
||||
const executeFunctions = mock<executionContexts.ExecuteContext>();
|
||||
Object.assign(executeFunctions, {
|
||||
runIndex,
|
||||
additionalData,
|
||||
workflow,
|
||||
node,
|
||||
connectionInputData,
|
||||
runExecutionData ?? null,
|
||||
additionalData,
|
||||
mode,
|
||||
);
|
||||
connectionInputData,
|
||||
runExecutionData,
|
||||
});
|
||||
const routingNode = new RoutingNode(executeFunctions, nodeType);
|
||||
|
||||
const executeSingleFunctions = getExecuteSingleFunctions(
|
||||
workflow,
|
||||
|
@ -1947,15 +1950,6 @@ describe('RoutingNode', () => {
|
|||
nodeTypes,
|
||||
});
|
||||
|
||||
const routingNode = new RoutingNode(
|
||||
workflow,
|
||||
node,
|
||||
connectionInputData,
|
||||
runExecutionData ?? null,
|
||||
additionalData,
|
||||
mode,
|
||||
);
|
||||
|
||||
const executeData = {
|
||||
data: {},
|
||||
node,
|
||||
|
@ -1963,6 +1957,18 @@ describe('RoutingNode', () => {
|
|||
} as IExecuteData;
|
||||
|
||||
const executeFunctions = mock<executionContexts.ExecuteContext>();
|
||||
Object.assign(executeFunctions, {
|
||||
executeData,
|
||||
inputData,
|
||||
runIndex,
|
||||
additionalData,
|
||||
workflow,
|
||||
node,
|
||||
mode,
|
||||
connectionInputData,
|
||||
runExecutionData,
|
||||
});
|
||||
|
||||
const executeSingleFunctions = getExecuteSingleFunctions(
|
||||
workflow,
|
||||
runExecutionData,
|
||||
|
@ -1971,7 +1977,6 @@ describe('RoutingNode', () => {
|
|||
itemIndex,
|
||||
);
|
||||
|
||||
jest.spyOn(executionContexts, 'ExecuteContext').mockReturnValue(executeFunctions);
|
||||
jest
|
||||
.spyOn(executionContexts, 'ExecuteSingleContext')
|
||||
.mockReturnValue(executeSingleFunctions);
|
||||
|
@ -2004,7 +2009,8 @@ describe('RoutingNode', () => {
|
|||
? testData.input.node.parameters[parameterName]
|
||||
: (getNodeParameter(parameterName) ?? {});
|
||||
|
||||
const result = await routingNode.runNode(inputData, runIndex, nodeType, executeData);
|
||||
const routingNode = new RoutingNode(executeFunctions, nodeType);
|
||||
const result = await routingNode.runNode();
|
||||
|
||||
if (testData.input.specialTestOptions?.sleepCalls) {
|
||||
expect(spy.mock.calls).toEqual(testData.input.specialTestOptions?.sleepCalls);
|
||||
|
|
|
@ -40,7 +40,6 @@ export const describeCommonTests = (
|
|||
executeData: IExecuteData;
|
||||
},
|
||||
) => {
|
||||
// @ts-expect-error `additionalData` is private
|
||||
const additionalData = context.additionalData as MockProxy<IWorkflowExecuteAdditionalData>;
|
||||
|
||||
describe('getExecutionCancelSignal', () => {
|
||||
|
|
|
@ -41,11 +41,11 @@ export class BaseExecuteContext extends NodeExecutionContext {
|
|||
node: INode,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
protected readonly runExecutionData: IRunExecutionData,
|
||||
readonly runExecutionData: IRunExecutionData,
|
||||
runIndex: number,
|
||||
protected readonly connectionInputData: INodeExecutionData[],
|
||||
protected readonly inputData: ITaskDataConnections,
|
||||
protected readonly executeData: IExecuteData,
|
||||
readonly connectionInputData: INodeExecutionData[],
|
||||
readonly inputData: ITaskDataConnections,
|
||||
readonly executeData: IExecuteData,
|
||||
protected readonly abortSignal?: AbortSignal,
|
||||
) {
|
||||
super(workflow, node, additionalData, mode, runExecutionData, runIndex);
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import { Container } from '@n8n/di';
|
||||
import type { ICredentialTestFunctions } from 'n8n-workflow';
|
||||
|
||||
import { Memoized } from '@/decorators';
|
||||
import { Logger } from '@/logging';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { getSSHTunnelFunctions, proxyRequestToAxios } from '@/node-execute-functions';
|
||||
|
||||
export class CredentialTestContext implements ICredentialTestFunctions {
|
||||
readonly helpers: ICredentialTestFunctions['helpers'];
|
||||
|
||||
constructor() {
|
||||
this.helpers = {
|
||||
...getSSHTunnelFunctions(),
|
||||
request: async (uriOrObject: string | object, options?: object) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return await proxyRequestToAxios(undefined, undefined, undefined, uriOrObject, options);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@Memoized
|
||||
get logger() {
|
||||
return Container.get(Logger);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
// eslint-disable-next-line import/no-cycle
|
||||
export { CredentialTestContext } from './credentials-test-context';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
export { ExecuteContext } from './execute-context';
|
||||
export { ExecuteSingleContext } from './execute-single-context';
|
||||
export { HookContext } from './hook-context';
|
||||
|
|
|
@ -43,12 +43,12 @@ export abstract class NodeExecutionContext implements Omit<FunctionsBase, 'getCr
|
|||
protected readonly instanceSettings = Container.get(InstanceSettings);
|
||||
|
||||
constructor(
|
||||
protected readonly workflow: Workflow,
|
||||
protected readonly node: INode,
|
||||
protected readonly additionalData: IWorkflowExecuteAdditionalData,
|
||||
protected readonly mode: WorkflowExecuteMode,
|
||||
readonly workflow: Workflow,
|
||||
readonly node: INode,
|
||||
readonly additionalData: IWorkflowExecuteAdditionalData,
|
||||
readonly mode: WorkflowExecuteMode,
|
||||
protected readonly runExecutionData: IRunExecutionData | null = null,
|
||||
protected readonly runIndex = 0,
|
||||
readonly runIndex = 0,
|
||||
protected readonly connectionInputData: INodeExecutionData[] = [],
|
||||
protected readonly executeData?: IExecuteData,
|
||||
) {}
|
||||
|
|
|
@ -6,24 +6,25 @@
|
|||
import get from 'lodash/get';
|
||||
import merge from 'lodash/merge';
|
||||
import set from 'lodash/set';
|
||||
import { NodeHelpers, NodeApiError, NodeOperationError, sleep } from 'n8n-workflow';
|
||||
import {
|
||||
NodeHelpers,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
sleep,
|
||||
NodeConnectionType,
|
||||
} from 'n8n-workflow';
|
||||
import type {
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialsDecrypted,
|
||||
IHttpRequestOptions,
|
||||
IN8nHttpFullResponse,
|
||||
INode,
|
||||
INodeExecutionData,
|
||||
INodeParameters,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
DeclarativeRestApiSettings,
|
||||
IRunExecutionData,
|
||||
ITaskDataConnections,
|
||||
IWorkflowDataProxyAdditionalKeys,
|
||||
IWorkflowExecuteAdditionalData,
|
||||
NodeParameterValue,
|
||||
WorkflowExecuteMode,
|
||||
IDataObject,
|
||||
IExecuteData,
|
||||
IExecuteSingleFunctions,
|
||||
|
@ -33,70 +34,39 @@ import type {
|
|||
NodeParameterValueType,
|
||||
PostReceiveAction,
|
||||
JsonObject,
|
||||
CloseFunction,
|
||||
INodeCredentialDescription,
|
||||
IExecutePaginationFunctions,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
import url from 'node:url';
|
||||
|
||||
import { ExecuteContext, ExecuteSingleContext } from './node-execution-context';
|
||||
import { type ExecuteContext, ExecuteSingleContext } from './node-execution-context';
|
||||
|
||||
export class RoutingNode {
|
||||
additionalData: IWorkflowExecuteAdditionalData;
|
||||
|
||||
connectionInputData: INodeExecutionData[];
|
||||
|
||||
node: INode;
|
||||
|
||||
mode: WorkflowExecuteMode;
|
||||
|
||||
runExecutionData: IRunExecutionData;
|
||||
|
||||
workflow: Workflow;
|
||||
|
||||
constructor(
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
connectionInputData: INodeExecutionData[],
|
||||
runExecutionData: IRunExecutionData,
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
mode: WorkflowExecuteMode,
|
||||
) {
|
||||
this.additionalData = additionalData;
|
||||
this.connectionInputData = connectionInputData;
|
||||
this.runExecutionData = runExecutionData;
|
||||
this.mode = mode;
|
||||
this.node = node;
|
||||
this.workflow = workflow;
|
||||
}
|
||||
private readonly context: ExecuteContext,
|
||||
private readonly nodeType: INodeType,
|
||||
private readonly credentialsDecrypted?: ICredentialsDecrypted,
|
||||
) {}
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
async runNode(
|
||||
inputData: ITaskDataConnections,
|
||||
runIndex: number,
|
||||
nodeType: INodeType,
|
||||
executeData: IExecuteData,
|
||||
credentialsDecrypted?: ICredentialsDecrypted,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<INodeExecutionData[][] | null | undefined> {
|
||||
const items = inputData.main[0] as INodeExecutionData[];
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
const closeFunctions: CloseFunction[] = [];
|
||||
const executeFunctions = new ExecuteContext(
|
||||
this.workflow,
|
||||
this.node,
|
||||
this.additionalData,
|
||||
this.mode,
|
||||
this.runExecutionData,
|
||||
runIndex,
|
||||
this.connectionInputData,
|
||||
inputData,
|
||||
async runNode(): Promise<INodeExecutionData[][] | undefined> {
|
||||
const { context, nodeType, credentialsDecrypted } = this;
|
||||
const {
|
||||
additionalData,
|
||||
executeData,
|
||||
closeFunctions,
|
||||
abortSignal,
|
||||
);
|
||||
inputData,
|
||||
node,
|
||||
workflow,
|
||||
mode,
|
||||
runIndex,
|
||||
connectionInputData,
|
||||
runExecutionData,
|
||||
} = context;
|
||||
const abortSignal = context.getExecutionCancelSignal();
|
||||
|
||||
const items = (inputData[NodeConnectionType.Main] ??
|
||||
inputData[NodeConnectionType.AiTool])[0] as INodeExecutionData[];
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
let credentialDescription: INodeCredentialDescription | undefined;
|
||||
|
||||
|
@ -104,17 +74,14 @@ export class RoutingNode {
|
|||
if (nodeType.description.credentials.length === 1) {
|
||||
credentialDescription = nodeType.description.credentials[0];
|
||||
} else {
|
||||
const authenticationMethod = executeFunctions.getNodeParameter(
|
||||
'authentication',
|
||||
0,
|
||||
) as string;
|
||||
const authenticationMethod = context.getNodeParameter('authentication', 0) as string;
|
||||
credentialDescription = nodeType.description.credentials.find((x) =>
|
||||
x.displayOptions?.show?.authentication?.includes(authenticationMethod),
|
||||
);
|
||||
if (!credentialDescription) {
|
||||
throw new NodeOperationError(
|
||||
this.node,
|
||||
`Node type "${this.node.type}" does not have any credentials of type "${authenticationMethod}" defined`,
|
||||
node,
|
||||
`Node type "${node.type}" does not have any credentials of type "${authenticationMethod}" defined`,
|
||||
{ level: 'warning' },
|
||||
);
|
||||
}
|
||||
|
@ -127,7 +94,7 @@ export class RoutingNode {
|
|||
} else if (credentialDescription) {
|
||||
try {
|
||||
credentials =
|
||||
(await executeFunctions.getCredentials<ICredentialDataDecryptedObject>(
|
||||
(await context.getCredentials<ICredentialDataDecryptedObject>(
|
||||
credentialDescription.name,
|
||||
0,
|
||||
)) || {};
|
||||
|
@ -142,7 +109,7 @@ export class RoutingNode {
|
|||
}
|
||||
}
|
||||
|
||||
const { batching } = executeFunctions.getNodeParameter('requestOptions', 0, {}) as {
|
||||
const { batching } = context.getNodeParameter('requestOptions', 0, {}) as {
|
||||
batching: { batch: { batchSize: number; batchInterval: number } };
|
||||
};
|
||||
|
||||
|
@ -163,13 +130,13 @@ export class RoutingNode {
|
|||
}
|
||||
|
||||
const thisArgs = new ExecuteSingleContext(
|
||||
this.workflow,
|
||||
this.node,
|
||||
this.additionalData,
|
||||
this.mode,
|
||||
this.runExecutionData,
|
||||
workflow,
|
||||
node,
|
||||
additionalData,
|
||||
mode,
|
||||
runExecutionData,
|
||||
runIndex,
|
||||
this.connectionInputData,
|
||||
connectionInputData,
|
||||
inputData,
|
||||
itemIndex,
|
||||
executeData,
|
||||
|
@ -214,7 +181,7 @@ export class RoutingNode {
|
|||
itemIndex,
|
||||
runIndex,
|
||||
executeData,
|
||||
{ $credentials: credentials, $version: this.node.typeVersion },
|
||||
{ $credentials: credentials, $version: node.typeVersion },
|
||||
false,
|
||||
) as string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
@ -223,14 +190,14 @@ export class RoutingNode {
|
|||
}
|
||||
|
||||
for (const property of nodeType.description.properties) {
|
||||
let value = get(this.node.parameters, property.name, []) as string | NodeParameterValue;
|
||||
let value = get(node.parameters, property.name, []) as string | NodeParameterValue;
|
||||
// If the value is an expression resolve it
|
||||
value = this.getParameterValue(
|
||||
value,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
executeData,
|
||||
{ $credentials: credentials, $version: this.node.typeVersion },
|
||||
{ $credentials: credentials, $version: node.typeVersion },
|
||||
false,
|
||||
) as string | NodeParameterValue;
|
||||
|
||||
|
@ -240,7 +207,7 @@ export class RoutingNode {
|
|||
itemIndex,
|
||||
runIndex,
|
||||
'',
|
||||
{ $credentials: credentials, $value: value, $version: this.node.typeVersion },
|
||||
{ $credentials: credentials, $value: value, $version: node.typeVersion },
|
||||
);
|
||||
|
||||
this.mergeOptions(itemContext[itemIndex].requestData, tempOptions);
|
||||
|
@ -255,7 +222,7 @@ export class RoutingNode {
|
|||
!(property in proxyParsed) ||
|
||||
proxyParsed[property as keyof typeof proxyParsed] === null
|
||||
) {
|
||||
throw new NodeOperationError(this.node, 'The proxy is not value', {
|
||||
throw new NodeOperationError(node, 'The proxy is not value', {
|
||||
runIndex,
|
||||
itemIndex,
|
||||
description: `The proxy URL does not contain a valid value for "${property}"`,
|
||||
|
@ -291,7 +258,7 @@ export class RoutingNode {
|
|||
}
|
||||
|
||||
requestPromises.push(
|
||||
this.makeRoutingRequest(
|
||||
this.makeRequest(
|
||||
itemContext[itemIndex].requestData,
|
||||
itemContext[itemIndex].thisArgs,
|
||||
itemIndex,
|
||||
|
@ -327,7 +294,7 @@ export class RoutingNode {
|
|||
throw error;
|
||||
}
|
||||
|
||||
throw new NodeApiError(this.node, error as JsonObject, {
|
||||
throw new NodeApiError(node, error as JsonObject, {
|
||||
runIndex,
|
||||
itemIndex,
|
||||
message: error?.message,
|
||||
|
@ -347,7 +314,7 @@ export class RoutingNode {
|
|||
return [returnData];
|
||||
}
|
||||
|
||||
mergeOptions(
|
||||
private mergeOptions(
|
||||
destinationOptions: DeclarativeRestApiSettings.ResultOptions,
|
||||
sourceOptions?: DeclarativeRestApiSettings.ResultOptions,
|
||||
): void {
|
||||
|
@ -368,7 +335,7 @@ export class RoutingNode {
|
|||
}
|
||||
}
|
||||
|
||||
async runPostReceiveAction(
|
||||
private async runPostReceiveAction(
|
||||
executeSingleFunctions: IExecuteSingleFunctions,
|
||||
action: PostReceiveAction,
|
||||
inputData: INodeExecutionData[],
|
||||
|
@ -380,6 +347,9 @@ export class RoutingNode {
|
|||
if (typeof action === 'function') {
|
||||
return await action.call(executeSingleFunctions, inputData, responseData);
|
||||
}
|
||||
|
||||
const { node } = this.context;
|
||||
|
||||
if (action.type === 'rootProperty') {
|
||||
try {
|
||||
return inputData.flatMap((item) => {
|
||||
|
@ -395,13 +365,14 @@ export class RoutingNode {
|
|||
});
|
||||
});
|
||||
} catch (error) {
|
||||
throw new NodeOperationError(this.node, error as Error, {
|
||||
throw new NodeOperationError(node, error as Error, {
|
||||
runIndex,
|
||||
itemIndex,
|
||||
description: `The rootProperty "${action.properties.property}" could not be found on item.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (action.type === 'filter') {
|
||||
const passValue = action.properties.pass;
|
||||
|
||||
|
@ -416,7 +387,7 @@ export class RoutingNode {
|
|||
$response: responseData,
|
||||
$responseItem: item.json,
|
||||
$value: parameterValue,
|
||||
$version: this.node.typeVersion,
|
||||
$version: node.typeVersion,
|
||||
},
|
||||
false,
|
||||
) as boolean;
|
||||
|
@ -424,17 +395,19 @@ export class RoutingNode {
|
|||
|
||||
return inputData;
|
||||
}
|
||||
|
||||
if (action.type === 'limit') {
|
||||
const maxResults = this.getParameterValue(
|
||||
action.properties.maxResults,
|
||||
itemIndex,
|
||||
runIndex,
|
||||
executeSingleFunctions.getExecuteData(),
|
||||
{ $response: responseData, $value: parameterValue, $version: this.node.typeVersion },
|
||||
{ $response: responseData, $value: parameterValue, $version: node.typeVersion },
|
||||
false,
|
||||
) as string;
|
||||
return inputData.slice(0, parseInt(maxResults, 10));
|
||||
}
|
||||
|
||||
if (action.type === 'set') {
|
||||
const { value } = action.properties;
|
||||
// If the value is an expression resolve it
|
||||
|
@ -445,12 +418,13 @@ export class RoutingNode {
|
|||
itemIndex,
|
||||
runIndex,
|
||||
executeSingleFunctions.getExecuteData(),
|
||||
{ $response: responseData, $value: parameterValue, $version: this.node.typeVersion },
|
||||
{ $response: responseData, $value: parameterValue, $version: node.typeVersion },
|
||||
false,
|
||||
) as IDataObject,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (action.type === 'sort') {
|
||||
// Sort the returned options
|
||||
const sortKey = action.properties.key;
|
||||
|
@ -468,6 +442,7 @@ export class RoutingNode {
|
|||
|
||||
return inputData;
|
||||
}
|
||||
|
||||
if (action.type === 'setKeyValue') {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
|
@ -491,7 +466,7 @@ export class RoutingNode {
|
|||
$response: responseData,
|
||||
$responseItem: item.json,
|
||||
$value: parameterValue,
|
||||
$version: this.node.typeVersion,
|
||||
$version: node.typeVersion,
|
||||
},
|
||||
false,
|
||||
) as string;
|
||||
|
@ -503,6 +478,7 @@ export class RoutingNode {
|
|||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
if (action.type === 'binaryData') {
|
||||
const body = (responseData.body = Buffer.from(responseData.body as string));
|
||||
let { destinationProperty } = action.properties;
|
||||
|
@ -512,7 +488,7 @@ export class RoutingNode {
|
|||
itemIndex,
|
||||
runIndex,
|
||||
executeSingleFunctions.getExecuteData(),
|
||||
{ $response: responseData, $value: parameterValue, $version: this.node.typeVersion },
|
||||
{ $response: responseData, $value: parameterValue, $version: node.typeVersion },
|
||||
false,
|
||||
) as string;
|
||||
|
||||
|
@ -535,7 +511,7 @@ export class RoutingNode {
|
|||
return [];
|
||||
}
|
||||
|
||||
async postProcessResponseData(
|
||||
private async postProcessResponseData(
|
||||
executeSingleFunctions: IExecuteSingleFunctions,
|
||||
responseData: IN8nHttpFullResponse,
|
||||
requestData: DeclarativeRestApiSettings.ResultOptions,
|
||||
|
@ -580,7 +556,7 @@ export class RoutingNode {
|
|||
return returnData;
|
||||
}
|
||||
|
||||
async rawRoutingRequest(
|
||||
private async rawRoutingRequest(
|
||||
executeSingleFunctions: IExecuteSingleFunctions,
|
||||
requestData: DeclarativeRestApiSettings.ResultOptions,
|
||||
credentialType?: string,
|
||||
|
@ -604,7 +580,7 @@ export class RoutingNode {
|
|||
return responseData;
|
||||
}
|
||||
|
||||
async makeRoutingRequest(
|
||||
private async makeRequest(
|
||||
requestData: DeclarativeRestApiSettings.ResultOptions,
|
||||
executeSingleFunctions: IExecuteSingleFunctions,
|
||||
itemIndex: number,
|
||||
|
@ -639,6 +615,7 @@ export class RoutingNode {
|
|||
);
|
||||
};
|
||||
|
||||
const { node } = this.context;
|
||||
const executePaginationFunctions = Object.create(executeSingleFunctions, {
|
||||
makeRoutingRequest: { value: makeRoutingRequest },
|
||||
}) as IExecutePaginationFunctions;
|
||||
|
@ -669,7 +646,7 @@ export class RoutingNode {
|
|||
const additionalKeys = {
|
||||
$request: requestData.options,
|
||||
$response: {} as IN8nHttpFullResponse,
|
||||
$version: this.node.typeVersion,
|
||||
$version: node.typeVersion,
|
||||
};
|
||||
|
||||
do {
|
||||
|
@ -763,7 +740,7 @@ export class RoutingNode {
|
|||
| undefined;
|
||||
if (tempResponseValue === undefined) {
|
||||
throw new NodeOperationError(
|
||||
this.node,
|
||||
node,
|
||||
`The rootProperty "${properties.rootProperty}" could not be found on item.`,
|
||||
{ runIndex, itemIndex },
|
||||
);
|
||||
|
@ -801,7 +778,7 @@ export class RoutingNode {
|
|||
return responseData;
|
||||
}
|
||||
|
||||
getParameterValue(
|
||||
private getParameterValue(
|
||||
parameterValue: NodeParameterValueType,
|
||||
itemIndex: number,
|
||||
runIndex: number,
|
||||
|
@ -813,14 +790,15 @@ export class RoutingNode {
|
|||
typeof parameterValue === 'object' ||
|
||||
(typeof parameterValue === 'string' && parameterValue.charAt(0) === '=')
|
||||
) {
|
||||
return this.workflow.expression.getParameterValue(
|
||||
const { node, workflow, mode, connectionInputData, runExecutionData } = this.context;
|
||||
return workflow.expression.getParameterValue(
|
||||
parameterValue,
|
||||
this.runExecutionData ?? null,
|
||||
runExecutionData ?? null,
|
||||
runIndex,
|
||||
itemIndex,
|
||||
this.node.name,
|
||||
this.connectionInputData,
|
||||
this.mode,
|
||||
node.name,
|
||||
connectionInputData,
|
||||
mode,
|
||||
additionalKeys ?? {},
|
||||
executeData,
|
||||
returnObjectAsString,
|
||||
|
@ -851,14 +829,8 @@ export class RoutingNode {
|
|||
};
|
||||
let basePath = path ? `${path}.` : '';
|
||||
|
||||
if (
|
||||
!NodeHelpers.displayParameter(
|
||||
this.node.parameters,
|
||||
nodeProperties,
|
||||
this.node,
|
||||
this.node.parameters,
|
||||
)
|
||||
) {
|
||||
const { node } = this.context;
|
||||
if (!NodeHelpers.displayParameter(node.parameters, nodeProperties, node, node.parameters)) {
|
||||
return undefined;
|
||||
}
|
||||
if (nodeProperties.routing) {
|
||||
|
@ -1032,7 +1004,7 @@ export class RoutingNode {
|
|||
let value;
|
||||
if (nodeProperties.type === 'options') {
|
||||
const optionValue = NodeHelpers.getParameterValueByPath(
|
||||
this.node.parameters,
|
||||
node.parameters,
|
||||
nodeProperties.name,
|
||||
basePath.slice(0, -1),
|
||||
);
|
||||
|
@ -1050,14 +1022,14 @@ export class RoutingNode {
|
|||
itemIndex,
|
||||
runIndex,
|
||||
`${basePath}${nodeProperties.name}`,
|
||||
{ $value: optionValue, $version: this.node.typeVersion },
|
||||
{ $value: optionValue, $version: node.typeVersion },
|
||||
);
|
||||
|
||||
this.mergeOptions(returnData, tempOptions);
|
||||
}
|
||||
} else if (nodeProperties.type === 'collection') {
|
||||
value = NodeHelpers.getParameterValueByPath(
|
||||
this.node.parameters,
|
||||
node.parameters,
|
||||
nodeProperties.name,
|
||||
basePath.slice(0, -1),
|
||||
);
|
||||
|
@ -1074,7 +1046,7 @@ export class RoutingNode {
|
|||
itemIndex,
|
||||
runIndex,
|
||||
`${basePath}${nodeProperties.name}`,
|
||||
{ $version: this.node.typeVersion },
|
||||
{ $version: node.typeVersion },
|
||||
);
|
||||
|
||||
this.mergeOptions(returnData, tempOptions);
|
||||
|
@ -1085,7 +1057,7 @@ export class RoutingNode {
|
|||
for (const propertyOptions of nodeProperties.options as INodePropertyCollection[]) {
|
||||
// Check if the option got set and if not skip it
|
||||
value = NodeHelpers.getParameterValueByPath(
|
||||
this.node.parameters,
|
||||
node.parameters,
|
||||
propertyOptions.name,
|
||||
basePath.slice(0, -1),
|
||||
);
|
||||
|
|
|
@ -69,7 +69,6 @@ import {
|
|||
handleCycles,
|
||||
filterDisabledNodes,
|
||||
} from './partial-execution-utils';
|
||||
import { RoutingNode } from './routing-node';
|
||||
import { TriggersAndPollers } from './triggers-and-pollers';
|
||||
|
||||
export class WorkflowExecute {
|
||||
|
@ -1171,27 +1170,7 @@ export class WorkflowExecute {
|
|||
// For webhook nodes always simply pass the data through
|
||||
return { data: inputData.main as INodeExecutionData[][] };
|
||||
} else {
|
||||
// For nodes which have routing information on properties
|
||||
|
||||
const routingNode = new RoutingNode(
|
||||
workflow,
|
||||
node,
|
||||
connectionInputData,
|
||||
runExecutionData ?? null,
|
||||
additionalData,
|
||||
mode,
|
||||
);
|
||||
|
||||
return {
|
||||
data: await routingNode.runNode(
|
||||
inputData,
|
||||
runIndex,
|
||||
nodeType,
|
||||
executionData,
|
||||
undefined,
|
||||
abortSignal,
|
||||
),
|
||||
};
|
||||
throw new ApplicationError('This should not have happened');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,6 @@ import type {
|
|||
IAllExecuteFunctions,
|
||||
IBinaryData,
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
IExecuteData,
|
||||
IExecuteFunctions,
|
||||
|
@ -2555,15 +2554,3 @@ export function getExecuteTriggerFunctions(
|
|||
): ITriggerFunctions {
|
||||
return new TriggerContext(workflow, node, additionalData, mode, activation);
|
||||
}
|
||||
|
||||
export function getCredentialTestFunctions(): ICredentialTestFunctions {
|
||||
return {
|
||||
logger: Container.get(Logger),
|
||||
helpers: {
|
||||
...getSSHTunnelFunctions(),
|
||||
request: async (uriOrObject: string | object, options?: object) => {
|
||||
return await proxyRequestToAxios(undefined, undefined, undefined, uriOrObject, options);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ export class Npm implements INodeType {
|
|||
},
|
||||
inputs: [NodeConnectionType.Main],
|
||||
outputs: [NodeConnectionType.Main],
|
||||
usableAsTool: true,
|
||||
credentials: [
|
||||
{
|
||||
name: 'npmApi',
|
||||
|
|
Loading…
Reference in a new issue