bito-code-review[bot] commented on code in PR #37625:
URL: https://github.com/apache/superset/pull/37625#discussion_r2758861841


##########
superset-frontend/packages/superset-ui-core/src/components/CronPicker/CronPicker.stories.tsx:
##########
@@ -28,7 +28,7 @@ export default {
 };
 
 export const InteractiveCronPicker = (props: CronProps) => {
-  // @ts-ignore
+  // @ts-expect-error

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Fix TypeScript errors instead of suppressing</b></div>
   <div id="fix">
   
   The @ts-expect-error suppresses TypeScript errors for incorrect useRef 
typing and an invalid method call. Instead of suppressing, fix the root issues: 
use InputRef type, make Input controlled with value prop, and remove the 
non-existent setValue method call.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ```
    -import { Input } from '../Input';
    +import { Input, InputRef } from '../Input';
    @@ -30,5 +30,4 @@
    export const InteractiveCronPicker = (props: CronProps) => {
    -  // @ts-expect-error
      const inputRef = useRef<InputRef>(null);
      const [value, setValue] = useState(props.value);
      const customSetValue = useCallback(
        (newValue: string) => {
          setValue(newValue);
    -      inputRef.current?.setValue(newValue);
        },
        [inputRef],
      );
    @@ -43,4 +42,5 @@
      return (
        <div>
          <Input
    +        value={value}
            ref={inputRef}
            onBlur={event => {
              setValue(event.target.value);
            }}
            onChange={e => setValue(e.target.value || '')}
          />
   ```
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #52392d</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a 
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/dashboard/util/dropOverflowsParent.test.ts:
##########
@@ -17,9 +17,15 @@
  * under the License.
  */
 // Layout type not directly used in tests - using object shapes for test data
-import dropOverflowsParent, {
-  type DropResult,
-} from 'src/dashboard/util/dropOverflowsParent';
+import dropOverflowsParent from 'src/dashboard/util/dropOverflowsParent';
+import type { DropResult } from 
'src/dashboard/components/dnd/dragDroppableConfig';
+
+// Test data uses minimal shapes - cast to satisfy DropResult interface
+const mockDropResult = (
+  source: { id: string },
+  destination: { id: string },
+  dragging: { id: string },
+): DropResult => ({ source, destination, dragging }) as unknown as DropResult;

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Invalid test mock alters behavior</b></div>
   <div id="fix">
   
   The mockDropResult function produces incomplete DropResult objects that omit 
required fields like 'type' and 'index', altering test data from the original 
direct object literals. This changes behavior in functions like 
getComponentWidthFromDrop, which accesses dragging.type—now undefined in tests 
expecting CHART_TYPE—potentially causing tests to fail or pass incorrectly.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #52392d</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a 
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/features/reports/ReportModal/index.tsx:
##########
@@ -207,11 +207,11 @@ function ReportModal({
 
     setCurrentReport({ isSubmitting: true, error: undefined });
     try {
-      if (isEditMode) {
+      if (isEditMode && currentReport.id) {
         await dispatch(
           editReport(currentReport.id, newReportValues as ReportObject),
         );
-      } else {
+      } else if (!isEditMode) {

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Incorrect conditional logic</b></div>
   <div id="fix">
   
   The conditional logic here changes the behavior so that if isEditMode is 
true but currentReport.id is falsy, no dispatch occurs and the modal just 
closes. This could cause silent failure where the user thinks the report was 
saved but it wasn't. Consider changing to '} else {' to match the original 
intent of edit if possible, add otherwise.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ````suggestion
         } else {
   ````
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #52392d</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a 
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/transformProps.ts:
##########
@@ -217,7 +217,7 @@ export default function transformProps(
 
   if (data.length > 0) {
     const reversedData = [...sortedData].reverse();
-    // @ts-ignore
+    // @ts-expect-error

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Type Mismatch in TimeSeriesDatum</b></div>
   <div id="fix">
   
   The assignment to trendLineData assigns reversedData (type [number | null, 
number | null][]) but TimeSeriesDatum expects [number, number | null][], 
causing a type error suppressed here. The type should allow null timestamps to 
match actual data handling.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ```
    - export type TimeSeriesDatum = [number, number | null];
    + export type TimeSeriesDatum = [number | null, number | null];
   ```
   
   </div>
   </details>
   
   
   
   </div>
   
   <details>
   <summary><b>Citations</b></summary>
   <ul>
   
   <li>
   Rule Violated: <a 
href="https://github.com/apache/superset/blob/79e7d81/AGENTS.md#L78";>AGENTS.md:78</a>
   </li>
   
   </ul>
   </details>
   
   
   
   
   <small><i>Code Review Run #52392d</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a 
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/dashboard/actions/dashboardState.ts:
##########
@@ -0,0 +1,1517 @@
+/**
+ * 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.
+ */
+/* eslint camelcase: 0 */
+import { ActionCreators as UndoActionCreators } from 'redux-undo';
+import rison from 'rison';
+import {
+  ensureIsArray,
+  isFeatureEnabled,
+  FeatureFlag,
+  getLabelsColorMap,
+  SupersetClient,
+  getClientErrorObject,
+  getCategoricalSchemeRegistry,
+  promiseTimeout,
+  JsonObject,
+} from '@superset-ui/core';
+import {
+  addChart,
+  removeChart,
+  refreshChart,
+} from 'src/components/Chart/chartAction';
+import { logging } from '@apache-superset/core';
+import { t } from '@apache-superset/core/ui';
+import { chart as initChart } from 'src/components/Chart/chartReducer';
+import { applyDefaultFormData } from 'src/explore/store';
+import {
+  SAVE_TYPE_OVERWRITE,
+  SAVE_TYPE_OVERWRITE_CONFIRMED,
+} from 'src/dashboard/util/constants';
+import {
+  getCrossFiltersConfiguration,
+  isCrossFiltersEnabled,
+} from 'src/dashboard/util/crossFilters';
+import {
+  addSuccessToast,
+  addWarningToast,
+  addDangerToast,
+} from 'src/components/MessageToasts/actions';
+import serializeActiveFilterValues from 
'src/dashboard/util/serializeActiveFilterValues';
+import serializeFilterScopes from 'src/dashboard/util/serializeFilterScopes';
+import { getActiveFilters } from 'src/dashboard/util/activeDashboardFilters';
+import { safeStringify } from 'src/utils/safeStringify';
+import { logEvent } from 'src/logger/actions';
+import { LOG_ACTIONS_CONFIRM_OVERWRITE_DASHBOARD_METADATA } from 
'src/logger/LogUtils';
+import { isEqual } from 'lodash';
+import { navigateWithState, navigateTo } from 'src/utils/navigationUtils';
+import type { AnyAction } from 'redux';
+import type { ThunkDispatch } from 'redux-thunk';
+import { ResourceStatus } from 'src/hooks/apiResources/apiResources';
+import type { AgGridChartState } from '@superset-ui/core';
+import type { DashboardChartStates } from 'src/dashboard/types/chartState';
+import { UPDATE_COMPONENTS_PARENTS_LIST } from './dashboardLayout';
+import {
+  saveChartConfiguration,
+  dashboardInfoChanged,
+  SAVE_CHART_CONFIG_COMPLETE,
+} from './dashboardInfo';
+import { fetchDatasourceMetadata, setDatasources } from './datasources';
+import { updateDirectPathToFilter } from './dashboardFilters';
+import { SET_IN_SCOPE_STATUS_OF_FILTERS } from './nativeFilters';
+import getOverwriteItems from '../util/getOverwriteItems';
+import {
+  applyColors,
+  enforceSharedLabelsColorsArray,
+  isLabelsColorMapSynced,
+  getColorSchemeDomain,
+  getColorNamespace,
+  getFreshLabelsColorMapEntries,
+  getFreshSharedLabels,
+  getDynamicLabelsColors,
+} from '../../utils/colorScheme';
+import type { DashboardState, RootState, Slice } from '../types';
+
+type GetState = () => RootState;
+
+// Dashboard dispatch type. The base ThunkDispatch handles dashboard-specific
+// thunks. The intersection with a generic function-accepting overload allows
+// dispatching thunks from other modules (e.g. chart actions) whose RootState
+// type differs from the dashboard RootState. At runtime the Redux store
+// satisfies all module state shapes.
+interface AppDispatch extends ThunkDispatch<RootState, undefined, AnyAction> {
+  <R>(asyncAction: (...args: never[]) => R): R;
+}
+
+// ---------------------------------------------------------------------------
+// Simple action creators
+// ---------------------------------------------------------------------------
+
+export const TOGGLE_NATIVE_FILTERS_BAR = 'TOGGLE_NATIVE_FILTERS_BAR';
+
+interface ToggleNativeFiltersBarAction {
+  type: typeof TOGGLE_NATIVE_FILTERS_BAR;
+  isOpen: boolean;
+}
+
+export function toggleNativeFiltersBar(
+  isOpen: boolean,
+): ToggleNativeFiltersBarAction {
+  return { type: TOGGLE_NATIVE_FILTERS_BAR, isOpen };
+}
+
+export const SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES';
+
+interface SetUnsavedChangesAction {
+  type: typeof SET_UNSAVED_CHANGES;
+  payload: { hasUnsavedChanges: boolean };
+}
+
+export function setUnsavedChanges(
+  hasUnsavedChanges: boolean,
+): SetUnsavedChangesAction {
+  return { type: SET_UNSAVED_CHANGES, payload: { hasUnsavedChanges } };
+}
+
+export const ADD_SLICE = 'ADD_SLICE';
+
+interface AddSliceAction {
+  type: typeof ADD_SLICE;
+  slice: Slice;
+}
+
+export function addSlice(slice: Slice): AddSliceAction {
+  return { type: ADD_SLICE, slice };
+}
+
+export const REMOVE_SLICE = 'REMOVE_SLICE';
+
+interface RemoveSliceAction {
+  type: typeof REMOVE_SLICE;
+  sliceId: number;
+}
+
+export function removeSlice(sliceId: number): RemoveSliceAction {
+  return { type: REMOVE_SLICE, sliceId };
+}
+
+export const TOGGLE_FAVE_STAR = 'TOGGLE_FAVE_STAR';
+
+interface ToggleFaveStarAction {
+  type: typeof TOGGLE_FAVE_STAR;
+  isStarred: boolean;
+}
+
+export function toggleFaveStar(isStarred: boolean): ToggleFaveStarAction {
+  return { type: TOGGLE_FAVE_STAR, isStarred };
+}
+
+export function fetchFaveStar(id: number) {
+  return function fetchFaveStarThunk(dispatch: AppDispatch) {
+    return SupersetClient.get({
+      endpoint: `/api/v1/dashboard/favorite_status/?q=${rison.encode([id])}`,
+    })
+      .then(({ json }: { json: JsonObject }) => {
+        dispatch(toggleFaveStar(!!(json?.result as JsonObject[])?.[0]?.value));
+      })
+      .catch(() =>
+        dispatch(
+          addDangerToast(
+            t(
+              'There was an issue fetching the favorite status of this 
dashboard.',
+            ),
+          ),
+        ),
+      );
+  };
+}
+
+export function saveFaveStar(id: number, isStarred: boolean) {
+  return function saveFaveStarThunk(dispatch: AppDispatch) {
+    const endpoint = `/api/v1/dashboard/${id}/favorites/`;
+    const apiCall = isStarred
+      ? SupersetClient.delete({
+          endpoint,
+        })
+      : SupersetClient.post({ endpoint });
+
+    return apiCall
+      .then(() => {
+        dispatch(toggleFaveStar(!isStarred));
+      })
+      .catch(() =>
+        dispatch(
+          addDangerToast(t('There was an issue favoriting this dashboard.')),
+        ),
+      );
+  };
+}
+
+export const TOGGLE_PUBLISHED = 'TOGGLE_PUBLISHED';
+
+interface TogglePublishedAction {
+  type: typeof TOGGLE_PUBLISHED;
+  isPublished: boolean;
+}
+
+export function togglePublished(isPublished: boolean): TogglePublishedAction {
+  return { type: TOGGLE_PUBLISHED, isPublished };
+}
+
+export function savePublished(
+  id: number,
+  isPublished: boolean,
+): (dispatch: AppDispatch) => Promise<void> {
+  return function savePublishedThunk(dispatch: AppDispatch): Promise<void> {
+    return SupersetClient.put({
+      endpoint: `/api/v1/dashboard/${id}`,
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({
+        published: isPublished,
+      }),
+    })
+      .then(() => {
+        dispatch(
+          addSuccessToast(
+            isPublished
+              ? t('This dashboard is now published')
+              : t('This dashboard is now hidden'),
+          ),
+        );
+        dispatch(togglePublished(isPublished));
+      })
+      .catch(() => {
+        dispatch(
+          addDangerToast(
+            t('You do not have permissions to edit this dashboard.'),
+          ),
+        );
+      });
+  };
+}
+
+export const TOGGLE_EXPAND_SLICE = 'TOGGLE_EXPAND_SLICE';
+
+interface ToggleExpandSliceAction {
+  type: typeof TOGGLE_EXPAND_SLICE;
+  sliceId: number;
+}
+
+export function toggleExpandSlice(sliceId: number): ToggleExpandSliceAction {
+  return { type: TOGGLE_EXPAND_SLICE, sliceId };
+}
+
+export const SET_EDIT_MODE = 'SET_EDIT_MODE';
+
+interface SetEditModeAction {
+  type: typeof SET_EDIT_MODE;
+  editMode: boolean;
+}
+
+export function setEditMode(editMode: boolean): SetEditModeAction {
+  return { type: SET_EDIT_MODE, editMode };
+}
+
+export const ON_CHANGE = 'ON_CHANGE';
+
+interface OnChangeAction {
+  type: typeof ON_CHANGE;
+}
+
+export function onChange(): OnChangeAction {
+  return { type: ON_CHANGE };
+}
+
+export const ON_SAVE = 'ON_SAVE';
+
+interface OnSaveAction {
+  type: typeof ON_SAVE;
+  lastModifiedTime: number;
+}
+
+export function onSave(lastModifiedTime: number): OnSaveAction {
+  return { type: ON_SAVE, lastModifiedTime };
+}
+
+export const SET_REFRESH_FREQUENCY = 'SET_REFRESH_FREQUENCY';
+
+interface SetRefreshFrequencyAction {
+  type: typeof SET_REFRESH_FREQUENCY;
+  refreshFrequency: number;
+  isPersistent: boolean;
+}
+
+export function setRefreshFrequency(
+  refreshFrequency: number,
+  isPersistent = false,
+): SetRefreshFrequencyAction {
+  return { type: SET_REFRESH_FREQUENCY, refreshFrequency, isPersistent };
+}
+
+export function saveDashboardRequestSuccess(
+  lastModifiedTime: number,
+): (dispatch: AppDispatch) => void {
+  return (dispatch: AppDispatch) => {
+    dispatch(onSave(lastModifiedTime));
+    // clear layout undo history
+    dispatch(UndoActionCreators.clearHistory());
+  };
+}
+
+export const SET_OVERRIDE_CONFIRM = 'SET_OVERRIDE_CONFIRM';
+
+interface SetOverrideConfirmAction {
+  type: typeof SET_OVERRIDE_CONFIRM;
+  overwriteConfirmMetadata: DashboardState['overwriteConfirmMetadata'];
+}
+
+export function setOverrideConfirm(
+  overwriteConfirmMetadata: DashboardState['overwriteConfirmMetadata'],
+): SetOverrideConfirmAction {
+  return {
+    type: SET_OVERRIDE_CONFIRM,
+    overwriteConfirmMetadata,
+  };
+}
+
+export const SAVE_DASHBOARD_STARTED = 'SAVE_DASHBOARD_STARTED';
+
+interface SaveDashboardStartedAction {
+  type: typeof SAVE_DASHBOARD_STARTED;
+}
+
+export function saveDashboardStarted(): SaveDashboardStartedAction {
+  return { type: SAVE_DASHBOARD_STARTED };
+}
+
+export const SAVE_DASHBOARD_FINISHED = 'SAVE_DASHBOARD_FINISHED';
+
+interface SaveDashboardFinishedAction {
+  type: typeof SAVE_DASHBOARD_FINISHED;
+}
+
+export function saveDashboardFinished(): SaveDashboardFinishedAction {
+  return { type: SAVE_DASHBOARD_FINISHED };
+}
+
+export const SET_DASHBOARD_LABELS_COLORMAP_SYNCABLE =
+  'SET_DASHBOARD_LABELS_COLORMAP_SYNCABLE';
+export const SET_DASHBOARD_LABELS_COLORMAP_SYNCED =
+  'SET_DASHBOARD_LABELS_COLORMAP_SYNCED';
+export const SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCABLE =
+  'SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCABLE';
+export const SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCED =
+  'SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCED';
+
+interface SetDashboardLabelsColorMapSyncAction {
+  type: typeof SET_DASHBOARD_LABELS_COLORMAP_SYNCABLE;
+}
+
+interface SetDashboardLabelsColorMapSyncedAction {
+  type: typeof SET_DASHBOARD_LABELS_COLORMAP_SYNCED;
+}
+
+interface SetDashboardSharedLabelsColorsSyncAction {
+  type: typeof SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCABLE;
+}
+
+interface SetDashboardSharedLabelsColorsSyncedAction {
+  type: typeof SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCED;
+}
+
+export function setDashboardLabelsColorMapSync(): 
SetDashboardLabelsColorMapSyncAction {
+  return { type: SET_DASHBOARD_LABELS_COLORMAP_SYNCABLE };
+}
+
+export function setDashboardLabelsColorMapSynced(): 
SetDashboardLabelsColorMapSyncedAction {
+  return { type: SET_DASHBOARD_LABELS_COLORMAP_SYNCED };
+}
+
+export function setDashboardSharedLabelsColorsSync(): 
SetDashboardSharedLabelsColorsSyncAction {
+  return { type: SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCABLE };
+}
+
+export function setDashboardSharedLabelsColorsSynced(): 
SetDashboardSharedLabelsColorsSyncedAction {
+  return { type: SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCED };
+}
+
+export const setDashboardMetadata =
+  (updatedMetadata: JsonObject) =>
+  async (dispatch: AppDispatch, getState: GetState): Promise<void> => {
+    const { dashboardInfo } = getState();
+    dispatch(
+      dashboardInfoChanged({
+        metadata: {
+          ...dashboardInfo?.metadata,
+          ...updatedMetadata,
+        },
+      }),
+    );
+  };
+
+// ---------------------------------------------------------------------------
+// saveDashboardRequest
+// ---------------------------------------------------------------------------
+
+interface DashboardSaveData extends JsonObject {
+  certified_by?: string;
+  certification_details?: string;
+  css?: string;
+  dashboard_title?: string;
+  owners?: { id: number }[] | number[];
+  roles?: { id: number }[] | number[];
+  slug?: string | null;
+  tags?: { id: number }[] | number[];
+  metadata?: JsonObject;
+  positions?: JsonObject;
+  duplicate_slices?: boolean;
+  theme_id?: number | null;
+}
+
+export function saveDashboardRequest(
+  data: DashboardSaveData,
+  id: number,
+  saveType: string,
+): (dispatch: AppDispatch, getState: GetState) => Promise<JsonObject | void> {
+  return (dispatch: AppDispatch, getState: GetState) => {
+    dispatch({ type: UPDATE_COMPONENTS_PARENTS_LIST });
+    dispatch(saveDashboardStarted());
+
+    const { dashboardFilters, dashboardLayout } = getState();
+    const layout = dashboardLayout.present;
+    Object.values(dashboardFilters).forEach((filter: JsonObject) => {
+      const { chartId } = filter;
+      const componentId = (filter.directPathToFilter as string[])
+        .slice()
+        .pop() as string;
+      const directPathToFilter = (layout[componentId]?.parents || []).slice();
+      directPathToFilter.push(componentId);
+      dispatch(updateDirectPathToFilter(chartId as number, 
directPathToFilter));
+    });
+    // serialize selected values for each filter field, grouped by filter id
+    const serializedFilters = serializeActiveFilterValues(getActiveFilters());
+    // serialize filter scope for each filter field, grouped by filter id
+    const serializedFilterScopes = serializeFilterScopes(dashboardFilters);
+    const {
+      certified_by,
+      certification_details,
+      css,
+      dashboard_title,
+      owners,
+      roles,
+      slug,
+      tags,
+    } = data;
+
+    const hasId = (item: JsonObject): boolean => item.id !== undefined;
+    const metadataCrossFiltersEnabled = data.metadata?.cross_filters_enabled;
+    const colorScheme = data.metadata?.color_scheme as string | undefined;
+    const customLabelsColor = (data.metadata?.label_colors || {}) as Record<
+      string,
+      string
+    >;
+    const sharedLabelsColor = enforceSharedLabelsColorsArray(
+      data.metadata?.shared_label_colors,
+    );
+    const cleanedData: JsonObject = {
+      ...data,
+      certified_by: certified_by || '',
+      certification_details:
+        certified_by && certification_details ? certification_details : '',
+      css: css || '',
+      dashboard_title: dashboard_title || t('[ untitled dashboard ]'),
+      owners: ensureIsArray(owners as JsonObject[]).map((o: JsonObject) =>
+        hasId(o) ? o.id : o,
+      ),
+      roles: !isFeatureEnabled(FeatureFlag.DashboardRbac)
+        ? undefined
+        : ensureIsArray(roles as JsonObject[]).map((r: JsonObject) =>
+            hasId(r) ? r.id : r,
+          ),
+      slug: slug || null,
+      tags: !isFeatureEnabled(FeatureFlag.TaggingSystem)
+        ? undefined
+        : ensureIsArray((tags || []) as JsonObject[]).map((r: JsonObject) =>
+            hasId(r) ? r.id : r,
+          ),
+      metadata: {
+        ...data.metadata,
+        color_namespace: getColorNamespace(
+          data.metadata?.color_namespace as string | undefined,
+        ),
+        color_scheme: colorScheme || '',
+        color_scheme_domain: colorScheme
+          ? getColorSchemeDomain(colorScheme)
+          : [],
+        expanded_slices: data.metadata?.expanded_slices || {},
+        label_colors: customLabelsColor,
+        shared_label_colors: getFreshSharedLabels(sharedLabelsColor),
+        map_label_colors: getFreshLabelsColorMapEntries(customLabelsColor),
+        refresh_frequency: data.metadata?.refresh_frequency || 0,
+        timed_refresh_immune_slices:
+          data.metadata?.timed_refresh_immune_slices || [],
+        // cross-filters should be enabled by default
+        cross_filters_enabled: isCrossFiltersEnabled(
+          metadataCrossFiltersEnabled as boolean | undefined,
+        ),
+      },
+    };
+
+    const handleChartConfiguration = () => {
+      const {
+        dashboardLayout: currentDashboardLayout,
+        charts,
+        dashboardInfo: { metadata },
+      } = getState();
+      return getCrossFiltersConfiguration(
+        currentDashboardLayout.present,
+        metadata,
+        charts,
+      );
+    };
+
+    const onCopySuccess = (response: JsonObject): JsonObject => {
+      const lastModifiedTime = (response.json as JsonObject).result
+        ?.last_modified_time as number;
+      if (lastModifiedTime) {
+        dispatch(saveDashboardRequestSuccess(lastModifiedTime));
+      }
+      const { chartConfiguration, globalChartConfiguration } =
+        handleChartConfiguration();
+      dispatch(
+        saveChartConfiguration({
+          chartConfiguration,
+          globalChartConfiguration,
+        }),
+      );
+      dispatch(saveDashboardFinished());
+      navigateTo(
+        `/superset/dashboard/${(response.json as JsonObject).result?.id}/`,
+      );
+      dispatch(addSuccessToast(t('This dashboard was saved successfully.')));
+      return response;
+    };
+
+    const onUpdateSuccess = (response: JsonObject): JsonObject => {
+      const updatedDashboard = (response.json as JsonObject)
+        .result as JsonObject;
+      const lastModifiedTime = (response.json as JsonObject)
+        .last_modified_time as number;
+      // syncing with the backend transformations of the metadata
+      if (updatedDashboard.json_metadata) {
+        const parsedMetadata: JsonObject = JSON.parse(
+          updatedDashboard.json_metadata as string,
+        );
+        dispatch(setDashboardMetadata(parsedMetadata));
+        if (parsedMetadata.chart_configuration) {
+          dispatch({
+            type: SAVE_CHART_CONFIG_COMPLETE,
+            chartConfiguration: parsedMetadata.chart_configuration,
+          });
+        }
+        if (parsedMetadata.native_filter_configuration) {
+          dispatch({
+            type: SET_IN_SCOPE_STATUS_OF_FILTERS,
+            filterConfig: parsedMetadata.native_filter_configuration,
+          });
+        }
+
+        // fetch datasets to make sure they are up to date
+        SupersetClient.get({
+          endpoint: `/api/v1/dashboard/${id}/datasets`,
+          headers: { 'Content-Type': 'application/json' },
+        })
+          .then(({ json }: { json: JsonObject }) => {
+            const datasources = json?.result ?? [];
+            if ((datasources as JsonObject[]).length) {
+              dispatch(
+                setDatasources(
+                  datasources as Parameters<typeof setDatasources>[0],
+                ),
+              );
+            }
+          })
+          .catch((error: Error) => {
+            logging.error('Error fetching dashboard datasets:', error);
+          });
+      }
+      if (lastModifiedTime) {
+        dispatch(saveDashboardRequestSuccess(lastModifiedTime));
+      }
+      dispatch(saveDashboardFinished());
+      // redirect to the new slug or id
+      navigateWithState(`/superset/dashboard/${slug || id}/`, {
+        event: 'dashboard_properties_changed',
+      });
+
+      dispatch(addSuccessToast(t('This dashboard was saved successfully.')));
+      dispatch(setOverrideConfirm(undefined));
+      return response;
+    };
+
+    const onError = async (response: Response): Promise<void> => {
+      const { error, message } = await getClientErrorObject(response);
+      let errorText = t('Sorry, an unknown error occurred');
+
+      if (error) {
+        errorText = t(
+          'Sorry, there was an error saving this dashboard: %s',
+          error,
+        );
+      }
+      if (typeof message === 'string' && message === 'Forbidden') {
+        errorText = t('You do not have permission to edit this dashboard');
+      }
+      dispatch(saveDashboardFinished());
+      dispatch(addDangerToast(errorText));
+    };
+
+    if (
+      [SAVE_TYPE_OVERWRITE, SAVE_TYPE_OVERWRITE_CONFIRMED].includes(saveType)
+    ) {
+      const { chartConfiguration, globalChartConfiguration } =
+        handleChartConfiguration();
+      const updatedDashboard: JsonObject =
+        saveType === SAVE_TYPE_OVERWRITE_CONFIRMED
+          ? data
+          : {
+              certified_by: cleanedData.certified_by,
+              certification_details: cleanedData.certification_details,
+              css: cleanedData.css,
+              dashboard_title: cleanedData.dashboard_title,
+              slug: cleanedData.slug,
+              owners: cleanedData.owners,
+              roles: cleanedData.roles,
+              tags: cleanedData.tags || [],
+              theme_id: cleanedData.theme_id,
+              json_metadata: safeStringify({
+                ...(cleanedData?.metadata as JsonObject),
+                default_filters: safeStringify(serializedFilters),
+                filter_scopes: serializedFilterScopes,
+                chart_configuration: chartConfiguration,
+                global_chart_configuration: globalChartConfiguration,
+              }),
+            };
+
+      const updateDashboard = (): Promise<JsonObject | void> =>
+        SupersetClient.put({
+          endpoint: `/api/v1/dashboard/${id}`,
+          headers: { 'Content-Type': 'application/json' },
+          body: JSON.stringify(updatedDashboard),
+        })
+          .then(response => onUpdateSuccess(response))
+          .catch(response => onError(response));
+      return new Promise<void>((resolve, reject) => {
+        if (
+          !isFeatureEnabled(FeatureFlag.ConfirmDashboardDiff) ||
+          saveType === SAVE_TYPE_OVERWRITE_CONFIRMED
+        ) {
+          // skip overwrite precheck
+          resolve();
+          return;
+        }
+
+        // precheck for overwrite items
+        SupersetClient.get({
+          endpoint: `/api/v1/dashboard/${id}`,
+        }).then((response: JsonObject) => {
+          const dashboard = (response.json as JsonObject).result as JsonObject;
+          const overwriteConfirmItems = getOverwriteItems(
+            dashboard,
+            updatedDashboard,
+          );
+          if (overwriteConfirmItems.length > 0) {
+            dispatch(
+              setOverrideConfirm({
+                updatedAt: dashboard.changed_on as string,
+                updatedBy: dashboard.changed_by_name as string,
+                overwriteConfirmItems:
+                  overwriteConfirmItems as 
DashboardState['overwriteConfirmMetadata'] extends
+                    | { overwriteConfirmItems: infer I }
+                    | undefined
+                    ? I
+                    : never,
+                dashboardId: id,
+                data: updatedDashboard,
+              }),
+            );
+            return reject(overwriteConfirmItems);
+          }
+          return resolve();
+        });
+      })
+        .then(updateDashboard)
+        .catch((overwriteConfirmItems: JsonObject[]) => {
+          const errorText = t('Please confirm the overwrite values.');
+          dispatch(
+            logEvent(LOG_ACTIONS_CONFIRM_OVERWRITE_DASHBOARD_METADATA, {
+              dashboard_id: id,
+              items: overwriteConfirmItems,
+            }),
+          );
+          dispatch(addDangerToast(errorText));
+        });
+    }
+    // changing the data as the endpoint requires
+    if (
+      'positions' in cleanedData &&
+      !('positions' in (cleanedData.metadata as JsonObject))
+    ) {
+      (cleanedData.metadata as JsonObject).positions = cleanedData.positions;
+    }
+    (cleanedData.metadata as JsonObject).default_filters =
+      safeStringify(serializedFilters);
+    (cleanedData.metadata as JsonObject).filter_scopes = 
serializedFilterScopes;
+    const copyPayload = {
+      dashboard_title: cleanedData.dashboard_title,
+      css: cleanedData.css,
+      duplicate_slices: cleanedData.duplicate_slices,
+      json_metadata: JSON.stringify(cleanedData.metadata),
+    };
+
+    return SupersetClient.post({
+      endpoint: `/api/v1/dashboard/${id}/copy/`,
+      jsonPayload: copyPayload,
+    })
+      .then(response => onCopySuccess(response))
+      .catch(response => onError(response));
+  };
+}
+
+// ---------------------------------------------------------------------------
+// Chart refresh
+// ---------------------------------------------------------------------------
+
+export function fetchCharts(
+  chartList: number[] = [],
+  force = false,
+  interval = 0,
+  dashboardId?: number,
+): (dispatch: AppDispatch, getState: GetState) => void {
+  return (dispatch: AppDispatch, getState: GetState) => {
+    if (!interval) {
+      chartList.forEach(chartKey =>
+        dispatch(refreshChart(chartKey, force, dashboardId)),
+      );
+      return;
+    }
+
+    const { metadata } = getState().dashboardInfo;
+    const meta = metadata as JsonObject;
+    const refreshTime = Math.max(
+      interval,
+      (meta.stagger_time as number) || 5000,
+    ); // default 5 seconds
+    if (typeof meta.stagger_refresh !== 'boolean') {
+      meta.stagger_refresh =
+        meta.stagger_refresh === undefined
+          ? true
+          : meta.stagger_refresh === 'true';
+    }
+    const delay = meta.stagger_refresh
+      ? refreshTime / (chartList.length - 1)
+      : 0;

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>State Mutation Issue</b></div>
   <div id="fix">
   
   Mutating the meta object from getState() violates Redux immutability, 
potentially corrupting shared state. If this normalization is needed, copy the 
value locally instead.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ````suggestion
       let staggerRefresh = meta.stagger_refresh;
       if (typeof staggerRefresh !== 'boolean') {
         staggerRefresh =
           staggerRefresh === undefined
             ? true
             : staggerRefresh === 'true';
       }
       const delay = staggerRefresh && chartList.length > 1
         ? refreshTime / (chartList.length - 1)
         : 0;
   ````
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #52392d</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a 
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx:
##########
@@ -351,7 +351,7 @@ const ColumnSelectPopover = ({
     tab => {
       getCurrentTab(tab);
       setSelectedTab(tab);
-      // @ts-ignore
+      // @ts-expect-error - AceEditor ref type doesn't expose editor.focus()
       sqlEditorRef.current?.editor.focus();

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Incorrect API usage on EditorHandle</b></div>
   <div id="fix">
   
   The code accesses .editor.focus() on an EditorHandle ref, but EditorHandle 
does not expose an .editor property. Use .focus() directly instead, as the 
interface provides this method.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ````suggestion
       setSelectedTab(tab);
       sqlEditorRef.current?.focus();
   ````
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #52392d</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a 
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/filters/components/TimeColumn/TimeColumnFilterPlugin.tsx:
##########
@@ -111,7 +111,7 @@ export default function PluginFilterTimeColumn(
           allowClear
           value={value}
           placeholder={placeholderText}
-          // @ts-ignore
+          // @ts-expect-error

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Type mismatch in Select onChange</b></div>
   <div id="fix">
   
   The Select component is configured for single select but value is typed as 
string[], causing a type mismatch with onChange that expects string | null. 
This can lead to runtime issues if types diverge. Change to single-value 
handling to match the component's usage.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ```
    No patch provided - syntactically invalid
   ```
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #52392d</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a 
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/dashboard/actions/dashboardState.ts:
##########
@@ -0,0 +1,1517 @@
+/**
+ * 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.
+ */
+/* eslint camelcase: 0 */
+import { ActionCreators as UndoActionCreators } from 'redux-undo';
+import rison from 'rison';
+import {
+  ensureIsArray,
+  isFeatureEnabled,
+  FeatureFlag,
+  getLabelsColorMap,
+  SupersetClient,
+  getClientErrorObject,
+  getCategoricalSchemeRegistry,
+  promiseTimeout,
+  JsonObject,
+} from '@superset-ui/core';
+import {
+  addChart,
+  removeChart,
+  refreshChart,
+} from 'src/components/Chart/chartAction';
+import { logging } from '@apache-superset/core';
+import { t } from '@apache-superset/core/ui';
+import { chart as initChart } from 'src/components/Chart/chartReducer';
+import { applyDefaultFormData } from 'src/explore/store';
+import {
+  SAVE_TYPE_OVERWRITE,
+  SAVE_TYPE_OVERWRITE_CONFIRMED,
+} from 'src/dashboard/util/constants';
+import {
+  getCrossFiltersConfiguration,
+  isCrossFiltersEnabled,
+} from 'src/dashboard/util/crossFilters';
+import {
+  addSuccessToast,
+  addWarningToast,
+  addDangerToast,
+} from 'src/components/MessageToasts/actions';
+import serializeActiveFilterValues from 
'src/dashboard/util/serializeActiveFilterValues';
+import serializeFilterScopes from 'src/dashboard/util/serializeFilterScopes';
+import { getActiveFilters } from 'src/dashboard/util/activeDashboardFilters';
+import { safeStringify } from 'src/utils/safeStringify';
+import { logEvent } from 'src/logger/actions';
+import { LOG_ACTIONS_CONFIRM_OVERWRITE_DASHBOARD_METADATA } from 
'src/logger/LogUtils';
+import { isEqual } from 'lodash';
+import { navigateWithState, navigateTo } from 'src/utils/navigationUtils';
+import type { AnyAction } from 'redux';
+import type { ThunkDispatch } from 'redux-thunk';
+import { ResourceStatus } from 'src/hooks/apiResources/apiResources';
+import type { AgGridChartState } from '@superset-ui/core';
+import type { DashboardChartStates } from 'src/dashboard/types/chartState';
+import { UPDATE_COMPONENTS_PARENTS_LIST } from './dashboardLayout';
+import {
+  saveChartConfiguration,
+  dashboardInfoChanged,
+  SAVE_CHART_CONFIG_COMPLETE,
+} from './dashboardInfo';
+import { fetchDatasourceMetadata, setDatasources } from './datasources';
+import { updateDirectPathToFilter } from './dashboardFilters';
+import { SET_IN_SCOPE_STATUS_OF_FILTERS } from './nativeFilters';
+import getOverwriteItems from '../util/getOverwriteItems';
+import {
+  applyColors,
+  enforceSharedLabelsColorsArray,
+  isLabelsColorMapSynced,
+  getColorSchemeDomain,
+  getColorNamespace,
+  getFreshLabelsColorMapEntries,
+  getFreshSharedLabels,
+  getDynamicLabelsColors,
+} from '../../utils/colorScheme';
+import type { DashboardState, RootState, Slice } from '../types';
+
+type GetState = () => RootState;
+
+// Dashboard dispatch type. The base ThunkDispatch handles dashboard-specific
+// thunks. The intersection with a generic function-accepting overload allows
+// dispatching thunks from other modules (e.g. chart actions) whose RootState
+// type differs from the dashboard RootState. At runtime the Redux store
+// satisfies all module state shapes.
+interface AppDispatch extends ThunkDispatch<RootState, undefined, AnyAction> {
+  <R>(asyncAction: (...args: never[]) => R): R;
+}
+
+// ---------------------------------------------------------------------------
+// Simple action creators
+// ---------------------------------------------------------------------------
+
+export const TOGGLE_NATIVE_FILTERS_BAR = 'TOGGLE_NATIVE_FILTERS_BAR';
+
+interface ToggleNativeFiltersBarAction {
+  type: typeof TOGGLE_NATIVE_FILTERS_BAR;
+  isOpen: boolean;
+}
+
+export function toggleNativeFiltersBar(
+  isOpen: boolean,
+): ToggleNativeFiltersBarAction {
+  return { type: TOGGLE_NATIVE_FILTERS_BAR, isOpen };
+}
+
+export const SET_UNSAVED_CHANGES = 'SET_UNSAVED_CHANGES';
+
+interface SetUnsavedChangesAction {
+  type: typeof SET_UNSAVED_CHANGES;
+  payload: { hasUnsavedChanges: boolean };
+}
+
+export function setUnsavedChanges(
+  hasUnsavedChanges: boolean,
+): SetUnsavedChangesAction {
+  return { type: SET_UNSAVED_CHANGES, payload: { hasUnsavedChanges } };
+}
+
+export const ADD_SLICE = 'ADD_SLICE';
+
+interface AddSliceAction {
+  type: typeof ADD_SLICE;
+  slice: Slice;
+}
+
+export function addSlice(slice: Slice): AddSliceAction {
+  return { type: ADD_SLICE, slice };
+}
+
+export const REMOVE_SLICE = 'REMOVE_SLICE';
+
+interface RemoveSliceAction {
+  type: typeof REMOVE_SLICE;
+  sliceId: number;
+}
+
+export function removeSlice(sliceId: number): RemoveSliceAction {
+  return { type: REMOVE_SLICE, sliceId };
+}
+
+export const TOGGLE_FAVE_STAR = 'TOGGLE_FAVE_STAR';
+
+interface ToggleFaveStarAction {
+  type: typeof TOGGLE_FAVE_STAR;
+  isStarred: boolean;
+}
+
+export function toggleFaveStar(isStarred: boolean): ToggleFaveStarAction {
+  return { type: TOGGLE_FAVE_STAR, isStarred };
+}
+
+export function fetchFaveStar(id: number) {
+  return function fetchFaveStarThunk(dispatch: AppDispatch) {
+    return SupersetClient.get({
+      endpoint: `/api/v1/dashboard/favorite_status/?q=${rison.encode([id])}`,
+    })
+      .then(({ json }: { json: JsonObject }) => {
+        dispatch(toggleFaveStar(!!(json?.result as JsonObject[])?.[0]?.value));
+      })
+      .catch(() =>
+        dispatch(
+          addDangerToast(
+            t(
+              'There was an issue fetching the favorite status of this 
dashboard.',
+            ),
+          ),
+        ),
+      );
+  };
+}
+
+export function saveFaveStar(id: number, isStarred: boolean) {
+  return function saveFaveStarThunk(dispatch: AppDispatch) {
+    const endpoint = `/api/v1/dashboard/${id}/favorites/`;
+    const apiCall = isStarred
+      ? SupersetClient.delete({
+          endpoint,
+        })
+      : SupersetClient.post({ endpoint });
+
+    return apiCall
+      .then(() => {
+        dispatch(toggleFaveStar(!isStarred));
+      })
+      .catch(() =>
+        dispatch(
+          addDangerToast(t('There was an issue favoriting this dashboard.')),
+        ),
+      );
+  };
+}
+
+export const TOGGLE_PUBLISHED = 'TOGGLE_PUBLISHED';
+
+interface TogglePublishedAction {
+  type: typeof TOGGLE_PUBLISHED;
+  isPublished: boolean;
+}
+
+export function togglePublished(isPublished: boolean): TogglePublishedAction {
+  return { type: TOGGLE_PUBLISHED, isPublished };
+}
+
+export function savePublished(
+  id: number,
+  isPublished: boolean,
+): (dispatch: AppDispatch) => Promise<void> {
+  return function savePublishedThunk(dispatch: AppDispatch): Promise<void> {
+    return SupersetClient.put({
+      endpoint: `/api/v1/dashboard/${id}`,
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({
+        published: isPublished,
+      }),
+    })
+      .then(() => {
+        dispatch(
+          addSuccessToast(
+            isPublished
+              ? t('This dashboard is now published')
+              : t('This dashboard is now hidden'),
+          ),
+        );
+        dispatch(togglePublished(isPublished));
+      })
+      .catch(() => {
+        dispatch(
+          addDangerToast(
+            t('You do not have permissions to edit this dashboard.'),
+          ),
+        );
+      });
+  };
+}
+
+export const TOGGLE_EXPAND_SLICE = 'TOGGLE_EXPAND_SLICE';
+
+interface ToggleExpandSliceAction {
+  type: typeof TOGGLE_EXPAND_SLICE;
+  sliceId: number;
+}
+
+export function toggleExpandSlice(sliceId: number): ToggleExpandSliceAction {
+  return { type: TOGGLE_EXPAND_SLICE, sliceId };
+}
+
+export const SET_EDIT_MODE = 'SET_EDIT_MODE';
+
+interface SetEditModeAction {
+  type: typeof SET_EDIT_MODE;
+  editMode: boolean;
+}
+
+export function setEditMode(editMode: boolean): SetEditModeAction {
+  return { type: SET_EDIT_MODE, editMode };
+}
+
+export const ON_CHANGE = 'ON_CHANGE';
+
+interface OnChangeAction {
+  type: typeof ON_CHANGE;
+}
+
+export function onChange(): OnChangeAction {
+  return { type: ON_CHANGE };
+}
+
+export const ON_SAVE = 'ON_SAVE';
+
+interface OnSaveAction {
+  type: typeof ON_SAVE;
+  lastModifiedTime: number;
+}
+
+export function onSave(lastModifiedTime: number): OnSaveAction {
+  return { type: ON_SAVE, lastModifiedTime };
+}
+
+export const SET_REFRESH_FREQUENCY = 'SET_REFRESH_FREQUENCY';
+
+interface SetRefreshFrequencyAction {
+  type: typeof SET_REFRESH_FREQUENCY;
+  refreshFrequency: number;
+  isPersistent: boolean;
+}
+
+export function setRefreshFrequency(
+  refreshFrequency: number,
+  isPersistent = false,
+): SetRefreshFrequencyAction {
+  return { type: SET_REFRESH_FREQUENCY, refreshFrequency, isPersistent };
+}
+
+export function saveDashboardRequestSuccess(
+  lastModifiedTime: number,
+): (dispatch: AppDispatch) => void {
+  return (dispatch: AppDispatch) => {
+    dispatch(onSave(lastModifiedTime));
+    // clear layout undo history
+    dispatch(UndoActionCreators.clearHistory());
+  };
+}
+
+export const SET_OVERRIDE_CONFIRM = 'SET_OVERRIDE_CONFIRM';
+
+interface SetOverrideConfirmAction {
+  type: typeof SET_OVERRIDE_CONFIRM;
+  overwriteConfirmMetadata: DashboardState['overwriteConfirmMetadata'];
+}
+
+export function setOverrideConfirm(
+  overwriteConfirmMetadata: DashboardState['overwriteConfirmMetadata'],
+): SetOverrideConfirmAction {
+  return {
+    type: SET_OVERRIDE_CONFIRM,
+    overwriteConfirmMetadata,
+  };
+}
+
+export const SAVE_DASHBOARD_STARTED = 'SAVE_DASHBOARD_STARTED';
+
+interface SaveDashboardStartedAction {
+  type: typeof SAVE_DASHBOARD_STARTED;
+}
+
+export function saveDashboardStarted(): SaveDashboardStartedAction {
+  return { type: SAVE_DASHBOARD_STARTED };
+}
+
+export const SAVE_DASHBOARD_FINISHED = 'SAVE_DASHBOARD_FINISHED';
+
+interface SaveDashboardFinishedAction {
+  type: typeof SAVE_DASHBOARD_FINISHED;
+}
+
+export function saveDashboardFinished(): SaveDashboardFinishedAction {
+  return { type: SAVE_DASHBOARD_FINISHED };
+}
+
+export const SET_DASHBOARD_LABELS_COLORMAP_SYNCABLE =
+  'SET_DASHBOARD_LABELS_COLORMAP_SYNCABLE';
+export const SET_DASHBOARD_LABELS_COLORMAP_SYNCED =
+  'SET_DASHBOARD_LABELS_COLORMAP_SYNCED';
+export const SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCABLE =
+  'SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCABLE';
+export const SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCED =
+  'SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCED';
+
+interface SetDashboardLabelsColorMapSyncAction {
+  type: typeof SET_DASHBOARD_LABELS_COLORMAP_SYNCABLE;
+}
+
+interface SetDashboardLabelsColorMapSyncedAction {
+  type: typeof SET_DASHBOARD_LABELS_COLORMAP_SYNCED;
+}
+
+interface SetDashboardSharedLabelsColorsSyncAction {
+  type: typeof SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCABLE;
+}
+
+interface SetDashboardSharedLabelsColorsSyncedAction {
+  type: typeof SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCED;
+}
+
+export function setDashboardLabelsColorMapSync(): 
SetDashboardLabelsColorMapSyncAction {
+  return { type: SET_DASHBOARD_LABELS_COLORMAP_SYNCABLE };
+}
+
+export function setDashboardLabelsColorMapSynced(): 
SetDashboardLabelsColorMapSyncedAction {
+  return { type: SET_DASHBOARD_LABELS_COLORMAP_SYNCED };
+}
+
+export function setDashboardSharedLabelsColorsSync(): 
SetDashboardSharedLabelsColorsSyncAction {
+  return { type: SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCABLE };
+}
+
+export function setDashboardSharedLabelsColorsSynced(): 
SetDashboardSharedLabelsColorsSyncedAction {
+  return { type: SET_DASHBOARD_SHARED_LABELS_COLORS_SYNCED };
+}
+
+export const setDashboardMetadata =
+  (updatedMetadata: JsonObject) =>
+  async (dispatch: AppDispatch, getState: GetState): Promise<void> => {
+    const { dashboardInfo } = getState();
+    dispatch(
+      dashboardInfoChanged({
+        metadata: {
+          ...dashboardInfo?.metadata,
+          ...updatedMetadata,
+        },
+      }),
+    );
+  };
+
+// ---------------------------------------------------------------------------
+// saveDashboardRequest
+// ---------------------------------------------------------------------------
+
+interface DashboardSaveData extends JsonObject {
+  certified_by?: string;
+  certification_details?: string;
+  css?: string;
+  dashboard_title?: string;
+  owners?: { id: number }[] | number[];
+  roles?: { id: number }[] | number[];
+  slug?: string | null;
+  tags?: { id: number }[] | number[];
+  metadata?: JsonObject;
+  positions?: JsonObject;
+  duplicate_slices?: boolean;
+  theme_id?: number | null;
+}
+
+export function saveDashboardRequest(
+  data: DashboardSaveData,
+  id: number,
+  saveType: string,
+): (dispatch: AppDispatch, getState: GetState) => Promise<JsonObject | void> {
+  return (dispatch: AppDispatch, getState: GetState) => {
+    dispatch({ type: UPDATE_COMPONENTS_PARENTS_LIST });
+    dispatch(saveDashboardStarted());
+
+    const { dashboardFilters, dashboardLayout } = getState();
+    const layout = dashboardLayout.present;
+    Object.values(dashboardFilters).forEach((filter: JsonObject) => {
+      const { chartId } = filter;
+      const componentId = (filter.directPathToFilter as string[])
+        .slice()
+        .pop() as string;
+      const directPathToFilter = (layout[componentId]?.parents || []).slice();
+      directPathToFilter.push(componentId);
+      dispatch(updateDirectPathToFilter(chartId as number, 
directPathToFilter));
+    });
+    // serialize selected values for each filter field, grouped by filter id
+    const serializedFilters = serializeActiveFilterValues(getActiveFilters());
+    // serialize filter scope for each filter field, grouped by filter id
+    const serializedFilterScopes = serializeFilterScopes(dashboardFilters);
+    const {
+      certified_by,
+      certification_details,
+      css,
+      dashboard_title,
+      owners,
+      roles,
+      slug,
+      tags,
+    } = data;
+
+    const hasId = (item: JsonObject): boolean => item.id !== undefined;
+    const metadataCrossFiltersEnabled = data.metadata?.cross_filters_enabled;
+    const colorScheme = data.metadata?.color_scheme as string | undefined;
+    const customLabelsColor = (data.metadata?.label_colors || {}) as Record<
+      string,
+      string
+    >;
+    const sharedLabelsColor = enforceSharedLabelsColorsArray(
+      data.metadata?.shared_label_colors,
+    );
+    const cleanedData: JsonObject = {
+      ...data,
+      certified_by: certified_by || '',
+      certification_details:
+        certified_by && certification_details ? certification_details : '',
+      css: css || '',
+      dashboard_title: dashboard_title || t('[ untitled dashboard ]'),
+      owners: ensureIsArray(owners as JsonObject[]).map((o: JsonObject) =>
+        hasId(o) ? o.id : o,
+      ),
+      roles: !isFeatureEnabled(FeatureFlag.DashboardRbac)
+        ? undefined
+        : ensureIsArray(roles as JsonObject[]).map((r: JsonObject) =>
+            hasId(r) ? r.id : r,
+          ),
+      slug: slug || null,
+      tags: !isFeatureEnabled(FeatureFlag.TaggingSystem)
+        ? undefined
+        : ensureIsArray((tags || []) as JsonObject[]).map((r: JsonObject) =>
+            hasId(r) ? r.id : r,
+          ),
+      metadata: {
+        ...data.metadata,
+        color_namespace: getColorNamespace(
+          data.metadata?.color_namespace as string | undefined,
+        ),
+        color_scheme: colorScheme || '',
+        color_scheme_domain: colorScheme
+          ? getColorSchemeDomain(colorScheme)
+          : [],
+        expanded_slices: data.metadata?.expanded_slices || {},
+        label_colors: customLabelsColor,
+        shared_label_colors: getFreshSharedLabels(sharedLabelsColor),
+        map_label_colors: getFreshLabelsColorMapEntries(customLabelsColor),
+        refresh_frequency: data.metadata?.refresh_frequency || 0,
+        timed_refresh_immune_slices:
+          data.metadata?.timed_refresh_immune_slices || [],
+        // cross-filters should be enabled by default
+        cross_filters_enabled: isCrossFiltersEnabled(
+          metadataCrossFiltersEnabled as boolean | undefined,
+        ),
+      },
+    };
+
+    const handleChartConfiguration = () => {
+      const {
+        dashboardLayout: currentDashboardLayout,
+        charts,
+        dashboardInfo: { metadata },
+      } = getState();
+      return getCrossFiltersConfiguration(
+        currentDashboardLayout.present,
+        metadata,
+        charts,
+      );
+    };
+
+    const onCopySuccess = (response: JsonObject): JsonObject => {
+      const lastModifiedTime = (response.json as JsonObject).result
+        ?.last_modified_time as number;
+      if (lastModifiedTime) {
+        dispatch(saveDashboardRequestSuccess(lastModifiedTime));
+      }
+      const { chartConfiguration, globalChartConfiguration } =
+        handleChartConfiguration();
+      dispatch(
+        saveChartConfiguration({
+          chartConfiguration,
+          globalChartConfiguration,
+        }),
+      );
+      dispatch(saveDashboardFinished());
+      navigateTo(
+        `/superset/dashboard/${(response.json as JsonObject).result?.id}/`,
+      );
+      dispatch(addSuccessToast(t('This dashboard was saved successfully.')));
+      return response;
+    };
+
+    const onUpdateSuccess = (response: JsonObject): JsonObject => {
+      const updatedDashboard = (response.json as JsonObject)
+        .result as JsonObject;
+      const lastModifiedTime = (response.json as JsonObject)
+        .last_modified_time as number;
+      // syncing with the backend transformations of the metadata
+      if (updatedDashboard.json_metadata) {
+        const parsedMetadata: JsonObject = JSON.parse(
+          updatedDashboard.json_metadata as string,
+        );
+        dispatch(setDashboardMetadata(parsedMetadata));
+        if (parsedMetadata.chart_configuration) {
+          dispatch({
+            type: SAVE_CHART_CONFIG_COMPLETE,
+            chartConfiguration: parsedMetadata.chart_configuration,
+          });
+        }
+        if (parsedMetadata.native_filter_configuration) {
+          dispatch({
+            type: SET_IN_SCOPE_STATUS_OF_FILTERS,
+            filterConfig: parsedMetadata.native_filter_configuration,
+          });
+        }
+
+        // fetch datasets to make sure they are up to date
+        SupersetClient.get({
+          endpoint: `/api/v1/dashboard/${id}/datasets`,
+          headers: { 'Content-Type': 'application/json' },
+        })
+          .then(({ json }: { json: JsonObject }) => {
+            const datasources = json?.result ?? [];
+            if ((datasources as JsonObject[]).length) {
+              dispatch(
+                setDatasources(
+                  datasources as Parameters<typeof setDatasources>[0],
+                ),
+              );
+            }
+          })
+          .catch((error: Error) => {
+            logging.error('Error fetching dashboard datasets:', error);
+          });
+      }
+      if (lastModifiedTime) {
+        dispatch(saveDashboardRequestSuccess(lastModifiedTime));
+      }
+      dispatch(saveDashboardFinished());
+      // redirect to the new slug or id
+      navigateWithState(`/superset/dashboard/${slug || id}/`, {
+        event: 'dashboard_properties_changed',
+      });
+
+      dispatch(addSuccessToast(t('This dashboard was saved successfully.')));
+      dispatch(setOverrideConfirm(undefined));
+      return response;
+    };
+
+    const onError = async (response: Response): Promise<void> => {
+      const { error, message } = await getClientErrorObject(response);
+      let errorText = t('Sorry, an unknown error occurred');
+
+      if (error) {
+        errorText = t(
+          'Sorry, there was an error saving this dashboard: %s',
+          error,
+        );
+      }
+      if (typeof message === 'string' && message === 'Forbidden') {
+        errorText = t('You do not have permission to edit this dashboard');
+      }
+      dispatch(saveDashboardFinished());
+      dispatch(addDangerToast(errorText));
+    };
+
+    if (
+      [SAVE_TYPE_OVERWRITE, SAVE_TYPE_OVERWRITE_CONFIRMED].includes(saveType)
+    ) {
+      const { chartConfiguration, globalChartConfiguration } =
+        handleChartConfiguration();
+      const updatedDashboard: JsonObject =
+        saveType === SAVE_TYPE_OVERWRITE_CONFIRMED
+          ? data
+          : {
+              certified_by: cleanedData.certified_by,
+              certification_details: cleanedData.certification_details,
+              css: cleanedData.css,
+              dashboard_title: cleanedData.dashboard_title,
+              slug: cleanedData.slug,
+              owners: cleanedData.owners,
+              roles: cleanedData.roles,
+              tags: cleanedData.tags || [],
+              theme_id: cleanedData.theme_id,
+              json_metadata: safeStringify({
+                ...(cleanedData?.metadata as JsonObject),
+                default_filters: safeStringify(serializedFilters),
+                filter_scopes: serializedFilterScopes,
+                chart_configuration: chartConfiguration,
+                global_chart_configuration: globalChartConfiguration,
+              }),
+            };
+
+      const updateDashboard = (): Promise<JsonObject | void> =>
+        SupersetClient.put({
+          endpoint: `/api/v1/dashboard/${id}`,
+          headers: { 'Content-Type': 'application/json' },
+          body: JSON.stringify(updatedDashboard),
+        })
+          .then(response => onUpdateSuccess(response))
+          .catch(response => onError(response));
+      return new Promise<void>((resolve, reject) => {
+        if (
+          !isFeatureEnabled(FeatureFlag.ConfirmDashboardDiff) ||
+          saveType === SAVE_TYPE_OVERWRITE_CONFIRMED
+        ) {
+          // skip overwrite precheck
+          resolve();
+          return;
+        }
+
+        // precheck for overwrite items
+        SupersetClient.get({
+          endpoint: `/api/v1/dashboard/${id}`,
+        }).then((response: JsonObject) => {
+          const dashboard = (response.json as JsonObject).result as JsonObject;
+          const overwriteConfirmItems = getOverwriteItems(
+            dashboard,
+            updatedDashboard,
+          );
+          if (overwriteConfirmItems.length > 0) {
+            dispatch(
+              setOverrideConfirm({
+                updatedAt: dashboard.changed_on as string,
+                updatedBy: dashboard.changed_by_name as string,
+                overwriteConfirmItems:
+                  overwriteConfirmItems as 
DashboardState['overwriteConfirmMetadata'] extends
+                    | { overwriteConfirmItems: infer I }
+                    | undefined
+                    ? I
+                    : never,
+                dashboardId: id,
+                data: updatedDashboard,
+              }),
+            );
+            return reject(overwriteConfirmItems);
+          }
+          return resolve();
+        });
+      })
+        .then(updateDashboard)
+        .catch((overwriteConfirmItems: JsonObject[]) => {
+          const errorText = t('Please confirm the overwrite values.');
+          dispatch(
+            logEvent(LOG_ACTIONS_CONFIRM_OVERWRITE_DASHBOARD_METADATA, {
+              dashboard_id: id,
+              items: overwriteConfirmItems,
+            }),
+          );
+          dispatch(addDangerToast(errorText));
+        });
+    }
+    // changing the data as the endpoint requires
+    if (
+      'positions' in cleanedData &&
+      !('positions' in (cleanedData.metadata as JsonObject))
+    ) {
+      (cleanedData.metadata as JsonObject).positions = cleanedData.positions;
+    }
+    (cleanedData.metadata as JsonObject).default_filters =
+      safeStringify(serializedFilters);
+    (cleanedData.metadata as JsonObject).filter_scopes = 
serializedFilterScopes;
+    const copyPayload = {
+      dashboard_title: cleanedData.dashboard_title,
+      css: cleanedData.css,
+      duplicate_slices: cleanedData.duplicate_slices,
+      json_metadata: JSON.stringify(cleanedData.metadata),
+    };
+
+    return SupersetClient.post({
+      endpoint: `/api/v1/dashboard/${id}/copy/`,
+      jsonPayload: copyPayload,
+    })
+      .then(response => onCopySuccess(response))
+      .catch(response => onError(response));
+  };
+}
+
+// ---------------------------------------------------------------------------
+// Chart refresh
+// ---------------------------------------------------------------------------
+
+export function fetchCharts(
+  chartList: number[] = [],
+  force = false,
+  interval = 0,
+  dashboardId?: number,
+): (dispatch: AppDispatch, getState: GetState) => void {
+  return (dispatch: AppDispatch, getState: GetState) => {
+    if (!interval) {
+      chartList.forEach(chartKey =>
+        dispatch(refreshChart(chartKey, force, dashboardId)),
+      );
+      return;
+    }
+
+    const { metadata } = getState().dashboardInfo;
+    const meta = metadata as JsonObject;
+    const refreshTime = Math.max(
+      interval,
+      (meta.stagger_time as number) || 5000,
+    ); // default 5 seconds
+    if (typeof meta.stagger_refresh !== 'boolean') {
+      meta.stagger_refresh =
+        meta.stagger_refresh === undefined
+          ? true
+          : meta.stagger_refresh === 'true';
+    }
+    const delay = meta.stagger_refresh
+      ? refreshTime / (chartList.length - 1)
+      : 0;

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Division by Zero Risk</b></div>
   <div id="fix">
   
   The delay calculation divides by (chartList.length - 1), causing Infinity 
when length is 1 and stagger_refresh is enabled, preventing chart refresh. It 
looks like staggering should only apply for multiple charts.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ````suggestion
       const delay = meta.stagger_refresh && chartList.length > 1
         ? refreshTime / (chartList.length - 1)
         : 0;
   ````
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #52392d</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a 
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberViz.tsx:
##########
@@ -192,7 +192,7 @@ function BigNumberVis({
 
   const renderHeader = (maxHeight: number) => {
     const { bigNumber, width, colorThresholdFormatters, onContextMenu } = 
props;
-    // @ts-ignore
+    // @ts-expect-error
     const text = bigNumber === null ? t('No data') : 
headerFormatter(bigNumber);

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Type suppression instead of proper typing</b></div>
   <div id="fix">
   
   The @ts-expect-error suppresses a type mismatch where headerFormatter 
expects a number, but bigNumber can be string, boolean, or undefined. Instead 
of suppressing, add a type check to ensure only numbers are passed to 
headerFormatter, aligning with the guideline to use proper TypeScript types 
without suppressions.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ````suggestion
       const text = bigNumber === null || typeof bigNumber !== 'number' ? t('No 
data') : headerFormatter(bigNumber);
   ````
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #52392d</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a 
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



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