From b3749f8fe515021dda2a878488df4885a0fae33a Mon Sep 17 00:00:00 2001
From: Alexander Polyankin <alexander.polyankin@metabase.com>
Date: Thu, 20 Jun 2024 10:29:28 -0400
Subject: [PATCH] E2E tests for click behaviors with temporal unit parameters
 (#44436)

---
 .../temporal-unit-parameters.cy.spec.js       | 269 +++++++++++++++---
 1 file changed, 226 insertions(+), 43 deletions(-)

diff --git a/e2e/test/scenarios/dashboard-filters/temporal-unit-parameters.cy.spec.js b/e2e/test/scenarios/dashboard-filters/temporal-unit-parameters.cy.spec.js
index 04e51fa8864..d6d9defe523 100644
--- a/e2e/test/scenarios/dashboard-filters/temporal-unit-parameters.cy.spec.js
+++ b/e2e/test/scenarios/dashboard-filters/temporal-unit-parameters.cy.spec.js
@@ -1,15 +1,16 @@
 import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
 import {
-  addOrUpdateDashboardCard,
   appBar,
   clearFilterWidget,
   createNativeQuestion,
   createQuestion,
+  dashboardHeader,
   dashboardParametersDoneButton,
   dashboardParameterSidebar,
   editDashboard,
   filterWidget,
   getDashboardCard,
+  modal,
   popover,
   queryBuilderHeader,
   queryBuilderMain,
@@ -18,8 +19,10 @@ import {
   saveDashboard,
   selectDashboardFilter,
   setFilter,
+  sidebar,
   undoToast,
   undoToastList,
+  updateDashboardCards,
   visitDashboard,
   visitEmbeddedPage,
 } from "e2e/support/helpers";
@@ -27,7 +30,7 @@ import {
 const { ORDERS, ORDERS_ID, PRODUCTS } = SAMPLE_DATABASE;
 
 const dashboardDetails = {
-  name: "Test dashboard",
+  name: "Test Dashboard",
 };
 
 const singleBreakoutQuestionDetails = {
@@ -139,8 +142,24 @@ const nativeQuestionDetails = {
   },
 };
 
-const nativeQuestionWithParameterDetails = {
-  name: "SQL query with a parameter",
+const nativeQuestionWithTextParameterDetails = {
+  name: "SQL query with a text parameter",
+  display: "table",
+  native: {
+    query: "SELECT * FROM PRODUCTS WHERE CATEGORY = {{category}}",
+    "template-tags": {
+      category: {
+        id: "6b8b10ef-0104-1047-1e1b-2492d5954555",
+        name: "category",
+        "display-name": "Category",
+        type: "text",
+      },
+    },
+  },
+};
+
+const nativeQuestionWithDateParameterDetails = {
+  name: "SQL query with a date parameter",
   display: "table",
   native: {
     query: "SELECT * FROM ORDERS WHERE {{date}}",
@@ -157,6 +176,14 @@ const nativeQuestionWithParameterDetails = {
   },
 };
 
+const nativeUnitQuestionDetails = {
+  name: "SQL units",
+  display: "table",
+  native: {
+    query: "SELECT 'month' as UNIT UNION ALL SELECT 'year' as UNIT",
+  },
+};
+
 const parameterDetails = {
   id: "1",
   name: "Unit of Time",
@@ -195,7 +222,7 @@ describe("scenarios > dashboard > temporal unit parameters", () => {
       createQuestion(multiStageQuestionDetails);
       createQuestion(expressionBreakoutQuestionDetails);
       createQuestion(binningBreakoutQuestionDetails);
-      createNativeQuestion(nativeQuestionWithParameterDetails);
+      createNativeQuestion(nativeQuestionWithDateParameterDetails);
       cy.createDashboard(dashboardDetails).then(({ body: dashboard }) =>
         visitDashboard(dashboard.id),
       );
@@ -279,7 +306,7 @@ describe("scenarios > dashboard > temporal unit parameters", () => {
       removeQuestion();
 
       cy.log("native query");
-      addQuestion(nativeQuestionWithParameterDetails.name);
+      addQuestion(nativeQuestionWithDateParameterDetails.name);
       editParameter(parameterDetails.name);
       getDashboardCard()
         .findByText(/Add a variable to this question/)
@@ -407,6 +434,149 @@ describe("scenarios > dashboard > temporal unit parameters", () => {
     });
   });
 
+  describe("click behaviors", () => {
+    it("should pass a temporal unit with 'update dashboard filter' click behavior", () => {
+      createDashboardWithMappedQuestion({
+        extraQuestions: [nativeUnitQuestionDetails],
+      }).then(dashboard => visitDashboard(dashboard.id));
+
+      cy.log("setup click behavior");
+      editDashboard();
+      getDashboardCard(1)
+        .findByLabelText("Click behavior")
+        .click({ force: true });
+      sidebar().within(() => {
+        cy.findByText("UNIT").click();
+        cy.findByText("Update a dashboard filter").click();
+        cy.findByText(parameterDetails.name).click();
+      });
+      popover().findByText("UNIT").click();
+      saveDashboard();
+
+      cy.log("verify click behavior");
+      getDashboardCard(1).findByText("year").click();
+      filterWidget().findByText("Year").should("be.visible");
+      getDashboardCard(0).findByText("Created At: Year").should("be.visible");
+    });
+
+    it("should pass a temporal unit 'custom destination -> dashboard' click behavior", () => {
+      createDashboardWithMappedQuestion({
+        dashboardDetails: {
+          name: "Target dashboard",
+        },
+      });
+      cy.createDashboardWithQuestions({
+        dashboardDetails: {
+          name: "Source dashboard",
+        },
+        questions: [nativeUnitQuestionDetails],
+      }).then(({ dashboard }) => visitDashboard(dashboard.id));
+
+      cy.log("setup click behavior");
+      editDashboard();
+      getDashboardCard()
+        .findByLabelText("Click behavior")
+        .click({ force: true });
+      sidebar().within(() => {
+        cy.findByText("UNIT").click();
+        cy.findByText("Go to a custom destination").click();
+        cy.findByText("Dashboard").click();
+      });
+      modal().findByText("Target dashboard").click();
+      sidebar().findByText(parameterDetails.name).click();
+      popover().findByText("UNIT").click();
+      saveDashboard();
+
+      cy.log("verify click behavior");
+      getDashboardCard().findByText("year").click();
+      dashboardHeader().findByText("Target dashboard").should("be.visible");
+      filterWidget().findByText("Year").should("be.visible");
+      getDashboardCard().findByText("Created At: Year").should("be.visible");
+    });
+
+    it("should pass a temporal unit with 'custom destination -> url' click behavior", () => {
+      createDashboardWithMappedQuestion({
+        dashboardDetails: {
+          name: "Target dashboard",
+        },
+      }).then(dashboard => cy.wrap(dashboard.id).as("targetDashboardId"));
+      cy.createDashboardWithQuestions({
+        dashboardDetails: {
+          name: "Source dashboard",
+        },
+        questions: [nativeUnitQuestionDetails],
+      }).then(({ dashboard }) => visitDashboard(dashboard.id));
+
+      cy.log("setup click behavior");
+      editDashboard();
+      getDashboardCard()
+        .findByLabelText("Click behavior")
+        .click({ force: true });
+      sidebar().within(() => {
+        cy.findByText("UNIT").click();
+        cy.findByText("Go to a custom destination").click();
+        cy.findByText("URL").click();
+      });
+      modal().findByText("Values you can reference").click();
+      popover().within(() => {
+        cy.findByText("UNIT").should("be.visible");
+        cy.findByText(parameterDetails.name).should("not.exist");
+      });
+      cy.get("@targetDashboardId").then(targetDashboardId => {
+        modal().within(() => {
+          cy.findByPlaceholderText("e.g. http://acme.com/id/{{user_id}}").type(
+            `http://localhost:4000/dashboard/${targetDashboardId}?${parameterDetails.slug}={{UNIT}}`,
+            { parseSpecialCharSequences: false },
+          );
+          cy.button("Done").click();
+        });
+      });
+      saveDashboard();
+
+      cy.log("verify click behavior");
+      getDashboardCard().findByText("year").click();
+      dashboardHeader().findByText("Target dashboard").should("be.visible");
+      filterWidget().findByText("Year").should("be.visible");
+      getDashboardCard().findByText("Created At: Year").should("be.visible");
+    });
+
+    it("should not allow to use temporal unit parameter values with SQL queries", () => {
+      createNativeQuestion(nativeQuestionWithTextParameterDetails);
+      createDashboardWithMappedQuestion().then(dashboard =>
+        visitDashboard(dashboard.id),
+      );
+
+      cy.log("setup click behavior only with a temporal unit parameter");
+      editDashboard();
+      getDashboardCard()
+        .findByLabelText("Click behavior")
+        .click({ force: true });
+      sidebar().within(() => {
+        cy.findByText("Count").click();
+        cy.findByText("Go to a custom destination").click();
+        cy.findByText("Saved question").click();
+      });
+      modal().findByText(nativeQuestionWithTextParameterDetails.name).click();
+      sidebar().findByText("No available targets").should("be.visible");
+
+      cy.log("setup click behavior with a text parameter");
+      setFilter("Text or Category");
+      dashboardParametersDoneButton().click();
+      getDashboardCard()
+        .findByLabelText("Click behavior")
+        .click({ force: true });
+      sidebar().within(() => {
+        cy.findByText(/Count goes to/).click();
+        cy.findByText("Go to a custom destination").click();
+        cy.findByText("Category").click();
+      });
+      popover().within(() => {
+        cy.findByText("Text").should("be.visible");
+        cy.findByText(parameterDetails.name).should("not.exist");
+      });
+    });
+  });
+
   describe("auto-wiring", () => {
     it("should not auto-wire to cards without breakout columns", () => {
       cy.createDashboardWithQuestions({
@@ -517,7 +687,9 @@ describe("scenarios > dashboard > temporal unit parameters", () => {
 
   describe("parameter settings", () => {
     it("should be able to set available temporal units", () => {
-      createDashboardWithCard().then(dashboard => visitDashboard(dashboard.id));
+      createDashboardWithMappedQuestion().then(dashboard =>
+        visitDashboard(dashboard.id),
+      );
 
       editDashboard();
       editParameter(parameterDetails.name);
@@ -541,7 +713,9 @@ describe("scenarios > dashboard > temporal unit parameters", () => {
     });
 
     it("should clear the default value if it is no longer within the allowed unit list", () => {
-      createDashboardWithCard().then(dashboard => visitDashboard(dashboard.id));
+      createDashboardWithMappedQuestion().then(dashboard =>
+        visitDashboard(dashboard.id),
+      );
 
       cy.log("set the default value");
       editDashboard();
@@ -560,7 +734,7 @@ describe("scenarios > dashboard > temporal unit parameters", () => {
     });
 
     it("should be able to set the default value and make it required", () => {
-      createDashboardWithCard().then(dashboard =>
+      createDashboardWithMappedQuestion().then(dashboard =>
         cy.wrap(dashboard.id).as("dashboardId"),
       );
       visitDashboard("@dashboardId");
@@ -600,7 +774,7 @@ describe("scenarios > dashboard > temporal unit parameters", () => {
 
   describe("query string parameters", () => {
     it("should be able to parse the parameter value from the url", () => {
-      createDashboardWithCard().then(dashboard => {
+      createDashboardWithMappedQuestion().then(dashboard => {
         visitDashboard(dashboard.id, { params: { unit_of_time: "year" } });
       });
       getDashboardCard().findByText("Created At: Year").should("be.visible");
@@ -609,7 +783,7 @@ describe("scenarios > dashboard > temporal unit parameters", () => {
 
   describe("permissions", () => {
     it("should add a temporal unit parameter and connect it to a card and drill thru", () => {
-      createDashboardWithCard().then(dashboard => {
+      createDashboardWithMappedQuestion().then(dashboard => {
         cy.signIn("nodata");
         visitDashboard(dashboard.id);
       });
@@ -632,7 +806,7 @@ describe("scenarios > dashboard > temporal unit parameters", () => {
     });
 
     it("should be able to use temporal unit parameters in a public dashboard", () => {
-      createDashboardWithCard().then(dashboard => {
+      createDashboardWithMappedQuestion().then(dashboard => {
         cy.request("POST", `/api/dashboard/${dashboard.id}/public_link`).then(
           ({ body: { uuid } }) => {
             cy.signOut();
@@ -647,10 +821,12 @@ describe("scenarios > dashboard > temporal unit parameters", () => {
     });
 
     it("should be able to use temporal unit parameters in a embedded dashboard", () => {
-      createDashboardWithCard({
-        enable_embedding: true,
-        embedding_params: {
-          [parameterDetails.slug]: "enabled",
+      createDashboardWithMappedQuestion({
+        dashboardDetails: {
+          enable_embedding: true,
+          embedding_params: {
+            [parameterDetails.slug]: "enabled",
+          },
         },
       }).then(dashboard => {
         visitEmbeddedPage({
@@ -691,25 +867,30 @@ function editParameter(name) {
     .click();
 }
 
-function createDashboardWithCard(dashboardDetails = {}) {
-  return createQuestion(singleBreakoutQuestionDetails).then(
-    ({ body: card }) => {
-      return cy
-        .createDashboard({
-          ...dashboardDetails,
-          parameters: [parameterDetails],
-        })
-        .then(({ body: dashboard }) => {
-          return addOrUpdateDashboardCard({
-            dashboard_id: dashboard.id,
+function createDashboardWithMappedQuestion({
+  dashboardDetails = {},
+  extraQuestions = [],
+} = {}) {
+  return cy
+    .createDashboardWithQuestions({
+      dashboardDetails: {
+        ...dashboardDetails,
+        parameters: [parameterDetails],
+      },
+      questions: [singleBreakoutQuestionDetails, ...extraQuestions],
+    })
+    .then(({ dashboard, questions: [card, ...extraCards] }) => {
+      return updateDashboardCards({
+        dashboard_id: dashboard.id,
+        cards: [
+          {
             card_id: card.id,
-            card: {
-              parameter_mappings: [getParameterMapping(card)],
-            },
-          }).then(() => dashboard);
-        });
-    },
-  );
+            parameter_mappings: [getParameterMapping(card)],
+          },
+          ...extraCards.map(({ id }) => ({ card_id: id })),
+        ],
+      }).then(() => dashboard);
+    });
 }
 
 function createDashboardWithMultiSeriesCard() {
@@ -724,16 +905,18 @@ function createDashboardWithMultiSeriesCard() {
         name: "Question 2",
         display: "line",
       }).then(({ body: card2 }) => {
-        addOrUpdateDashboardCard({
-          card_id: card1.id,
+        updateDashboardCards({
           dashboard_id: dashboard.id,
-          card: {
-            series: [
-              {
-                id: card2.id,
-              },
-            ],
-          },
+          cards: [
+            {
+              card_id: card1.id,
+              series: [
+                {
+                  id: card2.id,
+                },
+              ],
+            },
+          ],
         }).then(() => dashboard);
       });
     });
-- 
GitLab