extract workflow inputs to generic function

This commit is contained in:
Ivan Atanasov 2024-12-04 17:24:31 +01:00
parent 4ae18fa25f
commit dacd1907f3
No known key found for this signature in database
8 changed files with 137 additions and 128 deletions

View file

@ -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,
);
}

View file

@ -193,8 +193,6 @@ export class DynamicNodeParametersService {
path: string,
additionalData: IWorkflowExecuteAdditionalData,
nodeTypeAndVersion: INodeTypeNameVersion,
currentNodeParameters: INodeParameters,
credentials?: INodeCredentials,
): Promise<ResourceMapperFields> {
const nodeType = this.getNodeType(nodeTypeAndVersion);
const method = this.getMethod('localResourceMapping', methodName, nodeType);

View file

@ -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<FieldValueOption[]> {
async getExecuteWorkflowTriggerNode(): Promise<INode | undefined> {
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 {

View file

@ -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<ResourceMapperFields> {
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 = {

View file

@ -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<SchemaObject>(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 = {

View file

@ -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<SchemaObject>(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);
}

View file

@ -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;

View file

@ -1073,7 +1073,7 @@ export interface ILoadOptionsFunctions extends FunctionsBase {
export type FieldValueOption = { name: string; type: FieldType | 'any' };
export interface ILocalLoadOptionsFunctions {
getWorkflowInputValues(): Promise<FieldValueOption[]>;
getExecuteWorkflowTriggerNode(): Promise<INode | undefined>;
}
export interface IWorkflowLoader {