From e883cdfffcc80241777dccf366839eba82c6e4af Mon Sep 17 00:00:00 2001
From: Oisin Coveney <oisin@metabase.com>
Date: Wed, 16 Oct 2024 17:07:55 +0300
Subject: [PATCH] Convert `dashcardData` dashboard reducers to TS (#48679)

---
 .../src/metabase-types/api/mocks/revision.ts  | 35 ++++----
 frontend/src/metabase-types/api/revision.ts   |  3 +
 .../src/metabase/dashboard/reducers-typed.ts  | 82 +++++++++++++------
 frontend/src/metabase/dashboard/reducers.js   | 30 +------
 4 files changed, 80 insertions(+), 70 deletions(-)

diff --git a/frontend/src/metabase-types/api/mocks/revision.ts b/frontend/src/metabase-types/api/mocks/revision.ts
index 12833b5c1c7..f9f3783786a 100644
--- a/frontend/src/metabase-types/api/mocks/revision.ts
+++ b/frontend/src/metabase-types/api/mocks/revision.ts
@@ -1,21 +1,20 @@
 import type { Revision } from "metabase-types/api";
 
-export const createMockRevision = (opts?: Partial<Revision>): Revision => {
-  return {
+export const createMockRevision = (opts?: Partial<Revision>): Revision => ({
+  model_id: 1,
+  id: 1,
+  description: "created this",
+  message: null,
+  timestamp: "2023-05-16T13:33:30.198622-07:00",
+  is_creation: true,
+  is_reversion: false,
+  has_multiple_changes: false,
+  user: {
     id: 1,
-    description: "created this",
-    message: null,
-    timestamp: "2023-05-16T13:33:30.198622-07:00",
-    is_creation: true,
-    is_reversion: false,
-    has_multiple_changes: false,
-    user: {
-      id: 1,
-      first_name: "Admin",
-      last_name: "Test",
-      common_name: "Admin Test",
-    },
-    diff: null,
-    ...opts,
-  };
-};
+    first_name: "Admin",
+    last_name: "Test",
+    common_name: "Admin Test",
+  },
+  diff: null,
+  ...opts,
+});
diff --git a/frontend/src/metabase-types/api/revision.ts b/frontend/src/metabase-types/api/revision.ts
index 10e6b962068..f0536bc65a6 100644
--- a/frontend/src/metabase-types/api/revision.ts
+++ b/frontend/src/metabase-types/api/revision.ts
@@ -1,3 +1,5 @@
+import type { CardId } from "metabase-types/api";
+
 export interface Revision {
   id: number;
   description: string;
@@ -13,6 +15,7 @@ export interface Revision {
     last_name: string;
     common_name: string;
   };
+  model_id: CardId;
 }
 
 export interface RevisionListQuery {
diff --git a/frontend/src/metabase/dashboard/reducers-typed.ts b/frontend/src/metabase/dashboard/reducers-typed.ts
index 22d8513c70d..d48f831835d 100644
--- a/frontend/src/metabase/dashboard/reducers-typed.ts
+++ b/frontend/src/metabase/dashboard/reducers-typed.ts
@@ -1,5 +1,5 @@
 import { createReducer } from "@reduxjs/toolkit";
-import { assocIn } from "icepick";
+import { assocIn, dissocIn } from "icepick";
 import { omit } from "underscore";
 
 import {
@@ -9,14 +9,18 @@ import {
   updateDashboardEnableEmbedding,
 } from "metabase/api";
 import Dashboards from "metabase/entities/dashboards";
+import Questions from "metabase/entities/questions";
+import Revisions from "metabase/entities/revisions";
 import { handleActions } from "metabase/lib/redux";
 import { NAVIGATE_BACK_TO_DASHBOARD } from "metabase/query_builder/actions";
 import type { UiParameter } from "metabase-lib/v1/parameters/types";
 import type {
+  Card,
   DashCardId,
   Dashboard,
   ParameterId,
   ParameterValueOrArray,
+  Revision,
 } from "metabase-types/api";
 import type {
   DashboardSidebarName,
@@ -40,6 +44,7 @@ import {
   addDashcardIdsToLoadingQueue,
   addManyCardsToDash,
   cancelFetchCardData,
+  clearCardData,
   fetchCardDataAction,
   fetchDashboard,
   fetchDashboardCardDataAction,
@@ -204,17 +209,12 @@ export const sidebar = createReducer(
 export const parameterValues = createReducer(
   INITIAL_DASHBOARD_STATE.parameterValues,
   builder => {
-    builder.addCase<
-      string,
-      {
-        type: string;
-        payload: {
-          clearCache?: boolean;
-        };
-      }
-    >(INITIALIZE, (state, { payload: { clearCache = true } = {} }) => {
-      return clearCache ? {} : state;
-    });
+    builder.addCase(
+      initialize,
+      (state, { payload: { clearCache = true } = {} }) => {
+        return clearCache ? {} : state;
+      },
+    );
 
     builder.addCase(fetchDashboard.fulfilled, (_state, { payload }) => {
       return payload.parameterValues;
@@ -259,17 +259,12 @@ export const parameterValues = createReducer(
       }
     });
 
-    builder.addCase<
-      string,
-      {
-        type: string;
-        payload: {
-          id: ParameterId;
-        };
-      }
-    >(REMOVE_PARAMETER, (state, { payload: { id } }) => {
-      delete state[id];
-    });
+    builder.addCase<string, { type: string; payload: { id: ParameterId } }>(
+      REMOVE_PARAMETER,
+      (state, { payload: { id } }) => {
+        delete state[id];
+      },
+    );
   },
 );
 
@@ -413,3 +408,44 @@ export const loadingDashCards = createReducer(
       }));
   },
 );
+
+export const dashcardData = createReducer(
+  INITIAL_DASHBOARD_STATE.dashcardData,
+  builder => {
+    builder
+      .addCase(initialize, (state, action) => {
+        const { clearCache = true } = action.payload ?? {};
+        return clearCache ? {} : state;
+      })
+      .addCase(fetchCardDataAction.fulfilled, (state, action) => {
+        const { dashcard_id, card_id, result } = action.payload ?? {};
+        if (dashcard_id && card_id) {
+          return assocIn(state, [dashcard_id, card_id], result);
+        }
+      })
+      .addCase(clearCardData, (state, action) => {
+        const { cardId, dashcardId } = action.payload;
+        return dissocIn(state, [dashcardId, cardId]);
+      })
+      .addCase<string, { type: string; payload: { object: Card } }>(
+        Questions.actionTypes.UPDATE,
+        (state, action) => {
+          const id = action.payload.object.id;
+          for (const dashcardId in state) {
+            delete state[dashcardId][id];
+          }
+        },
+      )
+      .addCase<string, { type: string; payload: Revision }>(
+        Revisions.actionTypes.REVERT,
+        (state, action) => {
+          const { model_id } = action.payload;
+          if (model_id) {
+            for (const dashcardId in state) {
+              delete state[dashcardId][model_id];
+            }
+          }
+        },
+      );
+  },
+);
diff --git a/frontend/src/metabase/dashboard/reducers.js b/frontend/src/metabase/dashboard/reducers.js
index fe5bf641438..3b3f35cdf3e 100644
--- a/frontend/src/metabase/dashboard/reducers.js
+++ b/frontend/src/metabase/dashboard/reducers.js
@@ -4,13 +4,11 @@ import _ from "underscore";
 
 import Actions from "metabase/entities/actions";
 import Questions from "metabase/entities/questions";
-import Revisions from "metabase/entities/revisions";
 import { combineReducers, handleActions } from "metabase/lib/redux";
 
 import {
   ADD_CARD_TO_DASH,
   ADD_MANY_CARDS_TO_DASH,
-  CLEAR_CARD_DATA,
   INITIALIZE,
   MARK_NEW_CARD_SEEN,
   REMOVE_CARD_FROM_DASH,
@@ -24,7 +22,6 @@ import {
   UNDO_REMOVE_CARD_FROM_DASH,
   UPDATE_DASHCARD_VISUALIZATION_SETTINGS,
   UPDATE_DASHCARD_VISUALIZATION_SETTINGS_FOR_COLUMN,
-  fetchCardDataAction,
   fetchDashboard,
   tabsReducer,
 } from "./actions";
@@ -33,6 +30,7 @@ import {
   autoApplyFilters,
   dashboardId,
   dashboards,
+  dashcardData,
   editingDashboard,
   isAddParameterPopoverOpen,
   isNavigatingBackToDashboard,
@@ -162,32 +160,6 @@ const dashcards = handleActions(
   INITIAL_DASHBOARD_STATE.dashcards,
 );
 
-// Many of these slices are also updated by `tabsReducer` in `frontend/src/metabase/dashboard/actions/tabs.ts`
-const dashcardData = handleActions(
-  {
-    // clear existing dashboard data when loading a dashboard
-    [INITIALIZE]: {
-      next: (state, { payload: { clearCache = true } = {} }) =>
-        clearCache ? {} : state,
-    },
-    [fetchCardDataAction.fulfilled]: {
-      next: (state, { payload: { dashcard_id, card_id, result } }) =>
-        assocIn(state, [dashcard_id, card_id], result),
-    },
-    [CLEAR_CARD_DATA]: {
-      next: (state, { payload: { cardId, dashcardId } }) =>
-        assocIn(state, [dashcardId, cardId]),
-    },
-    [Questions.actionTypes.UPDATE]: (state, { payload: { object: card } }) =>
-      _.mapObject(state, dashboardData => dissoc(dashboardData, card.id)),
-    [Revisions.actionTypes.REVERT]: (state, { payload: revision }) =>
-      _.mapObject(state, dashboardData =>
-        dissoc(dashboardData, revision.model_id),
-      ),
-  },
-  INITIAL_DASHBOARD_STATE.dashcardData,
-);
-
 const draftParameterValues = handleActions(
   {
     [INITIALIZE]: {
-- 
GitLab