From 53656a02550e50a3a6c0b4d6173ae9c4584e0ec0 Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Wed, 5 Mar 2025 11:12:23 +0200 Subject: [PATCH] fix(editor): Fix using `option.value` as for loop key (no-changelog) (#13458) Co-authored-by: Elias Meire Co-authored-by: Tomi Turtiainen <10324676+tomi@users.noreply.github.com> --- .../src/components/ParameterInput.vue | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/packages/frontend/editor-ui/src/components/ParameterInput.vue b/packages/frontend/editor-ui/src/components/ParameterInput.vue index 08d3cf15f1..510bb7b115 100644 --- a/packages/frontend/editor-ui/src/components/ParameterInput.vue +++ b/packages/frontend/editor-ui/src/components/ParameterInput.vue @@ -18,11 +18,12 @@ import type { INodeParameterResourceLocator, INodeParameters, INodeProperties, + INodePropertyCollection, INodePropertyOptions, IParameterLabel, NodeParameterValueType, } from 'n8n-workflow'; -import { CREDENTIAL_EMPTY_VALUE, NodeHelpers } from 'n8n-workflow'; +import { CREDENTIAL_EMPTY_VALUE, isINodePropertyOptions, NodeHelpers } from 'n8n-workflow'; import CodeNodeEditor from '@/components/CodeNodeEditor/CodeNodeEditor.vue'; import CredentialsSelect from '@/components/CredentialsSelect.vue'; @@ -66,7 +67,9 @@ import type { EventBus } from '@n8n/utils/event-bus'; import { createEventBus } from '@n8n/utils/event-bus'; import { useRouter } from 'vue-router'; import { useElementSize } from '@vueuse/core'; +import { captureMessage } from '@sentry/vue'; import { completeExpressionSyntax, isStringWithExpressionSyntax } from '@/utils/expressions'; +import { isPresent } from '@/utils/typesUtils'; import CssEditor from './CssEditor/CssEditor.vue'; type Picker = { $emit: (arg0: string, arg1: Date) => void }; @@ -422,14 +425,11 @@ const editorLanguage = computed(() => { return getArgument('editorLanguage') ?? 'javaScript'; }); -const parameterOptions = computed(() => { - if (!hasRemoteMethod.value) { - // Options are already given - return props.parameter.options as INodePropertyOptions[]; - } +const parameterOptions = computed(() => { + const options = hasRemoteMethod.value ? remoteParameterOptions.value : props.parameter.options; + const safeOptions = (options ?? []).filter(isValidParameterOption); - // Options get loaded from server - return remoteParameterOptions.value; + return safeOptions; }); const isSwitch = computed( @@ -571,6 +571,12 @@ const shouldCaptureForPosthog = computed(() => { return false; }); +function isValidParameterOption( + option: INodePropertyOptions | INodeProperties | INodePropertyCollection, +): option is INodePropertyOptions { + return isINodePropertyOptions(option) && isPresent(option.value) && isPresent(option.name); +} + function isRemoteParameterOption(option: INodePropertyOptions) { return remoteParameterOptionsKeys.value.includes(option.name); } @@ -1084,6 +1090,28 @@ watch(isModelValueExpression, async (isExpression, wasExpression) => { } }); +// Investigate invalid parameter options +// Sentry issue: https://n8nio.sentry.io/issues/6275981089/?project=4503960699273216 +const unwatchParameterOptions = watch( + [remoteParameterOptions, () => props.parameter.options], + ([remoteOptions, options]) => { + const allOptions = [...remoteOptions, ...(options ?? [])]; + const invalidOptions = allOptions.filter((option) => !isValidParameterOption(option)); + + if (invalidOptions.length > 0) { + captureMessage('Invalid parameter options', { + level: 'error', + extra: { + invalidOptions, + parameter: props.parameter.name, + node: node.value, + }, + }); + unwatchParameterOptions(); + } + }, +); + onUpdated(async () => { await nextTick();