From dacd1907f3a6dc5afccad475da7995820371db4b Mon Sep 17 00:00:00 2001 From: Ivan Atanasov Date: Wed, 4 Dec 2024 17:24:31 +0100 Subject: [PATCH] extract workflow inputs to generic function --- .../dynamic-node-parameters.controller.ts | 4 +- .../dynamic-node-parameters.service.ts | 2 - .../local-load-options-context.ts | 16 +-- .../methods/resourceMapping.ts | 13 +- .../ExecuteWorkflowTrigger.node.ts | 117 ++---------------- .../nodes/ExecuteWorkflow/GenericFunctions.ts | 75 +++++++++++ .../nodes/ExecuteWorkflow/constants.ts | 36 ++++++ packages/workflow/src/Interfaces.ts | 2 +- 8 files changed, 137 insertions(+), 128 deletions(-) create mode 100644 packages/nodes-base/nodes/ExecuteWorkflow/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/ExecuteWorkflow/constants.ts diff --git a/packages/cli/src/controllers/dynamic-node-parameters.controller.ts b/packages/cli/src/controllers/dynamic-node-parameters.controller.ts index dd02f72932..2858fd99ca 100644 --- a/packages/cli/src/controllers/dynamic-node-parameters.controller.ts +++ b/packages/cli/src/controllers/dynamic-node-parameters.controller.ts @@ -95,7 +95,7 @@ export class DynamicNodeParametersController { @Post('/local-resource-mapper-fields') async getLocalResourceMappingFields(req: DynamicNodeParametersRequest.ResourceMapperFields) { - const { path, methodName, credentials, currentNodeParameters, nodeTypeAndVersion } = req.body; + const { path, methodName, currentNodeParameters, nodeTypeAndVersion } = req.body; if (!methodName) throw new BadRequestError('Missing `methodName` in request body'); @@ -106,8 +106,6 @@ export class DynamicNodeParametersController { path, additionalData, nodeTypeAndVersion, - currentNodeParameters, - credentials, ); } diff --git a/packages/cli/src/services/dynamic-node-parameters.service.ts b/packages/cli/src/services/dynamic-node-parameters.service.ts index 57956e3b4e..98ed48e2ac 100644 --- a/packages/cli/src/services/dynamic-node-parameters.service.ts +++ b/packages/cli/src/services/dynamic-node-parameters.service.ts @@ -193,8 +193,6 @@ export class DynamicNodeParametersService { path: string, additionalData: IWorkflowExecuteAdditionalData, nodeTypeAndVersion: INodeTypeNameVersion, - currentNodeParameters: INodeParameters, - credentials?: INodeCredentials, ): Promise { const nodeType = this.getNodeType(nodeTypeAndVersion); const method = this.getMethod('localResourceMapping', methodName, nodeType); diff --git a/packages/core/src/node-execution-context/local-load-options-context.ts b/packages/core/src/node-execution-context/local-load-options-context.ts index cef0509c6e..4e88fddbb5 100644 --- a/packages/core/src/node-execution-context/local-load-options-context.ts +++ b/packages/core/src/node-execution-context/local-load-options-context.ts @@ -5,8 +5,8 @@ import type { IWorkflowExecuteAdditionalData, NodeParameterValueType, ILocalLoadOptionsFunctions, - FieldValueOption, IWorkflowLoader, + INode, } from 'n8n-workflow'; export class LocalLoadOptionsContext implements ILocalLoadOptionsFunctions { @@ -16,7 +16,7 @@ export class LocalLoadOptionsContext implements ILocalLoadOptionsFunctions { private workflowLoader: IWorkflowLoader, ) {} - async getWorkflowInputValues(): Promise { + async getExecuteWorkflowTriggerNode(): Promise { const { value } = this.getCurrentNodeParameter('workflowId') as INodeParameterResourceLocator; const workflowId = value as string; @@ -26,17 +26,7 @@ export class LocalLoadOptionsContext implements ILocalLoadOptionsFunctions { const workflow = await this.workflowLoader.get(workflowId); - workflow.nodes.find((node) => node.type === 'n8n-nodes-base.start'); - - // TODO: load the inputs from the workflow - const dummyFields = [ - { name: 'field1', type: 'string' as const }, - { name: 'field2', type: 'number' as const }, - { name: 'field3', type: 'boolean' as const }, - { name: 'field4', type: 'any' as const }, - ]; - - return dummyFields; + return workflow.nodes.find((node) => node.type === 'n8n-nodes-base.executeWorkflowTrigger'); } getCurrentNodeParameter(parameterPath: string): NodeParameterValueType | object | undefined { diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/methods/resourceMapping.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/methods/resourceMapping.ts index 027d9b3c91..6c9cfa99c2 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/methods/resourceMapping.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/methods/resourceMapping.ts @@ -1,6 +1,6 @@ import type { - FieldValueOption, ILocalLoadOptionsFunctions, + INode, ResourceMapperField, ResourceMapperFields, } from 'n8n-workflow'; @@ -8,7 +8,16 @@ import type { export async function getWorkflowInputs( this: ILocalLoadOptionsFunctions, ): Promise { - const workflowInputFields = (await this.getWorkflowInputValues()) as FieldValueOption[]; + const executeWorkflowTriggerNode = (await this.getExecuteWorkflowTriggerNode()) as INode; + + // const fieldValues = getFieldEntries(executeWorkflowTriggerNode); + + const workflowInputFields = [ + { name: 'field1', type: 'string' as const }, + { name: 'field2', type: 'number' as const }, + { name: 'field3', type: 'boolean' as const }, + { name: 'field4', type: 'any' as const }, + ]; const fields: ResourceMapperField[] = workflowInputFields.map((currentWorkflowInput) => { const field: ResourceMapperField = { diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts index 292626ab87..8873291222 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts @@ -1,5 +1,3 @@ -import { json as generateSchemaFromExample, type SchemaObject } from 'generate-schema'; -import type { JSONSchema7 } from 'json-schema'; import { type INodeExecutionData, NodeConnectionType, @@ -8,113 +6,18 @@ import { type INodeType, type INodeTypeDescription, validateFieldType, - type FieldType, - jsonParse, - FieldValueOption, } from 'n8n-workflow'; -const INPUT_SOURCE = 'inputSource'; -const WORKFLOW_INPUTS = 'workflowInputs'; -const INPUT_OPTIONS = 'inputOptions'; -const VALUES = 'values'; -const JSON_EXAMPLE = 'jsonExample'; -const TYPE_OPTIONS: Array<{ name: string; value: FieldType | 'any' }> = [ - { - name: 'Allow Any Type', - value: 'any', - }, - { - name: 'String', - value: 'string', - }, - { - name: 'Number', - value: 'number', - }, - { - name: 'Boolean', - value: 'boolean', - }, - { - name: 'Array', - value: 'array', - }, - { - name: 'Object', - value: 'object', - }, - // Intentional omission of `dateTime`, `time`, `string-alphanumeric`, `form-fields`, `jwt` and `url` -]; -const SUPPORTED_TYPES = TYPE_OPTIONS.map((x) => x.value); - -const DEFAULT_PLACEHOLDER = null; - -function parseJsonSchema(schema: JSONSchema7): FieldValueOption[] | string { - if (!schema?.properties) { - return 'Invalid JSON schema. Missing key `properties` in schema'; - } - - if (typeof schema.properties !== 'object') { - return 'Invalid JSON schema. Key `properties` is not an object'; - } - - const result: FieldValueOption[] = []; - for (const [name, v] of Object.entries(schema.properties)) { - if (typeof v !== 'object') { - return `Invalid JSON schema. Value for property '${name}' is not an object`; - } - - const type = v?.type; - - if (type === 'null') { - result.push({ name, type: 'any' }); - } else if (Array.isArray(type)) { - // Schema allows an array of types, but we don't - return `Invalid JSON schema. Array of types for property '${name}' is not supported by n8n. Either provide a single type or use type 'any' to allow any type`; - } else if (typeof type !== 'string') { - return `Invalid JSON schema. Unexpected non-string type ${type} for property '${name}'`; - } else if (!SUPPORTED_TYPES.includes(type as never)) { - return `Invalid JSON schema. Unsupported type ${type} for property '${name}'. Supported types are ${JSON.stringify(SUPPORTED_TYPES, null, 1)}`; - } else { - result.push({ name, type: type as FieldType }); - } - } - return result; -} - -function parseJsonExample(context: IExecuteFunctions): JSONSchema7 { - const jsonString = context.getNodeParameter(JSON_EXAMPLE, 0, '') as string; - const json = jsonParse(jsonString); - - return generateSchemaFromExample(json) as JSONSchema7; -} - -function getFieldEntries(context: IExecuteFunctions): FieldValueOption[] { - const inputSource = context.getNodeParameter(INPUT_SOURCE, 0) as string; - let result: FieldValueOption[] | string = 'Internal Error: Invalid input source'; - try { - if (inputSource === WORKFLOW_INPUTS) { - result = context.getNodeParameter( - `${WORKFLOW_INPUTS}.${VALUES}`, - 0, - [], - ) as FieldValueOption[]; - } else if (inputSource === JSON_EXAMPLE) { - const schema = parseJsonExample(context); - result = parseJsonSchema(schema); - } - } catch (e: unknown) { - result = - e && typeof e === 'object' && 'message' in e && typeof e.message === 'string' - ? e.message - : `Unknown error occurred: ${JSON.stringify(e)}`; - } - - if (Array.isArray(result)) { - return result; - } - throw new NodeOperationError(context.getNode(), result); -} +import { + INPUT_SOURCE, + WORKFLOW_INPUTS, + JSON_EXAMPLE, + VALUES, + INPUT_OPTIONS, + DEFAULT_PLACEHOLDER, + TYPE_OPTIONS, +} from '../constants'; +import { getFieldEntries } from '../GenericFunctions'; export class ExecuteWorkflowTrigger implements INodeType { description: INodeTypeDescription = { diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/GenericFunctions.ts b/packages/nodes-base/nodes/ExecuteWorkflow/GenericFunctions.ts new file mode 100644 index 0000000000..0f03f364fb --- /dev/null +++ b/packages/nodes-base/nodes/ExecuteWorkflow/GenericFunctions.ts @@ -0,0 +1,75 @@ +import { json as generateSchemaFromExample, type SchemaObject } from 'generate-schema'; +import type { JSONSchema7 } from 'json-schema'; +import type { FieldValueOption, FieldType, IExecuteFunctions } from 'n8n-workflow'; +import { jsonParse, NodeOperationError } from 'n8n-workflow'; + +import { JSON_EXAMPLE, INPUT_SOURCE, WORKFLOW_INPUTS, VALUES, TYPE_OPTIONS } from './constants'; + +const SUPPORTED_TYPES = TYPE_OPTIONS.map((x) => x.value); + +function parseJsonSchema(schema: JSONSchema7): FieldValueOption[] | string { + if (!schema?.properties) { + return 'Invalid JSON schema. Missing key `properties` in schema'; + } + + if (typeof schema.properties !== 'object') { + return 'Invalid JSON schema. Key `properties` is not an object'; + } + + const result: FieldValueOption[] = []; + for (const [name, v] of Object.entries(schema.properties)) { + if (typeof v !== 'object') { + return `Invalid JSON schema. Value for property '${name}' is not an object`; + } + + const type = v?.type; + + if (type === 'null') { + result.push({ name, type: 'any' }); + } else if (Array.isArray(type)) { + // Schema allows an array of types, but we don't + return `Invalid JSON schema. Array of types for property '${name}' is not supported by n8n. Either provide a single type or use type 'any' to allow any type`; + } else if (typeof type !== 'string') { + return `Invalid JSON schema. Unexpected non-string type ${type} for property '${name}'`; + } else if (!SUPPORTED_TYPES.includes(type as never)) { + return `Invalid JSON schema. Unsupported type ${type} for property '${name}'. Supported types are ${JSON.stringify(SUPPORTED_TYPES, null, 1)}`; + } else { + result.push({ name, type: type as FieldType }); + } + } + return result; +} + +function parseJsonExample(context: IExecuteFunctions): JSONSchema7 { + const jsonString = context.getNodeParameter(JSON_EXAMPLE, 0, '') as string; + const json = jsonParse(jsonString); + + return generateSchemaFromExample(json) as JSONSchema7; +} + +export function getFieldEntries(context: IExecuteFunctions): FieldValueOption[] { + const inputSource = context.getNodeParameter(INPUT_SOURCE, 0); + let result: FieldValueOption[] | string = 'Internal Error: Invalid input source'; + try { + if (inputSource === WORKFLOW_INPUTS) { + result = context.getNodeParameter( + `${WORKFLOW_INPUTS}.${VALUES}`, + 0, + [], + ) as FieldValueOption[]; + } else if (inputSource === JSON_EXAMPLE) { + const schema = parseJsonExample(context); + result = parseJsonSchema(schema); + } + } catch (e: unknown) { + result = + e && typeof e === 'object' && 'message' in e && typeof e.message === 'string' + ? e.message + : `Unknown error occurred: ${JSON.stringify(e)}`; + } + + if (Array.isArray(result)) { + return result; + } + throw new NodeOperationError(context.getNode(), result); +} diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/constants.ts b/packages/nodes-base/nodes/ExecuteWorkflow/constants.ts new file mode 100644 index 0000000000..17afaf45ed --- /dev/null +++ b/packages/nodes-base/nodes/ExecuteWorkflow/constants.ts @@ -0,0 +1,36 @@ +import type { FieldType } from 'n8n-workflow'; + +export const INPUT_SOURCE = 'inputSource'; +export const WORKFLOW_INPUTS = 'workflowInputs'; +export const INPUT_OPTIONS = 'inputOptions'; +export const VALUES = 'values'; +export const JSON_EXAMPLE = 'jsonExample'; +export const TYPE_OPTIONS: Array<{ name: string; value: FieldType | 'any' }> = [ + { + name: 'Allow Any Type', + value: 'any', + }, + { + name: 'String', + value: 'string', + }, + { + name: 'Number', + value: 'number', + }, + { + name: 'Boolean', + value: 'boolean', + }, + { + name: 'Array', + value: 'array', + }, + { + name: 'Object', + value: 'object', + }, + // Intentional omission of `dateTime`, `time`, `string-alphanumeric`, `form-fields`, `jwt` and `url` +]; + +export const DEFAULT_PLACEHOLDER = null; diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 647d49470c..d9719ccf40 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -1073,7 +1073,7 @@ export interface ILoadOptionsFunctions extends FunctionsBase { export type FieldValueOption = { name: string; type: FieldType | 'any' }; export interface ILocalLoadOptionsFunctions { - getWorkflowInputValues(): Promise; + getExecuteWorkflowTriggerNode(): Promise; } export interface IWorkflowLoader {