diff --git a/packages/core/src/execution-engine/node-execution-context/local-load-options-context.ts b/packages/core/src/execution-engine/node-execution-context/local-load-options-context.ts index caf90a7739..c318578b09 100644 --- a/packages/core/src/execution-engine/node-execution-context/local-load-options-context.ts +++ b/packages/core/src/execution-engine/node-execution-context/local-load-options-context.ts @@ -1,5 +1,5 @@ import get from 'lodash/get'; -import { ApplicationError, Workflow } from 'n8n-workflow'; +import { ApplicationError, resolveRelativePath, Workflow } from 'n8n-workflow'; import type { INodeParameterResourceLocator, IWorkflowExecuteAdditionalData, @@ -61,9 +61,7 @@ export class LocalLoadOptionsContext implements ILocalLoadOptionsFunctions { getCurrentNodeParameter(parameterPath: string): NodeParameterValueType | object | undefined { const nodeParameters = this.additionalData.currentNodeParameters; - if (parameterPath.startsWith('&')) { - parameterPath = `${this.path.split('.').slice(1, -1).join('.')}.${parameterPath.slice(1)}`; - } + parameterPath = resolveRelativePath(this.path, parameterPath); return get(nodeParameters, parameterPath); } diff --git a/packages/frontend/editor-ui/src/components/ParameterInput.vue b/packages/frontend/editor-ui/src/components/ParameterInput.vue index d0e86375f0..9ddf7989c5 100644 --- a/packages/frontend/editor-ui/src/components/ParameterInput.vue +++ b/packages/frontend/editor-ui/src/components/ParameterInput.vue @@ -21,7 +21,12 @@ import type { IParameterLabel, NodeParameterValueType, } from 'n8n-workflow'; -import { CREDENTIAL_EMPTY_VALUE, isResourceLocatorValue, NodeHelpers } from 'n8n-workflow'; +import { + CREDENTIAL_EMPTY_VALUE, + isResourceLocatorValue, + NodeHelpers, + resolveRelativePath, +} from 'n8n-workflow'; import type { CodeNodeLanguageOption } from '@/components/CodeNodeEditor/CodeNodeEditor.vue'; import CodeNodeEditor from '@/components/CodeNodeEditor/CodeNodeEditor.vue'; @@ -376,7 +381,9 @@ const dependentParametersValues = computed(() => { const resolvedNodeParameters = workflowHelpers.resolveParameter(currentNodeParameters); const returnValues: string[] = []; - for (const parameterPath of loadOptionsDependsOn) { + for (let parameterPath of loadOptionsDependsOn) { + parameterPath = resolveRelativePath(props.path, parameterPath); + returnValues.push(get(resolvedNodeParameters, parameterPath) as string); } diff --git a/packages/frontend/editor-ui/src/components/ParameterInputList.vue b/packages/frontend/editor-ui/src/components/ParameterInputList.vue index fa3122928b..39c76f3925 100644 --- a/packages/frontend/editor-ui/src/components/ParameterInputList.vue +++ b/packages/frontend/editor-ui/src/components/ParameterInputList.vue @@ -5,7 +5,12 @@ import type { INodeProperties, NodeParameterValueType, } from 'n8n-workflow'; -import { ADD_FORM_NOTICE, getParameterValueByPath, NodeHelpers } from 'n8n-workflow'; +import { + ADD_FORM_NOTICE, + getParameterValueByPath, + NodeHelpers, + resolveRelativePath, +} from 'n8n-workflow'; import { computed, defineAsyncComponent, onErrorCaptured, ref, watch, type WatchSource } from 'vue'; import type { INodeUi, IUpdateInformation } from '@/Interface'; @@ -402,7 +407,9 @@ function getDependentParametersValues(parameter: INodeProperties): string | null const resolvedNodeParameters = workflowHelpers.resolveParameter(currentNodeParameters); const returnValues: string[] = []; - for (const parameterPath of loadOptionsDependsOn) { + for (let parameterPath of loadOptionsDependsOn) { + parameterPath = resolveRelativePath(props.path, parameterPath); + returnValues.push(get(resolvedNodeParameters, parameterPath) as string); } diff --git a/packages/workflow/src/index.ts b/packages/workflow/src/index.ts index 7dbcc09394..67a58048bd 100644 --- a/packages/workflow/src/index.ts +++ b/packages/workflow/src/index.ts @@ -69,6 +69,7 @@ export * as ExpressionParser from './extensions/expression-parser'; export { NativeMethods } from './native-methods'; export * from './node-parameters/filter-parameter'; export * from './node-parameters/parameter-type-validation'; +export * from './node-parameters/path-utils'; export * from './evaluation-helpers'; export type { diff --git a/packages/workflow/src/node-parameters/path-utils.ts b/packages/workflow/src/node-parameters/path-utils.ts new file mode 100644 index 0000000000..0e5116f5f3 --- /dev/null +++ b/packages/workflow/src/node-parameters/path-utils.ts @@ -0,0 +1,24 @@ +/** + * Resolve relative paths starting in & in the context of a given full path including parameters, + * which will be dropped in the process. + * If `candidateRelativePath` is not relative, it is returned unchanged. + * + * `parameters.a.b.c`, `&d` -> `a.b.d` + * `parameters.a.b[0].c`, `&d` -> `a.b[0].d` + * `parameters.a.b.c`, `d` -> `d` + */ +export function resolveRelativePath( + fullPathWithParameters: string, + candidateRelativePath: string, +): string { + if (candidateRelativePath.startsWith('&')) { + const resolvedLeaf = candidateRelativePath.slice(1); + const pathToLeaf = fullPathWithParameters.split('.').slice(1, -1).join('.'); + + if (!pathToLeaf) return resolvedLeaf; + + return `${pathToLeaf}.${resolvedLeaf}`; + } + + return candidateRelativePath; +} diff --git a/packages/workflow/test/node-parameters/path-utils.test.ts b/packages/workflow/test/node-parameters/path-utils.test.ts new file mode 100644 index 0000000000..c0077337e2 --- /dev/null +++ b/packages/workflow/test/node-parameters/path-utils.test.ts @@ -0,0 +1,20 @@ +import { resolveRelativePath } from '../../src/node-parameters/path-utils'; + +describe('resolveRelativePath', () => { + test.each([ + ['parameters.level1.level2.field', '&childField', 'level1.level2.childField'], + ['parameters.level1.level2[0].field', '&childField', 'level1.level2[0].childField'], + ['parameters.level1.level2.field', 'absolute.path', 'absolute.path'], + ['parameters', '&childField', 'childField'], + ['parameters.level1.level2.field', '', ''], + ['', '&childField', 'childField'], + ['', '', ''], + ['parameters.level1.level2.field', 'relative.path', 'relative.path'], + ])( + 'should resolve relative path for fullPath: %s and candidateRelativePath: %s', + (fullPath, candidateRelativePath, expected) => { + const result = resolveRelativePath(fullPath, candidateRelativePath); + expect(result).toBe(expected); + }, + ); +});