From 5d58c8a55ec7e4559e35c97ec2ec81f93fb63ef3 Mon Sep 17 00:00:00 2001
From: Enzo Martellucci <52219496+EnxDev@users.noreply.github.com>
Date: Thu, 15 Jan 2026 15:22:45 +0100
Subject: [PATCH] fix(charts): properly parse error responses in StatefulChart
(#37130)
(cherry picked from commit 23b91d22efd68fa43a8f8dc3a80b917b329b2a65)
---
.../chart/components/StatefulChart.test.tsx | 92 +++++++++++++++++++
.../src/chart/components/StatefulChart.tsx | 11 ++-
2 files changed, 101 insertions(+), 2 deletions(-)
diff --git a/superset-frontend/packages/superset-ui-core/src/chart/components/StatefulChart.test.tsx b/superset-frontend/packages/superset-ui-core/src/chart/components/StatefulChart.test.tsx
index 35b65ac7727..b02d3bb04a8 100644
--- a/superset-frontend/packages/superset-ui-core/src/chart/components/StatefulChart.test.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/chart/components/StatefulChart.test.tsx
@@ -471,3 +471,95 @@ test('should handle chartId changes', async () => {
expect(mockChartClient.loadFormData).toHaveBeenCalledTimes(2);
});
});
+
+test('should display error message when HTTP request fails with Response object', async () => {
+ const errorBody = JSON.stringify({ message: 'Error: division by zero' });
+ const mockResponse = new Response(errorBody, {
+ status: 400,
+ statusText: 'Bad Request',
+ headers: { 'Content-Type': 'application/json' },
+ });
+ mockChartClient.client.post.mockRejectedValue(mockResponse);
+
+ const onError = jest.fn();
+ const { findByText } = render(
+ ,
+ );
+
+ const errorElement = await findByText(/Error: division by zero/i);
+ expect(errorElement).toBeInTheDocument();
+
+ await waitFor(() => {
+ expect(onError).toHaveBeenCalledTimes(1);
+ expect(onError).toHaveBeenCalledWith(expect.any(Error));
+ expect(onError.mock.calls[0][0].message).toBe('Error: division by zero');
+ });
+});
+
+test('should display error message when HTTP request fails with errors array', async () => {
+ const errorBody = JSON.stringify({
+ errors: [
+ {
+ message: 'Query failed: column "invalid_col" does not exist',
+ error_type: 'COLUMN_DOES_NOT_EXIST_ERROR',
+ },
+ ],
+ });
+ const mockResponse = new Response(errorBody, {
+ status: 422,
+ statusText: 'Unprocessable Entity',
+ headers: { 'Content-Type': 'application/json' },
+ });
+ mockChartClient.client.post.mockRejectedValue(mockResponse);
+
+ const { findByText } = render(
+ ,
+ );
+
+ const errorElement = await findByText(
+ /Query failed: column "invalid_col" does not exist/i,
+ );
+ expect(errorElement).toBeInTheDocument();
+});
+
+test('should display generic error message for network failures', async () => {
+ const networkError = new TypeError('Failed to fetch');
+ mockChartClient.client.post.mockRejectedValue(networkError);
+
+ const { findByText } = render(
+ ,
+ );
+
+ const errorElement = await findByText(/Network error/i);
+ expect(errorElement).toBeInTheDocument();
+});
+
+test('should pass error to custom errorComponent when provided', async () => {
+ const errorBody = JSON.stringify({ message: 'Custom error message' });
+ const mockResponse = new Response(errorBody, {
+ status: 400,
+ statusText: 'Bad Request',
+ headers: { 'Content-Type': 'application/json' },
+ });
+ mockChartClient.client.post.mockRejectedValue(mockResponse);
+
+ const CustomErrorComponent = ({ error }: { error: Error }) => (
+
Custom: {error.message}
+ );
+
+ const { findByTestId } = render(
+ ,
+ );
+
+ const customError = await findByTestId('custom-error');
+ expect(customError).toBeInTheDocument();
+ expect(customError).toHaveTextContent('Custom: Custom error message');
+});
diff --git a/superset-frontend/packages/superset-ui-core/src/chart/components/StatefulChart.tsx b/superset-frontend/packages/superset-ui-core/src/chart/components/StatefulChart.tsx
index ba22e64808a..83ddb688b96 100644
--- a/superset-frontend/packages/superset-ui-core/src/chart/components/StatefulChart.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/chart/components/StatefulChart.tsx
@@ -25,6 +25,7 @@ import {
SupersetClientInterface,
buildQueryContext,
RequestConfig,
+ getClientErrorObject,
} from '../..';
import { Loading } from '../../components/Loading';
import ChartClient from '../clients/ChartClient';
@@ -279,11 +280,17 @@ export default function StatefulChart(props: StatefulChartProps) {
}
} catch (err) {
// Ignore abort errors
- if (err.name === 'AbortError') {
+ if ((err as Error).name === 'AbortError') {
return;
}
- const errorObj = err as Error;
+ const parsedError = await getClientErrorObject(
+ err as Parameters[0],
+ );
+ const errorMessage =
+ parsedError.error || parsedError.message || 'An error occurred';
+
+ const errorObj = new Error(errorMessage);
setStatus('error');
setError(errorObj);