Skip to content
Snippets Groups Projects
Unverified Commit 28433747 authored by Ryan Laurie's avatar Ryan Laurie Committed by GitHub
Browse files

Actions/e2e model actions tests (#29295)

* extract model creation helper

* update model actions test to actually mutate data

* fix merge conflicts and clean up
parent e95d9c77
No related branches found
No related tags found
No related merge requests found
......@@ -185,6 +185,29 @@ export function getTableId({ databaseId = WRITABLE_DB_ID, name }) {
});
}
export const createModelFromTableName = ({
tableName,
modelName = "Test Action Model",
idAlias = 'modelId'
}) => {
getTableId({ name: tableName }).then(tableId => {
cy.createQuestion(
{
database: WRITABLE_DB_ID,
name: modelName,
query: {
"source-table": tableId,
},
dataset: true,
},
{
wrapId: true,
idAlias,
},
);
});
};
export function waitForSyncToFinish({ iteration = 0, dbId = 2, tableName = '' }) {
// 100 x 100ms should be plenty of time for the sync to finish.
if (iteration === 100) {
......
......@@ -2,7 +2,7 @@ import {
restore,
queryWritableDB,
resetTestTable,
getTableId,
createModelFromTableName,
fillActionQuery,
resyncDatabase,
visitDashboard,
......@@ -55,6 +55,7 @@ const MODEL_NAME = "Test Action Model";
restore(`${dialect}-writable`);
cy.signInAsAdmin();
resyncDatabase({ dbId: WRITABLE_DB_ID, tableName: TEST_TABLE });
createModelFromTableName({ tableName: TEST_TABLE, modelName: MODEL_NAME });
});
it("adds a custom query action to a dashboard and runs it", () => {
......@@ -68,8 +69,6 @@ const MODEL_NAME = "Test Action Model";
expect(result.rows[0].score).to.equal(0);
});
createModelFromTable(TEST_TABLE);
cy.get("@modelId").then(id => {
cy.visit(`/model/${id}/detail`);
cy.wait(["@getModel", "@getModelActions", "@getCardAssociations"]);
......@@ -124,7 +123,6 @@ const MODEL_NAME = "Test Action Model";
});
it("adds an implicit create action to a dashboard and runs it", () => {
createModelFromTable(TEST_TABLE);
cy.get("@modelId").then(id => {
createImplicitAction({
kind: "create",
......@@ -160,8 +158,6 @@ const MODEL_NAME = "Test Action Model";
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",
......@@ -216,8 +212,6 @@ const MODEL_NAME = "Test Action Model";
expect(result.rows[0].id).to.equal(3);
});
createModelFromTable(TEST_TABLE);
cy.get("@modelId").then(id => {
createImplicitAction({
kind: "delete",
......@@ -252,11 +246,11 @@ const MODEL_NAME = "Test Action Model";
resetTestTable({ type: dialect, table: TEST_COLUMNS_TABLE });
restore(`${dialect}-writable`);
cy.signInAsAdmin();
resyncDatabase({ dbId: WRITABLE_DB_ID, tableName: TEST_TABLE });
resyncDatabase({ dbId: WRITABLE_DB_ID, tableName: TEST_COLUMNS_TABLE });
createModelFromTableName({ tableName: TEST_COLUMNS_TABLE, modelName: MODEL_NAME });
});
it("can update various data types via implicit actions", () => {
createModelFromTable(TEST_COLUMNS_TABLE);
cy.get("@modelId").then(id => {
createImplicitAction({
kind: "update",
......@@ -353,7 +347,6 @@ const MODEL_NAME = "Test Action Model";
});
it("can insert various data types via implicit actions", () => {
createModelFromTable(TEST_COLUMNS_TABLE);
cy.get("@modelId").then(id => {
createImplicitAction({
kind: "create",
......@@ -439,7 +432,6 @@ const MODEL_NAME = "Test Action Model";
});
it("does not show json, enum, or binary columns for implicit actions", () => {
createModelFromTable(TEST_COLUMNS_TABLE);
cy.get("@modelId").then(id => {
createImplicitAction({
kind: "create",
......@@ -468,7 +460,6 @@ const MODEL_NAME = "Test Action Model";
});
it("properly loads and updates date and time fields for implicit update actions", () => {
createModelFromTable(TEST_COLUMNS_TABLE);
cy.get("@modelId").then(id => {
createImplicitAction({
kind: "update",
......@@ -581,25 +572,6 @@ const MODEL_NAME = "Test Action Model";
});
});
const createModelFromTable = tableName => {
getTableId({ name: tableName }).then(tableId => {
cy.createQuestion(
{
database: WRITABLE_DB_ID,
name: MODEL_NAME,
query: {
"source-table": tableId,
},
dataset: true,
},
{
wrapId: true,
idAlias: "modelId",
},
);
});
};
function createDashboardWithActionButton({
actionName,
modelName = MODEL_NAME,
......
import { assocIn } from "icepick";
import {
setActionsEnabledForDB,
modal,
......@@ -7,13 +8,19 @@ import {
createAction,
navigationSidebar,
openNavigationSidebar,
resetTestTable,
resyncDatabase,
createModelFromTableName,
queryWritableDB,
} from "e2e/support/helpers";
import { SAMPLE_DB_ID, USER_GROUPS } from "e2e/support/cypress_data";
import { SAMPLE_DB_ID, USER_GROUPS, WRITABLE_DB_ID } from "e2e/support/cypress_data";
import { createMockActionParameter } from "metabase-types/api/mocks";
const PG_DB_ID = 2;
const PG_ORDERS_TABLE_ID = 9;
const WRITABLE_TEST_TABLE = "scoreboard_actions";
const SAMPLE_ORDERS_MODEL = {
name: "Order",
......@@ -68,6 +75,12 @@ const SAMPLE_QUERY_ACTION = {
},
};
const SAMPLE_WRITABLE_QUERY_ACTION = assocIn(
SAMPLE_QUERY_ACTION,
["dataset_query", "native", "query"],
`UPDATE ${WRITABLE_TEST_TABLE} SET score = 22 WHERE id = {{ ${TEST_TEMPLATE_TAG.name} }}`,
);
describe(
"scenarios > models > actions",
{ tags: ["@external", "@actions"] },
......@@ -193,105 +206,6 @@ describe(
cy.findByText(QUERY).should("be.visible");
});
it("should allow to execute actions from the model page", () => {
cy.get("@modelId").then(modelId => {
createAction({
...SAMPLE_QUERY_ACTION,
model_id: modelId,
});
cy.visit(`/model/${modelId}/detail/actions`);
cy.wait("@getModel");
});
runActionFor(SAMPLE_QUERY_ACTION.name);
modal().within(() => {
cy.findByLabelText(TEST_PARAMETER.name).type("1");
cy.button(SAMPLE_QUERY_ACTION.name).click();
});
cy.findByText(`${SAMPLE_QUERY_ACTION.name} ran successfully`).should(
"be.visible",
);
});
it("should allow to make actions public and execute them", () => {
const IMPLICIT_ACTION_NAME = "Update order";
cy.get("@modelId").then(modelId => {
createAction({
...SAMPLE_QUERY_ACTION,
model_id: modelId,
});
createAction({
type: "implicit",
kind: "row/update",
name: IMPLICIT_ACTION_NAME,
model_id: modelId,
});
cy.visit(`/model/${modelId}/detail/actions`);
cy.wait("@getModel");
});
enableSharingFor(SAMPLE_QUERY_ACTION.name, {
publicUrlAlias: "queryActionPublicUrl",
});
enableSharingFor(IMPLICIT_ACTION_NAME, {
publicUrlAlias: "implicitActionPublicUrl",
});
cy.signOut();
cy.get("@queryActionPublicUrl").then(url => {
cy.visit(url);
cy.findByLabelText(TEST_PARAMETER.name).type("1");
cy.button(SAMPLE_QUERY_ACTION.name).click();
cy.findByText(`${SAMPLE_QUERY_ACTION.name} ran successfully`).should(
"be.visible",
);
cy.findByRole("form").should("not.exist");
cy.button(SAMPLE_QUERY_ACTION.name).should("not.exist");
});
cy.get("@implicitActionPublicUrl").then(url => {
cy.visit(url);
// Order 1 has quantity 2 by default, so we're not actually mutating data
cy.findByLabelText("Id").type("1");
cy.findByLabelText(/quantity/i).type("2");
cy.button(IMPLICIT_ACTION_NAME).click();
cy.findByText(`${IMPLICIT_ACTION_NAME} ran successfully`).should(
"be.visible",
);
cy.findByRole("form").should("not.exist");
cy.button(IMPLICIT_ACTION_NAME).should("not.exist");
});
cy.signInAsAdmin();
cy.get("@modelId").then(modelId => {
cy.visit(`/model/${modelId}/detail/actions`);
cy.wait("@getModel");
});
disableSharingFor(SAMPLE_QUERY_ACTION.name);
disableSharingFor(IMPLICIT_ACTION_NAME);
cy.get("@queryActionPublicUrl").then(url => {
cy.visit(url);
cy.findByRole("form").should("not.exist");
cy.button(SAMPLE_QUERY_ACTION.name).should("not.exist");
cy.findByText("Not found").should("be.visible");
});
cy.get("@implicitActionPublicUrl").then(url => {
cy.visit(url);
cy.findByRole("form").should("not.exist");
cy.button(IMPLICIT_ACTION_NAME).should("not.exist");
cy.findByText("Not found").should("be.visible");
});
});
it("should respect permissions", () => {
// Enabling actions for sample database as well
// to test database picker behavior in the action editor
......@@ -381,6 +295,163 @@ describe(
},
);
['postgres', 'mysql'].forEach((dialect) => {
describe(`Write actions on model detail page (${dialect})`, () => {
beforeEach(() => {
cy.intercept("GET", "/api/card/*").as("getModel");
resetTestTable({ type: dialect, table: WRITABLE_TEST_TABLE });
restore(`${dialect}-writable`);
cy.signInAsAdmin();
resyncDatabase({ dbId: WRITABLE_DB_ID, tableName: WRITABLE_TEST_TABLE });
createModelFromTableName({ tableName: WRITABLE_TEST_TABLE, idAlias: "writableModelId" });
});
it("should allow action execution from the model detail page", () => {
queryWritableDB(
`SELECT * FROM ${WRITABLE_TEST_TABLE} WHERE id = 1`,
dialect,
).then(result => {
const row = result.rows[0];
expect(row.score).to.equal(0);
});
cy.get("@writableModelId").then(modelId => {
createAction({
...SAMPLE_WRITABLE_QUERY_ACTION,
model_id: modelId,
});
cy.visit(`/model/${modelId}/detail/actions`);
cy.wait("@getModel");
});
runActionFor(SAMPLE_QUERY_ACTION.name);
modal().within(() => {
cy.findByLabelText(TEST_PARAMETER.name).type("1");
cy.button(SAMPLE_QUERY_ACTION.name).click();
});
cy.findByText(`${SAMPLE_QUERY_ACTION.name} ran successfully`).should(
"be.visible",
);
queryWritableDB(
`SELECT * FROM ${WRITABLE_TEST_TABLE} WHERE id = 1`,
dialect,
).then(result => {
const row = result.rows[0];
expect(row.score).to.equal(22);
});
});
it("should allow public sharing of actions and execution of public actions", () => {
const IMPLICIT_ACTION_NAME = "Update";
cy.get("@writableModelId").then(modelId => {
createAction({
...SAMPLE_WRITABLE_QUERY_ACTION,
model_id: modelId,
});
createAction({
type: "implicit",
kind: "row/update",
name: IMPLICIT_ACTION_NAME,
model_id: modelId,
});
cy.visit(`/model/${modelId}/detail/actions`);
cy.wait("@getModel");
});
enableSharingFor(SAMPLE_WRITABLE_QUERY_ACTION.name, {
publicUrlAlias: "queryActionPublicUrl",
});
enableSharingFor(IMPLICIT_ACTION_NAME, {
publicUrlAlias: "implicitActionPublicUrl",
});
cy.signOut();
cy.get("@queryActionPublicUrl").then(url => {
cy.visit(url);
cy.findByLabelText(TEST_PARAMETER.name).type("1");
cy.button(SAMPLE_QUERY_ACTION.name).click();
cy.findByText(`${SAMPLE_WRITABLE_QUERY_ACTION.name} ran successfully`).should(
"be.visible",
);
cy.findByRole("form").should("not.exist");
cy.button(SAMPLE_QUERY_ACTION.name).should("not.exist");
queryWritableDB(
`SELECT * FROM ${WRITABLE_TEST_TABLE} WHERE id = 1`,
dialect,
).then(result => {
const row = result.rows[0];
expect(row.score).to.equal(22);
});
});
cy.get("@implicitActionPublicUrl").then(url => {
cy.visit(url);
// team 2 has 10 points, let's give them more
cy.findByLabelText("Id").type("2");
cy.findByLabelText(/score/i).type("16");
cy.findByLabelText(/team name/i).type("Bouncy Bears");
cy.button(IMPLICIT_ACTION_NAME).click();
cy.findByText(`${IMPLICIT_ACTION_NAME} ran successfully`).should(
"be.visible",
);
cy.findByRole("form").should("not.exist");
cy.button(IMPLICIT_ACTION_NAME).should("not.exist");
queryWritableDB(
`SELECT * FROM ${WRITABLE_TEST_TABLE} WHERE id = 2`,
dialect,
).then(result => {
const row = result.rows[0];
expect(row.score).to.equal(16);
expect(row.team_name).to.equal("Bouncy Bears");
// should not mutate form fields that we don't touch
expect(row.status).to.not.be.a('null');
});
});
cy.signInAsAdmin();
cy.get("@writableModelId").then(modelId => {
cy.visit(`/model/${modelId}/detail/actions`);
cy.wait("@getModel");
});
disableSharingFor(SAMPLE_QUERY_ACTION.name);
disableSharingFor(IMPLICIT_ACTION_NAME);
cy.signOut();
cy.get("@queryActionPublicUrl").then(url => {
cy.visit(url);
cy.findByRole("form").should("not.exist");
cy.button(SAMPLE_QUERY_ACTION.name).should("not.exist");
cy.findByText("Not found").should("be.visible");
});
cy.get("@implicitActionPublicUrl").then(url => {
cy.visit(url);
cy.findByRole("form").should("not.exist");
cy.button(SAMPLE_QUERY_ACTION.name).should("not.exist");
cy.findByText("Not found").should("be.visible");
});
});
});
});
function runActionFor(actionName) {
cy.findByRole("listitem", { name: actionName }).within(() => {
cy.icon("play").click();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment