Repository: metabase/metabase
Pull request #54518: Smarter cards edition routing (followup on VIZ-470)
Author: login: lorem--ipsum
Labels: name: no-backport
description: Do not backport this PR to any branch
name: .Team/DashViz
description: DEPRECATED  Please use .Team/UXWest instead
Created: 2025-02-28T16:10:13Z | Merged: 2025-03-04T18:15:50Z

In #54416 we restrict the visualizations accessible from the visualizer. However dashboard cards created from a question still need to be editable by the visualizer. This PR 
 - makes it so the distinction between what can be edited in the visualizer and what cannot is based on the dashboard card content, not on its nature
 - initializes the visualizer modal with the question that triggered its opening, _if_ the display supports visualizer, otherwise it changes it to `bar`
 - resets the selected columns when switching to another visualization to avoid stuff like [VIZ-292: Data manager sometimes keeps selected state on unused cards](https://linear.app/metabase/issue/VIZ-292/data-manager-sometimes-keeps-selected-state-on-unused-cards) and [VIZ-469: Switching to a pie chart from a multi question viz causes drag and drop issues](https://linear.app/metabase/issue/VIZ-469/switching-to-a-pie-chart-from-a-multi-question-viz-causes-drag-and)


Diff:
diff --git a/frontend/src/metabase/components/EditBar/EditBar.styled.tsx b/frontend/src/metabase/components/EditBar/EditBar.styled.tsx
index 1f5f73881498..48aa849b71a4 100644
--- a/frontend/src/metabase/components/EditBar/EditBar.styled.tsx
+++ b/frontend/src/metabase/components/EditBar/EditBar.styled.tsx
@@ -1,4 +1,6 @@
 // eslint-disable-next-line no-restricted-imports
+import isPropValid from "@emotion/is-prop-valid";
+// eslint-disable-next-line no-restricted-imports
 import styled from "@emotion/styled";
 
 import ButtonsS from "metabase/css/components/buttons.module.css";
@@ -6,7 +8,9 @@ import { alpha, color } from "metabase/lib/colors";
 import { FullWidthContainer } from "metabase/styled-components/layout/FullWidthContainer";
 import { Icon } from "metabase/ui";
 
-export const Root = styled(FullWidthContainer)<{ admin: boolean }>`
+export const Root = styled(FullWidthContainer, {
+  shouldForwardProp: isPropValid,
+})<{ admin: boolean }>`
   display: flex;
   align-items: center;
   justify-content: space-between;
diff --git a/frontend/src/metabase/dashboard/components/DashCard/DashCardActionsPanel/DashCardActionsPanel.tsx b/frontend/src/metabase/dashboard/components/DashCard/DashCardActionsPanel/DashCardActionsPanel.tsx
index 53ced7e9ee09..6acd6f0dbc48 100644
--- a/frontend/src/metabase/dashboard/components/DashCard/DashCardActionsPanel/DashCardActionsPanel.tsx
+++ b/frontend/src/metabase/dashboard/components/DashCard/DashCardActionsPanel/DashCardActionsPanel.tsx
@@ -153,21 +153,16 @@ function DashCardActionsPanelInner({
   }
 
   if (!isLoading && !hasError) {
-    if (
-      dashcard &&
-      !isVirtualDashCard(dashcard) &&
-      dashboardCardSupportsVisualizer(dashcard)
-    ) {
+    if (dashcard && dashboardCardSupportsVisualizer(dashcard)) {
       buttons.push(
         <VisualizerButton
           key="visualizer-button"
-          series={series}
+          card={series[0].card}
           dashcard={dashcard}
+          columns={series[0].data.cols}
         />,
       );
-    }
-
-    if (!disableSettingsConfig) {
+    } else if (!disableSettingsConfig) {
       buttons.push(
         <ChartSettingsButton
           key="chart-settings-button"
diff --git a/frontend/src/metabase/dashboard/components/DashCard/DashCardActionsPanel/VisualizerButton/VisualizerButton.tsx b/frontend/src/metabase/dashboard/components/DashCard/DashCardActionsPanel/VisualizerButton/VisualizerButton.tsx
index e8a4b552ad35..d4d7317c91c5 100644
--- a/frontend/src/metabase/dashboard/components/DashCard/DashCardActionsPanel/VisualizerButton/VisualizerButton.tsx
+++ b/frontend/src/metabase/dashboard/components/DashCard/DashCardActionsPanel/VisualizerButton/VisualizerButton.tsx
@@ -6,17 +6,27 @@ import { replaceCardWithVisualization } from "metabase/dashboard/actions";
 import { useDispatch } from "metabase/lib/redux";
 import { VisualizerModal } from "metabase/visualizer/components/VisualizerModal";
 import { getInitialStateForCardDataSource } from "metabase/visualizer/utils";
-import type { DashboardCard, Series } from "metabase-types/api";
+import type {
+  Card,
+  DashboardCard,
+  DatasetColumn,
+  DatasetQuery,
+} from "metabase-types/api";
 import type { VisualizerHistoryItem } from "metabase-types/store/visualizer";
 
 import { DashCardActionButton } from "../DashCardActionButton";
 
 interface VisualizerButtonProps {
-  series: Series;
+  card: Card<DatasetQuery>;
   dashcard: DashboardCard;
+  columns: DatasetColumn[];
 }
 
-export function VisualizerButton({ series, dashcard }: VisualizerButtonProps) {
+export function VisualizerButton({
+  card,
+  dashcard,
+  columns,
+}: VisualizerButtonProps) {
   const [isOpen, { open, close }] = useDisclosure(false);
   const dispatch = useDispatch();
 
@@ -28,11 +38,11 @@ export function VisualizerButton({ series, dashcard }: VisualizerButtonProps) {
         };
       } else {
         return {
-          state: getInitialStateForCardDataSource(series[0]),
+          state: getInitialStateForCardDataSource(card, columns),
         };
       }
     }
-  }, [isOpen, dashcard, series]);
+  }, [isOpen, dashcard, card, columns]);
 
   const handleChangeVisualization = (visualization: VisualizerHistoryItem) => {
     dispatch(
diff --git a/frontend/src/metabase/dashboard/components/DashCard/DashCardVisualization.tsx b/frontend/src/metabase/dashboard/components/DashCard/DashCardVisualization.tsx
index b1bcc5bf6de0..c4cd3a051eae 100644
--- a/frontend/src/metabase/dashboard/components/DashCard/DashCardVisualization.tsx
+++ b/frontend/src/metabase/dashboard/components/DashCard/DashCardVisualization.tsx
@@ -35,6 +35,7 @@ import type {
   Dashboard,
   DashboardCard,
   Dataset,
+  RawSeries,
   Series,
   VirtualCardDisplay,
   VisualizationSettings,
@@ -136,9 +137,7 @@ export function DashCardVisualization({
   downloadsEnabled,
   editDashboard,
 }: DashCardVisualizationProps) {
-  const datasets = useSelector(
-    state => getDashcardData(state, dashcard.id) ?? {},
-  );
+  const datasets = useSelector(state => getDashcardData(state, dashcard.id));
   const [
     isVisualizerModalOpen,
     { open: openVisualizerModal, close: closeVisualizerModal },
@@ -219,7 +218,7 @@ export function DashCardVisualization({
     );
 
     const dataSourceDatasets = Object.fromEntries(
-      Object.entries(datasets).map(([cardId, dataset]) => [
+      Object.entries(datasets ?? {}).map(([cardId, dataset]) => [
         `card:${cardId}`,
         dataset,
       ]),
@@ -263,8 +262,9 @@ export function DashCardVisualization({
 
   const visualizationOverlay = useMemo(() => {
     if (isClickBehaviorSidebarOpen) {
-      const disableClickBehavior =
-        getVisualizationRaw(series)?.disableClickBehavior;
+      const disableClickBehavior = getVisualizationRaw(
+        series as RawSeries,
+      )?.disableClickBehavior;
       if (isVirtualDashCard(dashcard) || disableClickBehavior) {
         const virtualDashcardType = getVirtualCardType(
           dashcard,
diff --git a/frontend/src/metabase/dashboard/components/QuestionPicker/QuestionList.tsx b/frontend/src/metabase/dashboard/components/QuestionPicker/QuestionList.tsx
index f4abfe0e84dd..397dab37cf46 100644
--- a/frontend/src/metabase/dashboard/components/QuestionPicker/QuestionList.tsx
+++ b/frontend/src/metabase/dashboard/components/QuestionPicker/QuestionList.tsx
@@ -1,8 +1,9 @@
-import { useEffect, useMemo, useState } from "react";
+import { type ComponentProps, useEffect, useMemo, useState } from "react";
 import { t } from "ttag";
 
 import {
   skipToken,
+  useGetCardQuery,
   useListCollectionItemsQuery,
   useSearchQuery,
 } from "metabase/api";
@@ -23,6 +24,7 @@ import { VisualizerModal } from "metabase/visualizer/components/VisualizerModal"
 import type { CardId, CollectionId } from "metabase-types/api";
 
 import S from "./QuestionList.module.css";
+import { convertCardToInitialState } from "./convert-question-to-initial-state";
 
 interface QuestionListProps {
   searchText: string;
@@ -169,14 +171,8 @@ export function QuestionList({
         />
       </Flex>
       {isVisualizerModalOpen && (
-        <VisualizerModal
-          initialState={{
-            state: {
-              display: list.find(item => item.id === visualizerModalCardId)
-                ?.display,
-            },
-            extraDataSources: [`card:${visualizerModalCardId}`],
-          }}
+        <VisualizerModalWithCardId
+          cardId={visualizerModalCardId}
           onSave={visualization => {
             dispatch(addCardWithVisualization({ visualization }));
             setVisualizerModalCardId(null);
@@ -187,3 +183,25 @@ export function QuestionList({
     </>
   );
 }
+
+const VisualizerModalWithCardId = (
+  props: { cardId: CardId } & ComponentProps<typeof VisualizerModal>,
+) => {
+  const { cardId, ...otherProps } = props;
+
+  const { data: card, isLoading: isQuestionLoading } = useGetCardQuery(
+    cardId ? { id: cardId } : skipToken,
+  );
+
+  // TODO improve loading state?
+  if (isQuestionLoading || !card) {
+    return null;
+  }
+
+  return (
+    <VisualizerModal
+      initialState={convertCardToInitialState(card)}
+      {...otherProps}
+    />
+  );
+};
diff --git a/frontend/src/metabase/dashboard/components/QuestionPicker/convert-question-to-initial-state.ts b/frontend/src/metabase/dashboard/components/QuestionPicker/convert-question-to-initial-state.ts
new file mode 100644
index 000000000000..cd62245112ba
--- /dev/null
+++ b/frontend/src/metabase/dashboard/components/QuestionPicker/convert-question-to-initial-state.ts
@@ -0,0 +1,48 @@
+import visualizations from "metabase/visualizations";
+import {
+  getInitialStateForCardDataSource,
+  isVisualizerDashboardCard,
+} from "metabase/visualizer/utils";
+import type {
+  Card,
+  DatasetColumn,
+  VisualizationDisplay,
+} from "metabase-types/api";
+import type {
+  VisualizerDataSourceId,
+  VisualizerHistoryItem,
+} from "metabase-types/store/visualizer";
+
+export function convertCardToInitialState(card: Card): {
+  state: Partial<VisualizerHistoryItem>;
+  extraDataSources: [VisualizerDataSourceId];
+} {
+  if (isVisualizerDashboardCard(card)) {
+    return {
+      state: {},
+      extraDataSources: [`card:${card.id}` as const],
+    };
+  }
+
+  const initialState = getInitialStateForCardDataSource(
+    card,
+    // TODO fix that
+    card.result_metadata as unknown as DatasetColumn[],
+  );
+
+  // if the visualization doesn't support the visualizer, default to bar chart
+  if (!visualizations.get(initialState.display!)?.supportsVisualizer) {
+    return {
+      state: {
+        ...initialState,
+        display: "bar" as VisualizationDisplay,
+      },
+      extraDataSources: [`card:${card.id}` as const],
+    };
+  }
+
+  return {
+    state: initialState,
+    extraDataSources: [`card:${card.id}` as const],
+  };
+}
diff --git a/frontend/src/metabase/dashboard/components/QuestionPicker/convert-question-to-initial-state.unit.spec.ts b/frontend/src/metabase/dashboard/components/QuestionPicker/convert-question-to-initial-state.unit.spec.ts
new file mode 100644
index 000000000000..c9e2ec67e834
--- /dev/null
+++ b/frontend/src/metabase/dashboard/components/QuestionPicker/convert-question-to-initial-state.unit.spec.ts
@@ -0,0 +1,126 @@
+import registerVisualizations from "metabase/visualizations/register";
+import { createMockCard, createMockField } from "metabase-types/api/mocks";
+
+import { convertCardToInitialState } from "./convert-question-to-initial-state";
+
+registerVisualizations();
+
+describe("convertQuestionToInitialState", () => {
+  it("should work with a visualizer dashboard card", () => {
+    expect(
+      convertCardToInitialState(
+        createMockCard({ visualization_settings: { visualization: "line" } }),
+      ),
+    ).toEqual({
+      state: {
+        visualization_settings: {
+          visualization: "line",
+        },
+      },
+      extraDataSources: ["card:1"],
+    });
+  });
+
+  it("should work with a visualization supported by the visualizer", () => {
+    expect(
+      convertCardToInitialState(
+        createMockCard({
+          display: "line",
+          visualization_settings: {
+            "graph.show_values": true,
+            "graph.label_value_frequency": "fit",
+            "graph.dimensions": ["CREATED_AT"],
+            column_settings: {
+              '["name","count"]': {
+                number_style: "currency",
+                currency_style: "code",
+              },
+            },
+            "graph.metrics": ["count"],
+          },
+          result_metadata: [
+            createMockField({
+              semantic_type: "type/CreationTimestamp",
+              name: "CREATED_AT",
+              effective_type: "type/DateTime",
+              id: 13,
+              display_name: "Created At: Month",
+              base_type: "type/DateTime",
+            }),
+            createMockField({
+              display_name: "Count",
+              semantic_type: "type/Quantity",
+              base_type: "type/BigInteger",
+              effective_type: "type/BigInteger",
+              name: "count",
+            }),
+          ],
+        }),
+      ),
+    ).toEqual({
+      state: {
+        display: "line",
+        columnValuesMapping: {
+          COLUMN_1: [
+            {
+              name: "COLUMN_1",
+              originalName: "CREATED_AT",
+              sourceId: "card:1",
+            },
+          ],
+          COLUMN_2: [
+            {
+              name: "COLUMN_2",
+              originalName: "count",
+              sourceId: "card:1",
+            },
+          ],
+        },
+        columns: [
+          {
+            base_type: "type/DateTime",
+            display_name: "Created At: Month",
+            effective_type: "type/DateTime",
+            field_ref: [
+              "field",
+              "COLUMN_1",
+              {
+                "base-type": "type/DateTime",
+              },
+            ],
+            id: 13,
+            name: "COLUMN_1",
+            semantic_type: "type/CreationTimestamp",
+          },
+          {
+            base_type: "type/BigInteger",
+            display_name: "Count",
+            effective_type: "type/BigInteger",
+            field_ref: [
+              "field",
+              "COLUMN_2",
+              {
+                "base-type": "type/BigInteger",
+              },
+            ],
+            name: "COLUMN_2",
+            semantic_type: "type/Quantity",
+          },
+        ],
+        settings: {
+          column_settings: {
+            '["name","count"]': {
+              currency_style: "code",
+              number_style: "currency",
+            },
+          },
+          "graph.dimensions": ["COLUMN_1"],
+          "graph.label_value_frequency": "fit",
+          "graph.metrics": ["COLUMN_2"],
+          "graph.show_values": true,
+        },
+      },
+      extraDataSources: ["card:1"],
+    });
+  });
+});
diff --git a/frontend/src/metabase/visualizer/utils/column.ts b/frontend/src/metabase/visualizer/utils/column.ts
index ccf775241b12..6269f77fd746 100644
--- a/frontend/src/metabase/visualizer/utils/column.ts
+++ b/frontend/src/metabase/visualizer/utils/column.ts
@@ -1,5 +1,5 @@
 import { isDate } from "metabase-lib/v1/types/utils/isa";
-import type { DatasetColumn } from "metabase-types/api";
+import type { DatasetColumn, FieldLiteral } from "metabase-types/api";
 import type {
   VisualizerColumnReference,
   VisualizerColumnValueSource,
@@ -71,8 +71,11 @@ export function copyColumn(name: string, column: DatasetColumn): DatasetColumn {
   const copy: DatasetColumn = {
     ...column,
     name,
-    // @ts-expect-error TODO name is supposed to be a number (FieldId) but here it's a string?
-    field_ref: ["field", name, { "base-type": column.base_type }],
+    field_ref: [
+      "field",
+      name,
+      { "base-type": column.base_type },
+    ] as FieldLiteral,
   };
 
   // TODO Remove manual MBQL manipulation
@@ -80,9 +83,11 @@ export function copyColumn(name: string, column: DatasetColumn): DatasetColumn {
     const opts = copy.field_ref?.[2];
     const temporalUnit = maybeGetTemporalUnit(column);
     if (temporalUnit && opts) {
-      opts["temporal-unit"] = temporalUnit;
+      // TODO fix type
+      (opts as any)["temporal-unit"] = temporalUnit;
     }
-    copy.field_ref = ["field", name, opts];
+    // TODO fix type
+    copy.field_ref = ["field", name, opts as any];
   }
 
   return copy;
diff --git a/frontend/src/metabase/visualizer/utils/dashboard-card-supports-visualizer.ts b/frontend/src/metabase/visualizer/utils/dashboard-card-supports-visualizer.ts
index 9ac27b06e690..6a4905869b3a 100644
--- a/frontend/src/metabase/visualizer/utils/dashboard-card-supports-visualizer.ts
+++ b/frontend/src/metabase/visualizer/utils/dashboard-card-supports-visualizer.ts
@@ -4,11 +4,16 @@ import type { DashboardCard } from "metabase-types/api";
 import { isVisualizerDashboardCard } from "./is-visualizer-dashboard-card";
 
 export function dashboardCardSupportsVisualizer(dashcard: DashboardCard) {
-  if (!isVisualizerDashboardCard(dashcard)) {
-    return false;
+  if (isVisualizerDashboardCard(dashcard)) {
+    return visualizations.get(
+      (dashcard!.visualization_settings!.visualization as any).display,
+    )?.supportsVisualizer;
   }
 
-  return visualizations.get(
-    (dashcard!.visualization_settings!.visualization as any).display,
-  )?.supportsVisualizer;
+  const display = dashcard.card.display;
+  if (display) {
+    return visualizations.get(display)!.supportsVisualizer;
+  }
+
+  return false;
 }
diff --git a/frontend/src/metabase/visualizer/utils/is-visualizer-dashboard-card.ts b/frontend/src/metabase/visualizer/utils/is-visualizer-dashboard-card.ts
index a3a6419bb5a6..1fcdf4124491 100644
--- a/frontend/src/metabase/visualizer/utils/is-visualizer-dashboard-card.ts
+++ b/frontend/src/metabase/visualizer/utils/is-visualizer-dashboard-card.ts
@@ -1,7 +1,12 @@
-import type { DashboardCard, QuestionDashboardCard } from "metabase-types/api";
+import type {
+  Card,
+  DashboardCard,
+  DatasetQuery,
+  QuestionDashboardCard,
+} from "metabase-types/api";
 
 export function isVisualizerDashboardCard(
-  dashcard: DashboardCard | undefined,
+  dashcard: DashboardCard | Card<DatasetQuery> | undefined,
 ): dashcard is QuestionDashboardCard {
   if (!dashcard?.visualization_settings) {
     return false;
diff --git a/frontend/src/metabase/visualizer/utils/merge-data.ts b/frontend/src/metabase/visualizer/utils/merge-data.ts
index dc16b1df23d7..ad7314cf1286 100644
--- a/frontend/src/metabase/visualizer/utils/merge-data.ts
+++ b/frontend/src/metabase/visualizer/utils/merge-data.ts
@@ -1,11 +1,6 @@
 import _ from "underscore";
 
-import type {
-  Dataset,
-  DatasetColumn,
-  DatasetData,
-  RowValues,
-} from "metabase-types/api";
+import type { Dataset, DatasetColumn, RowValues } from "metabase-types/api";
 import type {
   VisualizerColumnValueSource,
   VisualizerDataSource,
@@ -27,7 +22,7 @@ export function mergeVisualizerData({
   columnValuesMapping,
   datasets,
   dataSources,
-}: MergeVisualizerSeries): DatasetData {
+}: MergeVisualizerSeries) {
   const referencedColumns = extractReferencedColumns(columnValuesMapping);
 
   const referencedColumnValuesMap: Record<string, RowValues> = {};
diff --git a/frontend/src/metabase/visualizer/utils/misc.ts b/frontend/src/metabase/visualizer/utils/misc.ts
index db46f04591e2..b2e2a2ad286e 100644
--- a/frontend/src/metabase/visualizer/utils/misc.ts
+++ b/frontend/src/metabase/visualizer/utils/misc.ts
@@ -6,7 +6,6 @@ import { isDate, isNumeric } from "metabase-lib/v1/types/utils/isa";
 import type {
   Card,
   DatasetColumn,
-  SingleSeries,
   VisualizationDisplay,
   VisualizationSettings,
 } from "metabase-types/api";
@@ -86,10 +85,10 @@ function areAreaBarLineSeriesCompatible(
   );
 }
 
-export function getInitialStateForCardDataSource({
-  card,
-  data,
-}: SingleSeries): VisualizerHistoryItem {
+export function getInitialStateForCardDataSource(
+  card: Card,
+  columns: DatasetColumn[],
+): VisualizerHistoryItem {
   const state: VisualizerHistoryItem = {
     display: card.display,
     columns: [],
@@ -98,7 +97,7 @@ export function getInitialStateForCardDataSource({
   };
   const dataSource = createDataSource("card", card.id, card.name);
 
-  data.cols.forEach(column => {
+  columns.forEach(column => {
     const columnRef = createVisualizerColumnReference(
       dataSource,
       column,
@@ -120,14 +119,14 @@ export function getInitialStateForCardDataSource({
         return [
           setting,
           originalValue.map(originalColumnName => {
-            const index = data.cols.findIndex(
+            const index = columns.findIndex(
               col => col.name === originalColumnName,
             );
             return state.columns[index].name;
           }),
         ];
       } else {
-        const index = data.cols.findIndex(col => col.name === originalValue);
+        const index = columns.findIndex(col => col.name === originalValue);
         return [setting, state.columns[index].name];
       }
     })
diff --git a/frontend/src/metabase/visualizer/utils/update-settings-for-display.ts b/frontend/src/metabase/visualizer/utils/update-settings-for-display.ts
new file mode 100644
index 000000000000..0280631b4172
--- /dev/null
+++ b/frontend/src/metabase/visualizer/utils/update-settings-for-display.ts
@@ -0,0 +1,94 @@
+import { isCartesianChart } from "metabase/visualizations";
+import type {
+  DatasetColumn,
+  VisualizationDisplay,
+  VisualizationSettings,
+} from "metabase-types/api";
+import type { VisualizerColumnValueSource } from "metabase-types/store/visualizer";
+
+export function updateSettingsForDisplay(
+  columnValuesMapping: Record<string, VisualizerColumnValueSource[]>,
+  columns: DatasetColumn[],
+  settings: VisualizationSettings,
+  sourceDisplay: VisualizationDisplay | null,
+  targetDisplay: VisualizationDisplay | null,
+): {
+  columnValuesMapping: Record<string, VisualizerColumnValueSource[]>;
+  columns: DatasetColumn[];
+  settings: VisualizationSettings;
+} {
+  if (!sourceDisplay || !targetDisplay) {
+    return {
+      columnValuesMapping,
+      columns,
+      settings,
+    };
+  }
+
+  const sourceIsCartesian = isCartesianChart(sourceDisplay);
+  const targetIsCartesian = isCartesianChart(targetDisplay);
+
+  if (sourceIsCartesian) {
+    // cartesian -> cartesian
+    if (targetIsCartesian) {
+      return {
+        columnValuesMapping,
+        columns,
+        settings,
+      };
+    }
+
+    // cartesian -> pie
+    if (targetDisplay === "pie") {
+      const {
+        "graph.metrics": metrics,
+        "graph.dimensions": dimensions,
+        ...otherSettings
+      } = settings;
+
+      return {
+        columnValuesMapping,
+        columns,
+        settings: {
+          ...otherSettings,
+          "pie.metric": metrics?.[0] ?? "",
+          "pie.dimension": dimensions?.[0] ?? "",
+        },
+      };
+    }
+  }
+
+  if (sourceDisplay === "pie") {
+    // pie -> cartesian
+    if (targetIsCartesian) {
+      const {
+        "pie.metric": metric,
+        "pie.dimension": dimensions,
+        ...otherSettings
+      } = settings;
+
+      return {
+        columnValuesMapping,
+        columns,
+        settings: {
+          ...otherSettings,
+          "graph.metrics": [metric].filter(Boolean) as string[],
+          "graph.dimensions": [dimensions].filter(Boolean) as string[],
+        },
+      };
+    } else {
+      // pie -> pie
+      return {
+        columnValuesMapping,
+        columns,
+        settings,
+      };
+    }
+  }
+
+  return {
+    columnValuesMapping,
+    columns,
+    settings,
+  };
+}
diff --git a/frontend/src/metabase/visualizer/utils/update-settings-for-display.unit.spec.ts b/frontend/src/metabase/visualizer/utils/update-settings-for-display.unit.spec.ts
new file mode 100644
index 000000000000..ce37e03c65ed
--- /dev/null
+++ b/frontend/src/metabase/visualizer/utils/update-settings-for-display.unit.spec.ts
@@ -0,0 +1,118 @@
+import registerVisualizations from "metabase/visualizations/register";
+import { createMockColumn } from "metabase-types/api/mocks";
+import type { VisualizerColumnValueSource } from "metabase-types/store/visualizer";
+
+import { updateSettingsForDisplay } from "./update-settings-for-display";
+
+registerVisualizations();
+
+describe("updateSettingsForDisplay", () => {
+  const columnValuesMapping = {
+    COLUMN_1: [
+      { sourceId: "card:45", originalName: "CREATED_AT", name: "COLUMN_1" },
+    ],
+    COLUMN_2: [
+      { sourceId: "card:45", originalName: "count", name: "COLUMN_2" },
+    ],
+  } as Record<string, VisualizerColumnValueSource[]>;
+
+  const columns = [
+    createMockColumn({
+      semantic_type: "type/CreationTimestamp",
+      name: "COLUMN_1",
+      field_ref: [
+        "field",
+        "COLUMN_1",
+        { "base-type": "type/DateTime", "temporal-unit": "month" },
+      ],
+      effective_type: "type/DateTime",
+      id: 13,
+      display_name: "Created At: Month",
+      base_type: "type/DateTime",
+    }),
+    createMockColumn({
+      display_name: "Count",
+      semantic_type: "type/Quantity",
+      field_ref: ["field", "COLUMN_2", { "base-type": "type/BigInteger" }],
+      base_type: "type/BigInteger",
+      effective_type: "type/BigInteger",
+      name: "COLUMN_2",
+    }),
+  ];
+
+  it("should return the same settings if sourceDisplay or targetDisplay is null", () => {
+    const settings = {};
+    const sourceDisplay = null;
+    const targetDisplay = null;
+    const result = updateSettingsForDisplay(
+      columnValuesMapping,
+      columns,
+      settings,
+      sourceDisplay,
+      targetDisplay,
+    );
+    expect(result).toEqual({ columnValuesMapping, columns, settings });
+  });
+
+  it("should return the same settings if sourceDisplay and targetDisplay are cartesian", () => {
+    const settings = {};
+    const sourceDisplay = "bar";
+    const targetDisplay = "bar";
+    const result = updateSettingsForDisplay(
+      columnValuesMapping,
+      columns,
+      settings,
+      sourceDisplay,
+      targetDisplay,
+    );
+    expect(result).toEqual({ columnValuesMapping, columns, settings });
+  });
+
+  it("should work if sourceDisplay is cartesian and targetDisplay is pie", () => {
+    const settings = {
+      "graph.metrics": ["count"],
+      "graph.dimensions": ["category"],
+    };
+    const sourceDisplay = "line";
+    const targetDisplay = "pie";
+    const result = updateSettingsForDisplay(
+      columnValuesMapping,
+      columns,
+      settings,
+      sourceDisplay,
+      targetDisplay,
+    );
+    expect(result).toEqual({
+      columnValuesMapping,
+      columns,
+      settings: {
+        "pie.metric": "count",
+        "pie.dimension": "category",
+      },
+    });
+  });
+
+  it("should work if sourceDisplay is pie and targetDisplay is cartesian", () => {
+    const settings = {
+      "pie.metric": "count",
+      "pie.dimension": "category",
+    };
+    const sourceDisplay = "pie";
+    const targetDisplay = "line";
+    const result = updateSettingsForDisplay(
+      columnValuesMapping,
+      columns,
+      settings,
+      sourceDisplay,
+      targetDisplay,
+    );
+    expect(result).toEqual({
+      columnValuesMapping,
+      columns,
+      settings: {
+        "graph.metrics": ["count"],
+        "graph.dimensions": ["category"],
+      },
+    });
+  });
+});
diff --git a/frontend/src/metabase/visualizer/visualizer.slice.ts b/frontend/src/metabase/visualizer/visualizer.slice.ts
index 7a00ee5d8a3e..621af611f843 100644
--- a/frontend/src/metabase/visualizer/visualizer.slice.ts
+++ b/frontend/src/metabase/visualizer/visualizer.slice.ts
@@ -41,6 +41,7 @@ import {
   getInitialStateForCardDataSource,
   parseDataSourceId,
 } from "./utils";
+import { updateSettingsForDisplay } from "./utils/update-settings-for-display";
 import {
   addMetricColumnToCartesianChart,
   cartesianDropHandler,
@@ -186,6 +187,18 @@ const visualizerHistoryItemSlice = createSlice({
       state.settings["card.title"] = action.payload;
     },
     setDisplay: (state, action: PayloadAction<VisualizationDisplay | null>) => {
+      const { columnValuesMapping, columns, settings } =
+        updateSettingsForDisplay(
+          state.columnValuesMapping,
+          state.columns,
+          state.settings,
+          state.display,
+          action.payload,
+        );
+
+      state.columnValuesMapping = columnValuesMapping;
+      state.columns = columns;
+      state.settings = settings;
       state.display = action.payload;
     },
     updateSettings: (state, action: PayloadAction<VisualizationSettings>) => {
@@ -483,7 +496,7 @@ function maybeCombineDataset(
     !state.display ||
     (card.display === state.display && state.columns.length === 0)
   ) {
-    return getInitialStateForCardDataSource({ card, ...dataset });
+    return getInitialStateForCardDataSource(card, dataset.data.cols);
   }
 
   if (


Reviews:
user: login: kulyk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T18:05:34Z
user: login: lorem--ipsum
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T10:20:27Z
user: login: lorem--ipsum
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T10:22:30Z
user: login: lorem--ipsum
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T10:23:09Z
user: login: lorem--ipsum
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T10:26:01Z

Review comments:
diff_hunk: @@ -0,0 +1,127 @@
+import registerVisualizations from "metabase/visualizations/register";
+import type { Field } from "metabase-types/api";
+import { createMockCard } from "metabase-types/api/mocks";
+
+import { convertCardToInitialState } from "./convert-question-to-initial-state";
+
+registerVisualizations();
+
+describe("convertQuestionToInitialState", () => {
+  it("should work with a visualizer dashboard card", () => {
+    expect(
+      convertCardToInitialState(
+        createMockCard({ visualization_settings: { visualization: "line" } }),
+      ),
+    ).toEqual({
+      state: {
+        visualization_settings: {
+          visualization: "line",
+        },
+      },
+      extraDataSources: ["card:1"],
+    });
+  });
+
+  it("should work with a visualization supported by the visualizer", () => {
+    expect(
+      convertCardToInitialState(
+        createMockCard({
+          display: "line",
+          visualization_settings: {
+            "graph.show_values": true,
+            "graph.label_value_frequency": "fit",
+            "graph.dimensions": ["CREATED_AT"],
+            column_settings: {
+              '["name","count"]': {
+                number_style: "currency",
+                currency_style: "code",
+              },
+            },
+            "graph.metrics": ["count"],
+          },
+          result_metadata: [
+            {
+              semantic_type: "type/CreationTimestamp",
+              name: "CREATED_AT",
+              effective_type: "type/DateTime",
+              id: 13,
+              display_name: "Created At: Month",
+              base_type: "type/DateTime",
+            },
path: frontend/src/metabase/dashboard/components/QuestionPicker/convert-question-to-initial-state.unit.spec.ts
user: login: kulyk
body: Let's use [`createMockField`](https://github.com/metabase/metabase/blob/954b21a88a05325225cd419ddbe51fb8526bd42d/frontend/src/metabase-types/api/mocks/field.ts#L12) here
created_at: 2025-03-03T17:48:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -83,7 +83,7 @@ export const getDashcardDataMap = (state: State) =>
 export const getDashcardData = createSelector(
   [getDashcardDataMap, (_state: State, dashcardId: DashCardId) => dashcardId],
   (dashcardDataMap, dashcardId) => {
-    return dashcardDataMap[dashcardId];
+    return dashcardDataMap[dashcardId] ?? {};
path: frontend/src/metabase/dashboard/selectors.ts
user: login: kulyk
body: I'm worried this might cause behavior changes in dashboards (e.g. if there're null checks somewhere). Can we keep it as is?
created_at: 2025-03-03T17:49:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -71,18 +71,23 @@ export function copyColumn(name: string, column: DatasetColumn): DatasetColumn {
   const copy: DatasetColumn = {
     ...column,
     name,
-    // @ts-expect-error TODO name is supposed to be a number (FieldId) but here it's a string?
-    field_ref: ["field", name, { "base-type": column.base_type }],
+    field_ref: [
+      "field",
+      name,
+      { "base-type": column.base_type },
+    ] as FieldLiteral,
path: frontend/src/metabase/visualizer/utils/column.ts
user: login: kulyk
body: Just FYI, that we'd need to refactor this later, so we're not doing manual MBQL manipulation (nothing actionable, just wanted to let you know)
created_at: 2025-03-03T17:52:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,118 @@
+import registerVisualizations from "metabase/visualizations/register";
+import type { Field } from "metabase-types/api";
+import type { VisualizerColumnValueSource } from "metabase-types/store/visualizer";
+
+import { updateSettingsForDisplay } from "./update-settings-for-display";
+
+registerVisualizations();
+
+describe("updateSettingsForDisplay", () => {
+  const columnValuesMapping = {
+    COLUMN_1: [
+      { sourceId: "card:45", originalName: "CREATED_AT", name: "COLUMN_1" },
+    ],
+    COLUMN_2: [
+      { sourceId: "card:45", originalName: "count", name: "COLUMN_2" },
+    ],
+  } as Record<string, VisualizerColumnValueSource[]>;
+
+  const columns = [
+    {
+      semantic_type: "type/CreationTimestamp",
+      name: "COLUMN_1",
+      field_ref: [
+        "field",
+        "COLUMN_1",
+        { "base-type": "type/DateTime", "temporal-unit": "month" },
+      ],
+      effective_type: "type/DateTime",
+      id: 13,
+      display_name: "Created At: Month",
+      base_type: "type/DateTime",
+    },
path: frontend/src/metabase/visualizer/utils/update-settings-for-display.unit.spec.ts
user: login: kulyk
body: Let's use [`createMockField`](https://github.com/metabase/metabase/blob/954b21a88a05325225cd419ddbe51fb8526bd42d/frontend/src/metabase-types/api/mocks/field.ts#L12) here
created_at: 2025-03-03T17:53:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,94 @@
+import { isCartesianChart } from "metabase/visualizations";
+import type {
+  DatasetColumn,
+  VisualizationDisplay,
+  VisualizationSettings,
+} from "metabase-types/api";
+import type { VisualizerColumnValueSource } from "metabase-types/store/visualizer";
+
+export function updateSettingsForDisplay(
+  columnValuesMapping: Record<string, VisualizerColumnValueSource[]>,
+  columns: DatasetColumn[],
+  settings: VisualizationSettings,
+  sourceDisplay: VisualizationDisplay | null,
+  targetDisplay: VisualizationDisplay | null,
+): {
+  columnValuesMapping: Record<string, VisualizerColumnValueSource[]>;
+  columns: DatasetColumn[];
+  settings: VisualizationSettings;
+} {
path: frontend/src/metabase/visualizer/utils/update-settings-for-display.ts
user: login: kulyk
body: nit: should it just return `VisualizationSettings`? Can't imagine a scenario when we'd want to update `columns` or `columnValuesMapping`
created_at: 2025-03-03T17:55:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -86,10 +86,10 @@ function areAreaBarLineSeriesCompatible(
   );
 }
 
-export function getInitialStateForCardDataSource({
-  card,
-  data,
-}: SingleSeries): VisualizerHistoryItem {
+export function getInitialStateForCardDataSource(
+  card: Card<DatasetQuery>,
+  columns: DatasetColumn[],
+): VisualizerHistoryItem {
path: frontend/src/metabase/visualizer/utils/misc.ts
user: login: kulyk
body: Curios why we needed to use `DatasetQuery` explicitly here?
created_at: 2025-03-03T17:56:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -153,21 +153,21 @@ function DashCardActionsPanelInner({
   }
 
   if (!isLoading && !hasError) {
-    if (
-      dashcard &&
-      !isVirtualDashCard(dashcard) &&
-      dashboardCardSupportsVisualizer(dashcard)
-    ) {
+    const supportsVisualizer =
+      dashcard && dashboardCardSupportsVisualizer(dashcard);
+
+    if (dashcard && !isVirtualDashCard(dashcard) && supportsVisualizer) {
path: frontend/src/metabase/dashboard/components/DashCard/DashCardActionsPanel/DashCardActionsPanel.tsx
user: login: kulyk
body: We should be able to simplify this condition: now we're checking if `dashcard` exists twice and if `dashboardCardSupportsVisualizer`, then it can't be a virtual card
created_at: 2025-03-03T17:59:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -187,3 +194,35 @@ export function QuestionList({
     </>
   );
 }
+
+const VisualizerModalWithCardId = (
+  props: { cardId: CardId } & ComponentProps<typeof VisualizerModal>,
+) => {
+  const { cardId, ...otherProps } = props;
+  const [selectedCard, setSelectedCard] = useState<Card<DatasetQuery>>();
+  const dispatch = useDispatch();
+
+  const loadCard = useCallback(async () => {
+    const { data } = await dispatch(
+      cardApi.endpoints.getCard.initiate({ id: cardId }),
+    );
+
+    setSelectedCard(data);
+  }, [dispatch, cardId]);
+
+  useEffect(() => {
+    loadCard();
+  }, [loadCard]);
path: frontend/src/metabase/dashboard/components/QuestionPicker/QuestionList.tsx
user: login: kulyk
body: We should use the `useGetCardQuery` hook here, please take a look at an [example](https://github.com/metabase/metabase/blob/0252fb2aa01929d5926f396752856923d3802fca/frontend/src/metabase/common/components/QuestionPicker/components/QuestionPicker.tsx#L65)
created_at: 2025-03-03T18:02:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -71,18 +71,23 @@ export function copyColumn(name: string, column: DatasetColumn): DatasetColumn {
   const copy: DatasetColumn = {
     ...column,
     name,
-    // @ts-expect-error TODO name is supposed to be a number (FieldId) but here it's a string?
-    field_ref: ["field", name, { "base-type": column.base_type }],
+    field_ref: [
+      "field",
+      name,
+      { "base-type": column.base_type },
+    ] as FieldLiteral,
path: frontend/src/metabase/visualizer/utils/column.ts
user: login: lorem--ipsum
body: Could you expand a bit on that?
created_at: 2025-03-04T10:20:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,118 @@
+import registerVisualizations from "metabase/visualizations/register";
+import type { Field } from "metabase-types/api";
+import type { VisualizerColumnValueSource } from "metabase-types/store/visualizer";
+
+import { updateSettingsForDisplay } from "./update-settings-for-display";
+
+registerVisualizations();
+
+describe("updateSettingsForDisplay", () => {
+  const columnValuesMapping = {
+    COLUMN_1: [
+      { sourceId: "card:45", originalName: "CREATED_AT", name: "COLUMN_1" },
+    ],
+    COLUMN_2: [
+      { sourceId: "card:45", originalName: "count", name: "COLUMN_2" },
+    ],
+  } as Record<string, VisualizerColumnValueSource[]>;
+
+  const columns = [
+    {
+      semantic_type: "type/CreationTimestamp",
+      name: "COLUMN_1",
+      field_ref: [
+        "field",
+        "COLUMN_1",
+        { "base-type": "type/DateTime", "temporal-unit": "month" },
+      ],
+      effective_type: "type/DateTime",
+      id: 13,
+      display_name: "Created At: Month",
+      base_type: "type/DateTime",
+    },
path: frontend/src/metabase/visualizer/utils/update-settings-for-display.unit.spec.ts
user: login: lorem--ipsum
body: I used `createMockColumn`
created_at: 2025-03-04T10:22:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,94 @@
+import { isCartesianChart } from "metabase/visualizations";
+import type {
+  DatasetColumn,
+  VisualizationDisplay,
+  VisualizationSettings,
+} from "metabase-types/api";
+import type { VisualizerColumnValueSource } from "metabase-types/store/visualizer";
+
+export function updateSettingsForDisplay(
+  columnValuesMapping: Record<string, VisualizerColumnValueSource[]>,
+  columns: DatasetColumn[],
+  settings: VisualizationSettings,
+  sourceDisplay: VisualizationDisplay | null,
+  targetDisplay: VisualizationDisplay | null,
+): {
+  columnValuesMapping: Record<string, VisualizerColumnValueSource[]>;
+  columns: DatasetColumn[];
+  settings: VisualizationSettings;
+} {
path: frontend/src/metabase/visualizer/utils/update-settings-for-display.ts
user: login: lorem--ipsum
body: From what I understand, we also want to uypdate what's selected and what isn't, and that is what `columnValuesMapping` and  `columns` control, correct?
created_at: 2025-03-04T10:23:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -86,10 +86,10 @@ function areAreaBarLineSeriesCompatible(
   );
 }
 
-export function getInitialStateForCardDataSource({
-  card,
-  data,
-}: SingleSeries): VisualizerHistoryItem {
+export function getInitialStateForCardDataSource(
+  card: Card<DatasetQuery>,
+  columns: DatasetColumn[],
+): VisualizerHistoryItem {
path: frontend/src/metabase/visualizer/utils/misc.ts
user: login: lorem--ipsum
body: No reason 😅
created_at: 2025-03-04T10:26:01Z
author_association: CONTRIBUTOR

PR comments:
user: login: trunk-io[bot]
created_at: 2025-02-28T16:55:22Z
body: <!-- Trunk Test Analytics -->


[![Static Badge](https://img.shields.io/badge/23193_tests-c0e0a707-purple)](https://app.trunk.io/metabase/flaky-tests/pr/54518?repo=metabase/metabase&commitHash=c0e0a707635252954ad2aa3eae9a1ea70e158467&utm_source=gh-comment) &ensp; [![Static Badge](https://img.shields.io/badge/17-failed-crimson)](https://app.trunk.io/metabase/flaky-tests/pr/54518?repo=metabase/metabase&commitHash=c0e0a707635252954ad2aa3eae9a1ea70e158467&utm_source=gh-comment&runConclusion=FAILURE) &ensp; [![Static Badge](https://img.shields.io/badge/0-flaky-red)](https://app.trunk.io/metabase/flaky-tests/pr/54518?repo=metabase/metabase&commitHash=c0e0a707635252954ad2aa3eae9a1ea70e158467&utm_source=gh-comment&runConclusion=FAILURE) &ensp; [![Static Badge](https://img.shields.io/badge/0-quarantined-yellow)](https://app.trunk.io/metabase/flaky-tests/pr/54518?repo=metabase/metabase&commitHash=c0e0a707635252954ad2aa3eae9a1ea70e158467&utm_source=gh-comment&runConclusion=QUARANTINED)


| Failed Test | Failure Summary | Logs |
| --- | --- | --- |
| <code>scenarios > dashboard cards > visualization options should show the ellipsis even with an empty card title on visualizations with noHeader (metabas...</code> | The expected element \`.Icon-palette\` was not found within 4000ms. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/54518?repo=metabase/metabase&commitHash=c0e0a707635252954ad2aa3eae9a1ea70e158467&utm_source=gh-comment&runConclusion=FAILURE&runId=f1247c5b-ea74-5491-97fb-1acf560816b5) |
| <code>scenarios > visualizations > line chart tooltip of combined dashboard cards (multi-series) should show the correct column title (metabase\#16249 cu...</code> | The expected element \`.Icon-palette\` was not found within 4000ms. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/54518?repo=metabase/metabase&commitHash=c0e0a707635252954ad2aa3eae9a1ea70e158467&utm_source=gh-comment&runConclusion=FAILURE&runId=6a87729d-a36a-5250-b817-b63bbeead33f) |
| <code>scenarios > visualizations > line chart tooltip of combined dashboard cards (multi-series) should show the correct column title (metabase\#16249 re...</code> | The expected element \`.Icon-palette\` was not found within 4000ms. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/54518?repo=metabase/metabase&commitHash=c0e0a707635252954ad2aa3eae9a1ea70e158467&utm_source=gh-comment&runConclusion=FAILURE&runId=f4414335-fcb0-5826-b7b8-2a0fdf24377c) |
| <code>scenarios > visualizations > legend should toggle series visibility on a dashboard</code> | The test timed out waiting for a label with the text 'Show visualization options' to be found. | [Logs ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/54518?repo=metabase/metabase&commitHash=c0e0a707635252954ad2aa3eae9a1ea70e158467&utm_source=gh-comment&runConclusion=FAILURE&runId=ce24c0f0-6a59-57fc-b8fa-cc53841ddebb) |

[... and 13 more](https://app.trunk.io/metabase/flaky-tests/pr/54518?repo=metabase/metabase&commitHash=c0e0a707635252954ad2aa3eae9a1ea70e158467&utm_source=gh-comment&runConclusion=FAILURE)
<sub>

[View Full Report ↗︎](https://app.trunk.io/metabase/flaky-tests/pr/54518?repo=metabase/metabase&commitHash=c0e0a707635252954ad2aa3eae9a1ea70e158467&utm_source=gh-comment) ⋅ [Docs](https://docs.trunk.io/flaky-tests?utm_source=gh-comment)

</sub>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-04T18:27:32Z
body: 
  ## e2e tests failed on `d3a77374fe5ac566bcb27cac6d86f01b271672e1-1`

  [e2e test run](https://github.com/metabase/metabase/actions/runs/13660174409?pr=54518)

  File | Test Name
  ---- | ---------
  `line-bar-tooltips.cy.spec.js` | scenarios > visualizations > line/bar chart > tooltips > > single series question on dashboard > should show updated column titles in tooltips after editing them via Visualization Options
`line-bar-tooltips.cy.spec.js` | scenarios > visualizations > line/bar chart > tooltips > > single series question on dashboard with added series > should show updated column titles in tooltips after editing them via Visualization Options
`line-bar-tooltips.cy.spec.js` | scenarios > visualizations > line/bar chart > tooltips > > multi series question on dashboard > should show updated column titles in tooltips after editing them via Visualization Options
`line-bar-tooltips.cy.spec.js` | scenarios > visualizations > line/bar chart > tooltips > > multi series question on dashboard with added question > should show updated column titles in tooltips after editing them via Visualization Options
`line-bar-tooltips.cy.spec.js` | scenarios > visualizations > line/bar chart > tooltips > > bar chart question on dashboard > should show updated column titles in tooltips after editing them via Visualization Options
`line-bar-tooltips.cy.spec.js` | scenarios > visualizations > line/bar chart > tooltips > > bar chart question on dashboard with added series > should show updated column titles in tooltips after editing them via Visualization Options
`dashboard-sections.cy.spec.js` | scenarios > dashboard cards > sections > should add sections and select a question for an empty card
`legend.cy.spec.js` | scenarios > visualizations > legend > should toggle series visibility on a dashboard
`line_chart.cy.spec.js` | scenarios > visualizations > line chart > tooltip of combined dashboard cards (multi-series) should show the correct column title (metabase#16249 > custom expression names (metabase#16249-1)
`visualization-options.cy.spec.js` | scenarios > dashboard cards > visualization options > should show the ellipsis even with an empty card title on visualizations with noHeader (metabase#46897)
and more... | and more...

author_association: CONTRIBUTOR