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


##########
superset-frontend/src/core/dashboard/index.test.ts:
##########
@@ -0,0 +1,220 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// ---------------------------------------------------------------------------
+// Captured listeners — allows tests to trigger action notifications manually.
+// ---------------------------------------------------------------------------
+type ListenerEntry = {
+  predicate: (action: { type: string }) => boolean;
+  effect: (action: { type: string }) => void;
+};
+
+const capturedListeners: ListenerEntry[] = [];
+
+// Declared before jest.mock so the factory closure can reference it.
+let mockState: Record<string, unknown>;
+
+jest.mock('src/views/store', () => ({
+  store: { getState: () => mockState, dispatch: jest.fn() },
+  listenerMiddleware: {
+    startListening: (opts: {
+      predicate: (action: { type: string }) => boolean;
+      effect: (action: { type: string }) => void;
+    }) => {
+      const entry = { predicate: opts.predicate, effect: opts.effect };
+      capturedListeners.push(entry);
+      return () => {
+        const idx = capturedListeners.indexOf(entry);
+        if (idx !== -1) capturedListeners.splice(idx, 1);
+      };
+    },
+  },
+}));
+
+jest.mock('../navigation', () => ({
+  navigation: { getPageType: jest.fn(() => 'dashboard') },
+}));
+
+function dispatch(actionType: string) {
+  const action = { type: actionType };
+  capturedListeners
+    .filter(e => e.predicate(action))
+    .forEach(e => e.effect(action));
+}
+
+// Imported after mocks
+// eslint-disable-next-line import/first
+import { dashboard } from './index';
+
+function makeState(
+  overrides: Partial<{
+    dashboardInfo: unknown;
+    nativeFilters: unknown;
+    dataMask: unknown;
+    sliceEntities: unknown;
+    dashboardLayout: unknown;
+  }> = {},
+) {
+  return {
+    dashboardInfo: { id: 1, dashboard_title: 'Sales', slug: 'sales' },
+    nativeFilters: { filters: { 'filter-1': { name: 'Region' } } },
+    dataMask: { 'filter-1': { filterState: { value: ['West'] } } },
+    sliceEntities: { slices: {} },
+    dashboardLayout: { present: {} },
+    ...overrides,
+  };
+}
+
+beforeEach(() => {
+  mockState = makeState();
+});
+
+afterEach(() => {
+  capturedListeners.length = 0;
+  jest.restoreAllMocks();
+});
+
+test('getCurrentDashboard returns undefined when not on dashboard page', () => 
{
+  const { navigation } = jest.requireMock('../navigation');
+  (navigation.getPageType as jest.Mock).mockReturnValueOnce('explore');
+  expect(dashboard.getCurrentDashboard()).toBeUndefined();
+});
+
+test('getCurrentDashboard returns undefined when dashboardInfo is absent', () 
=> {
+  mockState = makeState({ dashboardInfo: undefined });
+  expect(dashboard.getCurrentDashboard()).toBeUndefined();
+});
+
+test('getCurrentDashboard returns dashboard context with active filters', () 
=> {
+  expect(dashboard.getCurrentDashboard()).toEqual({
+    dashboardId: 1,
+    title: 'Sales',
+    filters: [{ filterId: 'filter-1', label: 'Region', value: ['West'] }],
+    // No charts on the (empty) layout fixture.
+    charts: [],
+  });
+});
+
+test('getCurrentDashboard reports charts placed on the dashboard layout', () 
=> {
+  mockState = makeState({
+    sliceEntities: {
+      slices: {
+        42: {
+          slice_name: 'Revenue by Region',
+          viz_type: 'echarts_timeseries_bar',
+          datasource_id: 7,
+          datasource_name: 'cleaned_sales',
+        },
+      },
+    },
+    dashboardLayout: {
+      present: {
+        'CHART-abc': { id: 'CHART-abc', type: 'CHART', meta: { chartId: 42 } },
+        // A chart id with no matching slice entity still appears, with blanks.
+        'CHART-def': { id: 'CHART-def', type: 'CHART', meta: { chartId: 99 } },
+        // Non-chart components are ignored.
+        'TAB-xyz': { id: 'TAB-xyz', type: 'TAB', meta: {} },
+      },
+    },
+  });
+
+  expect(dashboard.getCurrentDashboard()?.charts).toEqual([
+    {
+      chartId: 42,
+      chartName: 'Revenue by Region',
+      vizType: 'echarts_timeseries_bar',
+      datasourceId: 7,
+      datasourceName: 'cleaned_sales',
+      isVisible: true,
+    },
+    {
+      chartId: 99,
+      chartName: '',
+      vizType: '',
+      datasourceId: null,
+      datasourceName: null,
+      isVisible: true,
+    },
+  ]);
+});
+
+test('getCurrentDashboard excludes filters with null value', () => {
+  mockState = makeState({
+    dataMask: { 'filter-1': { filterState: { value: null } } },
+  });
+  expect(dashboard.getCurrentDashboard()?.filters).toHaveLength(0);
+});
+
+test('getCurrentDashboard excludes dataMask entries not in nativeFilters', () 
=> {
+  mockState = makeState({
+    dataMask: { 'chart-filter': { filterState: { value: 'foo' } } },
+  });
+  expect(dashboard.getCurrentDashboard()?.filters).toHaveLength(0);
+});
+
+test('filter array value is a defensive copy — mutation does not affect Redux 
state', () => {
+  const ctx = dashboard.getCurrentDashboard();
+  const original = [
+    ...(mockState as any).dataMask['filter-1'].filterState.value,

Review Comment:
   **Suggestion:** Replace the `any` cast with a concrete state shape for the 
mocked Redux data (or a narrowly scoped interface) so the test accesses typed 
properties without using `any`. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   The final file still contains a TypeScript `any` cast in newly added test 
code.
   This directly violates the rule that modified TypeScript/TSX code should not 
use `any` when a concrete or narrower type can be used.
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=fb8201d64adb458096956ee0f40f6d09&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=fb8201d64adb458096956ee0f40f6d09&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/core/dashboard/index.test.ts
   **Line:** 174:174
   **Comment:**
        *Custom Rule: Replace the `any` cast with a concrete state shape for 
the mocked Redux data (or a narrowly scoped interface) so the test accesses 
typed properties without using `any`.
   
   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.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41006&comment_hash=86539dd5b204af5195d85b4cc52b191fc6f0ffd77b2c76d96842974c2611af22&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41006&comment_hash=86539dd5b204af5195d85b4cc52b191fc6f0ffd77b2c76d96842974c2611af22&reaction=dislike'>👎</a>



##########
superset-frontend/src/core/dashboard/index.test.ts:
##########
@@ -0,0 +1,220 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// ---------------------------------------------------------------------------
+// Captured listeners — allows tests to trigger action notifications manually.
+// ---------------------------------------------------------------------------
+type ListenerEntry = {
+  predicate: (action: { type: string }) => boolean;
+  effect: (action: { type: string }) => void;
+};
+
+const capturedListeners: ListenerEntry[] = [];
+
+// Declared before jest.mock so the factory closure can reference it.
+let mockState: Record<string, unknown>;
+
+jest.mock('src/views/store', () => ({
+  store: { getState: () => mockState, dispatch: jest.fn() },
+  listenerMiddleware: {
+    startListening: (opts: {
+      predicate: (action: { type: string }) => boolean;
+      effect: (action: { type: string }) => void;
+    }) => {
+      const entry = { predicate: opts.predicate, effect: opts.effect };
+      capturedListeners.push(entry);
+      return () => {
+        const idx = capturedListeners.indexOf(entry);
+        if (idx !== -1) capturedListeners.splice(idx, 1);
+      };
+    },
+  },
+}));
+
+jest.mock('../navigation', () => ({
+  navigation: { getPageType: jest.fn(() => 'dashboard') },
+}));
+
+function dispatch(actionType: string) {
+  const action = { type: actionType };
+  capturedListeners
+    .filter(e => e.predicate(action))
+    .forEach(e => e.effect(action));
+}
+
+// Imported after mocks
+// eslint-disable-next-line import/first
+import { dashboard } from './index';
+
+function makeState(
+  overrides: Partial<{
+    dashboardInfo: unknown;
+    nativeFilters: unknown;
+    dataMask: unknown;
+    sliceEntities: unknown;
+    dashboardLayout: unknown;
+  }> = {},
+) {
+  return {
+    dashboardInfo: { id: 1, dashboard_title: 'Sales', slug: 'sales' },
+    nativeFilters: { filters: { 'filter-1': { name: 'Region' } } },
+    dataMask: { 'filter-1': { filterState: { value: ['West'] } } },
+    sliceEntities: { slices: {} },
+    dashboardLayout: { present: {} },
+    ...overrides,
+  };
+}
+
+beforeEach(() => {
+  mockState = makeState();
+});
+
+afterEach(() => {
+  capturedListeners.length = 0;
+  jest.restoreAllMocks();
+});
+
+test('getCurrentDashboard returns undefined when not on dashboard page', () => 
{
+  const { navigation } = jest.requireMock('../navigation');
+  (navigation.getPageType as jest.Mock).mockReturnValueOnce('explore');
+  expect(dashboard.getCurrentDashboard()).toBeUndefined();
+});
+
+test('getCurrentDashboard returns undefined when dashboardInfo is absent', () 
=> {
+  mockState = makeState({ dashboardInfo: undefined });
+  expect(dashboard.getCurrentDashboard()).toBeUndefined();
+});
+
+test('getCurrentDashboard returns dashboard context with active filters', () 
=> {
+  expect(dashboard.getCurrentDashboard()).toEqual({
+    dashboardId: 1,
+    title: 'Sales',
+    filters: [{ filterId: 'filter-1', label: 'Region', value: ['West'] }],
+    // No charts on the (empty) layout fixture.
+    charts: [],
+  });
+});
+
+test('getCurrentDashboard reports charts placed on the dashboard layout', () 
=> {
+  mockState = makeState({
+    sliceEntities: {
+      slices: {
+        42: {
+          slice_name: 'Revenue by Region',
+          viz_type: 'echarts_timeseries_bar',
+          datasource_id: 7,
+          datasource_name: 'cleaned_sales',
+        },
+      },
+    },
+    dashboardLayout: {
+      present: {
+        'CHART-abc': { id: 'CHART-abc', type: 'CHART', meta: { chartId: 42 } },
+        // A chart id with no matching slice entity still appears, with blanks.
+        'CHART-def': { id: 'CHART-def', type: 'CHART', meta: { chartId: 99 } },
+        // Non-chart components are ignored.
+        'TAB-xyz': { id: 'TAB-xyz', type: 'TAB', meta: {} },
+      },
+    },
+  });
+
+  expect(dashboard.getCurrentDashboard()?.charts).toEqual([
+    {
+      chartId: 42,
+      chartName: 'Revenue by Region',
+      vizType: 'echarts_timeseries_bar',
+      datasourceId: 7,
+      datasourceName: 'cleaned_sales',
+      isVisible: true,
+    },
+    {
+      chartId: 99,
+      chartName: '',
+      vizType: '',
+      datasourceId: null,
+      datasourceName: null,
+      isVisible: true,
+    },
+  ]);
+});
+
+test('getCurrentDashboard excludes filters with null value', () => {
+  mockState = makeState({
+    dataMask: { 'filter-1': { filterState: { value: null } } },
+  });
+  expect(dashboard.getCurrentDashboard()?.filters).toHaveLength(0);
+});
+
+test('getCurrentDashboard excludes dataMask entries not in nativeFilters', () 
=> {
+  mockState = makeState({
+    dataMask: { 'chart-filter': { filterState: { value: 'foo' } } },
+  });
+  expect(dashboard.getCurrentDashboard()?.filters).toHaveLength(0);
+});
+
+test('filter array value is a defensive copy — mutation does not affect Redux 
state', () => {
+  const ctx = dashboard.getCurrentDashboard();
+  const original = [
+    ...(mockState as any).dataMask['filter-1'].filterState.value,
+  ];
+  (ctx!.filters[0].value as string[]).push('East');
+  expect((mockState as any).dataMask['filter-1'].filterState.value).toEqual(

Review Comment:
   **Suggestion:** Remove the `any` cast in this assertion by reusing the same 
concrete mocked state type used elsewhere in the test, keeping property access 
type-safe. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   The file uses `as any` in the assertion path, which is a direct instance of 
the banned `any` type in modified TypeScript test code.
   This is a real violation of the custom rule.
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=abfd515f0a1044efaaf898a9b829d0f2&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=abfd515f0a1044efaaf898a9b829d0f2&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/core/dashboard/index.test.ts
   **Line:** 177:177
   **Comment:**
        *Custom Rule: Remove the `any` cast in this assertion by reusing the 
same concrete mocked state type used elsewhere in the test, keeping property 
access type-safe.
   
   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.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41006&comment_hash=63d9416a9f844007b13fa58fe0ec3ae2b456d4fc4fe10b6562119521cdf740f7&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41006&comment_hash=63d9416a9f844007b13fa58fe0ec3ae2b456d4fc4fe10b6562119521cdf740f7&reaction=dislike'>👎</a>



##########
superset-frontend/src/core/dashboard/index.ts:
##########
@@ -0,0 +1,123 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Host-internal implementation of the `dashboard` namespace.
+ *
+ * Wraps Redux dashboardInfo and dataMask state and normalizes them into the
+ * stable `DashboardContext` contract. Extensions must not depend on the Redux
+ * slice structure directly.
+ */
+
+import type { dashboard as dashboardApi } from '@apache-superset/core';
+import type { DataMaskStateWithId } from '@superset-ui/core';
+import { HYDRATE_DASHBOARD } from 'src/dashboard/actions/hydrate';
+import {
+  UPDATE_DATA_MASK,
+  SET_DATA_MASK_FOR_FILTER_CHANGES_COMPLETE,
+} from 'src/dataMask/actions';
+import { store, RootState } from 'src/views/store';
+import { AnyListenerPredicate } from '@reduxjs/toolkit';
+import getChartIdsFromLayout from 'src/dashboard/util/getChartIdsFromLayout';
+import { createActionListener } from '../utils';
+import { navigation } from '../navigation';
+
+type DashboardContext = dashboardApi.DashboardContext;
+type FilterValue = dashboardApi.FilterValue;
+type ChartSummary = NonNullable<DashboardContext['charts']>[number];
+
+function buildChartSummaries(state: RootState): ChartSummary[] {
+  const slices = state.sliceEntities?.slices ?? {};
+  const layout = state.dashboardLayout?.present ?? {};
+
+  // Only charts actually placed on the dashboard layout — `slices` can also
+  // hold entities that are not on the current dashboard.
+  return getChartIdsFromLayout(layout).map(chartId => {
+    const slice = slices[chartId];
+    return {
+      chartId,
+      chartName: slice?.slice_name ?? '',
+      vizType: slice?.viz_type ?? '',
+      datasourceId: slice?.datasource_id ?? null,
+      datasourceName: slice?.datasource_name ?? null,
+      // Tab-accurate visibility is a deferred phase; every chart on the
+      // dashboard is reported visible for now.
+      isVisible: true,
+    };
+  });
+}
+
+function buildDashboardContext(): DashboardContext | undefined {
+  if (navigation.getPageType() !== 'dashboard') return undefined;
+  // `store.getState()` is already typed as RootState, so the slices below are
+  // read with their real types — the host owns this normalization and must
+  // stay type-safe against slice reshapes.
+  const state = store.getState();
+  const info = state.dashboardInfo;
+  if (!info?.id) return undefined;
+
+  const nativeFilters = state.nativeFilters?.filters ?? {};
+  const dataMask: DataMaskStateWithId = state.dataMask ?? {};
+
+  const filters: FilterValue[] = Object.entries(dataMask)
+    .filter(([id, mask]) => {
+      if (!(id in nativeFilters)) return false;
+      const value = mask?.filterState?.value;
+      return value !== null && value !== undefined;
+    })
+    .map(([id, mask]) => {
+      const raw = mask.filterState?.value;
+      return {
+        filterId: id,
+        label: nativeFilters[id]?.name ?? id,
+        value: Array.isArray(raw) ? [...raw] : raw,
+      };
+    });
+
+  return {
+    dashboardId: info.id,
+    title: info.dashboard_title ?? info.slug ?? String(info.id),
+    filters,
+    charts: buildChartSummaries(state),
+  };
+}
+
+const dashboardChangePredicate: AnyListenerPredicate<RootState> = action =>
+  action.type === HYDRATE_DASHBOARD ||
+  action.type === UPDATE_DATA_MASK ||
+  action.type === SET_DATA_MASK_FOR_FILTER_CHANGES_COMPLETE;
+
+const getCurrentDashboard: typeof dashboardApi.getCurrentDashboard = () =>
+  buildDashboardContext();
+
+const onDidChangeDashboard: typeof dashboardApi.onDidChangeDashboard = (
+  listener: (ctx: DashboardContext) => void,
+  thisArgs?: any,

Review Comment:
   **Suggestion:** Replace the `any` annotation on `thisArgs` with a concrete 
type from the callback context (or a narrow generic/`unknown` if truly 
unconstrained) to preserve type safety. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   The new TypeScript file introduces `thisArgs?: any`, which directly violates 
the rule against using `any` in new or modified TypeScript/TSX code. A concrete 
type, reusable existing type, or `unknown`/narrower generic should be used 
instead.
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=ba476a532d9e436c9d79db68569e4b21&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=ba476a532d9e436c9d79db68569e4b21&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/core/dashboard/index.ts
   **Line:** 111:111
   **Comment:**
        *Custom Rule: Replace the `any` annotation on `thisArgs` with a 
concrete type from the callback context (or a narrow generic/`unknown` if truly 
unconstrained) to preserve type safety.
   
   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.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41006&comment_hash=2683271b1c7c6aa8cccc948345ba2e3f04bf15fcdf0373f8287b97d3e1697057&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41006&comment_hash=2683271b1c7c6aa8cccc948345ba2e3f04bf15fcdf0373f8287b97d3e1697057&reaction=dislike'>👎</a>



##########
superset-frontend/src/core/explore/index.ts:
##########
@@ -0,0 +1,92 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Host-internal implementation of the `explore` namespace.
+ *
+ * Wraps Redux explore state and normalizes it into the stable `ChartContext`
+ * contract. Extensions must not depend on the Redux slice structure directly.
+ */
+
+import type { explore as exploreApi } from '@apache-superset/core';
+import { HYDRATE_EXPLORE } from 'src/explore/actions/hydrateExplore';
+import {
+  CREATE_NEW_SLICE,
+  SET_FORM_DATA,
+  SLICE_UPDATED,
+  UPDATE_CHART_TITLE,
+} from 'src/explore/actions/exploreActions';
+import { SET_DATASOURCE } from 'src/explore/actions/datasourcesActions';
+import { store, RootState } from 'src/views/store';
+import { AnyListenerPredicate } from '@reduxjs/toolkit';
+import { createActionListener } from '../utils';
+import { navigation } from '../navigation';
+
+type ChartContext = exploreApi.ChartContext;
+
+function buildChartContext(): ChartContext | undefined {
+  if (navigation.getPageType() !== 'explore') return undefined;
+  // `store.getState()` is already RootState; read the typed `explore` slice
+  // directly rather than casting it away.
+  const state = store.getState();
+  const exploreState = state.explore;
+  if (!exploreState) return undefined;
+
+  const { slice, datasource, controls } = exploreState;
+  const vizType: string =
+    (controls?.viz_type?.value as string) ??
+    exploreState.form_data?.viz_type ??
+    '';
+
+  return {
+    chartId: slice?.slice_id ?? null,
+    chartName: exploreState.sliceName ?? slice?.slice_name ?? null,
+    vizType,
+    datasourceId: datasource?.id ?? null,
+    datasourceName:
+      datasource?.table_name ?? datasource?.datasource_name ?? null,
+  };
+}
+
+const exploreChangePredicate: AnyListenerPredicate<RootState> = action =>
+  action.type === HYDRATE_EXPLORE ||
+  action.type === SET_FORM_DATA ||
+  action.type === UPDATE_CHART_TITLE ||
+  action.type === SET_DATASOURCE ||
+  action.type === CREATE_NEW_SLICE ||
+  action.type === SLICE_UPDATED;
+
+const getCurrentChart: typeof exploreApi.getCurrentChart = () =>
+  buildChartContext();
+
+const onDidChangeChart: typeof exploreApi.onDidChangeChart = (
+  listener: (ctx: ChartContext) => void,
+  thisArgs?: any,

Review Comment:
   **Suggestion:** Replace the `any` type on `thisArgs` with a safer type (for 
example `unknown` or the existing event API argument type) so the new code does 
not introduce an untyped escape hatch. [custom_rule]
   
   **Severity Level:** Minor ⚠️
   <details>
   <summary><b>Why it matters? 🤔 </b></summary>
   
   The final file still introduces a TypeScript `any` usage on the new 
`thisArgs` parameter. This directly violates the rule against new or modified 
TypeScript/TSX code using `any`, and a narrower or existing type could be used 
instead.
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=08091a42dfa449f7846bd2b35693afc4&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=08091a42dfa449f7846bd2b35693afc4&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-frontend/src/core/explore/index.ts
   **Line:** 80:80
   **Comment:**
        *Custom Rule: Replace the `any` type on `thisArgs` with a safer type 
(for example `unknown` or the existing event API argument type) so the new code 
does not introduce an untyped escape hatch.
   
   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.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41006&comment_hash=243c977917f4b1a9da80b657621b6330eb28f6025a085f2077839d010d1a8b6a&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41006&comment_hash=243c977917f4b1a9da80b657621b6330eb28f6025a085f2077839d010d1a8b6a&reaction=dislike'>👎</a>



-- 
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