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