diff --git a/packages/editor-ui/src/components/AssignmentCollection/Assignment.vue b/packages/editor-ui/src/components/AssignmentCollection/Assignment.vue index f18b5d5c81142..816218c27fb17 100644 --- a/packages/editor-ui/src/components/AssignmentCollection/Assignment.vue +++ b/packages/editor-ui/src/components/AssignmentCollection/Assignment.vue @@ -4,14 +4,13 @@ import InputTriple from '@/components/InputTriple/InputTriple.vue'; import ParameterInputFull from '@/components/ParameterInputFull.vue'; import ParameterInputHint from '@/components/ParameterInputHint.vue'; import ParameterIssues from '@/components/ParameterIssues.vue'; -import { resolveParameter } from '@/composables/useWorkflowHelpers'; -import { isExpression } from '@/utils/expressions'; -import { isObject } from '@jsplumb/util'; -import type { AssignmentValue, INodeProperties } from 'n8n-workflow'; +import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers'; +import { isExpression, stringifyExpressionResult } from '@/utils/expressions'; +import type { AssignmentValue, INodeProperties, Result } from 'n8n-workflow'; import { computed, ref } from 'vue'; import TypeSelect from './TypeSelect.vue'; import { useNDVStore } from '@/stores/ndv.store'; -import { useI18n } from '@/composables/useI18n'; +import { useRouter } from 'vue-router'; interface Props { path: string; @@ -32,7 +31,8 @@ const emit = defineEmits<{ }>(); const ndvStore = useNDVStore(); -const i18n = useI18n(); +const router = useRouter(); +const { resolveExpression } = useWorkflowHelpers({ router }); const assignmentTypeToNodeProperty = ( type: string, @@ -81,29 +81,21 @@ const hint = computed(() => { return ''; } + let result: Result; try { - const resolvedValue = resolveParameter(value, { + const resolvedValue = resolveExpression(value, undefined, { targetItem: ndvStore.hoveringItem ?? undefined, inputNodeName: ndvStore.ndvInputNodeName, inputRunIndex: ndvStore.ndvInputRunIndex, inputBranchIndex: ndvStore.ndvInputBranchIndex, }) as unknown; - if (isObject(resolvedValue)) { - return JSON.stringify(resolvedValue); - } - if (typeof resolvedValue === 'boolean' || typeof resolvedValue === 'number') { - return resolvedValue.toString(); - } - - if (resolvedValue === '') { - return i18n.baseText('parameterInput.emptyString'); - } - - return resolvedValue as string; + result = { ok: true, result: resolvedValue }; } catch (error) { - return ''; + result = { ok: false, error }; } + + return stringifyExpressionResult(result); }); const highlightHint = computed(() => diff --git a/packages/editor-ui/src/components/ParameterInputWrapper.vue b/packages/editor-ui/src/components/ParameterInputWrapper.vue index efabfa53737f4..957b21a9ac842 100644 --- a/packages/editor-ui/src/components/ParameterInputWrapper.vue +++ b/packages/editor-ui/src/components/ParameterInputWrapper.vue @@ -71,7 +71,7 @@ import type { EventBus } from 'n8n-design-system/utils'; import { createEventBus } from 'n8n-design-system/utils'; import { useRouter } from 'vue-router'; import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers'; -import { getExpressionErrorMessage, getResolvableState } from '@/utils/expressions'; +import { stringifyExpressionResult } from '@/utils/expressions'; export default defineComponent({ name: 'ParameterInputWrapper', @@ -197,12 +197,13 @@ export default defineComponent({ isInputParentOfActiveNode(): boolean { return this.ndvStore.isInputParentOfActiveNode; }, - evaluatedExpression(): Result { + evaluatedExpression(): Result { const value = isResourceLocatorValue(this.modelValue) ? this.modelValue.value : this.modelValue; + if (!this.activeNode || !this.isValueExpression || typeof value !== 'string') { - return { ok: false, error: '' }; + return { ok: false, error: new Error() }; } try { @@ -227,28 +228,7 @@ export default defineComponent({ return evaluated.ok ? evaluated.result : null; }, evaluatedExpressionString(): string | null { - const evaluated = this.evaluatedExpression; - - if (!evaluated.ok) { - if (getResolvableState(evaluated.error) !== 'invalid') { - return null; - } - - return `[${this.$locale.baseText('parameterInput.error')}: ${getExpressionErrorMessage( - evaluated.error as Error, - )}]`; - } - - if (evaluated.result === null) { - return null; - } - - if (typeof evaluated.result === 'string' && evaluated.result.length === 0) { - return this.$locale.baseText('parameterInput.emptyString'); - } - return typeof evaluated.result === 'string' - ? evaluated.result - : JSON.stringify(evaluated.result); + return stringifyExpressionResult(this.evaluatedExpression); }, expressionOutput(): string | null { if (this.isValueExpression && this.evaluatedExpressionString) { diff --git a/packages/editor-ui/src/utils/__tests__/expressions.test.ts b/packages/editor-ui/src/utils/__tests__/expressions.test.ts new file mode 100644 index 0000000000000..5817a6a35bc74 --- /dev/null +++ b/packages/editor-ui/src/utils/__tests__/expressions.test.ts @@ -0,0 +1,34 @@ +import { ExpressionError } from 'n8n-workflow'; +import { stringifyExpressionResult } from '../expressions'; + +describe('stringifyExpressionResult()', () => { + it('should return empty string for non-critical errors', () => { + expect( + stringifyExpressionResult({ + ok: false, + error: new ExpressionError('error message', { type: 'no_execution_data' }), + }), + ).toEqual(''); + }); + + it('should return an error message for critical errors', () => { + expect( + stringifyExpressionResult({ + ok: false, + error: new ExpressionError('error message', { type: 'no_input_connection' }), + }), + ).toEqual('[ERROR: No input connected]'); + }); + + it('should return empty string when result is null', () => { + expect(stringifyExpressionResult({ ok: true, result: null })).toEqual(''); + }); + + it('should return [empty] message when result is empty string', () => { + expect(stringifyExpressionResult({ ok: true, result: '' })).toEqual('[empty]'); + }); + + it('should return the result when it is a string', () => { + expect(stringifyExpressionResult({ ok: true, result: 'foo' })).toEqual('foo'); + }); +}); diff --git a/packages/editor-ui/src/utils/expressions.ts b/packages/editor-ui/src/utils/expressions.ts index 2e9b2ee6aecdb..dbab832fd670c 100644 --- a/packages/editor-ui/src/utils/expressions.ts +++ b/packages/editor-ui/src/utils/expressions.ts @@ -1,5 +1,5 @@ import type { ResolvableState } from '@/types/expressions'; -import { ExpressionError, ExpressionParser } from 'n8n-workflow'; +import { ExpressionError, ExpressionParser, type Result } from 'n8n-workflow'; import { i18n } from '@/plugins/i18n'; import { useWorkflowsStore } from '@/stores/workflows.store'; @@ -110,3 +110,23 @@ export const getExpressionErrorMessage = (error: Error): string => { return error.message; }; + +export const stringifyExpressionResult = (result: Result): string => { + if (!result.ok) { + if (getResolvableState(result.error) !== 'invalid') { + return ''; + } + + return `[${i18n.baseText('parameterInput.error')}: ${getExpressionErrorMessage(result.error)}]`; + } + + if (result.result === null) { + return ''; + } + + if (typeof result.result === 'string' && result.result.length === 0) { + return i18n.baseText('parameterInput.emptyString'); + } + + return typeof result.result === 'string' ? result.result : JSON.stringify(result.result); +};