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

rusackas 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 067a356b6f0 fix(chart): include echart_options in Matrixify 
render-trigger allowlist (#43946)
067a356b6f0 is described below

commit 067a356b6f0d4ee619c9dcf93831a7f7957bf187
Author: Evan Rusackas <[email protected]>
AuthorDate: Tue Sep 8 13:24:13 2026 -0700

    fix(chart): include echart_options in Matrixify render-trigger allowlist 
(#43946)
    
    Co-authored-by: Claude Opus 4.8 <[email protected]>
---
 .../src/chart/components/StatefulChart.test.tsx    | 98 ++++++++++++++++++++++
 .../src/chart/components/StatefulChart.tsx         | 30 +++++++
 2 files changed, 128 insertions(+)

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 7d671f3ff5e..ff40b69c343 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
@@ -566,6 +566,104 @@ test('should NOT refetch data when other string-based 
renderTrigger controls cha
   });
 });
 
+test('should NOT refetch data when echart_options (string-based renderTrigger 
control) changes', async () => {
+  // Matches how the Timeseries/MixedTimeseries control panels reference this
+  // shared control: a bare string, e.g. ['echart_options'].
+  const controlPanelConfig = {
+    controlPanelSections: [
+      {
+        controlSetRows: [['echart_options']],
+      },
+    ],
+  };
+
+  jest.mocked(getChartControlPanelRegistry).mockReturnValue({
+    get: jest.fn().mockReturnValue(controlPanelConfig),
+  } as unknown as ReturnType<typeof getChartControlPanelRegistry>);
+
+  const formDataWithEchartOptions = {
+    ...mockFormData,
+    echart_options: '{}',
+  };
+
+  const { rerender, getByTestId } = render(
+    <StatefulChart
+      formData={formDataWithEchartOptions}
+      chartType="test_chart"
+    />,
+  );
+
+  await waitFor(() => {
+    expect(mockChartClient.client.post).toHaveBeenCalledTimes(1);
+  });
+
+  // Edit the ECharts Options field (e.g. from the Customize tab while the
+  // chart is part of a Matrixify grid cell).
+  const updatedFormData = {
+    ...formDataWithEchartOptions,
+    echart_options: '{"title": {"text": "My Chart"}}',
+  };
+
+  rerender(<StatefulChart formData={updatedFormData} chartType="test_chart" 
/>);
+
+  await waitFor(() => {
+    // Should NOT refetch data - echart_options is a renderTrigger control
+    expect(mockChartClient.client.post).toHaveBeenCalledTimes(1);
+    // But should re-render with the new formData
+    expect(getByTestId('super-chart')).toHaveTextContent(
+      JSON.stringify(updatedFormData),
+    );
+  });
+});
+
+test('should refetch when a chart overrides a shared renderTrigger control to 
renderTrigger: false', async () => {
+  // Matches Country Map's controlPanel.controlOverrides, which sets
+  // linear_color_scheme to renderTrigger: false because it drives the
+  // choropleth data query rather than just styling.
+  const controlPanelConfig = {
+    controlPanelSections: [
+      {
+        controlSetRows: [['linear_color_scheme']],
+      },
+    ],
+    controlOverrides: {
+      linear_color_scheme: {
+        renderTrigger: false,
+      },
+    },
+  };
+
+  jest.mocked(getChartControlPanelRegistry).mockReturnValue({
+    get: jest.fn().mockReturnValue(controlPanelConfig),
+  } as unknown as ReturnType<typeof getChartControlPanelRegistry>);
+
+  const formDataWithColorScheme = {
+    ...mockFormData,
+    linear_color_scheme: 'schemeA',
+  };
+
+  const { rerender } = render(
+    <StatefulChart formData={formDataWithColorScheme} chartType="test_chart" 
/>,
+  );
+
+  await waitFor(() => {
+    expect(mockChartClient.client.post).toHaveBeenCalledTimes(1);
+  });
+
+  const updatedFormData = {
+    ...formDataWithColorScheme,
+    linear_color_scheme: 'schemeB',
+  };
+
+  rerender(<StatefulChart formData={updatedFormData} chartType="test_chart" 
/>);
+
+  await waitFor(() => {
+    // Should refetch because this chart's controlOverrides mark the control
+    // as data-affecting, overriding the shared-control fallback.
+    expect(mockChartClient.client.post).toHaveBeenCalledTimes(2);
+  });
+});
+
 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 = {
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 e6c9103594b..723b33f7d31 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
@@ -50,6 +50,9 @@ type LoadingState = 'uninitialized' | 'loading' | 'loaded' | 
'error';
  * 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.
+ *
+ * Keep this list in sync with the `renderTrigger: true` entries in
+ * @superset-ui/chart-controls's sharedControls.tsx.
  */
 const RENDER_TRIGGER_SHARED_CONTROLS = new Set([
   'zoomable',
@@ -57,6 +60,11 @@ const RENDER_TRIGGER_SHARED_CONTROLS = new Set([
   'time_shift_color',
   'y_axis_format',
   'currency_format',
+  'color_picker',
+  'linear_color_scheme',
+  'x_axis_time_format',
+  'x_axis_number_format',
+  'echart_options',
 ]);
 
 /**
@@ -111,6 +119,28 @@ function shouldRefetchData(
       }
     });
 
+    // Individual chart types can override a shared control's renderTrigger
+    // behavior (e.g., Country Map sets `linear_color_scheme` to
+    // renderTrigger: false because it drives the choropleth query, not just
+    // styling). Apply those overrides on top of the shared-control fallback
+    // so such controls still trigger a refetch for that chart type.
+    const { controlOverrides } = controlPanel;
+    if (controlOverrides) {
+      Object.entries(controlOverrides).forEach(([controlName, override]) => {
+        if (
+          override &&
+          typeof override === 'object' &&
+          'renderTrigger' in override
+        ) {
+          if ((override as { renderTrigger?: boolean }).renderTrigger) {
+            renderTriggerControls.add(controlName);
+          } else {
+            renderTriggerControls.delete(controlName);
+          }
+        }
+      });
+    }
+
     // Check which fields changed
     const changedFields = Object.keys(nextFormData).filter(
       key =>

Reply via email to