fix(editor): Fix node execution errors showing undefined (#9487)

This commit is contained in:
Elias Meire
2024-05-22 15:54:25 +02:00
committed by GitHub
parent a217866cef
commit 62ee796895
6 changed files with 272 additions and 31 deletions

View File

@@ -1,6 +1,5 @@
<script lang="ts" setup>
import { useI18n } from '@/composables/useI18n';
import type { PropType } from 'vue';
import { computed } from 'vue';
import { useClipboard } from '@/composables/useClipboard';
import { useToast } from '@/composables/useToast';
@@ -20,13 +19,11 @@ import { sanitizeHtml } from '@/utils/htmlUtils';
import { MAX_DISPLAY_DATA_SIZE } from '@/constants';
import type { BaseTextKey } from '@/plugins/i18n';
const props = defineProps({
error: {
type: Object as PropType<NodeError | NodeApiError | NodeOperationError>,
required: true,
},
});
type Props = {
error: NodeError | NodeApiError | NodeOperationError;
};
const props = defineProps<Props>();
const clipboard = useClipboard();
const toast = useToast();
const i18n = useI18n();
@@ -36,7 +33,7 @@ const ndvStore = useNDVStore();
const rootStore = useRootStore();
const displayCause = computed(() => {
return JSON.stringify(props.error.cause).length < MAX_DISPLAY_DATA_SIZE;
return JSON.stringify(props.error.cause ?? '').length < MAX_DISPLAY_DATA_SIZE;
});
const parameters = computed<INodeProperties[]>(() => {
@@ -181,28 +178,31 @@ function addItemIndexSuffix(message: string): string {
}
function getErrorMessage(): string {
const baseErrorMessage = '';
let message = '';
const isSubNodeError =
props.error.name === 'NodeOperationError' &&
(props.error as NodeOperationError).functionality === 'configuration-node';
const isNonEmptyString = (value?: unknown): value is string =>
!!value && typeof value === 'string';
if (isSubNodeError) {
message = i18n.baseText('nodeErrorView.errorSubNode', {
interpolate: { node: props.error.node.name },
});
} else if (
props.error.message === props.error.description ||
!props.error.context?.messageTemplate
isNonEmptyString(props.error.message) &&
(props.error.message === props.error.description || !props.error.context?.messageTemplate)
) {
message = baseErrorMessage + props.error.message;
} else {
const parameterName = parameterDisplayName(props.error.context.parameter as string);
message =
baseErrorMessage +
(props.error.context.messageTemplate as string).replace(/%%PARAMETER%%/g, parameterName);
message = props.error.message;
} else if (
isNonEmptyString(props.error.context?.messageTemplate) &&
isNonEmptyString(props.error.context?.parameter)
) {
const parameterName = parameterDisplayName(props.error.context.parameter);
message = props.error.context.messageTemplate.replace(/%%PARAMETER%%/g, parameterName);
} else if (Array.isArray(props.error.messages) && props.error.messages.length > 0) {
message = props.error.messages[0];
}
return addItemIndexSuffix(message);
@@ -364,13 +364,14 @@ function copySuccess() {
<template>
<div class="node-error-view">
<div class="node-error-view__header">
<div class="node-error-view__header-message">
<div class="node-error-view__header-message" data-test-id="node-error-message">
<div>
{{ getErrorMessage() }}
</div>
</div>
<div
v-if="error.description || error.context?.descriptionKey"
data-test-id="node-error-description"
class="node-error-view__header-description"
v-html="getErrorDescription()"
></div>

View File

@@ -0,0 +1,66 @@
import { createComponentRenderer } from '@/__tests__/render';
import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
import NodeErrorView from '@/components/Error/NodeErrorView.vue';
import { STORES } from '@/constants';
import { createTestingPinia } from '@pinia/testing';
import { type INode } from 'n8n-workflow';
const DEFAULT_SETUP = {
pinia: createTestingPinia({
initialState: {
[STORES.SETTINGS]: SETTINGS_STORE_DEFAULT_STATE,
},
}),
};
const renderComponent = createComponentRenderer(NodeErrorView, DEFAULT_SETUP);
describe('NodeErrorView.vue', () => {
let mockNode: INode;
afterEach(() => {
mockNode = {
parameters: {
mode: 'runOnceForAllItems',
language: 'javaScript',
jsCode: 'cons error = 9;',
notice: '',
},
id: 'd1ce5dc9-f9ae-4ac6-84e5-0696ba175dd9',
name: 'Code',
type: 'n8n-nodes-base.code',
typeVersion: 2,
position: [940, 240],
};
vi.clearAllMocks();
});
it('renders an Error with a messages array', async () => {
const { getByTestId } = renderComponent({
props: {
error: {
node: mockNode,
messages: ['Unexpected identifier [line 1]'],
},
},
});
const errorMessage = getByTestId('node-error-message');
expect(errorMessage).toHaveTextContent('Unexpected identifier [line 1]');
});
it('renders an Error with a message string', async () => {
const { getByTestId } = renderComponent({
props: {
error: {
node: mockNode,
message: 'Unexpected identifier [line 1]',
},
},
});
const errorMessage = getByTestId('node-error-message');
expect(errorMessage).toHaveTextContent('Unexpected identifier [line 1]');
});
});

View File

@@ -837,13 +837,10 @@ export default defineComponent({
return Boolean(this.workflowsStore.subWorkflowExecutionError);
},
workflowRunErrorAsNodeError(): NodeError {
return {
node: this.node,
messages: [this.workflowRunData?.[this.node?.name]?.[this.runIndex]?.error?.message ?? ''],
} as NodeError;
return this.workflowRunData?.[this.node?.name]?.[this.runIndex]?.error as NodeError;
},
hasRunError(): boolean {
return Boolean(this.node && this.workflowRunData?.[this.node.name]?.[this.runIndex]?.error);
return Boolean(this.node && this.workflowRunErrorAsNodeError);
},
executionHints(): NodeHint[] {
if (this.hasNodeRun) {