From 2d432598a5ae6073686d7cd388f6022626e967c4 Mon Sep 17 00:00:00 2001
From: Ryan Laurie <30528226+iethree@users.noreply.github.com>
Date: Thu, 23 Feb 2023 16:40:03 -0700
Subject: [PATCH] Add basic implicit actions e2e tests (#28476)

* add basic implicit action e2e tests

* DRY up the actions tests
---
 .../dashboard/containers/DashboardHeader.jsx  |   1 +
 .../e2e/helpers/e2e-action-helpers.js         |  30 ++-
 .../e2e/helpers/e2e-dashboard-helpers.js      |   1 +
 .../actions-on-dashboards.cy.spec.js          | 203 ++++++++++++++++--
 4 files changed, 215 insertions(+), 20 deletions(-)

diff --git a/frontend/src/metabase/dashboard/containers/DashboardHeader.jsx b/frontend/src/metabase/dashboard/containers/DashboardHeader.jsx
index 0667dbc91b1..2ed8c04f1bf 100644
--- a/frontend/src/metabase/dashboard/containers/DashboardHeader.jsx
+++ b/frontend/src/metabase/dashboard/containers/DashboardHeader.jsx
@@ -304,6 +304,7 @@ class DashboardHeader extends Component {
             <Tooltip key="add-action-button" tooltip={t`Add action button`}>
               <DashboardHeaderButton
                 onClick={() => this.onAddAction()}
+                aria-label={t`Add action`}
                 data-metabase-event={`Dashboard;Add Action Button`}
               >
                 <Icon name="click" size={18} />
diff --git a/frontend/test/__support__/e2e/helpers/e2e-action-helpers.js b/frontend/test/__support__/e2e/helpers/e2e-action-helpers.js
index 09364d60648..93a805d8b9e 100644
--- a/frontend/test/__support__/e2e/helpers/e2e-action-helpers.js
+++ b/frontend/test/__support__/e2e/helpers/e2e-action-helpers.js
@@ -1,5 +1,5 @@
+import { capitalize } from "inflection";
 import { SAMPLE_DB_ID } from "__support__/e2e/cypress_data";
-
 export function enableActionsForDB(dbId = SAMPLE_DB_ID) {
   return cy.request("PUT", `/api/database/${dbId}`, {
     settings: {
@@ -18,3 +18,31 @@ export function fillActionQuery(query) {
 export function createAction(actionDetails) {
   return cy.request("POST", "/api/action", actionDetails);
 }
+
+/**
+ * create a single implicit action of the given kind for the given model
+ *
+ * @param {Object} actionParams
+ * @param {"create" | "update" | "delete "} actionParams.kind
+ * @param {number} actionParams.model_id
+ */
+export function createImplicitAction({ model_id, kind }) {
+  return createAction({
+    kind: `row/${kind}`,
+    name: capitalize(kind),
+    type: "implicit",
+    model_id,
+  });
+}
+
+/**
+ * create all implicit actions for the given model
+ *
+ * @param {object} actionParams
+ * @param {number} actionParams.model_id
+ */
+export function createImplicitActions({ modelId }) {
+  createImplicitAction({ modelId, kind: "create" });
+  createImplicitAction({ modelId, kind: "update" });
+  createImplicitAction({ modelId, kind: "delete" });
+}
diff --git a/frontend/test/__support__/e2e/helpers/e2e-dashboard-helpers.js b/frontend/test/__support__/e2e/helpers/e2e-dashboard-helpers.js
index 86240176ddf..3d38b3904da 100644
--- a/frontend/test/__support__/e2e/helpers/e2e-dashboard-helpers.js
+++ b/frontend/test/__support__/e2e/helpers/e2e-dashboard-helpers.js
@@ -22,6 +22,7 @@ export function editDashboard() {
 export function saveDashboard() {
   cy.findByText("Save").click();
   cy.findByText("You're editing this dashboard.").should("not.exist");
+  cy.wait(1); // this is stupid but necessary to due to the dashboard resizing and detaching elements
 }
 
 export function checkFilterLabelAndValue(label, value) {
diff --git a/frontend/test/metabase/scenarios/dashboard/actions-on-dashboards.cy.spec.js b/frontend/test/metabase/scenarios/dashboard/actions-on-dashboards.cy.spec.js
index f483f564f11..ec90a209d5b 100644
--- a/frontend/test/metabase/scenarios/dashboard/actions-on-dashboards.cy.spec.js
+++ b/frontend/test/metabase/scenarios/dashboard/actions-on-dashboards.cy.spec.js
@@ -8,11 +8,19 @@ import {
   visitDashboard,
   editDashboard,
   saveDashboard,
+  modal,
+  setFilter,
+  sidebar,
+  popover,
+  filterWidget,
+  createImplicitAction,
 } from "__support__/e2e/helpers";
 
 import { WRITABLE_DB_ID } from "__support__/e2e/cypress_data";
+import { addWidgetStringFilter } from "../native-filters/helpers/e2e-field-filter-helpers";
 
 const TEST_TABLE = "scoreboard_actions";
+const MODEL_NAME = "Test Action Model";
 
 ["mysql", "postgres"].forEach(dialect => {
   describe(
@@ -26,6 +34,11 @@ const TEST_TABLE = "scoreboard_actions";
         );
         cy.intercept("GET", "/api/action?model-id=*").as("getActions");
 
+        cy.intercept(
+          "GET",
+          "/api/dashboard/*/dashcard/*/execute?parameters=*",
+        ).as("executePrefetch");
+
         cy.intercept("POST", "/api/dashboard/*/dashcard/*/execute").as(
           "executeAPI",
         );
@@ -64,31 +77,43 @@ const TEST_TABLE = "scoreboard_actions";
         cy.findByPlaceholderText("My new fantastic action").type("Add Zebras");
         cy.findByText("Create").click();
 
-        cy.createDashboard({ name: `action packed dash` }).then(
-          ({ body: { id: dashboardId } }) => {
-            visitDashboard(dashboardId);
-          },
-        );
+        createDashboardWithActionButton({
+          actionName: "Add Zebras",
+        });
+
+        cy.button("Add Zebras").click();
+
+        cy.wait("@executeAPI");
 
-        editDashboard();
-        cy.icon("click").click();
-        cy.get("aside").within(() => {
-          cy.button("Pick an action").click();
+        queryWritableDB(
+          `SELECT * FROM ${TEST_TABLE} WHERE team_name = 'Zany Zebras'`,
+          dialect,
+        ).then(result => {
+          expect(result.rows.length).to.equal(1);
         });
+      });
 
-        cy.findByRole("dialog").within(() => {
-          cy.findByText("Test Model").click();
-          cy.findByText("Add Zebras").click();
-          cy.button("Done").click();
+      it("adds an implicit create action to a dashboard and runs it", () => {
+        createModelFromTable(TEST_TABLE);
+        cy.get("@modelId").then(id => {
+          createImplicitAction({
+            kind: "create",
+            model_id: id,
+          });
+        });
+
+        createDashboardWithActionButton({
+          actionName: "Create",
         });
 
-        saveDashboard();
+        cy.button("Create").click();
 
-        // this keeps the test from flaking because it's confused about the detached
-        // edit-mode button
-        cy.findByText(/^Edited a few seconds ago/).should("not.be.visible");
+        modal().within(() => {
+          cy.findByPlaceholderText("team_name").type("Zany Zebras");
+          cy.findByPlaceholderText("score").type("44");
 
-        cy.button("Click Me").click();
+          cy.button("Save").click();
+        });
 
         cy.wait("@executeAPI");
 
@@ -97,6 +122,97 @@ const TEST_TABLE = "scoreboard_actions";
           dialect,
         ).then(result => {
           expect(result.rows.length).to.equal(1);
+
+          expect(result.rows[0].score).to.equal(44);
+        });
+      });
+
+      it("adds an implicit update action to a dashboard and runs it", () => {
+        const actionName = "Update";
+
+        createModelFromTable(TEST_TABLE);
+
+        cy.get("@modelId").then(id => {
+          createImplicitAction({
+            kind: "update",
+            model_id: id,
+          });
+        });
+
+        createDashboardWithActionButton({
+          actionName,
+          idFilter: true,
+        });
+
+        filterWidget().click();
+        addWidgetStringFilter("5");
+
+        cy.button(actionName).click();
+
+        cy.wait("@executePrefetch");
+        // let's check that the existing values are pre-filled correctly
+        modal().within(() => {
+          cy.findByPlaceholderText("team_name")
+            .should("have.value", "Energetic Elephants")
+            .clear()
+            .type("Emotional Elephants");
+
+          cy.findByPlaceholderText("score")
+            .should("have.value", "30")
+            .clear()
+            .type("88");
+
+          cy.button("Update").click();
+        });
+
+        cy.wait("@executeAPI");
+
+        queryWritableDB(
+          `SELECT * FROM ${TEST_TABLE} WHERE team_name = 'Emotional Elephants'`,
+          dialect,
+        ).then(result => {
+          expect(result.rows.length).to.equal(1);
+
+          expect(result.rows[0].score).to.equal(88);
+        });
+      });
+
+      it("adds an implicit delete action to a dashboard and runs it", () => {
+        queryWritableDB(
+          `SELECT * FROM ${TEST_TABLE} WHERE team_name = 'Cuddly Cats'`,
+          dialect,
+        ).then(result => {
+          expect(result.rows.length).to.equal(1);
+          expect(result.rows[0].id).to.equal(3);
+        });
+
+        createModelFromTable(TEST_TABLE);
+
+        cy.get("@modelId").then(id => {
+          createImplicitAction({
+            kind: "delete",
+            model_id: id,
+          });
+        });
+
+        createDashboardWithActionButton({
+          actionName: "Delete",
+        });
+
+        cy.button("Delete").click();
+
+        modal().within(() => {
+          cy.findByPlaceholderText("id").type("3");
+          cy.button("Delete").click();
+        });
+
+        cy.wait("@executeAPI");
+
+        queryWritableDB(
+          `SELECT * FROM ${TEST_TABLE} WHERE team_name = 'Cuddly Cats'`,
+          dialect,
+        ).then(result => {
+          expect(result.rows.length).to.equal(0);
         });
       });
     },
@@ -108,7 +224,7 @@ const createModelFromTable = tableName => {
     cy.createQuestion(
       {
         database: WRITABLE_DB_ID,
-        name: "Test Model",
+        name: MODEL_NAME,
         query: {
           "source-table": tableId,
         },
@@ -121,3 +237,52 @@ const createModelFromTable = tableName => {
     );
   });
 };
+
+function createDashboardWithActionButton({
+  actionName,
+  modelName = MODEL_NAME,
+  idFilter = false,
+}) {
+  cy.createDashboard({ name: "action packed dashboard" }).then(
+    ({ body: { id: dashboardId } }) => {
+      visitDashboard(dashboardId);
+    },
+  );
+
+  editDashboard();
+
+  if (idFilter) {
+    setFilter("ID");
+    sidebar().within(() => {
+      cy.button("Done").click();
+    });
+  }
+
+  cy.button("Add action").click();
+  cy.get("aside").within(() => {
+    cy.findByPlaceholderText("Button text").clear().type(actionName);
+    cy.button("Pick an action").click();
+  });
+
+  cy.findByRole("dialog").within(() => {
+    cy.findByText(modelName).click();
+    cy.findByText(actionName).click();
+  });
+
+  if (idFilter) {
+    cy.findByRole("dialog").within(() => {
+      cy.findAllByText(/ask the user/i)
+        .first()
+        .click();
+    });
+    popover().within(() => {
+      cy.findByText("ID").click();
+    });
+  }
+
+  cy.findByRole("dialog").within(() => {
+    cy.button("Done").click();
+  });
+
+  saveDashboard();
+}
-- 
GitLab