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