mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
refactor(core): Extract load-options context out of NodeExecutionFunctions (no-changelog) (#11461)
This commit is contained in:
parent
a092b8e972
commit
da18e1ad29
|
@ -1,4 +1,4 @@
|
||||||
import { NodeExecuteFunctions } from 'n8n-core';
|
import { LoadOptionsContext, NodeExecuteFunctions } from 'n8n-core';
|
||||||
import type {
|
import type {
|
||||||
ILoadOptions,
|
ILoadOptions,
|
||||||
ILoadOptionsFunctions,
|
ILoadOptionsFunctions,
|
||||||
|
@ -253,6 +253,6 @@ export class DynamicNodeParametersService {
|
||||||
workflow: Workflow,
|
workflow: Workflow,
|
||||||
) {
|
) {
|
||||||
const node = workflow.nodes['Temp-Node'];
|
const node = workflow.nodes['Temp-Node'];
|
||||||
return NodeExecuteFunctions.getLoadOptionsFunctions(workflow, node, path, additionalData);
|
return new LoadOptionsContext(workflow, node, additionalData, path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,6 @@ import type {
|
||||||
IGetNodeParameterOptions,
|
IGetNodeParameterOptions,
|
||||||
IHookFunctions,
|
IHookFunctions,
|
||||||
IHttpRequestOptions,
|
IHttpRequestOptions,
|
||||||
ILoadOptionsFunctions,
|
|
||||||
IN8nHttpFullResponse,
|
IN8nHttpFullResponse,
|
||||||
IN8nHttpResponse,
|
IN8nHttpResponse,
|
||||||
INode,
|
INode,
|
||||||
|
@ -4332,85 +4331,6 @@ export function getCredentialTestFunctions(): ICredentialTestFunctions {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the execute functions regular nodes have access to in load-options-function.
|
|
||||||
*/
|
|
||||||
export function getLoadOptionsFunctions(
|
|
||||||
workflow: Workflow,
|
|
||||||
node: INode,
|
|
||||||
path: string,
|
|
||||||
additionalData: IWorkflowExecuteAdditionalData,
|
|
||||||
): ILoadOptionsFunctions {
|
|
||||||
return ((workflow: Workflow, node: INode, path: string) => {
|
|
||||||
return {
|
|
||||||
...getCommonWorkflowFunctions(workflow, node, additionalData),
|
|
||||||
getCredentials: async (type) =>
|
|
||||||
await getCredentials(workflow, node, type, additionalData, 'internal'),
|
|
||||||
getCurrentNodeParameter: (
|
|
||||||
parameterPath: string,
|
|
||||||
options?: IGetNodeParameterOptions,
|
|
||||||
): NodeParameterValueType | object | undefined => {
|
|
||||||
const nodeParameters = additionalData.currentNodeParameters;
|
|
||||||
|
|
||||||
if (parameterPath.charAt(0) === '&') {
|
|
||||||
parameterPath = `${path.split('.').slice(1, -1).join('.')}.${parameterPath.slice(1)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let returnData = get(nodeParameters, parameterPath);
|
|
||||||
|
|
||||||
// This is outside the try/catch because it throws errors with proper messages
|
|
||||||
if (options?.extractValue) {
|
|
||||||
const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
|
|
||||||
if (nodeType === undefined) {
|
|
||||||
throw new ApplicationError('Node type is not known so cannot return parameter value', {
|
|
||||||
tags: { nodeType: node.type },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
returnData = extractValue(
|
|
||||||
returnData,
|
|
||||||
parameterPath,
|
|
||||||
node,
|
|
||||||
nodeType,
|
|
||||||
) as NodeParameterValueType;
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnData;
|
|
||||||
},
|
|
||||||
getCurrentNodeParameters: () => additionalData.currentNodeParameters,
|
|
||||||
getNodeParameter: (
|
|
||||||
parameterName: string,
|
|
||||||
fallbackValue?: any,
|
|
||||||
options?: IGetNodeParameterOptions,
|
|
||||||
): NodeParameterValueType | object => {
|
|
||||||
const runExecutionData: IRunExecutionData | null = null;
|
|
||||||
const itemIndex = 0;
|
|
||||||
const runIndex = 0;
|
|
||||||
const mode = 'internal' as WorkflowExecuteMode;
|
|
||||||
const connectionInputData: INodeExecutionData[] = [];
|
|
||||||
|
|
||||||
return getNodeParameter(
|
|
||||||
workflow,
|
|
||||||
runExecutionData,
|
|
||||||
runIndex,
|
|
||||||
connectionInputData,
|
|
||||||
node,
|
|
||||||
parameterName,
|
|
||||||
itemIndex,
|
|
||||||
mode,
|
|
||||||
getAdditionalKeys(additionalData, mode, runExecutionData),
|
|
||||||
undefined,
|
|
||||||
fallbackValue,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
helpers: {
|
|
||||||
...getSSHTunnelFunctions(),
|
|
||||||
...getRequestHelperFunctions(workflow, node, additionalData),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
})(workflow, node, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the execute functions regular nodes have access to in hook-function.
|
* Returns the execute functions regular nodes have access to in hook-function.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import type {
|
||||||
|
Expression,
|
||||||
|
ICredentialDataDecryptedObject,
|
||||||
|
ICredentialsHelper,
|
||||||
|
INode,
|
||||||
|
INodeType,
|
||||||
|
INodeTypes,
|
||||||
|
IWorkflowExecuteAdditionalData,
|
||||||
|
Workflow,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { LoadOptionsContext } from '../load-options-context';
|
||||||
|
|
||||||
|
describe('LoadOptionsContext', () => {
|
||||||
|
const testCredentialType = 'testCredential';
|
||||||
|
const nodeType = mock<INodeType>({
|
||||||
|
description: {
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: testCredentialType,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: 'testParameter',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const nodeTypes = mock<INodeTypes>();
|
||||||
|
const expression = mock<Expression>();
|
||||||
|
const workflow = mock<Workflow>({ expression, nodeTypes });
|
||||||
|
const node = mock<INode>({
|
||||||
|
credentials: {
|
||||||
|
[testCredentialType]: {
|
||||||
|
id: 'testCredentialId',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
node.parameters = {
|
||||||
|
testParameter: 'testValue',
|
||||||
|
};
|
||||||
|
const credentialsHelper = mock<ICredentialsHelper>();
|
||||||
|
const additionalData = mock<IWorkflowExecuteAdditionalData>({ credentialsHelper });
|
||||||
|
const path = 'testPath';
|
||||||
|
|
||||||
|
const loadOptionsContext = new LoadOptionsContext(workflow, node, additionalData, path);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCredentials', () => {
|
||||||
|
it('should get decrypted credentials', async () => {
|
||||||
|
nodeTypes.getByNameAndVersion.mockReturnValue(nodeType);
|
||||||
|
credentialsHelper.getDecrypted.mockResolvedValue({ secret: 'token' });
|
||||||
|
|
||||||
|
const credentials =
|
||||||
|
await loadOptionsContext.getCredentials<ICredentialDataDecryptedObject>(testCredentialType);
|
||||||
|
|
||||||
|
expect(credentials).toEqual({ secret: 'token' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCurrentNodeParameter', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
nodeTypes.getByNameAndVersion.mockReturnValue(nodeType);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return parameter value when it exists', () => {
|
||||||
|
additionalData.currentNodeParameters = {
|
||||||
|
testParameter: 'testValue',
|
||||||
|
};
|
||||||
|
|
||||||
|
const parameter = loadOptionsContext.getCurrentNodeParameter('testParameter');
|
||||||
|
|
||||||
|
expect(parameter).toBe('testValue');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getNodeParameter', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
nodeTypes.getByNameAndVersion.mockReturnValue(nodeType);
|
||||||
|
expression.getParameterValue.mockImplementation((value) => value);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return parameter value when it exists', () => {
|
||||||
|
const parameter = loadOptionsContext.getNodeParameter('testParameter');
|
||||||
|
|
||||||
|
expect(parameter).toBe('testValue');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the fallback value when the parameter does not exist', () => {
|
||||||
|
const parameter = loadOptionsContext.getNodeParameter('otherParameter', 'fallback');
|
||||||
|
|
||||||
|
expect(parameter).toBe('fallback');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,4 +1,5 @@
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
|
export { LoadOptionsContext } from './load-options-context';
|
||||||
export { PollContext } from './poll-context';
|
export { PollContext } from './poll-context';
|
||||||
export { TriggerContext } from './trigger-context';
|
export { TriggerContext } from './trigger-context';
|
||||||
export { WebhookContext } from './webhook-context';
|
export { WebhookContext } from './webhook-context';
|
||||||
|
|
102
packages/core/src/node-execution-context/load-options-context.ts
Normal file
102
packages/core/src/node-execution-context/load-options-context.ts
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
import { get } from 'lodash';
|
||||||
|
import type {
|
||||||
|
ICredentialDataDecryptedObject,
|
||||||
|
IGetNodeParameterOptions,
|
||||||
|
INode,
|
||||||
|
INodeExecutionData,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
IRunExecutionData,
|
||||||
|
IWorkflowExecuteAdditionalData,
|
||||||
|
NodeParameterValueType,
|
||||||
|
Workflow,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { extractValue } from '@/ExtractValue';
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
|
import { getAdditionalKeys, getCredentials, getNodeParameter } from '@/NodeExecuteFunctions';
|
||||||
|
|
||||||
|
import { RequestHelpers } from './helpers/request-helpers';
|
||||||
|
import { SSHTunnelHelpers } from './helpers/ssh-tunnel-helpers';
|
||||||
|
import { NodeExecutionContext } from './node-execution-context';
|
||||||
|
|
||||||
|
export class LoadOptionsContext extends NodeExecutionContext implements ILoadOptionsFunctions {
|
||||||
|
readonly helpers: ILoadOptionsFunctions['helpers'];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
workflow: Workflow,
|
||||||
|
node: INode,
|
||||||
|
additionalData: IWorkflowExecuteAdditionalData,
|
||||||
|
private readonly path: string,
|
||||||
|
) {
|
||||||
|
super(workflow, node, additionalData, 'internal');
|
||||||
|
|
||||||
|
this.helpers = {
|
||||||
|
...new RequestHelpers(this, workflow, node, additionalData).exported,
|
||||||
|
...new SSHTunnelHelpers().exported,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCredentials<T extends object = ICredentialDataDecryptedObject>(type: string) {
|
||||||
|
return await getCredentials<T>(this.workflow, this.node, type, this.additionalData, this.mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentNodeParameter(
|
||||||
|
parameterPath: string,
|
||||||
|
options?: IGetNodeParameterOptions,
|
||||||
|
): NodeParameterValueType | object | undefined {
|
||||||
|
const nodeParameters = this.additionalData.currentNodeParameters;
|
||||||
|
|
||||||
|
if (parameterPath.charAt(0) === '&') {
|
||||||
|
parameterPath = `${this.path.split('.').slice(1, -1).join('.')}.${parameterPath.slice(1)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let returnData = get(nodeParameters, parameterPath);
|
||||||
|
|
||||||
|
// This is outside the try/catch because it throws errors with proper messages
|
||||||
|
if (options?.extractValue) {
|
||||||
|
const nodeType = this.workflow.nodeTypes.getByNameAndVersion(
|
||||||
|
this.node.type,
|
||||||
|
this.node.typeVersion,
|
||||||
|
);
|
||||||
|
returnData = extractValue(
|
||||||
|
returnData,
|
||||||
|
parameterPath,
|
||||||
|
this.node,
|
||||||
|
nodeType,
|
||||||
|
) as NodeParameterValueType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentNodeParameters() {
|
||||||
|
return this.additionalData.currentNodeParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodeParameter(
|
||||||
|
parameterName: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
fallbackValue?: any,
|
||||||
|
options?: IGetNodeParameterOptions,
|
||||||
|
): NodeParameterValueType | object {
|
||||||
|
const runExecutionData: IRunExecutionData | null = null;
|
||||||
|
const itemIndex = 0;
|
||||||
|
const runIndex = 0;
|
||||||
|
const connectionInputData: INodeExecutionData[] = [];
|
||||||
|
|
||||||
|
return getNodeParameter(
|
||||||
|
this.workflow,
|
||||||
|
runExecutionData,
|
||||||
|
runIndex,
|
||||||
|
connectionInputData,
|
||||||
|
this.node,
|
||||||
|
parameterName,
|
||||||
|
itemIndex,
|
||||||
|
this.mode,
|
||||||
|
getAdditionalKeys(this.additionalData, this.mode, runExecutionData),
|
||||||
|
undefined,
|
||||||
|
fallbackValue,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -74,38 +74,23 @@ export class WebhookContext extends NodeExecutionContext implements IWebhookFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
getBodyData() {
|
getBodyData() {
|
||||||
if (this.additionalData.httpRequest === undefined) {
|
return this.assertHttpRequest().body as IDataObject;
|
||||||
throw new ApplicationError('Request is missing');
|
|
||||||
}
|
|
||||||
return this.additionalData.httpRequest.body as IDataObject;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeaderData() {
|
getHeaderData() {
|
||||||
if (this.additionalData.httpRequest === undefined) {
|
return this.assertHttpRequest().headers;
|
||||||
throw new ApplicationError('Request is missing');
|
|
||||||
}
|
|
||||||
return this.additionalData.httpRequest.headers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getParamsData(): object {
|
getParamsData(): object {
|
||||||
if (this.additionalData.httpRequest === undefined) {
|
return this.assertHttpRequest().params;
|
||||||
throw new ApplicationError('Request is missing');
|
|
||||||
}
|
|
||||||
return this.additionalData.httpRequest.params;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getQueryData(): object {
|
getQueryData(): object {
|
||||||
if (this.additionalData.httpRequest === undefined) {
|
return this.assertHttpRequest().query;
|
||||||
throw new ApplicationError('Request is missing');
|
|
||||||
}
|
|
||||||
return this.additionalData.httpRequest.query;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getRequestObject(): Request {
|
getRequestObject(): Request {
|
||||||
if (this.additionalData.httpRequest === undefined) {
|
return this.assertHttpRequest();
|
||||||
throw new ApplicationError('Request is missing');
|
|
||||||
}
|
|
||||||
return this.additionalData.httpRequest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponseObject(): Response {
|
getResponseObject(): Response {
|
||||||
|
@ -115,6 +100,14 @@ export class WebhookContext extends NodeExecutionContext implements IWebhookFunc
|
||||||
return this.additionalData.httpResponse;
|
return this.additionalData.httpResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private assertHttpRequest() {
|
||||||
|
const { httpRequest } = this.additionalData;
|
||||||
|
if (httpRequest === undefined) {
|
||||||
|
throw new ApplicationError('Request is missing');
|
||||||
|
}
|
||||||
|
return httpRequest;
|
||||||
|
}
|
||||||
|
|
||||||
getNodeWebhookUrl(name: string): string | undefined {
|
getNodeWebhookUrl(name: string): string | undefined {
|
||||||
return getNodeWebhookUrl(
|
return getNodeWebhookUrl(
|
||||||
name,
|
name,
|
||||||
|
|
Loading…
Reference in a new issue