diff --git a/packages/cli/src/DynamicNodeParameters.ts b/packages/cli/src/DynamicNodeParameters.ts new file mode 100644 index 0000000000000..904e44418804e --- /dev/null +++ b/packages/cli/src/DynamicNodeParameters.ts @@ -0,0 +1,208 @@ +import type { + ILoadOptions, + ILoadOptionsFunctions, + INode, + INodeExecutionData, + INodeListSearchResult, + INodeProperties, + INodePropertyOptions, + INodeType, + IRunExecutionData, + ITaskDataConnections, + IWorkflowExecuteAdditionalData, + ResourceMapperFields, +} from 'n8n-workflow'; +import { + Workflow, + RoutingNode, + INodeCredentials, + INodeParameters, + INodeTypeNameVersion, + INodeTypes, +} from 'n8n-workflow'; +import { NodeExecuteFunctions } from 'n8n-core'; + +export class DynamicNodeParameters { + protected node: INode; + + protected nodeType: INodeType; + + protected workflow: Workflow; + + constructor( + nodeTypeNameAndVersion: INodeTypeNameVersion, + nodeTypes: INodeTypes, + currentNodeParameters: INodeParameters, + credentials?: INodeCredentials, + ) { + this.nodeType = nodeTypes.getByNameAndVersion( + nodeTypeNameAndVersion.name, + nodeTypeNameAndVersion.version, + ); + + this.node = { + parameters: currentNodeParameters, + id: 'uuid-1234', + name: 'Temp-Node', + type: nodeTypeNameAndVersion.name, + typeVersion: nodeTypeNameAndVersion.version, + position: [0, 0], + }; + + if (credentials) { + this.node.credentials = credentials; + } + + this.workflow = new Workflow({ + nodes: [this.node], + connections: {}, + active: false, + nodeTypes, + }); + } + + /** Returns the available options via a predefined method */ + async getOptionsViaMethodName( + methodName: string, + path: string, + additionalData: IWorkflowExecuteAdditionalData, + ): Promise { + const method = this.getMethod('loadOptions', methodName); + const thisArgs = this.getThisArg(path, additionalData); + return method.call(thisArgs); + } + + /** Returns the available options via a loadOptions param */ + async getOptionsViaLoadOptions( + loadOptions: ILoadOptions, + additionalData: IWorkflowExecuteAdditionalData, + ): Promise { + const node = this.node; + + if (!this.nodeType.description?.requestDefaults?.baseURL) { + // This in in here for now for security reasons. + // Background: As the full data for the request to make does get send, and the auth data + // will then be applied, would it be possible to retrieve that data like that. By at least + // requiring a baseURL to be defined can at least not a random server be called. + // In the future this code has to get improved that it does not use the request information from + // the request rather resolves it via the parameter-path and nodeType data. + throw new Error( + `The node-type "${node.type}" does not exist or does not have "requestDefaults.baseURL" defined!`, + ); + } + + const mode = 'internal'; + const runIndex = 0; + const connectionInputData: INodeExecutionData[] = []; + const runExecutionData: IRunExecutionData = { resultData: { runData: {} } }; + + const routingNode = new RoutingNode( + this.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 = { + ...this.nodeType, + ...{ + description: { + ...this.nodeType.description, + properties: [ + { + displayName: '', + type: 'string', + name: '', + default: '', + routing: loadOptions.routing, + } as INodeProperties, + ], + }, + }, + }; + + const inputData: ITaskDataConnections = { + main: [[{ json: {} }]], + }; + + const optionsData = await routingNode.runNode( + inputData, + runIndex, + tempNode, + { node, source: null, data: {} }, + NodeExecuteFunctions, + ); + + if (optionsData?.length === 0) { + return []; + } + + if (!Array.isArray(optionsData)) { + throw new Error('The returned data is not an array!'); + } + + return optionsData[0].map((item) => item.json) as unknown as INodePropertyOptions[]; + } + + async getResourceLocatorResults( + methodName: string, + path: string, + additionalData: IWorkflowExecuteAdditionalData, + filter?: string, + paginationToken?: string, + ): Promise { + const method = this.getMethod('listSearch', methodName); + const thisArgs = this.getThisArg(path, additionalData); + return method.call(thisArgs, filter, paginationToken); + } + + /** Returns the available mapping fields for the ResourceMapper component */ + async getResourceMappingFields( + methodName: string, + path: string, + additionalData: IWorkflowExecuteAdditionalData, + ): Promise { + const method = this.getMethod('resourceMapping', methodName); + const thisArgs = this.getThisArg(path, additionalData); + return method.call(thisArgs); + } + + protected getMethod( + type: 'resourceMapping', + methodName: string, + ): (this: ILoadOptionsFunctions) => Promise; + protected getMethod( + type: 'listSearch', + methodName: string, + ): ( + this: ILoadOptionsFunctions, + filter?: string | undefined, + paginationToken?: string | undefined, + ) => Promise; + protected getMethod( + type: 'loadOptions', + methodName: string, + ): (this: ILoadOptionsFunctions) => Promise; + + protected getMethod(type: 'resourceMapping' | 'listSearch' | 'loadOptions', methodName: string) { + const method = this.nodeType.methods?.[type]?.[methodName]; + if (typeof method !== 'function') { + throw new Error( + `The node-type "${this.node.type}" does not have the method "${methodName}" defined!`, + ); + } + return method; + } + + protected getThisArg(path: string, additionalData: IWorkflowExecuteAdditionalData) { + return NodeExecuteFunctions.getLoadOptionsFunctions( + this.workflow, + this.node, + path, + additionalData, + ); + } +} diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index c6dd61317ddbb..cc1bed9f69798 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -25,26 +25,14 @@ import axios from 'axios'; import type { RequestOptions } from 'oauth-1.0a'; import clientOAuth1 from 'oauth-1.0a'; -import { - Credentials, - LoadMappingOptions, - LoadNodeParameterOptions, - LoadNodeListSearch, - UserSettings, -} from 'n8n-core'; +import { Credentials, UserSettings } from 'n8n-core'; import type { - INodeCredentials, INodeCredentialsDetails, - INodeListSearchResult, - INodeParameters, - INodePropertyOptions, - INodeTypeNameVersion, WorkflowExecuteMode, ICredentialTypes, ExecutionStatus, IExecutionsSummary, - ResourceMapperFields, IN8nUISettings, } from 'n8n-workflow'; import { LoggerProxy, jsonParse } from 'n8n-workflow'; @@ -69,18 +57,11 @@ import { } from '@/constants'; import { credentialsController } from '@/credentials/credentials.controller'; import { oauth2CredentialController } from '@/credentials/oauth2Credential.api'; -import type { - CurlHelper, - ExecutionRequest, - NodeListSearchRequest, - NodeParameterOptionsRequest, - OAuthRequest, - ResourceMapperRequest, - WorkflowRequest, -} from '@/requests'; +import type { CurlHelper, ExecutionRequest, OAuthRequest, WorkflowRequest } from '@/requests'; import { registerController } from '@/decorators'; import { AuthController, + DynamicNodeParametersController, LdapController, MeController, MFAController, @@ -308,6 +289,7 @@ export class Server extends AbstractServer { postHog, ), new MeController(logger, externalHooks, internalHooks, userService), + Container.get(DynamicNodeParametersController), new NodeTypesController(config, nodeTypes), new PasswordResetController( logger, @@ -486,170 +468,6 @@ export class Server extends AbstractServer { LoggerProxy.warn(`Source Control initialization failed: ${error.message}`); } - // ---------------------------------------- - - // Returns parameter values which normally get loaded from an external API or - // get generated dynamically - this.app.get( - `/${this.restEndpoint}/node-parameter-options`, - ResponseHelper.send( - async (req: NodeParameterOptionsRequest): Promise => { - const nodeTypeAndVersion = jsonParse( - req.query.nodeTypeAndVersion, - ) as INodeTypeNameVersion; - - const { path, methodName } = req.query; - - const currentNodeParameters = jsonParse( - req.query.currentNodeParameters, - ) as INodeParameters; - - let credentials: INodeCredentials | undefined; - - if (req.query.credentials) { - credentials = jsonParse(req.query.credentials); - } - - const loadDataInstance = new LoadNodeParameterOptions( - nodeTypeAndVersion, - this.nodeTypes, - path, - currentNodeParameters, - credentials, - ); - - const additionalData = await WorkflowExecuteAdditionalData.getBase( - req.user.id, - currentNodeParameters, - ); - - if (methodName) { - return loadDataInstance.getOptionsViaMethodName(methodName, additionalData); - } - // @ts-ignore - if (req.query.loadOptions) { - return loadDataInstance.getOptionsViaRequestProperty( - // @ts-ignore - jsonParse(req.query.loadOptions as string), - additionalData, - ); - } - - return []; - }, - ), - ); - - // Returns parameter values which normally get loaded from an external API or - // get generated dynamically - this.app.get( - `/${this.restEndpoint}/nodes-list-search`, - ResponseHelper.send( - async ( - req: NodeListSearchRequest, - res: express.Response, - ): Promise => { - const nodeTypeAndVersion = jsonParse( - req.query.nodeTypeAndVersion, - ) as INodeTypeNameVersion; - - const { path, methodName } = req.query; - - if (!req.query.currentNodeParameters) { - throw new ResponseHelper.BadRequestError( - 'Parameter currentNodeParameters is required.', - ); - } - - const currentNodeParameters = jsonParse( - req.query.currentNodeParameters, - ) as INodeParameters; - - let credentials: INodeCredentials | undefined; - - if (req.query.credentials) { - credentials = jsonParse(req.query.credentials); - } - - const listSearchInstance = new LoadNodeListSearch( - nodeTypeAndVersion, - this.nodeTypes, - path, - currentNodeParameters, - credentials, - ); - - const additionalData = await WorkflowExecuteAdditionalData.getBase( - req.user.id, - currentNodeParameters, - ); - - if (methodName) { - return listSearchInstance.getOptionsViaMethodName( - methodName, - additionalData, - req.query.filter, - req.query.paginationToken, - ); - } - - throw new ResponseHelper.BadRequestError('Parameter methodName is required.'); - }, - ), - ); - - this.app.get( - `/${this.restEndpoint}/get-mapping-fields`, - ResponseHelper.send( - async ( - req: ResourceMapperRequest, - res: express.Response, - ): Promise => { - const nodeTypeAndVersion = jsonParse( - req.query.nodeTypeAndVersion, - ) as INodeTypeNameVersion; - - const { path, methodName } = req.query; - - if (!req.query.currentNodeParameters) { - throw new ResponseHelper.BadRequestError( - 'Parameter currentNodeParameters is required.', - ); - } - - const currentNodeParameters = jsonParse( - req.query.currentNodeParameters, - ) as INodeParameters; - - let credentials: INodeCredentials | undefined; - - if (req.query.credentials) { - credentials = jsonParse(req.query.credentials); - } - - const loadMappingOptionsInstance = new LoadMappingOptions( - nodeTypeAndVersion, - this.nodeTypes, - path, - currentNodeParameters, - credentials, - ); - - const additionalData = await WorkflowExecuteAdditionalData.getBase( - req.user.id, - currentNodeParameters, - ); - - const fields = await loadMappingOptionsInstance.getOptionsViaMethodName( - methodName, - additionalData, - ); - - return fields; - }, - ), - ); - // ---------------------------------------- // Active Workflows // ---------------------------------------- diff --git a/packages/cli/src/controllers/dynamicNodeParameters.controller.ts b/packages/cli/src/controllers/dynamicNodeParameters.controller.ts new file mode 100644 index 0000000000000..787d428eba76c --- /dev/null +++ b/packages/cli/src/controllers/dynamicNodeParameters.controller.ts @@ -0,0 +1,149 @@ +import { Service } from 'typedi'; +import type { RequestHandler } from 'express'; +import { NextFunction, Response } from 'express'; +import type { + INodeCredentials, + INodeListSearchResult, + INodeParameters, + INodePropertyOptions, + INodeTypeNameVersion, + ResourceMapperFields, +} from 'n8n-workflow'; +import { jsonParse } from 'n8n-workflow'; + +import { Authorized, Get, Middleware, RestController } from '@/decorators'; +import { DynamicNodeParameters } from '@/DynamicNodeParameters'; +import { getBase } from '@/WorkflowExecuteAdditionalData'; +import { NodeTypes } from '@/NodeTypes'; +import { BadRequestError } from '@/ResponseHelper'; +import type { AuthenticatedRequest } from '@/requests'; + +const assertMethodName: RequestHandler = (req, res, next) => { + const { methodName } = req.query as BaseRequest['query']; + if (!methodName) { + throw new BadRequestError('Parameter methodName is required.'); + } + next(); +}; + +@Service() +@Authorized() +@RestController('/dynamic-node-parameters') +export class DynamicNodeParametersController { + constructor(private readonly nodeTypes: NodeTypes) {} + + @Middleware() + parseQueryParams(req: BaseRequest, res: Response, next: NextFunction) { + const { credentials, currentNodeParameters, nodeTypeAndVersion } = req.query; + if (!nodeTypeAndVersion) { + throw new BadRequestError('Parameter nodeTypeAndVersion is required.'); + } + if (!currentNodeParameters) { + throw new BadRequestError('Parameter currentNodeParameters is required.'); + } + + req.params = { + nodeTypeAndVersion: jsonParse(nodeTypeAndVersion), + currentNodeParameters: jsonParse(currentNodeParameters), + credentials: credentials ? jsonParse(credentials) : undefined, + }; + + next(); + } + + /** Returns parameter values which normally get loaded from an external API or get generated dynamically */ + @Get('/options') + async getOptions(req: OptionsRequest): Promise { + const { path, methodName, loadOptions } = req.query; + const { credentials, currentNodeParameters, nodeTypeAndVersion } = req.params; + const additionalData = await getBase(req.user.id, currentNodeParameters); + const loadDataInstance = new DynamicNodeParameters( + nodeTypeAndVersion, + this.nodeTypes, + currentNodeParameters, + credentials, + ); + + if (methodName) { + return loadDataInstance.getOptionsViaMethodName(methodName, path, additionalData); + } + + if (loadOptions) { + return loadDataInstance.getOptionsViaLoadOptions(jsonParse(loadOptions), additionalData); + } + + return []; + } + + @Get('/resource-locator-results', { middlewares: [assertMethodName] }) + async getResourceLocatorResults( + req: ResourceLocatorResultsRequest, + ): Promise { + const { path, methodName, filter, paginationToken } = req.query; + const { credentials, currentNodeParameters, nodeTypeAndVersion } = req.params; + const additionalData = await getBase(req.user.id, currentNodeParameters); + const listSearchInstance = new DynamicNodeParameters( + nodeTypeAndVersion, + this.nodeTypes, + currentNodeParameters, + credentials, + ); + return listSearchInstance.getResourceLocatorResults( + methodName, + path, + additionalData, + filter, + paginationToken, + ); + } + + @Get('/resource-mapper-fields', { middlewares: [assertMethodName] }) + async getResourceMappingFields( + req: ResourceMapperFieldsRequest, + ): Promise { + const { path, methodName } = req.query; + const { credentials, currentNodeParameters, nodeTypeAndVersion } = req.params; + const additionalData = await getBase(req.user.id, currentNodeParameters); + const loadMappingOptionsInstance = new DynamicNodeParameters( + nodeTypeAndVersion, + this.nodeTypes, + currentNodeParameters, + credentials, + ); + return loadMappingOptionsInstance.getResourceMappingFields(methodName, path, additionalData); + } +} + +type BaseRequest = AuthenticatedRequest< + { + nodeTypeAndVersion: INodeTypeNameVersion; + currentNodeParameters: INodeParameters; + credentials?: INodeCredentials; + }, + {}, + {}, + { + path: string; + nodeTypeAndVersion: string; + currentNodeParameters: string; + methodName?: string; + credentials?: string; + } & QueryParams +>; + +/** GET /dynamic-node-parameters/options */ +type OptionsRequest = BaseRequest<{ + loadOptions?: string; +}>; + +/** GET /dynamic-node-parameters/resource-locator-results */ +type ResourceLocatorResultsRequest = BaseRequest<{ + methodName: string; + filter?: string; + paginationToken?: string; +}>; + +/** GET dynamic-node-parameters/resource-mapper-fields */ +type ResourceMapperFieldsRequest = BaseRequest<{ + methodName: string; +}>; diff --git a/packages/cli/src/controllers/index.ts b/packages/cli/src/controllers/index.ts index 4ccee477171d9..dcb0111fc25ce 100644 --- a/packages/cli/src/controllers/index.ts +++ b/packages/cli/src/controllers/index.ts @@ -1,4 +1,5 @@ export { AuthController } from './auth.controller'; +export { DynamicNodeParametersController } from './dynamicNodeParameters.controller'; export { LdapController } from './ldap.controller'; export { MeController } from './me.controller'; export { MFAController } from './mfa.controller'; diff --git a/packages/cli/src/requests.ts b/packages/cli/src/requests.ts index f50cdfda8c507..a8c4c89714d62 100644 --- a/packages/cli/src/requests.ts +++ b/packages/cli/src/requests.ts @@ -397,59 +397,6 @@ export declare namespace OAuthRequest { } } -// ---------------------------------- -// /node-parameter-options -// ---------------------------------- - -export type NodeParameterOptionsRequest = AuthenticatedRequest< - {}, - {}, - {}, - { - nodeTypeAndVersion: string; - methodName: string; - path: string; - currentNodeParameters: string; - credentials: string; - } ->; - -// ---------------------------------- -// /node-list-search -// ---------------------------------- - -export type NodeListSearchRequest = AuthenticatedRequest< - {}, - {}, - {}, - { - nodeTypeAndVersion: string; - methodName: string; - path: string; - currentNodeParameters: string; - credentials: string; - filter?: string; - paginationToken?: string; - } ->; - -// ---------------------------------- -// /get-mapping-fields -// ---------------------------------- - -export type ResourceMapperRequest = AuthenticatedRequest< - {}, - {}, - {}, - { - nodeTypeAndVersion: string; - methodName: string; - path: string; - currentNodeParameters: string; - credentials: string; - } ->; - // ---------------------------------- // /tags // ---------------------------------- diff --git a/packages/core/src/LoadMappingOptions.ts b/packages/core/src/LoadMappingOptions.ts deleted file mode 100644 index 614fe75efc56d..0000000000000 --- a/packages/core/src/LoadMappingOptions.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { IWorkflowExecuteAdditionalData, ResourceMapperFields } from 'n8n-workflow'; - -import * as NodeExecuteFunctions from './NodeExecuteFunctions'; -import { LoadNodeDetails } from './LoadNodeDetails'; - -export class LoadMappingOptions extends LoadNodeDetails { - /** - * Returns the available mapping fields for the ResourceMapper component - */ - async getOptionsViaMethodName( - methodName: string, - additionalData: IWorkflowExecuteAdditionalData, - ): Promise { - const node = this.getTempNode(); - - const nodeType = this.workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion); - const method = nodeType?.methods?.resourceMapping?.[methodName]; - - if (typeof method !== 'function') { - throw new Error( - `The node-type "${node.type}" does not have the method "${methodName}" defined!`, - ); - } - - const thisArgs = NodeExecuteFunctions.getLoadOptionsFunctions( - this.workflow, - node, - this.path, - additionalData, - ); - - return method.call(thisArgs); - } -} diff --git a/packages/core/src/LoadNodeDetails.ts b/packages/core/src/LoadNodeDetails.ts deleted file mode 100644 index a612759f07f91..0000000000000 --- a/packages/core/src/LoadNodeDetails.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { INode } from 'n8n-workflow'; -import { - Workflow, - INodeCredentials, - INodeParameters, - INodeTypeNameVersion, - INodeTypes, -} from 'n8n-workflow'; - -const TEMP_NODE_NAME = 'Temp-Node'; -const TEMP_WORKFLOW_NAME = 'Temp-Workflow'; - -export abstract class LoadNodeDetails { - path: string; - - workflow: Workflow; - - constructor( - nodeTypeNameAndVersion: INodeTypeNameVersion, - nodeTypes: INodeTypes, - path: string, - currentNodeParameters: INodeParameters, - credentials?: INodeCredentials, - ) { - const nodeType = nodeTypes.getByNameAndVersion( - nodeTypeNameAndVersion.name, - nodeTypeNameAndVersion.version, - ); - - this.path = path; - - if (nodeType === undefined) { - throw new Error( - `The node-type "${nodeTypeNameAndVersion.name} v${nodeTypeNameAndVersion.version}" is not known!`, - ); - } - - const nodeData: INode = { - parameters: currentNodeParameters, - id: 'uuid-1234', - name: TEMP_NODE_NAME, - type: nodeTypeNameAndVersion.name, - typeVersion: nodeTypeNameAndVersion.version, - position: [0, 0], - }; - - if (credentials) { - nodeData.credentials = credentials; - } - - const workflowData = { - nodes: [nodeData], - connections: {}, - }; - - this.workflow = new Workflow({ - nodes: workflowData.nodes, - connections: workflowData.connections, - active: false, - nodeTypes, - }); - } - - /** - * Returns data of a fake workflow - */ - getWorkflowData() { - return { - name: TEMP_WORKFLOW_NAME, - active: false, - connections: {}, - nodes: Object.values(this.workflow.nodes), - createdAt: new Date(), - updatedAt: new Date(), - }; - } - - protected getTempNode() { - return this.workflow.getNode(TEMP_NODE_NAME)!; - } -} diff --git a/packages/core/src/LoadNodeListSearch.ts b/packages/core/src/LoadNodeListSearch.ts deleted file mode 100644 index bac92c269b0ae..0000000000000 --- a/packages/core/src/LoadNodeListSearch.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { INodeListSearchResult, IWorkflowExecuteAdditionalData } from 'n8n-workflow'; - -import * as NodeExecuteFunctions from './NodeExecuteFunctions'; -import { LoadNodeDetails } from './LoadNodeDetails'; - -export class LoadNodeListSearch extends LoadNodeDetails { - /** - * Returns the available options via a predefined method - */ - async getOptionsViaMethodName( - methodName: string, - additionalData: IWorkflowExecuteAdditionalData, - filter?: string, - paginationToken?: string, - ): Promise { - const node = this.getTempNode(); - - const nodeType = this.workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion); - const method = nodeType?.methods?.listSearch?.[methodName]; - - if (typeof method !== 'function') { - throw new Error( - `The node-type "${node.type}" does not have the method "${methodName}" defined!`, - ); - } - - const thisArgs = NodeExecuteFunctions.getLoadOptionsFunctions( - this.workflow, - node, - this.path, - additionalData, - ); - - return method.call(thisArgs, filter, paginationToken); - } -} diff --git a/packages/core/src/LoadNodeParameterOptions.ts b/packages/core/src/LoadNodeParameterOptions.ts deleted file mode 100644 index 7bf39e193963d..0000000000000 --- a/packages/core/src/LoadNodeParameterOptions.ts +++ /dev/null @@ -1,123 +0,0 @@ -import type { - ILoadOptions, - INodeExecutionData, - INodeProperties, - INodePropertyOptions, - INodeType, - IRunExecutionData, - ITaskDataConnections, - IWorkflowExecuteAdditionalData, -} from 'n8n-workflow'; -import { RoutingNode } from 'n8n-workflow'; - -import * as NodeExecuteFunctions from './NodeExecuteFunctions'; -import { LoadNodeDetails } from './LoadNodeDetails'; - -export class LoadNodeParameterOptions extends LoadNodeDetails { - /** - * Returns the available options via a predefined method - */ - async getOptionsViaMethodName( - methodName: string, - additionalData: IWorkflowExecuteAdditionalData, - ): Promise { - const node = this.getTempNode(); - - const nodeType = this.workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion); - const method = nodeType?.methods?.loadOptions?.[methodName]; - - if (typeof method !== 'function') { - throw new Error( - `The node-type "${node.type}" does not have the method "${methodName}" defined!`, - ); - } - - const thisArgs = NodeExecuteFunctions.getLoadOptionsFunctions( - this.workflow, - node, - this.path, - additionalData, - ); - - return method.call(thisArgs); - } - - /** - * Returns the available options via a load request information - */ - async getOptionsViaRequestProperty( - loadOptions: ILoadOptions, - additionalData: IWorkflowExecuteAdditionalData, - ): Promise { - const node = this.getTempNode(); - - const nodeType = this.workflow.nodeTypes.getByNameAndVersion(node.type, node?.typeVersion); - - if (!nodeType?.description?.requestDefaults?.baseURL) { - // This in in here for now for security reasons. - // Background: As the full data for the request to make does get send, and the auth data - // will then be applied, would it be possible to retrieve that data like that. By at least - // requiring a baseURL to be defined can at least not a random server be called. - // In the future this code has to get improved that it does not use the request information from - // the request rather resolves it via the parameter-path and nodeType data. - throw new Error( - `The node-type "${node.type}" does not exist or does not have "requestDefaults.baseURL" defined!`, - ); - } - - const mode = 'internal'; - const runIndex = 0; - const connectionInputData: INodeExecutionData[] = []; - const runExecutionData: IRunExecutionData = { resultData: { runData: {} } }; - - const routingNode = new RoutingNode( - this.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 = { - ...nodeType, - ...{ - description: { - ...nodeType.description, - properties: [ - { - displayName: '', - type: 'string', - name: '', - default: '', - routing: loadOptions.routing, - } as INodeProperties, - ], - }, - }, - }; - - const inputData: ITaskDataConnections = { - main: [[{ json: {} }]], - }; - - const optionsData = await routingNode.runNode( - inputData, - runIndex, - tempNode, - { node, source: null, data: {} }, - NodeExecuteFunctions, - ); - - if (optionsData?.length === 0) { - return []; - } - - if (!Array.isArray(optionsData)) { - throw new Error('The returned data is not an array!'); - } - - return optionsData[0].map((item) => item.json) as unknown as INodePropertyOptions[]; - } -} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 0325dcfdb8276..a3556c81cd868 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -9,9 +9,6 @@ export * from './Constants'; export * from './Credentials'; export * from './DirectoryLoader'; export * from './Interfaces'; -export * from './LoadMappingOptions'; -export * from './LoadNodeParameterOptions'; -export * from './LoadNodeListSearch'; export * from './NodeExecuteFunctions'; export * from './WorkflowExecute'; export { NodeExecuteFunctions, UserSettings }; diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index ecd71cd7adc1d..e02252fb62a17 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -45,6 +45,7 @@ import type { BannerName, INodeExecutionData, INodeProperties, + NodeConnectionType, } from 'n8n-workflow'; import type { BulkCommand, Undoable } from '@/models/history'; import type { PartialBy, TupleToUnion } from '@/utils/typeHelpers'; @@ -1305,17 +1306,6 @@ export interface ITabBarItem { disabled?: boolean; } -export interface IResourceLocatorReqParams { - nodeTypeAndVersion: INodeTypeNameVersion; - path: string; - methodName?: string; - searchList?: ILoadOptions; - currentNodeParameters: INodeParameters; - credentials?: INodeCredentials; - filter?: string; - paginationToken?: unknown; -} - export interface IResourceLocatorResultExpanded extends INodeListSearchItems { linkAlt?: string; } @@ -1423,13 +1413,30 @@ export type NodeAuthenticationOption = { displayOptions?: IDisplayOptions; }; -export interface ResourceMapperReqParams { - nodeTypeAndVersion: INodeTypeNameVersion; - path: string; - methodName?: string; - currentNodeParameters: INodeParameters; - credentials?: INodeCredentials; +export declare namespace DynamicNodeParameters { + interface BaseRequest { + path: string; + nodeTypeAndVersion: INodeTypeNameVersion; + currentNodeParameters: INodeParameters; + methodName?: string; + credentials?: INodeCredentials; + } + + interface OptionsRequest extends BaseRequest { + loadOptions?: ILoadOptions; + } + + interface ResourceLocatorResultsRequest extends BaseRequest { + methodName: string; + filter?: string; + paginationToken?: string; + } + + interface ResourceMapperFieldsRequest extends BaseRequest { + methodName: string; + } } + export interface EnvironmentVariable { id: number; key: string; diff --git a/packages/editor-ui/src/api/nodeTypes.ts b/packages/editor-ui/src/api/nodeTypes.ts index a42eb51524f3c..1e99239491c50 100644 --- a/packages/editor-ui/src/api/nodeTypes.ts +++ b/packages/editor-ui/src/api/nodeTypes.ts @@ -1,16 +1,7 @@ import { makeRestApiRequest } from '@/utils/apiUtils'; +import type { DynamicNodeParameters, INodeTranslationHeaders, IRestApiContext } from '@/Interface'; import type { - INodeTranslationHeaders, - IResourceLocatorReqParams, - IRestApiContext, - ResourceMapperReqParams, -} from '@/Interface'; -import type { - IDataObject, - ILoadOptions, - INodeCredentials, INodeListSearchResult, - INodeParameters, INodePropertyOptions, INodeTypeDescription, INodeTypeNameVersion, @@ -38,38 +29,31 @@ export async function getNodesInformation( export async function getNodeParameterOptions( context: IRestApiContext, - sendData: { - nodeTypeAndVersion: INodeTypeNameVersion; - path: string; - methodName?: string; - loadOptions?: ILoadOptions; - currentNodeParameters: INodeParameters; - credentials?: INodeCredentials; - }, + sendData: DynamicNodeParameters.OptionsRequest, ): Promise { - return makeRestApiRequest(context, 'GET', '/node-parameter-options', sendData); + return makeRestApiRequest(context, 'GET', '/dynamic-node-parameters/options', sendData); } export async function getResourceLocatorResults( context: IRestApiContext, - sendData: IResourceLocatorReqParams, + sendData: DynamicNodeParameters.ResourceLocatorResultsRequest, ): Promise { return makeRestApiRequest( context, 'GET', - '/nodes-list-search', - sendData as unknown as IDataObject, + '/dynamic-node-parameters/resource-locator-results', + sendData, ); } export async function getResourceMapperFields( context: IRestApiContext, - sendData: ResourceMapperReqParams, + sendData: DynamicNodeParameters.ResourceMapperFieldsRequest, ): Promise { return makeRestApiRequest( context, 'GET', - '/get-mapping-fields', - sendData as unknown as IDataObject, + '/dynamic-node-parameters/resource-mapper-fields', + sendData, ); } diff --git a/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue b/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue index 4cb9f98b3e195..f7b3cbbe0633a 100644 --- a/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue +++ b/packages/editor-ui/src/components/ResourceLocator/ResourceLocator.vue @@ -166,7 +166,6 @@ import stringify from 'fast-json-stable-stringify'; import type { EventBus } from 'n8n-design-system/utils'; import { createEventBus } from 'n8n-design-system/utils'; import type { - ILoadOptions, INode, INodeCredentials, INodeListSearchItems, @@ -689,9 +688,6 @@ export default defineComponent({ const loadOptionsMethod = this.getPropertyArgument(this.currentMode, 'searchListMethod') as | string | undefined; - const searchList = this.getPropertyArgument(this.currentMode, 'searchList') as - | ILoadOptions - | undefined; const requestParams: IResourceLocatorReqParams = { nodeTypeAndVersion: { @@ -700,7 +696,6 @@ export default defineComponent({ }, path: this.path, methodName: loadOptionsMethod, - searchList, currentNodeParameters: resolvedNodeParameters, credentials: this.node.credentials, ...(params.filter ? { filter: params.filter } : {}), diff --git a/packages/editor-ui/src/stores/nodeTypes.store.ts b/packages/editor-ui/src/stores/nodeTypes.store.ts index fdb8e7ec6b4a7..f40231dcf9a21 100644 --- a/packages/editor-ui/src/stores/nodeTypes.store.ts +++ b/packages/editor-ui/src/stores/nodeTypes.store.ts @@ -7,20 +7,13 @@ import { getResourceMapperFields, } from '@/api/nodeTypes'; import { DEFAULT_NODETYPE_VERSION, STORES } from '@/constants'; -import type { - INodeTypesState, - IResourceLocatorReqParams, - ResourceMapperReqParams, -} from '@/Interface'; +import type { INodeTypesState, DynamicNodeParameters } from '@/Interface'; import { addHeaders, addNodeTranslation } from '@/plugins/i18n'; import { omit } from '@/utils'; import type { - ILoadOptions, INode, - INodeCredentials, INodeListSearchResult, INodeOutputConfiguration, - INodeParameters, INodePropertyOptions, INodeTypeDescription, INodeTypeNameVersion, @@ -249,25 +242,20 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, { addHeaders(headers, rootStore.defaultLocale); } }, - async getNodeParameterOptions(sendData: { - nodeTypeAndVersion: INodeTypeNameVersion; - path: string; - methodName?: string; - loadOptions?: ILoadOptions; - currentNodeParameters: INodeParameters; - credentials?: INodeCredentials; - }): Promise { + async getNodeParameterOptions( + sendData: DynamicNodeParameters.OptionsRequest, + ): Promise { const rootStore = useRootStore(); return getNodeParameterOptions(rootStore.getRestApiContext, sendData); }, async getResourceLocatorResults( - sendData: IResourceLocatorReqParams, + sendData: DynamicNodeParameters.ResourceLocatorResultsRequest, ): Promise { const rootStore = useRootStore(); return getResourceLocatorResults(rootStore.getRestApiContext, sendData); }, async getResourceMapperFields( - sendData: ResourceMapperReqParams, + sendData: DynamicNodeParameters.ResourceMapperFieldsRequest, ): Promise { const rootStore = useRootStore(); try {