From 12cd259c55142d7ec00a6cce8277ac8f45437b8f Mon Sep 17 00:00:00 2001 From: Mafi Date: Fri, 28 Aug 2026 15:01:17 +1000 Subject: [PATCH] fix(dataset): preserve validation error messages (#43631) Co-authored-by: Matt Fitzgerald --- .../src/query/getClientErrorObject.ts | 21 +++++++++++++++---- .../test/query/getClientErrorObject.test.ts | 18 ++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/superset-frontend/packages/superset-ui-core/src/query/getClientErrorObject.ts b/superset-frontend/packages/superset-ui-core/src/query/getClientErrorObject.ts index cc555fa4e93..3d31a3cd3de 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/getClientErrorObject.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/getClientErrorObject.ts @@ -119,6 +119,20 @@ export function retrieveErrorMessage( return statusError || parseStringResponse(str); } +function getFirstValidationError(message: JsonObject): string | undefined { + const [firstError] = Object.values(message); + + if (typeof firstError === 'string') { + return firstError; + } + + if (Array.isArray(firstError)) { + return firstError.find((item): item is string => typeof item === 'string'); + } + + return undefined; +} + export function parseErrorJson(responseJson: JsonObject): ClientErrorObject { let error = { ...responseJson }; // Backwards compatibility for old error renderers with the new error object @@ -126,13 +140,12 @@ export function parseErrorJson(responseJson: JsonObject): ClientErrorObject { error.error = error.description = error.errors[0].message; error.link = error.errors[0]?.extra?.link; } - // Marshmallow field validation returns the error message in the format - // of { message: { field1: [msg1, msg2], field2: [msg], } } + // Marshmallow field validation returns arrays for string messages, but + // serializes lazy translation messages as strings instead. if (!error.error && error.message) { if (typeof error.message === 'object') { error.error = - Object.values(error.message as Record)[0]?.[0] || - t('Invalid input'); + getFirstValidationError(error.message) || t('Invalid input'); } if (typeof error.message === 'string') { if (checkForHtml(error.message)) { diff --git a/superset-frontend/packages/superset-ui-core/test/query/getClientErrorObject.test.ts b/superset-frontend/packages/superset-ui-core/test/query/getClientErrorObject.test.ts index 70cc26ba110..afb86ba66b0 100644 --- a/superset-frontend/packages/superset-ui-core/test/query/getClientErrorObject.test.ts +++ b/superset-frontend/packages/superset-ui-core/test/query/getClientErrorObject.test.ts @@ -244,6 +244,24 @@ test('parseErrorJson with message', () => { }); }); +test('parseErrorJson preserves string-valued validation messages', () => { + const calculatedColumnError = + 'Custom SQL fields cannot be parsed as a single SQL statement.'; + + expect( + parseErrorJson({ + message: { + 'columns.0.expression': calculatedColumnError, + }, + }), + ).toEqual({ + message: { + 'columns.0.expression': calculatedColumnError, + }, + error: calculatedColumnError, + }); +}); + test('parseErrorJson with HTML message', () => { expect( parseErrorJson({