fix(editor): Improve sub-workflow debugging for more node error types (#14347)

This commit is contained in:
Charlie Kolb
2025-04-04 08:33:29 +02:00
committed by GitHub
parent 7cef314535
commit 9104743a5f
3 changed files with 41 additions and 18 deletions

View File

@@ -2,6 +2,11 @@ import { isObjectLiteral } from 'n8n-core';
import { NodeOperationError } from 'n8n-workflow';
import type { Workflow } from 'n8n-workflow';
/**
* Optional properties that should be propagated from an error object to the new Error instance.
*/
const errorProperties = ['description', 'stack', 'executionId', 'workflowId'];
export function objectToError(errorObject: unknown, workflow: Workflow): Error {
// TODO: Expand with other error types
if (errorObject instanceof Error) {
@@ -34,15 +39,11 @@ export function objectToError(errorObject: unknown, workflow: Workflow): Error {
error = new Error(errorObject.message);
}
if ('description' in errorObject) {
// @ts-expect-error Error descriptions are surfaced by the UI but
// not all backend errors account for this property yet.
error.description = errorObject.description as string;
for (const field of errorProperties) {
if (field in errorObject && errorObject[field]) {
// Not all errors contain these properties
(error as unknown as Record<string, unknown>)[field] = errorObject[field];
}
if ('stack' in errorObject) {
// If there's a 'stack' property, set it on the new Error instance.
error.stack = errorObject.stack as string;
}
return error;

View File

@@ -27,5 +27,8 @@ export function parseErrorMetadata(error: unknown): ISubWorkflowMetadata | undef
if (hasKey(error, 'errorResponse')) {
return parseErrorResponseWorkflowMetadata(error.errorResponse);
}
return undefined;
// This accounts for cases where the backend attaches the properties on plain errors
// e.g. from custom nodes throwing literal `Error` or `ApplicationError` objects directly
return parseErrorResponseWorkflowMetadata(error);
}

View File

@@ -2,12 +2,38 @@ import { parseErrorMetadata } from '@/MetadataUtils';
describe('MetadataUtils', () => {
describe('parseMetadataFromError', () => {
it('should return undefined if error does not have response', () => {
const expectedMetadata = {
subExecution: {
executionId: '123',
workflowId: '456',
},
subExecutionsCount: 1,
};
it('should return undefined if error does not have response or both keys on the object', () => {
const error = { message: 'An error occurred' };
const result = parseErrorMetadata(error);
expect(result).toBeUndefined();
});
it('should return undefined if errorResponse only has workflowId key', () => {
const error = { errorResponse: { executionId: '123' } };
const result = parseErrorMetadata(error);
expect(result).toBeUndefined();
});
it('should return undefined if error only has executionId key', () => {
const error = { executionId: '123' };
const result = parseErrorMetadata(error);
expect(result).toBeUndefined();
});
it('should support executionId and workflowId key directly on the error object', () => {
const error = { executionId: '123', workflowId: '456' };
const result = parseErrorMetadata(error);
expect(result).toEqual(expectedMetadata);
});
it('should return undefined if error response does not have subworkflow data', () => {
const error = { errorResponse: { someKey: 'someValue' } };
const result = parseErrorMetadata(error);
@@ -16,13 +42,6 @@ describe('MetadataUtils', () => {
it('should return metadata if error response has subworkflow data', () => {
const error = { errorResponse: { executionId: '123', workflowId: '456' } };
const expectedMetadata = {
subExecution: {
executionId: '123',
workflowId: '456',
},
subExecutionsCount: 1,
};
const result = parseErrorMetadata(error);
expect(result).toEqual(expectedMetadata);
});