From dc4614151c882ec4c2a14a00dfad362b1c8b584a Mon Sep 17 00:00:00 2001
From: Uladzimir Havenchyk <125459446+uladzimirdev@users.noreply.github.com>
Date: Mon, 10 Jun 2024 22:19:17 +0300
Subject: [PATCH] [Dashboard] Replace automatic wiring of the cards with opt-in
 variant (#43827)

---
 .../dashcard-replace-question.cy.spec.js      |   5 +-
 .../dashboard-filters-auto-wiring.cy.spec.js  | 344 +++++++++++++-----
 .../dashboard-filters/parameters.cy.spec.js   |  24 +-
 e2e/test/scenarios/dashboard/tabs.cy.spec.js  |   7 +-
 ...dashboard-filters-reproductions.cy.spec.js |  13 +-
 frontend/src/metabase-types/store/state.ts    |   7 +-
 frontend/src/metabase-types/store/undo.ts     |  13 +
 .../actions/auto-wire-parameters/actions.ts   | 102 ++++--
 .../actions/auto-wire-parameters/toasts.ts    | 151 ++++++--
 .../actions/auto-wire-parameters/utils.ts     |  68 ++--
 .../metabase/dashboard/actions/cards-typed.ts |  13 +-
 .../dashboard/actions/cards.unit.spec.ts      |  64 +++-
 .../metabase/dashboard/actions/parameters.ts  |  22 +-
 13 files changed, 592 insertions(+), 241 deletions(-)
 create mode 100644 frontend/src/metabase-types/store/undo.ts

diff --git a/e2e/test/scenarios/dashboard-cards/dashcard-replace-question.cy.spec.js b/e2e/test/scenarios/dashboard-cards/dashcard-replace-question.cy.spec.js
index 6bdfd7579c6..4d88e82b1b5 100644
--- a/e2e/test/scenarios/dashboard-cards/dashcard-replace-question.cy.spec.js
+++ b/e2e/test/scenarios/dashboard-cards/dashcard-replace-question.cy.spec.js
@@ -17,6 +17,7 @@ import {
   expectGoodSnowplowEvent,
   expectNoBadSnowplowEvents,
   entityPickerModal,
+  undoToastList,
 } from "e2e/support/helpers";
 import {
   createMockDashboardCard,
@@ -192,8 +193,8 @@ describeWithSnowplow("scenarios > dashboard cards > replace question", () => {
       nextQuestionName: "Next question",
     });
 
-    // There're two toasts: "Undo replace" and "Undo parameters auto-wiring"
-    cy.findAllByTestId("toast-undo").eq(0).button("Undo").click();
+    // There're two toasts: "Undo replace" and "Auto-connect"
+    undoToastList().eq(0).button("Undo").click();
 
     // Ensure we kept viz settings and parameter mapping changes from before
     findTargetDashcard().within(() => {
diff --git a/e2e/test/scenarios/dashboard-filters/dashboard-filters-auto-wiring.cy.spec.js b/e2e/test/scenarios/dashboard-filters/dashboard-filters-auto-wiring.cy.spec.js
index dc03290a008..c12b32a2633 100644
--- a/e2e/test/scenarios/dashboard-filters/dashboard-filters-auto-wiring.cy.spec.js
+++ b/e2e/test/scenarios/dashboard-filters/dashboard-filters-auto-wiring.cy.spec.js
@@ -16,12 +16,16 @@ import {
   goToTab,
   createNewTab,
   undoToast,
+  undoToastList,
   setFilter,
   visitQuestion,
   modal,
   dashboardParametersContainer,
   openQuestionActions,
   entityPickerModal,
+  findDashCardAction,
+  removeDashboardCard,
+  sidebar,
 } from "e2e/support/helpers";
 
 const { ORDERS_ID, PRODUCTS_ID, REVIEWS_ID } = SAMPLE_DATABASE;
@@ -50,8 +54,8 @@ describe("dashboard filters auto-wiring", () => {
     cy.intercept("GET", "/api/dashboard/**").as("dashboard");
   });
 
-  describe("when wiring parameter to all cards for a filter", () => {
-    it("should automatically wire parameters to cards with matching fields", () => {
+  describe("parameter mapping", () => {
+    it("should wire parameters to cards with matching fields", () => {
       createDashboardWithCards({ cards }).then(dashboardId => {
         visitDashboard(dashboardId);
       });
@@ -66,18 +70,23 @@ describe("dashboard filters auto-wiring", () => {
         cy.findByText("User.Name").should("exist");
       });
 
-      getDashboardCard(1).within(() => {
-        cy.findByText("User.Name").should("exist");
-      });
+      getDashboardCard(1).findByText("User.Name").should("not.exist");
 
       undoToast()
-        .findByText(
-          "This filter has been auto-connected with questions with the same field.",
+        .should(
+          "contain",
+          "Auto-connect this filter to all questions containing “User.Name”?",
         )
-        .should("be.visible");
+        .should("not.contain", "in the current tab")
+        .findByText("Auto-connect")
+        .click();
+      undoToast().should(
+        "contain",
+        "The filter was auto-connected to all questions containing “User.Name”.",
+      );
     });
 
-    it("should not automatically wire parameters to cards that already have a parameter, despite matching fields", () => {
+    it("should not wire parameters to cards that already have a parameter, despite matching fields", () => {
       createDashboardWithCards({ cards }).then(dashboardId => {
         visitDashboard(dashboardId);
       });
@@ -93,10 +102,12 @@ describe("dashboard filters auto-wiring", () => {
       });
 
       undoToast()
-        .findByText(
-          "This filter has been auto-connected with questions with the same field.",
+        .should(
+          "contain",
+          "Auto-connect this filter to all questions containing “User.Name”?",
         )
-        .should("be.visible");
+        .findByRole("button", { name: "Auto-connect" })
+        .click();
 
       getDashboardCard(1).within(() => {
         cy.findByLabelText("close icon").click();
@@ -112,10 +123,10 @@ describe("dashboard filters auto-wiring", () => {
         cy.findByText("User.Address").should("exist");
       });
 
-      undoToast().should("not.exist");
+      undoToast().should("contain", "Undo");
     });
 
-    it("should not automatically wire parameters to cards that don't have a matching field", () => {
+    it("should not suggest to wire parameters to cards that don't have a matching field", () => {
       cy.createQuestion({
         name: "Products Table",
         query: { "source-table": PRODUCTS_ID, limit: 1 },
@@ -148,48 +159,10 @@ describe("dashboard filters auto-wiring", () => {
 
       selectDashboardFilter(getDashboardCard(0), "Name");
 
-      getDashboardCard(0).within(() => {
-        cy.findByText("User.Name").should("exist");
-      });
-
-      getDashboardCard(1).within(() => {
-        cy.findByText("Select…").should("exist");
-      });
-
       undoToast().should("not.exist");
     });
 
-    it("should autowire parameters to cards in different tabs", () => {
-      createDashboardWithCards({ cards }).then(dashboardId => {
-        visitDashboardAndCreateTab({
-          dashboardId,
-          save: false,
-        });
-      });
-
-      setFilter("Text or Category", "Is");
-
-      addCardToDashboard();
-      goToFilterMapping();
-
-      selectDashboardFilter(getDashboardCard(0), "Name");
-
-      getDashboardCard(0).findByText("User.Name").should("exist");
-
-      goToTab("Tab 1");
-
-      for (let i = 0; i < cards.length; i++) {
-        getDashboardCard(i).findByText("User.Name").should("exist");
-      }
-
-      undoToast()
-        .findByText(
-          "This filter has been auto-connected with questions with the same field.",
-        )
-        .should("be.visible");
-    });
-
-    it("should undo parameter wiring when 'Undo auto-connection' is clicked", () => {
+    it("should undo parameter wiring when 'Undo' is clicked", () => {
       createDashboardWithCards({ cards }).then(dashboardId => {
         visitDashboard(dashboardId);
       });
@@ -202,13 +175,15 @@ describe("dashboard filters auto-wiring", () => {
 
       selectDashboardFilter(getDashboardCard(0), "Name");
 
+      undoToast().findByRole("button", { name: "Auto-connect" }).click();
+
       getDashboardCard(0).findByText("User.Name").should("exist");
 
       for (let i = 0; i < cards.length; i++) {
         getDashboardCard(i).findByText("User.Name").should("exist");
       }
 
-      undoToast().findByText("Undo auto-connection").click();
+      undoToast().findByRole("button", { name: "Undo" }).click();
 
       getDashboardCard(0).findByText("User.Name").should("exist");
       for (let i = 1; i < cards.length; i++) {
@@ -216,8 +191,8 @@ describe("dashboard filters auto-wiring", () => {
       }
     });
 
-    it("in case of two autowiring undo toast, the second one should last the default timeout of 5s", () => {
-      // The autowiring undo toasts use the same id, a bug in the undo logic caused the second toast to be dismissed by the
+    it("in case of two auto-wiring undo toast, the second one should last the default timeout of 12s", () => {
+      // The auto-wiring undo toasts use the same id, a bug in the undo logic caused the second toast to be dismissed by the
       // timeout set by the first. See https://github.com/metabase/metabase/pull/35461#pullrequestreview-1731776862
       const cardTemplate = {
         card_id: ORDERS_BY_YEAR_QUESTION_ID,
@@ -254,25 +229,59 @@ describe("dashboard filters auto-wiring", () => {
       selectDashboardFilter(getDashboardCard(0), "Name");
 
       removeFilterFromDashCard(0);
-      removeFilterFromDashCard(1);
 
       cy.tick(2000);
 
       selectDashboardFilter(getDashboardCard(0), "Name");
 
-      // since we waited 2 seconds earlier, if the toast is still visible after this other delay of 4s,
-      // it means the first timeout of 5s was cleared correctly
-      cy.tick(4000);
+      // since we waited 2s earlier, if the toast is still visible after this other delay of 11s,
+      // it means the first timeout of 12s was cleared correctly
+      cy.tick(11000);
       undoToast().should("exist");
 
       cy.tick(2000);
-
       undoToast().should("not.exist");
     });
+
+    describe("multiple tabs", () => {
+      it("should not wire parameters to cards in different tabs", () => {
+        createDashboardWithCards({ cards }).then(dashboardId => {
+          visitDashboardAndCreateTab({
+            dashboardId,
+            save: false,
+          });
+        });
+
+        setFilter("Text or Category", "Is");
+
+        addCardToDashboard();
+        goToFilterMapping();
+
+        selectDashboardFilter(getDashboardCard(0), "Name");
+
+        getDashboardCard(0).findByText("User.Name").should("exist");
+
+        undoToast().should("not.exist");
+
+        goToTab("Tab 1");
+
+        for (let i = 0; i < cards.length; i++) {
+          getDashboardCard(i).findByText("User.Name").should("not.exist");
+        }
+
+        selectDashboardFilter(getDashboardCard(0), "Name");
+
+        cy.log("verify prefix 'in the current tab'");
+        undoToast().should(
+          "contain",
+          "Auto-connect this filter to all questions containing “User.Name”, in the current tab?",
+        );
+      });
+    });
   });
 
-  describe("wiring parameters when adding a card", () => {
-    it("should automatically wire a parameters to cards that are added to the dashboard", () => {
+  describe("add a card", () => {
+    it("should wire parameters to cards that are added to the dashboard", () => {
       createDashboardWithCards({ cards }).then(dashboardId => {
         visitDashboard(dashboardId);
       });
@@ -282,26 +291,41 @@ describe("dashboard filters auto-wiring", () => {
       setFilter("Text or Category", "Is");
 
       selectDashboardFilter(getDashboardCard(0), "Name");
+      undoToast().findByRole("button", { name: "Auto-connect" }).click();
 
       for (let i = 0; i < cards.length; i++) {
         getDashboardCard(i).findByText("User.Name").should("exist");
       }
 
       addCardToDashboard();
+
+      cy.log("verify toast text and enable auto-connect");
+
+      undoToastList()
+        .eq(1)
+        .should("contain", "Auto-connect “Orders Model” to “Text”?")
+        .findByRole("button", { name: "Auto-connect" })
+        .click();
+
+      cy.log("verify toast text after auto-connect");
+
+      undoToastList()
+        .eq(1)
+        .should("contain", "“Orders Model” was auto-connected to “Text”.");
+
       goToFilterMapping();
 
       for (let i = 0; i < cards.length + 1; i++) {
         getDashboardCard(i).findByText("User.Name").should("exist");
       }
 
-      undoToast()
-        .findByText(
-          "Orders Model has been auto-connected with filters with the same field.",
-        )
+      undoToastList()
+        .eq(1)
+        .findByText("“Orders Model” was auto-connected to “Text”.")
         .should("be.visible");
     });
 
-    it("should automatically wire parameters to cards that are added to the dashboard in a different tab", () => {
+    it("should undo parameter wiring when 'Undo' is clicked", () => {
       createDashboardWithCards({ cards }).then(dashboardId => {
         visitDashboard(dashboardId);
       });
@@ -311,24 +335,94 @@ describe("dashboard filters auto-wiring", () => {
       setFilter("Text or Category", "Is");
 
       selectDashboardFilter(getDashboardCard(0), "Name");
+      undoToast().findByRole("button", { name: "Auto-connect" }).click();
+
       for (let i = 0; i < cards.length; i++) {
         getDashboardCard(i).findByText("User.Name").should("exist");
       }
 
-      createNewTab();
       addCardToDashboard();
       goToFilterMapping();
 
+      undoToastList()
+        .eq(1)
+        .should("contain", "Auto-connect “Orders Model” to “Text”?")
+        .findByRole("button", { name: "Auto-connect" })
+        .click();
+
+      for (let i = 0; i < cards.length + 1; i++) {
+        getDashboardCard(i).findByText("User.Name").should("exist");
+      }
+
+      cy.log("verify undo functionality");
+      undoToastList().eq(1).findByText("Undo").click();
+
       getDashboardCard(0).findByText("User.Name").should("exist");
+      getDashboardCard(1).findByText("User.Name").should("exist");
+      getDashboardCard(2).findByText("Select…").should("exist");
+    });
 
-      undoToast()
-        .findByText(
-          "Orders Model has been auto-connected with filters with the same field.",
-        )
-        .should("be.visible");
+    describe("multiple tabs", () => {
+      it("should not wire parameters to cards that are added to the dashboard in a different tab", () => {
+        createDashboardWithCards({ cards }).then(dashboardId => {
+          visitDashboard(dashboardId);
+        });
+
+        editDashboard();
+
+        setFilter("Number", "Equal to");
+        setFilter("Text or Category", "Is");
+
+        selectDashboardFilter(getDashboardCard(0), "Name");
+
+        undoToast()
+          .should(
+            "contain",
+            "Auto-connect this filter to all questions containing",
+          )
+          .findByRole("button", { name: "Auto-connect" })
+          .click();
+
+        for (let i = 0; i < cards.length; i++) {
+          getDashboardCard(i).findByText("User.Name").should("exist");
+        }
+
+        createNewTab();
+        addCardToDashboard();
+        goToFilterMapping();
+
+        getDashboardCard(0).findByText("User.Name").should("not.exist");
+
+        cy.log(
+          "verify that no new toast with suggestion to auto-wire appeared",
+        );
+
+        undoToastList()
+          .should("have.length", 1)
+          .should(
+            "contain",
+            "The filter was auto-connected to all questions containing “User.Name”",
+          );
+
+        selectDashboardFilter(getDashboardCard(0), "Name");
+        goToFilterMapping("Equal to");
+        selectDashboardFilter(getDashboardCard(0), "Total");
+
+        addCardToDashboard();
+
+        undoToastList().eq(1).findByText("Auto-connect").click();
+
+        cy.log("verify that toast shows number of filters that were connected");
+
+        undoToastList()
+          .eq(1)
+          .should("contain", "“Orders Model” was auto-connected to 2 filters.");
+      });
     });
+  });
 
-    it("should undo parameter wiring when 'Undo auto-connection' is clicked", () => {
+  describe("replace a card", () => {
+    it("should show auto-wire suggestion toast when a card is replaced", () => {
       createDashboardWithCards({ cards }).then(dashboardId => {
         visitDashboard(dashboardId);
       });
@@ -339,22 +433,23 @@ describe("dashboard filters auto-wiring", () => {
 
       selectDashboardFilter(getDashboardCard(0), "Name");
 
-      for (let i = 0; i < cards.length; i++) {
-        getDashboardCard(i).findByText("User.Name").should("exist");
-      }
+      undoToast().findByText("Auto-connect").click();
 
-      addCardToDashboard();
       goToFilterMapping();
 
-      for (let i = 0; i < cards.length + 1; i++) {
-        getDashboardCard(i).findByText("User.Name").should("exist");
-      }
+      findDashCardAction(getDashboardCard(1), "Replace").click();
 
-      undoToast().findByText("Undo auto-connection").click();
+      modal().findByText("Orders, Count").click();
 
-      getDashboardCard(0).findByText("User.Name").should("exist");
-      getDashboardCard(1).findByText("User.Name").should("exist");
-      getDashboardCard(2).findByText("Select…").should("exist");
+      undoToastList()
+        .eq(2)
+        .should("contain", "Auto-connect “Orders, Count” to “Text”?")
+        .button("Auto-connect")
+        .click();
+
+      undoToastList()
+        .eq(2)
+        .should("contain", "“Orders, Count” was auto-connected to “Text”.");
     });
   });
 
@@ -398,7 +493,7 @@ describe("dashboard filters auto-wiring", () => {
       });
     });
 
-    it("should autowire and filter cards with foreign keys when added to the dashboard via the sidebar", () => {
+    it("should auto-wire and filter cards with foreign keys when added to the dashboard via the sidebar", () => {
       visitDashboard("@dashboardId");
       editDashboard();
       setFilter("ID");
@@ -410,6 +505,18 @@ describe("dashboard filters auto-wiring", () => {
 
       goToFilterMapping("ID");
 
+      undoToastList()
+        .findByText("Auto-connect “Orders Question” to “ID”?")
+        .closest("[data-testid='toast-undo']")
+        .findByRole("button", { name: "Auto-connect" })
+        .click();
+
+      undoToastList()
+        .findByText("Auto-connect “Reviews Question” to “ID”?")
+        .closest("[data-testid='toast-undo']")
+        .findByRole("button", { name: "Auto-connect" })
+        .click();
+
       getDashboardCard(0).findByText("Product.ID").should("exist");
       getDashboardCard(1).findByText("Product.ID").should("exist");
       getDashboardCard(2).findByText("Product.ID").should("exist");
@@ -438,7 +545,7 @@ describe("dashboard filters auto-wiring", () => {
       });
     });
 
-    it("should autowire and filter cards with foreign keys when added to the dashboard via the query builder", () => {
+    it("should auto-wire and filter cards with foreign keys when added to the dashboard via the query builder", () => {
       visitDashboard("@dashboardId");
       editDashboard();
       setFilter("ID");
@@ -488,7 +595,58 @@ describe("dashboard filters auto-wiring", () => {
       });
     });
   });
+
+  describe("dismiss toasts", () => {
+    it("should dismiss auto-wire toasts on filter removal", () => {
+      createDashboardWithCards({ cards }).then(dashboardId => {
+        visitDashboard(dashboardId);
+      });
+
+      editDashboard();
+      setFilter("Text or Category", "Is");
+
+      selectDashboardFilter(getDashboardCard(0), "Name");
+
+      undoToast().findByRole("button", { name: "Auto-connect" }).click();
+
+      addCardToDashboard();
+
+      undoToastList()
+        .contains("Auto-connect “Orders Model” to “Text”?")
+        .should("be.visible");
+
+      removeFilterFromDashboard();
+
+      undoToast().should("not.exist");
+    });
+
+    it("should dismiss auto-wire toasts on card removal", () => {
+      createDashboardWithCards({ cards }).then(dashboardId => {
+        visitDashboard(dashboardId);
+      });
+
+      editDashboard();
+      setFilter("Text or Category", "Is");
+
+      selectDashboardFilter(getDashboardCard(0), "Name");
+
+      undoToast().findByRole("button", { name: "Auto-connect" }).click();
+
+      addCardToDashboard();
+
+      undoToastList()
+        .contains("Auto-connect “Orders Model” to “Text”?")
+        .should("be.visible");
+
+      removeDashboardCard(2);
+
+      undoToastList()
+        .should("have.length", 1)
+        .should("contain", "Removed card");
+    });
+  });
 });
+
 function createDashboardWithCards({
   dashboardName = "my dash",
   cards = [],
@@ -514,6 +672,12 @@ function addCardToDashboard(dashcardNames = "Orders Model") {
   }
 }
 
+function removeFilterFromDashboard(filterName = "Text") {
+  goToFilterMapping(filterName);
+
+  sidebar().findByRole("button", { name: "Remove" }).click();
+}
+
 function goToFilterMapping(name = "Text") {
   cy.findByTestId("edit-dashboard-parameters-widget-container")
     .findByText(name)
@@ -550,7 +714,9 @@ function addQuestionFromQueryBuilder({
     cy.button("Select").click();
   });
 
-  undoToast().should("be.visible");
+  undoToast().findByRole("button", { name: "Auto-connect" }).click();
+  undoToast().should("contain", "Undo");
+
   if (saveDashboardAfterAdd) {
     saveDashboard();
   }
diff --git a/e2e/test/scenarios/dashboard-filters/parameters.cy.spec.js b/e2e/test/scenarios/dashboard-filters/parameters.cy.spec.js
index fa2bcab214b..9ad1472e636 100644
--- a/e2e/test/scenarios/dashboard-filters/parameters.cy.spec.js
+++ b/e2e/test/scenarios/dashboard-filters/parameters.cy.spec.js
@@ -72,19 +72,9 @@ describe("scenarios > dashboard > parameters", () => {
     // (this doesn't make sense to do, but it illustrates the feature)
     selectDashboardFilter(getDashboardCard(0), "Name");
 
-    getDashboardCard(1).within(() => {
-      cy.findByLabelText("close icon").click();
-    });
     selectDashboardFilter(getDashboardCard(1), "Category");
 
-    // finish editing filter and save dashboard
-    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
-    cy.contains("Save").click();
-
-    // wait for saving to finish
-    cy.wait("@dashboard");
-    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
-    cy.contains("You're editing this dashboard.").should("not.exist");
+    saveDashboard();
 
     // confirm that typing searches both fields
     filterWidget().contains("Text").click();
@@ -638,28 +628,34 @@ describe("scenarios > dashboard > parameters", () => {
         .click();
 
       selectDashboardFilter(getDashboardCard(0), "Created At");
+      selectDashboardFilter(getDashboardCard(1), "Created At");
+
       saveDashboard();
 
       cy.get("@dashcardRequestSpy").should("have.callCount", 2);
     });
 
     it("should fetch dashcard data when parameter mapping is removed", () => {
-      // Connect filter to 1 card only
+      cy.log("Connect filter to 1 card only");
+
       editDashboard();
       cy.findByTestId("edit-dashboard-parameters-widget-container")
         .findByText("Date Filter")
         .click();
       selectDashboardFilter(getDashboardCard(0), "Created At");
-      disconnectDashboardFilter(getDashboardCard(1));
+
       saveDashboard();
 
       cy.get("@dashcardRequestSpy").should("have.callCount", 1);
 
-      // Disconnect filter from the 1st card
+      cy.log("Disconnect filter from the 1st card");
+
       editDashboard();
+
       cy.findByTestId("edit-dashboard-parameters-widget-container")
         .findByText("Date Filter")
         .click();
+
       disconnectDashboardFilter(getDashboardCard(0));
       saveDashboard();
 
diff --git a/e2e/test/scenarios/dashboard/tabs.cy.spec.js b/e2e/test/scenarios/dashboard/tabs.cy.spec.js
index 0e3ab068018..9f7c339ed59 100644
--- a/e2e/test/scenarios/dashboard/tabs.cy.spec.js
+++ b/e2e/test/scenarios/dashboard/tabs.cy.spec.js
@@ -672,7 +672,6 @@ describe("scenarios > dashboard > tabs", () => {
 
     setFilter("Time", "Relative Date");
 
-    // Auto-connection happens here
     selectDashboardFilter(getDashboardCard(0), "Created At");
     saveDashboard();
 
@@ -692,11 +691,11 @@ describe("scenarios > dashboard > tabs", () => {
       cy.findAllByTestId("table-row").should("exist");
     });
 
-    // Loader in the 1st tab
+    // we do not auto-wire automatically in different tabs anymore, so first tab
+    // should not show a loader and re-run query
     goToTab("Tab 1");
     getDashboardCard(0).within(() => {
-      cy.findByTestId("loading-spinner").should("exist");
-      cy.wait("@saveCard");
+      cy.findByTestId("loading-spinner").should("not.exist");
       cy.findAllByTestId("table-row").should("exist");
     });
   });
diff --git a/e2e/test/scenarios/filters-reproductions/dashboard-filters-reproductions.cy.spec.js b/e2e/test/scenarios/filters-reproductions/dashboard-filters-reproductions.cy.spec.js
index 783001f5364..c1a5828b40d 100644
--- a/e2e/test/scenarios/filters-reproductions/dashboard-filters-reproductions.cy.spec.js
+++ b/e2e/test/scenarios/filters-reproductions/dashboard-filters-reproductions.cy.spec.js
@@ -211,6 +211,9 @@ describe("issue 8030 + 32444", () => {
 
         setFilter("Text or Category", "Is");
         selectDashboardFilter(cy.findAllByTestId("dashcard").first(), "Title");
+
+        undoToast().findByRole("button", { name: "Auto-connect" }).click();
+
         cy.findAllByTestId("dashcard")
           .eq(1)
           .findByLabelText("Disconnect")
@@ -923,21 +926,19 @@ describe("issue 19494", () => {
 
     connectFilterToCard({ filterName: "Card 1 Filter", cardPosition: 0 });
     setDefaultFilter("Doohickey");
-    undoToast().findByText("Undo auto-connection").click();
 
     connectFilterToCard({ filterName: "Card 2 Filter", cardPosition: -1 });
     setDefaultFilter("Gizmo");
-    undoToast().findByText("Undo auto-connection").click();
 
     saveDashboard();
 
     checkAppliedFilter("Card 1 Filter", "Doohickey");
-    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
-    cy.findByText("148.23");
+
+    getDashboardCard(0).should("contain", "148.23");
 
     checkAppliedFilter("Card 2 Filter", "Gizmo");
-    // eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
-    cy.findByText("110.93");
+
+    getDashboardCard(1).should("contain", "110.93");
   });
 });
 
diff --git a/frontend/src/metabase-types/store/state.ts b/frontend/src/metabase-types/store/state.ts
index db05f67a500..e82937ac8a2 100644
--- a/frontend/src/metabase-types/store/state.ts
+++ b/frontend/src/metabase-types/store/state.ts
@@ -14,9 +14,11 @@ import type { QueryBuilderState } from "./qb";
 import type { RequestsState } from "./requests";
 import type { SettingsState } from "./settings";
 import type { SetupState } from "./setup";
+import type { UndoState } from "./undo";
 import type { FileUploadState } from "./upload";
 
-type modalName = null | "collection" | "dashboard" | "action";
+type ModalName = null | "collection" | "dashboard" | "action";
+
 export interface State {
   admin: AdminState;
   app: AppState;
@@ -33,7 +35,8 @@ export interface State {
   settings: SettingsState;
   setup: SetupState;
   upload: FileUploadState;
-  modal: modalName;
+  modal: ModalName;
+  undo: UndoState;
 }
 
 export type Dispatch<T = any> = (action: T) => unknown | Promise<unknown>;
diff --git a/frontend/src/metabase-types/store/undo.ts b/frontend/src/metabase-types/store/undo.ts
new file mode 100644
index 00000000000..67e139159cf
--- /dev/null
+++ b/frontend/src/metabase-types/store/undo.ts
@@ -0,0 +1,13 @@
+// TODO: convert redux/undo and UndoListing.jsx to TS and update type
+export type UndoState = {
+  id: string | number;
+  type?: string;
+  action?: () => void;
+  actions?: (() => void)[];
+  icon?: string;
+  toastColor?: string;
+  actionLabel?: string;
+  canDismiss?: boolean;
+  dismissIconColor?: string;
+  _domId?: string | number;
+}[];
diff --git a/frontend/src/metabase/dashboard/actions/auto-wire-parameters/actions.ts b/frontend/src/metabase/dashboard/actions/auto-wire-parameters/actions.ts
index 49e84278fbd..7132347fde8 100644
--- a/frontend/src/metabase/dashboard/actions/auto-wire-parameters/actions.ts
+++ b/frontend/src/metabase/dashboard/actions/auto-wire-parameters/actions.ts
@@ -1,7 +1,3 @@
-import {
-  setDashCardAttributes,
-  setMultipleDashCardAttributes,
-} from "metabase/dashboard/actions";
 import {
   closeAutoWireParameterToast,
   showAddedCardAutoWireParametersToast,
@@ -10,6 +6,7 @@ import {
 import {
   getAllDashboardCardsWithUnmappedParameters,
   getAutoWiredMappingsForDashcards,
+  getMatchingParameterOption,
   getParameterMappings,
 } from "metabase/dashboard/actions/auto-wire-parameters/utils";
 import { getExistingDashCards } from "metabase/dashboard/actions/utils";
@@ -18,62 +15,62 @@ import {
   getDashCardById,
   getParameters,
   getQuestions,
+  getDashboard,
+  getSelectedTabId,
+  getTabs,
 } from "metabase/dashboard/selectors";
 import { isQuestionDashCard } from "metabase/dashboard/utils";
 import { getParameterMappingOptions } from "metabase/parameters/utils/mapping-options";
-import { getMetadata } from "metabase/selectors/metadata";
-import Question from "metabase-lib/v1/Question";
 import type {
   QuestionDashboardCard,
   DashCardId,
   ParameterId,
   ParameterTarget,
+  DashboardTabId,
+  DashboardParameterMapping,
 } from "metabase-types/api";
 import type { Dispatch, GetState, StoreDashcard } from "metabase-types/store";
 
-export function autoWireDashcardsWithMatchingParameters(
+export function showAutoWireToast(
   parameter_id: ParameterId,
   dashcard: QuestionDashboardCard,
   target: ParameterTarget,
+  selectedTabId: DashboardTabId,
 ) {
   return function (dispatch: Dispatch, getState: GetState) {
-    const metadata = getMetadata(getState());
-    const dashboard_state = getState().dashboard;
+    const dashboardState = getState().dashboard;
     const questions = getQuestions(getState());
     const parameter = getParameters(getState()).find(
       ({ id }) => id === parameter_id,
     );
 
-    if (!dashboard_state.dashboardId || !parameter) {
+    if (!dashboardState.dashboardId || !parameter) {
       return;
     }
 
     const dashcardsToAutoApply = getAllDashboardCardsWithUnmappedParameters({
-      dashboardState: dashboard_state,
-      dashboardId: dashboard_state.dashboardId,
+      dashboards: dashboardState.dashboards,
+      dashcards: dashboardState.dashcards,
+      dashboardId: dashboardState.dashboardId,
       parameterId: parameter_id,
+      selectedTabId,
+      // exclude current dashcard as it's being updated in another action
       excludeDashcardIds: [dashcard.id],
     });
 
     const dashcardAttributes = getAutoWiredMappingsForDashcards(
       parameter,
-      dashcard,
       dashcardsToAutoApply,
       target,
-      metadata,
       questions,
     );
 
-    if (dashcardAttributes.length === 0) {
+    const shouldShowToast = dashcardAttributes.length > 0;
+
+    if (!shouldShowToast) {
       return;
     }
 
-    dispatch(
-      setMultipleDashCardAttributes({
-        dashcards: dashcardAttributes,
-      }),
-    );
-
     const originalDashcardAttributes = dashcardsToAutoApply.map(dashcard => ({
       id: dashcard.id,
       attributes: {
@@ -81,15 +78,31 @@ export function autoWireDashcardsWithMatchingParameters(
       },
     }));
 
+    const mappingOption = getMatchingParameterOption(
+      parameter,
+      dashcard,
+      target,
+      questions,
+    );
+
+    const tabs = getTabs(getState());
+
+    if (!mappingOption) {
+      return;
+    }
+
     dispatch(
       showAutoWireParametersToast({
-        dashcardAttributes: originalDashcardAttributes,
+        dashcardAttributes,
+        originalDashcardAttributes,
+        columnName: formatMappingOption(mappingOption),
+        hasMultipleTabs: tabs.length > 1,
       }),
     );
   };
 }
 
-export function autoWireParametersToNewCard({
+export function showAutoWireToastNewCard({
   dashcard_id,
 }: {
   dashcard_id: DashCardId;
@@ -97,7 +110,6 @@ export function autoWireParametersToNewCard({
   return function (dispatch: Dispatch, getState: GetState) {
     dispatch(closeAutoWireParameterToast());
 
-    const metadata = getMetadata(getState());
     const dashboardState = getState().dashboard;
     const dashboardId = dashboardState.dashboardId;
 
@@ -105,13 +117,20 @@ export function autoWireParametersToNewCard({
       return;
     }
 
+    const dashboard = getDashboard(getState());
+    if (!dashboard || !dashboard.parameters) {
+      return;
+    }
+
     const questions = getQuestions(getState());
     const parameters = getParameters(getState());
+    const selectedTabId = getSelectedTabId(getState());
 
     const dashcards = getExistingDashCards(
       dashboardState.dashboards,
       dashboardState.dashcards,
       dashboardId,
+      selectedTabId,
     );
 
     const targetDashcard: StoreDashcard = getDashCardById(
@@ -123,11 +142,9 @@ export function autoWireParametersToNewCard({
       return;
     }
 
-    const targetQuestion =
-      questions[targetDashcard.card.id] ??
-      new Question(targetDashcard.card, metadata);
+    const targetQuestion = questions[targetDashcard.card.id];
 
-    const parametersToAutoApply = [];
+    const parametersMappingsToApply: DashboardParameterMapping[] = [];
     const processedParameterIds = new Set();
 
     for (const parameter of parameters) {
@@ -153,7 +170,7 @@ export function autoWireParametersToNewCard({
             targetDashcard.card_id &&
             !processedParameterIds.has(parameter.id)
           ) {
-            parametersToAutoApply.push(
+            parametersMappingsToApply.push(
               ...getParameterMappings(
                 targetDashcard,
                 parameter.id,
@@ -167,24 +184,35 @@ export function autoWireParametersToNewCard({
       }
     }
 
-    if (parametersToAutoApply.length === 0) {
+    if (parametersMappingsToApply.length === 0) {
       return;
     }
 
-    dispatch(
-      setDashCardAttributes({
-        id: dashcard_id,
-        attributes: {
-          parameter_mappings: parametersToAutoApply,
-        },
-      }),
+    const parametersToMap = dashboard.parameters.filter(p =>
+      processedParameterIds.has(p.id),
     );
 
     dispatch(
       showAddedCardAutoWireParametersToast({
         targetDashcard,
         dashcard_id,
+        parametersMappingsToApply,
+        parametersToMap,
       }),
     );
   };
 }
+
+function formatMappingOption({
+  name,
+  sectionName,
+}: {
+  name: string;
+  sectionName?: string;
+}) {
+  if (sectionName == null) {
+    // for native question variables or field literals we just display the name
+    return name;
+  }
+  return `${sectionName}.${name}`;
+}
diff --git a/frontend/src/metabase/dashboard/actions/auto-wire-parameters/toasts.ts b/frontend/src/metabase/dashboard/actions/auto-wire-parameters/toasts.ts
index 1f03177f9a8..bbadcf1ae5f 100644
--- a/frontend/src/metabase/dashboard/actions/auto-wire-parameters/toasts.ts
+++ b/frontend/src/metabase/dashboard/actions/auto-wire-parameters/toasts.ts
@@ -7,64 +7,165 @@ import {
   setMultipleDashCardAttributes,
 } from "metabase/dashboard/actions";
 import { addUndo, dismissUndo } from "metabase/redux/undo";
-import type { QuestionDashboardCard, DashCardId } from "metabase-types/api";
-import type { Dispatch } from "metabase-types/store";
+import type {
+  QuestionDashboardCard,
+  DashCardId,
+  DashboardParameterMapping,
+  Parameter,
+} from "metabase-types/api";
+import type { Dispatch, GetState } from "metabase-types/store";
 
 export const AUTO_WIRE_TOAST_ID = _.uniqueId();
+const AUTO_WIRE_UNDO_TOAST_ID = _.uniqueId();
 
 export const showAutoWireParametersToast =
   ({
     dashcardAttributes,
+    originalDashcardAttributes,
+    columnName,
+    hasMultipleTabs,
   }: {
     dashcardAttributes: SetMultipleDashCardAttributesOpts;
+    originalDashcardAttributes: SetMultipleDashCardAttributesOpts;
+    columnName: string;
+    hasMultipleTabs: boolean;
   }) =>
   (dispatch: Dispatch) => {
+    const message = hasMultipleTabs
+      ? t`Auto-connect this filter to all questions containing “${columnName}”, in the current tab?`
+      : t`Auto-connect this filter to all questions containing “${columnName}”?`;
+
     dispatch(
       addUndo({
         id: AUTO_WIRE_TOAST_ID,
-        message: t`This filter has been auto-connected with questions with the same field.`,
-        actionLabel: t`Undo auto-connection`,
-        undo: true,
+        icon: null,
+        message,
+        actionLabel: t`Auto-connect`,
+        timeout: 12000,
         action: () => {
-          dispatch(
-            setMultipleDashCardAttributes({
-              dashcards: dashcardAttributes,
-            }),
-          );
+          connectAll();
+          showUndoToast();
         },
       }),
     );
+
+    function connectAll() {
+      dispatch(
+        setMultipleDashCardAttributes({
+          dashcards: dashcardAttributes,
+        }),
+      );
+    }
+
+    function revertConnectAll() {
+      dispatch(
+        setMultipleDashCardAttributes({
+          dashcards: originalDashcardAttributes,
+        }),
+      );
+    }
+
+    function showUndoToast() {
+      dispatch(
+        addUndo({
+          id: AUTO_WIRE_UNDO_TOAST_ID,
+          message: t`The filter was auto-connected to all questions containing “${columnName}”.`,
+          actionLabel: t`Undo`,
+          timeout: 12000,
+          type: "filterAutoConnect",
+          action: revertConnectAll,
+        }),
+      );
+    }
   };
 
 export const showAddedCardAutoWireParametersToast =
   ({
     targetDashcard,
     dashcard_id,
+    parametersMappingsToApply,
+    parametersToMap,
   }: {
     targetDashcard: QuestionDashboardCard;
     dashcard_id: DashCardId;
+    parametersMappingsToApply: DashboardParameterMapping[];
+    parametersToMap: Parameter[];
   }) =>
   (dispatch: Dispatch) => {
+    const shouldShowParameterName = parametersMappingsToApply.length === 1;
+    const message = shouldShowParameterName
+      ? t`Auto-connect “${targetDashcard.card.name}” to “${parametersToMap[0].name}”?`
+      : t`Auto-connect “${targetDashcard.card.name}” to ${parametersMappingsToApply.length} filters with the same field?`;
+
+    const toastId = _.uniqueId();
+
     dispatch(
       addUndo({
-        id: AUTO_WIRE_TOAST_ID,
-        message: t`${targetDashcard.card.name} has been auto-connected with filters with the same field.`,
-        actionLabel: t`Undo auto-connection`,
-        undo: true,
+        id: toastId,
+        icon: null,
+        type: "filterAutoConnect",
+        message,
+        actionLabel: t`Auto-connect`,
+        timeout: 12000,
         action: () => {
-          dispatch(
-            setDashCardAttributes({
-              id: dashcard_id,
-              attributes: {
-                parameter_mappings: [],
-              },
-            }),
-          );
+          closeAutoWireParameterToast(toastId);
+          autoWireParametersToNewCard();
+          showUndoToast();
         },
       }),
     );
+
+    function autoWireParametersToNewCard() {
+      dispatch(
+        setDashCardAttributes({
+          id: dashcard_id,
+          attributes: {
+            parameter_mappings: parametersMappingsToApply,
+          },
+        }),
+      );
+    }
+
+    function revertAutoWireParametersToNewCard() {
+      dispatch(
+        setDashCardAttributes({
+          id: dashcard_id,
+          attributes: {
+            parameter_mappings: [],
+          },
+        }),
+      );
+    }
+
+    function showUndoToast() {
+      const message = shouldShowParameterName
+        ? t`“${targetDashcard.card.name}” was auto-connected to “${parametersToMap[0].name}”.`
+        : t`“${targetDashcard.card.name}” was auto-connected to ${parametersToMap.length} filters.`;
+
+      dispatch(
+        addUndo({
+          message,
+          timeout: 12000,
+          type: "filterAutoConnect",
+          action: revertAutoWireParametersToNewCard,
+        }),
+      );
+    }
+  };
+
+export const closeAutoWireParameterToast =
+  (toastId: string = AUTO_WIRE_TOAST_ID) =>
+  (dispatch: Dispatch) => {
+    dispatch(dismissUndo(toastId, false));
   };
 
-export const closeAutoWireParameterToast = () => (dispatch: Dispatch) => {
-  dispatch(dismissUndo(AUTO_WIRE_TOAST_ID, false));
-};
+export const closeAddCardAutoWireToasts =
+  () => (dispatch: Dispatch, getState: GetState) => {
+    const undos = getState().undo;
+
+    for (const undo of undos) {
+      if (undo.type === "filterAutoConnect") {
+        dispatch(dismissUndo(undo.id, false));
+      }
+    }
+  };
diff --git a/frontend/src/metabase/dashboard/actions/auto-wire-parameters/utils.ts b/frontend/src/metabase/dashboard/actions/auto-wire-parameters/utils.ts
index 0834c1e9b2e..c15a857bbf4 100644
--- a/frontend/src/metabase/dashboard/actions/auto-wire-parameters/utils.ts
+++ b/frontend/src/metabase/dashboard/actions/auto-wire-parameters/utils.ts
@@ -7,9 +7,11 @@ import {
   isQuestionDashCard,
   isVirtualDashCard,
 } from "metabase/dashboard/utils";
-import { getParameterMappingOptions } from "metabase/parameters/utils/mapping-options";
+import {
+  getParameterMappingOptions,
+  type ParameterMappingOption,
+} from "metabase/parameters/utils/mapping-options";
 import type Question from "metabase-lib/v1/Question";
-import type Metadata from "metabase-lib/v1/metadata/Metadata";
 import type {
   CardId,
   QuestionDashboardCard,
@@ -18,28 +20,36 @@ import type {
   ParameterId,
   ParameterTarget,
   Parameter,
+  DashboardTabId,
+  DashboardCard,
 } from "metabase-types/api";
 import type { DashboardState } from "metabase-types/store";
 
 import type { SetMultipleDashCardAttributesOpts } from "../core";
 
 export function getAllDashboardCardsWithUnmappedParameters({
-  dashboardState,
+  dashboards,
+  dashcards,
   dashboardId,
   parameterId,
+  selectedTabId,
   excludeDashcardIds = [],
 }: {
-  dashboardState: DashboardState;
+  dashboards: DashboardState["dashboards"];
+  dashcards: DashboardState["dashcards"];
   dashboardId: DashboardId;
   parameterId: ParameterId;
+  selectedTabId: DashboardTabId;
   excludeDashcardIds?: DashCardId[];
 }): QuestionDashboardCard[] {
-  const dashCards = getExistingDashCards(
-    dashboardState.dashboards,
-    dashboardState.dashcards,
+  const existingDashcards = getExistingDashCards(
+    dashboards,
+    dashcards,
     dashboardId,
+    selectedTabId,
   );
-  return dashCards.filter(
+
+  return existingDashcards.filter(
     (dashcard): dashcard is QuestionDashboardCard =>
       isQuestionDashCard(dashcard) &&
       !excludeDashcardIds.includes(dashcard.id) &&
@@ -53,12 +63,8 @@ export function getMatchingParameterOption(
   parameter: Parameter,
   targetDashcard: QuestionDashboardCard,
   targetDimension: ParameterTarget,
-  sourceDashcard: QuestionDashboardCard,
-  metadata: Metadata,
   questions: Record<CardId, Question>,
-): {
-  target: ParameterTarget;
-} | null {
+): ParameterMappingOption | null | undefined {
   if (!targetDashcard) {
     return null;
   }
@@ -84,10 +90,8 @@ export function getMatchingParameterOption(
 
 export function getAutoWiredMappingsForDashcards(
   parameter: Parameter,
-  sourceDashcard: QuestionDashboardCard,
   targetDashcards: QuestionDashboardCard[],
   target: ParameterTarget,
-  metadata: Metadata,
   questions: Record<CardId, Question>,
 ): SetMultipleDashCardAttributesOpts {
   if (targetDashcards.length === 0) {
@@ -97,14 +101,10 @@ export function getAutoWiredMappingsForDashcards(
   const targetDashcardMappings: SetMultipleDashCardAttributesOpts = [];
 
   for (const targetDashcard of targetDashcards) {
-    const selectedMappingOption: {
-      target: ParameterTarget;
-    } | null = getMatchingParameterOption(
+    const selectedMappingOption = getMatchingParameterOption(
       parameter,
       targetDashcard,
       target,
-      sourceDashcard,
-      metadata,
       questions,
     );
 
@@ -124,22 +124,24 @@ export function getAutoWiredMappingsForDashcards(
   }
   return targetDashcardMappings;
 }
-
-export function getParameterMappings(
-  dashcard: QuestionDashboardCard,
+export function getParameterMappings<DC extends DashboardCard>(
+  dashcard: DC,
   parameter_id: ParameterId,
   card_id: CardId,
   target: ParameterTarget | null,
-) {
+): NonNullable<DC["parameter_mappings"]> {
   const isVirtual = isVirtualDashCard(dashcard);
   const isAction = isActionDashCard(dashcard);
 
-  let parameter_mappings = dashcard.parameter_mappings || [];
+  let parameter_mappings: NonNullable<DC["parameter_mappings"]> =
+    dashcard.parameter_mappings ?? [];
 
   // allow mapping the same parameter to multiple action targets
   if (!isAction) {
     parameter_mappings = parameter_mappings.filter(
-      m => m.card_id !== card_id || m.parameter_id !== parameter_id,
+      m =>
+        ("card_id" in m && m.card_id !== card_id) ||
+        m.parameter_id !== parameter_id,
     );
   }
 
@@ -151,11 +153,15 @@ export function getParameterMappings(
         m => !_.isEqual(m.target, target),
       );
     }
-    parameter_mappings = parameter_mappings.concat({
-      parameter_id,
-      card_id,
-      target,
-    });
+
+    return [
+      ...parameter_mappings,
+      {
+        parameter_id,
+        card_id,
+        target,
+      },
+    ];
   }
 
   return parameter_mappings;
diff --git a/frontend/src/metabase/dashboard/actions/cards-typed.ts b/frontend/src/metabase/dashboard/actions/cards-typed.ts
index 8b589d31ba4..a29f9e522f0 100644
--- a/frontend/src/metabase/dashboard/actions/cards-typed.ts
+++ b/frontend/src/metabase/dashboard/actions/cards-typed.ts
@@ -32,7 +32,8 @@ import {
   isVirtualDashCard,
 } from "../utils";
 
-import { autoWireParametersToNewCard } from "./auto-wire-parameters/actions";
+import { showAutoWireToastNewCard } from "./auto-wire-parameters/actions";
+import { closeAddCardAutoWireToasts } from "./auto-wire-parameters/toasts";
 import {
   ADD_CARD_TO_DASH,
   ADD_MANY_CARDS_TO_DASH,
@@ -53,7 +54,7 @@ type NewDashboardCard = Omit<
   "entity_id" | "created_at" | "updated_at"
 >;
 
-type AddDashCardOpts = NewDashCardOpts & {
+export type AddDashCardOpts = NewDashCardOpts & {
   dashcardOverrides: Partial<NewDashboardCard> & {
     card: Card | VirtualCard;
   };
@@ -136,7 +137,7 @@ export const addSectionToDashboard =
     trackSectionAdded(dashId, sectionLayout.id);
   };
 
-type AddCardToDashboardOpts = NewDashCardOpts & {
+export type AddCardToDashboardOpts = NewDashCardOpts & {
   cardId: CardId;
 };
 
@@ -159,7 +160,7 @@ export const addCardToDashboard =
 
     dispatch(fetchCardData(card, dashcard, { reload: true, clearCache: true }));
     await dispatch(loadMetadataForCard(card));
-    dispatch(autoWireParametersToNewCard({ dashcard_id: dashcardId }));
+    dispatch(showAutoWireToastNewCard({ dashcard_id: dashcardId }));
   };
 
 export const addHeadingDashCardToDashboard =
@@ -234,7 +235,7 @@ export const replaceCard =
 
     dispatch(fetchCardData(card, dashcard, { reload: true, clearCache: true }));
     await dispatch(loadMetadataForCard(card));
-    dispatch(autoWireParametersToNewCard({ dashcard_id: dashcardId }));
+    dispatch(showAutoWireToastNewCard({ dashcard_id: dashcardId }));
 
     dashboardId && trackQuestionReplaced(dashboardId);
   };
@@ -249,6 +250,8 @@ export const removeCardFromDashboard = createThunkAction(
       cardId: DashboardCard["card_id"];
     }) =>
     dispatch => {
+      dispatch(closeAddCardAutoWireToasts());
+
       // @ts-expect-error — data-fetching.js actions must be converted to TypeScript
       dispatch(cancelFetchCardData(cardId, dashcardId));
       return { dashcardId };
diff --git a/frontend/src/metabase/dashboard/actions/cards.unit.spec.ts b/frontend/src/metabase/dashboard/actions/cards.unit.spec.ts
index d4a1854db44..1cedb3bf504 100644
--- a/frontend/src/metabase/dashboard/actions/cards.unit.spec.ts
+++ b/frontend/src/metabase/dashboard/actions/cards.unit.spec.ts
@@ -9,7 +9,6 @@ import {
   setupCardQueryMetadataEndpoint,
 } from "__support__/server-mocks";
 import { Api } from "metabase/api";
-import { checkNotNull } from "metabase/lib/types";
 import { mainReducers } from "metabase/reducers-main";
 import { CardApi } from "metabase/services";
 import type {
@@ -45,9 +44,14 @@ import {
 
 import type { SectionLayout } from "../sections";
 import { layoutOptions } from "../sections";
-import { getDashCardById, getDashcards } from "../selectors";
+import { getDashboardById, getDashCardById, getDashcards } from "../selectors";
 
-import { addSectionToDashboard, replaceCard } from "./cards-typed";
+import type { AddCardToDashboardOpts } from "./cards-typed";
+import {
+  addCardToDashboard,
+  addSectionToDashboard,
+  replaceCard,
+} from "./cards-typed";
 
 const DATE_PARAMETER = createMockParameter({
   id: "1",
@@ -264,17 +268,8 @@ describe("dashboard/actions/cards", () => {
       );
     });
 
-    it("should auto-wire parameters", async () => {
+    it("should not auto-wire parameters", async () => {
       const nextCardId = ORDERS_LINE_CHART_CARD.id;
-      const otherCardParameterMappings = checkNotNull(
-        PIE_CHART_DASHCARD.parameter_mappings,
-      );
-      const expectedParameterMappings = otherCardParameterMappings.map(
-        mapping => ({
-          ...mapping,
-          card_id: nextCardId,
-        }),
-      );
 
       const { nextDashCard } = await runReplaceCardAction({
         dashcardId: TABLE_DASHCARD.id,
@@ -282,9 +277,21 @@ describe("dashboard/actions/cards", () => {
         dashcards: [...DASHCARDS, PIE_CHART_DASHCARD],
       });
 
-      expect(nextDashCard.parameter_mappings).toEqual(
-        expectedParameterMappings,
-      );
+      expect(nextDashCard.parameter_mappings).toEqual([]);
+    });
+  });
+
+  describe("addCardToDashboard", () => {
+    it("should not auto-wire parameters", async () => {
+      const { nextDashCard } = await runAddCardToDashboard({
+        dashId: DASHBOARD.id,
+        cardId: ORDERS_LINE_CHART_CARD.id,
+        tabId: null,
+        // for auto-wiring
+        dashcards: [...DASHCARDS, PIE_CHART_DASHCARD],
+      });
+
+      expect(nextDashCard.parameter_mappings).toEqual([]);
     });
   });
 });
@@ -338,3 +345,28 @@ async function runReplaceCardAction({
     cardQueryEndpointSpy,
   };
 }
+
+async function runAddCardToDashboard({
+  dashId,
+  tabId,
+  cardId,
+  ...opts
+}: SetupOpts & AddCardToDashboardOpts) {
+  const { store } = setup(opts);
+
+  await addCardToDashboard({
+    dashId,
+    tabId,
+    cardId,
+  })(store.dispatch, store.getState);
+  const nextState = store.getState();
+
+  const tempDashCardId =
+    getDashboardById(nextState, dashId).dashcards.find(
+      dashcardId => dashcardId < 0,
+    ) ?? -1;
+
+  return {
+    nextDashCard: getDashCardById(nextState, tempDashCardId),
+  };
+}
diff --git a/frontend/src/metabase/dashboard/actions/parameters.ts b/frontend/src/metabase/dashboard/actions/parameters.ts
index b0fb9bdea61..74aa2331bfb 100644
--- a/frontend/src/metabase/dashboard/actions/parameters.ts
+++ b/frontend/src/metabase/dashboard/actions/parameters.ts
@@ -2,8 +2,11 @@ import { assoc } from "icepick";
 import { t } from "ttag";
 import _ from "underscore";
 
-import { autoWireDashcardsWithMatchingParameters } from "metabase/dashboard/actions/auto-wire-parameters/actions";
-import { closeAutoWireParameterToast } from "metabase/dashboard/actions/auto-wire-parameters/toasts";
+import { showAutoWireToast } from "metabase/dashboard/actions/auto-wire-parameters/actions";
+import {
+  closeAutoWireParameterToast,
+  closeAddCardAutoWireToasts,
+} from "metabase/dashboard/actions/auto-wire-parameters/toasts";
 import { getParameterMappings } from "metabase/dashboard/actions/auto-wire-parameters/utils";
 import { updateDashboard } from "metabase/dashboard/actions/save";
 import { SIDEBAR_NAME } from "metabase/dashboard/constants";
@@ -28,7 +31,6 @@ import type {
   ParameterId,
   ParameterMappingOptions,
   ParameterTarget,
-  QuestionDashboardCard,
   ValuesQueryType,
   ValuesSourceConfig,
   ValuesSourceType,
@@ -52,6 +54,7 @@ import {
   getParameters,
   getParameterValues,
   getParameterMappingsBeforeEditing,
+  getSelectedTabId,
 } from "../selectors";
 import { isQuestionDashCard } from "../utils";
 
@@ -154,6 +157,8 @@ export const REMOVE_PARAMETER = "metabase/dashboard/REMOVE_PARAMETER";
 export const removeParameter = createThunkAction(
   REMOVE_PARAMETER,
   (parameterId: ParameterId) => (dispatch, getState) => {
+    dispatch(closeAddCardAutoWireToasts());
+
     updateParameters(dispatch, getState, parameters =>
       parameters.filter(p => p.id !== parameterId),
     );
@@ -178,12 +183,10 @@ export const setParameterMapping = createThunkAction(
       const dashcard = getDashCardById(getState(), dashcardId);
 
       if (target !== null && isQuestionDashCard(dashcard)) {
+        const selectedTabId = getSelectedTabId(getState());
+
         dispatch(
-          autoWireDashcardsWithMatchingParameters(
-            parameterId,
-            dashcard,
-            target,
-          ),
+          showAutoWireToast(parameterId, dashcard, target, selectedTabId),
         );
       }
 
@@ -192,8 +195,7 @@ export const setParameterMapping = createThunkAction(
           id: dashcardId,
           attributes: {
             parameter_mappings: getParameterMappings(
-              // TODO remove type casting when getParameterMappings is fixed
-              dashcard as QuestionDashboardCard,
+              dashcard,
               parameterId,
               cardId,
               target,
-- 
GitLab