Consolidate sharing e2e tests (#83657)

* Remove duplicate public sharing e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Merge sharing reproductions e2e tests that share setup

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Merge downloads e2e tests that share setup

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Merge public sharing e2e tests that share setup

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Repair downloads e2e tests that cannot fail

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Repair sharing reproductions e2e tests that cannot fail

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Drop unused setup in sharing reproductions e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Remove duplicate subscriptions e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Repair public sharing e2e tests that cannot fail

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Drop unused setup in downloads e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Drop unused setup in public sharing e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Remove duplicate alert e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Merge alert e2e tests that share setup

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Repair alert e2e tests that cannot fail

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Drop unused setup in alert e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Remove duplicate public sharing e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Merge subscriptions e2e tests that share setup

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Repair subscriptions e2e tests that cannot fail

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Drop unused setup in subscriptions e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Fold public dashboard link creation e2e test into 20393 repro

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fold 30314 subscription cancel e2e test into 14117 test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Fold 16108 tooltip e2e test into download format test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Merge public sharing e2e tests that share setup

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Place merged 18352 card below the Orders card

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Type a name before Escape in the 24629 subscriptions e2e test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Repair public sharing e2e tests that cannot fail

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Tag sharing e2e tests that need maildev as external

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Drop unused setup in public sharing e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Fix popover check after Escape in dashboard downloads e2e test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Keep the webhook channel in the 12349 alert e2e test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Tighten subscriptions e2e checks found in parity review

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Tighten sharing reproductions e2e checks found in parity review

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Restore admin and UI-created link coverage in public sharing e2e tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Jc2P2sKm39MghfKPxCSiv

* Fix 7 sharing e2e failures from CI

* Restore 48402 coverage and merge more sharing e2e tests

- Restore the metabase#48402 delete-alert step in the 12349 test and check the delete modal
- Make 18669 check the email filter value and 21559 check the card image
- Merge public dashboard download flag tests with the real downloads
- Merge static-viz, branding, and default-filter subscription tests
- Merge dashboard PDF and PNG download tests and check download parameters
- Merge the two background=false public dashboard tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Address review on sharing e2e consolidation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fix sharing e2e CI failures

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chris Vandoodewaard
2026-10-06 17:39:59 -04:00
committed by GitHub
parent cc2db95954
commit 519834f9ee
15 changed files with 1163 additions and 1657 deletions

View File

@@ -119,10 +119,13 @@ export function downloadAndAssert({
expect(res.statusCode).to.eq(200); expect(res.statusCode).to.eq(200);
expect(res.headers["content-type"]).to.include(expectedContentType); expect(res.headers["content-type"]).to.include(expectedContentType);
if (assertParameters && req.body?.parameters) { if (assertParameters) {
expect(req.body.parameters).to.have.lengthOf(assertParameters.length); const parameters = JSON.parse(
new URLSearchParams(req.body).get("parameters") ?? "null",
);
expect(parameters).to.have.lengthOf(assertParameters.length);
assertParameters.forEach((expectedParam, index) => { assertParameters.forEach((expectedParam, index) => {
expect(req.body.parameters[index]).to.deep.include(expectedParam); expect(parameters[index]).to.deep.include(expectedParam);
}); });
} }

View File

@@ -42,30 +42,6 @@ describe("scenarios > alert > alert permissions", { tags: "@external" }, () => {
expect(questionAlerts).to.have.length(3); expect(questionAlerts).to.have.length(3);
}); });
}); });
it("should let you edit an alert", () => {
cy.intercept("PUT", "/api/notification/*").as("updatedAlert");
// Change alert
H.visitQuestion(ORDERS_QUESTION_ID);
cy.findByLabelText("Move, trash, and more…").click();
H.popover().findByText("Edit alerts").click();
H.modal()
.findByText(/Created by you/)
.click();
H.modal().findByTestId("select-frequency").click();
H.popover().findByText("weekly").click();
H.selectScheduleTime();
H.modal().button("Save changes").click();
// Check that changes stuck
cy.wait("@updatedAlert").then(({ response: { body } }) => {
expect(body.subscriptions[0].cron_schedule).to.equal("0 0 8 ? * 2 *");
});
});
}); });
describe("as a non-admin / normal user", () => { describe("as a non-admin / normal user", () => {
@@ -75,40 +51,22 @@ describe("scenarios > alert > alert permissions", { tags: "@external" }, () => {
H.visitQuestion(ORDERS_QUESTION_ID); H.visitQuestion(ORDERS_QUESTION_ID);
cy.findByLabelText("Move, trash, and more…").click(); cy.findByLabelText("Move, trash, and more…").click();
H.popover().findByText("Edit alerts").should("not.exist");
H.popover().findByText("Create an alert").should("be.visible"); H.popover().findByText("Create an alert").should("be.visible");
H.popover().findByText("Edit alerts").should("not.exist");
}); });
it("should let you see other alerts where you are a recipient", () => { it("should let you see and unsubscribe from others' alerts where you are a recipient", () => {
H.visitQuestion(ORDERS_COUNT_QUESTION_ID); H.visitQuestion(ORDERS_COUNT_QUESTION_ID);
cy.findByLabelText("Move, trash, and more…").click(); cy.findByLabelText("Move, trash, and more…").click();
H.popover().findByText("Edit alerts").click(); H.popover().findByText("Edit alerts").click();
H.modal().within(() => { H.modal().within(() => {
cy.findByText(`Created by ${H.getFullName(admin)}`, {
exact: false,
}).should("be.visible");
cy.button("New alert").should("be.visible"); cy.button("New alert").should("be.visible");
});
});
it("should let you see your own alerts", () => {
H.visitQuestion(ORDERS_BY_YEAR_QUESTION_ID);
cy.findByLabelText("Move, trash, and more…").click();
H.popover().findByText("Edit alerts").click();
H.modal().findByText(/Created by you/);
});
it("should let you unsubscribe from others' alerts", () => {
H.visitQuestion(ORDERS_COUNT_QUESTION_ID);
cy.findByLabelText("Move, trash, and more…").click();
H.popover().findByText("Edit alerts").click();
H.modal().within(() => {
cy.findByText(`Created by ${H.getFullName(admin)}`, { cy.findByText(`Created by ${H.getFullName(admin)}`, {
exact: false, exact: false,
}).realHover(); })
.should("be.visible")
.realHover();
cy.icon("unsubscribe").click(); cy.icon("unsubscribe").click();
}); });

View File

@@ -1,22 +1,18 @@
const { H } = cy; const { H } = cy;
import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database"; import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
import { import { ORDERS_BY_YEAR_QUESTION_ID } from "e2e/support/cypress_sample_instance_data";
ORDERS_BY_YEAR_QUESTION_ID,
ORDERS_QUESTION_ID,
} from "e2e/support/cypress_sample_instance_data";
const { PEOPLE, PEOPLE_ID } = SAMPLE_DATABASE; const { ORDERS, ORDERS_ID } = SAMPLE_DATABASE;
const multiSeriesQuestionWithGoal = { const multiSeriesQuestionWithGoal = {
name: "multi", name: "multi",
query: { query: {
"source-table": PEOPLE_ID, "source-table": ORDERS_ID,
aggregation: [["count"]], aggregation: [["count"], ["sum", ["field", ORDERS.TOTAL, null]]],
breakout: [ breakout: [
["field", PEOPLE.SOURCE, null],
[ [
"field", "field",
PEOPLE.CREATED_AT, ORDERS.CREATED_AT,
{ {
"temporal-unit": "month", "temporal-unit": "month",
}, },
@@ -24,21 +20,16 @@ const multiSeriesQuestionWithGoal = {
], ],
}, },
display: "line", display: "line",
visualization_settings: {
"graph.show_goal": true,
"graph.goal_value": 7000,
"graph.dimensions": ["CREATED_AT"],
"graph.metrics": ["count", "sum"],
},
}; };
const timeSeriesQuestionId = ORDERS_BY_YEAR_QUESTION_ID; const timeSeriesQuestionId = ORDERS_BY_YEAR_QUESTION_ID;
const rawTestCases = [
{
questionType: "raw data question",
questionId: ORDERS_QUESTION_ID,
},
{
questionType: "timeseries question without a goal",
questionId: timeSeriesQuestionId,
},
];
describe("scenarios > alert > types", { tags: "@external" }, () => { describe("scenarios > alert > types", { tags: "@external" }, () => {
beforeEach(() => { beforeEach(() => {
cy.intercept("POST", "/api/notification").as("updateAlert"); cy.intercept("POST", "/api/notification").as("updateAlert");
@@ -51,28 +42,26 @@ describe("scenarios > alert > types", { tags: "@external" }, () => {
}); });
describe("rows based alerts", () => { describe("rows based alerts", () => {
rawTestCases.forEach(({ questionType, questionId }) => { it("should be supported for timeseries question without a goal", () => {
it(`should be supported for ${questionType}`, () => { H.visitQuestion(timeSeriesQuestionId);
H.visitQuestion(questionId);
cy.findByLabelText("Move, trash, and more…").click(); cy.findByLabelText("Move, trash, and more…").click();
H.popover().findByText("Create an alert").click(); H.popover().findByText("Create an alert").click();
cy.wait("@channel"); cy.wait("@channel");
H.selectScheduleTime(); H.selectScheduleTime();
H.modal().within(() => { H.modal().within(() => {
cy.findByText("New alert").should("be.visible"); cy.findByText("New alert").should("be.visible");
cy.findByTestId("alert-goal-select") cy.findByTestId("alert-goal-select")
.should("not.be.enabled") .should("not.be.enabled")
.should("have.text", "When this question has results"); .should("have.text", "When this question has results");
cy.findByText("Done").click(); cy.findByText("Done").click();
}); });
cy.wait("@updateAlert").then(({ response: { body } }) => { cy.wait("@updateAlert").then(({ response: { body } }) => {
expect(body.payload?.send_condition).to.equal("has_result"); expect(body.payload?.send_condition).to.equal("has_result");
});
}); });
}); });
}); });
@@ -120,6 +109,7 @@ describe("scenarios > alert > types", { tags: "@external" }, () => {
it("should not be possible to create goal based alert for a multi-series question", () => { it("should not be possible to create goal based alert for a multi-series question", () => {
H.createQuestion(multiSeriesQuestionWithGoal, { visitQuestion: true }); H.createQuestion(multiSeriesQuestionWithGoal, { visitQuestion: true });
cy.findByTestId("chart-container").should("contain", "Goal");
cy.findByLabelText("Move, trash, and more…").click(); cy.findByLabelText("Move, trash, and more…").click();
H.popover().findByText("Create an alert").click(); H.popover().findByText("Create an alert").click();

View File

@@ -13,7 +13,7 @@ describe("scenarios > alert", () => {
}); });
describe("with nothing set", () => { describe("with nothing set", () => {
it("should prompt you to add email/slack credentials", () => { it("should prompt admins to add email/slack credentials and tell non-admins to ask an admin", () => {
H.visitQuestion(ORDERS_QUESTION_ID); H.visitQuestion(ORDERS_QUESTION_ID);
cy.findByLabelText("Move, trash, and more…").click(); cy.findByLabelText("Move, trash, and more…").click();
@@ -37,9 +37,7 @@ describe("scenarios > alert", () => {
.closest("a") .closest("a")
.should("have.attr", "href", "/admin/settings/webhooks"); .should("have.attr", "href", "/admin/settings/webhooks");
}); });
});
it("should say to non-admins that admin must add email credentials", () => {
cy.signInAsNormalUser(); cy.signInAsNormalUser();
H.visitQuestion(ORDERS_QUESTION_ID); H.visitQuestion(ORDERS_QUESTION_ID);
@@ -50,7 +48,7 @@ describe("scenarios > alert", () => {
H.modal().within(() => { H.modal().within(() => {
cy.findByText( cy.findByText(
"To get notified when something happens, or to send this chart on a schedule, ask your admin to set up email, Slack, or a webhook.", "To get notified when something happens, or to send this chart on a schedule, ask your admin to set up email, Slack, or a webhook.",
); ).should("be.visible");
cy.findByText("Set up email").should("not.exist"); cy.findByText("Set up email").should("not.exist");
cy.findByText("Set up Slack").should("not.exist"); cy.findByText("Set up Slack").should("not.exist");
@@ -69,7 +67,6 @@ describe("scenarios > alert", () => {
name: "Bar Hook", name: "Bar Hook",
description: "This is another hook", description: "This is another hook",
}); });
cy.setCookie("metabase.SEEN_ALERT_SPLASH", "true");
}); });
it("should be able to create and delete alerts with webhooks enabled", () => { it("should be able to create and delete alerts with webhooks enabled", () => {
@@ -118,6 +115,13 @@ describe("scenarios > alert", () => {
}); });
H.sharingMenuButton().should("not.exist"); H.sharingMenuButton().should("not.exist");
cy.findByLabelText("Move, trash, and more…").click();
H.popover().within(() => {
cy.findByText("Turn back to saved question").should("be.visible");
cy.findByText("Create an alert").should("not.exist");
cy.findByText("Edit alerts").should("not.exist");
});
}); });
it("can set up an alert for a question saved in a dashboard", () => { it("can set up an alert for a question saved in a dashboard", () => {
@@ -150,101 +154,93 @@ describe("scenarios > alert", () => {
}); });
}); });
describe( describe("approved domains (EE)", { tags: "@external" }, () => {
"scenarios > sharing > approved domains (EE)", const allowedDomain = "metabase.test";
{ tags: "@external" }, const deniedDomain = "metabase.example";
() => { const deniedEmail = `mailer@${deniedDomain}`;
const allowedDomain = "metabase.test"; // We're not exposing allowed domains to normal users.
const deniedDomain = "metabase.example"; const normalUserAlertError = `Failed save alert. The following email addresses are not allowed: ${deniedEmail}`;
const deniedEmail = `mailer@${deniedDomain}`; const normalUserSubscriptionError = `Cannot create subscription. The following email addresses are not allowed: ${deniedEmail} Please contact your administrator.`;
// We're not exposing allowed domains to normal users. const adminAlertError = `You're only allowed to email alerts to addresses ending in ${allowedDomain}`;
const normalUserAlertError = `Failed save alert. The following email addresses are not allowed: ${deniedEmail}`; const adminSubscriptionError = `You're only allowed to email subscriptions to addresses ending in ${allowedDomain}`;
const normalUserSubscriptionError = `Cannot create subscription. The following email addresses are not allowed: ${deniedEmail} Please contact your administrator.`;
const adminAlertError = `You're only allowed to email alerts to addresses ending in ${allowedDomain}`;
const adminSubscriptionError = `You're only allowed to email subscriptions to addresses ending in ${allowedDomain}`;
function addEmailRecipient(email) { function addEmailRecipient(email) {
cy.findByTestId("token-field") cy.findByTestId("token-field")
.findByRole("combobox") .findByRole("combobox")
.click() .click()
.type(`${email}`) .type(`${email}`)
.blur(); .blur();
} }
function setAllowedDomains() { function setAllowedDomains() {
H.updateSetting("subscription-allowed-domains", allowedDomain); H.updateSetting("subscription-allowed-domains", allowedDomain);
} }
beforeEach(() => { beforeEach(() => {
H.restore(); H.activateToken("pro-self-hosted");
cy.signInAsAdmin(); H.setupSMTP();
H.activateToken("pro-self-hosted"); setAllowedDomains();
H.setupSMTP(); });
setAllowedDomains();
it("should validate approved email domains for a question alert and a dashboard subscription (metabase#17977)", () => {
H.visitQuestion(ORDERS_QUESTION_ID);
cy.findByLabelText("Move, trash, and more…").click();
H.popover().findByText("Create an alert").click();
H.selectScheduleTime();
H.modal().within(() => {
cy.findByText("New alert").should("be.visible");
addEmailRecipient(deniedEmail);
cy.findByText(adminAlertError);
cy.button("Done").should("be.disabled");
}); });
it("should validate approved email domains for a question alert", () => { H.visitDashboard(ORDERS_DASHBOARD_ID);
H.visitQuestion(ORDERS_QUESTION_ID); H.toggleDashboardSubscriptionsSidebar();
cy.findByLabelText("Move, trash, and more…").click(); H.sidebar().within(() => {
H.popover().findByText("Create an alert").click(); cy.findByText("Email it").click();
addEmailRecipient(deniedEmail);
H.selectScheduleTime(); // Reproduces metabase#17977
H.modal().within(() => { cy.button("Send email now").should("be.disabled");
cy.findByText("New alert").should("be.visible"); cy.button("Done").should("be.disabled");
cy.findByText(adminSubscriptionError);
});
});
addEmailRecipient(deniedEmail); it("should not display the list of approved domains for non-admins (metabase#57138)", () => {
cy.signInAsNormalUser();
H.visitQuestion(ORDERS_QUESTION_ID);
cy.findByText(adminAlertError); cy.findByLabelText("Move, trash, and more…").click();
cy.button("Done").should("be.disabled"); H.popover().findByText("Create an alert").click();
}); H.selectScheduleTime();
H.modal().within(() => {
cy.findByText("New alert").should("be.visible");
addEmailRecipient(deniedEmail);
cy.button("Done").click();
});
cy.findByTestId("toast-undo").within(() => {
cy.root().should("have.text", normalUserAlertError);
}); });
it("should validate approved email domains for a dashboard subscription (metabase#17977)", () => { H.visitDashboard(ORDERS_DASHBOARD_ID);
H.visitDashboard(ORDERS_DASHBOARD_ID); H.toggleDashboardSubscriptionsSidebar();
H.toggleDashboardSubscriptionsSidebar();
H.sidebar().within(() => { H.sidebar().within(() => {
cy.findByText("Email it").click(); addEmailRecipient(deniedEmail);
addEmailRecipient(deniedEmail);
// Reproduces metabase#17977 cy.button("Done").click();
cy.button("Send email now").should("be.disabled");
cy.button("Done").should("be.disabled");
cy.findByText(adminSubscriptionError);
});
}); });
cy.findByTestId("toast-undo").within(() => {
it("should not display the list of approved domains for non-admins (metabase#57138)", () => { cy.root().should("have.text", normalUserSubscriptionError);
cy.signInAsNormalUser();
H.visitQuestion(ORDERS_QUESTION_ID);
cy.findByLabelText("Move, trash, and more…").click();
H.popover().findByText("Create an alert").click();
H.selectScheduleTime();
H.modal().within(() => {
cy.findByText("New alert").should("be.visible");
addEmailRecipient(deniedEmail);
cy.button("Done").click();
});
cy.findByTestId("toast-undo").within(() => {
cy.root().should("have.text", normalUserAlertError);
});
H.visitDashboard(ORDERS_DASHBOARD_ID);
H.toggleDashboardSubscriptionsSidebar();
H.sidebar().within(() => {
addEmailRecipient(deniedEmail);
cy.button("Done").click();
});
cy.findByTestId("toast-undo").within(() => {
cy.root().should("have.text", normalUserSubscriptionError);
});
}); });
}, });
); });
}); });

View File

@@ -11,22 +11,10 @@ describe("scenarios > alert > email_alert", { tags: "@external" }, () => {
H.restore(); H.restore();
cy.signInAsAdmin(); cy.signInAsAdmin();
cy.setCookie("metabase.SEEN_ALERT_SPLASH", "true");
H.setupSMTP(); H.setupSMTP();
}); });
it("should have no alerts set up initially", () => {
cy.visit("/");
cy.request("/api/notification").then(({ body }) => {
const questionAlerts = body.filter(
(notification) => notification.payload_type === "notification/card",
);
expect(questionAlerts).to.have.length(0);
});
});
it("should set up an email alert", () => { it("should set up an email alert", () => {
openAlertForQuestion(ORDERS_QUESTION_ID); openAlertForQuestion(ORDERS_QUESTION_ID);
@@ -35,34 +23,31 @@ describe("scenarios > alert > email_alert", { tags: "@external" }, () => {
); );
cy.findByTestId("alert-configured-channel").within(() => { cy.findByTestId("alert-configured-channel").within(() => {
cy.findByTestId("loading-indicator").should("not.exist"); cy.findByTestId("loading-indicator").should("not.exist");
cy.findByText("Email").should("be.visible");
cy.findByText("Slack").should("not.exist"); cy.findByText("Slack").should("not.exist");
}); });
H.modal()
.findByRole("link", { name: "Slack" })
.should("have.attr", "href", "/admin/settings/slack");
H.selectScheduleTime(); H.selectScheduleTime();
H.modal().within(() => {
cy.findByText("New alert").should("be.visible");
cy.findByTestId("alert-goal-select")
.should("not.be.enabled")
.should("have.text", "When this question has results");
});
cy.button("Done").click(); cy.button("Done").click();
cy.wait("@saveAlert").then(({ response: { body } }) => { cy.wait("@saveAlert").then(({ response: { body } }) => {
expect(body.handlers).to.have.length(1); expect(body.handlers).to.have.length(1);
expect(body.handlers[0].channel_type).to.eq("channel/email"); expect(body.handlers[0].channel_type).to.eq("channel/email");
expect(body.payload?.send_condition).to.equal("has_result");
}); });
}); });
it("should not claim a schedule when sending a one-off alert", () => { it("should respect email alerts toggled off and save the Slack channel_id (metabase#12349)", () => {
createQuestionAndOpenAlert("One-off alert");
H.sendAlertAndVisitIt();
cy.get("table.header")
.first()
.within(() => {
cy.findByText("One-off alert").should("be.visible");
cy.findByText(/Run daily at/).should("not.exist");
});
});
it("should respect email alerts toggled off (metabase#12349)", () => {
H.updateSetting("report-timezone", "America/New_York");
//For this test, we need to pretend that slack is set up //For this test, we need to pretend that slack is set up
H.mockSlackConfigured(); H.mockSlackConfigured();
H.setupNotificationChannel({ name: "Webhook" }); H.setupNotificationChannel({ name: "Webhook" });
@@ -91,6 +76,13 @@ describe("scenarios > alert > email_alert", { tags: "@external" }, () => {
cy.wait("@saveAlert").then(({ response: { body } }) => { cy.wait("@saveAlert").then(({ response: { body } }) => {
expect(body.handlers).to.have.length(1); expect(body.handlers).to.have.length(1);
expect(body.handlers[0].channel_type).to.eq("channel/slack"); expect(body.handlers[0].channel_type).to.eq("channel/slack");
// The mocked channel `#work` has id `C001` in e2e-slack-helpers.js.
// Storing the immutable channel_id at save time is what makes the
// subscription survive future channel renames in Slack.
const slackDetails = body.handlers[0].recipients[0].details;
expect(slackDetails.value).to.eq("#work");
expect(slackDetails.channel_id).to.eq("C001");
}); });
cy.log( cy.log(
@@ -104,53 +96,20 @@ describe("scenarios > alert > email_alert", { tags: "@external" }, () => {
}); });
cy.findByRole("button", { name: "Delete this alert" }).click(); cy.findByRole("button", { name: "Delete this alert" }).click();
});
it("should persist the immutable Slack channel_id alongside the channel name", () => { cy.findByTestId("alert-delete").within(() => {
H.mockSlackConfigured(); cy.findByText("Delete this alert?").should("be.visible");
cy.findByText("This can't be undone.").should("be.visible");
openAlertForQuestion(ORDERS_QUESTION_ID); cy.findByText(/email/i).should("not.exist");
cy.button("Cancel").should("be.visible");
H.removeNotificationHandlerChannel("Email"); cy.button("Delete it").should("be.visible");
H.addNotificationHandlerChannel("Slack", { hasNoChannelsAdded: true });
H.modal()
.findByPlaceholderText(/Pick a user or channel/)
.click();
H.popover().findByText("#work").click();
H.selectScheduleTime();
H.modal().within(() => {
cy.button("Done").click();
});
cy.wait("@saveAlert").then(({ response: { body } }) => {
// The mocked channel `#work` has id `C001` in e2e-slack-helpers.js.
// Storing the immutable channel_id at save time is what makes the
// subscription survive future channel renames in Slack.
const slackDetails = body.handlers[0].recipients[0].details;
expect(slackDetails.value).to.eq("#work");
expect(slackDetails.channel_id).to.eq("C001");
}); });
}); });
it("should set up an email alert for newly created question", () => { it("should set up an email alert for a new question and update it without updating the question (metabase#36866)", () => {
H.openTable({ cy.intercept("PUT", "/api/notification/*").as("updatedAlert");
table: PEOPLE_ID, cy.intercept("PUT", "/api/card/*").as("updateCard");
});
saveAlert();
cy.findByTestId("toast-undo")
.findByText("Your alert is all set up.")
.should("be.visible");
cy.wait("@saveAlert").then(({ response: { body } }) => {
expect(body.handlers[0].channel_type).to.eq("channel/email");
});
});
it("should enable alert to be updated (without updating question) (metabase#36866)", () => {
H.openTable({ H.openTable({
table: PEOPLE_ID, table: PEOPLE_ID,
}); });
@@ -164,6 +123,10 @@ describe("scenarios > alert > email_alert", { tags: "@external" }, () => {
.findByText("Your alert is all set up.") .findByText("Your alert is all set up.")
.should("be.visible"); .should("be.visible");
cy.wait("@saveAlert").then(({ response: { body } }) => {
expect(body.handlers[0].channel_type).to.eq("channel/email");
});
cy.findByLabelText("Move, duplicate, and more…").click(); cy.findByLabelText("Move, duplicate, and more…").click();
H.popover().findByText("Edit alerts").click(); H.popover().findByText("Edit alerts").click();
@@ -183,8 +146,13 @@ describe("scenarios > alert > email_alert", { tags: "@external" }, () => {
H.selectScheduleTime(); H.selectScheduleTime();
cy.button("Save changes").click(); cy.button("Save changes").click();
cy.wait("@updatedAlert").then(({ response: { body } }) => {
expect(body.subscriptions[0].cron_schedule).to.equal("0 0 8 ? * 2 *");
});
cy.log("Check that /api/card has still only been called once"); cy.log("Check that /api/card has still only been called once");
cy.get("@saveCard.all").should("have.length", 1); cy.get("@saveCard.all").should("have.length", 1);
cy.get("@updateCard.all").should("have.length", 0);
}); });
describe("branding", () => { describe("branding", () => {
@@ -202,9 +170,21 @@ describe("scenarios > alert > email_alert", { tags: "@external" }, () => {
); );
}); });
it("should include branding for Starter instances", () => { it("should include branding for Starter instances and not claim a schedule for a one-off alert", () => {
H.activateToken("starter"); H.activateToken("starter");
sendTestAlertForQuestion(questionName); createQuestionAndOpenAlert(questionName);
H.sendAlertAndVisitIt();
cy.findAllByRole("link")
.filter(`:contains(${questionName})`)
.should("be.visible");
cy.get("table.header")
.first()
.within(() => {
cy.findByText(questionName).should("be.visible");
cy.findByText(/Run daily at/).should("not.exist");
});
cy.findAllByRole("link") cy.findAllByRole("link")
.filter(":contains(Made with)") .filter(":contains(Made with)")
.should("contain", "Metabase") .should("contain", "Metabase")

View File

@@ -10,6 +10,9 @@ const { ORDERS, ORDERS_ID, PRODUCTS, PRODUCTS_ID } = SAMPLE_DATABASE;
const testCases = ["csv", "xlsx"]; const testCases = ["csv", "xlsx"];
const formatUrl =
"/api/user-key-value/namespace/last_download_format/key/download_format_preference";
const canSavePngQuestion = { const canSavePngQuestion = {
name: "Q1", name: "Q1",
display: "line", display: "line",
@@ -49,18 +52,18 @@ describe("scenarios > question > download", () => {
H.expectNoBadSnowplowEvents(); H.expectNoBadSnowplowEvents();
}); });
testCases.forEach((fileType) => { it("downloads csv and xlsx files", () => {
it(`downloads ${fileType} file`, () => { H.startNewQuestion();
H.startNewQuestion(); H.miniPicker().within(() => {
H.miniPicker().within(() => { cy.findByText("Our analytics").click();
cy.findByText("Our analytics").click(); cy.findByText("Orders, Count").click();
cy.findByText("Orders, Count").click(); });
});
H.visualize(); H.visualize();
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage // eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.contains("18,760"); cy.contains("18,760");
testCases.forEach((fileType) => {
H.downloadAndAssert({ fileType }); H.downloadAndAssert({ fileType });
H.expectUnstructuredSnowplowEvent({ H.expectUnstructuredSnowplowEvent({
@@ -73,34 +76,34 @@ describe("scenarios > question > download", () => {
}); });
}); });
testCases.forEach((fileType) => { it("should allow downloading unformatted csv and xlsx data", () => {
it(`should allow downloading unformatted ${fileType} data`, () => { const fieldRef = ["field", ORDERS.TOTAL, null];
const fieldRef = ["field", ORDERS.TOTAL, null]; const columnKey = `["ref",${JSON.stringify(fieldRef)}]`;
const columnKey = `["ref",${JSON.stringify(fieldRef)}]`;
H.createQuestion( H.createQuestion(
{ {
query: { query: {
"source-table": ORDERS_ID, "source-table": ORDERS_ID,
fields: [fieldRef], fields: [fieldRef],
}, },
visualization_settings: { visualization_settings: {
column_settings: { column_settings: {
[columnKey]: { [columnKey]: {
currency: "USD", currency: "USD",
currency_in_header: false, currency_in_header: false,
currency_style: "code", currency_style: "code",
number_style: "currency", number_style: "currency",
},
}, },
}, },
}, },
{ visitQuestion: true, wrapId: true }, },
); { visitQuestion: true, wrapId: true },
);
H.queryBuilderMain().findByText("USD 39.72").should("exist"); H.queryBuilderMain().findByText("USD 39.72").should("exist");
cy.get("@questionId").then((questionId) => { cy.get("@questionId").then((questionId) => {
testCases.forEach((fileType) => {
const opts = { questionId, fileType }; const opts = { questionId, fileType };
H.downloadAndAssert({ H.downloadAndAssert({
@@ -147,13 +150,11 @@ describe("scenarios > question > download", () => {
describe("download format preference", () => { describe("download format preference", () => {
beforeEach(() => { beforeEach(() => {
const formatUrl =
"/api/user-key-value/namespace/last_download_format/key/download_format_preference";
cy.intercept("PUT", formatUrl).as("saveFormat"); cy.intercept("PUT", formatUrl).as("saveFormat");
cy.intercept("GET", formatUrl).as("fetchFormat"); cy.intercept("GET", formatUrl).as("fetchFormat");
}); });
it("should remember the downloaded format across page reloads", () => { it("should show tooltips for the download and share buttons and remember the downloaded format across page reloads (metabase#16108)", () => {
H.createQuestion( H.createQuestion(
{ {
name: "Format Preference Test", name: "Format Preference Test",
@@ -169,6 +170,12 @@ describe("scenarios > question > download", () => {
cy.findByTestId("view-footer") cy.findByTestId("view-footer")
.findByText("Showing 5 rows") .findByText("Showing 5 rows")
.should("be.visible"); .should("be.visible");
cy.icon("download").realHover();
H.tooltip().findByText("Download results");
H.sharingMenuButton().realHover();
H.tooltip().findByText("Share");
cy.findByTestId("view-footer").button("Download results").click(); cy.findByTestId("view-footer").button("Download results").click();
H.popover().findByText(".xlsx").click(); H.popover().findByText(".xlsx").click();
@@ -188,46 +195,7 @@ describe("scenarios > question > download", () => {
cy.findByText(".xlsx") cy.findByText(".xlsx")
.parent() .parent()
.should("have.attr", "data-active", "true"); .should("have.attr", "data-active", "true");
}); cy.findByText(".png").should("not.exist");
});
it("should remember the downloaded format on dashboards", () => {
H.createQuestion({
name: "Dashboard Format Test",
query: {
"source-table": ORDERS_ID,
limit: 5,
},
display: "table",
}).then(({ body: { id: questionId } }) => {
H.createDashboard().then(({ body: { id: dashboardId } }) => {
H.addOrUpdateDashboardCard({
card_id: questionId,
dashboard_id: dashboardId,
});
H.visitDashboard(dashboardId);
H.getDashboardCard(0).realHover();
H.getDashboardCardMenu(0).click();
H.popover().findByText("Download results").click();
H.popover().findByText(".xlsx").click();
cy.findByTestId("download-results-button").click();
cy.wait("@saveFormat");
cy.reload();
cy.wait("@fetchFormat");
H.getDashboardCard(0).realHover();
H.getDashboardCardMenu(0).click();
H.popover().findByText("Download results").click();
H.popover().within(() => {
cy.findByText(".xlsx")
.parent()
.should("have.attr", "data-active", "true");
});
});
}); });
}); });
}); });
@@ -314,15 +282,31 @@ describe("scenarios > question > download", () => {
}); });
describe("from dashboards", () => { describe("from dashboards", () => {
it("should allow downloading card data", () => { it("should allow downloading card data and remember the downloaded format", () => {
cy.intercept("GET", "/api/dashboard/**").as("dashboard"); cy.intercept("PUT", formatUrl).as("saveFormat");
cy.intercept("GET", formatUrl).as("fetchFormat");
H.visitDashboard(ORDERS_DASHBOARD_ID); H.visitDashboard(ORDERS_DASHBOARD_ID);
cy.findByTestId("dashcard").within(() => { cy.findByTestId("dashcard").within(() => {
cy.findByTestId("legend-caption").realHover(); cy.findByTestId("legend-caption").realHover();
}); });
// In CI agents after downloads Cypress gets stuck for a while so the downloads status gets closed by timeout // In CI agents after downloads Cypress gets stuck for a while so the downloads status gets closed by timeout
assertOrdersExport(18760); assertOrdersExport();
cy.wait("@saveFormat");
cy.reload();
cy.wait("@fetchFormat");
H.getDashboardCard(0).realHover();
H.getDashboardCardMenu(0).click();
H.popover().findByText("Download results").click();
H.popover().within(() => {
cy.findByText(".xlsx")
.parent()
.should("have.attr", "data-active", "true");
});
cy.realPress("Escape");
H.popover({ skipVisibilityCheck: true }).should("not.exist");
H.editDashboard(); H.editDashboard();
@@ -337,17 +321,20 @@ describe("scenarios > question > download", () => {
H.popover().within(() => H.fieldValuesCombobox().type("1")); H.popover().within(() => H.fieldValuesCombobox().type("1"));
cy.intercept("POST", "/api/dashboard/*/dashcard/*/card/*/query").as(
"dashcardQuery",
);
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage // eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("Add filter").click(); cy.findByText("Add filter").click();
cy.wait("@dashboard"); cy.wait("@dashcardQuery");
cy.findByTestId("dashcard").within(() => { cy.findByTestId("dashcard").within(() => {
cy.findByTestId("legend-caption").realHover(); cy.findByTestId("legend-caption").realHover();
}); });
// In CI agents after downloads Cypress gets stuck for a while so the downloads status gets closed by timeout // In CI agents after downloads Cypress gets stuck for a while so the downloads status gets closed by timeout
assertOrdersExport(1); assertOrdersExport({ assertParameters: [{ type: "number/=" }] });
}); });
it("should allow downloading parameterized cards opened from dashboards as a user with no self-service permission (metabase#20868)", () => { it("should allow downloading parameterized cards opened from dashboards as a user with no self-service permission (metabase#20868)", () => {
@@ -420,32 +407,8 @@ describe("scenarios > question > download", () => {
}); });
describe("png images", () => { describe("png images", () => {
it("from dashboards", () => { beforeEach(() => {
H.createDashboardWithQuestions({ cy.deleteDownloadsFolder();
dashboardName: "saving pngs dashboard",
questions: [canSavePngQuestion, cannotSavePngQuestion],
}).then(({ dashboard }) => {
H.visitDashboard(dashboard.id);
});
H.showDashboardCardActions(0);
H.getDashboardCard(0)
.findByText("Created At: Month")
.should("be.visible");
H.getDashboardCardMenu(0).click();
H.exportFromDashcard(".png");
H.showDashboardCardActions(1);
H.getDashboardCard(1).findByText("User ID").should("be.visible");
H.getDashboardCardMenu(1).click();
H.popover().within(() => {
cy.findByText("Download results").click();
cy.findByText(".png").should("not.exist");
});
cy.verifyDownload(".png", { contains: true });
}); });
it("from query builder", () => { it("from query builder", () => {
@@ -459,59 +422,27 @@ describe("scenarios > question > download", () => {
}); });
cy.verifyDownload(".png", { contains: true }); cy.verifyDownload(".png", { contains: true });
H.createQuestion(cannotSavePngQuestion, { visitQuestion: true });
cy.findByRole("button", { name: "Download results" }).click();
H.popover().within(() => {
cy.findByText(".png").should("not.exist");
});
}); });
}); });
}); });
describe("scenarios > dashboard > download pdf", () => {
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
cy.deleteDownloadsFolder();
});
it("should allow you to download a PDF of a dashboard", () => {
const date = Date.now();
H.createDashboardWithQuestions({
dashboardName: `saving pdf dashboard - ${date}`,
questions: [canSavePngQuestion, cannotSavePngQuestion],
}).then(({ dashboard }) => {
H.visitDashboard(dashboard.id);
});
H.openSharingMenu("Export as PDF");
cy.findByTestId("status-root-container")
.should("contain", "Downloading")
.and("contain", `Dashboard for saving pdf dashboard - ${date}`);
cy.log("We're adding a 'Metabase-' prefix for non-whitelabelled instances");
cy.verifyDownload(`Metabase - saving pdf dashboard - ${date}.pdf`);
});
});
describe("[snowplow] scenarios > dashboard", () => { describe("[snowplow] scenarios > dashboard", () => {
beforeEach(() => { beforeEach(() => {
H.restore(); H.restore();
H.resetSnowplow(); H.resetSnowplow();
cy.signInAsAdmin(); cy.signInAsAdmin();
H.enableTracking(); H.enableTracking();
cy.deleteDownloadsFolder();
}); });
afterEach(() => { afterEach(() => {
H.expectNoBadSnowplowEvents(); H.expectNoBadSnowplowEvents();
}); });
it("should allow you to download a PDF of a dashboard", () => { it("should download a PDF of a dashboard and png images from dashcards", () => {
const date = Date.now();
H.createDashboardWithQuestions({ H.createDashboardWithQuestions({
dashboardName: "test dashboard", dashboardName: `saving pdf dashboard - ${date}`,
questions: [canSavePngQuestion, cannotSavePngQuestion], questions: [canSavePngQuestion, cannotSavePngQuestion],
}).then(({ dashboard }) => { }).then(({ dashboard }) => {
H.visitDashboard(dashboard.id); H.visitDashboard(dashboard.id);
@@ -519,7 +450,7 @@ describe("[snowplow] scenarios > dashboard", () => {
cy.findByTestId("status-root-container") cy.findByTestId("status-root-container")
.should("contain", "Downloading") .should("contain", "Downloading")
.and("contain", "Dashboard for test dashboard"); .and("contain", `Dashboard for saving pdf dashboard - ${date}`);
H.expectUnstructuredSnowplowEvent({ H.expectUnstructuredSnowplowEvent({
event: "dashboard_pdf_exported", event: "dashboard_pdf_exported",
@@ -527,15 +458,10 @@ describe("[snowplow] scenarios > dashboard", () => {
dashboard_accessed_via: "internal", dashboard_accessed_via: "internal",
}); });
}); });
});
it("should send the `download_results_clicked` event when downloading dashcards results", () => { cy.log("We're adding a 'Metabase-' prefix for non-whitelabelled instances");
H.createDashboardWithQuestions({ cy.verifyDownload(`Metabase - saving pdf dashboard - ${date}.pdf`);
dashboardName: "saving pngs dashboard", H.ensureDownloadStatusDismissed();
questions: [canSavePngQuestion, cannotSavePngQuestion],
}).then(({ dashboard }) => {
H.visitDashboard(dashboard.id);
});
H.showDashboardCardActions(0); H.showDashboardCardActions(0);
H.getDashboardCard(0).findByText("Created At: Month").should("be.visible"); H.getDashboardCard(0).findByText("Created At: Month").should("be.visible");
@@ -549,15 +475,28 @@ describe("[snowplow] scenarios > dashboard", () => {
accessed_via: "internal", accessed_via: "internal",
export_type: "png", export_type: "png",
}); });
H.showDashboardCardActions(1);
H.getDashboardCard(1).findByText("User ID").should("be.visible");
H.getDashboardCardMenu(1).click();
H.popover().within(() => {
cy.findByText("Download results").click();
cy.findByText(".csv").should("be.visible");
cy.findByText(".png").should("not.exist");
});
cy.verifyDownload(".png", { contains: true });
}); });
}); });
function assertOrdersExport(length) { function assertOrdersExport({ assertParameters } = {}) {
H.downloadAndAssert({ H.downloadAndAssert({
fileType: "xlsx", fileType: "xlsx",
questionId: ORDERS_QUESTION_ID, questionId: ORDERS_QUESTION_ID,
dashcardId: ORDERS_DASHBOARD_DASHCARD_ID, dashcardId: ORDERS_DASHBOARD_DASHCARD_ID,
dashboardId: ORDERS_DASHBOARD_ID, dashboardId: ORDERS_DASHBOARD_ID,
isDashboard: true, isDashboard: true,
assertParameters,
}); });
} }

View File

@@ -9,8 +9,6 @@ describe("issue 10803", () => {
const testCases = ["csv", "xlsx"]; const testCases = ["csv", "xlsx"];
beforeEach(() => { beforeEach(() => {
cy.intercept("POST", "/api/dataset").as("dataset");
H.restore(); H.restore();
cy.signInAsAdmin(); cy.signInAsAdmin();
@@ -26,20 +24,20 @@ describe("issue 10803", () => {
); );
}); });
testCases.forEach((fileType) => { it("should format the date properly in saved and unsaved questions (metabase#10803)", () => {
it(`should format the date properly for ${fileType} in saved questions (metabase#10803)`, () => { cy.get("@questionId").then((questionId) => {
cy.get("@questionId").then((questionId) => { testCases.forEach((fileType) => {
H.downloadAndAssert({ fileType, questionId }); H.downloadAndAssert({ fileType, questionId });
}); });
}); });
it(`should format the date properly for ${fileType} in unsaved questions`, () => { // Add a space at the end of the query to make it "dirty"
// Add a space at the end of the query to make it "dirty" // eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage cy.contains(/open editor/i).click();
cy.contains(/open editor/i).click(); H.NativeEditor.focus().type("{movetoend} ");
H.NativeEditor.focus().type("{movetoend} ");
H.runNativeQuery(); H.runNativeQuery();
testCases.forEach((fileType) => {
H.downloadAndAssert({ fileType }); H.downloadAndAssert({ fileType });
}); });
}); });
@@ -125,14 +123,14 @@ describe("issue 18382", () => {
H.visitQuestionAdhoc(questionDetails); H.visitQuestionAdhoc(questionDetails);
}); });
testCases.forEach((fileType) => { it("should handle the old syntax in downloads (metabase#18382)", () => {
it(`should handle the old syntax in downloads for ${fileType} (metabase#18382)`, () => { testCases.forEach((fileType) => {
H.downloadAndAssert({ fileType }); H.downloadAndAssert({ fileType });
}); });
}); });
}); });
describe("issue 18440", () => { describe("issues 18440 and 18573", () => {
const query = { "source-table": ORDERS_ID, limit: 5 }; const query = { "source-table": ORDERS_ID, limit: 5 };
const questionDetails = { const questionDetails = {
@@ -143,49 +141,7 @@ describe("issue 18440", () => {
}, },
}; };
const testCases = ["csv", "xlsx"]; const renamedColumnQuestionDetails = {
beforeEach(() => {
cy.intercept("POST", "/api/card").as("saveQuestion");
H.restore();
cy.signInAsAdmin();
// Remap Product ID -> Product Title
cy.request("POST", `/api/field/${ORDERS.PRODUCT_ID}/dimension`, {
name: "Product ID",
type: "external",
human_readable_field_id: PRODUCTS.TITLE,
});
});
testCases.forEach((fileType) => {
it(`export should include a column with remapped values for ${fileType} (metabase#18440-1)`, () => {
H.visitQuestionAdhoc(questionDetails);
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("Product ID");
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("Awesome Concrete Shoes");
H.downloadAndAssert({ fileType });
});
it(`export should include a column with remapped values for ${fileType} for a saved question (metabase#18440-2)`, () => {
H.createQuestion({ query }).then(({ body: { id } }) => {
H.visitQuestion(id);
cy.findByText("Product ID");
cy.findByText("Awesome Concrete Shoes");
H.downloadAndAssert({ fileType, questionId: id });
});
});
});
});
describe("issue 18573", () => {
const questionDetails = {
dataset_query: { dataset_query: {
type: "query", type: "query",
query: { "source-table": ORDERS_ID, limit: 2 }, query: { "source-table": ORDERS_ID, limit: 2 },
@@ -200,6 +156,8 @@ describe("issue 18573", () => {
}, },
}; };
const testCases = ["csv", "xlsx"];
beforeEach(() => { beforeEach(() => {
H.restore(); H.restore();
cy.signInAsAdmin(); cy.signInAsAdmin();
@@ -212,9 +170,34 @@ describe("issue 18573", () => {
}); });
}); });
it("for the remapped columns, it should preserve renamed column name in exports for xlsx (metabase#18573)", () => { it("export should include remapped values and preserve renamed remapped columns (metabase#18440, metabase#18573)", () => {
cy.log("ad-hoc question (metabase#18440-1)");
H.visitQuestionAdhoc(questionDetails); H.visitQuestionAdhoc(questionDetails);
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("Product ID");
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("Awesome Concrete Shoes");
testCases.forEach((fileType) => {
H.downloadAndAssert({ fileType });
});
cy.log("saved question (metabase#18440-2)");
H.createQuestion({ query }).then(({ body: { id } }) => {
H.visitQuestion(id);
cy.findByText("Product ID");
cy.findByText("Awesome Concrete Shoes");
testCases.forEach((fileType) => {
H.downloadAndAssert({ fileType, questionId: id });
});
});
cy.log("renamed remapped column (metabase#18573)");
H.visitQuestionAdhoc(renamedColumnQuestionDetails);
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage // eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("Foo"); cy.findByText("Foo");
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage // eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
@@ -247,10 +230,10 @@ describe("issue 18729", () => {
cy.signInAsAdmin(); cy.signInAsAdmin();
}); });
["csv", "xlsx"].forEach((fileType) => { it("should properly format the 'X of Y' dates in exports (metabase#18729)", () => {
it(`should properly format the 'X of Y'dates in ${fileType} exports (metabase#18729)`, () => { H.visitQuestionAdhoc(questionDetails);
H.visitQuestionAdhoc(questionDetails);
["csv", "xlsx"].forEach((fileType) => {
H.downloadAndAssert({ fileType }); H.downloadAndAssert({ fileType });
}); });
}); });
@@ -274,8 +257,6 @@ describe("issue 19889", () => {
} }
beforeEach(() => { beforeEach(() => {
cy.intercept("POST", "/api/dataset").as("dataset");
H.restore(); H.restore();
cy.signInAsAdmin(); cy.signInAsAdmin();
@@ -303,31 +284,33 @@ describe("issue 19889", () => {
cy.findByText("Started from").click(); // Give DOM some time to update cy.findByText("Started from").click(); // Give DOM some time to update
}); });
testCases.forEach((fileType) => { it("should order columns correctly in unsaved and saved native query exports", () => {
it("should order columns correctly in unsaved native query exports", () => { testCases.forEach((fileType) => {
H.downloadAndAssert({ fileType }); H.downloadAndAssert({ fileType });
}); });
it("should order columns correctly in saved native query exports", () => { saveAndOverwrite();
saveAndOverwrite();
cy.get("@questionId").then((questionId) => { cy.get("@questionId").then((questionId) => {
testCases.forEach((fileType) => {
H.downloadAndAssert({ fileType, questionId }); H.downloadAndAssert({ fileType, questionId });
}); });
}); });
});
it("should order columns correctly in saved native query exports when the query was modified but not re-run before save (#19889)", () => { it("should order columns correctly in saved native query exports when the query was modified but not re-run before save (#19889)", () => {
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage // eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.contains(/open editor/i).click(); cy.contains(/open editor/i).click();
H.NativeEditor.focus().type( H.NativeEditor.focus().type(
'{selectall}select 1 "column x", 2 "column y", 3 "column c"', '{selectall}select 1 "column x", 2 "column y", 3 "column c"',
); );
saveAndOverwrite(); saveAndOverwrite();
cy.get("@questionId").then((questionId) => { cy.get("@questionId").then((questionId) => {
H.visitQuestion(questionId); H.visitQuestion(questionId);
testCases.forEach((fileType) => {
H.downloadAndAssert({ fileType, questionId }); H.downloadAndAssert({ fileType, questionId });
}); });
}); });
@@ -347,8 +330,6 @@ describe("metabase#28834", () => {
// difficulties, I've decided to test with Cypress instead. // difficulties, I've decided to test with Cypress instead.
beforeEach(() => { beforeEach(() => {
cy.intercept("POST", "/api/dataset").as("dataset");
H.restore(); H.restore();
cy.signInAsAdmin(); cy.signInAsAdmin();
@@ -361,13 +342,9 @@ describe("metabase#28834", () => {
H.NativeEditor.focus().type(', select 2 "column b"'); H.NativeEditor.focus().type(', select 2 "column b"');
}); });
it("should be able to export unsaved native query results as CSV even after the query has changed", () => { it("should be able to export unsaved native query results as CSV and XLSX even after the query has changed", () => {
const fileType = "csv"; ["csv", "xlsx"].forEach((fileType) => {
H.downloadAndAssert({ fileType }); H.downloadAndAssert({ fileType });
}); });
it("should be able to export unsaved native query results as XLSX even after the query has changed", () => {
const fileType = "xlsx";
H.downloadAndAssert({ fileType });
}); });
}); });

View File

@@ -81,25 +81,24 @@ describe("scenarios > sharing > invite someone to view", () => {
id: ORDERS_QUESTION_ID, id: ORDERS_QUESTION_ID,
}); });
}); });
H.modal().within(() => {
cy.findByText(/We couldn't send an email invitation/).should(
"be.visible",
);
cy.findByLabelText("Temporary password")
.invoke("val")
.should("not.be.empty");
cy.findByLabelText("Link to share")
.invoke("val")
.should("contain", `/question/${ORDERS_QUESTION_ID}`);
});
}); });
}); });
describe("invite email", () => { describe("invite email", { tags: "@external" }, () => {
beforeEach(() => H.setupSMTP()); beforeEach(() => H.setupSMTP());
it("scopes the subject/body to the dashboard and links to it", () => {
H.visitDashboard(ORDERS_DASHBOARD_ID);
inviteFromShareMenu(inviteEmail());
H.getInbox().then(({ body: [email] }) => {
expect(email.subject).to.contain("invited to view the dashboard");
expect(email.html).to.contain(`/dashboard/${ORDERS_DASHBOARD_ID}`);
expect(email.html).to.contain(
"wants to share a Metabase dashboard with you",
);
});
});
it("uses the SSO login link (not a password reset) when password login is disabled", () => { it("uses the SSO login link (not a password reset) when password login is disabled", () => {
enableGoogleSSO(); enableGoogleSSO();
cy.request("PUT", "/api/setting", { "enable-password-login": false }); cy.request("PUT", "/api/setting", { "enable-password-login": false });
@@ -118,14 +117,20 @@ describe("scenarios > sharing > invite someone to view", () => {
}); });
}); });
describe("landing after signup", () => { describe("landing after signup", { tags: "@external" }, () => {
beforeEach(() => H.setupSMTP()); beforeEach(() => H.setupSMTP());
it("lands the invited user on the shared dashboard after they set a password", () => { it("scopes the invite email to the dashboard and lands the invited user on it after they set a password", () => {
H.visitDashboard(ORDERS_DASHBOARD_ID); H.visitDashboard(ORDERS_DASHBOARD_ID);
inviteFromShareMenu(inviteEmail()); inviteFromShareMenu(inviteEmail());
H.getInbox().then(({ body: [sent] }) => { H.getInbox().then(({ body: [sent] }) => {
expect(sent.subject).to.contain("invited to view the dashboard");
expect(sent.html).to.contain(`/dashboard/${ORDERS_DASHBOARD_ID}`);
expect(sent.html).to.contain(
"wants to share a Metabase dashboard with you",
);
cy.signOut(); cy.signOut();
cy.visit(joinUrlFromEmail(sent)); cy.visit(joinUrlFromEmail(sent));
completeSignup(); completeSignup();
@@ -138,7 +143,7 @@ describe("scenarios > sharing > invite someone to view", () => {
}); });
}); });
describe("permissions", () => { describe("permissions", { tags: "@external" }, () => {
beforeEach(() => H.setupSMTP()); beforeEach(() => H.setupSMTP());
// The redirect only navigates; it is not an access grant. An invitee whose // The redirect only navigates; it is not an access grant. An invitee whose

View File

@@ -53,9 +53,6 @@ const dashboardDetails = {
tabs: [tab1, tab2], tabs: [tab1, tab2],
}; };
const PUBLIC_DASHBOARD_REGEX =
/\/public\/dashboard\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/;
const COUNT_ALL = "200"; const COUNT_ALL = "200";
const COUNT_DOOHICKEY = "42"; const COUNT_DOOHICKEY = "42";
@@ -66,10 +63,6 @@ const USERS = {
}; };
const prepareDashboard = () => { const prepareDashboard = () => {
H.updateSetting("enable-public-sharing", true);
cy.intercept("/api/dashboard/*/public_link").as("publicLink");
H.createNativeQuestionAndDashboard({ H.createNativeQuestionAndDashboard({
questionDetails, questionDetails,
dashboardDetails, dashboardDetails,
@@ -113,26 +106,6 @@ describe("scenarios > public > dashboard", () => {
prepareDashboard(); prepareDashboard();
}); });
it("should allow users to create public dashboards", () => {
H.visitDashboard("@dashboardId");
H.openNewPublicLinkDropdown("dashboard");
cy.wait("@publicLink").then(({ response }) => {
expect(response.body.uuid).not.to.be.null;
cy.findByTestId("public-link-input").should("be.visible");
cy.findByTestId("public-link-input").should(
"not.have.attr",
"placeholder",
"Loading…",
);
cy.findByTestId("public-link-input").should(($input) => {
expect($input.val()).to.match(PUBLIC_DASHBOARD_REGEX);
});
});
});
Object.entries(USERS).map(([userType, setUser]) => Object.entries(USERS).map(([userType, setUser]) =>
describe(`${userType}`, () => { describe(`${userType}`, () => {
it("should be able to view public dashboards", () => { it("should be able to view public dashboards", () => {
@@ -176,6 +149,7 @@ describe("scenarios > public > dashboard", () => {
cy.button("Add filter").click(); cy.button("Add filter").click();
}); });
H.applyFilterButton().should("be.visible");
cy.findByTestId("scalar-value").should("have.text", COUNT_ALL); cy.findByTestId("scalar-value").should("have.text", COUNT_ALL);
H.applyFilterButton().click(); H.applyFilterButton().click();
@@ -188,6 +162,9 @@ describe("scenarios > public > dashboard", () => {
H.visitPublicDashboard(id); H.visitPublicDashboard(id);
}); });
// new dashboards should default to 'fixed' width
H.assertDashboardFixedWidth();
H.dashboardParametersContainer().within(() => { H.dashboardParametersContainer().within(() => {
cy.findByText(textFilter.name).should("be.visible"); cy.findByText(textFilter.name).should("be.visible");
cy.findByText(unusedFilter.name).should("not.exist"); cy.findByText(unusedFilter.name).should("not.exist");
@@ -202,36 +179,29 @@ describe("scenarios > public > dashboard", () => {
}); });
}); });
it("should respect dashboard width setting in a public dashboard", () => { it("should render when a filter passed with value starting from '0', support #theme=dark, and respect full width (metabase#41483, metabase#65731)", () => {
cy.get("@dashboardId").then((id) => { cy.get("@dashboardId").then((id) => {
H.visitPublicDashboard(id);
});
// new dashboards should default to 'fixed' width
H.assertDashboardFixedWidth();
// toggle full-width
cy.get("@dashboardId").then((id) => {
cy.signInAsAdmin();
cy.request("PUT", `/api/dashboard/${id}`, { cy.request("PUT", `/api/dashboard/${id}`, {
width: "full", width: "full",
}); });
H.visitPublicDashboard(id);
});
H.assertDashboardFullWidth();
});
it("should render when a filter passed with value starting from '0' (metabase#41483)", () => {
cy.get("@dashboardId").then((id) => {
H.visitPublicDashboard(id, { H.visitPublicDashboard(id, {
params: { text: "002" }, params: { text: "002" },
hash: {
theme: "dark",
},
}); });
}); });
cy.url().should("include", "text=002"); cy.url().should("include", "text=002");
H.filterWidget().findByText("002").should("be.visible"); H.filterWidget().findByText("002").should("be.visible");
cy.log("dark theme should have white text");
cy.findByRole("heading", {
name: "Test Dashboard",
}).should("have.css", "color", "rgba(255, 255, 255, 0.95)");
H.assertDashboardFullWidth();
}); });
it("should respect click behavior", () => { it("should respect click behavior", () => {
@@ -281,25 +251,6 @@ describe("scenarios > public > dashboard", () => {
expect(element.href).to.eq("https://metabase.com/"); expect(element.href).to.eq("https://metabase.com/");
}); });
}); });
it("should support #theme=dark (metabase#65731)", () => {
const dashboardName = "Dashboard Theme Test";
H.createDashboardWithQuestions({
dashboardName,
questions: [],
}).then(({ dashboard }) => {
H.visitPublicDashboard(dashboard.id, {
hash: {
theme: "dark",
},
});
});
cy.log("dark theme should have white text");
cy.findByRole("heading", {
name: dashboardName,
}).should("have.css", "color", "rgba(255, 255, 255, 0.95)");
});
}); });
describe("scenarios [EE] > public > dashboard", () => { describe("scenarios [EE] > public > dashboard", () => {
@@ -312,36 +263,28 @@ describe("scenarios [EE] > public > dashboard", () => {
H.activateToken("pro-self-hosted"); H.activateToken("pro-self-hosted");
}); });
it("should set the window title to `{dashboard name} · {application name}`", () => { it("should set the window title to `{dashboard name} · {application name}`, keep the background via `#background=false` without an iframe, and disable it inside an iframe (metabase#62391)", () => {
H.updateSetting("application-name", "Custom Application Name"); H.updateSetting("application-name", "Custom Application Name");
cy.get("@dashboardId").then((id) => { cy.get("@dashboardId").then((id) => {
H.visitPublicDashboard(id); H.visitPublicDashboard(id, {
hash: { background: "false" },
});
cy.title().should("eq", "Test Dashboard · Custom Application Name"); cy.title().should("eq", "Test Dashboard · Custom Application Name");
}); });
});
it("should allow to set locale from the `#locale` hash parameter (metabase#50182)", () => { cy.findByTestId("embed-frame").should("exist");
// We don't have a de-CH.json file, so it should fallback to de.json, see metabase#51039 for more details
cy.intercept("GET", "**/locale-de-json*.js").as("deLocale");
cy.get("@dashboardId").then((id) => { cy.get("body.mb-wrapper").should(
H.visitPublicDashboard(id, { "not.have.css",
hash: { locale: "de-CH" }, "background-color",
}); "rgba(0, 0, 0, 0)",
}); );
cy.wait("@deLocale"); cy.signInAsAdmin();
cy.visit("/");
cy.findByRole("button", {
name: "Automatische Aktualisierung",
}).should("exist");
cy.url().should("include", "locale=de");
});
it("should disable background via `#background=false` hash parameter when rendered inside an iframe (metabase#62391)", () => {
cy.get("@dashboardId").then((id) => { cy.get("@dashboardId").then((id) => {
H.visitPublicDashboard(id, { H.visitPublicDashboard(id, {
hash: { background: "false" }, hash: { background: "false" },
@@ -364,20 +307,23 @@ describe("scenarios [EE] > public > dashboard", () => {
}); });
}); });
it("should not disable background via `#background=false` hash parameter when rendered without an iframe", () => { it("should allow to set locale from the `#locale` hash parameter (metabase#50182)", () => {
// We don't have a de-CH.json file, so it should fallback to de.json, see metabase#51039 for more details
cy.intercept("GET", "**/locale-de-json*.js").as("deLocale");
cy.get("@dashboardId").then((id) => { cy.get("@dashboardId").then((id) => {
H.visitPublicDashboard(id, { H.visitPublicDashboard(id, {
hash: { background: "false" }, hash: { locale: "de-CH" },
}); });
}); });
cy.findByTestId("embed-frame").should("exist"); cy.wait("@deLocale");
cy.get("body.mb-wrapper").should( cy.findByRole("button", {
"not.have.css", name: "Automatische Aktualisierung",
"background-color", }).should("exist");
"rgba(0, 0, 0, 0)",
); cy.url().should("include", "locale=de");
}); });
it("should handle /api/session/properties incorrect response (metabase#62501)", () => { it("should handle /api/session/properties incorrect response (metabase#62501)", () => {

View File

@@ -45,8 +45,6 @@ describe("scenarios > public > question", () => {
H.restore(); H.restore();
cy.signInAsAdmin(); cy.signInAsAdmin();
H.updateSetting("enable-public-sharing", true);
}); });
it("adds filters to url as get params and renders the results correctly (metabase#7120, metabase#17033, metabase#21993)", () => { it("adds filters to url as get params and renders the results correctly (metabase#7120, metabase#17033, metabase#21993)", () => {
@@ -72,6 +70,8 @@ describe("scenarios > public > question", () => {
cy.wait("@publicQuery"); cy.wait("@publicQuery");
cy.findAllByTestId("cell-data").should("contain.text", "Affiliate");
// Make sure we can download the public question (metabase#21993) // Make sure we can download the public question (metabase#21993)
cy.get("@uuid").then((publicUuid) => { cy.get("@uuid").then((publicUuid) => {
H.main().realHover(); H.main().realHover();
@@ -104,52 +104,29 @@ describe("scenarios > public > question", () => {
}), }),
); );
it("should be able to view public questions with snippets", () => { it("should be able to view public questions with snippets and card template tags", () => {
H.startNewNativeQuestion({ display: "table" });
// Create a snippet
cy.icon("snippet").click();
cy.findByTestId("sidebar-content").findByText("Create snippet").click();
H.modal().within(() => {
cy.findByLabelText("Enter some SQL here so you can reuse it later").type(
"'test'",
);
cy.findByLabelText("Give your snippet a name").type("string 'test'");
cy.findByText("Save").click();
});
H.NativeEditor.type("{moveToStart}select ");
H.saveQuestion(
"test question",
{ wrapId: true },
{
path: ["Our analytics"],
},
);
cy.get("@questionId").then((id) => {
H.createPublicQuestionLink(id).then(({ body: { uuid } }) => {
cy.signOut();
cy.signInAsNormalUser().then(() => {
cy.visit(`/public/question/${uuid}`);
cy.get("[data-testid=cell-data]").contains("test");
});
});
});
});
it("should be able to view public questions with card template tags", () => {
H.createNativeQuestion({ H.createNativeQuestion({
name: "Nested Question", name: "Nested Question",
native: { native: {
query: "SELECT * FROM PEOPLE LIMIT 5", query: "SELECT * FROM PEOPLE LIMIT 5",
}, },
}).then(({ body: { id } }) => { }).then(({ body: { id: nestedQuestionId } }) => {
H.startNewNativeQuestion({ display: "table" }); H.startNewNativeQuestion({ display: "table" });
H.NativeEditor.type(`select * from {{#${id}`); // Create a snippet
cy.icon("snippet").click();
cy.findByTestId("sidebar-content").findByText("Create snippet").click();
H.modal().within(() => {
cy.findByLabelText(
"Enter some SQL here so you can reuse it later",
).type("'test'");
cy.findByLabelText("Give your snippet a name").type("string 'test'");
cy.findByText("Save").click();
});
H.NativeEditor.type("{moveToStart}select ");
H.NativeEditor.type(`{moveToEnd}, * from {{#${nestedQuestionId}`);
H.saveQuestion( H.saveQuestion(
"test question", "test question",
@@ -158,14 +135,16 @@ describe("scenarios > public > question", () => {
path: ["Our analytics"], path: ["Our analytics"],
}, },
); );
cy.get("@questionId").then((id) => { });
H.createPublicQuestionLink(id).then(({ body: { uuid } }) => {
cy.signOut(); cy.get("@questionId").then((id) => {
cy.signInAsNormalUser().then(() => { H.createPublicQuestionLink(id).then(({ body: { uuid } }) => {
cy.visit(`/public/question/${uuid}`); cy.signOut();
// Check the name of the first person in the PEOPLE table cy.signInAsNormalUser().then(() => {
cy.get("[data-testid=cell-data]").contains("Hudson Borer"); cy.visit(`/public/question/${uuid}`);
}); cy.get("[data-testid=cell-data]").contains("test");
// Check the name of the first person in the PEOPLE table
cy.get("[data-testid=cell-data]").contains("Hudson Borer");
}); });
}); });
}); });
@@ -193,26 +172,6 @@ describe("scenarios > public > question", () => {
}); });
}); });
describe("scenarios > question > public link with extension", () => {
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
H.createNativeQuestion(
{
name: "Question A",
native: {
query: "SELECT ID from (SELECT * FROM ORDERS LIMIT 1) as order_row",
},
},
{
visitQuestion: true,
wrapId: true,
},
).as("questionId");
});
});
describe("scenarios [EE] > public > question", () => { describe("scenarios [EE] > public > question", () => {
beforeEach(() => { beforeEach(() => {
cy.intercept("GET", "/api/public/card/*/query?*").as("publicQuery"); cy.intercept("GET", "/api/public/card/*/query?*").as("publicQuery");
@@ -220,8 +179,6 @@ describe("scenarios [EE] > public > question", () => {
H.restore(); H.restore();
cy.signInAsAdmin(); cy.signInAsAdmin();
H.activateToken("pro-self-hosted"); H.activateToken("pro-self-hosted");
H.updateSetting("enable-public-sharing", true);
}); });
it("should allow to set locale from the `#locale` hash parameter (metabase#50182)", () => { it("should allow to set locale from the `#locale` hash parameter (metabase#50182)", () => {

View File

@@ -47,77 +47,30 @@ describe("Public dashboards/questions downloads (results and pdf)", () => {
cy.visit(`${publicLink}#downloads=false`); cy.visit(`${publicLink}#downloads=false`);
waitLoading(); waitLoading();
cy.findByRole("heading", { name: "Orders in a dashboard" }).should(
"be.visible",
);
H.getDashboardCard()
.findAllByTestId("cell-data")
.should("have.length.above", 0);
cy.findByRole("button", { name: "Download as PDF" }).should("not.exist"); cy.findByRole("button", { name: "Download as PDF" }).should("not.exist");
// we should not have any dashcard action in a public/embed scenario, so the menu should not be there // we should not have any dashcard action in a public/embed scenario, so the menu should not be there
H.getEmbeddedDashboardCardMenu().should("not.exist"); H.getEmbeddedDashboardCardMenu().should("not.exist");
}); });
it("#downloads=pdf should enable only PDF downloads", () => { it("#downloads=pdf should enable only PDF downloads and allow downloading a public dashboard as PDF", () => {
cy.visit(`${publicLink}#downloads=pdf`); cy.visit(`${publicLink}#downloads=pdf`);
waitLoading(); waitLoading();
cy.get("header") cy.get("header")
.findByRole("button", { name: "Download as PDF" }) .findByRole("button", { name: "Download as PDF" })
.should("exist"); .should("exist");
H.getDashboardCard()
.findAllByTestId("cell-data")
.should("have.length.above", 0);
H.getEmbeddedDashboardCardMenu().should("not.exist"); H.getEmbeddedDashboardCardMenu().should("not.exist");
});
it("#downloads=results should enable only dashcard results downloads", () => {
cy.visit(`${publicLink}#downloads=results`);
waitLoading();
cy.get("header")
.findByRole("button", { name: "Download as PDF" })
.should("not.exist");
H.main().realHover();
H.getEmbeddedDashboardCardMenu().click();
cy.findByLabelText("Download results").should("be.visible");
});
it("#downloads=pdf,results should enable both PDF and results downloads", () => {
cy.visit(`${publicLink}#downloads=pdf,results`);
waitLoading();
cy.get("header")
.findByRole("button", { name: "Download as PDF" })
.should("exist");
H.main().realHover();
H.getEmbeddedDashboardCardMenu().should("exist").click();
cy.findByLabelText("Download results").should("be.visible");
});
it("#downloads=results,pdf should enable both PDF and results downloads (order agnostic)", () => {
cy.visit(`${publicLink}#downloads=results,pdf`);
waitLoading();
cy.get("header")
.findByRole("button", { name: "Download as PDF" })
.should("exist");
H.main().realHover();
H.getEmbeddedDashboardCardMenu().click();
cy.findByLabelText("Download results").should("be.visible");
});
it("#downloads=results, pdf should handle whitespace between parameters", () => {
cy.visit(`${publicLink}#downloads=results, pdf`);
waitLoading();
cy.get("header")
.findByRole("button", { name: "Download as PDF" })
.should("exist");
H.main().realHover();
H.getEmbeddedDashboardCardMenu().click();
cy.findByLabelText("Download results").should("be.visible");
});
it("should be able to download a public dashboard as PDF", () => {
cy.visit(`${publicLink}#downloads=true`);
waitLoading();
cy.get("header") cy.get("header")
.findByRole("button", { name: "Download as PDF" }) .findByRole("button", { name: "Download as PDF" })
@@ -132,10 +85,14 @@ describe("Public dashboards/questions downloads (results and pdf)", () => {
}); });
}); });
it("should be able to download a public dashcard as CSV", () => { it("should enable both downloads by default, allow downloading a public dashcard as CSV, and #downloads=results should enable only dashcard results downloads", () => {
cy.visit(`${publicLink}`); cy.visit(publicLink);
waitLoading(); waitLoading();
cy.get("header")
.findByRole("button", { name: "Download as PDF" })
.should("exist");
const uuid = publicLink.split("/").at(-1); const uuid = publicLink.split("/").at(-1);
H.downloadAndAssert({ H.downloadAndAssert({
@@ -153,6 +110,34 @@ describe("Public dashboards/questions downloads (results and pdf)", () => {
accessed_via: "public-link", accessed_via: "public-link",
export_type: "csv", export_type: "csv",
}); });
cy.visit(`${publicLink}#downloads=results`);
cy.reload();
waitLoading();
H.getDashboardCard()
.findAllByTestId("cell-data")
.should("have.length.above", 0);
cy.get("header")
.findByRole("button", { name: "Download as PDF" })
.should("not.exist");
H.main().realHover();
H.getEmbeddedDashboardCardMenu().click();
cy.findByLabelText("Download results").should("be.visible");
});
it("#downloads=results, pdf should handle whitespace between parameters", () => {
cy.visit(`${publicLink}#downloads=results, pdf`);
waitLoading();
cy.get("header")
.findByRole("button", { name: "Download as PDF" })
.should("exist");
H.main().realHover();
H.getEmbeddedDashboardCardMenu().click();
cy.findByLabelText("Download results").should("be.visible");
}); });
}); });
@@ -180,20 +165,11 @@ describe("Public dashboards/questions downloads (results and pdf)", () => {
cy.signOut(); cy.signOut();
}); });
it("#downloads=results should enable result downloads", () => {
cy.visit(`${publicLink}#downloads=results`);
waitLoading();
H.main().realHover();
cy.findByRole("button", { name: "Download results" }).should(
"be.visible",
);
});
it("#downloads=false should disable result downloads", () => { it("#downloads=false should disable result downloads", () => {
cy.visit(`${publicLink}#downloads=false`); cy.visit(`${publicLink}#downloads=false`);
waitLoading(); waitLoading();
H.echartsContainer().should("be.visible");
H.main().realHover(); H.main().realHover();
cy.findByRole("button", { name: "Download results" }).should("not.exist"); cy.findByRole("button", { name: "Download results" }).should("not.exist");
}); });
@@ -218,8 +194,8 @@ describe("Public dashboards/questions downloads (results and pdf)", () => {
}); });
}); });
it("should be able to download a public card as CSV", () => { it("#downloads=results should enable result downloads and allow downloading a public card as CSV", () => {
cy.visit(`${publicLink}`); cy.visit(`${publicLink}#downloads=results`);
waitLoading(); waitLoading();
H.main().realHover(); H.main().realHover();
@@ -307,7 +283,6 @@ describe("Public dashboards/questions downloads (results and pdf)", () => {
H.downloadAndAssert({ H.downloadAndAssert({
publicUuid: uuid, publicUuid: uuid,
fileType: "csv", fileType: "csv",
questionId: ORDERS_BY_YEAR_QUESTION_ID,
isDashboard: false, isDashboard: false,
isEmbed: true, isEmbed: true,
}); });

View File

@@ -17,156 +17,73 @@ const { H } = cy;
}); });
}); });
describe("when embedding is disabled", () => { describe("when embedding and public sharing are disabled", () => {
beforeEach(() => { beforeEach(() => {
H.updateSetting("enable-public-sharing", false);
H.updateSetting("enable-embedding-static", false); H.updateSetting("enable-embedding-static", false);
}); });
describe("when user is admin", () => { it(`should hide the public link option for ${resource} and still let an admin open the embed modal`, () => {
it(`should always show the embed button for ${resource}`, () => { cy.get("@resourceId").then((id) => {
cy.get("@resourceId").then((id) => { visitResource(resource, id);
visitResource(resource, id);
});
H.openSharingMenu();
H.sharingMenu()
.findByRole("button", { name: "Embed" })
.should("be.visible")
.and("be.enabled")
.click();
H.embedModalContent().should("be.visible");
}); });
H.openSharingMenu();
H.sharingMenu().within(() => {
cy.findByRole("button", { name: "Embed" })
.should("be.visible")
.and("be.enabled");
cy.findByText(/public link/i).should("not.exist");
cy.findByText("Enable").should("not.exist");
});
H.sharingMenu().findByRole("button", { name: "Embed" }).click();
H.embedModalContent().should("be.visible");
cy.signInAsNormalUser();
cy.get("@resourceId").then((id) => {
visitResource(resource, id);
});
assertNonAdminCannotCreatePublicLink(resource);
});
});
describe("when embedding and public sharing are enabled", () => {
beforeEach(() => {
H.updateSetting("enable-public-sharing", true);
H.updateSetting("enable-embedding-static", true);
}); });
describe("when user is non-admin", () => { describe("when user is non-admin", () => {
it(`should not show embed button for ${resource}`, () => { it(`should let a non-admin copy the existing public link for a ${resource}`, () => {
cy.get("@resourceId").then((id) => {
createPublicResourceLink(resource, id);
});
cy.signInAsNormalUser(); cy.signInAsNormalUser();
cy.get("@resourceId").then((id) => { cy.get("@resourceId").then((id) => {
visitResource(resource, id); visitResource(resource, id);
}); });
if (resource === "question") { cy.window().then((win) => {
// No public link: the share button copies directly, so there's no menu. cy.stub(win.navigator.clipboard, "writeText")
H.sharingMenuButton().should( .as("copyLink")
"have.attr", .resolves();
"aria-label", });
"Copy link",
H.openSharingMenu("Copy public link");
H.tooltip().findByText("Public link copied to clipboard");
cy.get("@copyLink").should((stub) => {
expect(stub.firstCall.args[0]).to.match(
new RegExp(`/public/${resource}/`),
); );
}
if (resource === "dashboard") {
H.openSharingMenu();
H.sharingMenu().findByText(/embed/i).should("not.exist");
}
});
});
});
describe("when embedding is enabled", () => {
describe("when public sharing is enabled", () => {
beforeEach(() => {
H.updateSetting("enable-public-sharing", true);
H.updateSetting("enable-embedding-static", true);
});
describe("when user is admin", () => {
it(`should show the embed menu for ${resource}`, () => {
cy.get("@resourceId").then((id) => {
visitResource(resource, id);
});
H.openSharingMenu("Embed");
H.embedModalContent().should("be.visible");
});
it(`should let the user create a public link for ${resource}`, () => {
cy.get("@resourceId").then((id) => {
createPublicResourceLink(resource, id);
visitResource(resource, id);
});
H.openSharingMenu(/public link/i);
assertValidPublicLink({ resource, shouldHaveRemoveLink: true });
});
});
describe("when user is non-admin", () => {
it(`should not prompt a non-admin to create a public link for a ${resource} without an existing link`, () => {
cy.signInAsNormalUser();
cy.get("@resourceId").then((id) => {
visitResource(resource, id);
});
assertNonAdminCannotCreatePublicLink(resource);
});
it(`should let a non-admin copy the existing public link for a ${resource}`, () => {
cy.get("@resourceId").then((id) => {
createPublicResourceLink(resource, id);
});
cy.signInAsNormalUser();
cy.get("@resourceId").then((id) => {
visitResource(resource, id);
});
cy.window().then((win) => {
cy.stub(win.navigator.clipboard, "writeText")
.as("copyLink")
.resolves();
});
H.openSharingMenu("Copy public link");
H.tooltip().findByText("Public link copied to clipboard");
cy.get("@copyLink").should((stub) => {
expect(stub.firstCall.args[0]).to.match(
new RegExp(`/public/${resource}/`),
);
});
cy.findByTestId("public-link-popover-content").should("not.exist");
});
});
});
describe("when public sharing is disabled", () => {
beforeEach(() => {
H.updateSetting("enable-public-sharing", false);
H.updateSetting("enable-embedding-static", true);
});
describe("when user is admin", () => {
it(`should hide the public link option for ${resource} and allow the user to access the embed modal`, () => {
cy.get("@resourceId").then((id) => {
visitResource(resource, id);
});
H.openSharingMenu();
H.sharingMenu().within(() => {
cy.findByText(/public link/i).should("not.exist");
cy.findByText("Enable").should("not.exist");
});
H.sharingMenu().findByRole("button", { name: "Embed" }).click();
});
});
describe("when user is non-admin", () => {
it(`should not prompt a non-admin to create a public link for ${resource}`, () => {
cy.signInAsNormalUser();
cy.get("@resourceId").then((id) => {
visitResource(resource, id);
});
assertNonAdminCannotCreatePublicLink(resource);
}); });
cy.findByTestId("public-link-popover-content").should("not.exist");
}); });
}); });
}); });
@@ -203,21 +120,17 @@ describe("Embed JS modal display", () => {
"should display a link to the product page for embedded analytics", "should display a link to the product page for embedded analytics",
{ tags: "@OSS" }, { tags: "@OSS" },
() => { () => {
cy.signInAsAdmin();
H.visitDashboard("@dashboardId"); H.visitDashboard("@dashboardId");
H.openSharingMenu("Embed"); H.openSharingMenu("Embed");
it("should open Embed JS modal with the `enable guest embedding` card", () => { H.embedModalContent().should("be.visible");
H.activateToken("pro-self-hosted"); H.embedModalContent()
H.updateSetting("enable-embedding-static", false); .findByTestId("upsell-card")
H.visitDashboard("@dashboardId"); .scrollIntoView()
.within(() => {
H.openSharingMenu("Embed"); cy.findByText("Get more powerful embedding").should("be.visible");
cy.findByText("Upgrade to Metabase Pro").should("be.visible");
H.embedModalEnableEmbeddingCard().within(() => {
cy.findByText(/guest embeds/).should("be.visible");
}); });
});
}, },
); );
}); });
@@ -263,67 +176,44 @@ describe("#39152 sharing an unsaved question", () => {
beforeEach(() => { beforeEach(() => {
H.restore(); H.restore();
cy.signInAsAdmin(); cy.signInAsAdmin();
H.enableTracking();
createResource(resource).then(({ body }) => { createResource(resource).then(({ body }) => {
cy.wrap(body.id).as("resourceId"); cy.wrap(body.id).as("resourceId");
}); });
}); });
[ it("should show the legacy alert for a null embedding type but not for guest-embed", () => {
{ embeddingType: "guest-embed", shouldShowAlert: false },
{ embeddingType: "static-legacy", shouldShowAlert: true },
{ embeddingType: null, shouldShowAlert: true },
].forEach(({ embeddingType, shouldShowAlert }) => {
it(`should ${shouldShowAlert ? "show" : "not show"} legacy alert for ${embeddingType} embedding type`, () => {
cy.get("@resourceId").then((id) => {
visitResource(resource, id);
const apiPath = resource === "question" ? "card" : "dashboard";
cy.request("PUT", `/api/${apiPath}/${id}`, {
enable_embedding: true,
embedding_type: embeddingType,
});
openSharingMenu("Embed");
embedModalContent().should("exist");
legacyStaticEmbeddingButton().should(
shouldShowAlert ? "exist" : "not.exist",
);
});
});
});
it("should set a proper embedding_type", () => {
cy.get("@resourceId").then((id) => { cy.get("@resourceId").then((id) => {
visitResource(resource, id); visitResource(resource, id);
H.openLegacyStaticEmbeddingModal({ cy.request("PUT", `/api/${apiPath}/${id}`, {
resource, enable_embedding: true,
resourceId: id, embedding_type: null,
activeTab: "parameters",
}); });
});
H.publishChanges(apiPath, ({ request, response }) => { openSharingMenu("Embed");
assert.deepEqual(request.body.embedding_type, "static-legacy");
assert.deepEqual(response.body.embedding_type, "static-legacy");
});
H.modal().findByLabelText("Price").click(); embedModalContent().should("exist");
H.selectDropdown().findByText("Editable").click(); legacyStaticEmbeddingButton().should("exist");
H.publishChanges(apiPath, ({ request, response }) => { cy.intercept({
assert.deepEqual(request.body.embedding_type, "static-legacy"); method: "GET",
assert.deepEqual(response.body.embedding_type, "static-legacy"); pathname: `/api/${apiPath}/${id}`,
}); }).as("getResource");
H.unpublishChanges(apiPath, ({ request, response }) => { embedModalContent().findByLabelText("Close").click();
assert.deepEqual(request.body.embedding_type, null); embedModalContent().should("not.exist");
assert.deepEqual(response.body.embedding_type, null);
cy.request("PUT", `/api/${apiPath}/${id}`, {
enable_embedding: true,
embedding_type: "guest-embed",
});
openSharingMenu("Embed");
cy.wait("@getResource");
embedModalContent().should("exist");
legacyStaticEmbeddingButton().should("not.exist");
}); });
}); });
}); });
@@ -336,7 +226,6 @@ describe("#39152 sharing an unsaved question", () => {
H.enableTracking(); H.enableTracking();
createResource(resource).then(({ body }) => { createResource(resource).then(({ body }) => {
cy.wrap(body).as("resource");
cy.wrap(body.id).as("resourceId"); cy.wrap(body.id).as("resourceId");
}); });
}); });
@@ -347,7 +236,7 @@ describe("#39152 sharing an unsaved question", () => {
describe(`when embedding ${resource}`, () => { describe(`when embedding ${resource}`, () => {
describe("when interacting with public link popover", () => { describe("when interacting with public link popover", () => {
it("should send `public_link_copied` event when copying public link", () => { it("should send `public_link_copied` and `public_link_removed` events and show an existing public link", () => {
cy.get("@resourceId").then((id) => { cy.get("@resourceId").then((id) => {
visitResource(resource, id); visitResource(resource, id);
}); });
@@ -393,62 +282,22 @@ describe("#39152 sharing an unsaved question", () => {
format: "json", format: "json",
}); });
} }
});
it("should send `public_link_removed` when removing the public link", () => {
cy.get("@resourceId").then((id) => {
visitResource(resource, id);
});
H.openSharingMenu(/public link/i);
H.popover().button("Remove public link").click(); H.popover().button("Remove public link").click();
H.expectUnstructuredSnowplowEvent({ H.expectUnstructuredSnowplowEvent({
event: "public_link_removed", event: "public_link_removed",
artifact: resource, artifact: resource,
source: "public-share", source: "public-share",
}); });
});
});
describe("when interacting with public embedding", () => {
it("should send `public_embed_code_copied` event when copying the public embed iframe", () => {
cy.get("@resourceId").then((id) => { cy.get("@resourceId").then((id) => {
createPublicResourceLink(resource, id);
visitResource(resource, id); visitResource(resource, id);
}); });
H.openSharingMenu("Create a public link"); H.openSharingMenu(/public link/i);
// mock clipboardData so that copy-to-clipboard doesn't use window.prompt, pausing the tests assertValidPublicLink({ resource, shouldHaveRemoveLink: true });
cy.window().then((win) => {
win.clipboardData = {
setData: (...args) =>
// eslint-disable-next-line no-console
console.log("clipboardData.setData", ...args),
};
});
H.popover().findByTestId("copy-button").click();
H.expectUnstructuredSnowplowEvent({
event: "public_link_copied",
artifact: resource,
});
});
it("should send `public_link_removed` event when removing the public embed", () => {
cy.get("@resourceId").then((id) => {
visitResource(resource, id);
});
H.openSharingMenu("Create a public link");
H.popover().findByText("Remove public link").click();
H.expectUnstructuredSnowplowEvent({
event: "public_link_removed",
artifact: resource,
source: "public-share",
});
}); });
}); });
@@ -607,6 +456,78 @@ describe("#39152 sharing an unsaved question", () => {
}); });
}); });
// Individual download options are only supported for dashboards
if (resource === "dashboard") {
H.modal().within(() => {
cy.findByRole("tab", { name: "Look and Feel" }).click();
});
cy.log(
"Assert that it sends `enabled_download_types: { pdf: false, results: true }` when only results download is enabled",
);
// Disable PDF exports
cy.findByLabelText("Export to PDF").click({ force: true });
cy.findByTestId("embed-backend")
.findByTestId("copy-button")
.realClick();
H.expectUnstructuredSnowplowEvent({
event: "static_embed_code_copied",
artifact: resource,
language: "node",
location: "code_appearance",
code: "backend",
appearance: {
background: true,
bordered: true,
titled: true,
font: "instance",
theme: "light",
enabled_download_types: { pdf: false, results: true },
},
});
cy.log(
"Assert that it sends `enabled_download_types: { pdf: true, results: false }` when only PDF is enabled",
);
// Enable PDF exports again
cy.findByLabelText("Export to PDF").click({ force: true });
// Disable results download
cy.findByLabelText("Results (csv, xlsx, json, png)").click({
force: true,
});
cy.findByTestId("embed-backend")
.findByTestId("copy-button")
.realClick();
H.expectUnstructuredSnowplowEvent({
event: "static_embed_code_copied",
artifact: resource,
language: "node",
location: "code_appearance",
code: "backend",
appearance: {
background: true,
bordered: true,
titled: true,
font: "instance",
theme: "light",
enabled_download_types: { pdf: true, results: false },
},
});
cy.log("Restore the default download options");
cy.findByLabelText("Results (csv, xlsx, json, png)").click({
force: true,
});
H.modal().findByRole("tab", { name: "Overview" }).click();
}
cy.log("Assert copying codes in Overview tab"); cy.log("Assert copying codes in Overview tab");
cy.findByTestId("embed-backend") cy.findByTestId("embed-backend")
.findByTestId("copy-button") .findByTestId("copy-button")
@@ -733,88 +654,9 @@ describe("#39152 sharing an unsaved question", () => {
}, },
}); });
}); });
// Individual download options are only supported for dashboards
if (resource === "dashboard") {
it("should support disabling PDF and result downloads individually in `static_embed_code_copied`", () => {
cy.get("@resourceId").then((id) => {
visitResource(resource, id);
H.openLegacyStaticEmbeddingModal({
resource: "dashboard",
resourceId: id,
});
});
H.modal().within(() => {
cy.findByRole("tab", { name: "Look and Feel" }).click();
});
cy.log(
"Assert that it sends `enabled_download_types: { pdf: false, results: true }` when only results download is enabled",
);
// Disable PDF exports
cy.findByLabelText("Export to PDF").click({ force: true });
cy.findByTestId("embed-backend")
.findByTestId("copy-button")
.realClick();
H.expectUnstructuredSnowplowEvent({
event: "static_embed_code_copied",
artifact: resource,
language: "node",
location: "code_appearance",
code: "backend",
appearance: {
background: true,
bordered: true,
titled: true,
font: "instance",
theme: "light",
enabled_download_types: { pdf: false, results: true },
},
});
cy.log(
"Assert that it sends `enabled_download_types: { pdf: true, results: false }` when only PDF is enabled",
);
// Enable PDF exports again
cy.findByLabelText("Export to PDF").click({ force: true });
// Disable results download
cy.findByLabelText(
resource === "dashboard"
? "Results (csv, xlsx, json, png)"
: "Download (csv, xlsx, json, png)",
).click({ force: true });
cy.findByTestId("embed-backend")
.findByTestId("copy-button")
.realClick();
H.expectUnstructuredSnowplowEvent({
event: "static_embed_code_copied",
artifact: resource,
language: "node",
location: "code_appearance",
code: "backend",
appearance: {
background: true,
bordered: true,
titled: true,
font: "instance",
theme: "light",
enabled_download_types: { pdf: true, results: false },
},
});
});
}
}); });
it("should send `static_embed_discarded` when discarding changes in the static embed modal", () => { it("should set a proper embedding_type and send `static_embed_discarded` and `static_embed_unpublished` in the static embed modal", () => {
cy.get("@resourceId").then((id) => { cy.get("@resourceId").then((id) => {
visitResource(resource, id); visitResource(resource, id);
@@ -823,8 +665,11 @@ describe("#39152 sharing an unsaved question", () => {
resourceId: id, resourceId: id,
activeTab: "parameters", activeTab: "parameters",
}); });
});
H.publishChanges(apiPath); H.publishChanges(apiPath, ({ request, response }) => {
assert.deepEqual(request.body.embedding_type, "static-legacy");
assert.deepEqual(response.body.embedding_type, "static-legacy");
}); });
cy.log("changing parameters, so we could discard changes"); cy.log("changing parameters, so we could discard changes");
@@ -839,6 +684,29 @@ describe("#39152 sharing an unsaved question", () => {
event: "static_embed_discarded", event: "static_embed_discarded",
artifact: resource, artifact: resource,
}); });
H.modal().findByLabelText("Price").click();
H.selectDropdown().findByText("Editable").click();
H.publishChanges(apiPath, ({ request, response }) => {
assert.deepEqual(request.body.embedding_type, "static-legacy");
assert.deepEqual(response.body.embedding_type, "static-legacy");
});
const HOUR = 60 * 60 * 1000;
cy.clock(new Date(Date.now() + HOUR));
H.unpublishChanges(apiPath, ({ request, response }) => {
assert.deepEqual(request.body.embedding_type, null);
assert.deepEqual(response.body.embedding_type, null);
});
H.expectUnstructuredSnowplowEvent({
event: "static_embed_unpublished",
artifact: resource,
time_since_creation: closeTo(toSecond(HOUR), 15),
time_since_initial_publication: closeTo(toSecond(HOUR), 15),
});
}); });
it("should send `static_embed_published` when publishing changes in the static embed modal", () => { it("should send `static_embed_published` when publishing changes in the static embed modal", () => {
@@ -911,29 +779,6 @@ describe("#39152 sharing an unsaved question", () => {
}); });
}); });
}); });
it("should send `static_embed_unpublished` when unpublishing changes in the static embed modal", () => {
cy.get("@resourceId").then((id) => {
visitResource(resource, id);
H.openLegacyStaticEmbeddingModal({ resource, resourceId: id });
H.publishChanges(apiPath);
});
const HOUR = 60 * 60 * 1000;
cy.clock(new Date(Date.now() + HOUR));
cy.findByTestId("embed-modal-content-status-bar").within(() => {
cy.findByText("Unpublish").click();
});
H.expectUnstructuredSnowplowEvent({
event: "static_embed_unpublished",
artifact: resource,
time_since_creation: closeTo(toSecond(HOUR), 10),
time_since_initial_publication: closeTo(toSecond(HOUR), 10),
});
});
}); });
}); });
}); });
@@ -1039,7 +884,7 @@ function assertNonAdminCannotCreatePublicLink(resource) {
H.openSharingMenu(); H.openSharingMenu();
H.sharingMenu().within(() => { H.sharingMenu().within(() => {
cy.findByText("Copy link").should("be.visible"); cy.findByText("Copy link").should("be.visible");
cy.findByText("Embed").should("not.exist"); cy.findByText(/embed/i).should("not.exist");
cy.findByText(/public link/i).should("not.exist"); cy.findByText(/public link/i).should("not.exist");
}); });
} }

View File

@@ -62,94 +62,53 @@ describe("scenarios > admin > settings > public sharing", () => {
cy.intercept("GET", "/api/card/public").as("getPublicQuestions"); cy.intercept("GET", "/api/card/public").as("getPublicQuestions");
}); });
it("should be able to toggle public sharing", () => { it("should list, open, and revoke public dashboards, questions, and actions, and toggle public sharing", () => {
cy.visit("/admin/settings/public-sharing"); cy.intercept("PUT", "/api/setting/enable-public-sharing").as(
cy.findByTestId("enable-public-sharing-setting").within(() => { "updatePublicSharing",
cy.findByText("Enabled").should("be.visible"); );
cy.findByLabelText("Enabled").click();
cy.findByText("Disabled").should("be.visible");
});
});
it("should see public dashboards", () => {
const expectedDashboardName = "Public dashboard"; const expectedDashboardName = "Public dashboard";
const expectedDashboardSlug = "public-dashboard"; const expectedDashboardSlug = "public-dashboard";
H.createQuestionAndDashboard({ const expectedQuestionName = "Public question";
dashboardDetails: { const expectedQuestionSlug = "public-question";
name: expectedDashboardName, const expectedActionName = "Public action";
},
questionDetails: { H.setActionsEnabledForDB(SAMPLE_DB_ID);
name: "Question",
query: { H.createQuestion({
"source-table": ORDERS_ID, name: "Question",
}, query: {
"source-table": ORDERS_ID,
}, },
}) })
.then(({ body }) => { .then(({ body: { id: card_id } }) =>
const dashboardId = body.dashboard_id; H.createDashboardWithTabs({
cy.wrap(dashboardId).as("dashboardId"); name: expectedDashboardName,
cy.request("POST", `/api/dashboard/${dashboardId}/public_link`, {}); tabs: [
{ id: 1, name: "Tab 1" },
{ id: 2, name: "Tab 2" },
],
dashcards: [
{
id: -1,
card_id,
dashboard_tab_id: 1,
row: 0,
col: 0,
size_x: 8,
size_y: 8,
},
],
}),
)
.then((dashboard) => {
cy.wrap(dashboard.id).as("dashboardId");
cy.request("POST", `/api/dashboard/${dashboard.id}/public_link`, {});
}) })
.then((response) => { .then((response) => {
cy.wrap(response.body.uuid).as("dashboardUuid"); cy.wrap(response.body.uuid).as("dashboardUuid");
}); });
cy.get("@dashboardId").then((dashboardId) =>
H.visitDashboardAndCreateTab({ dashboardId }),
);
cy.visit("/admin/settings/public-sharing");
cy.wait([
"@getPublicActions",
"@getPublicQuestions",
"@getPublicDashboards",
]);
cy.findByTestId("admin-layout-content")
.findByText("Shared dashboards")
.should("be.visible");
cy.findByTestId("admin-layout-content")
.findByText(expectedDashboardName)
.should("be.visible");
cy.get("@dashboardUuid").then((dashboardUuid) => {
cy.findByText(
`${location.origin}/public/dashboard/${dashboardUuid}`,
).click();
cy.findByRole("heading", { name: expectedDashboardName }).should(
"be.visible",
);
cy.findByRole("tab", { name: "Tab 1" }).should("be.visible");
cy.visit("/admin/settings/public-sharing");
});
cy.get("@dashboardId").then((dashboardId) => {
cy.findByText(expectedDashboardName).click();
cy.log(
"Sometimes the URL will be updated with the tab ID, so we need to account for that",
);
cy.url().should(
"match",
new RegExp(
`${location.origin}/dashboard/${dashboardId}-${expectedDashboardSlug}*`,
),
);
cy.visit("/admin/settings/public-sharing");
});
cy.button("Revoke link").click();
H.modal().within(() => {
cy.findByText("Disable this link?").should("be.visible");
cy.button("Yes").click();
});
cy.findByTestId("admin-layout-content")
.findByText("No dashboards have been publicly shared yet.")
.scrollIntoView()
.should("be.visible");
});
it("should see public questions", () => {
const expectedQuestionName = "Public question";
const expectedQuestionSlug = "public-question";
H.createQuestion({ H.createQuestion({
name: expectedQuestionName, name: expectedQuestionName,
query: { query: {
@@ -165,53 +124,6 @@ describe("scenarios > admin > settings > public sharing", () => {
cy.wrap(response.body.uuid).as("questionUuid"); cy.wrap(response.body.uuid).as("questionUuid");
}); });
cy.visit("/admin/settings/public-sharing");
cy.wait([
"@getPublicActions",
"@getPublicQuestions",
"@getPublicDashboards",
]);
cy.findByTestId("admin-layout-content")
.findByText("Shared questions")
.should("be.visible");
cy.findByTestId("admin-layout-content")
.findByText(expectedQuestionName)
.should("be.visible");
cy.get("@questionUuid").then((questionUuid) => {
cy.findByText(
`${location.origin}/public/question/${questionUuid}`,
).click();
cy.findByRole("heading", { name: expectedQuestionName }).should(
"be.visible",
);
cy.visit("/admin/settings/public-sharing");
});
cy.get("@questionId").then((questionId) => {
cy.findByText(expectedQuestionName).click();
cy.url().should(
"eq",
`${location.origin}/question/${questionId}-${expectedQuestionSlug}`,
);
cy.visit("/admin/settings/public-sharing");
});
cy.button("Revoke link").click();
H.modal().within(() => {
cy.findByText("Disable this link?").should("be.visible");
cy.button("Yes").click();
});
cy.findByTestId("admin-layout-content")
.findByText("No questions have been publicly shared yet.")
.scrollIntoView()
.should("be.visible");
});
it("should see public actions", () => {
H.setActionsEnabledForDB(SAMPLE_DB_ID);
const expectedActionName = "Public action";
H.createQuestion({ H.createQuestion({
name: "Model", name: "Model",
query: { query: {
@@ -249,6 +161,18 @@ describe("scenarios > admin > settings > public sharing", () => {
"@getPublicDashboards", "@getPublicDashboards",
]); ]);
cy.findByTestId("admin-layout-content")
.findByText("Shared dashboards")
.should("be.visible");
cy.findByTestId("admin-layout-content")
.findByText(expectedDashboardName)
.should("be.visible");
cy.findByTestId("admin-layout-content")
.findByText("Shared questions")
.should("be.visible");
cy.findByTestId("admin-layout-content")
.findByText(expectedQuestionName)
.should("be.visible");
cy.findByTestId("admin-layout-content") cy.findByTestId("admin-layout-content")
.findByText("Shared action forms") .findByText("Shared action forms")
.scrollIntoView() .scrollIntoView()
@@ -257,6 +181,54 @@ describe("scenarios > admin > settings > public sharing", () => {
.findByText(expectedActionName) .findByText(expectedActionName)
.scrollIntoView() .scrollIntoView()
.should("be.visible"); .should("be.visible");
cy.log("public dashboard");
cy.get("@dashboardUuid").then((dashboardUuid) => {
cy.findByText(
`${location.origin}/public/dashboard/${dashboardUuid}`,
).click();
cy.findByRole("heading", { name: expectedDashboardName }).should(
"be.visible",
);
cy.findByRole("tab", { name: "Tab 1" }).should("be.visible");
cy.visit("/admin/settings/public-sharing");
});
cy.get("@dashboardId").then((dashboardId) => {
cy.findByText(expectedDashboardName).click();
cy.log(
"Sometimes the URL will be updated with the tab ID, so we need to account for that",
);
cy.url().should(
"match",
new RegExp(
`${location.origin}/dashboard/${dashboardId}-${expectedDashboardSlug}*`,
),
);
cy.visit("/admin/settings/public-sharing");
});
cy.log("public question");
cy.get("@questionUuid").then((questionUuid) => {
cy.findByText(
`${location.origin}/public/question/${questionUuid}`,
).click();
cy.findByRole("heading", { name: expectedQuestionName }).should(
"be.visible",
);
cy.visit("/admin/settings/public-sharing");
});
cy.get("@questionId").then((questionId) => {
cy.findByText(expectedQuestionName).click();
cy.url().should(
"eq",
`${location.origin}/question/${questionId}-${expectedQuestionSlug}`,
);
cy.visit("/admin/settings/public-sharing");
});
cy.log("public action");
cy.get("@actionUuid").then((actionUuid) => { cy.get("@actionUuid").then((actionUuid) => {
cy.findByText(`${location.origin}/public/action/${actionUuid}`).click(); cy.findByText(`${location.origin}/public/action/${actionUuid}`).click();
cy.findByRole("heading", { name: expectedActionName }).should( cy.findByRole("heading", { name: expectedActionName }).should(
@@ -277,14 +249,45 @@ describe("scenarios > admin > settings > public sharing", () => {
cy.visit("/admin/settings/public-sharing"); cy.visit("/admin/settings/public-sharing");
}); });
cy.button("Revoke link").click(); revokePublicLink(expectedDashboardName);
H.modal().within(() => { cy.findByTestId("admin-layout-content")
cy.findByText("Disable this link?").should("be.visible"); .findByText("No dashboards have been publicly shared yet.")
cy.button("Yes").click(); .scrollIntoView()
}); .should("be.visible");
revokePublicLink(expectedQuestionName);
cy.findByTestId("admin-layout-content")
.findByText("No questions have been publicly shared yet.")
.scrollIntoView()
.should("be.visible");
revokePublicLink(expectedActionName);
cy.findByTestId("admin-layout-content") cy.findByTestId("admin-layout-content")
.findByText("No actions have been publicly shared yet.") .findByText("No actions have been publicly shared yet.")
.scrollIntoView() .scrollIntoView()
.should("be.visible"); .should("be.visible");
cy.findByTestId("enable-public-sharing-setting")
.scrollIntoView()
.within(() => {
cy.findByText("Enabled").should("be.visible");
cy.findByLabelText("Enabled").click();
});
cy.wait("@updatePublicSharing");
cy.findByTestId("enable-public-sharing-setting")
.findByText("Disabled")
.should("be.visible");
}); });
}); });
function revokePublicLink(name) {
cy.findByTestId("admin-layout-content")
.findByText(name)
.closest("tr")
.findByRole("button", { name: "Revoke link" })
.click();
H.modal().within(() => {
cy.findByText("Disable this link?").should("be.visible");
cy.button("Yes").click();
});
}

View File

@@ -57,11 +57,18 @@ describe("issue 18009", { tags: "@external" }, () => {
}); });
}); });
describe("issue 18344", { tags: "@external" }, () => { describe("issues 18344 and 18352", { tags: "@external" }, () => {
const { const {
admin: { first_name, last_name }, admin: { first_name, last_name },
} = USERS; } = USERS;
const questionDetails = {
name: "18352",
native: {
query: "SELECT 'foo', 1 UNION ALL SELECT 'bar', 2",
},
};
beforeEach(() => { beforeEach(() => {
H.restore(); H.restore();
cy.signInAsAdmin(); cy.signInAsAdmin();
@@ -86,57 +93,19 @@ describe("issue 18344", { tags: "@external" }, () => {
H.saveDashboard(); H.saveDashboard();
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage // eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("OrdersFoo"); cy.findByText("OrdersFoo");
});
it("subscription should not include original question name when it's been renamed in the dashboard (metabase#18344)", () => { H.createQuestionAndAddToDashboard(questionDetails, ORDERS_DASHBOARD_ID, {
// Send a test email subscription row: 8,
H.toggleDashboardSubscriptionsSidebar(); }).then(({ body: { card_id } }) => {
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage H.visitQuestion(card_id);
cy.findByText("Email it").click();
cy.findByPlaceholderText("Enter user names or email addresses").click(); H.visitDashboard(ORDERS_DASHBOARD_ID);
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText(`${first_name} ${last_name}`).click();
// Click this just to close the popover that is blocking the "Send email now" button
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("To:").click();
H.sendEmailAndAssert((email) => {
expect(email.html).to.include("OrdersFoo");
}); });
}); });
});
describe("issue 18352", { tags: "@external" }, () => { it("subscription should use the renamed question name and send the card with the INT64 values (metabase#18344, metabase#18352)", () => {
const { // Send a test email subscription
admin: { first_name, last_name },
} = USERS;
const questionDetails = {
name: "18352",
native: {
query: "SELECT 'foo', 1 UNION ALL SELECT 'bar', 2",
},
};
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
H.setupSMTP();
H.createNativeQuestionAndDashboard({ questionDetails }).then(
({ body: { card_id, dashboard_id } }) => {
H.visitQuestion(card_id);
H.visitDashboard(dashboard_id);
},
);
});
it("should send the card with the INT64 values (metabase#18352)", () => {
H.toggleDashboardSubscriptionsSidebar(); H.toggleDashboardSubscriptionsSidebar();
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage // eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("Email it").click(); cy.findByText("Email it").click();
@@ -152,8 +121,9 @@ describe("issue 18352", { tags: "@external" }, () => {
"An error occurred while displaying this card.", "An error occurred while displaying this card.",
); );
expect(html).to.include("foo"); expect(html).to.include("OrdersFoo");
expect(html).to.include("bar"); expect(html).to.match(/>\s*foo\s*</);
expect(html).to.match(/>\s*bar\s*</);
}); });
}); });
}); });
@@ -224,7 +194,10 @@ describe("issue 18669", { tags: "@external" }, () => {
cy.button("Update filter").click(); cy.button("Update filter").click();
}); });
H.clickSend(); H.sendEmailAndVisitIt();
cy.get("table.header")
.should("contain", filterDetails.name)
.and("contain", "Gizmo");
}); });
}); });
@@ -247,13 +220,11 @@ describe("issue 20393", () => {
} }
beforeEach(() => { beforeEach(() => {
cy.intercept("POST", "/api/dashboard/*/public_link").as("publicLink");
H.restore(); H.restore();
cy.signInAsAdmin(); cy.signInAsAdmin();
}); });
it("should show public dashboards with nested cards mapped to parameters (metabase#20393)", () => { it("should create a public link and show public dashboards with nested cards mapped to parameters (metabase#20393)", () => {
createDashboardWithNestedCard(); createDashboardWithNestedCard();
H.editDashboard(); H.editDashboard();
@@ -268,12 +239,20 @@ describe("issue 20393", () => {
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage // eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("Save").click(); cy.findByText("Save").click();
// open the sharing modal and enable sharing
H.openNewPublicLinkDropdown("dashboard"); H.openNewPublicLinkDropdown("dashboard");
// navigate to the public dashboard link cy.get("@uuid").then((uuid) => {
cy.wait("@publicLink").then(({ response: { body } }) => { expect(uuid).not.to.be.null;
const { uuid } = body;
cy.findByTestId("public-link-input").should("be.visible");
cy.findByTestId("public-link-input").should(
"not.have.attr",
"placeholder",
"Loading…",
);
cy.findByTestId("public-link-input").should(($input) => {
expect($input.val()).to.match(new RegExp(`/public/dashboard/${uuid}$`));
});
cy.signOut(); cy.signOut();
cy.visit(`/public/dashboard/${uuid}`); cy.visit(`/public/dashboard/${uuid}`);
@@ -417,7 +396,7 @@ describe("issue 22524", () => {
}); });
}); });
describe("issue 24223", () => { describe("issue 24223", { tags: "@external" }, () => {
const questionDetails = { const questionDetails = {
name: "24223", name: "24223",
query: { query: {
@@ -605,15 +584,7 @@ describe("issue 25473", () => {
); );
}); });
it("public sharing: dashboard text filter on a custom column should accept text input (metabase#25473-1)", () => { it("public sharing and signed embedding: dashboard text filter on a custom column should accept text input (metabase#25473)", () => {
cy.get("@dashboardId").then((id) => {
H.visitPublicDashboard(id);
});
assertOnResults();
});
it("signed embedding: dashboard text filter on a custom column should accept text input (metabase#25473-2)", () => {
cy.get("@dashboardId").then((id) => { cy.get("@dashboardId").then((id) => {
cy.request("PUT", `/api/dashboard/${id}`, { cy.request("PUT", `/api/dashboard/${id}`, {
embedding_params: { embedding_params: {
@@ -622,6 +593,12 @@ describe("issue 25473", () => {
enable_embedding: true, enable_embedding: true,
}); });
H.visitPublicDashboard(id);
});
assertOnResults();
cy.get("@dashboardId").then((id) => {
const payload = { const payload = {
resource: { dashboard: id }, resource: { dashboard: id },
params: {}, params: {},
@@ -699,38 +676,6 @@ describe("issue 26988", () => {
}); });
}); });
describe("issue 30314", () => {
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
H.setupSMTP();
});
it("should clean the new subscription form on cancel (metabase#30314)", () => {
H.visitDashboard(ORDERS_DASHBOARD_ID);
H.toggleDashboardSubscriptionsSidebar();
H.sidebar().within(() => {
cy.findByText("Email it").click();
cy.findByLabelText("Attach results")
.should("not.be.checked")
.click({ force: true }); // Input is placed behind the lable due to tooltip in label
cy.findByLabelText("Questions to attach")
.should("not.be.checked")
.click();
cy.button("Cancel").click();
cy.findByText("Email it").click();
cy.findByLabelText("Attach results").should("not.be.checked");
cy.findByText("Questions to attach").should("not.exist");
cy.findByText(".xlsx").should("not.exist");
cy.findByText(".csv").should("not.exist");
});
});
});
describe("issue 17657", () => { describe("issue 17657", () => {
const { first_name, last_name } = admin; const { first_name, last_name } = admin;
@@ -879,6 +824,7 @@ describe("issue 17658", { tags: "@external" }, () => {
H.toggleDashboardSubscriptionsSidebar(); H.toggleDashboardSubscriptionsSidebar();
H.sidebar().findByLabelText("Pulse Card").should("be.visible");
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage // eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText(/^Emailed monthly/).click(); cy.findByText(/^Emailed monthly/).click();
@@ -891,10 +837,14 @@ describe("issue 17658", { tags: "@external" }, () => {
cy.wait("@deletePulse").then(({ response }) => { cy.wait("@deletePulse").then(({ response }) => {
expect(response.body.cause).not.to.exist; expect(response.body.cause).not.to.exist;
expect(response.statusCode).not.to.eq(500); expect(response.statusCode).to.eq(200);
}); });
cy.button("Delete").should("not.exist"); cy.button("Delete").should("not.exist");
H.sidebar().within(() => {
cy.findByText("Set up a dashboard subscription").should("be.visible");
cy.findByLabelText("Pulse Card").should("not.exist");
});
}); });
}); });
@@ -961,21 +911,6 @@ describe("issue 17547", () => {
}); });
}); });
describe("issue 16108", () => {
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
});
it("should display a tooltip for CTA icons on an individual question (metabase#16108)", () => {
H.visitQuestion(ORDERS_QUESTION_ID);
cy.icon("download").realHover();
H.tooltip().findByText("Download results");
H.sharingMenuButton().realHover();
H.tooltip().findByText("Share");
});
});
describe("issue 49525", { tags: "@external" }, () => { describe("issue 49525", { tags: "@external" }, () => {
const { const {
admin: { first_name, last_name }, admin: { first_name, last_name },
@@ -1023,7 +958,7 @@ describe("issue 49525", { tags: "@external" }, () => {
}); });
}); });
it("Subscriptions with 'Keep the data pivoted' checked should work (metabase#49525)", () => { it("Subscriptions with 'Keep the data pivoted' checked should work and render the pivot table inline in the email body (metabase#49525, UXW-4378)", () => {
// Send a test email subscription // Send a test email subscription
H.toggleDashboardSubscriptionsSidebar(); H.toggleDashboardSubscriptionsSidebar();
H.sidebar().within(() => { H.sidebar().within(() => {
@@ -1033,9 +968,16 @@ describe("issue 49525", { tags: "@external" }, () => {
H.popover().findByText(`${first_name} ${last_name}`).click(); H.popover().findByText(`${first_name} ${last_name}`).click();
// Click this just to close the popover that is blocking the "Send email now" button
H.sidebar().findByText("To:").click();
H.clickSend();
cy.request("GET", `http://localhost:${WEB_PORT}/email`).then(({ body }) => {
cy.wrap(body.slice(-1)[0].id).as("plainEmailId");
});
H.sidebar().button("Send email now", 10000).should("be.enabled");
H.sidebar().within(() => { H.sidebar().within(() => {
// Click this just to close the popover that is blocking the "Send email now" button
cy.findByText("To:").click();
cy.findByLabelText("Attach results") cy.findByLabelText("Attach results")
.should("not.be.checked") .should("not.be.checked")
.click({ force: true }); // Input is placed behind the lable due to tooltip in label .click({ force: true }); // Input is placed behind the lable due to tooltip in label
@@ -1043,7 +985,10 @@ describe("issue 49525", { tags: "@external" }, () => {
cy.findByText("Questions to attach").click(); cy.findByText("Questions to attach").click();
}); });
H.sendEmailAndAssert((email) => { H.clickSend();
cy.request("GET", `http://localhost:${WEB_PORT}/email`).then(({ body }) => {
const email = body.slice(-1)[0];
// Get the CSV attachment data // Get the CSV attachment data
const csvAttachment = email.attachments.find( const csvAttachment = email.attachments.find(
(attachment) => attachment.contentType === "text/csv", (attachment) => attachment.contentType === "text/csv",
@@ -1067,22 +1012,15 @@ describe("issue 49525", { tags: "@external" }, () => {
); );
}); });
}); });
});
it("renders the pivot table inline in the subscription email body (UXW-4378)", () => { H.sidebar().button("Done").click();
H.toggleDashboardSubscriptionsSidebar(); H.openPulseSubscription();
H.sidebar().within(() => { H.sidebar().findByLabelText("Keep the data pivoted").should("be.checked");
cy.findByText("Email it").click();
cy.findByPlaceholderText("Enter user names or email addresses").click(); cy.get("@plainEmailId").then((emailId) => {
cy.visit(`http://localhost:${WEB_PORT}/email/${emailId}/html`);
}); });
H.popover().findByText(`${first_name} ${last_name}`).click();
// Close the recipient popover so the send button is clickable
H.sidebar().findByText("To:").click();
H.sendEmailAndVisitIt();
cy.get(".container").within(() => { cy.get(".container").within(() => {
// Transposed pivot: row dimension as the top-left label, categories across the top, // Transposed pivot: row dimension as the top-left label, categories across the top,
// a grand-totals column appended, and no internal pivot-grouping column. // a grand-totals column appended, and no internal pivot-grouping column.
@@ -1117,19 +1055,32 @@ describe("issue 54603", () => {
type: "category", type: "category",
}; };
const VENDOR_FILTER_PARAMETER = {
id: "54603-vendor",
name: "Vendor",
slug: "vendor",
type: "category",
};
const PRODUCT_CATEGORY_FIELD_REF = [ const PRODUCT_CATEGORY_FIELD_REF = [
"field", "field",
PRODUCTS.CATEGORY, PRODUCTS.CATEGORY,
{ "base-type": "type/Text", "source-field": ORDERS.PRODUCT_ID }, { "base-type": "type/Text", "source-field": ORDERS.PRODUCT_ID },
]; ];
const PRODUCT_VENDOR_FIELD_REF = [
"field",
PRODUCTS.VENDOR,
{ "base-type": "type/Text", "source-field": ORDERS.PRODUCT_ID },
];
beforeEach(() => { beforeEach(() => {
H.restore(); H.restore();
cy.signInAsAdmin(); cy.signInAsAdmin();
H.createDashboard({ H.createDashboard({
name: "Dashboard 54603", name: "Dashboard 54603",
parameters: [FILTER_PARAMETER], parameters: [FILTER_PARAMETER, VENDOR_FILTER_PARAMETER],
}).then(({ body: dashboard }) => { }).then(({ body: dashboard }) => {
H.updateDashboardCards({ H.updateDashboardCards({
dashboard_id: dashboard.id, dashboard_id: dashboard.id,
@@ -1147,6 +1098,15 @@ describe("issue 54603", () => {
{ "stage-number": 0 }, { "stage-number": 0 },
], ],
}, },
{
parameter_id: VENDOR_FILTER_PARAMETER.id,
card_id: ORDERS_QUESTION_ID,
target: [
"dimension",
PRODUCT_VENDOR_FIELD_REF,
{ "stage-number": 0 },
],
},
], ],
}), }),
], ],
@@ -1177,9 +1137,25 @@ describe("issue 54603", () => {
}); });
}); });
it("warns before removing a filter that has active subscriptions (metabase#54603)", () => { it("removes a filter without subscriptions right away and warns before removing a filter that has active subscriptions (metabase#54603)", () => {
H.editDashboard(); H.editDashboard();
cy.findByTestId("fixed-width-filters").findByText("Vendor").click();
H.dashboardParameterSidebar()
.findByRole("button", { name: "Remove" })
.should("be.enabled")
.click();
// No modal — the filter is gone right away (sidebar closes too).
cy.findByRole("dialog", { name: /remove this filter/i }).should(
"not.exist",
);
cy.findByTestId("dashboard-parameter-sidebar").should("not.exist");
cy.findByTestId("fixed-width-filters").within(() => {
cy.findByText("Category").should("be.visible");
cy.findByText("Vendor").should("not.exist");
});
cy.findByTestId("fixed-width-filters").findByText("Category").click(); cy.findByTestId("fixed-width-filters").findByText("Category").click();
// The Remove button stays disabled until the subscriptions query resolves; // The Remove button stays disabled until the subscriptions query resolves;
@@ -1189,6 +1165,9 @@ describe("issue 54603", () => {
.should("be.enabled") .should("be.enabled")
.click(); .click();
cy.findByRole("dialog", { name: /remove this filter/i }).should(
"be.visible",
);
H.modal().within(() => { H.modal().within(() => {
cy.findByText("Remove this filter?").should("be.visible"); cy.findByText("Remove this filter?").should("be.visible");
cy.findByText(/active subscription/i).should("be.visible"); cy.findByText(/active subscription/i).should("be.visible");
@@ -1217,32 +1196,8 @@ describe("issue 54603", () => {
H.saveDashboard(); H.saveDashboard();
H.toggleDashboardSubscriptionsSidebar(); H.toggleDashboardSubscriptionsSidebar();
H.sidebar().findByText("Weekly Category Roundup").should("not.exist"); H.sidebar()
}); .findByText("Set up a dashboard subscription")
.should("be.visible");
it("removes a filter immediately when no subscription references it (metabase#54603)", () => {
// Replace the existing subscription with one that does NOT include the filter.
cy.request("GET", "/api/pulse").then(({ body: pulses }) => {
pulses.forEach((pulse) => {
cy.request("PUT", `/api/pulse/${pulse.id}`, {
...pulse,
parameters: [],
});
});
});
H.editDashboard();
cy.findByTestId("fixed-width-filters").findByText("Category").click();
H.dashboardParameterSidebar()
.findByRole("button", { name: "Remove" })
.should("be.enabled")
.click();
// No modal — the filter is gone right away (sidebar closes too).
cy.findByRole("dialog", { name: /remove this filter/i }).should(
"not.exist",
);
cy.findByTestId("dashboard-parameter-sidebar").should("not.exist");
}); });
}); });

View File

@@ -13,51 +13,52 @@ describe("scenarios > dashboard > subscriptions", () => {
cy.signInAsAdmin(); cy.signInAsAdmin();
}); });
it("should allow sharing if there are no dashboard cards", () => { it("should allow sharing if there are no dashboard cards or only text cards (metabase#15077)", () => {
H.createDashboard().then(({ body: { id: DASHBOARD_ID } }) => { H.createDashboard().then(({ body: { id: DASHBOARD_ID } }) => {
H.visitDashboard(DASHBOARD_ID); H.visitDashboard(DASHBOARD_ID);
}); });
cy.findByLabelText("subscriptions").should("not.exist"); H.dashboardHeader().within(() => {
H.sharingMenuButton().should("be.visible");
cy.findByTestId("dashboard-subscriptions-button").should("not.exist");
});
H.openSharingMenu(/public link/i); H.openSharingMenu();
H.sharingMenu()
.findByText(/public link/i)
.click();
cy.findByTestId("public-link-popover-content").should("be.visible"); cy.findByTestId("public-link-popover-content").should("be.visible");
H.openSharingMenu("Embed"); H.openSharingMenu("Embed");
H.embedModalContent().should("be.visible"); H.embedModalContent().should("be.visible");
});
it("should allow sharing if dashboard contains only text cards (metabase#15077)", () => { H.createDashboard({ name: "Text only dashboard" }).then(
H.createDashboard().then(({ body: { id: DASHBOARD_ID } }) => { ({ body: { id: DASHBOARD_ID } }) => {
H.visitDashboard(DASHBOARD_ID); H.visitDashboard(DASHBOARD_ID);
}); },
);
H.addTextBox("Foo"); H.addTextBox("Foo");
cy.button("Save").click(); cy.button("Save").click();
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage // eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("You're editing this dashboard.").should("not.exist"); cy.findByText("You're editing this dashboard.").should("not.exist");
H.openSharingMenu();
// Dashboard subscriptions are not shown because // Dashboard subscriptions are not shown because
// getting notifications with static text-only cards doesn't make a lot of sense // getting notifications with static text-only cards doesn't make a lot of sense
H.sharingMenu().findByText("subscriptions").should("not.exist"); H.dashboardHeader().within(() => {
H.sharingMenuButton().should("be.visible");
cy.findByTestId("dashboard-subscriptions-button").should("not.exist");
});
H.openSharingMenu();
H.sharingMenu().within(() => { H.sharingMenu().within(() => {
cy.findByText("Create a public link").should("be.visible"); cy.findByText("Create a public link").should("be.visible");
cy.findByText("Embed").should("be.visible"); cy.findByText("Embed").should("be.visible");
}); });
}); });
describe("sidebar toggling behavior", () => {
it("should allow toggling the sidebar", () => {
openDashboardSubscriptions();
// The sidebar starts open after the method there, so test that clicking the icon closes it
H.toggleDashboardSubscriptionsSidebar();
H.sidebar().should("not.exist");
});
});
describe("with no channels set up", () => { describe("with no channels set up", () => {
it("should instruct user to connect email or slack", () => { it("should instruct user to connect email or slack and allow closing the sidebar", () => {
openDashboardSubscriptions(); openDashboardSubscriptions();
// Look for the messaging about configuring slack and email // Look for the messaging about configuring slack and email
cy.findByRole("link", { name: /set up email/i }).should( cy.findByRole("link", { name: /set up email/i }).should(
@@ -70,16 +71,61 @@ describe("scenarios > dashboard > subscriptions", () => {
"href", "href",
"/admin/settings/slack", "/admin/settings/slack",
); );
H.sidebar().should("be.visible");
H.toggleDashboardSubscriptionsSidebar();
H.sidebar().should("not.exist");
}); });
}); });
it("should show errors on the unsubscribe page for invalid links", () => {
const nonUserEmail = "non-user@example.com";
cy.visit({
url: "/unsubscribe",
qs: {
hash: "459a8e9f8d9e",
email: nonUserEmail,
}, // missing pulse-id
});
cy.findByLabelText("error page").should("exist");
cy.visit({
url: "/unsubscribe",
qs: {
hash: "459a8e9f8d9e",
email: nonUserEmail,
"pulse-id": "f", // invalid pulse-id
},
});
cy.findByLabelText("error message").should("exist");
});
describe("with email set up", { tags: "@external" }, () => { describe("with email set up", { tags: "@external" }, () => {
beforeEach(() => { beforeEach(() => {
H.setupSMTP(); H.setupSMTP();
}); });
it("renders an object detail as a label/value table in a subscription email", () => { it("renders object detail, month-of-year line, and region map cards in a subscription email (metabase#16918)", () => {
const questionDetails = { const regionMapDetails = {
name: "Region map static-viz smoke",
native: {
query:
"SELECT 'CA' AS state, 99999 AS metric " +
"UNION ALL SELECT 'NY' AS state, 11111 AS metric",
},
display: "map",
visualization_settings: {
"map.type": "region",
"map.region": "us_states",
"map.dimension": "STATE",
"map.metric": "METRIC",
},
};
const objectDetailDetails = {
name: "Object detail static-viz smoke", name: "Object detail static-viz smoke",
native: { native: {
query: "SELECT 'Hammer' AS product, 19 AS price, NULL AS discount", query: "SELECT 'Hammer' AS product, 19 AS price, NULL AS discount",
@@ -87,11 +133,32 @@ describe("scenarios > dashboard > subscriptions", () => {
display: "object", display: "object",
}; };
H.createNativeQuestionAndDashboard({ questionDetails }).then( const monthOfYearDetails = {
({ dashboardId }) => { name: "16918",
H.visitDashboard(dashboardId); query: {
"source-table": PRODUCTS_ID,
aggregation: [["count"]],
breakout: [
[
"field",
PRODUCTS.CREATED_AT,
{ "temporal-unit": "month-of-year" },
],
["field", PRODUCTS.CATEGORY, null],
],
}, },
); display: "line",
};
const dashboardName = "Repro Dashboard";
H.createDashboardWithQuestions({
dashboardName,
questions: [regionMapDetails, objectDetailDetails, monthOfYearDetails],
cards: [{ row: 0 }, { row: 8 }, { row: 16 }],
}).then(({ dashboard }) => {
H.visitDashboard(dashboard.id);
});
H.openAndAddEmailsToSubscriptions([ H.openAndAddEmailsToSubscriptions([
`${admin.first_name} ${admin.last_name}`, `${admin.first_name} ${admin.last_name}`,
@@ -101,36 +168,63 @@ describe("scenarios > dashboard > subscriptions", () => {
expect(html).not.to.include( expect(html).not.to.include(
"An error occurred while displaying this card.", "An error occurred while displaying this card.",
); );
expect(html).to.include("Hammer"); expect(html).to.include(dashboardName);
const regionMapStart = html.indexOf(regionMapDetails.name);
const objectDetailStart = html.indexOf(objectDetailDetails.name);
const monthOfYearStart = html.indexOf(monthOfYearDetails.name);
expect(regionMapStart).to.be.greaterThan(-1);
expect(objectDetailStart).to.be.greaterThan(regionMapStart);
expect(monthOfYearStart).to.be.greaterThan(objectDetailStart);
const regionMapSection = html.slice(regionMapStart, objectDetailStart);
const objectDetailSection = html.slice(
objectDetailStart,
monthOfYearStart,
);
expect(regionMapSection).to.match(
/<img src="cid:[^"]+" style="display: block; width: 100%;"/,
);
// The map rasterizes to a PNG; a table fallback would instead leak these values as text.
expect(html).not.to.include("99999");
expect(html).not.to.include("11111");
expect(html).not.to.include("99,999");
expect(html).not.to.include("11,111");
expect(objectDetailSection).to.include("Hammer");
// "Empty" (the null column) is unique to the :object renderer — a table fallback leaves it blank. // "Empty" (the null column) is unique to the :object renderer — a table fallback leaves it blank.
expect(html).to.include("Empty"); expect(objectDetailSection).to.include("Empty");
}); });
}); });
describe("with no existing subscriptions", () => { describe("with no existing subscriptions", () => {
it("should not enable subscriptions without the recipient (metabase#17657)", () => { it("should not enable subscriptions without the recipient and still send a one-off email after the frequency change clears the time (metabase#17657)", () => {
openDashboardSubscriptions(); openDashboardSubscriptions();
H.sidebar().findByText("Email it").click(); H.sidebar().findByText("Email it").click();
// Make sure no recipients have been assigned // Make sure no recipients have been assigned
cy.findByPlaceholderText("Enter user names or email addresses"); cy.findByPlaceholderText("Enter user names or email addresses").should(
"be.visible",
);
// Change the schedule to "Monthly" // Change the schedule to "Monthly"
cy.findByTestId("select-frequency").click(); cy.findByTestId("select-frequency").click();
H.popover().findByText("monthly").click(); H.popover().findByText("monthly").click();
H.selectScheduleTime();
H.sidebar().button("Done").should("be.disabled"); H.sidebar().button("Done").should("be.disabled");
});
it("should allow creation of a new email subscription", () => { cy.findByPlaceholderText("Enter user names or email addresses").click();
createEmailSubscription(); H.popover().contains(admin.first_name).click();
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage cy.realPress("Escape");
cy.findByText("Emailed hourly");
});
it("should still send a one-off email after the frequency change clears the time", () => { H.sidebar().button("Done").should("be.enabled");
assignRecipient();
cy.findByTestId("select-frequency").click();
H.popover().findByText("hourly").click();
H.sidebar().button("Done").should("be.enabled");
cy.findByTestId("select-frequency").click(); cy.findByTestId("select-frequency").click();
H.popover().findByText("weekly").click(); H.popover().findByText("weekly").click();
@@ -142,41 +236,37 @@ describe("scenarios > dashboard > subscriptions", () => {
H.getInbox(1).its("body").should("have.length", 1); H.getInbox(1).its("body").should("have.length", 1);
}); });
it("should not add a recipient when Escape is pressed (metabase#24629)", () => { it("should keep the people dropdown on screen and not add a recipient when Escape is pressed (metabase#17186, metabase#24629)", () => {
Cypress._.times(30, (index) => {
cy.request("POST", "/api/user", {
first_name: `Recipient${index}`,
last_name: "Example",
email: `recipient${index}@metabase.test`,
});
});
openDashboardSubscriptions(ORDERS_DASHBOARD_ID); openDashboardSubscriptions(ORDERS_DASHBOARD_ID);
H.sidebar().findByText("Email it").click(); H.sidebar().findByText("Email it").click();
cy.findByPlaceholderText("Enter user names or email addresses").click(); cy.findByPlaceholderText("Enter user names or email addresses").click();
H.popover().should("be.visible").and("contain", `${admin.first_name}`); H.popover().should("be.visible").and("contain", `${admin.first_name}`);
H.popover().isRenderedWithinViewport();
cy.findByPlaceholderText("Enter user names or email addresses").type(
admin.first_name,
);
H.popover().should("contain", `${admin.first_name}`);
cy.realPress("Escape"); cy.realPress("Escape");
H.popover({ skipVisibilityCheck: true }).should("not.be.visible"); H.popover({ skipVisibilityCheck: true }).should("not.be.visible");
cy.findByPlaceholderText("Enter user names or email addresses").should( cy.findByPlaceholderText("Enter user names or email addresses")
"not.have.value", .should("be.visible")
); .and("have.value", admin.first_name);
H.sidebar().button("Done").should("be.disabled");
cy.findByTestId("token-field-popover").should("not.exist");
});
it("should not render people dropdown outside of the borders of the screen (metabase#17186)", () => {
openDashboardSubscriptions();
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("Email it").click();
cy.findByPlaceholderText("Enter user names or email addresses").click();
H.popover().isRenderedWithinViewport();
}); });
}); });
describe("with existing subscriptions", () => { describe("with existing subscriptions", () => {
it("should show existing dashboard subscriptions", () => {
createEmailSubscription();
openDashboardSubscriptions();
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("Emailed hourly");
});
it("should forward non-admin users to add email form when clicking add", () => { it("should forward non-admin users to add email form when clicking add", () => {
cy.signInAsNormalUser(); cy.signInAsNormalUser();
@@ -194,7 +284,7 @@ describe("scenarios > dashboard > subscriptions", () => {
}); });
}); });
it("should send as BCC by default", () => { it("should send as BCC by default and as CC when opted-in", () => {
const ORDERS_DASHBOARD_NAME = "Orders in a dashboard"; const ORDERS_DASHBOARD_NAME = "Orders in a dashboard";
assignRecipients(); assignRecipients();
@@ -209,17 +299,15 @@ describe("scenarios > dashboard > subscriptions", () => {
cy.findByText(`${admin.email}`).should("exist"); cy.findByText(`${admin.email}`).should("exist");
cy.findByText(`${normal.email}`).should("exist"); cy.findByText(`${normal.email}`).should("exist");
}); });
});
it("should send as CC when opted-in", () => { H.clearInbox();
// opt-in to CC // opt-in to CC
cy.visit("/admin/settings/email"); cy.visit("/admin/settings/email");
cy.findByTestId("bcc-enabled?-setting") cy.findByTestId("bcc-enabled?-setting")
.findByLabelText("CC - Disclose recipients") .findByLabelText("CC - Disclose recipients")
.click(); .click();
const ORDERS_DASHBOARD_NAME = "Orders in a dashboard";
assignRecipients(); assignRecipients();
H.sidebar().within(() => { H.sidebar().within(() => {
H.clickSend(); H.clickSend();
@@ -228,40 +316,15 @@ describe("scenarios > dashboard > subscriptions", () => {
H.viewEmailPage(ORDERS_DASHBOARD_NAME); H.viewEmailPage(ORDERS_DASHBOARD_NAME);
cy.get(".main-container").within(() => { cy.get(".main-container").within(() => {
cy.findByText("Bcc:").should("not.exist");
cy.findByText(`${admin.email}`).should("exist"); cy.findByText(`${admin.email}`).should("exist");
cy.findByText(`${normal.email}`).should("exist"); cy.findByText(`${normal.email}`).should("exist");
cy.findByText("Bcc:").should("not.exist");
}); });
}); });
}); });
describe("let non-users unsubscribe from subscriptions", () => { describe("let non-users unsubscribe from subscriptions", () => {
it("should allow non-user to unsubscribe from subscription", () => { it("should allow non-user to unsubscribe and undo-unsubscribe from subscription", () => {
const nonUserEmail = "non-user@example.com";
const dashboardName = "Orders in a dashboard";
H.visitDashboard(ORDERS_DASHBOARD_ID);
H.setupSubscriptionWithRecipients([nonUserEmail]);
H.emailSubscriptionRecipients();
H.openEmailPage(dashboardName).then(() => {
cy.intercept("/api/pulse/unsubscribe").as("unsubscribe");
cy.findByText("Unsubscribe").click();
cy.wait("@unsubscribe");
cy.contains(
`You've unsubscribed ${nonUserEmail} from the "${dashboardName}" alert.`,
).should("exist");
});
openDashboardSubscriptions();
H.openPulseSubscription();
H.sidebar().findByText(nonUserEmail).should("not.exist");
});
it("should allow non-user to undo-unsubscribe from subscription", () => {
const nonUserEmail = "non-user@example.com"; const nonUserEmail = "non-user@example.com";
const dashboardName = "Orders in a dashboard"; const dashboardName = "Orders in a dashboard";
H.visitDashboard(ORDERS_DASHBOARD_ID); H.visitDashboard(ORDERS_DASHBOARD_ID);
@@ -271,8 +334,14 @@ describe("scenarios > dashboard > subscriptions", () => {
H.emailSubscriptionRecipients(); H.emailSubscriptionRecipients();
H.openEmailPage(dashboardName).then(() => { H.openEmailPage(dashboardName).then(() => {
cy.intercept("/api/pulse/unsubscribe").as("unsubscribe"); cy.intercept({
cy.intercept("/api/pulse/unsubscribe/undo").as("resubscribe"); method: "POST",
pathname: "/api/pulse/unsubscribe",
}).as("unsubscribe");
cy.intercept({
method: "POST",
pathname: "/api/pulse/unsubscribe/undo",
}).as("resubscribe");
cy.findByText("Unsubscribe").click(); cy.findByText("Unsubscribe").click();
cy.wait("@unsubscribe"); cy.wait("@unsubscribe");
@@ -293,44 +362,47 @@ describe("scenarios > dashboard > subscriptions", () => {
H.openPulseSubscription(); H.openPulseSubscription();
H.sidebar().findByText(nonUserEmail).should("exist"); H.sidebar().findByText(nonUserEmail).should("exist");
});
it("should show 404 page when missing required parameters", () => { H.openEmailPage(dashboardName).then(() => {
const nonUserEmail = "non-user@example.com"; cy.findByText("Unsubscribe").click();
cy.wait("@unsubscribe");
const params = { cy.contains(
hash: "459a8e9f8d9e", `You've unsubscribed ${nonUserEmail} from the "${dashboardName}" alert.`,
email: nonUserEmail, ).should("exist");
}; // missing pulse-id
cy.visit({
url: "/unsubscribe",
qs: params,
}); });
cy.findByLabelText("error page").should("exist"); openDashboardSubscriptions();
}); H.openPulseSubscription();
it("should show error message when server responds with an error", () => { H.sidebar().findByText("Delete this subscription").should("exist");
const nonUserEmail = "non-user@example.com"; H.sidebar().findByText(nonUserEmail).should("not.exist");
const params = {
hash: "459a8e9f8d9e",
email: nonUserEmail,
"pulse-id": "f", // invalid pulse-id
};
cy.visit({
url: "/unsubscribe",
qs: params,
});
cy.findByLabelText("error message").should("exist");
}); });
}); });
it("should persist attachments for dashboard subscriptions (metabase#14117)", () => { it("should clean the new subscription form on cancel and persist attachments for dashboard subscriptions (metabase#30314, metabase#14117)", () => {
assignRecipient(); openDashboardSubscriptions();
H.sidebar().within(() => {
cy.findByText("Email it").click();
cy.findByLabelText("Attach results")
.should("not.be.checked")
.click({ force: true }); // Input is placed behind the lable due to tooltip in label
cy.findByLabelText("Questions to attach")
.should("not.be.checked")
.click();
cy.button("Cancel").click();
cy.findByText("Email it").click();
cy.findByLabelText("Attach results").should("not.be.checked");
cy.findByText("Questions to attach").should("not.exist");
cy.findByText(".xlsx").should("not.exist");
cy.findByText(".csv").should("not.exist");
});
cy.findByPlaceholderText("Enter user names or email addresses").click();
H.popover().contains(admin.first_name).click();
cy.realPress("Escape");
H.sidebar().within(() => { H.sidebar().within(() => {
cy.findByLabelText("Attach results") cy.findByLabelText("Attach results")
@@ -492,72 +564,6 @@ describe("scenarios > dashboard > subscriptions", () => {
expect(email.html).to.include(TEXT_CARD); expect(email.html).to.include(TEXT_CARD);
}); });
}); });
it('should load question binned by "Month of year" or similar granularity (metabase#16918)', () => {
const questionDetails = {
name: "16918",
query: {
"source-table": PRODUCTS_ID,
aggregation: [["count"]],
breakout: [
[
"field",
PRODUCTS.CREATED_AT,
{ "temporal-unit": "month-of-year" },
],
["field", PRODUCTS.CATEGORY, null],
],
},
display: "line",
};
const dashboardDetails = { name: "Repro Dashboard" };
H.createQuestionAndDashboard({ questionDetails, dashboardDetails }).then(
({ body: { dashboard_id } }) => {
assignRecipient({ dashboard_id });
},
);
H.sendEmailAndAssert((email) => {
expect(email.html).to.include(dashboardDetails.name);
expect(email.html).to.include(questionDetails.name);
});
});
it("renders a region (choropleth) map as an image in a subscription email", () => {
const questionDetails = {
name: "Region map static-viz smoke",
native: {
query:
"SELECT 'CA' AS state, 99999 AS metric " +
"UNION ALL SELECT 'NY' AS state, 11111 AS metric",
},
display: "map",
visualization_settings: {
"map.type": "region",
"map.region": "us_states",
"map.dimension": "STATE",
"map.metric": "METRIC",
},
};
H.createNativeQuestionAndDashboard({ questionDetails }).then(
({ dashboardId }) => {
assignRecipient({ dashboard_id: dashboardId });
},
);
H.sendEmailAndAssert(({ html }) => {
expect(html).not.to.include(
"An error occurred while displaying this card.",
);
// The map rasterizes to a PNG <img>; a table fallback would instead leak these values as text.
expect(html).to.include("<img");
expect(html).not.to.include("99999");
expect(html).not.to.include("11111");
});
});
}); });
describe("with Slack set up", () => { describe("with Slack set up", () => {
@@ -565,48 +571,37 @@ describe("scenarios > dashboard > subscriptions", () => {
H.mockSlackConfigured(); H.mockSlackConfigured();
}); });
it("should not enable 'Done' button before channel is selected (metabase#14494)", () => { it("should allow non-admin users to create subscriptions", () => {
openSlackCreationForm(); cy.signInAsNormalUser();
H.visitDashboard(ORDERS_DASHBOARD_ID);
cy.findAllByRole("button", { name: "Done" }).should("be.disabled"); H.toggleDashboardSubscriptionsSidebar();
cy.findByPlaceholderText("Pick a user or channel...").click(); H.sidebar()
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage .findByText("Send this dashboard to Slack")
cy.findByText("#work").click(); .should("be.visible");
cy.findAllByRole("button", { name: "Done" }).should("not.be.disabled");
}); });
it("should have 'Send to Slack now' button (metabase#14515)", () => { it("should enable 'Done' and 'Send to Slack now' only after a channel is selected and persist the immutable Slack channel_id (metabase#14494, metabase#14515)", () => {
cy.intercept("POST", "/api/pulse").as("createPulse");
openSlackCreationForm(); openSlackCreationForm();
H.sidebar().within(() => { H.sidebar().within(() => {
cy.findAllByRole("button", { name: "Send to Slack now" }).should( cy.findByRole("button", { name: "Done" }).should("be.disabled");
cy.findByRole("button", { name: "Send to Slack now" }).should(
"be.disabled", "be.disabled",
); );
cy.findByPlaceholderText("Pick a user or channel...").click(); cy.findByPlaceholderText("Pick a user or channel...").click();
}); });
H.popover().findByText("#work").click(); H.popover().findByText("#work").click();
H.sidebar()
.findAllByRole("button", { name: "Done" })
.should("not.be.disabled");
});
it("should allow non-admin users to create subscriptions", () => { H.sidebar().within(() => {
cy.signInAsNormalUser(); cy.findByRole("button", { name: "Done" }).should("be.enabled");
H.visitDashboard(ORDERS_DASHBOARD_ID); cy.findByRole("button", { name: "Send to Slack now" }).should(
H.toggleDashboardSubscriptionsSidebar(); "be.enabled",
H.sidebar().should("be.visible"); );
}); cy.findByRole("button", { name: "Done" }).click();
});
it("should persist the immutable Slack channel_id alongside the channel name", () => {
cy.intercept("POST", "/api/pulse").as("createPulse");
openSlackCreationForm();
cy.findByPlaceholderText("Pick a user or channel...").click();
H.popover().findByText("#work").click();
H.sidebar().findByRole("button", { name: "Done" }).click();
cy.wait("@createPulse").then(({ request: { body } }) => { cy.wait("@createPulse").then(({ request: { body } }) => {
// The mocked channel `#work` has id `C001` in e2e-slack-helpers.js. // The mocked channel `#work` has id `C001` in e2e-slack-helpers.js.
@@ -621,10 +616,9 @@ describe("scenarios > dashboard > subscriptions", () => {
}); });
}); });
describe("OSS email subscriptions", { tags: ["@OSS", "external"] }, () => { describe("OSS email subscriptions", { tags: ["@OSS", "@external"] }, () => {
beforeEach(() => { beforeEach(() => {
H.setupSMTP(); H.setupSMTP();
cy.visit(`/dashboard/${ORDERS_DASHBOARD_ID}`);
}); });
it("should include branding", () => { it("should include branding", () => {
@@ -645,6 +639,7 @@ describe("scenarios > dashboard > subscriptions", () => {
describe("with parameters", () => { describe("with parameters", () => {
beforeEach(() => { beforeEach(() => {
cy.visit(`/dashboard/${ORDERS_DASHBOARD_ID}`);
addParametersToDashboard(); addParametersToDashboard();
}); });
@@ -710,52 +705,22 @@ describe("scenarios > dashboard > subscriptions", () => {
beforeEach(() => { beforeEach(() => {
H.activateToken("pro-self-hosted"); H.activateToken("pro-self-hosted");
H.setupSMTP(); H.setupSMTP();
cy.visit(`/dashboard/${ORDERS_DASHBOARD_ID}`);
}); });
it("should not include branding", () => { it("should show recipients in dropdown based on the `user-visiblity` setting", () => {
assignRecipient();
H.sendEmailAndVisitIt();
cy.findAllByRole("link")
.filter(":contains(Orders in a dashboard)")
.should("be.visible");
cy.findAllByRole("link")
.filter(":contains(Made with)")
.should("not.exist");
});
it("should only show current user in recipients dropdown if `user-visiblity` setting is `none`", () => {
openRecipientsWithUserVisibilitySetting("none"); openRecipientsWithUserVisibilitySetting("none");
H.popover().find("span").should("have.length", 1); H.popover().find("span").should("have.length", 1);
});
it("should only show users in same group in recipients dropdown if `user-visiblity` setting is `group`", () => { cy.signInAsAdmin();
openRecipientsWithUserVisibilitySetting("group"); openRecipientsWithUserVisibilitySetting("group");
H.popover().find("span").should("have.length", 5); H.popover().find("span").should("have.length", 5);
});
it("should show all users in recipients dropdown if `user-visiblity` setting is `all`", () => { cy.signInAsAdmin();
openRecipientsWithUserVisibilitySetting("all"); openRecipientsWithUserVisibilitySetting("all");
H.popover().find("span").should("have.length", 10); H.popover().find("span").should("have.length", 10);
}); });
describe("with no parameters", () => { it("should send a dashboard with questions saved in the dashboard and without branding", () => {
it("should have no parameters section", () => {
openDashboardSubscriptions();
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("Email it").click();
// eslint-disable-next-line metabase/no-unscoped-text-selectors -- deprecated usage
cy.findByText("Set filter values for when this gets sent").should(
"not.exist",
);
});
});
it("should send a dashboard with questions saved in the dashboard", () => {
H.createQuestion({ H.createQuestion({
name: "Total Orders", name: "Total Orders",
database_id: SAMPLE_DATABASE.id, database_id: SAMPLE_DATABASE.id,
@@ -770,6 +735,13 @@ describe("scenarios > dashboard > subscriptions", () => {
assignRecipient(); assignRecipient();
H.sendEmailAndVisitIt(); H.sendEmailAndVisitIt();
cy.findAllByRole("link")
.filter(":contains(Orders in a dashboard)")
.should("be.visible");
cy.findAllByRole("link")
.filter(":contains(Made with)")
.should("not.exist");
cy.get(".container").within(() => { cy.get(".container").within(() => {
cy.findByText("Total Orders"); cy.findByText("Total Orders");
// the scalar counts all orders, but the body-only Orders table is // the scalar counts all orders, but the body-only Orders table is
@@ -782,6 +754,7 @@ describe("scenarios > dashboard > subscriptions", () => {
describe("with parameters", () => { describe("with parameters", () => {
beforeEach(() => { beforeEach(() => {
cy.visit(`/dashboard/${ORDERS_DASHBOARD_ID}`);
addParametersToDashboard(); addParametersToDashboard();
}); });
@@ -880,24 +853,43 @@ describe("scenarios > dashboard > subscriptions", () => {
}); });
}); });
describe("with unconnected parameters", () => { describe("with no or unconnected parameters", () => {
it("should show only connected parameters in subscription sidebar", () => { it("should show only connected parameters in subscription sidebar", () => {
addConnectedAndUnconnectedParameterToDashboard(); openDashboardSubscriptions();
openDashboardSubscriptions(ORDERS_DASHBOARD_ID);
H.sidebar().findByText("Email it").click();
H.sidebar().findByText("Text 1").should("not.exist");
});
it("should not show filters section in subscription sidebar with no connected parameters", () => {
H.editDashboard();
setTextFilter();
openDashboardSubscriptions(ORDERS_DASHBOARD_ID);
H.sidebar().findByText("Email it").click(); H.sidebar().findByText("Email it").click();
H.sidebar().findByText("Email this dashboard").should("be.visible");
H.sidebar() H.sidebar()
.findByText("Set filter values for when this gets sent") .findByText("Set filter values for when this gets sent")
.should("not.exist"); .should("not.exist");
H.visitDashboard(ORDERS_DASHBOARD_ID);
H.editDashboard();
setTextFilter();
H.saveDashboard();
openDashboardSubscriptions();
H.sidebar().findByText("Email it").click();
H.sidebar().findByText("Email this dashboard").should("be.visible");
H.sidebar()
.findByText("Set filter values for when this gets sent")
.should("not.exist");
H.visitDashboard(ORDERS_DASHBOARD_ID);
H.editDashboard();
setTextFilter();
H.getDashboardCard().findByText("Select…").click();
H.popover().findByText("Name").click();
H.saveDashboard();
openDashboardSubscriptions();
H.sidebar().findByText("Email it").click();
cy.findByTestId("subscription-parameters-section").within(() => {
cy.findByText("Set filter values for when this gets sent").should(
"be.visible",
);
cy.findByText("Text 1").should("be.visible");
cy.findByText("Text").should("not.exist");
});
}); });
}); });
@@ -1015,21 +1007,6 @@ function addParametersToDashboard() {
cy.contains("You're editing this dashboard.").should("not.exist"); cy.contains("You're editing this dashboard.").should("not.exist");
} }
function addConnectedAndUnconnectedParameterToDashboard() {
H.editDashboard();
setTextFilter();
cy.findByText("Select…").click();
H.popover().within(() => {
cy.findByText("Name").click();
});
setTextFilter();
cy.findByText("Save").click();
cy.contains("You're editing this dashboard.").should("not.exist");
}
function setTextFilter() { function setTextFilter() {
H.setFilter("Text or Category", "Is"); H.setFilter("Text or Category", "Is");
} }