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:
committed by
GitHub
parent
cc2db95954
commit
519834f9ee
@@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 });
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)", () => {
|
||||||
|
|||||||
@@ -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)", () => {
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -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");
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user