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

enzomartellucci pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 9555798d37 fix(data-zoom): Data-zoom not rendered properly in 
Matrixify (#37134)
9555798d37 is described below

commit 9555798d37ddd1af3a61d3bdb3cfb13fee3333e5
Author: Alexandru Soare <[email protected]>
AuthorDate: Fri Jan 16 15:55:21 2026 +0200

    fix(data-zoom): Data-zoom not rendered properly in Matrixify (#37134)
---
 .../src/chart/components/StatefulChart.test.tsx    | 248 +++++++++++++++++++++
 .../src/chart/components/StatefulChart.tsx         |  26 ++-
 2 files changed, 273 insertions(+), 1 deletion(-)

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 b02d3bb04a..3d73326b45 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
@@ -472,6 +472,254 @@ test('should handle chartId changes', async () => {
   });
 });
 
+test('should NOT refetch data when string-based renderTrigger control 
(zoomable) changes', async () => {
+  // Control panel with zoomable as a string reference (like ['zoomable'] in 
control panels)
+  const controlPanelConfig = {
+    controlPanelSections: [
+      {
+        controlSetRows: [
+          ['zoomable'], // String reference to shared control
+          [
+            {
+              name: 'metrics',
+              config: {
+                renderTrigger: false,
+              },
+            },
+          ],
+        ],
+      },
+    ],
+  };
+
+  (getChartControlPanelRegistry as any).mockReturnValue({
+    get: jest.fn().mockReturnValue(controlPanelConfig),
+  });
+
+  const formDataWithZoom = {
+    ...mockFormData,
+    zoomable: false,
+  };
+
+  const { rerender, getByTestId } = render(
+    <StatefulChart formData={formDataWithZoom} chartType="test_chart" />,
+  );
+
+  await waitFor(() => {
+    expect(mockChartClient.client.post).toHaveBeenCalledTimes(1);
+  });
+
+  // Toggle zoomable (string-based shared control with renderTrigger: true)
+  const updatedFormData = {
+    ...formDataWithZoom,
+    zoomable: true,
+  };
+
+  rerender(<StatefulChart formData={updatedFormData} chartType="test_chart" 
/>);
+
+  await waitFor(() => {
+    // Should NOT refetch data - zoomable is a renderTrigger control
+    expect(mockChartClient.client.post).toHaveBeenCalledTimes(1);
+    // But should re-render with new formData
+    expect(getByTestId('super-chart')).toHaveTextContent(
+      JSON.stringify(updatedFormData),
+    );
+  });
+});
+
+test('should NOT refetch data when other string-based renderTrigger controls 
change', async () => {
+  // Test other controls in RENDER_TRIGGER_SHARED_CONTROLS set
+  const controlPanelConfig = {
+    controlPanelSections: [
+      {
+        controlSetRows: [
+          ['color_scheme'], // String reference
+          ['y_axis_format'], // String reference
+          ['currency_format'], // String reference
+          ['time_shift_color'], // String reference
+        ],
+      },
+    ],
+  };
+
+  (getChartControlPanelRegistry as any).mockReturnValue({
+    get: jest.fn().mockReturnValue(controlPanelConfig),
+  });
+
+  const { rerender, getByTestId } = render(
+    <StatefulChart formData={mockFormData} chartType="test_chart" />,
+  );
+
+  await waitFor(() => {
+    expect(mockChartClient.client.post).toHaveBeenCalledTimes(1);
+  });
+
+  // Change multiple string-based renderTrigger controls
+  const updatedFormData = {
+    ...mockFormData,
+    color_scheme: 'new_scheme',
+    y_axis_format: '.2f',
+  };
+
+  rerender(<StatefulChart formData={updatedFormData} chartType="test_chart" 
/>);
+
+  await waitFor(() => {
+    // Should NOT refetch data
+    expect(mockChartClient.client.post).toHaveBeenCalledTimes(1);
+    // But should re-render
+    expect(getByTestId('super-chart')).toHaveTextContent(
+      JSON.stringify(updatedFormData),
+    );
+  });
+});
+
+test('should refetch when string control is NOT in 
RENDER_TRIGGER_SHARED_CONTROLS', async () => {
+  // Control panel with a string control that is NOT in the renderTrigger set
+  const controlPanelConfig = {
+    controlPanelSections: [
+      {
+        controlSetRows: [
+          ['some_unknown_control'], // Not in RENDER_TRIGGER_SHARED_CONTROLS
+        ],
+      },
+    ],
+  };
+
+  (getChartControlPanelRegistry as any).mockReturnValue({
+    get: jest.fn().mockReturnValue(controlPanelConfig),
+  });
+
+  const { rerender } = render(
+    <StatefulChart formData={mockFormData} chartType="test_chart" />,
+  );
+
+  await waitFor(() => {
+    expect(mockChartClient.client.post).toHaveBeenCalledTimes(1);
+  });
+
+  // Change the unknown control
+  const updatedFormData = {
+    ...mockFormData,
+    some_unknown_control: 'new_value',
+  };
+
+  rerender(<StatefulChart formData={updatedFormData} chartType="test_chart" 
/>);
+
+  await waitFor(() => {
+    // Should refetch because the control is not recognized as renderTrigger
+    expect(mockChartClient.client.post).toHaveBeenCalledTimes(2);
+  });
+});
+
+test('should handle mixed string and object controls correctly', async () => {
+  // Control panel with both string references and object definitions
+  const controlPanelConfig = {
+    controlPanelSections: [
+      {
+        controlSetRows: [
+          ['zoomable'], // String reference (in RENDER_TRIGGER_SHARED_CONTROLS)
+          [
+            {
+              name: 'minorTicks',
+              config: {
+                renderTrigger: true,
+              },
+            },
+          ], // Object definition
+        ],
+      },
+    ],
+  };
+
+  (getChartControlPanelRegistry as any).mockReturnValue({
+    get: jest.fn().mockReturnValue(controlPanelConfig),
+  });
+
+  const formDataWithControls = {
+    ...mockFormData,
+    zoomable: false,
+    minorTicks: false,
+  };
+
+  const { rerender, getByTestId } = render(
+    <StatefulChart formData={formDataWithControls} chartType="test_chart" />,
+  );
+
+  await waitFor(() => {
+    expect(mockChartClient.client.post).toHaveBeenCalledTimes(1);
+  });
+
+  // Change both string-based and object-based renderTrigger controls
+  const updatedFormData = {
+    ...formDataWithControls,
+    zoomable: true,
+    minorTicks: true,
+  };
+
+  rerender(<StatefulChart formData={updatedFormData} chartType="test_chart" 
/>);
+
+  await waitFor(() => {
+    // Should NOT refetch - both are renderTrigger controls
+    expect(mockChartClient.client.post).toHaveBeenCalledTimes(1);
+    // But should re-render
+    expect(getByTestId('super-chart')).toHaveTextContent(
+      JSON.stringify(updatedFormData),
+    );
+  });
+});
+
+test('should refetch when mixing renderTrigger string control with 
non-renderTrigger change', async () => {
+  const controlPanelConfig = {
+    controlPanelSections: [
+      {
+        controlSetRows: [
+          ['zoomable'], // String reference (renderTrigger)
+          [
+            {
+              name: 'metrics',
+              config: {
+                renderTrigger: false,
+              },
+            },
+          ],
+        ],
+      },
+    ],
+  };
+
+  (getChartControlPanelRegistry as any).mockReturnValue({
+    get: jest.fn().mockReturnValue(controlPanelConfig),
+  });
+
+  const formDataWithZoom = {
+    ...mockFormData,
+    zoomable: false,
+    metrics: ['metric1'],
+  };
+
+  const { rerender } = render(
+    <StatefulChart formData={formDataWithZoom} chartType="test_chart" />,
+  );
+
+  await waitFor(() => {
+    expect(mockChartClient.client.post).toHaveBeenCalledTimes(1);
+  });
+
+  // Change both zoomable (renderTrigger) and metrics (non-renderTrigger)
+  const updatedFormData = {
+    ...formDataWithZoom,
+    zoomable: true,
+    metrics: ['metric2'],
+  };
+
+  rerender(<StatefulChart formData={updatedFormData} chartType="test_chart" 
/>);
+
+  await waitFor(() => {
+    // Should refetch because metrics changed (non-renderTrigger)
+    expect(mockChartClient.client.post).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, {
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 83ddb688b9..d9ee87e9ea 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
@@ -37,6 +37,24 @@ import SuperChart from './SuperChart';
 // Using more specific states that align with chart loading process
 type LoadingState = 'uninitialized' | 'loading' | 'loaded' | 'error';
 
+/**
+ * Known shared controls that have renderTrigger: true.
+ * These are controls defined in sharedControls that only affect rendering,
+ * not data fetching. When these controls change, we should re-render
+ * without refetching data.
+ *
+ * This list is needed because string-based control references (e.g., 
['zoomable'])
+ * cannot be introspected for their renderTrigger property without importing
+ * sharedControls, which would create a circular dependency.
+ */
+const RENDER_TRIGGER_SHARED_CONTROLS = new Set([
+  'zoomable',
+  'color_scheme',
+  'time_shift_color',
+  'y_axis_format',
+  'currency_format',
+]);
+
 /**
  * Helper function to determine if data should be refetched based on formData 
changes
  * @param prevFormData Previous formData
@@ -73,7 +91,13 @@ function shouldRefetchData(
       if (section.controlSetRows) {
         section.controlSetRows.forEach((row: any) => {
           row.forEach((control: any) => {
-            if (control && typeof control === 'object') {
+            // Handle string references to shared controls with renderTrigger
+            if (
+              typeof control === 'string' &&
+              RENDER_TRIGGER_SHARED_CONTROLS.has(control)
+            ) {
+              renderTriggerControls.add(control);
+            } else if (control && typeof control === 'object') {
               const controlName = control.name || control.config?.name;
               if (controlName && control.config?.renderTrigger === true) {
                 renderTriggerControls.add(controlName);

Reply via email to