codeant-ai-for-open-source[bot] commented on code in PR #36708:
URL: https://github.com/apache/superset/pull/36708#discussion_r2715117268


##########
superset-frontend/src/components/Chart/chartReducer.ts:
##########
@@ -64,6 +64,7 @@ export default function chartReducer(
         chartAlert: null,
         queriesResponse: action.queriesResponse,
         chartUpdateEndTime: now(),
+        form_data: state.latestQueryFormData,

Review Comment:
   **Suggestion:** In the CHART_UPDATE_SUCCEEDED handler a snake_case property 
`form_data` is being set from `state.latestQueryFormData`. The rest of this 
reducer and the typed `ChartState` use `latestQueryFormData` (camelCase). 
Introducing `form_data` creates an inconsistent state shape and consumers that 
read `latestQueryFormData` will still see stale data; replace the snake_case 
property with `latestQueryFormData` so the reducer consistently updates the 
canonical field. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Dashboard force-refresh shows stale BigNumber metric.
   - ⚠️ Chart update reducer state shape inconsistent.
   - ⚠️ CHART_UPDATE_SUCCEEDED flow uses wrong field.
   ```
   </details>
   
   ```suggestion
           latestQueryFormData: state.latestQueryFormData,
   ```
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. In Tab A, edit and save a chart. The save flow dispatches the 
UPDATE_CHART_FORM_DATA
   action handled by chartReducer at 
superset-frontend/src/components/Chart/chartReducer.ts
   lines 196-205 (the top-level if (action.type === 
actions.UPDATE_CHART_FORM_DATA) block
   added in this PR). That block updates both charts[chartId].form_data and
   charts[chartId].latestQueryFormData (see chartReducer.ts:196-205).
   
   2. In a different browser Tab B showing the dashboard, click "Force Refresh" 
which runs
   the chart update lifecycle and dispatches CHART_UPDATE_STARTED then
   CHART_UPDATE_SUCCEEDED. The reducer's inlined action handler for 
CHART_UPDATE_SUCCEEDED is
   defined in chartReducer.ts lines 64-67 (the actionHandlers mapping); inspect
   chartReducer.ts:64-67 where the handler sets the single property "form_data:
   state.latestQueryFormData,".
   
   3. The actionHandlers are applied to the per-chart state entry by the 
reducer code path
   that uses actionHandlers (the reducer later applies actionHandlers to 
charts[action.key]).
   Concretely, CHART_UPDATE_SUCCEEDED's handler at chartReducer.ts:64-67 writes 
only
   form_data on the chart state but does not update the canonical 
latestQueryFormData field
   used elsewhere. Because Tab B's charts[chartId].latestQueryFormData was not 
updated from
   Tab A (the cross-tab update flow did not occur), setting only form_data here 
leaves
   latestQueryFormData stale.
   
   4. Observe the user-visible result: after Force Refresh in Tab B the chart 
rendering logic
   that reads latestQueryFormData continues to use the stale metric/config and 
displays stale
   BigNumber data. This reproduces the bug reported in the PR description.
   ```
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/components/Chart/chartReducer.ts
   **Line:** 67:67
   **Comment:**
        *Logic Error: In the CHART_UPDATE_SUCCEEDED handler a snake_case 
property `form_data` is being set from `state.latestQueryFormData`. The rest of 
this reducer and the typed `ChartState` use `latestQueryFormData` (camelCase). 
Introducing `form_data` creates an inconsistent state shape and consumers that 
read `latestQueryFormData` will still see stale data; replace the snake_case 
property with `latestQueryFormData` so the reducer consistently updates the 
canonical field.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   ```
   </details>



##########
superset-frontend/src/explore/actions/saveModalActions.ts:
##########
@@ -64,6 +64,39 @@ export function saveSliceSuccess(data: 
Partial<QueryFormData>) {
   return { type: SAVE_SLICE_SUCCESS, data };
 }
 
+// Broadcast chart updates to other tabs
+let chartUpdateChannel: BroadcastChannel | null = null;
+
+function getChartUpdateChannel(): BroadcastChannel | null {
+  if (typeof BroadcastChannel === 'undefined') {
+    return null;
+  }
+
+  if (!chartUpdateChannel) {
+    chartUpdateChannel = new BroadcastChannel('superset_chart_updates');

Review Comment:
   **Suggestion:** Creating a BroadcastChannel can throw (for example in some 
browsers' private modes or restricted environments). The current code 
constructs a new BroadcastChannel without guarding against exceptions, which 
can bubble up and crash save flows; wrap the constructor in a try/catch and 
gracefully disable broadcasting on failure. [possible bug]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Save/update chart flow can fail in some browsers.
   - ⚠️ Explore save UI reports failure incorrectly.
   - ⚠️ Inter-tab update notifications disabled silently.
   ```
   </details>
   
   ```suggestion
       try {
         chartUpdateChannel = new BroadcastChannel('superset_chart_updates');
       } catch (err) {
         // Some browsers (e.g., Safari private mode or restricted 
environments) throw when constructing BroadcastChannel.
         // Disable broadcasting instead of letting the exception propagate and 
break save operations.
         // eslint-disable-next-line no-console
         console.warn('BroadcastChannel unavailable, disabling chart update 
broadcasts:', err);
         chartUpdateChannel = null;
       }
   ```
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. In the browser, open the Explore UI and edit a chart. Trigger save which 
invokes
   updateSlice in superset-frontend/src/explore/actions/saveModalActions.ts 
(function export
   const updateSlice) — flow starts at the try block after SupersetClient.put() 
and reaches
   the broadcast call at lines 290-293.
   
   2. updateSlice calls broadcastChartUpdate(sliceId, formData) which calls
   getChartUpdateChannel() defined in
   superset-frontend/src/explore/actions/saveModalActions.ts at lines 70-80.
   
   3. In restrictive environments (e.g., Safari private mode), the 
BroadcastChannel
   constructor at line 76 (chartUpdateChannel = new
   BroadcastChannel('superset_chart_updates')) can throw a DOMException.
   
   4. Because getChartUpdateChannel() does not catch that exception, the thrown 
error bubbles
   back into updateSlice's try block, is caught by its catch, dispatches 
SAVE_SLICE_FAILED,
   and rethrows — causing the UI to report save failure even though the server 
request
   already succeeded. Wrapping the constructor in try/catch prevents the 
exception from
   aborting the save flow.
   ```
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/explore/actions/saveModalActions.ts
   **Line:** 76:76
   **Comment:**
        *Possible Bug: Creating a BroadcastChannel can throw (for example in 
some browsers' private modes or restricted environments). The current code 
constructs a new BroadcastChannel without guarding against exceptions, which 
can bubble up and crash save flows; wrap the constructor in a try/catch and 
gracefully disable broadcasting on failure.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   ```
   </details>



##########
superset-frontend/src/components/Chart/chartReducers.test.js:
##########
@@ -19,45 +19,136 @@
 import chartReducer, { chart } from 'src/components/Chart/chartReducer';
 import * as actions from 'src/components/Chart/chartAction';
 
-// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from 
describe blocks
-describe('chart reducers', () => {
-  const chartKey = 1;
-  let testChart;
-  let charts;
-  beforeEach(() => {
-    testChart = {
-      ...chart,
-      id: chartKey,
-    };
-    charts = { [chartKey]: testChart };
+const CHART_KEY = 1;
+
+// Helper function to create initial chart state
+const createChartState = (overrides = {}) => ({
+  [CHART_KEY]: {
+    ...chart,
+    id: CHART_KEY,
+    ...overrides,
+  },
+});
+
+test('chartReducer updates endtime and status when chart update is stopped', 
() => {
+  const charts = createChartState();
+
+  const newState = chartReducer(charts, actions.chartUpdateStopped(CHART_KEY));
+
+  expect(newState[CHART_KEY].chartUpdateEndTime).toBeGreaterThan(0);
+  expect(newState[CHART_KEY].chartStatus).toEqual('stopped');
+});
+
+test('chartReducer updates endtime and status when chart update fails with 
timeout', () => {
+  const charts = createChartState();
+
+  const newState = chartReducer(
+    charts,
+    actions.chartUpdateFailed(
+      {
+        statusText: 'timeout',
+        error: 'Request timed out',
+        errors: [
+          {
+            error_type: 'FRONTEND_TIMEOUT_ERROR',
+            extra: { timeout: 1 },
+            level: 'error',
+            message: 'Request timed out',
+          },
+        ],
+      },
+      CHART_KEY,
+    ),
+  );
+
+  expect(newState[CHART_KEY].chartUpdateEndTime).toBeGreaterThan(0);
+  expect(newState[CHART_KEY].chartStatus).toEqual('failed');
+});
+
+test('chartReducer updates form_data from latestQueryFormData on chart update 
success', () => {
+  const latestQueryFormData = {
+    datasource: '1__table',
+    viz_type: 'big_number_total',
+    slice_id: CHART_KEY,
+    metric: { aggregate: 'COUNT_DISTINCT' },
+  };
+
+  const queriesResponse = [
+    {
+      data: [{ 'COUNT_DISTINCT(column)': 42 }],
+      colnames: ['COUNT_DISTINCT(column)'],
+      coltypes: [0],
+    },
+  ];
+
+  const charts = createChartState({
+    latestQueryFormData,
+    form_data: { datasource: '1__table', viz_type: 'big_number_total' },
   });
 
-  test('should update endtime on fail', () => {
-    const newState = chartReducer(charts, 
actions.chartUpdateStopped(chartKey));
-    expect(newState[chartKey].chartUpdateEndTime).toBeGreaterThan(0);
-    expect(newState[chartKey].chartStatus).toEqual('stopped');
+  const newState = chartReducer(
+    charts,
+    actions.chartUpdateSucceeded(queriesResponse, CHART_KEY),
+  );
+
+  expect(newState[CHART_KEY].chartStatus).toEqual('success');
+  expect(newState[CHART_KEY].queriesResponse).toEqual(queriesResponse);
+  expect(newState[CHART_KEY].form_data).toEqual(latestQueryFormData);
+  expect(newState[CHART_KEY].chartUpdateEndTime).toBeGreaterThan(0);
+});
+
+test('chartReducer syncs form_data with latestQueryFormData after refresh', () 
=> {
+  const oldFormData = {
+    datasource: '1__table',
+    viz_type: 'big_number_total',
+    metric: { aggregate: 'COUNT' },
+  };
+
+  const newFormData = {
+    datasource: '1__table',
+    viz_type: 'big_number_total',
+    metric: { aggregate: 'MAX' },
+  };
+
+  const charts = createChartState({
+    latestQueryFormData: newFormData,
+    form_data: oldFormData,
   });
 
-  test('should update endtime on timeout', () => {
-    const newState = chartReducer(
-      charts,
-      actions.chartUpdateFailed(
-        {
-          statusText: 'timeout',
-          error: 'Request timed out',
-          errors: [
-            {
-              error_type: 'FRONTEND_TIMEOUT_ERROR',
-              extra: { timeout: 1 },
-              level: 'error',
-              message: 'Request timed out',
-            },
-          ],
-        },
-        chartKey,
-      ),
-    );
-    expect(newState[chartKey].chartUpdateEndTime).toBeGreaterThan(0);
-    expect(newState[chartKey].chartStatus).toEqual('failed');
+  const newState = chartReducer(
+    charts,
+    actions.chartUpdateSucceeded([], CHART_KEY),
+  );
+
+  expect(newState[CHART_KEY].form_data).toEqual(newFormData);
+  expect(newState[CHART_KEY].form_data.metric.aggregate).toBe('MAX');
+});
+
+test('UPDATE_CHART_FORM_DATA updates both form_data and latestQueryFormData 
for cross-tab sync', () => {
+  const oldFormData = {
+    datasource: '1__table',
+    viz_type: 'big_number_total',
+    metric: { aggregate: 'MIN' },
+  };
+
+  const updatedFormData = {
+    datasource: '1__table',
+    viz_type: 'big_number_total',
+    metric: { aggregate: 'MAX' },
+  };
+
+  const charts = createChartState({
+    form_data: oldFormData,
+    latestQueryFormData: oldFormData,
   });
+
+  const newState = chartReducer(charts, {
+    type: actions.UPDATE_CHART_FORM_DATA,
+    chartId: CHART_KEY,
+    formData: updatedFormData,

Review Comment:
   **Suggestion:** The dispatched action for updating chart form data uses 
`chartId` and `formData` property names, but the reducer/action creators in 
this filebase use `key` and `form_data` conventions; using the wrong property 
names will cause the reducer to ignore the payload and the test to fail. 
Dispatch the action with the expected property names (`key` and `form_data`). 
[logic error]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Unit test fails in chartReducers.test.js.
   - ❌ CI job can fail blocking merges.
   - ⚠️ Runtime UI unaffected if action creators used.
   ```
   </details>
   
   ```suggestion
       key: CHART_KEY,
       form_data: updatedFormData,
   ```
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Run the unit tests for charts: execute Jest for file
   `superset-frontend/src/components/Chart/chartReducers.test.js`. The failing 
test block is
   defined starting at line 127 and the specific action dispatch is at lines 
145-149 (`const
   newState = chartReducer(...`).
   
   2. The test constructs initial state via `createChartState()` at
   `superset-frontend/src/components/Chart/chartReducers.test.js:25-31`, 
producing an object
   keyed by `CHART_KEY`.
   
   3. The test dispatches a raw action object (not using an action creator) at
   `...chartReducers.test.js:145-149` with properties `chartId` and `formData`. 
Because the
   codebase consistently passes `key`/`form_data` in actions (see action 
creators that
   include `key` at 
`superset-frontend/src/components/Chart/chartAction.js:58-70`), the
   reducer will not find the expected `action.key`/`action.form_data` fields.
   
   4. The reducer returns an unchanged chart entry, so the assertions 
immediately after
   (expectations at `...chartReducers.test.js:151-153`) observe the old 
`form_data` and the
   test fails. This reproduces deterministically by running the test file as-is.
   ```
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/components/Chart/chartReducers.test.js
   **Line:** 147:148
   **Comment:**
        *Logic Error: The dispatched action for updating chart form data uses 
`chartId` and `formData` property names, but the reducer/action creators in 
this filebase use `key` and `form_data` conventions; using the wrong property 
names will cause the reducer to ignore the payload and the test to fail. 
Dispatch the action with the expected property names (`key` and `form_data`).
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   ```
   </details>



##########
superset-frontend/src/explore/actions/saveModalActions.test.ts:
##########
@@ -727,3 +727,143 @@ describe('getSlicePayload', () => {
     });
   });
 });
+
+// Helper function to set up broadcast channel listener
+const setupBroadcastListener = () => {
+  const receivedMessages: any[] = [];
+  const MockBroadcastChannel = global.BroadcastChannel as any;
+  const listener = new MockBroadcastChannel('superset_chart_updates');
+  listener.onmessage = (event: { data: any }) => {
+    receivedMessages.push(event.data);
+  };
+  return { receivedMessages, listener };
+};

Review Comment:
   **Suggestion:** Duplicate helper declaration: `setupBroadcastListener` is 
defined twice in this file (the PR added a second declaration). Redeclaring the 
same `const` in the same scope will cause a runtime/parse error when the test 
file is loaded; remove the duplicated definition (keep only one helper). 
[possible bug]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Test file fails to load blocking Jest runs.
   - ❌ CI job fails on this test file.
   - ⚠️ Developers cannot run these local tests.
   - ⚠️ Duplicate is accidental, not intentional design.
   ```
   </details>
   
   ```suggestion
   
   ```
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Open the test file at 
superset-frontend/src/explore/actions/saveModalActions.test.ts
   and observe the helper defined at lines 731-740 (the snippet above).
   
   2. Inspect the same file and find the earlier helper declaration with the 
same identifier
   (setupBroadcastListener) that already exists elsewhere in the same file (the 
file contains
   a second identical declaration later in the file). The duplicate definitions 
are in the
   same top-level scope of this test file.
   
   3. Run the tests (e.g., npm test --
   superset-frontend/src/explore/actions/saveModalActions.test.ts or the 
repository's Jest
   runner). When Node/TypeScript compiles/loads the test file, the 
parser/runner will raise a
   runtime/syntax error about the identifier already being declared (duplicate 
const) because
   setupBroadcastListener is declared twice in the same scope.
   
   4. Result: the test file fails to load and the test run aborts with a
   SyntaxError/ReferenceError. Fix: remove one of the duplicate declarations so 
the helper is
   declared exactly once.
   ```
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/explore/actions/saveModalActions.test.ts
   **Line:** 731:740
   **Comment:**
        *Possible Bug: Duplicate helper declaration: `setupBroadcastListener` 
is defined twice in this file (the PR added a second declaration). Redeclaring 
the same `const` in the same scope will cause a runtime/parse error when the 
test file is loaded; remove the duplicated definition (keep only one helper).
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   ```
   </details>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to