This is an automated email from the ASF dual-hosted git repository.

enzomartellucci pushed a commit to branch enxdev/fix/matrix-chart-error-bubbling
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 28f33c81f5ef5678e8e7ed04f5d8b013502cc59f
Author: Enzo Martellucci <[email protected]>
AuthorDate: Tue Jan 13 16:26:39 2026 +0100

    fix(charts): properly parse error responses in StatefulChart
---
 .../src/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 35b65ac772..b02d3bb04a 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(
+    <StatefulChart
+      formData={mockFormData}
+      chartType="test_chart"
+      onError={onError}
+    />,
+  );
+
+  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(
+    <StatefulChart formData={mockFormData} chartType="test_chart" />,
+  );
+
+  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(
+    <StatefulChart formData={mockFormData} chartType="test_chart" />,
+  );
+
+  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 }) => (
+    <div data-test="custom-error">Custom: {error.message}</div>
+  );
+
+  const { findByTestId } = render(
+    <StatefulChart
+      formData={mockFormData}
+      chartType="test_chart"
+      errorComponent={CustomErrorComponent}
+    />,
+  );
+
+  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 ba22e64808..83ddb688b9 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<typeof getClientErrorObject>[0],
+      );
+      const errorMessage =
+        parsedError.error || parsedError.message || 'An error occurred';
+
+      const errorObj = new Error(errorMessage);
       setStatus('error');
       setError(errorObj);
 

Reply via email to