extract convert/select workflow inputs to generic function

This commit is contained in:
Ivan Atanasov 2024-12-04 23:08:38 +01:00
parent c83bbcf50a
commit f5b7ecdcda
No known key found for this signature in database
4 changed files with 126 additions and 89 deletions

View file

@ -1,15 +1,34 @@
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import type {
ExecuteWorkflowData,
FieldValueOption,
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
ResourceMapperField,
} from 'n8n-workflow';
import { getWorkflowInfo } from './GenericFunctions';
import { getWorkflowInputs } from './methods/resourceMapping';
import { loadWorkflowInputMappings } from './methods/resourceMapping';
import { generatePairedItemData } from '../../../utils/utilities';
import { getWorkflowInputData } from '../GenericFunctions';
function getCurrentWorkflowInputData(this: IExecuteFunctions) {
const inputData = this.getInputData();
if (this.getNode().typeVersion < 1.2) {
return inputData;
} else {
const schema = this.getNodeParameter('workflowInputs.schema', 0, []) as ResourceMapperField[];
const newParams = schema
.filter((x) => !x.removed)
.map((x) => ({ name: x.displayName, type: x.type ?? 'any' })) as FieldValueOption[];
// TODO: map every row to field values so we can set the static value or expression later on
return getWorkflowInputData.call(this, inputData, newParams);
}
}
export class ExecuteWorkflow implements INodeType {
description: INodeTypeDescription = {
@ -201,7 +220,7 @@ export class ExecuteWorkflow implements INodeType {
typeOptions: {
loadOptionsDependsOn: ['workflowId.value'],
resourceMapper: {
localResourceMapperMethod: 'getWorkflowInputs',
localResourceMapperMethod: 'loadWorkflowInputMappings',
valuesLabel: 'Workflow Inputs',
mode: 'add',
fieldWords: {
@ -266,14 +285,14 @@ export class ExecuteWorkflow implements INodeType {
methods = {
localResourceMapping: {
getWorkflowInputs,
loadWorkflowInputMappings,
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const source = this.getNodeParameter('source', 0) as string;
const mode = this.getNodeParameter('mode', 0, false) as string;
const items = this.getInputData();
const items = getCurrentWorkflowInputData.call(this);
const workflowProxy = this.getWorkflowDataProxy(0);
const currentWorkflowId = workflowProxy.$workflow.id as string;

View file

@ -7,7 +7,7 @@ import {
import { getFieldEntries } from '../../GenericFunctions';
export async function getWorkflowInputs(
export async function loadWorkflowInputMappings(
this: ILocalLoadOptionsFunctions,
): Promise<ResourceMapperFields> {
const nodeLoadContext = await this.getWorkflowNodeContext(EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE);

View file

@ -1,11 +1,8 @@
import {
type INodeExecutionData,
NodeConnectionType,
NodeOperationError,
type IExecuteFunctions,
type INodeType,
type INodeTypeDescription,
validateFieldType,
} from 'n8n-workflow';
import {
@ -14,10 +11,9 @@ import {
JSON_EXAMPLE,
VALUES,
INPUT_OPTIONS,
DEFAULT_PLACEHOLDER,
TYPE_OPTIONS,
} from '../constants';
import { getFieldEntries } from '../GenericFunctions';
import { getFieldEntries, getWorkflowInputData } from '../GenericFunctions';
export class ExecuteWorkflowTrigger implements INodeType {
description: INodeTypeDescription = {
@ -202,83 +198,9 @@ export class ExecuteWorkflowTrigger implements INodeType {
if (this.getNode().typeVersion < 1.1) {
return [inputData];
} else {
const items: INodeExecutionData[] = [];
const newParams = getFieldEntries(this);
for (const [itemIndex, item] of inputData.entries()) {
const attemptToConvertTypes = this.getNodeParameter(
`${INPUT_OPTIONS}.attemptToConvertTypes`,
itemIndex,
false,
);
const ignoreTypeErrors = this.getNodeParameter(
`${INPUT_OPTIONS}.ignoreTypeErrors`,
itemIndex,
false,
);
// Fields listed here will explicitly overwrite original fields
const newItem: INodeExecutionData = {
json: {},
index: itemIndex,
// TODO: Ensure we handle sub-execution jumps correctly.
// metadata: {
// subExecution: {
// executionId: 'uhh',
// workflowId: 'maybe?',
// },
// },
pairedItem: { item: itemIndex },
};
try {
const newParams = getFieldEntries(this);
for (const { name, type } of newParams) {
if (!item.json.hasOwnProperty(name)) {
newItem.json[name] = DEFAULT_PLACEHOLDER;
continue;
}
const result =
type === 'any'
? ({ valid: true, newValue: item.json[name] } as const)
: validateFieldType(name, item.json[name], type, {
strict: !attemptToConvertTypes,
parseStrings: true, // Default behavior is to accept anything as a string, this is a good opportunity for a stricter boundary
});
if (!result.valid) {
if (ignoreTypeErrors) {
newItem.json[name] = item.json[name];
continue;
}
throw new NodeOperationError(this.getNode(), result.errorMessage, {
itemIndex,
});
} else {
// If the value is `null` or `undefined`, then `newValue` is not in the returned object
if (result.hasOwnProperty('newValue')) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
newItem.json[name] = result.newValue;
} else {
newItem.json[name] = item.json[name];
}
}
}
items.push(newItem);
} catch (error) {
if (this.continueOnFail()) {
/** todo error case? */
} else {
throw new NodeOperationError(this.getNode(), error, {
itemIndex,
});
}
}
}
return [items];
return [getWorkflowInputData.call(this, inputData, newParams)];
}
}
}

View file

@ -1,9 +1,23 @@
import { json as generateSchemaFromExample, type SchemaObject } from 'generate-schema';
import type { JSONSchema7 } from 'json-schema';
import type { FieldValueOption, FieldType, IWorkflowNodeContext } from 'n8n-workflow';
import { jsonParse, NodeOperationError } from 'n8n-workflow';
import type {
FieldValueOption,
FieldType,
IWorkflowNodeContext,
INodeExecutionData,
IExecuteFunctions,
} from 'n8n-workflow';
import { jsonParse, NodeOperationError, validateFieldType } 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,
INPUT_OPTIONS,
DEFAULT_PLACEHOLDER,
} from './constants';
const SUPPORTED_TYPES = TYPE_OPTIONS.map((x) => x.value);
@ -73,3 +87,85 @@ export function getFieldEntries(context: IWorkflowNodeContext): FieldValueOption
}
throw new NodeOperationError(context.getNode(), result);
}
export function getWorkflowInputData(
this: IExecuteFunctions,
inputData: INodeExecutionData[],
newParams: FieldValueOption[],
): INodeExecutionData[] {
const items: INodeExecutionData[] = [];
for (const [itemIndex, item] of inputData.entries()) {
const attemptToConvertTypes = this.getNodeParameter(
`${INPUT_OPTIONS}.attemptToConvertTypes`,
itemIndex,
false,
);
const ignoreTypeErrors = this.getNodeParameter(
`${INPUT_OPTIONS}.ignoreTypeErrors`,
itemIndex,
false,
);
// Fields listed here will explicitly overwrite original fields
const newItem: INodeExecutionData = {
json: {},
index: itemIndex,
// TODO: Ensure we handle sub-execution jumps correctly.
// metadata: {
// subExecution: {
// executionId: 'uhh',
// workflowId: 'maybe?',
// },
// },
pairedItem: { item: itemIndex },
};
try {
for (const { name, type } of newParams) {
if (!item.json.hasOwnProperty(name)) {
newItem.json[name] = DEFAULT_PLACEHOLDER;
continue;
}
const result =
type === 'any'
? ({ valid: true, newValue: item.json[name] } as const)
: validateFieldType(name, item.json[name], type, {
strict: !attemptToConvertTypes,
parseStrings: true, // Default behavior is to accept anything as a string, this is a good opportunity for a stricter boundary
});
if (!result.valid) {
if (ignoreTypeErrors) {
newItem.json[name] = item.json[name];
continue;
}
throw new NodeOperationError(this.getNode(), result.errorMessage, {
itemIndex,
});
} else {
// If the value is `null` or `undefined`, then `newValue` is not in the returned object
if (result.hasOwnProperty('newValue')) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
newItem.json[name] = result.newValue;
} else {
newItem.json[name] = item.json[name];
}
}
}
items.push(newItem);
} catch (error) {
if (this.continueOnFail()) {
/** todo error case? */
} else {
throw new NodeOperationError(this.getNode(), error, {
itemIndex,
});
}
}
}
return items;
}