From c8f1b05f63320dff5858662cb85ace9cfff28521 Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Tue, 17 Dec 2024 12:04:32 +0100 Subject: [PATCH 01/13] wip commit --- .../core/src/node-execution-context/utils.ts | 13 ++-- .../src/components/ParameterInputFull.vue | 1 + .../src/components/ParameterInputList.vue | 1 + .../ResourceMapper/ResourceMapper.vue | 48 +++++++++++++++ .../ExecuteWorkflow/ExecuteWorkflow.node.ts | 61 ++++++++++++++++--- .../ExecuteWorkflowTrigger.node.ts | 58 +++++++----------- .../nodes/ExecuteWorkflow/GenericFunctions.ts | 50 ++------------- .../nodes/ExecuteWorkflow/constants.ts | 1 - .../nodes-base/nodes/Set/v2/helpers/utils.ts | 2 +- packages/workflow/src/Interfaces.ts | 4 ++ packages/workflow/src/TypeValidation.ts | 2 +- 11 files changed, 146 insertions(+), 95 deletions(-) diff --git a/packages/core/src/node-execution-context/utils.ts b/packages/core/src/node-execution-context/utils.ts index a09147d543b23..bcde2fc6d7365 100644 --- a/packages/core/src/node-execution-context/utils.ts +++ b/packages/core/src/node-execution-context/utils.ts @@ -83,7 +83,6 @@ const validateResourceMapperValue = ( for (let i = 0; i < paramValueNames.length; i++) { const key = paramValueNames[i]; const resolvedValue = paramValues[key]; - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call const schemaEntry = schema.find((s) => s.id === key); if ( @@ -99,15 +98,19 @@ const validateResourceMapperValue = ( }; } - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (schemaEntry?.type) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const validationResult = validateFieldType(key, resolvedValue, schemaEntry.type, { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access valueOptions: schemaEntry.options, + strict: !resourceMapperField.attemptToConvertTypes, + parseStrings: !!resourceMapperField.convertFieldsToString, }); + if (!validationResult.valid) { - return { ...validationResult, fieldName: key }; + if (!resourceMapperField.ignoreTypeMismatchErrors) { + return { ...validationResult, fieldName: key }; + } else { + paramValues[key] = resolvedValue; + } } else { // If it's valid, set the casted value paramValues[key] = validationResult.newValue; diff --git a/packages/editor-ui/src/components/ParameterInputFull.vue b/packages/editor-ui/src/components/ParameterInputFull.vue index 332011b9ea743..9e1aeb2ba0c5c 100644 --- a/packages/editor-ui/src/components/ParameterInputFull.vue +++ b/packages/editor-ui/src/components/ParameterInputFull.vue @@ -102,6 +102,7 @@ function optionSelected(command: string) { } function valueChanged(parameterData: IUpdateInformation) { + console.log('valueChanged', parameterData); emit('update', parameterData); } diff --git a/packages/editor-ui/src/components/ParameterInputList.vue b/packages/editor-ui/src/components/ParameterInputList.vue index 91249fbafba45..30adef5d15758 100644 --- a/packages/editor-ui/src/components/ParameterInputList.vue +++ b/packages/editor-ui/src/components/ParameterInputList.vue @@ -423,6 +423,7 @@ function displayNodeParameter( } function valueChanged(parameterData: IUpdateInformation): void { + console.log('ParameterInputList valueChanged'); emit('valueChanged', parameterData); } diff --git a/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue b/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue index f8103cc624ef2..6c1bd0da80931 100644 --- a/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue +++ b/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue @@ -52,6 +52,10 @@ const state = reactive({ value: {}, matchingColumns: [] as string[], schema: [] as ResourceMapperField[], + ignoreTypeMismatchErrors: false, + attemptToConvertTypes: false, + // This will always be true if `showTypeConversionOptions` is provided + convertFieldsToString: false, } as ResourceMapperValue, parameterValues: {} as INodeParameters, loading: false, @@ -97,6 +101,10 @@ onMounted(async () => { ...state.parameterValues, parameters: props.node.parameters, }; + + if (showTypeConversionOptions.value) { + state.paramValue.convertFieldsToString = true; + } } const params = state.parameterValues.parameters as INodeParameters; const parameterName = props.parameter.name; @@ -161,6 +169,10 @@ const showMappingModeSelect = computed(() => { return props.parameter.typeOptions?.resourceMapper?.supportAutoMap !== false; }); +const showTypeConversionOptions = computed(() => { + return props.parameter.typeOptions?.resourceMapper?.showTypeConversionOptions === true; +}); + const showMatchingColumnsSelector = computed(() => { return ( !state.loading && @@ -568,5 +580,41 @@ defineExpose({ }) }} + + diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts index 4403a9f7384c6..cedfe4d1b769c 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts @@ -14,8 +14,10 @@ import { getWorkflowInfo } from './GenericFunctions'; import { loadWorkflowInputMappings } from './methods/resourceMapping'; import { generatePairedItemData } from '../../../utils/utilities'; import { getWorkflowInputData } from '../GenericFunctions'; +import _ from 'lodash'; +import { FALLBACK_DEFAULT_VALUE } from '../constants'; -function getWorkflowInputValues(this: IExecuteFunctions) { +function getWorkflowInputValues(this: IExecuteFunctions): INodeExecutionData[] { const inputData = this.getInputData(); return inputData.map((item, itemIndex) => { @@ -39,18 +41,30 @@ function getWorkflowInputValues(this: IExecuteFunctions) { } function getCurrentWorkflowInputData(this: IExecuteFunctions) { - const inputData = getWorkflowInputValues.call(this); + const inputData: INodeExecutionData[] = getWorkflowInputValues.call(this); const schema = this.getNodeParameter('workflowInputs.schema', 0, []) as ResourceMapperField[]; if (schema.length === 0) { return inputData; } else { - const newParams = schema - .filter((x) => !x.removed) - .map((x) => ({ name: x.displayName, type: x.type ?? 'any' })) as FieldValueOption[]; - - return getWorkflowInputData.call(this, inputData, newParams); + const [removed, added] = _.partition(schema, (x) => x.removed); + + const addedParams: FieldValueOption[] = added.map((x) => ({ + name: x.displayName, + type: x.type ?? 'any', + })); + + const removedKeys = new Set(removed.map((x) => x.displayName)); + const filteredInputData: INodeExecutionData[] = inputData.map((item, index) => ({ + json: _.assign( + // itemIndex and the other thing + _.pickBy(item.json, (_v, k) => !removedKeys.has(k)), + _.fromPairs(addedParams.map((x) => [x, FALLBACK_DEFAULT_VALUE])), + ), + index, + })); + return filteredInputData; } } @@ -84,6 +98,14 @@ export class ExecuteWorkflow implements INodeType { }, ], }, + { + displayName: 'Outdated Version Warning', + name: 'outdatedVersionWarning', + type: 'notice', + description: 'Please update this node by removing it and adding a new one', + displayOptions: { show: { '@version': [{ _cnd: { lte: 1.1 } }] } }, + default: '', + }, { displayName: 'Source', name: 'source', @@ -254,6 +276,7 @@ export class ExecuteWorkflow implements INodeType { addAllFields: true, multiKeyMatch: false, supportAutoMap: false, + showTypeConversionOptions: true, }, }, displayOptions: { @@ -302,6 +325,30 @@ export class ExecuteWorkflow implements INodeType { description: 'Whether the main workflow should wait for the sub-workflow to complete its execution before proceeding', }, + // Note that, while the defaults are true, the user has to add these in the first place + // We default to false if absent in the execute function + { + displayName: 'Attempt to Convert Types', + name: 'attemptToConvertTypes', + type: 'boolean', + default: true, + description: + 'Whether to attempt conversion on type mismatch, rather than directly returning an Error', + displayOptions: { + show: { '@version': [{ _cnd: { gte: 1.2 } }] }, + }, + }, + { + displayName: 'Ignore Type Mismatch Errors', + name: 'ignoreTypeErrors', + type: 'boolean', + default: true, + description: + 'Whether type mismatches should be ignored, rather than returning an Error', + displayOptions: { + show: { '@version': [{ _cnd: { gte: 1.2 } }] }, + }, + }, ], }, ], diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts index 7853f96f2fde6..f7678c9674eac 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts @@ -1,4 +1,6 @@ +import _ from 'lodash'; import { + type INodeExecutionData, NodeConnectionType, type IExecuteFunctions, type INodeType, @@ -10,11 +12,10 @@ import { WORKFLOW_INPUTS, JSON_EXAMPLE, VALUES, - INPUT_OPTIONS, TYPE_OPTIONS, PASSTHROUGH, } from '../constants'; -import { getFieldEntries, getWorkflowInputData } from '../GenericFunctions'; +import { getFieldEntries } from '../GenericFunctions'; export class ExecuteWorkflowTrigger implements INodeType { description: INodeTypeDescription = { @@ -163,39 +164,6 @@ export class ExecuteWorkflowTrigger implements INodeType { }, ], }, - { - displayName: 'Input Options', - name: INPUT_OPTIONS, - placeholder: 'Options', - type: 'collection', - description: 'Options controlling how input data is handled, converted and rejected', - displayOptions: { - show: { '@version': [{ _cnd: { gte: 1.1 } }] }, - }, - default: {}, - // Note that, while the defaults are true, the user has to add these in the first place - // We default to false if absent in the execute function below - options: [ - { - displayName: 'Attempt to Convert Types', - name: 'attemptToConvertTypes', - type: 'boolean', - default: true, - description: - 'Whether to attempt conversion on type mismatch, rather than directly returning an Error', - noDataExpression: true, - }, - { - displayName: 'Ignore Type Mismatch Errors', - name: 'ignoreTypeErrors', - type: 'boolean', - default: true, - description: - 'Whether type mismatches should be ignored, rather than returning an Error', - noDataExpression: true, - }, - ], - }, ], }; @@ -203,12 +171,30 @@ export class ExecuteWorkflowTrigger implements INodeType { const inputData = this.getInputData(); const inputSource = this.getNodeParameter(INPUT_SOURCE, 0, PASSTHROUGH) as string; + // Note on the data we receive from ExecuteWorkflow caller: + // + // The caller typechecks all fields explicitly provided by the user via the resourceMapper + // and removes all fields that are in the schema, but `removed` in the resourceMapper. + // + // Then these fields - including removed fields - "shadow" inputData fields, hiding them from the trigger. + // + // In passthrough and legacy versions, inputData will line up since the resourceMapper is empty + // In other cases we will already have matching types, so we just need to be permissive on this end, + // while filtering out fields that are not in the schema. + if (inputSource === PASSTHROUGH) { return [inputData]; } else { const newParams = getFieldEntries(this); + const newKeys = new Set(newParams.map(({ name }) => name)); + + // todo: add keys in schema anyway so `Test step` provides at least columns + const itemsInSchema: INodeExecutionData[] = inputData.map((row, index) => ({ + json: _.pickBy(row.json, (_v, key) => newKeys.has(key)), + index, + })); - return [getWorkflowInputData.call(this, inputData, newParams)]; + return [itemsInSchema]; } } } diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/GenericFunctions.ts b/packages/nodes-base/nodes/ExecuteWorkflow/GenericFunctions.ts index b36d4575d1696..cdb32976e21d9 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/GenericFunctions.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/GenericFunctions.ts @@ -7,7 +7,7 @@ import type { INodeExecutionData, IExecuteFunctions, } from 'n8n-workflow'; -import { jsonParse, NodeOperationError, validateFieldType } from 'n8n-workflow'; +import { jsonParse, NodeOperationError } from 'n8n-workflow'; import { JSON_EXAMPLE, @@ -15,7 +15,6 @@ import { WORKFLOW_INPUTS, VALUES, TYPE_OPTIONS, - INPUT_OPTIONS, FALLBACK_DEFAULT_VALUE, PASSTHROUGH, } from './constants'; @@ -98,18 +97,7 @@ export function getWorkflowInputData( ): 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, - ); - + for (const [itemIndex, row] of inputData.entries()) { // Fields listed here will explicitly overwrite original fields const newItem: INodeExecutionData = { json: {}, @@ -124,37 +112,11 @@ export function getWorkflowInputData( pairedItem: { item: itemIndex }, }; try { - for (const { name, type } of newParams) { - if (!item.json.hasOwnProperty(name)) { + for (const { name } of newParams) { + if (!row.json.hasOwnProperty(name)) { newItem.json[name] = FALLBACK_DEFAULT_VALUE; - 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]; - } + newItem.json[name] = row.json[name]; } } @@ -170,5 +132,5 @@ export function getWorkflowInputData( } } - return items; + return inputData; } diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/constants.ts b/packages/nodes-base/nodes/ExecuteWorkflow/constants.ts index 69bb41af7397f..409d8d703ecab 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/constants.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/constants.ts @@ -2,7 +2,6 @@ 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 PASSTHROUGH = 'passthrough'; diff --git a/packages/nodes-base/nodes/Set/v2/helpers/utils.ts b/packages/nodes-base/nodes/Set/v2/helpers/utils.ts index d6754914ced38..144c6bd50b89e 100644 --- a/packages/nodes-base/nodes/Set/v2/helpers/utils.ts +++ b/packages/nodes-base/nodes/Set/v2/helpers/utils.ts @@ -190,7 +190,7 @@ export const validateEntry = ( } else { throw new NodeOperationError( node, - `'${name}' expects a ${type} but we got ${getValueDescription(value)} [item ${itemIndex}]`, + `'${name}' expect s a ${type} but we got ${getValueDescription(value)} [item ${itemIndex}]`, { description }, ); } diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index b37f9f83ed655..2ae292d0e326d 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -1383,6 +1383,7 @@ export interface ResourceMapperTypeOptionsBase { description?: string; hint?: string; }; + showTypeConversionOptions?: boolean; } // Enforce at least one of resourceMapperMethod or localResourceMapperMethod @@ -2729,6 +2730,9 @@ export type ResourceMapperValue = { value: { [key: string]: string | number | boolean | null } | null; matchingColumns: string[]; schema: ResourceMapperField[]; + ignoreTypeMismatchErrors: boolean; + attemptToConvertTypes: boolean; + convertFieldsToString: boolean; }; export type FilterOperatorType = diff --git a/packages/workflow/src/TypeValidation.ts b/packages/workflow/src/TypeValidation.ts index 6b1557af8369c..6e0cf9d735f41 100644 --- a/packages/workflow/src/TypeValidation.ts +++ b/packages/workflow/src/TypeValidation.ts @@ -305,7 +305,7 @@ export function validateFieldType( const valueOptions = options.valueOptions ?? []; const parseStrings = options.parseStrings ?? false; - const defaultErrorMessage = `'${fieldName}' expects a ${type} but we got ${getValueDescription(value)}`; + const defaultErrorMessage = `'${fieldName}' expect s a ${type} but we got ${getValueDescription(value)}`; switch (type.toLowerCase()) { case 'string': { if (!parseStrings) return { valid: true, newValue: value }; From 7712477cc724876b203954211aea40b95d6faae1 Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Tue, 17 Dec 2024 12:36:20 +0100 Subject: [PATCH 02/13] clean up --- .../src/components/ParameterInputFull.vue | 1 - .../src/components/ParameterInputList.vue | 1 - .../ResourceMapper/ResourceMapper.vue | 4 +- .../ExecuteWorkflow/ExecuteWorkflow.node.ts | 3 +- .../ExecuteWorkflowTrigger.node.test.ts | 6 +-- .../nodes/ExecuteWorkflow/GenericFunctions.ts | 45 ------------------- .../nodes-base/nodes/Set/v2/helpers/utils.ts | 2 +- packages/workflow/src/TypeValidation.ts | 2 +- 8 files changed, 6 insertions(+), 58 deletions(-) diff --git a/packages/editor-ui/src/components/ParameterInputFull.vue b/packages/editor-ui/src/components/ParameterInputFull.vue index 9e1aeb2ba0c5c..332011b9ea743 100644 --- a/packages/editor-ui/src/components/ParameterInputFull.vue +++ b/packages/editor-ui/src/components/ParameterInputFull.vue @@ -102,7 +102,6 @@ function optionSelected(command: string) { } function valueChanged(parameterData: IUpdateInformation) { - console.log('valueChanged', parameterData); emit('update', parameterData); } diff --git a/packages/editor-ui/src/components/ParameterInputList.vue b/packages/editor-ui/src/components/ParameterInputList.vue index 30adef5d15758..91249fbafba45 100644 --- a/packages/editor-ui/src/components/ParameterInputList.vue +++ b/packages/editor-ui/src/components/ParameterInputList.vue @@ -423,7 +423,6 @@ function displayNodeParameter( } function valueChanged(parameterData: IUpdateInformation): void { - console.log('ParameterInputList valueChanged'); emit('valueChanged', parameterData); } diff --git a/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue b/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue index 6c1bd0da80931..e8baaff8d656b 100644 --- a/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue +++ b/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue @@ -603,9 +603,9 @@ defineExpose({ :parameter="{ name: 'ignoreTypeMismatchErrors', type: 'boolean', - displayName: 'To Do', + displayName: 'Ignore Type Mismatch Errors', default: false, - description: 'To Do', + description: 'Whether type mismatches should be ignored, rather than returning an Error', }" :path="props.path + '.attemptToConvertTypes'" :value="state.paramValue.ignoreTypeMismatchErrors" diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts index cedfe4d1b769c..2f8649a09a9e5 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts @@ -1,3 +1,4 @@ +import _ from 'lodash'; import { NodeConnectionType, NodeOperationError } from 'n8n-workflow'; import type { ExecuteWorkflowData, @@ -13,8 +14,6 @@ import type { import { getWorkflowInfo } from './GenericFunctions'; import { loadWorkflowInputMappings } from './methods/resourceMapping'; import { generatePairedItemData } from '../../../utils/utilities'; -import { getWorkflowInputData } from '../GenericFunctions'; -import _ from 'lodash'; import { FALLBACK_DEFAULT_VALUE } from '../constants'; function getWorkflowInputValues(this: IExecuteFunctions): INodeExecutionData[] { diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.test.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.test.ts index dfc30d3ea8900..ceace413b4f02 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.test.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.test.ts @@ -2,7 +2,7 @@ import { mock } from 'jest-mock-extended'; import type { FieldValueOption, IExecuteFunctions, INode, INodeExecutionData } from 'n8n-workflow'; import { ExecuteWorkflowTrigger } from './ExecuteWorkflowTrigger.node'; -import { getFieldEntries, getWorkflowInputData } from '../GenericFunctions'; +import { getFieldEntries } from '../GenericFunctions'; jest.mock('../GenericFunctions'); @@ -32,14 +32,10 @@ describe('ExecuteWorkflowTrigger', () => { { name: 'value2', type: 'number' }, ] as FieldValueOption[]; const getFieldEntriesMock = (getFieldEntries as jest.Mock).mockReturnValue(mockNewParams); - const getWorkflowInputDataMock = (getWorkflowInputData as jest.Mock).mockReturnValue( - mockInputData, - ); const result = await new ExecuteWorkflowTrigger().execute.call(executeFns); expect(result).toEqual([mockInputData]); expect(getFieldEntriesMock).toHaveBeenCalledWith(executeFns); - expect(getWorkflowInputDataMock).toHaveBeenCalledWith(mockInputData, mockNewParams); }); }); diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/GenericFunctions.ts b/packages/nodes-base/nodes/ExecuteWorkflow/GenericFunctions.ts index cdb32976e21d9..95bab9481e4a2 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/GenericFunctions.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/GenericFunctions.ts @@ -89,48 +89,3 @@ 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, row] of inputData.entries()) { - // 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 } of newParams) { - if (!row.json.hasOwnProperty(name)) { - newItem.json[name] = FALLBACK_DEFAULT_VALUE; - } else { - newItem.json[name] = row.json[name]; - } - } - - items.push(newItem); - } catch (error) { - if (this.continueOnFail()) { - /** todo error case? */ - } else { - throw new NodeOperationError(this.getNode(), error, { - itemIndex, - }); - } - } - } - - return inputData; -} diff --git a/packages/nodes-base/nodes/Set/v2/helpers/utils.ts b/packages/nodes-base/nodes/Set/v2/helpers/utils.ts index 144c6bd50b89e..d6754914ced38 100644 --- a/packages/nodes-base/nodes/Set/v2/helpers/utils.ts +++ b/packages/nodes-base/nodes/Set/v2/helpers/utils.ts @@ -190,7 +190,7 @@ export const validateEntry = ( } else { throw new NodeOperationError( node, - `'${name}' expect s a ${type} but we got ${getValueDescription(value)} [item ${itemIndex}]`, + `'${name}' expects a ${type} but we got ${getValueDescription(value)} [item ${itemIndex}]`, { description }, ); } diff --git a/packages/workflow/src/TypeValidation.ts b/packages/workflow/src/TypeValidation.ts index 6e0cf9d735f41..6b1557af8369c 100644 --- a/packages/workflow/src/TypeValidation.ts +++ b/packages/workflow/src/TypeValidation.ts @@ -305,7 +305,7 @@ export function validateFieldType( const valueOptions = options.valueOptions ?? []; const parseStrings = options.parseStrings ?? false; - const defaultErrorMessage = `'${fieldName}' expect s a ${type} but we got ${getValueDescription(value)}`; + const defaultErrorMessage = `'${fieldName}' expects a ${type} but we got ${getValueDescription(value)}`; switch (type.toLowerCase()) { case 'string': { if (!parseStrings) return { valid: true, newValue: value }; From 8403c250563d3f4374430340712f076166d64acf Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Tue, 17 Dec 2024 13:13:12 +0100 Subject: [PATCH 03/13] more clean up --- .../ResourceMapper/ResourceMapper.vue | 76 ++++++++++--------- .../ExecuteWorkflow/ExecuteWorkflow.node.ts | 4 +- .../ExecuteWorkflowTrigger.node.ts | 7 +- 3 files changed, 46 insertions(+), 41 deletions(-) diff --git a/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue b/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue index e8baaff8d656b..385d13208f791 100644 --- a/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue +++ b/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue @@ -54,7 +54,9 @@ const state = reactive({ schema: [] as ResourceMapperField[], ignoreTypeMismatchErrors: false, attemptToConvertTypes: false, - // This will always be true if `showTypeConversionOptions` is provided + // This should always be true if `showTypeConversionOptions` is provided + // It's used to avoid accepting any value as string without casting it + // Which is the legacy behavior without these type options. convertFieldsToString: false, } as ResourceMapperValue, parameterValues: {} as INodeParameters, @@ -580,41 +582,41 @@ defineExpose({ }) }} - - +
+ + +
diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts index 2f8649a09a9e5..61db8541838c2 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts @@ -56,12 +56,12 @@ function getCurrentWorkflowInputData(this: IExecuteFunctions) { const removedKeys = new Set(removed.map((x) => x.displayName)); const filteredInputData: INodeExecutionData[] = inputData.map((item, index) => ({ + index, + pairedItem: { item: index }, json: _.assign( - // itemIndex and the other thing _.pickBy(item.json, (_v, k) => !removedKeys.has(k)), _.fromPairs(addedParams.map((x) => [x, FALLBACK_DEFAULT_VALUE])), ), - index, })); return filteredInputData; } diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts index f7678c9674eac..bbf9665b3f061 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts @@ -14,6 +14,7 @@ import { VALUES, TYPE_OPTIONS, PASSTHROUGH, + FALLBACK_DEFAULT_VALUE, } from '../constants'; import { getFieldEntries } from '../GenericFunctions'; @@ -188,9 +189,11 @@ export class ExecuteWorkflowTrigger implements INodeType { const newParams = getFieldEntries(this); const newKeys = new Set(newParams.map(({ name }) => name)); - // todo: add keys in schema anyway so `Test step` provides at least columns const itemsInSchema: INodeExecutionData[] = inputData.map((row, index) => ({ - json: _.pickBy(row.json, (_v, key) => newKeys.has(key)), + json: _.assign( + _.fromPairs(newParams.map((x) => [x.name, FALLBACK_DEFAULT_VALUE])), + _.pickBy(row.json, (_v, key) => newKeys.has(key)), + ), index, })); From f027395848a49043419be9e34450848120340f69 Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Tue, 17 Dec 2024 14:42:22 +0100 Subject: [PATCH 04/13] simplify --- cypress/e2e/48-subworkflow-inputs.cy.ts | 19 +++++----- cypress/fixtures/Test_Subworkflow-Inputs.json | 38 +++++++++++++++++-- .../ExecuteWorkflow/ExecuteWorkflow.node.ts | 15 ++------ .../ExecuteWorkflowTrigger.node.ts | 21 +++++----- 4 files changed, 57 insertions(+), 36 deletions(-) diff --git a/cypress/e2e/48-subworkflow-inputs.cy.ts b/cypress/e2e/48-subworkflow-inputs.cy.ts index d3315f6cca724..e74d8528c667c 100644 --- a/cypress/e2e/48-subworkflow-inputs.cy.ts +++ b/cypress/e2e/48-subworkflow-inputs.cy.ts @@ -107,6 +107,7 @@ function validateAndReturnToParent(targetChild: string, offset: number, fields: // Due to our workaround to remain in the same tab we need to select the correct tab manually navigateWorkflowSelectionDropdown(offset, targetChild); + // This fails, pointing to `usePushConnection` `const triggerNode = subWorkflow?.nodes.find` being `undefined.find()`I ndv.actions.execute(); getOutputTableHeaders().should('have.length', fields.length + 1); @@ -149,7 +150,7 @@ describe('Sub-workflow creation', () => { openNode('Execute Workflow Trigger'); }); - it('works with Fields input source into JSON input source', () => { + it.only('works with Fields input source into JSON input source', () => { ndv.getters.nodeOutputHint().should('exist'); const fields = [ @@ -194,14 +195,14 @@ describe('Sub-workflow creation', () => { .type(`{selectAll}{backspace}${exampleJson}{enter}`); // first one doesn't work for some reason, might need to wait for something? - ndv.actions.execute(); - ndv.actions.execute(); - - validateAndReturnToParent( - DEFAULT_SUBWORKFLOW_NAME_2, - 2, - fields.map((f) => f[0]), - ); + // ndv.actions.execute(); + // ndv.actions.execute(); + + // validateAndReturnToParent( + // DEFAULT_SUBWORKFLOW_NAME_2, + // 2, + // fields.map((f) => f[0]), + // ); // populateJson(fields); diff --git a/cypress/fixtures/Test_Subworkflow-Inputs.json b/cypress/fixtures/Test_Subworkflow-Inputs.json index 1b2320510fdea..eb56bc34ec101 100644 --- a/cypress/fixtures/Test_Subworkflow-Inputs.json +++ b/cypress/fixtures/Test_Subworkflow-Inputs.json @@ -23,13 +23,43 @@ }, { "parameters": { + "workflowId": {}, + "workflowInputs": { + "mappingMode": "defineBelow", + "value": {}, + "matchingColumns": [], + "schema": [ + { + "id": "email", + "displayName": "email", + "required": false, + "defaultMatch": false, + "display": true, + "canBeUsedToMatch": true, + "type": "string", + "removed": true + }, + { + "id": "email2", + "displayName": "email2", + "required": false, + "defaultMatch": false, + "display": true, + "canBeUsedToMatch": true, + "type": "string" + } + ], + "ignoreTypeMismatchErrors": false, + "attemptToConvertTypes": false, + "convertFieldsToString": true + }, "options": {} }, - "id": "ddc82976-edd9-4488-a5a5-7f558a7d905b", - "name": "Execute Workflow", "type": "n8n-nodes-base.executeWorkflow", - "typeVersion": 1.1, - "position": [500, 240] + "typeVersion": 1.2, + "position": [1240, 460], + "id": "6b6e2e34-c6ab-4083-b8e3-6b0d56be5453", + "name": "Execute Workflow" } ], "connections": { diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts index 61db8541838c2..ace810f8eff73 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts @@ -2,7 +2,6 @@ import _ from 'lodash'; import { NodeConnectionType, NodeOperationError } from 'n8n-workflow'; import type { ExecuteWorkflowData, - FieldValueOption, IDataObject, IExecuteFunctions, INodeExecutionData, @@ -47,22 +46,14 @@ function getCurrentWorkflowInputData(this: IExecuteFunctions) { if (schema.length === 0) { return inputData; } else { - const [removed, added] = _.partition(schema, (x) => x.removed); + const removedKeys = new Set(schema.filter((x) => x.removed).map((x) => x.displayName)); - const addedParams: FieldValueOption[] = added.map((x) => ({ - name: x.displayName, - type: x.type ?? 'any', - })); - - const removedKeys = new Set(removed.map((x) => x.displayName)); const filteredInputData: INodeExecutionData[] = inputData.map((item, index) => ({ index, pairedItem: { item: index }, - json: _.assign( - _.pickBy(item.json, (_v, k) => !removedKeys.has(k)), - _.fromPairs(addedParams.map((x) => [x, FALLBACK_DEFAULT_VALUE])), - ), + json: _.pickBy(item.json, (_v, key) => !removedKeys.has(key)), })); + return filteredInputData; } } diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts index bbf9665b3f061..76aa9d3d3b18f 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts @@ -174,26 +174,25 @@ export class ExecuteWorkflowTrigger implements INodeType { // Note on the data we receive from ExecuteWorkflow caller: // - // The caller typechecks all fields explicitly provided by the user via the resourceMapper + // The ExecuteWorkflow node typechecks all fields explicitly provided by the user here via the resourceMapper // and removes all fields that are in the schema, but `removed` in the resourceMapper. // - // Then these fields - including removed fields - "shadow" inputData fields, hiding them from the trigger. - // - // In passthrough and legacy versions, inputData will line up since the resourceMapper is empty - // In other cases we will already have matching types, so we just need to be permissive on this end, - // while filtering out fields that are not in the schema. + // In passthrough and legacy node versions, inputData will line up since the resourceMapper is empty, + // in which case all input is passed through. + // In other cases we will already have matching types and fields provided by the resource mapper, + // so we just need to be permissive on this end, + // while ensuring we provide default values for fields in our schema, which are removed in the resourceMapper. if (inputSource === PASSTHROUGH) { return [inputData]; } else { const newParams = getFieldEntries(this); - const newKeys = new Set(newParams.map(({ name }) => name)); const itemsInSchema: INodeExecutionData[] = inputData.map((row, index) => ({ - json: _.assign( - _.fromPairs(newParams.map((x) => [x.name, FALLBACK_DEFAULT_VALUE])), - _.pickBy(row.json, (_v, key) => newKeys.has(key)), - ), + json: { + ...Object.fromEntries(newParams.map((x) => [x.name, FALLBACK_DEFAULT_VALUE])), + ...row.json, + }, index, })); From e2201d2ec69a481d498ffebf6b58aff805b7fb4a Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Tue, 17 Dec 2024 15:21:46 +0100 Subject: [PATCH 05/13] self review --- cypress/e2e/48-subworkflow-inputs.cy.ts | 18 +++++++------- cypress/fixtures/Test_Subworkflow-Inputs.json | 24 ++----------------- .../ResourceMapper/ResourceMapper.vue | 13 ++++++++-- .../ExecuteWorkflow/ExecuteWorkflow.node.ts | 24 ------------------- 4 files changed, 22 insertions(+), 57 deletions(-) diff --git a/cypress/e2e/48-subworkflow-inputs.cy.ts b/cypress/e2e/48-subworkflow-inputs.cy.ts index e74d8528c667c..02eff605f8494 100644 --- a/cypress/e2e/48-subworkflow-inputs.cy.ts +++ b/cypress/e2e/48-subworkflow-inputs.cy.ts @@ -150,7 +150,7 @@ describe('Sub-workflow creation', () => { openNode('Execute Workflow Trigger'); }); - it.only('works with Fields input source into JSON input source', () => { + it('works with Fields input source into JSON input source', () => { ndv.getters.nodeOutputHint().should('exist'); const fields = [ @@ -195,14 +195,14 @@ describe('Sub-workflow creation', () => { .type(`{selectAll}{backspace}${exampleJson}{enter}`); // first one doesn't work for some reason, might need to wait for something? - // ndv.actions.execute(); - // ndv.actions.execute(); - - // validateAndReturnToParent( - // DEFAULT_SUBWORKFLOW_NAME_2, - // 2, - // fields.map((f) => f[0]), - // ); + ndv.actions.execute(); + ndv.actions.execute(); + + validateAndReturnToParent( + DEFAULT_SUBWORKFLOW_NAME_2, + 2, + fields.map((f) => f[0]), + ); // populateJson(fields); diff --git a/cypress/fixtures/Test_Subworkflow-Inputs.json b/cypress/fixtures/Test_Subworkflow-Inputs.json index eb56bc34ec101..5b96c0e3f2c8e 100644 --- a/cypress/fixtures/Test_Subworkflow-Inputs.json +++ b/cypress/fixtures/Test_Subworkflow-Inputs.json @@ -28,27 +28,7 @@ "mappingMode": "defineBelow", "value": {}, "matchingColumns": [], - "schema": [ - { - "id": "email", - "displayName": "email", - "required": false, - "defaultMatch": false, - "display": true, - "canBeUsedToMatch": true, - "type": "string", - "removed": true - }, - { - "id": "email2", - "displayName": "email2", - "required": false, - "defaultMatch": false, - "display": true, - "canBeUsedToMatch": true, - "type": "string" - } - ], + "schema": [], "ignoreTypeMismatchErrors": false, "attemptToConvertTypes": false, "convertFieldsToString": true @@ -57,7 +37,7 @@ }, "type": "n8n-nodes-base.executeWorkflow", "typeVersion": 1.2, - "position": [1240, 460], + "position": [500, 240], "id": "6b6e2e34-c6ab-4083-b8e3-6b0d56be5453", "name": "Execute Workflow" } diff --git a/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue b/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue index 385d13208f791..e0da855159424 100644 --- a/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue +++ b/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue @@ -582,7 +582,10 @@ defineExpose({ }) }} -
+
+.typeConversionOptions { + margin-top: var(--spacing-m); +} + diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts index ace810f8eff73..10579457d6204 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts @@ -315,30 +315,6 @@ export class ExecuteWorkflow implements INodeType { description: 'Whether the main workflow should wait for the sub-workflow to complete its execution before proceeding', }, - // Note that, while the defaults are true, the user has to add these in the first place - // We default to false if absent in the execute function - { - displayName: 'Attempt to Convert Types', - name: 'attemptToConvertTypes', - type: 'boolean', - default: true, - description: - 'Whether to attempt conversion on type mismatch, rather than directly returning an Error', - displayOptions: { - show: { '@version': [{ _cnd: { gte: 1.2 } }] }, - }, - }, - { - displayName: 'Ignore Type Mismatch Errors', - name: 'ignoreTypeErrors', - type: 'boolean', - default: true, - description: - 'Whether type mismatches should be ignored, rather than returning an Error', - displayOptions: { - show: { '@version': [{ _cnd: { gte: 1.2 } }] }, - }, - }, ], }, ], From 311bbefab8100218acca5979ce2a4f2ce0925bec Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Tue, 17 Dec 2024 15:35:09 +0100 Subject: [PATCH 06/13] fix build --- .../ExecuteWorkflow/ExecuteWorkflow.node.ts | 1 - .../nodes-base/nodes/ExecuteWorkflow/GenericFunctions.ts | 9 +-------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts index 10579457d6204..8a30890440f45 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts @@ -13,7 +13,6 @@ import type { import { getWorkflowInfo } from './GenericFunctions'; import { loadWorkflowInputMappings } from './methods/resourceMapping'; import { generatePairedItemData } from '../../../utils/utilities'; -import { FALLBACK_DEFAULT_VALUE } from '../constants'; function getWorkflowInputValues(this: IExecuteFunctions): INodeExecutionData[] { const inputData = this.getInputData(); diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/GenericFunctions.ts b/packages/nodes-base/nodes/ExecuteWorkflow/GenericFunctions.ts index 95bab9481e4a2..147984d84c267 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/GenericFunctions.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/GenericFunctions.ts @@ -1,12 +1,6 @@ import { json as generateSchemaFromExample, type SchemaObject } from 'generate-schema'; import type { JSONSchema7 } from 'json-schema'; -import type { - FieldValueOption, - FieldType, - IWorkflowNodeContext, - INodeExecutionData, - IExecuteFunctions, -} from 'n8n-workflow'; +import type { FieldValueOption, FieldType, IWorkflowNodeContext } from 'n8n-workflow'; import { jsonParse, NodeOperationError } from 'n8n-workflow'; import { @@ -15,7 +9,6 @@ import { WORKFLOW_INPUTS, VALUES, TYPE_OPTIONS, - FALLBACK_DEFAULT_VALUE, PASSTHROUGH, } from './constants'; From 15d15adc0e1fe4a086fbbba4a48d5132dbf0c2c1 Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Tue, 17 Dec 2024 15:36:27 +0100 Subject: [PATCH 07/13] fix build 2 --- .../ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts index 76aa9d3d3b18f..ab6f5ea71b1c0 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts @@ -1,4 +1,3 @@ -import _ from 'lodash'; import { type INodeExecutionData, NodeConnectionType, From 952f829dae05df657b07da0ddc492d8e86aa653c Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Tue, 17 Dec 2024 15:51:31 +0100 Subject: [PATCH 08/13] type check 3 --- .../src/components/ResourceMapper/ResourceMapper.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue b/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue index e0da855159424..d57055e0c34c4 100644 --- a/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue +++ b/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue @@ -7,6 +7,7 @@ import type { INodeParameters, INodeProperties, INodeTypeDescription, + NodeParameterValueType, ResourceMapperField, ResourceMapperValue, } from 'n8n-workflow'; @@ -597,7 +598,7 @@ defineExpose({ :path="props.path + '.attemptToConvertTypes'" :value="state.paramValue.attemptToConvertTypes" @update=" - (x) => { + (x: IUpdateInformation) => { state.paramValue.attemptToConvertTypes = x.value as boolean; emitValueChanged(); } @@ -614,7 +615,7 @@ defineExpose({ :path="props.path + '.ignoreTypeMismatchErrors'" :value="state.paramValue.ignoreTypeMismatchErrors" @update=" - (x) => { + (x: IUpdateInformation) => { state.paramValue.ignoreTypeMismatchErrors = x.value as boolean; emitValueChanged(); } From 3a7e6a73302f89b4b917775d5a65dc65346ca9b9 Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Wed, 18 Dec 2024 09:34:01 +0100 Subject: [PATCH 09/13] fix accidental passthrough --- .../ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts index ab6f5ea71b1c0..16ccd0cff0ed3 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts @@ -1,3 +1,4 @@ +import _ from 'lodash'; import { type INodeExecutionData, NodeConnectionType, @@ -186,11 +187,13 @@ export class ExecuteWorkflowTrigger implements INodeType { return [inputData]; } else { const newParams = getFieldEntries(this); - + const newKeys = new Set(newParams.map((x) => x.name)); const itemsInSchema: INodeExecutionData[] = inputData.map((row, index) => ({ json: { ...Object.fromEntries(newParams.map((x) => [x.name, FALLBACK_DEFAULT_VALUE])), - ...row.json, + // Need to trim to the expected schema to support legacy Execute Workflow callers passing through all their data + // which we do not want to expose past this node. + ..._.pickBy(row.json, (_value, key) => newKeys.has(key)), }, index, })); From 1216035b9a6b83e178771d476805f48052e77987 Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Wed, 18 Dec 2024 10:25:44 +0100 Subject: [PATCH 10/13] readd v1 trigger notice and add/fix out of date notices --- .../ExecuteWorkflow/ExecuteWorkflow.node.ts | 3 +-- .../ExecuteWorkflowTrigger.node.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts index 8a30890440f45..8405dec26aa81 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts @@ -88,10 +88,9 @@ export class ExecuteWorkflow implements INodeType { ], }, { - displayName: 'Outdated Version Warning', + displayName: 'This node is out of date. Please upgrade by removing it and adding a new one', name: 'outdatedVersionWarning', type: 'notice', - description: 'Please update this node by removing it and adding a new one', displayOptions: { show: { '@version': [{ _cnd: { lte: 1.1 } }] } }, default: '', }, diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts index 16ccd0cff0ed3..3cabae2a165cc 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.ts @@ -65,6 +65,23 @@ export class ExecuteWorkflowTrigger implements INodeType { ], default: 'worklfow_call', }, + { + displayName: + "When an ‘execute workflow’ node calls this workflow, the execution starts here. Any data passed into the 'execute workflow' node will be output by this node.", + name: 'notice', + type: 'notice', + default: '', + displayOptions: { + show: { '@version': [{ _cnd: { eq: 1 } }] }, + }, + }, + { + displayName: 'This node is out of date. Please upgrade by removing it and adding a new one', + name: 'outdatedVersionWarning', + type: 'notice', + displayOptions: { show: { '@version': [{ _cnd: { eq: 1 } }] } }, + default: '', + }, { displayName: 'Input Source', name: INPUT_SOURCE, From aa41de4cc541b656c72a2d7382cba8ced8ec1404 Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Wed, 18 Dec 2024 14:26:02 +0100 Subject: [PATCH 11/13] PR Feedback --- .../src/components/ResourceMapper/ResourceMapper.vue | 12 +++++++----- packages/editor-ui/src/plugins/i18n/locales/en.json | 4 ++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue b/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue index d57055e0c34c4..654a72064dbf5 100644 --- a/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue +++ b/packages/editor-ui/src/components/ResourceMapper/ResourceMapper.vue @@ -591,9 +591,9 @@ defineExpose({ :parameter="{ name: 'attemptToConvertTypes', type: 'boolean', - displayName: 'Attempt to convert types', + displayName: locale.baseText('resourceMapper.attemptToConvertTypes.displayName'), default: false, - description: 'Attempt to convert types when mapping fields', + description: locale.baseText('resourceMapper.attemptToConvertTypes.description'), }" :path="props.path + '.attemptToConvertTypes'" :value="state.paramValue.attemptToConvertTypes" @@ -608,9 +608,9 @@ defineExpose({ :parameter="{ name: 'ignoreTypeMismatchErrors', type: 'boolean', - displayName: 'Ignore Type Mismatch Errors', + displayName: locale.baseText('resourceMapper.ignoreTypeMismatchErrors.displayName'), default: false, - description: 'Whether type mismatches should be ignored, rather than returning an Error', + description: locale.baseText('resourceMapper.ignoreTypeMismatchErrors.description'), }" :path="props.path + '.ignoreTypeMismatchErrors'" :value="state.paramValue.ignoreTypeMismatchErrors" @@ -627,6 +627,8 @@ defineExpose({ diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index a5c9361e9faa6..790b2e8487b9a 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -1581,6 +1581,10 @@ "resourceMapper.addAllFields": "Add All {fieldWord}", "resourceMapper.removeAllFields": "Remove All {fieldWord}", "resourceMapper.refreshFieldList": "Refresh {fieldWord} List", + "resourceMapper.attemptToConvertTypes.displayName": "Attempt to convert types", + "resourceMapper.attemptToConvertTypes.description": "Attempt to convert types when mapping fields", + "resourceMapper.ignoreTypeMismatchErrors.displayName": "Ignore type mismatch errors", + "resourceMapper.ignoreTypeMismatchErrors.description": "Whether type mismatches should be ignored, rather than returning an Error", "runData.openSubExecution": "Inspect Sub-Execution {id}", "runData.openParentExecution": "Inspect Parent Execution {id}", "runData.emptyItemHint": "This is an item, but it's empty.", From c70aae9b54d94ce0ecbbd76056c5aa91e833a63c Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Thu, 19 Dec 2024 09:07:45 +0100 Subject: [PATCH 12/13] Update test --- .../ExecuteWorkflowTrigger.node.test.ts | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.test.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.test.ts index ceace413b4f02..9403053fd8b44 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.test.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflowTrigger/ExecuteWorkflowTrigger.node.test.ts @@ -3,13 +3,15 @@ import type { FieldValueOption, IExecuteFunctions, INode, INodeExecutionData } f import { ExecuteWorkflowTrigger } from './ExecuteWorkflowTrigger.node'; import { getFieldEntries } from '../GenericFunctions'; +import { WORKFLOW_INPUTS } from '../constants'; +import { json } from 'generate-schema'; jest.mock('../GenericFunctions'); describe('ExecuteWorkflowTrigger', () => { const mockInputData: INodeExecutionData[] = [ - { json: { item: 0, foo: 'bar' } }, - { json: { item: 1, foo: 'quz' } }, + { json: { item: 0, foo: 'bar' }, index: 0 }, + { json: { item: 1, foo: 'quz' }, index: 1 }, ]; const mockNode = mock({ typeVersion: 1 }); const executeFns = mock({ @@ -18,24 +20,32 @@ describe('ExecuteWorkflowTrigger', () => { getNodeParameter: jest.fn(), }); - it('should return its input data on V1', async () => { + it('should return its input data on V1 or V1.1 passthrough', async () => { + // User selection in V1.1, or fallback return value in V1 with dropdown not displayed executeFns.getNodeParameter.mockReturnValueOnce('passthrough'); const result = await new ExecuteWorkflowTrigger().execute.call(executeFns); expect(result).toEqual([mockInputData]); }); - it('should return transformed input data based on newParams when input source is not passthrough', async () => { - executeFns.getNodeParameter.mockReturnValueOnce('usingFieldsBelow'); + it('should filter out parent input in `Using Fields below` mode', async () => { + executeFns.getNodeParameter.mockReturnValueOnce(WORKFLOW_INPUTS); const mockNewParams = [ { name: 'value1', type: 'string' }, { name: 'value2', type: 'number' }, + { name: 'foo', type: 'string' }, ] as FieldValueOption[]; const getFieldEntriesMock = (getFieldEntries as jest.Mock).mockReturnValue(mockNewParams); const result = await new ExecuteWorkflowTrigger().execute.call(executeFns); - - expect(result).toEqual([mockInputData]); + const expected = [ + [ + { index: 0, json: { value1: null, value2: null, foo: mockInputData[0].json.foo } }, + { index: 1, json: { value1: null, value2: null, foo: mockInputData[1].json.foo } }, + ], + ]; + + expect(result).toEqual(expected); expect(getFieldEntriesMock).toHaveBeenCalledWith(executeFns); }); }); From 99fd0e19ab3711b802233d139531754662aa9aec Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Thu, 19 Dec 2024 11:43:43 +0100 Subject: [PATCH 13/13] merge part two --- .../ToolWorkflow/v2/ToolWorkflowV2.node.ts | 2 +- .../v2/methods/resourceMapping.ts | 79 ------------ .../v2/utils/WorkflowToolService.ts | 2 +- .../ExecuteWorkflow/ExecuteWorkflow.node.ts | 52 +------- .../methods/resourceMapping.ts | 37 ------ .../workflowInputsResourceMapping/.readme | 2 +- .../GenericFunctions.ts | 80 ++++++++++++ pnpm-lock.yaml | 120 ++++-------------- 8 files changed, 112 insertions(+), 262 deletions(-) delete mode 100644 packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/methods/resourceMapping.ts delete mode 100644 packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/methods/resourceMapping.ts diff --git a/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/ToolWorkflowV2.node.ts b/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/ToolWorkflowV2.node.ts index 5ab1604262bc4..22ca31e4da2b1 100644 --- a/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/ToolWorkflowV2.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/ToolWorkflowV2.node.ts @@ -1,3 +1,4 @@ +import { loadWorkflowInputMappings } from 'n8n-nodes-base/dist/utils/workflowInputsResourceMapping/GenericFunctions'; import type { INodeTypeBaseDescription, ISupplyDataFunctions, @@ -6,7 +7,6 @@ import type { INodeTypeDescription, } from 'n8n-workflow'; -import { loadWorkflowInputMappings } from './methods/resourceMapping'; import { WorkflowToolService } from './utils/WorkflowToolService'; import { versionDescription } from './versionDescription'; diff --git a/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/methods/resourceMapping.ts b/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/methods/resourceMapping.ts deleted file mode 100644 index e0c0b0d6650d2..0000000000000 --- a/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/methods/resourceMapping.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { - getFieldEntries, - getWorkflowInputData, -} from 'n8n-nodes-base/dist/utils/workflowInputsResourceMapping/GenericFunctions'; -import type { - ISupplyDataFunctions, - IDataObject, - FieldValueOption, - ResourceMapperField, - ILocalLoadOptionsFunctions, - ResourceMapperFields, -} from 'n8n-workflow'; -import { EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE } from 'n8n-workflow'; - -export async function loadWorkflowInputMappings( - this: ILocalLoadOptionsFunctions, -): Promise { - const nodeLoadContext = await this.getWorkflowNodeContext(EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE); - let fields: ResourceMapperField[] = []; - if (nodeLoadContext) { - const fieldValues = getFieldEntries(nodeLoadContext); - fields = fieldValues.map((currentWorkflowInput) => { - const field: ResourceMapperField = { - id: currentWorkflowInput.name, - displayName: currentWorkflowInput.name, - required: false, - defaultMatch: false, - display: true, - canBeUsedToMatch: true, - }; - - if (currentWorkflowInput.type !== 'any') { - field.type = currentWorkflowInput.type; - } - - return field; - }); - } - return { fields }; -} - -export function getWorkflowInputValues(this: ISupplyDataFunctions) { - const inputData = this.getInputData(); - - return inputData.map((item, itemIndex) => { - const itemFieldValues = this.getNodeParameter( - 'workflowInputs.value', - itemIndex, - {}, - ) as IDataObject; - - return { - json: { - ...item.json, - ...itemFieldValues, - }, - index: itemIndex, - pairedItem: { - item: itemIndex, - }, - }; - }); -} - -export function getCurrentWorkflowInputData(this: ISupplyDataFunctions) { - const inputData = getWorkflowInputValues.call(this); - - const schema = this.getNodeParameter('workflowInputs.schema', 0, []) as ResourceMapperField[]; - - if (schema.length === 0) { - return inputData; - } else { - const newParams = schema - .filter((x) => !x.removed) - .map((x) => ({ name: x.displayName, type: x.type ?? 'any' })) as FieldValueOption[]; - - return getWorkflowInputData.call(this, inputData, newParams); - } -} diff --git a/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/utils/WorkflowToolService.ts b/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/utils/WorkflowToolService.ts index 7ea2bf0284174..5412f17ccd46c 100644 --- a/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/utils/WorkflowToolService.ts +++ b/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/utils/WorkflowToolService.ts @@ -4,6 +4,7 @@ import get from 'lodash/get'; import isObject from 'lodash/isObject'; import type { SetField, SetNodeOptions } from 'n8n-nodes-base/dist/nodes/Set/v2/helpers/interfaces'; import * as manual from 'n8n-nodes-base/dist/nodes/Set/v2/manual.mode'; +import { getCurrentWorkflowInputData } from 'n8n-nodes-base/dist/utils/workflowInputsResourceMapping/GenericFunctions'; import type { ExecuteWorkflowData, ExecutionError, @@ -22,7 +23,6 @@ import { z } from 'zod'; import type { FromAIArgument } from './FromAIParser'; import { AIParametersParser } from './FromAIParser'; -import { getCurrentWorkflowInputData } from '../methods/resourceMapping'; /** Main class for creating the Workflow tool diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts index 8405dec26aa81..2f67d7bfe9b6f 100644 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts +++ b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/ExecuteWorkflow.node.ts @@ -1,62 +1,18 @@ -import _ from 'lodash'; import { NodeConnectionType, NodeOperationError } from 'n8n-workflow'; import type { ExecuteWorkflowData, - IDataObject, IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription, - ResourceMapperField, } from 'n8n-workflow'; import { getWorkflowInfo } from './GenericFunctions'; -import { loadWorkflowInputMappings } from './methods/resourceMapping'; import { generatePairedItemData } from '../../../utils/utilities'; - -function getWorkflowInputValues(this: IExecuteFunctions): INodeExecutionData[] { - const inputData = this.getInputData(); - - return inputData.map((item, itemIndex) => { - const itemFieldValues = this.getNodeParameter( - 'workflowInputs.value', - itemIndex, - {}, - ) as IDataObject; - - return { - json: { - ...item.json, - ...itemFieldValues, - }, - index: itemIndex, - pairedItem: { - item: itemIndex, - }, - }; - }); -} - -function getCurrentWorkflowInputData(this: IExecuteFunctions) { - const inputData: INodeExecutionData[] = getWorkflowInputValues.call(this); - - const schema = this.getNodeParameter('workflowInputs.schema', 0, []) as ResourceMapperField[]; - - if (schema.length === 0) { - return inputData; - } else { - const removedKeys = new Set(schema.filter((x) => x.removed).map((x) => x.displayName)); - - const filteredInputData: INodeExecutionData[] = inputData.map((item, index) => ({ - index, - pairedItem: { item: index }, - json: _.pickBy(item.json, (_v, key) => !removedKeys.has(key)), - })); - - return filteredInputData; - } -} - +import { + getCurrentWorkflowInputData, + loadWorkflowInputMappings, +} from '../../../utils/workflowInputsResourceMapping/GenericFunctions'; export class ExecuteWorkflow implements INodeType { description: INodeTypeDescription = { displayName: 'Execute Workflow', diff --git a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/methods/resourceMapping.ts b/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/methods/resourceMapping.ts deleted file mode 100644 index c747f6f505b9d..0000000000000 --- a/packages/nodes-base/nodes/ExecuteWorkflow/ExecuteWorkflow/methods/resourceMapping.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE, - type ILocalLoadOptionsFunctions, - type ResourceMapperField, - type ResourceMapperFields, -} from 'n8n-workflow'; - -import { getFieldEntries } from '../../../../utils/workflowInputsResourceMapping/GenericFunctions'; - -export async function loadWorkflowInputMappings( - this: ILocalLoadOptionsFunctions, -): Promise { - const nodeLoadContext = await this.getWorkflowNodeContext(EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE); - let fields: ResourceMapperField[] = []; - - if (nodeLoadContext) { - const fieldValues = getFieldEntries(nodeLoadContext); - - fields = fieldValues.map((currentWorkflowInput) => { - const field: ResourceMapperField = { - id: currentWorkflowInput.name, - displayName: currentWorkflowInput.name, - required: false, - defaultMatch: false, - display: true, - canBeUsedToMatch: true, - }; - - if (currentWorkflowInput.type !== 'any') { - field.type = currentWorkflowInput.type; - } - - return field; - }); - } - return { fields }; -} diff --git a/packages/nodes-base/utils/workflowInputsResourceMapping/.readme b/packages/nodes-base/utils/workflowInputsResourceMapping/.readme index d92560581d2fe..e5556cc0cccaf 100644 --- a/packages/nodes-base/utils/workflowInputsResourceMapping/.readme +++ b/packages/nodes-base/utils/workflowInputsResourceMapping/.readme @@ -2,4 +2,4 @@ These files contain reusable logic for workflow inputs mapping used in these nod - n8n-nodes-base.executeWorkflow - n8n-nodes-base.executeWorkflowTrigger - - @n8n/n8n-nodes-langchain.toolWorkflow" + - @n8n/n8n-nodes-langchain.toolWorkflow diff --git a/packages/nodes-base/utils/workflowInputsResourceMapping/GenericFunctions.ts b/packages/nodes-base/utils/workflowInputsResourceMapping/GenericFunctions.ts index acd35e11c643f..8708b8e18aa8a 100644 --- a/packages/nodes-base/utils/workflowInputsResourceMapping/GenericFunctions.ts +++ b/packages/nodes-base/utils/workflowInputsResourceMapping/GenericFunctions.ts @@ -1,11 +1,19 @@ import { json as generateSchemaFromExample, type SchemaObject } from 'generate-schema'; import type { JSONSchema7 } from 'json-schema'; +import _ from 'lodash'; import { type FieldValueOption, type FieldType, type IWorkflowNodeContext, jsonParse, NodeOperationError, + type INodeExecutionData, + type IDataObject, + type ResourceMapperField, + type ILocalLoadOptionsFunctions, + type ResourceMapperFields, + EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE, + type ISupplyDataFunctions, } from 'n8n-workflow'; import { @@ -87,3 +95,75 @@ export function getFieldEntries(context: IWorkflowNodeContext): FieldValueOption } throw new NodeOperationError(context.getNode(), result); } + +export function getWorkflowInputValues(this: ISupplyDataFunctions): INodeExecutionData[] { + const inputData = this.getInputData(); + + return inputData.map((item, itemIndex) => { + const itemFieldValues = this.getNodeParameter( + 'workflowInputs.value', + itemIndex, + {}, + ) as IDataObject; + + return { + json: { + ...item.json, + ...itemFieldValues, + }, + index: itemIndex, + pairedItem: { + item: itemIndex, + }, + }; + }); +} + +export function getCurrentWorkflowInputData(this: ISupplyDataFunctions) { + const inputData: INodeExecutionData[] = getWorkflowInputValues.call(this); + + const schema = this.getNodeParameter('workflowInputs.schema', 0, []) as ResourceMapperField[]; + + if (schema.length === 0) { + return inputData; + } else { + const removedKeys = new Set(schema.filter((x) => x.removed).map((x) => x.displayName)); + + const filteredInputData: INodeExecutionData[] = inputData.map((item, index) => ({ + index, + pairedItem: { item: index }, + json: _.pickBy(item.json, (_v, key) => !removedKeys.has(key)), + })); + + return filteredInputData; + } +} + +export async function loadWorkflowInputMappings( + this: ILocalLoadOptionsFunctions, +): Promise { + const nodeLoadContext = await this.getWorkflowNodeContext(EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE); + let fields: ResourceMapperField[] = []; + + if (nodeLoadContext) { + const fieldValues = getFieldEntries(nodeLoadContext); + + fields = fieldValues.map((currentWorkflowInput) => { + const field: ResourceMapperField = { + id: currentWorkflowInput.name, + displayName: currentWorkflowInput.name, + required: false, + defaultMatch: false, + display: true, + canBeUsedToMatch: true, + }; + + if (currentWorkflowInput.type !== 'any') { + field.type = currentWorkflowInput.type; + } + + return field; + }); + } + return { fields }; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba2200020b469..955d19b18e3ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -283,7 +283,7 @@ importers: version: 4.0.7 axios: specifier: 'catalog:' - version: 1.7.4(debug@4.3.7) + version: 1.7.4 dotenv: specifier: 8.6.0 version: 8.6.0 @@ -354,7 +354,7 @@ importers: dependencies: axios: specifier: 'catalog:' - version: 1.7.4(debug@4.3.7) + version: 1.7.4 packages/@n8n/codemirror-lang: dependencies: @@ -428,7 +428,7 @@ importers: version: 3.666.0(@aws-sdk/client-sts@3.666.0) '@getzep/zep-cloud': specifier: 1.0.12 - version: 1.0.12(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i)) + version: 1.0.12(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.6(e4rnrwhosnp2xiru36mqgdy2bu)) '@getzep/zep-js': specifier: 0.9.0 version: 0.9.0 @@ -455,7 +455,7 @@ importers: version: 0.3.1(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) '@langchain/community': specifier: 0.3.15 - version: 0.3.15(v4qhcw25bevfr6xzz4fnsvjiqe) + version: 0.3.15(vc5hvyy27o4cmm4jplsptc2fqm) '@langchain/core': specifier: 'catalog:' version: 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)) @@ -542,7 +542,7 @@ importers: version: 23.0.1 langchain: specifier: 0.3.6 - version: 0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i) + version: 0.3.6(e4rnrwhosnp2xiru36mqgdy2bu) lodash: specifier: 'catalog:' version: 4.17.21 @@ -801,7 +801,7 @@ importers: version: 1.11.0 axios: specifier: 'catalog:' - version: 1.7.4(debug@4.3.7) + version: 1.7.4 bcryptjs: specifier: 2.4.3 version: 2.4.3 @@ -1120,7 +1120,7 @@ importers: dependencies: '@langchain/core': specifier: 'catalog:' - version: 0.3.19(openai@4.73.1(zod@3.23.8)) + version: 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)) '@n8n/client-oauth2': specifier: workspace:* version: link:../@n8n/client-oauth2 @@ -1135,7 +1135,7 @@ importers: version: 1.11.0 axios: specifier: 'catalog:' - version: 1.7.4(debug@4.3.7) + version: 1.7.4 chardet: specifier: 2.0.0 version: 2.0.0 @@ -1431,7 +1431,7 @@ importers: version: 10.11.0(vue@3.5.13(typescript@5.7.2)) axios: specifier: 'catalog:' - version: 1.7.4(debug@4.3.7) + version: 1.7.4 bowser: specifier: 2.11.0 version: 2.11.0 @@ -1932,7 +1932,7 @@ importers: version: 0.15.2 axios: specifier: 'catalog:' - version: 1.7.4(debug@4.3.7) + version: 1.7.4 callsites: specifier: 3.1.0 version: 3.1.0 @@ -1978,7 +1978,7 @@ importers: devDependencies: '@langchain/core': specifier: 'catalog:' - version: 0.3.19(openai@4.73.1) + version: 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)) '@types/deep-equal': specifier: ^1.0.1 version: 1.0.1 @@ -15666,7 +15666,7 @@ snapshots: '@gar/promisify@1.1.3': optional: true - '@getzep/zep-cloud@1.0.12(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i))': + '@getzep/zep-cloud@1.0.12(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.6(e4rnrwhosnp2xiru36mqgdy2bu))': dependencies: form-data: 4.0.0 node-fetch: 2.7.0(encoding@0.1.13) @@ -15675,7 +15675,7 @@ snapshots: zod: 3.23.8 optionalDependencies: '@langchain/core': 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)) - langchain: 0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i) + langchain: 0.3.6(e4rnrwhosnp2xiru36mqgdy2bu) transitivePeerDependencies: - encoding @@ -16139,7 +16139,7 @@ snapshots: - aws-crt - encoding - '@langchain/community@0.3.15(v4qhcw25bevfr6xzz4fnsvjiqe)': + '@langchain/community@0.3.15(vc5hvyy27o4cmm4jplsptc2fqm)': dependencies: '@ibm-cloud/watsonx-ai': 1.1.2 '@langchain/core': 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)) @@ -16149,7 +16149,7 @@ snapshots: flat: 5.0.2 ibm-cloud-sdk-core: 5.1.0 js-yaml: 4.1.0 - langchain: 0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i) + langchain: 0.3.6(e4rnrwhosnp2xiru36mqgdy2bu) langsmith: 0.2.3(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)) uuid: 10.0.0 zod: 3.23.8 @@ -16162,7 +16162,7 @@ snapshots: '@aws-sdk/client-s3': 3.666.0 '@aws-sdk/credential-provider-node': 3.666.0(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@aws-sdk/client-sts@3.666.0) '@azure/storage-blob': 12.18.0(encoding@0.1.13) - '@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i)) + '@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.6(e4rnrwhosnp2xiru36mqgdy2bu)) '@getzep/zep-js': 0.9.0 '@google-ai/generativelanguage': 2.6.0(encoding@0.1.13) '@google-cloud/storage': 7.12.1(encoding@0.1.13) @@ -16226,38 +16226,6 @@ snapshots: transitivePeerDependencies: - openai - '@langchain/core@0.3.19(openai@4.73.1(zod@3.23.8))': - dependencies: - ansi-styles: 5.2.0 - camelcase: 6.3.0 - decamelize: 1.2.0 - js-tiktoken: 1.0.12 - langsmith: 0.2.3(openai@4.73.1(zod@3.23.8)) - mustache: 4.2.0 - p-queue: 6.6.2 - p-retry: 4.6.2 - uuid: 10.0.0 - zod: 3.23.8 - zod-to-json-schema: 3.23.3(zod@3.23.8) - transitivePeerDependencies: - - openai - - '@langchain/core@0.3.19(openai@4.73.1)': - dependencies: - ansi-styles: 5.2.0 - camelcase: 6.3.0 - decamelize: 1.2.0 - js-tiktoken: 1.0.12 - langsmith: 0.2.3(openai@4.73.1) - mustache: 4.2.0 - p-queue: 6.6.2 - p-retry: 4.6.2 - uuid: 10.0.0 - zod: 3.23.8 - zod-to-json-schema: 3.23.3(zod@3.23.8) - transitivePeerDependencies: - - openai - '@langchain/google-common@0.1.3(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(zod@3.23.8)': dependencies: '@langchain/core': 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)) @@ -17173,7 +17141,7 @@ snapshots: '@rudderstack/rudder-sdk-node@2.0.9(tslib@2.6.2)': dependencies: - axios: 1.7.4(debug@4.3.7) + axios: 1.7.4 axios-retry: 3.7.0 component-type: 1.2.1 join-component: 1.1.0 @@ -19470,7 +19438,7 @@ snapshots: '@babel/runtime': 7.24.7 is-retry-allowed: 2.2.0 - axios@1.7.4(debug@4.3.7): + axios@1.7.4: dependencies: follow-redirects: 1.15.6(debug@4.3.6) form-data: 4.0.0 @@ -19478,9 +19446,9 @@ snapshots: transitivePeerDependencies: - debug - axios@1.7.7: + axios@1.7.4(debug@4.3.7): dependencies: - follow-redirects: 1.15.6(debug@4.3.6) + follow-redirects: 1.15.6(debug@4.3.7) form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -22347,7 +22315,7 @@ snapshots: isstream: 0.1.2 jsonwebtoken: 9.0.2 mime-types: 2.1.35 - retry-axios: 2.6.0(axios@1.7.4(debug@4.3.7)) + retry-axios: 2.6.0(axios@1.7.4) tough-cookie: 4.1.3 transitivePeerDependencies: - supports-color @@ -23346,7 +23314,7 @@ snapshots: kuler@2.0.0: {} - langchain@0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i): + langchain@0.3.6(e4rnrwhosnp2xiru36mqgdy2bu): dependencies: '@langchain/core': 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)) '@langchain/openai': 0.3.14(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) @@ -23370,7 +23338,7 @@ snapshots: '@langchain/groq': 0.1.2(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) '@langchain/mistralai': 0.2.0(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))) '@langchain/ollama': 0.1.2(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))) - axios: 1.7.4(debug@4.3.7) + axios: 1.7.4 cheerio: 1.0.0 handlebars: 4.7.8 transitivePeerDependencies: @@ -23389,28 +23357,6 @@ snapshots: optionalDependencies: openai: 4.73.1(encoding@0.1.13)(zod@3.23.8) - langsmith@0.2.3(openai@4.73.1(zod@3.23.8)): - dependencies: - '@types/uuid': 10.0.0 - commander: 10.0.1 - p-queue: 6.6.2 - p-retry: 4.6.2 - semver: 7.6.0 - uuid: 10.0.0 - optionalDependencies: - openai: 4.73.1(zod@3.23.8) - - langsmith@0.2.3(openai@4.73.1): - dependencies: - '@types/uuid': 10.0.0 - commander: 10.0.1 - p-queue: 6.6.2 - p-retry: 4.6.2 - semver: 7.6.0 - uuid: 10.0.0 - optionalDependencies: - openai: 4.73.1(zod@3.23.8) - lazy-ass@1.6.0: {} ldapts@4.2.6: @@ -24743,22 +24689,6 @@ snapshots: - encoding - supports-color - openai@4.73.1(zod@3.23.8): - dependencies: - '@types/node': 18.16.16 - '@types/node-fetch': 2.6.4 - abort-controller: 3.0.0 - agentkeepalive: 4.2.1 - form-data-encoder: 1.7.2 - formdata-node: 4.4.1 - node-fetch: 2.7.0(encoding@0.1.13) - optionalDependencies: - zod: 3.23.8 - transitivePeerDependencies: - - encoding - - supports-color - optional: true - openapi-sampler@1.5.1: dependencies: '@types/json-schema': 7.0.15 @@ -25754,9 +25684,9 @@ snapshots: ret@0.1.15: {} - retry-axios@2.6.0(axios@1.7.4(debug@4.3.7)): + retry-axios@2.6.0(axios@1.7.4): dependencies: - axios: 1.7.4(debug@4.3.7) + axios: 1.7.4 retry-request@7.0.2(encoding@0.1.13): dependencies: