load on demand selected workflow inputs

This commit is contained in:
Ivan Atanasov 2024-12-04 22:30:41 +01:00
parent dacd1907f3
commit c83bbcf50a
No known key found for this signature in database
6 changed files with 97 additions and 37 deletions

View file

@ -300,6 +300,11 @@ export class DynamicNodeParametersService {
} }
private getLocalLoadOptionsContext(path: string, additionalData: IWorkflowExecuteAdditionalData) { private getLocalLoadOptionsContext(path: string, additionalData: IWorkflowExecuteAdditionalData) {
return new LocalLoadOptionsContext(additionalData, path, this.workflowLoaderService); return new LocalLoadOptionsContext(
this.nodeTypes,
additionalData,
path,
this.workflowLoaderService,
);
} }
} }

View file

@ -1,22 +1,27 @@
import { get } from 'lodash'; import { get } from 'lodash';
import { ApplicationError } from 'n8n-workflow'; import { ApplicationError, deepCopy, Workflow } from 'n8n-workflow';
import type { import type {
INodeParameterResourceLocator, INodeParameterResourceLocator,
IWorkflowExecuteAdditionalData, IWorkflowExecuteAdditionalData,
NodeParameterValueType, NodeParameterValueType,
ILocalLoadOptionsFunctions, ILocalLoadOptionsFunctions,
IWorkflowLoader, IWorkflowLoader,
IWorkflowNodeContext,
INode, INode,
INodeTypes,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { LoadWorkflowNodeContext } from './workflow-node-context';
export class LocalLoadOptionsContext implements ILocalLoadOptionsFunctions { export class LocalLoadOptionsContext implements ILocalLoadOptionsFunctions {
constructor( constructor(
private nodeTypes: INodeTypes,
private additionalData: IWorkflowExecuteAdditionalData, private additionalData: IWorkflowExecuteAdditionalData,
private path: string, private path: string,
private workflowLoader: IWorkflowLoader, private workflowLoader: IWorkflowLoader,
) {} ) {}
async getExecuteWorkflowTriggerNode(): Promise<INode | undefined> { async getWorkflowNodeContext(nodeType: string): Promise<IWorkflowNodeContext | null> {
const { value } = this.getCurrentNodeParameter('workflowId') as INodeParameterResourceLocator; const { value } = this.getCurrentNodeParameter('workflowId') as INodeParameterResourceLocator;
const workflowId = value as string; const workflowId = value as string;
@ -24,9 +29,25 @@ export class LocalLoadOptionsContext implements ILocalLoadOptionsFunctions {
throw new ApplicationError('No workflowId parameter defined on node!'); throw new ApplicationError('No workflowId parameter defined on node!');
} }
const workflow = await this.workflowLoader.get(workflowId); const dbWorkflow = await this.workflowLoader.get(workflowId);
return workflow.nodes.find((node) => node.type === 'n8n-nodes-base.executeWorkflowTrigger'); const workflowNode = dbWorkflow.nodes.find((node) => node.type === nodeType) as INode;
if (workflowNode) {
const workflow = new Workflow({
nodes: [workflowNode],
connections: {},
active: false,
nodeTypes: this.nodeTypes,
});
const workflowAdditionalData = deepCopy(this.additionalData);
workflowAdditionalData.currentNodeParameters = workflowNode.parameters;
return new LoadWorkflowNodeContext(workflow, workflowNode, workflowAdditionalData);
}
return null;
} }
getCurrentNodeParameter(parameterPath: string): NodeParameterValueType | object | undefined { getCurrentNodeParameter(parameterPath: string): NodeParameterValueType | object | undefined {

View file

@ -0,0 +1,32 @@
import type {
IGetNodeParameterOptions,
INode,
IWorkflowExecuteAdditionalData,
Workflow,
IWorkflowNodeContext,
} from 'n8n-workflow';
import { NodeExecutionContext } from './node-execution-context';
export class LoadWorkflowNodeContext extends NodeExecutionContext implements IWorkflowNodeContext {
readonly getNodeParameter: IWorkflowNodeContext['getNodeParameter'];
constructor(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData) {
super(workflow, node, additionalData, 'internal');
{
this.getNodeParameter = ((
parameterName: string,
itemIndex: number,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fallbackValue?: any,
options?: IGetNodeParameterOptions,
) =>
this._getNodeParameter(
parameterName,
itemIndex,
fallbackValue,
options,
)) as IWorkflowNodeContext['getNodeParameter'];
}
}
}

View file

@ -1,39 +1,37 @@
import type { import {
ILocalLoadOptionsFunctions, EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE,
INode, type ILocalLoadOptionsFunctions,
ResourceMapperField, type ResourceMapperField,
ResourceMapperFields, type ResourceMapperFields,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { getFieldEntries } from '../../GenericFunctions';
export async function getWorkflowInputs( export async function getWorkflowInputs(
this: ILocalLoadOptionsFunctions, this: ILocalLoadOptionsFunctions,
): Promise<ResourceMapperFields> { ): Promise<ResourceMapperFields> {
const executeWorkflowTriggerNode = (await this.getExecuteWorkflowTriggerNode()) as INode; const nodeLoadContext = await this.getWorkflowNodeContext(EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE);
let fields: ResourceMapperField[] = [];
// const fieldValues = getFieldEntries(executeWorkflowTriggerNode); if (nodeLoadContext) {
const fieldValues = getFieldEntries(nodeLoadContext);
const workflowInputFields = [ fields = fieldValues.map((currentWorkflowInput) => {
{ name: 'field1', type: 'string' as const }, const field: ResourceMapperField = {
{ name: 'field2', type: 'number' as const }, id: currentWorkflowInput.name,
{ name: 'field3', type: 'boolean' as const }, displayName: currentWorkflowInput.name,
{ name: 'field4', type: 'any' as const }, required: false,
]; defaultMatch: true,
display: true,
canBeUsedToMatch: true,
};
const fields: ResourceMapperField[] = workflowInputFields.map((currentWorkflowInput) => { if (currentWorkflowInput.type !== 'any') {
const field: ResourceMapperField = { field.type = currentWorkflowInput.type;
id: currentWorkflowInput.name, }
displayName: currentWorkflowInput.name,
required: false,
defaultMatch: true,
display: true,
canBeUsedToMatch: true,
};
if (currentWorkflowInput.type !== 'any') { return field;
field.type = currentWorkflowInput.type; });
} }
return field;
});
return { fields }; return { fields };
} }

View file

@ -1,6 +1,6 @@
import { json as generateSchemaFromExample, type SchemaObject } from 'generate-schema'; import { json as generateSchemaFromExample, type SchemaObject } from 'generate-schema';
import type { JSONSchema7 } from 'json-schema'; import type { JSONSchema7 } from 'json-schema';
import type { FieldValueOption, FieldType, IExecuteFunctions } from 'n8n-workflow'; import type { FieldValueOption, FieldType, IWorkflowNodeContext } from 'n8n-workflow';
import { jsonParse, NodeOperationError } from 'n8n-workflow'; import { jsonParse, NodeOperationError } from 'n8n-workflow';
import { JSON_EXAMPLE, INPUT_SOURCE, WORKFLOW_INPUTS, VALUES, TYPE_OPTIONS } from './constants'; import { JSON_EXAMPLE, INPUT_SOURCE, WORKFLOW_INPUTS, VALUES, TYPE_OPTIONS } from './constants';
@ -40,14 +40,14 @@ function parseJsonSchema(schema: JSONSchema7): FieldValueOption[] | string {
return result; return result;
} }
function parseJsonExample(context: IExecuteFunctions): JSONSchema7 { function parseJsonExample(context: IWorkflowNodeContext): JSONSchema7 {
const jsonString = context.getNodeParameter(JSON_EXAMPLE, 0, '') as string; const jsonString = context.getNodeParameter(JSON_EXAMPLE, 0, '') as string;
const json = jsonParse<SchemaObject>(jsonString); const json = jsonParse<SchemaObject>(jsonString);
return generateSchemaFromExample(json) as JSONSchema7; return generateSchemaFromExample(json) as JSONSchema7;
} }
export function getFieldEntries(context: IExecuteFunctions): FieldValueOption[] { export function getFieldEntries(context: IWorkflowNodeContext): FieldValueOption[] {
const inputSource = context.getNodeParameter(INPUT_SOURCE, 0); const inputSource = context.getNodeParameter(INPUT_SOURCE, 0);
let result: FieldValueOption[] | string = 'Internal Error: Invalid input source'; let result: FieldValueOption[] | string = 'Internal Error: Invalid input source';
try { try {

View file

@ -1067,13 +1067,17 @@ export interface ILoadOptionsFunctions extends FunctionsBase {
options?: IGetNodeParameterOptions, options?: IGetNodeParameterOptions,
): NodeParameterValueType | object | undefined; ): NodeParameterValueType | object | undefined;
getCurrentNodeParameters(): INodeParameters | undefined; getCurrentNodeParameters(): INodeParameters | undefined;
helpers: RequestHelperFunctions & SSHTunnelFunctions; helpers: RequestHelperFunctions & SSHTunnelFunctions;
} }
export type FieldValueOption = { name: string; type: FieldType | 'any' }; export type FieldValueOption = { name: string; type: FieldType | 'any' };
export type IWorkflowNodeContext = ExecuteFunctions.GetNodeParameterFn &
Pick<FunctionsBase, 'getNode'>;
export interface ILocalLoadOptionsFunctions { export interface ILocalLoadOptionsFunctions {
getExecuteWorkflowTriggerNode(): Promise<INode | undefined>; getWorkflowNodeContext(nodeType: string): Promise<IWorkflowNodeContext | null>;
} }
export interface IWorkflowLoader { export interface IWorkflowLoader {