1945 lines
63 KiB
JavaScript
1945 lines
63 KiB
JavaScript
import { assoc } from "icepick";
|
||
import _ from "underscore";
|
||
|
||
const { H } = cy;
|
||
import { SAMPLE_DB_ID } from "e2e/support/cypress_data";
|
||
import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
|
||
import {
|
||
ORDERS_DASHBOARD_DASHCARD_ID,
|
||
ORDERS_DASHBOARD_ENTITY_ID,
|
||
ORDERS_DASHBOARD_ID,
|
||
ORDERS_QUESTION_ID,
|
||
} from "e2e/support/cypress_sample_instance_data";
|
||
import { GRID_WIDTH } from "metabase/lib/dashboard_grid";
|
||
import {
|
||
createMockVirtualCard,
|
||
createMockVirtualDashCard,
|
||
} from "metabase-types/api/mocks";
|
||
|
||
import { interceptPerformanceRoutes } from "../admin/performance/helpers/e2e-performance-helpers";
|
||
import {
|
||
adaptiveRadioButton,
|
||
durationRadioButton,
|
||
openSidebarCacheStrategyForm,
|
||
} from "../admin/performance/helpers/e2e-strategy-form-helpers";
|
||
|
||
const { ORDERS, ORDERS_ID, PRODUCTS, PEOPLE, PEOPLE_ID } = SAMPLE_DATABASE;
|
||
|
||
// There's a race condition when saving a dashboard
|
||
// and then immediately editing it again. After saving,
|
||
// we exit the edit mode and that can happen after
|
||
// `H.editDashboard` is called for some reason
|
||
const DASHBOARD_SAVE_WAIT_TIME = 450;
|
||
|
||
describe("scenarios > dashboard", () => {
|
||
beforeEach(() => {
|
||
H.restore();
|
||
cy.signInAsAdmin();
|
||
});
|
||
|
||
describe("create", () => {
|
||
it("new dashboard UI flow", { tags: "@smoke" }, () => {
|
||
cy.intercept("POST", "/api/dashboard").as("createDashboard");
|
||
cy.intercept("POST", "/api/card").as("createQuestion");
|
||
|
||
const dashboardName = "Dash A";
|
||
const dashboardDescription = "Fresh new dashboard";
|
||
const newQuestionName = "New dashboard question";
|
||
|
||
cy.visit("/");
|
||
H.appBar().findByText("New").click();
|
||
H.popover().findByText("Dashboard").should("be.visible").click();
|
||
|
||
cy.log(
|
||
"pressing escape should only close the entity picker modal, not the new dashboard modal",
|
||
);
|
||
H.modal().findByTestId("collection-picker-button").click();
|
||
cy.realPress("Escape");
|
||
H.modal().findByText("New dashboard").should("be.visible");
|
||
|
||
cy.log("Create a new dashboard");
|
||
H.modal().within(() => {
|
||
// Without waiting for this, the test was constantly flaking locally.
|
||
cy.findByText("Our analytics");
|
||
|
||
cy.findByPlaceholderText(/name of your dashboard/i).type(dashboardName);
|
||
cy.findByLabelText("Description").type(dashboardDescription, {
|
||
delay: 0,
|
||
});
|
||
cy.button("Create").click();
|
||
});
|
||
|
||
cy.log("Router should immediately navigate to it");
|
||
cy.wait("@createDashboard").then(({ response: { body } }) => {
|
||
cy.location("pathname").should("contain", `/dashboard/${body.id}`);
|
||
});
|
||
|
||
cy.log("New dashboards are opened in editing mode by default");
|
||
cy.findByTestId("dashboard-empty-state").should(
|
||
"contain",
|
||
"Create a new question or browse your collections for an existing one.",
|
||
);
|
||
cy.findByTestId("edit-bar").findByText("You're editing this dashboard.");
|
||
|
||
cy.log(
|
||
"Should create new question from an empty dashboard (metabase#31848)",
|
||
);
|
||
cy.findByTestId("dashboard-empty-state").button("Add a chart").click();
|
||
cy.findByTestId("new-button-bar").findByText("New Question").click();
|
||
|
||
H.miniPickerBrowseAll().click();
|
||
H.entityPickerModal().within(() => {
|
||
cy.findByPlaceholderText("Search…").type("Pro");
|
||
cy.findByText("Products").click();
|
||
});
|
||
|
||
H.queryBuilderHeader().findByText("Save").click();
|
||
cy.findByTestId("save-question-modal").within((modal) => {
|
||
cy.findByLabelText("Name").clear().type(newQuestionName);
|
||
cy.findByLabelText("Where do you want to save this?").should(
|
||
"not.exist",
|
||
);
|
||
cy.findByText("Save").click();
|
||
});
|
||
cy.wait("@createQuestion");
|
||
|
||
H.openQuestionsSidebar();
|
||
H.sidebar().findByText("Orders, Count").click();
|
||
|
||
H.getDashboardCards().should("have.length", 2);
|
||
|
||
H.saveDashboard();
|
||
|
||
cy.log("Breadcrumbs should show a collection dashboard was saved in");
|
||
H.appBar().findByText("Our analytics").click();
|
||
|
||
cy.log("New dashboard question should not appear in the collection");
|
||
cy.findAllByTestId("collection-entry-name")
|
||
.should("contain", dashboardName)
|
||
.and("not.contain", newQuestionName);
|
||
});
|
||
|
||
it(
|
||
"should create new dashboard inside a collection created on the go",
|
||
// Increased height to avoid scrolling when opening a collection picker
|
||
{ viewportHeight: 1000 },
|
||
() => {
|
||
cy.intercept("POST", "api/collection").as("createCollection");
|
||
cy.visit("/");
|
||
cy.findByTestId("home-page").should(
|
||
"contain",
|
||
"Try out these sample x-rays to see what Metabase can do.",
|
||
);
|
||
H.closeNavigationSidebar();
|
||
H.appBar().findByText("New").click();
|
||
H.popover().findByText("Dashboard").should("be.visible").click();
|
||
const NEW_DASHBOARD = "Foo";
|
||
cy.findByTestId("new-dashboard-modal").then((modal) => {
|
||
cy.findByRole("heading", { name: "New dashboard" });
|
||
cy.findByLabelText("Name").type(NEW_DASHBOARD).blur();
|
||
cy.findByTestId("collection-picker-button")
|
||
.should("have.text", "Our analytics")
|
||
.click();
|
||
});
|
||
|
||
H.entityPickerModal()
|
||
.findByRole("tab", { name: /Collections/ })
|
||
.click();
|
||
H.entityPickerModal()
|
||
.findByText("New collection")
|
||
.click({ force: true });
|
||
const NEW_COLLECTION = "Bar";
|
||
H.collectionOnTheGoModal().within(() => {
|
||
cy.findByText("Create a new collection");
|
||
cy.findByPlaceholderText(/My new collection/)
|
||
.type(NEW_COLLECTION)
|
||
.blur();
|
||
cy.findByText("Create").click();
|
||
cy.wait("@createCollection");
|
||
});
|
||
H.entityPickerModal().within(() => {
|
||
cy.findByText(NEW_COLLECTION).click();
|
||
cy.button("Select").click();
|
||
});
|
||
H.modal().within(() => {
|
||
cy.findByText("New dashboard");
|
||
cy.findByTestId("collection-picker-button").should(
|
||
"have.text",
|
||
NEW_COLLECTION,
|
||
);
|
||
cy.button("Create").click();
|
||
});
|
||
|
||
H.saveDashboard({ awaitRequest: false });
|
||
cy.findByTestId("app-bar").findByText(NEW_COLLECTION);
|
||
},
|
||
);
|
||
|
||
it("adding question to one dashboard shouldn't affect previously visited unrelated dashboards (metabase#26826)", () => {
|
||
cy.intercept("POST", "/api/card").as("saveQuestion");
|
||
|
||
H.visitDashboard(ORDERS_DASHBOARD_ID);
|
||
|
||
cy.log("Save new question from an ad-hoc query");
|
||
H.openProductsTable();
|
||
cy.findByTestId("qb-header").findByText("Save").click();
|
||
cy.findByTestId("save-question-modal").within(() => {
|
||
cy.findByTestId("dashboard-and-collection-picker-button").click();
|
||
});
|
||
H.entityPickerModal().within(() => {
|
||
cy.findByText("First collection").click();
|
||
cy.findByText("Select this collection").click();
|
||
});
|
||
cy.findByTestId("save-question-modal").within(() => {
|
||
cy.findByText("Save").click();
|
||
});
|
||
cy.wait("@saveQuestion");
|
||
|
||
cy.log("Add this new question to a dashboard created on the fly");
|
||
|
||
H.checkSavedToCollectionQuestionToast(true);
|
||
|
||
H.entityPickerModal()
|
||
.findByRole("tab", { name: /Dashboards/ })
|
||
.click();
|
||
H.entityPickerModal().findByText("New dashboard").click();
|
||
cy.findByTestId("create-dashboard-on-the-go").within(() => {
|
||
cy.findByPlaceholderText("My new dashboard").type("Foo");
|
||
cy.findByText("Create").click();
|
||
});
|
||
H.entityPickerModal().button("Select").click();
|
||
|
||
cy.findByTestId("dashcard").should("be.visible");
|
||
H.saveDashboard();
|
||
|
||
cy.log(
|
||
"Find the originally visited (unrelated) dashboard in search and go to it",
|
||
);
|
||
|
||
H.commandPaletteButton().click();
|
||
H.commandPalette().within(() => {
|
||
cy.findByText("Recents").should("exist");
|
||
cy.findByRole("option", { name: "Orders in a dashboard" }).click();
|
||
});
|
||
|
||
cy.log("It should not contain an alien card from the other dashboard");
|
||
H.getDashboardCards().should("have.length", 1).and("contain", "37.65");
|
||
cy.log("It should not open in editing mode");
|
||
cy.findByTestId("edit-bar").should("not.exist");
|
||
});
|
||
});
|
||
|
||
describe("existing dashboard", () => {
|
||
const originalDashboardName = "Amazing Dashboard";
|
||
|
||
beforeEach(() => {
|
||
H.createDashboard({ name: originalDashboardName }).then(
|
||
({ body: { id } }) => {
|
||
H.visitDashboard(id);
|
||
},
|
||
);
|
||
});
|
||
|
||
context("add a question (dashboard card)", () => {
|
||
it("should be possible via questions sidebar", () => {
|
||
H.editDashboard();
|
||
H.openQuestionsSidebar();
|
||
|
||
cy.log("The list of saved questions");
|
||
H.sidebar().findByText("Orders, Count").click();
|
||
|
||
cy.log("The search component");
|
||
cy.intercept("GET", "/api/search*").as("search");
|
||
cy.findByPlaceholderText("Search…").type("Orders{enter}");
|
||
cy.wait("@search");
|
||
cy.findByTestId("select-list").findByText("Orders, Count").click();
|
||
|
||
cy.log(
|
||
"should show values of added dashboard card via search immediately (metabase#15959)",
|
||
);
|
||
assertBothCardsArePresent();
|
||
|
||
cy.log("Remove one card");
|
||
H.removeDashboardCard(0);
|
||
H.getDashboardCards().should("have.length", 1);
|
||
|
||
cy.log("It should be possible to undo remove that card");
|
||
cy.findByTestId("toast-undo").within(() => {
|
||
cy.findByText("Removed card");
|
||
cy.button("Undo").click();
|
||
});
|
||
|
||
assertBothCardsArePresent();
|
||
H.saveDashboard();
|
||
assertBothCardsArePresent();
|
||
|
||
function assertBothCardsArePresent() {
|
||
H.getDashboardCards()
|
||
.should("have.length", 2)
|
||
.and("contain", "Orders, Count")
|
||
.and("contain", "18,760");
|
||
}
|
||
});
|
||
|
||
it("should hide personal collections when adding questions to a dashboard in public collection", () => {
|
||
const collectionInRoot = {
|
||
name: "Collection in root collection",
|
||
};
|
||
H.createCollection(collectionInRoot);
|
||
const myPersonalCollection = "My personal collection";
|
||
H.createDashboard({
|
||
name: "dashboard in root collection",
|
||
}).then(({ body: { id: dashboardId } }) => {
|
||
H.visitDashboard(dashboardId);
|
||
});
|
||
|
||
cy.log("assert that personal collections are not visible");
|
||
H.editDashboard();
|
||
H.openQuestionsSidebar();
|
||
H.sidebar().within(() => {
|
||
cy.findByText("Our analytics").should("be.visible");
|
||
cy.findByText(myPersonalCollection).should("not.exist");
|
||
cy.findByText(collectionInRoot.name).should("be.visible");
|
||
});
|
||
|
||
cy.log("Move dashboard to a personal collection");
|
||
cy.findByTestId("edit-bar").button("Cancel").click();
|
||
H.openDashboardMenu();
|
||
H.popover().findByText("Move").click();
|
||
H.entityPickerModal().within(() => {
|
||
cy.findByRole("tab", { name: /Collections/ }).click();
|
||
cy.findByText("Bobby Tables's Personal Collection").click();
|
||
cy.button("Move").click();
|
||
});
|
||
|
||
H.editDashboard();
|
||
H.openQuestionsSidebar();
|
||
H.sidebar().within(() => {
|
||
cy.log("go to the root collection");
|
||
cy.findByText("Our analytics").click();
|
||
cy.findByText(myPersonalCollection).should("be.visible");
|
||
cy.findByText(collectionInRoot.name).should("be.visible");
|
||
});
|
||
|
||
cy.log("Move dashboard back to a root collection");
|
||
cy.findByTestId("edit-bar").button("Cancel").click();
|
||
H.openDashboardMenu();
|
||
H.popover().findByText("Move").click();
|
||
H.entityPickerModal().within(() => {
|
||
cy.findByRole("tab", { name: /Collections/ }).click();
|
||
cy.findByText("Our analytics").click();
|
||
cy.button("Move").click();
|
||
});
|
||
|
||
H.editDashboard();
|
||
H.openQuestionsSidebar();
|
||
H.sidebar().within(() => {
|
||
cy.findByText("Our analytics").should("be.visible");
|
||
cy.findByText(myPersonalCollection).should("not.exist");
|
||
cy.findByText(collectionInRoot.name).should("be.visible");
|
||
});
|
||
});
|
||
|
||
it("should save a dashboard after adding a saved question from an empty state (metabase#29450)", () => {
|
||
cy.findByTestId("dashboard-empty-state").within(() => {
|
||
cy.findByText("This dashboard is empty");
|
||
cy.findByText("Add a chart").click();
|
||
});
|
||
|
||
H.sidebar().findByText("Orders, Count").click();
|
||
|
||
H.saveDashboard();
|
||
|
||
H.getDashboardCards()
|
||
.should("have.length", 1)
|
||
.and("contain", "Orders, Count")
|
||
.and("contain", "18,760");
|
||
});
|
||
|
||
it("should save changes to a dashboard after using the 'Add a chart' button from an empty tab (metabase#53132)", () => {
|
||
cy.log("add an existing card");
|
||
H.editDashboard();
|
||
cy.findByTestId("dashboard-header").icon("add").click();
|
||
H.sidebar().findByText("Orders, Count").click();
|
||
cy.findByTestId("dashboard-header").icon("add").click();
|
||
|
||
cy.log("create a tab to access emtpy state again");
|
||
H.createNewTab();
|
||
cy.findByTestId("dashboard-empty-state")
|
||
.findByText("Add a chart")
|
||
.click();
|
||
|
||
cy.log("save changes before leaving");
|
||
H.sidebar().findByText("New SQL query").click();
|
||
H.modal().findByRole("button", { name: "Save changes" }).click();
|
||
|
||
cy.log("create a dashboard question");
|
||
H.NativeEditor.focus().type("SELECT 1");
|
||
H.saveQuestion("Foo question");
|
||
|
||
cy.log(
|
||
"should have persisted changes from when dashboard was saved before creating a question",
|
||
);
|
||
cy.findAllByRole("tab", { name: /Tab \d/ }).should("have.length", 2);
|
||
H.getDashboardCards().should("have.length", 2);
|
||
});
|
||
|
||
it("should allow navigating to the notebook editor directly from a dashboard card", () => {
|
||
H.visitDashboard(ORDERS_DASHBOARD_ID);
|
||
H.showDashboardCardActions();
|
||
H.getDashboardCardMenu().click();
|
||
H.popover().findByText("Edit question").should("be.visible").click();
|
||
cy.findByRole("button", { name: "Visualize" }).should("be.visible");
|
||
});
|
||
|
||
it("should allow navigating to the model editor directly from a dashboard card", () => {
|
||
H.createQuestionAndDashboard({
|
||
questionDetails: {
|
||
name: "orders",
|
||
type: "model",
|
||
query: {
|
||
"source-table": ORDERS_ID,
|
||
},
|
||
},
|
||
dashboardDetails: {
|
||
name: "Dashboard",
|
||
},
|
||
}).then(({ body: { dashboard_id, card } }) => {
|
||
cy.wrap(`${card.id}-${card.name}`).as("slug");
|
||
H.visitDashboard(dashboard_id);
|
||
});
|
||
|
||
H.showDashboardCardActions();
|
||
H.getDashboardCardMenu().click();
|
||
H.popover().findByText("Edit model").should("be.visible").click();
|
||
cy.get("@slug").then((slug) => {
|
||
cy.location("pathname").should("eq", `/model/${slug}/query`);
|
||
});
|
||
});
|
||
|
||
it("should allow navigating to the metric editor directly from a dashboard card", () => {
|
||
H.createQuestionAndDashboard({
|
||
questionDetails: {
|
||
name: "orders",
|
||
type: "metric",
|
||
query: {
|
||
"source-table": ORDERS_ID,
|
||
aggregation: [["count"]],
|
||
},
|
||
},
|
||
dashboardDetails: {
|
||
name: "Dashboard",
|
||
},
|
||
}).then(({ body: { dashboard_id, card } }) => {
|
||
cy.wrap(`${card.id}-${card.name}`).as("slug");
|
||
H.visitDashboard(dashboard_id);
|
||
});
|
||
|
||
H.showDashboardCardActions();
|
||
H.getDashboardCardMenu().click();
|
||
H.popover().findByText("Edit metric").should("be.visible").click();
|
||
cy.get("@slug").then((slug) => {
|
||
cy.location("pathname").should("eq", `/metric/${slug}/query`);
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("title and description", () => {
|
||
beforeEach(() => {
|
||
cy.intercept("GET", "/api/dashboard/*").as("getDashboard");
|
||
cy.intercept(
|
||
"PUT",
|
||
"/api/dashboard/*",
|
||
cy.spy().as("updateDashboardSpy"),
|
||
).as("updateDashboard");
|
||
});
|
||
|
||
const newTitle = "Renamed";
|
||
const newDescription = "Foo Bar";
|
||
|
||
it("should update the name and description without entering the dashboard edit mode", () => {
|
||
cy.findByTestId("dashboard-name-heading").clear().type(newTitle).blur();
|
||
|
||
cy.wait("@updateDashboard");
|
||
cy.wait("@getDashboard");
|
||
|
||
H.openDashboardInfoSidebar();
|
||
|
||
H.sidesheet()
|
||
.findByPlaceholderText("Add description")
|
||
.type(newDescription)
|
||
.blur();
|
||
|
||
cy.wait("@updateDashboard");
|
||
cy.wait("@getDashboard");
|
||
|
||
cy.log(
|
||
"New title and description should be preserved upon page reload",
|
||
);
|
||
cy.reload();
|
||
cy.wait("@getDashboard");
|
||
|
||
H.dashboardHeader().findByDisplayValue(newTitle);
|
||
H.openDashboardInfoSidebar();
|
||
H.sidesheet().findByText(newDescription);
|
||
H.closeDashboardInfoSidebar();
|
||
|
||
cy.log("should not call unnecessary API requests (metabase#31721)");
|
||
cy.get("@updateDashboardSpy").should("have.callCount", 2);
|
||
|
||
cy.log("Should revert the title change if escaped");
|
||
H.dashboardHeader().findByDisplayValue(newTitle).type("Whatever{esc}");
|
||
H.dashboardHeader().findByDisplayValue(newTitle);
|
||
cy.get("@updateDashboardSpy").should("have.callCount", 2);
|
||
|
||
cy.log("Should revert the description change if escaped");
|
||
H.openDashboardInfoSidebar();
|
||
H.sidesheet().within(() => {
|
||
cy.findByText(newDescription).type("Baz{esc}");
|
||
cy.findByText(newDescription);
|
||
});
|
||
cy.get("@updateDashboardSpy").should("have.callCount", 2);
|
||
});
|
||
|
||
it("should update the name and description in the dashboard edit mode", () => {
|
||
H.editDashboard();
|
||
|
||
cy.log("Should revert the title change if editing is cancelled");
|
||
cy.findByTestId("dashboard-name-heading").clear().type(newTitle).blur();
|
||
cy.findByTestId("edit-bar").button("Cancel").click();
|
||
H.modal().button("Discard changes").click();
|
||
cy.findByTestId("edit-bar").should("not.exist");
|
||
cy.get("@updateDashboardSpy").should("not.have.been.called");
|
||
cy.findByDisplayValue(originalDashboardName);
|
||
|
||
H.editDashboard();
|
||
|
||
cy.log("should not take you out of the edit mode when updating title");
|
||
cy.findByTestId("dashboard-name-heading").clear().type(newTitle).blur();
|
||
cy.log(
|
||
"The only way to open a sidebar in edit mode is to click on a revision history",
|
||
);
|
||
H.dashboardHeader()
|
||
.findByText(/^Edited a few seconds ago/)
|
||
.click();
|
||
|
||
H.sidesheet()
|
||
.findByPlaceholderText("Add description")
|
||
.type(newDescription)
|
||
.blur();
|
||
|
||
// TODO
|
||
// This might be a bug! We're applying the description while still in the edit mode!
|
||
// OTOH, the title is preserved only on save.
|
||
cy.wait("@updateDashboard");
|
||
H.closeDashboardInfoSidebar();
|
||
|
||
H.saveDashboard();
|
||
cy.wait("@updateDashboard");
|
||
cy.get("@updateDashboardSpy").should("have.callCount", 2);
|
||
});
|
||
|
||
it("should not have markdown content overflow the description area (metabase#31326)", () => {
|
||
H.openDashboardInfoSidebar();
|
||
|
||
const testMarkdownContent =
|
||
"# Heading 1{enter}{enter}**bold** https://www.metabase.com/community_posts/how-to-measure-the-success-of-new-product-features-and-why-it-is-important{enter}{enter}{enter}{enter}This is my description. ";
|
||
|
||
H.sidesheet()
|
||
.findByPlaceholderText("Add description")
|
||
.type(testMarkdownContent, { delay: 0 })
|
||
.blur();
|
||
|
||
cy.wait("@updateDashboard");
|
||
|
||
H.sidesheet().within(() => {
|
||
cy.log("Markdown content should not be bigger than its container");
|
||
cy.findByTestId("editable-text").then(($markdown) => {
|
||
const el = $markdown[0];
|
||
|
||
// vertical
|
||
expect(el.clientHeight).to.be.gte(
|
||
el.firstElementChild.clientHeight,
|
||
);
|
||
|
||
// horizontal
|
||
$markdown.find("*").each((_index, childEl) => {
|
||
const parentRect = el.getBoundingClientRect();
|
||
const childRect = childEl.getBoundingClientRect();
|
||
|
||
expect(parentRect.left).to.be.lte(childRect.left);
|
||
expect(parentRect.right).to.be.gte(childRect.right);
|
||
});
|
||
});
|
||
|
||
cy.log(
|
||
"Textarea should have a proper height when we change markdown text",
|
||
);
|
||
cy.findByTestId("editable-text")
|
||
.click()
|
||
.then(($el) => {
|
||
const lineHeight = parseFloat(
|
||
window.getComputedStyle($el[0]).lineHeight,
|
||
);
|
||
|
||
expect($el[0].scrollHeight).to.be.gte(
|
||
testMarkdownContent.split("{enter}").length * lineHeight, // num of lines * lineHeight
|
||
);
|
||
});
|
||
});
|
||
});
|
||
|
||
it("should prevent entering a title longer than 254 chars", () => {
|
||
const longTitle = "A".repeat(256);
|
||
cy.findByTestId("dashboard-name-heading")
|
||
.as("dashboardInput")
|
||
.clear()
|
||
.type(longTitle, { delay: 0 })
|
||
.blur();
|
||
cy.get("@dashboardInput").invoke("text").should("have.length", 254);
|
||
});
|
||
});
|
||
|
||
it(
|
||
"should not allow dashboard editing on small screens",
|
||
{ viewportWidth: 480, viewportHeight: 800 },
|
||
() => {
|
||
cy.findByLabelText("Edit dashboard").should("not.be.visible");
|
||
|
||
cy.viewport(660, 800);
|
||
|
||
cy.findByLabelText("Edit dashboard").should("be.visible").click();
|
||
cy.findByTestId("edit-bar").findByText(
|
||
"You're editing this dashboard.",
|
||
);
|
||
},
|
||
);
|
||
|
||
it(
|
||
"shows sorted cards on mobile screens",
|
||
{ viewportWidth: 400, viewportHeight: 800 },
|
||
() => {
|
||
H.createDashboard().then(({ body: { id: dashboard_id } }) => {
|
||
const cards = [
|
||
// the bottom card intentionally goes first to have unsorted cards coming from the BE
|
||
H.getTextCardDetails({
|
||
row: 1,
|
||
size_x: 24,
|
||
size_y: 1,
|
||
text: "bottom",
|
||
}),
|
||
H.getTextCardDetails({
|
||
row: 0,
|
||
size_x: 24,
|
||
size_y: 1,
|
||
text: "top",
|
||
}),
|
||
];
|
||
|
||
H.updateDashboardCards({ dashboard_id, cards });
|
||
|
||
H.visitDashboard(dashboard_id);
|
||
});
|
||
|
||
H.getDashboardCard(0).contains("top");
|
||
H.getDashboardCard(1).contains("bottom");
|
||
},
|
||
);
|
||
|
||
it("should not save the dashboard when the user clicks 'Discard changes'", () => {
|
||
// Navigate to the dashboard via client-side navigation (to trigger the client-side "Discard changes" prompt)
|
||
cy.visit("/");
|
||
cy.findByTestId("main-navbar-root").findByText("Our analytics").click();
|
||
cy.findByTestId("collection-table")
|
||
.findByText(originalDashboardName)
|
||
.click();
|
||
|
||
cy.log("Make a change to the dashboard");
|
||
H.editDashboard();
|
||
cy.findByTestId("dashboard-empty-state")
|
||
.findByText("Add a chart")
|
||
.click();
|
||
H.sidebar().findByText("Orders, Count").click();
|
||
H.getDashboardCards().should("have.length", 1);
|
||
|
||
cy.log("Navigate back and discard changes");
|
||
cy.go("back");
|
||
H.modal().button("Discard changes").click();
|
||
cy.findByTestId("collection-table")
|
||
.findByText(originalDashboardName)
|
||
.click();
|
||
|
||
cy.log("Verify changes were not saved");
|
||
cy.findByTestId("dashboard-empty-state").should("exist");
|
||
});
|
||
});
|
||
|
||
describe("iframe cards", () => {
|
||
it("should handle various iframe and URL inputs", () => {
|
||
const testCases = [
|
||
{
|
||
input: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||
expected: "https://www.youtube.com/embed/dQw4w9WgXcQ",
|
||
},
|
||
{
|
||
input: "https://youtu.be/dQw4w9WgXcQ",
|
||
expected: "https://www.youtube.com/embed/dQw4w9WgXcQ",
|
||
},
|
||
{
|
||
input: "https://www.loom.com/share/1234567890abcdef",
|
||
expected: "https://www.loom.com/embed/1234567890abcdef",
|
||
},
|
||
{
|
||
input: "https://vimeo.com/123456789",
|
||
expected: "https://player.vimeo.com/video/123456789",
|
||
},
|
||
{
|
||
input: "example.com",
|
||
expected: "https://example.com",
|
||
},
|
||
{
|
||
input: "https://example.com",
|
||
expected: "https://example.com",
|
||
},
|
||
{
|
||
input:
|
||
'<iframe src="https://example.com" onload="alert(\'XSS\')"></iframe>',
|
||
expected: "https://example.com",
|
||
},
|
||
];
|
||
|
||
H.updateSetting("allowed-iframe-hosts", "*");
|
||
|
||
H.createDashboard().then(({ body: { id } }) => {
|
||
H.visitDashboard(id);
|
||
});
|
||
|
||
H.editDashboard();
|
||
|
||
testCases.forEach(({ input, expected }, index) => {
|
||
H.addIFrameWhileEditing(input);
|
||
cy.button("Done").click();
|
||
validateIFrame(expected, index);
|
||
});
|
||
});
|
||
|
||
it("should respect allowed-iframe-hosts setting", () => {
|
||
const errorMessage = /can not be embedded in iframe cards/;
|
||
|
||
H.updateSetting(
|
||
"allowed-iframe-hosts",
|
||
["youtube.com", "player.videos.com"].join("\n"),
|
||
);
|
||
|
||
H.createDashboard().then(({ body: { id } }) => H.visitDashboard(id));
|
||
H.editDashboard();
|
||
|
||
// Test allowed domain with subdomains
|
||
H.addIFrameWhileEditing("https://youtube.com/watch?v=dQw4w9WgXcQ");
|
||
cy.button("Done").click();
|
||
validateIFrame("https://www.youtube.com/embed/dQw4w9WgXcQ");
|
||
|
||
H.editIFrameWhileEditing(
|
||
0,
|
||
"https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||
);
|
||
cy.button("Done").click();
|
||
validateIFrame("https://www.youtube.com/embed/dQw4w9WgXcQ");
|
||
|
||
// Test allowed subdomain, but no other domains
|
||
H.editIFrameWhileEditing(0, "player.videos.com/video/123456789");
|
||
cy.button("Done").click();
|
||
validateIFrame("https://player.videos.com/video/123456789");
|
||
|
||
H.editIFrameWhileEditing(0, "videos.com/video/123456789");
|
||
cy.button("Done").click();
|
||
H.getDashboardCard().within(() => {
|
||
cy.findByText(errorMessage).should("be.visible");
|
||
cy.get("iframe").should("not.exist");
|
||
});
|
||
|
||
H.editIFrameWhileEditing(0, "www.videos.com/video");
|
||
cy.button("Done").click();
|
||
H.getDashboardCard().within(() => {
|
||
cy.findByText(errorMessage).should("be.visible");
|
||
cy.get("iframe").should("not.exist");
|
||
});
|
||
|
||
// Test forbidden domain and subdomains
|
||
H.editIFrameWhileEditing(0, "https://example.com");
|
||
cy.button("Done").click();
|
||
H.getDashboardCard().within(() => {
|
||
cy.findByText(errorMessage).should("be.visible");
|
||
cy.get("iframe").should("not.exist");
|
||
});
|
||
|
||
H.editIFrameWhileEditing(0, "www.example.com");
|
||
cy.button("Done").click();
|
||
H.getDashboardCard().within(() => {
|
||
cy.findByText(errorMessage).should("be.visible");
|
||
cy.get("iframe").should("not.exist");
|
||
});
|
||
});
|
||
});
|
||
|
||
it("should add a filter", () => {
|
||
H.visitDashboard(ORDERS_DASHBOARD_ID);
|
||
H.editDashboard();
|
||
|
||
// Adding location/state doesn't make much sense for this case,
|
||
// but we're testing just that the filter is added to the dashboard
|
||
H.setFilter("Location", "Is");
|
||
|
||
H.getDashboardCard().findByText("Select…").click();
|
||
|
||
H.popover().findByText("State").click();
|
||
|
||
cy.icon("close");
|
||
cy.button("Done").click();
|
||
|
||
H.saveDashboard();
|
||
|
||
cy.log("Assert that the selected filter is present in the dashboard");
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.findByText("Location", { exact: false }).should("be.visible");
|
||
});
|
||
|
||
it("should link filters to custom question with filtered aggregate data (metabase#11007)", () => {
|
||
// programmatically create and save a question as per repro instructions in #11007
|
||
cy.request("POST", "/api/card", {
|
||
name: "11007",
|
||
dataset_query: {
|
||
database: SAMPLE_DB_ID,
|
||
filter: [">", ["field", "sum", { "base-type": "type/Float" }], 100],
|
||
query: {
|
||
"source-table": ORDERS_ID,
|
||
aggregation: [["sum", ["field", ORDERS.TOTAL, null]]],
|
||
breakout: [
|
||
["field", ORDERS.CREATED_AT, { "temporal-unit": "day" }],
|
||
["field", PRODUCTS.ID, { "source-field": ORDERS.PRODUCT_ID }],
|
||
["field", PRODUCTS.CATEGORY, { "source-field": ORDERS.PRODUCT_ID }],
|
||
],
|
||
filter: ["=", ["field", ORDERS.USER_ID, null], 1],
|
||
},
|
||
type: "query",
|
||
},
|
||
display: "table",
|
||
visualization_settings: {},
|
||
});
|
||
|
||
H.createDashboard({ name: "dash:11007" });
|
||
|
||
cy.visit("/collection/root");
|
||
// enter newly created dashboard
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.findByText("dash:11007").click();
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.findByText("This dashboard is empty");
|
||
// add previously created question to it
|
||
cy.findByLabelText("Edit dashboard").click();
|
||
H.openQuestionsSidebar();
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.findByText("11007").click();
|
||
|
||
H.setFilter("Date picker", "All Options");
|
||
|
||
// and connect it to the card
|
||
H.selectDashboardFilter(
|
||
cy.findByTestId("dashcard-container"),
|
||
"Created At",
|
||
);
|
||
|
||
// add second filter
|
||
H.setFilter("ID");
|
||
|
||
// and connect it to the card
|
||
H.selectDashboardFilter(
|
||
cy.findByTestId("dashcard-container"),
|
||
"Product ID",
|
||
);
|
||
|
||
// add third filter
|
||
H.setFilter("Text or Category", "Starts with");
|
||
// and connect it to the card
|
||
H.selectDashboardFilter(cy.findByTestId("dashcard-container"), "Category");
|
||
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.findByText("Save").click();
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.findByText("You're editing this dashboard.").should("not.exist");
|
||
});
|
||
|
||
it("should update a dashboard filter by clicking on a map pin (metabase#13597)", () => {
|
||
H.createQuestion({
|
||
name: "13597",
|
||
query: {
|
||
"source-table": PEOPLE_ID,
|
||
limit: 2,
|
||
},
|
||
display: "map",
|
||
}).then(({ body: { id: questionId } }) => {
|
||
H.createDashboard().then(({ body: { id: dashboardId } }) => {
|
||
// add filter (ID) to the dashboard
|
||
cy.request("PUT", `/api/dashboard/${dashboardId}`, {
|
||
parameters: [
|
||
{
|
||
id: "92eb69ea",
|
||
name: "ID",
|
||
sectionId: "id",
|
||
slug: "id",
|
||
type: "id",
|
||
},
|
||
],
|
||
});
|
||
|
||
H.addOrUpdateDashboardCard({
|
||
card_id: questionId,
|
||
dashboard_id: dashboardId,
|
||
card: {
|
||
parameter_mappings: [
|
||
{
|
||
parameter_id: "92eb69ea",
|
||
card_id: questionId,
|
||
target: ["dimension", ["field", PEOPLE.ID, null]],
|
||
},
|
||
],
|
||
visualization_settings: {
|
||
// set click behavior to update filter (ID)
|
||
click_behavior: {
|
||
type: "crossfilter",
|
||
parameterMapping: {
|
||
"92eb69ea": {
|
||
id: "92eb69ea",
|
||
source: { id: "ID", name: "ID", type: "column" },
|
||
target: {
|
||
id: "92eb69ea",
|
||
type: "parameter",
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
});
|
||
|
||
H.visitDashboard(dashboardId);
|
||
H.mapPinIcon().eq(0).click({ force: true });
|
||
cy.url().should("include", `/dashboard/${dashboardId}?id=1`);
|
||
cy.contains("Hudson Borer - 1");
|
||
});
|
||
});
|
||
});
|
||
|
||
it("should display column options for cross-filter (metabase#14473)", () => {
|
||
const questionDetails = {
|
||
name: "14473",
|
||
native: { query: "SELECT COUNT(*) FROM PRODUCTS", "template-tags": {} },
|
||
};
|
||
|
||
H.createNativeQuestionAndDashboard({ questionDetails }).then(
|
||
({ body: { dashboard_id } }) => {
|
||
cy.log("Add 4 filters to the dashboard");
|
||
|
||
cy.request("PUT", `/api/dashboard/${dashboard_id}`, {
|
||
parameters: [
|
||
{ name: "ID", slug: "id", id: "729b6456", type: "id" },
|
||
{ name: "ID 1", slug: "id_1", id: "bb20f59e", type: "id" },
|
||
{
|
||
name: "Category",
|
||
slug: "category",
|
||
id: "89873480",
|
||
type: "category",
|
||
},
|
||
{
|
||
name: "Category 1",
|
||
slug: "category_1",
|
||
id: "cbc045f2",
|
||
type: "category",
|
||
},
|
||
],
|
||
});
|
||
|
||
H.visitDashboard(dashboard_id);
|
||
},
|
||
);
|
||
|
||
// Add cross-filter click behavior manually
|
||
cy.icon("pencil").click();
|
||
H.showDashboardCardActions();
|
||
cy.findByTestId("dashboardcard-actions-panel").within(() => {
|
||
cy.icon("click").click();
|
||
});
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.findByText("COUNT(*)").click();
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.findByText("Update a dashboard filter").click();
|
||
|
||
checkOptionsForFilter("ID");
|
||
checkOptionsForFilter("Category");
|
||
});
|
||
|
||
it("should not get the parameter values from the field API", () => {
|
||
// In this test we're using already present dashboard ("Orders in a dashboard")
|
||
const FILTER_ID = "d7988e02";
|
||
|
||
cy.log("Add filter to the dashboard");
|
||
cy.request("PUT", `/api/dashboard/${ORDERS_DASHBOARD_ID}`, {
|
||
parameters: [
|
||
{
|
||
id: FILTER_ID,
|
||
name: "Category",
|
||
slug: "category",
|
||
type: "category",
|
||
},
|
||
],
|
||
});
|
||
|
||
cy.log("Connect filter to the existing card");
|
||
cy.request("PUT", `/api/dashboard/${ORDERS_DASHBOARD_ID}`, {
|
||
dashcards: [
|
||
{
|
||
id: ORDERS_DASHBOARD_DASHCARD_ID,
|
||
card_id: ORDERS_QUESTION_ID,
|
||
row: 0,
|
||
col: 0,
|
||
size_x: 16,
|
||
size_y: 8,
|
||
parameter_mappings: [
|
||
{
|
||
parameter_id: FILTER_ID,
|
||
card_id: ORDERS_QUESTION_ID,
|
||
target: [
|
||
"dimension",
|
||
[
|
||
"field",
|
||
PRODUCTS.CATEGORY,
|
||
{ "source-field": ORDERS.PRODUCT_ID },
|
||
],
|
||
],
|
||
},
|
||
],
|
||
visualization_settings: {},
|
||
},
|
||
],
|
||
});
|
||
|
||
cy.intercept(
|
||
`/api/dashboard/${ORDERS_DASHBOARD_ID}/params/${FILTER_ID}/values`,
|
||
cy.spy().as("fetchDashboardParams"),
|
||
);
|
||
cy.intercept(`/api/field/${PRODUCTS.CATEGORY}`, cy.spy().as("fetchField"));
|
||
cy.intercept(
|
||
`/api/field/${PRODUCTS.CATEGORY}/values`,
|
||
cy.spy().as("fetchFieldValues"),
|
||
);
|
||
|
||
H.visitDashboard(ORDERS_DASHBOARD_ID);
|
||
|
||
H.filterWidget().as("filterWidget").click();
|
||
|
||
["Doohickey", "Gadget", "Gizmo", "Widget"].forEach((category) => {
|
||
cy.findByText(category);
|
||
});
|
||
|
||
cy.get("@fetchDashboardParams").should("have.been.calledOnce");
|
||
cy.get("@fetchField").should("not.have.been.called");
|
||
cy.get("@fetchFieldValues").should("not.have.been.called");
|
||
});
|
||
|
||
it("should be possible to visit a dashboard with click-behavior linked to the dashboard without permissions (metabase#15368)", () => {
|
||
cy.request("GET", "/api/user/current").then(
|
||
({ body: { personal_collection_id } }) => {
|
||
// Save new dashboard in admin's personal collection
|
||
cy.request("POST", "/api/dashboard", {
|
||
name: "15368D",
|
||
collection_id: personal_collection_id,
|
||
}).then(({ body: { id: NEW_DASHBOARD_ID } }) => {
|
||
const COLUMN_REF = `["ref",["field-id",${ORDERS.ID}]]`;
|
||
// Add click behavior to the existing "Orders in a dashboard" dashboard
|
||
cy.request("PUT", `/api/dashboard/${ORDERS_DASHBOARD_ID}`, {
|
||
dashcards: [
|
||
{
|
||
id: ORDERS_DASHBOARD_DASHCARD_ID,
|
||
card_id: ORDERS_QUESTION_ID,
|
||
row: 0,
|
||
col: 0,
|
||
size_x: 16,
|
||
size_y: 8,
|
||
series: [],
|
||
visualization_settings: {
|
||
column_settings: {
|
||
[COLUMN_REF]: {
|
||
click_behavior: {
|
||
type: "link",
|
||
linkType: "dashboard",
|
||
parameterMapping: {},
|
||
targetId: NEW_DASHBOARD_ID,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
parameter_mappings: [],
|
||
},
|
||
],
|
||
});
|
||
|
||
cy.intercept(
|
||
"GET",
|
||
`/api/dashboard/${ORDERS_DASHBOARD_ID}/query_metadata*`,
|
||
).as("queryMetadata");
|
||
});
|
||
},
|
||
);
|
||
cy.signInAsNormalUser();
|
||
H.visitDashboard(ORDERS_DASHBOARD_ID);
|
||
|
||
cy.wait("@queryMetadata");
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.findByText("Orders in a dashboard");
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.contains("37.65");
|
||
});
|
||
|
||
it("should be possible to scroll vertically after fullscreen layer is closed (metabase#15596)", () => {
|
||
// Make this dashboard card extremely tall so that it spans outside of visible viewport
|
||
cy.request("PUT", `/api/dashboard/${ORDERS_DASHBOARD_ID}`, {
|
||
dashcards: [
|
||
{
|
||
id: ORDERS_DASHBOARD_DASHCARD_ID,
|
||
card_id: ORDERS_QUESTION_ID,
|
||
row: 0,
|
||
col: 0,
|
||
size_x: 16,
|
||
size_y: 20,
|
||
series: [],
|
||
visualization_settings: {},
|
||
parameter_mappings: [],
|
||
},
|
||
],
|
||
});
|
||
|
||
H.visitDashboard(ORDERS_DASHBOARD_ID);
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.contains("37.65");
|
||
assertScrollBarExists();
|
||
|
||
H.openLegacyStaticEmbeddingModal({
|
||
resource: "dashboard",
|
||
resourceId: ORDERS_DASHBOARD_ID,
|
||
});
|
||
|
||
H.modal().within(() => {
|
||
cy.icon("close").click();
|
||
});
|
||
H.modal().should("not.exist");
|
||
assertScrollBarExists();
|
||
});
|
||
|
||
it("should support auto-scrolling to a dashcard via a url hash param", () => {
|
||
const questionCard = {
|
||
id: ORDERS_DASHBOARD_DASHCARD_ID,
|
||
card_id: ORDERS_QUESTION_ID,
|
||
row: 0,
|
||
col: 0,
|
||
size_x: 16,
|
||
size_y: 9,
|
||
};
|
||
const paddingCard = H.getTextCardDetails({
|
||
col: 0,
|
||
text: "I'm just padding",
|
||
});
|
||
const TARGET_TEXT = "Scroll to me plz.";
|
||
const targetCard = H.getTextCardDetails({ col: 0, text: TARGET_TEXT });
|
||
const dashcards = [questionCard, paddingCard, targetCard];
|
||
|
||
H.createDashboard({ name: "Auto-scroll test", dashcards }).then(
|
||
({ body: dashboard }) => {
|
||
const targetCard = dashboard.dashcards.find(
|
||
(dc) => dc.visualization_settings?.text === TARGET_TEXT,
|
||
);
|
||
|
||
cy.log("should not be visible (below the fold)");
|
||
cy.visit(`/dashboard/${dashboard.id}}`);
|
||
cy.findByText(TARGET_TEXT).should("not.be.visible");
|
||
|
||
cy.log("should scroll into view w/ scrollTo hash param");
|
||
cy.visit(`/dashboard/${dashboard.id}#scrollTo=${targetCard.id}`);
|
||
cy.location("hash").should("match", /scrollTo=\d+/); // url should have hash param to auto-scroll
|
||
cy.location("hash").should("not.include", "scrollTo"); // scrollTo param should get removed
|
||
cy.findByText(TARGET_TEXT).should("be.visible");
|
||
},
|
||
);
|
||
});
|
||
|
||
it("should allow making card hide when it is empty", () => {
|
||
const FILTER_ID = "d7988e02";
|
||
|
||
cy.log("Add filter to the dashboard");
|
||
cy.request("PUT", `/api/dashboard/${ORDERS_DASHBOARD_ID}`, {
|
||
parameters: [
|
||
{
|
||
id: FILTER_ID,
|
||
name: "ID",
|
||
slug: "id",
|
||
type: "id",
|
||
},
|
||
],
|
||
});
|
||
|
||
cy.log("Connect filter to the existing card");
|
||
cy.request("PUT", `/api/dashboard/${ORDERS_DASHBOARD_ID}`, {
|
||
dashcards: [
|
||
{
|
||
id: ORDERS_DASHBOARD_DASHCARD_ID,
|
||
card_id: ORDERS_QUESTION_ID,
|
||
row: 0,
|
||
col: 0,
|
||
size_x: 16,
|
||
size_y: 8,
|
||
parameter_mappings: [
|
||
{
|
||
parameter_id: FILTER_ID,
|
||
card_id: ORDERS_QUESTION_ID,
|
||
target: ["dimension", ["field", ORDERS.ID]],
|
||
},
|
||
],
|
||
visualization_settings: {},
|
||
},
|
||
],
|
||
});
|
||
|
||
H.visitDashboard(ORDERS_DASHBOARD_ID);
|
||
H.editDashboard();
|
||
|
||
cy.findByTestId("dashboardcard-actions-panel").within(() => {
|
||
cy.icon("palette").click({ force: true });
|
||
});
|
||
|
||
cy.findByRole("dialog").within(() => {
|
||
cy.findByRole("switch", {
|
||
name: "Hide this card if there are no results",
|
||
}).click({ force: true });
|
||
cy.button("Done").click();
|
||
});
|
||
|
||
H.saveDashboard();
|
||
|
||
// Verify the card is hidden when the value is correct but produces empty results
|
||
H.filterWidget().click();
|
||
H.dashboardParametersPopover().within(() => {
|
||
cy.findByPlaceholderText("Enter an ID").type("-1{enter}");
|
||
cy.button("Add filter").click();
|
||
});
|
||
|
||
cy.findByTestId("dashcard").should("not.exist");
|
||
|
||
// Verify it becomes visible once the filter is cleared
|
||
H.filterWidget().within(() => {
|
||
cy.icon("close").click();
|
||
});
|
||
|
||
cy.findByTestId("dashcard").findByText("Orders");
|
||
});
|
||
|
||
describe("warn before leave", () => {
|
||
beforeEach(() => {
|
||
cy.intercept("GET", "/api/card/*/query_metadata").as("queryMetadata");
|
||
});
|
||
|
||
it("should warn a user before leaving after adding, editing, or removing a card on a dashboard", () => {
|
||
cy.visit("/");
|
||
cy.findByTestId("loading-indicator").should("not.exist");
|
||
|
||
cy.findByTestId("home-page").should(
|
||
"contain",
|
||
"Try out these sample x-rays to see what Metabase can do.",
|
||
);
|
||
|
||
// add
|
||
createNewDashboard();
|
||
cy.findByTestId("dashboard-header").icon("add").click();
|
||
cy.findByTestId("add-card-sidebar").findByText("Orders").click();
|
||
cy.wait("@queryMetadata");
|
||
assertPreventLeave({ openSidebar: false });
|
||
H.saveDashboard();
|
||
|
||
// edit
|
||
H.editDashboard();
|
||
const card = () =>
|
||
cy
|
||
.findAllByTestId("dashcard-container", { scrollBehavior: false })
|
||
.eq(0);
|
||
dragOnXAxis(card(), 100);
|
||
assertPreventLeave();
|
||
H.saveDashboard({ waitMs: DASHBOARD_SAVE_WAIT_TIME });
|
||
|
||
// remove
|
||
H.editDashboard();
|
||
H.removeDashboardCard();
|
||
assertPreventLeave();
|
||
});
|
||
|
||
it("should warn a user before leaving after adding, removing, moving, or duplicating a tab", () => {
|
||
cy.visit("/");
|
||
cy.findByTestId("loading-indicator").should("not.exist");
|
||
|
||
// add tab
|
||
createNewDashboard();
|
||
H.createNewTab();
|
||
assertPreventLeave();
|
||
H.saveDashboard();
|
||
|
||
// move tab
|
||
H.editDashboard();
|
||
dragOnXAxis(cy.findByRole("tab", { name: "Tab 2" }), -200);
|
||
// assert tab order is now correct and ui has caught up to result of dragging the tab
|
||
cy.findAllByRole("tab").eq(0).should("have.text", "Tab 2");
|
||
cy.findAllByRole("tab").eq(1).should("have.text", "Tab 1");
|
||
|
||
cy.wait(1000);
|
||
assertPreventLeave();
|
||
H.saveDashboard();
|
||
|
||
// duplicate tab
|
||
H.editDashboard();
|
||
H.duplicateTab("Tab 1");
|
||
assertPreventLeave();
|
||
H.saveDashboard();
|
||
|
||
cy.findByRole("tab", { name: "Copy of Tab 1" }).should(
|
||
"have.attr",
|
||
"aria-selected",
|
||
"true",
|
||
);
|
||
|
||
// remove tab
|
||
H.editDashboard();
|
||
H.deleteTab("Copy of Tab 1");
|
||
// url is changed after removing the tab
|
||
// can be a side effect
|
||
cy.url().should("include", "tab-1");
|
||
assertPreventLeave();
|
||
H.saveDashboard({ waitMs: DASHBOARD_SAVE_WAIT_TIME });
|
||
|
||
// rename tab
|
||
H.editDashboard();
|
||
H.renameTab("Tab 2", "Foo tab");
|
||
assertPreventLeave();
|
||
});
|
||
|
||
function createNewDashboard() {
|
||
H.newButton("Dashboard").click();
|
||
H.modal().within(() => {
|
||
cy.findByLabelText("Name").type("Test");
|
||
cy.findByRole("button", { name: "Create" }).click();
|
||
});
|
||
}
|
||
|
||
function dragOnXAxis(el, distance) {
|
||
el.trigger("mousedown", { clientX: 0 })
|
||
.trigger("mousemove", { clientX: distance })
|
||
// to avoid flakiness
|
||
.wait(100)
|
||
.trigger("mouseup");
|
||
}
|
||
|
||
function assertPreventLeave(options = { openSidebar: true }) {
|
||
if (options.openSidebar) {
|
||
H.openQuestionsSidebar();
|
||
}
|
||
cy.findByText("New Question").click();
|
||
H.modal()
|
||
.should("exist")
|
||
.within(() => {
|
||
cy.findByText("Save your changes?").should("exist");
|
||
cy.findByRole("button", { name: "Cancel" }).click();
|
||
});
|
||
}
|
||
});
|
||
});
|
||
|
||
describe("scenarios > dashboard", () => {
|
||
beforeEach(() => {
|
||
cy.intercept("GET", "/api/activity/recents?*").as("recentViews");
|
||
H.resetSnowplow();
|
||
H.restore();
|
||
cy.signInAsAdmin();
|
||
H.enableTracking();
|
||
});
|
||
|
||
afterEach(() => {
|
||
H.expectNoBadSnowplowEvents();
|
||
});
|
||
|
||
it("should be possible to add an iframe card", () => {
|
||
H.updateSetting("allowed-iframe-hosts", "*");
|
||
H.createDashboard({ name: "iframe card" }).then(({ body: { id } }) => {
|
||
H.visitDashboard(id);
|
||
|
||
H.editDashboard();
|
||
H.addIFrameWhileEditing("https://example.com");
|
||
cy.findByTestId("dashboardcard-actions-panel").should("not.exist");
|
||
cy.button("Done").click();
|
||
H.getDashboardCard(0).realHover();
|
||
cy.findByTestId("dashboardcard-actions-panel").should("be.visible");
|
||
validateIFrame("https://example.com");
|
||
H.saveDashboard();
|
||
validateIFrame("https://example.com");
|
||
|
||
H.expectUnstructuredSnowplowEvent({
|
||
event: "new_iframe_card_created",
|
||
target_id: id,
|
||
event_detail: "example.com",
|
||
});
|
||
});
|
||
});
|
||
|
||
it("saving a dashboard should track a 'dashboard_saved' snowplow event", () => {
|
||
H.visitDashboard(ORDERS_DASHBOARD_ID);
|
||
H.editDashboard();
|
||
const newTitle = "New title";
|
||
cy.findByTestId("dashboard-name-heading").clear().type(newTitle).blur();
|
||
H.saveDashboard();
|
||
H.expectUnstructuredSnowplowEvent({
|
||
event: "dashboard_saved",
|
||
});
|
||
});
|
||
|
||
it("should allow users to add link cards to dashboards", () => {
|
||
H.visitDashboard(ORDERS_DASHBOARD_ID);
|
||
H.editDashboard();
|
||
cy.findByLabelText("Add a link or iframe").click();
|
||
H.popover().findByText("Link").click();
|
||
|
||
cy.wait("@recentViews");
|
||
|
||
cy.findByTestId("custom-edit-text-link")
|
||
.findByPlaceholderText("https://example.com")
|
||
.type("Orders");
|
||
|
||
H.popover().within(() => {
|
||
cy.findByText(/Loading/i).should("not.exist");
|
||
cy.findByText("Orders in a dashboard").click();
|
||
});
|
||
|
||
cy.findByTestId("entity-edit-display-link").findByText(
|
||
/orders in a dashboard/i,
|
||
);
|
||
|
||
H.saveDashboard();
|
||
|
||
cy.findByTestId("entity-view-display-link").findByText(
|
||
/orders in a dashboard/i,
|
||
);
|
||
|
||
H.expectUnstructuredSnowplowEvent({
|
||
event: "new_link_card_created",
|
||
});
|
||
});
|
||
|
||
it("should track enabling the hide empty cards setting", () => {
|
||
H.visitDashboard(ORDERS_DASHBOARD_ID);
|
||
H.editDashboard();
|
||
|
||
cy.findByTestId("dashboardcard-actions-panel").within(() => {
|
||
cy.icon("palette").click({ force: true });
|
||
});
|
||
|
||
cy.findByRole("dialog").within(() => {
|
||
cy.findByRole("switch", {
|
||
name: "Hide this card if there are no results",
|
||
})
|
||
.click({ force: true }) // enable
|
||
.click({ force: true }) // disable
|
||
.click({ force: true }); // enable
|
||
|
||
H.expectUnstructuredSnowplowEvent(
|
||
{
|
||
event: "card_set_to_hide_when_no_results",
|
||
dashboard_id: ORDERS_DASHBOARD_ID,
|
||
},
|
||
2,
|
||
);
|
||
});
|
||
});
|
||
|
||
it("should allow the creator to change the dashboard width to 'fixed' or 'full'", () => {
|
||
const TAB_1 = {
|
||
id: 1,
|
||
name: "Tab 1",
|
||
};
|
||
const TAB_2 = {
|
||
id: 2,
|
||
name: "Tab 2",
|
||
};
|
||
const DASHBOARD_TEXT_FILTER = {
|
||
id: "94f9e513",
|
||
name: "Text filter",
|
||
slug: "filter-text",
|
||
type: "string/contains",
|
||
};
|
||
|
||
H.createDashboardWithTabs({
|
||
tabs: [TAB_1, TAB_2],
|
||
parameters: [{ ...DASHBOARD_TEXT_FILTER, default: "Example Input" }],
|
||
dashcards: [
|
||
createMockVirtualDashCard({
|
||
id: -1,
|
||
dashboard_tab_id: TAB_1.id,
|
||
size_x: GRID_WIDTH,
|
||
parameter_mappings: [
|
||
{ parameter_id: "94f9e513", target: ["text-tag", "Name"] },
|
||
],
|
||
card: createMockVirtualCard({ display: "text" }),
|
||
visualization_settings: {
|
||
text: "Top: {{Name}}",
|
||
},
|
||
}),
|
||
createMockVirtualDashCard({
|
||
id: -2,
|
||
size_x: GRID_WIDTH,
|
||
dashboard_tab_id: TAB_1.id,
|
||
card: createMockVirtualCard({ display: "text" }),
|
||
visualization_settings: {
|
||
text: "Bottom",
|
||
},
|
||
}),
|
||
],
|
||
}).then((dashboard) => H.visitDashboard(dashboard.id));
|
||
|
||
// new dashboards should default to 'fixed' width
|
||
H.assertDashboardFixedWidth();
|
||
|
||
// toggle full-width
|
||
H.editDashboard();
|
||
cy.findByLabelText("Toggle width").click();
|
||
H.popover().findByText("Full width").click();
|
||
H.assertDashboardFullWidth();
|
||
H.expectUnstructuredSnowplowEvent({
|
||
event: "dashboard_width_toggled",
|
||
full_width: true,
|
||
});
|
||
|
||
// confirm it saves the state after saving and refreshing
|
||
H.saveDashboard();
|
||
cy.reload();
|
||
H.assertDashboardFullWidth();
|
||
|
||
// toggle back to fixed
|
||
H.editDashboard();
|
||
cy.findByLabelText("Toggle width").click();
|
||
H.popover().findByText("Full width").click();
|
||
H.assertDashboardFixedWidth();
|
||
H.expectUnstructuredSnowplowEvent({
|
||
event: "dashboard_width_toggled",
|
||
full_width: false,
|
||
});
|
||
});
|
||
|
||
it("should track reverting to an old version", () => {
|
||
H.createDashboard({ name: "Foo" }).then(({ body: { id } }) => {
|
||
cy.request("PUT", `/api/dashboard/${id}`, { name: "Bar" });
|
||
H.visitDashboard(id);
|
||
|
||
cy.intercept("GET", "/api/revision*").as("revisionHistory");
|
||
|
||
cy.findByTestId("dashboard-header")
|
||
.findByLabelText("More info")
|
||
.should("be.visible")
|
||
.click();
|
||
|
||
H.sidesheet().within(() => {
|
||
cy.findByRole("tab", { name: "History" }).click();
|
||
cy.wait("@revisionHistory");
|
||
cy.findByTestId("dashboard-history-list").should("be.visible");
|
||
cy.findByTestId("question-revert-button").click();
|
||
});
|
||
|
||
H.expectUnstructuredSnowplowEvent({
|
||
event: "revert_version_clicked",
|
||
event_detail: "dashboard",
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
function checkOptionsForFilter(filter) {
|
||
cy.findByText("Available filters").parent().contains(filter).click();
|
||
H.popover()
|
||
.should("contain", "Columns")
|
||
.and("contain", "COUNT(*)")
|
||
.and("not.contain", "Dashboard filters");
|
||
|
||
// Get rid of the open popover to be able to select another filter
|
||
// Uses force: true because the popover is covering this text.
|
||
cy.findByText("Pick one or more filters to update").click({ force: true });
|
||
}
|
||
|
||
function assertScrollBarExists() {
|
||
cy.get("body").then(($body) => {
|
||
const bodyWidth = $body[0].getBoundingClientRect().width;
|
||
cy.window().its("innerWidth").should("be.gte", bodyWidth);
|
||
});
|
||
}
|
||
|
||
describe("LOCAL TESTING ONLY > dashboard", () => {
|
||
beforeEach(() => {
|
||
H.restore();
|
||
cy.signInAsAdmin();
|
||
});
|
||
|
||
/**
|
||
* WARNING:
|
||
* https://github.com/metabase/metabase/issues/15656
|
||
* - We are currently not able to test translations in CI
|
||
* - DO NOT unskip this test even after the issue is fixed
|
||
* - To be used for local testing only
|
||
* - Make sure you have translation resources built first.
|
||
* - Run `./bin/i18n/build-translation-resources`
|
||
* - Then start the server and Cypress tests
|
||
*/
|
||
|
||
it(
|
||
"dashboard filter should not show placeholder for translated languages (metabase#15694)",
|
||
{ tags: "@skip" },
|
||
() => {
|
||
cy.request("GET", "/api/user/current").then(
|
||
({ body: { id: USER_ID } }) => {
|
||
cy.request("PUT", `/api/user/${USER_ID}`, { locale: "fr" });
|
||
},
|
||
);
|
||
H.createQuestionAndDashboard({
|
||
questionDetails: {
|
||
name: "15694",
|
||
query: { "source-table": PEOPLE_ID },
|
||
},
|
||
dashboardDetails: {
|
||
parameters: [
|
||
{
|
||
name: "Location",
|
||
slug: "location",
|
||
id: "5aefc725",
|
||
type: "string/=",
|
||
sectionId: "location",
|
||
},
|
||
],
|
||
},
|
||
}).then(({ body: { card_id, dashboard_id } }) => {
|
||
H.addOrUpdateDashboardCard({
|
||
card_id,
|
||
dashboard_id,
|
||
card: {
|
||
parameter_mappings: [
|
||
{
|
||
parameter_id: "5aefc725",
|
||
card_id,
|
||
target: ["dimension", ["field", PEOPLE.STATE, null]],
|
||
},
|
||
],
|
||
},
|
||
});
|
||
|
||
cy.visit(`/dashboard/${dashboard_id}?location=AK&location=CA`);
|
||
H.filterWidget().contains(/\{0\}/).should("not.exist");
|
||
});
|
||
},
|
||
);
|
||
});
|
||
|
||
describe("scenarios > dashboard > caching", () => {
|
||
beforeEach(() => {
|
||
H.restore();
|
||
cy.signInAsAdmin();
|
||
H.activateToken("pro-self-hosted");
|
||
});
|
||
|
||
/**
|
||
* @note There is a similar test for the cache config form that appears in the question sidebar.
|
||
* It's in the Cypress describe block labeled "scenarios > question > caching"
|
||
*/
|
||
it("can configure cache for a dashboard, on an enterprise instance", () => {
|
||
interceptPerformanceRoutes();
|
||
H.visitDashboard(ORDERS_DASHBOARD_ID);
|
||
|
||
openSidebarCacheStrategyForm("dashboard");
|
||
|
||
H.sidesheet().within(() => {
|
||
cy.findByText(/Caching settings/).should("be.visible");
|
||
durationRadioButton().click();
|
||
cy.findByLabelText("Cache results for this many hours").type("48");
|
||
cy.findByRole("button", { name: /Save/ }).click();
|
||
cy.wait("@putCacheConfig");
|
||
cy.log(
|
||
"Check that the newly chosen cache invalidation policy - Duration - is now visible in the sidebar",
|
||
);
|
||
cy.findByLabelText(/When to get new results/).should(
|
||
"contain",
|
||
"Duration",
|
||
);
|
||
cy.findByLabelText(/When to get new results/).click();
|
||
adaptiveRadioButton().click();
|
||
cy.findByLabelText(/Minimum query duration/).type("999");
|
||
cy.findByRole("button", { name: /Save/ }).click();
|
||
cy.wait("@putCacheConfig");
|
||
cy.findByLabelText(/When to get new results/).should(
|
||
"contain",
|
||
"Adaptive",
|
||
);
|
||
});
|
||
});
|
||
|
||
it("can click 'Clear cache' for a dashboard", () => {
|
||
interceptPerformanceRoutes();
|
||
H.visitDashboard(ORDERS_DASHBOARD_ID);
|
||
|
||
openSidebarCacheStrategyForm("dashboard");
|
||
|
||
H.sidesheet().within(() => {
|
||
cy.findByText(/Caching settings/).should("be.visible");
|
||
cy.findByRole("button", {
|
||
name: /Clear cache for this dashboard/,
|
||
}).click();
|
||
});
|
||
|
||
cy.findByTestId("confirm-modal").within(() => {
|
||
cy.findByRole("button", { name: /Clear cache/ }).click();
|
||
});
|
||
cy.wait("@invalidateCache");
|
||
H.sidesheet().within(() => {
|
||
cy.findByText("Cache cleared").should("be.visible");
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("scenarios > dashboard > permissions", () => {
|
||
let dashboardId;
|
||
|
||
beforeEach(() => {
|
||
H.restore();
|
||
// This first test creates a dashboard with two questions.
|
||
// One is in Our Analytics the other is in a more locked down collection.
|
||
cy.signInAsAdmin();
|
||
|
||
// The setup is a bunch of nested API calls to create the questions, dashboard, dashcards, collections and link them all up together.
|
||
let firstQuestionId, secondQuestionId;
|
||
|
||
cy.request("POST", "/api/collection", {
|
||
name: "locked down collection",
|
||
parent_id: null,
|
||
}).then(({ body: { id: collection_id } }) => {
|
||
cy.request("GET", "/api/collection/graph").then(
|
||
({ body: { revision, groups } }) => {
|
||
// update the perms for the just-created collection
|
||
cy.request("PUT", "/api/collection/graph", {
|
||
revision,
|
||
groups: _.mapObject(groups, (groupPerms, groupId) =>
|
||
assoc(
|
||
groupPerms,
|
||
collection_id,
|
||
// 2 is admins, so leave that as "write"
|
||
groupId === "2" ? "write" : "none",
|
||
),
|
||
),
|
||
});
|
||
},
|
||
);
|
||
|
||
cy.request("POST", "/api/card", {
|
||
dataset_query: {
|
||
database: SAMPLE_DB_ID,
|
||
type: "native",
|
||
native: { query: "select 'foo'" },
|
||
},
|
||
display: "table",
|
||
visualization_settings: {},
|
||
name: "First Question",
|
||
collection_id,
|
||
}).then(({ body: { id } }) => (firstQuestionId = id));
|
||
|
||
cy.request("POST", "/api/card", {
|
||
dataset_query: {
|
||
database: SAMPLE_DB_ID,
|
||
type: "native",
|
||
native: { query: "select 'bar'" },
|
||
},
|
||
display: "table",
|
||
visualization_settings: {},
|
||
name: "Second Question",
|
||
collection_id: null,
|
||
}).then(({ body: { id } }) => (secondQuestionId = id));
|
||
});
|
||
|
||
H.createDashboard().then(({ body: { id: dashId } }) => {
|
||
dashboardId = dashId;
|
||
|
||
H.updateDashboardCards({
|
||
dashboard_id: dashId,
|
||
cards: [
|
||
{ card_id: firstQuestionId, row: 0, col: 0, size_x: 8, size_y: 6 },
|
||
{ card_id: secondQuestionId, row: 0, col: 6, size_x: 8, size_y: 6 },
|
||
],
|
||
});
|
||
});
|
||
});
|
||
|
||
it("should let admins view all cards in a dashboard", () => {
|
||
H.visitDashboard(dashboardId);
|
||
// Admin can see both questions
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.findByText("First Question");
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.findByText("foo");
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.findByText("Second Question");
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.findByText("bar");
|
||
});
|
||
|
||
it("should display dashboards with some cards locked down", () => {
|
||
cy.signIn("nodata");
|
||
H.visitDashboard(dashboardId);
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.findByText("Sorry, you don't have permission to see this card.");
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.findByText("Second Question");
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.findByText("bar");
|
||
});
|
||
|
||
it("should display an error if they don't have perms for the dashboard", () => {
|
||
cy.signIn("nocollection");
|
||
H.visitDashboard(dashboardId);
|
||
// eslint-disable-next-line no-unscoped-text-selectors -- deprecated usage
|
||
cy.findByText("Sorry, you don’t have permission to see that.");
|
||
});
|
||
});
|
||
|
||
describe("scenarios > dashboard > entity id support", () => {
|
||
beforeEach(() => {
|
||
H.restore();
|
||
cy.signInAsAdmin();
|
||
});
|
||
|
||
it("when loading `/dashboard/entity/${entity_id}`, it should redirect to `/dashboard/${id}` and display the dashboard correctly", () => {
|
||
cy.visit(`/dashboard/entity/${ORDERS_DASHBOARD_ENTITY_ID}`);
|
||
|
||
cy.url().should("contain", `/dashboard/${ORDERS_DASHBOARD_ID}`);
|
||
|
||
// Making sure the dashboard loads
|
||
H.main().findByText("Orders in a dashboard").should("be.visible");
|
||
});
|
||
|
||
it("when loading `/dashboard/entity/${entity_id}?tab=${tab_entity_id}`, it should redirect to `/dashboard/${id}?tab=${tab_id}` and select the correct tab", () => {
|
||
H.createDashboardWithTabs({
|
||
tabs: [
|
||
{ name: "Tab 1", id: -1 },
|
||
{ name: "Tab 2", id: -2 },
|
||
],
|
||
dashcards: [],
|
||
}).then((dashboard) => {
|
||
cy.visit(
|
||
`/dashboard/entity/${dashboard.entity_id}?tab=${dashboard.tabs[1].entity_id}`,
|
||
);
|
||
|
||
cy.url().should(
|
||
"contain",
|
||
`/dashboard/${dashboard.id}?tab=${dashboard.tabs[1].id}`,
|
||
);
|
||
|
||
H.main()
|
||
.findByRole("tab", { name: "Tab 2" })
|
||
.should("have.attr", "aria-selected", "true");
|
||
});
|
||
});
|
||
|
||
it("it should preserve search params such as filters when redirecting", () => {
|
||
// Add filter to the dashboard
|
||
cy.request("PUT", `/api/dashboard/${ORDERS_DASHBOARD_ID}`, {
|
||
parameters: [
|
||
{
|
||
id: "abc123",
|
||
name: "Text",
|
||
slug: "text",
|
||
type: "string/=",
|
||
},
|
||
],
|
||
});
|
||
|
||
// Connect filter to the existing card
|
||
cy.request("PUT", `/api/dashboard/${ORDERS_DASHBOARD_ID}`, {
|
||
dashcards: [
|
||
{
|
||
id: ORDERS_DASHBOARD_DASHCARD_ID,
|
||
card_id: ORDERS_QUESTION_ID,
|
||
row: 0,
|
||
col: 0,
|
||
size_x: 16,
|
||
size_y: 8,
|
||
parameter_mappings: [
|
||
{
|
||
parameter_id: "abc123",
|
||
card_id: ORDERS_QUESTION_ID,
|
||
target: ["dimension", ["field", ORDERS.ID, null]],
|
||
},
|
||
],
|
||
visualization_settings: {},
|
||
},
|
||
],
|
||
});
|
||
|
||
// Visit the dashboard via the entity id path and verify that the filter is preserved
|
||
cy.visit(`/dashboard/entity/${ORDERS_DASHBOARD_ENTITY_ID}?text=123`);
|
||
|
||
cy.url()
|
||
.should("contain", `/dashboard/${ORDERS_DASHBOARD_ID}`)
|
||
.and("contain", "text=123");
|
||
|
||
H.filterWidget().should("contain", "Text").and("contain", "123");
|
||
});
|
||
|
||
it("when loading `/dashboard/entity/${entity_id}/move`, it should redirect to `/dashboard/${id}/move` and show the move modal", () => {
|
||
cy.visit(`/dashboard/entity/${ORDERS_DASHBOARD_ENTITY_ID}/move`);
|
||
cy.url().should("contain", `/dashboard/${ORDERS_DASHBOARD_ID}/move`);
|
||
|
||
H.main().findByText("Orders in a dashboard").should("be.visible");
|
||
H.modal().findByText("Move dashboard to…").should("be.visible");
|
||
});
|
||
|
||
it("when loading `/dashboard/entity/${non existing entity id}`, it should show a 404 page", () => {
|
||
const nonExistingEntityId = "x".repeat(21);
|
||
cy.visit(`/dashboard/entity/${nonExistingEntityId}`);
|
||
|
||
H.main().findByText("We're a little lost...").should("be.visible");
|
||
});
|
||
|
||
it("when loading `/dashboard/entity/${non existing entity id}`, it should show a 404 page even if the entity id starts with a number", () => {
|
||
const nonExistingEntityId = "12".padEnd(21, "x");
|
||
cy.visit(`/dashboard/entity/${nonExistingEntityId}`);
|
||
|
||
H.main().findByText("We're a little lost...").should("be.visible");
|
||
});
|
||
|
||
it("when loading `/dashboard/entity/${entity id}?tab=${non existing tab entity id}`, it should show a 404 page even if the entity id starts with a number", () => {
|
||
const nonExistingEntityId = "12".padEnd(21, "x");
|
||
cy.visit(
|
||
`/dashboard/entity/${ORDERS_DASHBOARD_ENTITY_ID}?tab=${nonExistingEntityId}`,
|
||
);
|
||
|
||
H.main().findByText("We're a little lost...").should("be.visible");
|
||
});
|
||
});
|
||
|
||
function validateIFrame(src, index = 0) {
|
||
// eslint-disable-next-line no-unsafe-element-filtering
|
||
H.getDashboardCards()
|
||
.get("iframe")
|
||
.eq(index)
|
||
.should("have.attr", "src", src)
|
||
.and(
|
||
"have.attr",
|
||
"sandbox",
|
||
"allow-forms allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts",
|
||
)
|
||
.and("not.have.attr", "onload");
|
||
}
|