Files
metabase/e2e/test/scenarios/embedding/embedding-dashboard.cy.spec.js

1337 lines
39 KiB
JavaScript

const { H } = cy;
import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
import { ORDERS_DASHBOARD_ID } from "e2e/support/cypress_sample_instance_data";
import { createMockParameter } from "metabase-types/api/mocks";
import { addWidgetStringFilter } from "../native-filters/helpers/e2e-field-filter-helpers";
import {
dashboardDetails,
mapParameters,
questionDetails,
questionDetailsWithDefaults,
} from "./shared/embedding-dashboard";
const { ORDERS, PEOPLE, PRODUCTS, ORDERS_ID } = SAMPLE_DATABASE;
describe("scenarios > embedding > static embedding dashboard", () => {
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
cy.request("PUT", `/api/dashboard/${ORDERS_DASHBOARD_ID}`, {
enable_embedding: true,
});
});
it("should not call `GET /api/database` (metabase#63310)", () => {
cy.intercept("GET", "/api/database").as("getDatabases");
const payload = {
resource: { dashboard: ORDERS_DASHBOARD_ID },
params: {},
};
H.visitEmbeddedPage(payload);
cy.findByRole("heading", { name: "Orders in a dashboard" }).should(
"be.visible",
);
cy.log("GET /api/database should not be called");
cy.get("@getDatabases.all").should("have.length", 0);
});
});
describe("scenarios > embedding > dashboard parameters", () => {
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
cy.request("POST", `/api/field/${ORDERS.USER_ID}/dimension`, {
type: "external",
name: "User ID",
human_readable_field_id: PEOPLE.NAME,
});
[ORDERS.USER_ID, PEOPLE.NAME, PEOPLE.ID].forEach((id) =>
cy.request("PUT", `/api/field/${id}`, { has_field_values: "search" }),
);
H.createNativeQuestionAndDashboard({
questionDetails,
dashboardDetails,
}).then(({ body: { id, card_id, dashboard_id } }) => {
cy.wrap(dashboard_id).as("dashboardId");
mapParameters({ id, card_id, dashboard_id });
});
});
context("UI", () => {
it("should be disabled by default but able to be set to editable and/or locked (metabase#20357)", () => {
H.visitDashboard("@dashboardId");
cy.get("@dashboardId").then((dashboardId) => {
H.openLegacyStaticEmbeddingModal({
resource: "dashboard",
resourceId: dashboardId,
activeTab: "parameters",
});
});
cy.findByLabelText("Configuring parameters").as("allParameters");
cy.get("@allParameters").within(() => {
// verify that all the parameters on the dashboard are defaulted to disabled
cy.findAllByText("Disabled").should(
"have.length",
dashboardDetails.parameters.length,
);
// select the dropdown next to the Name parameter so that we can set it to editable
cy.findByText("Name")
.parent()
.within(() => {
cy.findByText("Disabled").click();
});
});
H.popover().findByText("Editable").click();
cy.get("@allParameters")
.findByText("Id")
.parent()
.findByText("Disabled")
.click();
H.popover().findByText("Locked").click();
H.modal().within(() => {
// set the locked parameter's value
cy.findByText("Previewing locked parameters")
.parent()
.findByText("Id")
.click();
});
H.popover().within(() => {
cy.findByPlaceholderText("Search by Name or enter an ID").type("1,3,");
cy.button("Add filter").click();
});
// publish the embedded dashboard so that we can directly navigate to its url
H.publishChanges("dashboard", ({ request }) => {
assert.deepEqual(request.body.embedding_params, {
id: "locked",
name: "enabled",
source: "disabled",
user_id: "disabled",
not_used: "disabled",
});
});
// directly navigate to the embedded dashboard
H.visitIframe();
// verify that the Id parameter doesn't show up but that its value is reflected in the dashcard
H.filterWidget().contains("Id").should("not.exist");
cy.findByTestId("scalar-value").invoke("text").should("eq", "2");
// verify that disabled filters don't show up
cy.findByTestId("dashboard-parameters-widget-container").within(() => {
cy.findByText("Source").should("not.exist");
cy.findByText("User").should("not.exist");
});
// only Name parameter should be visible
openFilterOptions("Name");
cy.findByPlaceholderText("Search by Name").type("L");
H.popover().findByText("Lina Heaney").click();
cy.button("Add filter").click();
cy.findByTestId("scalar-value").invoke("text").should("eq", "1");
cy.log(
"Sanity check: lets make sure we can disable all previously set parameters",
);
cy.signInAsAdmin();
H.visitDashboard("@dashboardId");
cy.get("@dashboardId").then((dashboardId) => {
H.openLegacyStaticEmbeddingModal({
resource: "dashboard",
resourceId: dashboardId,
activeTab: "parameters",
unpublishBeforeOpen: false,
});
});
cy.get("@allParameters").findByText("Locked").click();
H.popover().contains("Disabled").click();
cy.get("@allParameters").findByText("Editable").click();
H.popover().contains("Disabled").click();
H.publishChanges("dashboard", ({ request }) => {
assert.deepEqual(request.body.embedding_params, {
name: "disabled",
id: "disabled",
source: "disabled",
user_id: "disabled",
not_used: "disabled",
});
});
H.visitIframe();
H.filterWidget().should("not.exist");
cy.findByTestId("scalar-value").invoke("text").should("eq", "2,500");
});
it("should only display filters mapped to cards on the selected tab", () => {
cy.get("@dashboardId").then((dashboardId) => {
cy.request("PUT", `/api/dashboard/${dashboardId}`, {
embedding_params: {
id: "enabled",
name: "enabled",
source: "enabled",
user_id: "enabled",
},
enable_embedding: true,
});
const payload = {
resource: { dashboard: dashboardId },
params: {},
};
H.visitEmbeddedPage(payload);
// wait for the results to load
cy.contains("Test Dashboard");
cy.contains("2,500");
});
H.dashboardParametersContainer().within(() => {
cy.findByText("Id").should("be.visible");
cy.findByText("Name").should("be.visible");
cy.findByText("Source").should("be.visible");
cy.findByText("User").should("be.visible");
cy.findByText("Not Used Filter").should("not.exist");
});
H.goToTab("Tab 2");
H.dashboardParametersContainer().should("not.exist");
cy.findByTestId("embed-frame").within(() => {
cy.findByText("Id").should("not.exist");
cy.findByText("Name").should("not.exist");
cy.findByText("Source").should("not.exist");
cy.findByText("User").should("not.exist");
cy.findByText("Not Used Filter").should("not.exist");
});
});
it("should handle required parameters", () => {
H.visitDashboard("@dashboardId");
H.editDashboard();
// Make one parameter required
getDashboardFilter("Name").click();
H.toggleRequiredParameter();
H.sidebar().findByText("Default value").next().click();
addWidgetStringFilter("Ferne Rosenbaum", {
buttonLabel: "Update filter",
});
H.saveDashboard();
cy.get("@dashboardId").then((dashboardId) => {
H.openLegacyStaticEmbeddingModal({
resource: "dashboard",
resourceId: dashboardId,
activeTab: "parameters",
});
});
// Check that parameter visibility is correct
H.assertEmbeddingParameter("Id", "Disabled");
H.assertEmbeddingParameter("Name", "Editable");
H.assertEmbeddingParameter("Source", "Disabled");
H.assertEmbeddingParameter("User", "Disabled");
H.assertEmbeddingParameter("Not Used Filter", "Disabled");
// We only expect name to be "enabled" because the rest
// weren't touched and therefore aren't changed, whereas
// "enabled" must be set by default for required params.
H.publishChanges("dashboard", ({ request }) => {
assert.deepEqual(request.body.embedding_params, {
id: "disabled",
name: "enabled",
source: "disabled",
user_id: "disabled",
not_used: "disabled",
});
});
H.visitIframe();
// Filter widget must be visible
H.filterWidget().contains("Name");
// Its default value must be in the URL
cy.location("search").should("contain", "name=Ferne+Rosenbaum");
// And the default should be applied giving us only 1 result
cy.findByTestId("scalar-value").invoke("text").should("eq", "1");
});
it("should (dis)allow setting parameters as required for a published embedding", () => {
H.visitDashboard("@dashboardId");
cy.get("@dashboardId").then((dashboardId) => {
H.openLegacyStaticEmbeddingModal({
resource: "dashboard",
resourceId: dashboardId,
activeTab: "parameters",
});
});
// Set an "editable" and "locked" parameters and leave the rest "disabled"
H.setEmbeddingParameter("Name", "Editable");
H.setEmbeddingParameter("Source", "Locked");
H.publishChanges("dashboard", ({ request }) => {
assert.deepEqual(request.body.embedding_params, {
id: "disabled",
name: "enabled",
source: "locked",
user_id: "disabled",
not_used: "disabled",
});
});
H.closeStaticEmbeddingModal();
H.editDashboard();
// Check each parameter's required state
assertRequiredEnabledForName({ name: "Name", enabled: true });
assertRequiredEnabledForName({ name: "Source", enabled: true });
// The rest must be disabled
assertRequiredEnabledForName({ name: "Id", enabled: false });
assertRequiredEnabledForName({ name: "User", enabled: false });
assertRequiredEnabledForName({ name: "Not Used Filter", enabled: false });
});
it("should render cursor pointer on hover over a toggle (metabase#46223)", () => {
H.visitDashboard("@dashboardId");
cy.findAllByTestId("parameter-value-widget-target")
.first()
.realHover()
.should("have.css", "cursor", "pointer");
});
});
context("API", () => {
beforeEach(() => {
cy.get("@dashboardId").then((dashboardId) => {
cy.request("PUT", `/api/dashboard/${dashboardId}`, {
embedding_params: {
id: "enabled",
name: "enabled",
source: "enabled",
user_id: "enabled",
},
enable_embedding: true,
});
const payload = {
resource: { dashboard: dashboardId },
params: {},
};
H.visitEmbeddedPage(payload);
// wait for the results to load
cy.contains("Test Dashboard");
cy.contains("2,500");
});
});
it("should work for all filters", () => {
cy.log("should allow searching PEOPLE.ID by PEOPLE.NAME");
openFilterOptions("Id");
H.popover().within(() => {
H.fieldValuesCombobox().type("Aly");
cy.contains("Alycia McCullough");
});
// close the suggestions popover
H.popover()
.first()
.within(() => {
H.fieldValuesCombobox().blur();
});
cy.log("should allow searching PEOPLE.NAME by PEOPLE.NAME");
openFilterOptions("Name");
H.popover().within(() => {
H.fieldValuesCombobox().type("{backspace}Aly");
cy.findByText("Alycia McCullough").should("be.visible");
});
// close the suggestions popover
H.popover()
.first()
.within(() => {
H.fieldValuesCombobox().blur();
});
cy.log("should show values for PEOPLE.SOURCE");
openFilterOptions("Source");
H.popover().contains("Affiliate");
cy.log("should allow searching ORDER.USER_ID by PEOPLE.NAME");
openFilterOptions("User");
H.popover().within(() => {
H.fieldValuesCombobox().type("Aly");
cy.contains("Alycia McCullough");
});
// close the suggestions popover
H.popover()
.first()
.within(() => {
H.fieldValuesCombobox().blur();
});
cy.log("should accept url parameters");
cy.location().then((location) =>
cy.visit(`${location.origin}${location.pathname}?id=1&id=3`),
);
cy.findByTestId("scalar-value").contains("2");
});
});
it("should allow searching dashboard parameters in preview embed modal", () => {
H.visitDashboard("@dashboardId");
cy.get("@dashboardId").then((dashboardId) => {
H.openLegacyStaticEmbeddingModal({
resource: "dashboard",
resourceId: dashboardId,
activeTab: "parameters",
previewMode: "preview",
});
});
H.modal().within(() => {
// Set the Name parameter to enabled so we can test searching
cy.findByText("Name")
.parent()
.within(() => {
cy.findByText("Disabled").click();
});
});
H.popover().findByText("Editable").click();
// Test the preview iframe parameter search functionality
H.getIframeBody().within(() => {
// Open the Name filter dropdown
cy.findByTestId("dashboard-parameters-widget-container")
.findByText("Name")
.click();
// Test searching for names containing specific text
cy.findByPlaceholderText("Search by Name").type("Af");
// Verify that search results are filtered
H.popover().within(() => {
// Should show names containing "Af"
cy.findByText("Afton Lesch").should("be.visible");
// Should not show names that don't match the search
cy.findByText("Lina Heaney").should("not.exist");
});
});
});
it("should render error message when `params` is not an object (metabase#14474)", () => {
cy.get("@dashboardId").then((dashboardId) => {
cy.request("PUT", `/api/dashboard/${dashboardId}`, {
embedding_params: {
id: "enabled",
name: "enabled",
source: "enabled",
user_id: "enabled",
},
enable_embedding: true,
});
const invalidParamsValue = [];
const payload = {
resource: { dashboard: dashboardId },
params: invalidParamsValue,
};
H.visitEmbeddedPage(payload);
H.getDashboardCard()
.findByText("There was a problem displaying this chart.")
.should("be.visible");
});
});
it("should render error without crashing when embed query returns error (metabase#34954)", () => {
const categoryTemplateTag = {
type: "text",
name: "category",
id: "377a4a4a-179e-4d86-8263-f3b3887df15f",
"display-name": "Category",
};
const createdAtTemplateTag = {
type: "dimension",
name: "createdAt",
id: "ae3bd89b-1b94-47db-9020-8ee74afdb67a",
"display-name": "CreatedAt",
dimension: ["field", PRODUCTS.CREATED_AT, null],
"widget-type": "date/month-year",
};
const questionDetails = {
native: {
query:
"Select * from products Where category = {{category}} [[and {{createdAt}}]]",
"template-tags": {
category: categoryTemplateTag,
createdAt: createdAtTemplateTag,
},
},
};
const dashboardCategoryParameter = createMockParameter({
name: "Category",
slug: "category",
id: "9cd1ee78",
type: "string/=",
sectionId: "string",
values_query_type: "none",
});
const dashboardCreatedAtParameter = createMockParameter({
name: "Created At",
slug: "createdAt",
id: "98831577",
type: "date/month-year",
sectionId: "date",
});
const dashboardDetails = {
name: "dashboard with parameters",
parameters: [dashboardCategoryParameter, dashboardCreatedAtParameter],
};
H.createNativeQuestionAndDashboard({
questionDetails,
dashboardDetails,
}).then(({ body: { card_id, dashboard_id } }) => {
cy.wrap(dashboard_id).as("dashboardId2");
H.addOrUpdateDashboardCard({
card_id,
dashboard_id,
card: {
parameter_mappings: [
{
parameter_id: dashboardCategoryParameter.id,
card_id,
target: ["variable", ["template-tag", categoryTemplateTag.name]],
},
{
parameter_id: dashboardCreatedAtParameter.id,
card_id,
target: [
"dimension",
["template-tag", createdAtTemplateTag.name],
],
},
],
visualization_settings: {
"card.hide_empty": true,
},
},
});
cy.request("PUT", `/api/dashboard/${dashboard_id}`, {
embedding_params: {
category: "enabled",
createdAt: "enabled",
},
enable_embedding: true,
});
const payload = {
resource: { dashboard: dashboard_id },
params: {},
};
H.visitEmbeddedPage(payload);
});
cy.log("The whole page would have crashed before the fix at this point");
H.getDashboardCard()
.findByText("There was a problem displaying this chart.")
.should("be.visible");
cy.log("Add a filter to complete the query");
H.filterWidget().findByPlaceholderText("Category").type("Widget{enter}");
H.getDashboardCard()
.findByText("Practical Bronze Computer")
.should("be.visible");
cy.log("test downloading result (metabase#36721)");
H.getDashboardCard().realHover();
H.downloadAndAssert({
fileType: "csv",
isDashboard: true,
isEmbed: true,
downloadUrl: "/api/embed/dashboard/*/dashcard/*/card/*/csv*",
downloadMethod: "GET",
});
cy.log(
"The PDF download button should be clickable when there is no title, but has parameters (metabase#59503)",
);
cy.get("@dashboardId2").then((dashboardId) => {
const payload = {
resource: { dashboard: dashboardId },
params: {},
};
H.visitEmbeddedPage(payload, {
pageStyle: {
downloads: true,
titled: false,
},
});
});
cy.findByTestId("export-as-pdf-button").should("be.visible").click();
});
it("should send 'X-Metabase-Client' header for api requests", () => {
cy.intercept("GET", "api/embed/dashboard/*").as("getEmbeddedDashboard");
cy.get("@dashboardId").then((dashboardId) => {
cy.request("PUT", `/api/dashboard/${dashboardId}`, {
embedding_params: {},
enable_embedding: true,
});
const payload = {
resource: { dashboard: dashboardId },
params: {},
};
H.visitEmbeddedPage(payload, {
onBeforeLoad: (window) => {
window.Cypress = undefined;
},
});
cy.wait("@getEmbeddedDashboard").then(({ request }) => {
expect(request?.headers?.["x-metabase-client"]).to.equal(
"embedding-iframe",
);
});
});
});
});
describe("scenarios > embedding > dashboard parameters with defaults", () => {
beforeEach(() => {
H.restore();
cy.signInAsAdmin();
H.createNativeQuestionAndDashboard({
questionDetails: questionDetailsWithDefaults,
dashboardDetails,
}).then(({ body: { id, card_id, dashboard_id } }) => {
cy.wrap(dashboard_id).as("dashboardId");
mapParameters({ id, card_id, dashboard_id });
});
H.visitDashboard("@dashboardId");
});
it("card parameter defaults should apply for disabled parameters, but not for editable or locked parameters", () => {
cy.get("@dashboardId").then((dashboardId) => {
H.openLegacyStaticEmbeddingModal({
resource: "dashboard",
resourceId: dashboardId,
activeTab: "parameters",
});
});
// ID param is disabled by default
H.setEmbeddingParameter("Name", "Editable");
H.setEmbeddingParameter("Source", "Locked");
H.publishChanges("dashboard", ({ request }) => {
assert.deepEqual(request.body.embedding_params, {
id: "disabled",
source: "locked",
name: "enabled",
user_id: "disabled",
not_used: "disabled",
});
});
cy.get("@dashboardId").then((dashboardId) => {
const payload = {
resource: { dashboard: dashboardId },
params: { source: [] },
};
H.visitEmbeddedPage(payload);
// wait for the results to load
// The ID default (1 and 2) should apply, because it is disabled.
// The Name default ('Lina Heaney') should not apply, because the Name param is editable and unset
// The Source default ('Facebook') should not apply because the param is locked but the value is unset
// If either the Name or Source default applied the result would be 0.
cy.contains("Test Dashboard");
cy.findByTestId("scalar-value").invoke("text").should("eq", "2");
});
//visitIframe();
});
it("locked parameters require a value to be specified in the JWT", () => {
const nameParameter = dashboardDetails.parameters.find(
(parameter) => parameter.name === "Name",
);
const sourceParameter = dashboardDetails.parameters.find(
(parameter) => parameter.name === "Source",
);
cy.get("@dashboardId").then((dashboardId) => {
cy.request("PUT", `api/dashboard/${dashboardId}`, {
enable_embedding: true,
embedding_params: {
[nameParameter.slug]: "enabled",
[sourceParameter.slug]: "locked",
},
});
const payload = {
resource: { dashboard: dashboardId },
params: { source: null },
};
H.visitEmbeddedPage(payload);
});
// The Source parameter is 'locked', and no value has been specified in the token,
// thus the API responds with "You must specify a value for :source in the JWT."
// and the card will not display.
H.getDashboardCard()
.findByText("There was a problem displaying this chart.")
.should("be.visible");
});
it("locked parameters should still render results in the preview by default (metabase#47570)", () => {
const nameParameter = dashboardDetails.parameters.find(
(parameter) => parameter.name === "Name",
);
const sourceParameter = dashboardDetails.parameters.find(
(parameter) => parameter.name === "Source",
);
cy.get("@dashboardId").then((dashboardId) => {
cy.request("PUT", `api/dashboard/${dashboardId}`, {
enable_embedding: true,
embedding_params: {
[nameParameter.slug]: "enabled",
[sourceParameter.slug]: "locked",
},
});
});
H.visitDashboard("@dashboardId");
cy.get("@dashboardId").then((dashboardId) => {
H.openLegacyStaticEmbeddingModal({
resource: "dashboard",
resourceId: dashboardId,
activeTab: "parameters",
unpublishBeforeOpen: false,
});
});
H.visitIframe();
cy.log("should show card results by default");
H.getDashboardCard().findByText("2").should("be.visible");
H.getDashboardCard().findByText("test question").should("be.visible");
});
});
describe("scenarios > embedding > dashboard appearance", () => {
const originalBaseUrl = Cypress.config("baseUrl");
beforeEach(() => {
// Reset the baseUrl to the default value
// needed because we do `Cypress.config("baseUrl", null);` in the iframe test
Cypress.config("baseUrl", originalBaseUrl);
H.restore();
cy.signInAsAdmin();
H.activateToken("pro-self-hosted");
});
it("should not rerender the static embed preview unnecessarily (metabase#38271)", () => {
const textFilter = createMockParameter({
id: "3",
name: "Text filter",
slug: "filter-text",
type: "string/contains",
sectionId: "string",
});
const dashboardDetails = {
name: "dashboard name",
enable_embedding: true,
embedding_params: {
/**
* Make sure the parameter is shown in embed preview, because it previously
* caused the iframe to rerender even when only the hash part of the embed
* preview URL is changed.
*
* @see useSyncedQueryString in frontend/src/metabase/hooks/use-synced-query-string.ts
*/
[textFilter.slug]: "enabled",
},
parameters: [textFilter],
};
const questionDetails = {
name: "Orders",
query: {
"source-table": ORDERS_ID,
},
};
H.createQuestionAndDashboard({
questionDetails,
dashboardDetails,
}).then(({ body: { dashboard_id } }) => {
H.visitDashboard(dashboard_id);
cy.intercept(
"GET",
"api/preview_embed/dashboard/*",
cy.spy().as("previewEmbedSpy"),
).as("previewEmbed");
H.openLegacyStaticEmbeddingModal({
resource: "dashboard",
resourceId: dashboard_id,
activeTab: "parameters",
previewMode: "preview",
});
});
cy.wait("@previewEmbed");
H.modal().within(() => {
cy.findByRole("tab", { name: "Look and Feel" }).click();
cy.get("@previewEmbedSpy").should("have.callCount", 1);
cy.log(
'Embed preview requests should not have "X-Metabase-Client: embedding-iframe" header (EMB-930)',
);
cy.get("@previewEmbed").then(({ request }) => {
expect(request?.headers?.["x-metabase-client"]).to.not.equal(
"embedding-iframe",
);
});
cy.log("Assert dashboard theme");
H.getIframeBody()
.findByTestId("embed-frame")
.invoke("attr", "data-embed-theme")
.then((embedTheme) => {
expect(embedTheme).to.eq("light"); // default value
});
// We're getting an input element which is 0x0 in size
cy.findByLabelText("Dark").click({ force: true });
cy.wait(1000);
H.getIframeBody()
.findByTestId("embed-frame")
.invoke("attr", "data-embed-theme")
.then((embedTheme) => {
expect(embedTheme).to.eq("night");
});
cy.get("@previewEmbedSpy").should("have.callCount", 1);
cy.log("Assert dashboard title");
H.getIframeBody().findByText(dashboardDetails.name).should("exist");
// We're getting an input element which is 0x0 in size
cy.findByLabelText("Dashboard title").click({ force: true });
H.getIframeBody().findByText(dashboardDetails.name).should("not.exist");
cy.get("@previewEmbedSpy").should("have.callCount", 1);
cy.log("Assert dashboard border");
H.getIframeBody()
.findByTestId("embed-frame")
.should("have.css", "border-top-width", "1px");
// We're getting an input element which is 0x0 in size
cy.findByLabelText("Dashboard border").click({ force: true });
H.getIframeBody()
.findByTestId("embed-frame")
.should("have.css", "border-top-width", "0px");
cy.get("@previewEmbedSpy").should("have.callCount", 1);
cy.log("Assert font");
H.getIframeBody().should("have.css", "font-family", "Lato, sans-serif");
cy.findByLabelText("Font").click();
});
// Since the select dropdown is rendered outside of the modal, we need to exit the modal context first.
H.selectDropdown().findByText("Oswald").click();
H.modal().within(() => {
H.getIframeBody().should("have.css", "font-family", "Oswald, sans-serif");
cy.get("@previewEmbedSpy").should("have.callCount", 1);
});
});
it("should not rerender the static dashboard with tabs preview unnecessarily (metabase#46378)", () => {
const textFilter = createMockParameter({
id: "3",
name: "Text filter",
slug: "filter-text",
type: "string/contains",
sectionId: "string",
});
const TAB_1 = { id: "11", name: "Tab 1" };
const TAB_2 = { id: "12", name: "Tab 2" };
const dashboardDetails = {
name: "dashboard name",
enable_embedding: true,
embedding_params: {
/**
* Make sure the parameter is shown in embed preview, because it previously
* caused the iframe to rerender even when only the hash part of the embed
* preview URL is changed.
*
* @see useSyncedQueryString in frontend/src/metabase/hooks/use-synced-query-string.ts
*/
[textFilter.slug]: "enabled",
},
parameters: [textFilter],
tabs: [TAB_1, TAB_2],
};
const questionDetails = {
name: "Orders",
query: {
"source-table": ORDERS_ID,
},
};
H.createQuestion(questionDetails)
.then(({ body: { id: card_id } }) => {
H.createDashboardWithTabs({
...dashboardDetails,
dashcards: [
{
id: -1,
card_id,
dashboard_tab_id: TAB_1.id,
row: 0,
col: 0,
size_x: 8,
size_y: 12,
},
],
});
})
.then((dashboard) => {
H.visitDashboard(dashboard.id);
cy.intercept(
"GET",
"api/preview_embed/dashboard/*",
cy.spy().as("previewEmbedSpy"),
).as("previewEmbed");
H.openLegacyStaticEmbeddingModal({
resource: "dashboard",
resourceId: dashboard.id,
activeTab: "parameters",
previewMode: "preview",
});
});
cy.wait("@previewEmbed");
H.modal().within(() => {
cy.findByRole("tab", { name: "Look and Feel" }).click();
cy.get("@previewEmbedSpy").should("have.callCount", 1);
cy.log("Assert dashboard theme");
H.getIframeBody()
.findByTestId("embed-frame")
.invoke("attr", "data-embed-theme")
.then((embedTheme) => {
expect(embedTheme).to.eq("light"); // default value
});
// We're getting an input element which is 0x0 in size
cy.findByLabelText("Dark").click({ force: true });
cy.wait(1000);
H.getIframeBody()
.findByTestId("embed-frame")
.invoke("attr", "data-embed-theme")
.then((embedTheme) => {
expect(embedTheme).to.eq("night");
});
cy.get("@previewEmbedSpy").should("have.callCount", 1);
cy.log("Assert dashboard title");
H.getIframeBody().findByText(dashboardDetails.name).should("exist");
// We're getting an input element which is 0x0 in size
cy.findByLabelText("Dashboard title").click({ force: true });
H.getIframeBody().findByText(dashboardDetails.name).should("not.exist");
cy.get("@previewEmbedSpy").should("have.callCount", 1);
cy.log("Assert dashboard border");
H.getIframeBody()
.findByTestId("embed-frame")
.should("have.css", "border-top-width", "1px");
// We're getting an input element which is 0x0 in size
cy.findByLabelText("Dashboard border").click({ force: true });
H.getIframeBody()
.findByTestId("embed-frame")
.should("have.css", "border-top-width", "0px");
cy.get("@previewEmbedSpy").should("have.callCount", 1);
cy.log("Assert font");
H.getIframeBody().should("have.css", "font-family", "Lato, sans-serif");
cy.findByLabelText("Font").click();
});
// Since the select dropdown is rendered outside of the modal, we need to exit the modal context first.
H.selectDropdown().findByText("Oswald").click();
H.modal().within(() => {
H.getIframeBody().should("have.css", "font-family", "Oswald, sans-serif");
cy.get("@previewEmbedSpy").should("have.callCount", 1);
});
});
it("should resize iframe to dashboard content size (metabase#47061)", () => {
const dashboardDetails = {
name: "dashboard name",
enable_embedding: true,
};
const questionDetails = {
name: "Line chart",
query: {
"source-table": ORDERS_ID,
aggregation: [["count"]],
breakout: [
[
"field",
ORDERS.CREATED_AT,
{ "base-type": "type/DateTime", "temporal-unit": "month" },
],
],
limit: 5,
},
display: "bar",
};
H.createQuestion(questionDetails)
.then(({ body: { id: card_id } }) => {
H.createDashboardWithTabs({
...dashboardDetails,
dashcards: [
{
id: -1,
card_id,
row: 0,
col: 0,
size_x: 8,
size_y: 20,
},
],
});
})
.then((dashboard) => {
return H.getEmbeddedPageUrl({
resource: { dashboard: dashboard.id },
params: {},
});
})
.then((urlOptions) => {
const baseUrl = Cypress.config("baseUrl");
Cypress.config("baseUrl", null);
cy.visit(
`e2e/test/scenarios/embedding/embedding-dashboard.html?iframeUrl=${baseUrl + urlOptions.url}`,
);
});
H.getIframeBody().within(() => {
cy.findByText(questionDetails.name).should("exist");
cy.findByText("April 2022").should("exist");
// TODO: Enable this once we fix the flakiness https://app.trunk.io/metabase/flaky-tests/test/facb35f0-6d76-5e7d-b21c-40401bbc3ff6?repo=metabase%2Fmetabase
// (metabase#49537)
// chartPathWithFillColor("#509EE3").last().realHover();
// echartsTooltip().should("be.visible");
// assertEChartsTooltip({
// header: "August 2022",
// rows: [
// {
// name: "Count",
// value: "79",
// secondaryValue: "+23.44%",
// },
// ],
// });
});
cy.get("#iframe").should(($iframe) => {
const [iframe] = $iframe;
expect(iframe.clientHeight).to.be.greaterThan(1000);
});
});
it("should allow to set locale from the `#locale` hash parameter (metabase#50182)", () => {
cy.request("PUT", `/api/dashboard/${ORDERS_DASHBOARD_ID}`, {
enable_embedding: true,
});
cy.signOut();
// We don't have a de-CH.json file, so it should fallback to de.json, see metabase#51039 for more details
cy.intercept("/app/locales/de.json").as("deLocale");
H.visitEmbeddedPage(
{
resource: { dashboard: ORDERS_DASHBOARD_ID },
params: {},
},
{
additionalHashOptions: {
locale: "de-CH",
},
},
);
cy.wait("@deLocale");
H.main().findByText("Februar 11, 2025, 9:40 PM");
cy.findByRole("button", {
name: "Automatische Aktualisierung",
}).should("exist");
cy.url().should("include", "locale=de");
});
it("should allow to set font from the `font` hash parameter", () => {
cy.request("PUT", `/api/dashboard/${ORDERS_DASHBOARD_ID}`, {
enable_embedding: true,
});
cy.signOut();
H.visitEmbeddedPage(
{
resource: { dashboard: ORDERS_DASHBOARD_ID },
params: {},
},
{
additionalHashOptions: {
font: "Roboto",
},
},
);
H.main().should("have.css", "font-family", "Roboto, sans-serif");
});
it("should disable background via `#background=false` hash parameter when rendered inside an iframe (metabase#62391)", () => {
cy.request("PUT", `/api/dashboard/${ORDERS_DASHBOARD_ID}`, {
enable_embedding: true,
});
cy.signOut();
H.visitEmbeddedPage(
{
resource: { dashboard: ORDERS_DASHBOARD_ID },
params: {},
},
{
additionalHashOptions: {
background: "false",
},
onBeforeLoad: (window) => {
window.overrideIsWithinIframe = true;
},
},
);
cy.findByTestId("embed-frame").should("exist");
cy.get("body.mb-wrapper").should(
"have.css",
"background-color",
"rgba(0, 0, 0, 0)",
);
cy.window().then((win) => {
delete win.overrideIsWithinIframe;
});
});
it("should not disable background via `#background=false` hash parameter when rendered without an iframe", () => {
cy.request("PUT", `/api/dashboard/${ORDERS_DASHBOARD_ID}`, {
enable_embedding: true,
});
cy.signOut();
H.visitEmbeddedPage(
{
resource: { dashboard: ORDERS_DASHBOARD_ID },
params: {},
},
{
additionalHashOptions: {
background: "false",
},
},
);
cy.findByTestId("embed-frame").should("exist");
cy.get("body.mb-wrapper").should(
"not.have.css",
"background-color",
"rgba(0, 0, 0, 0)",
);
});
it("should use transparent pivot table cells in static embedding's dark mode (metabase#61741)", () => {
const testQuery = {
type: "query",
query: {
"source-table": ORDERS_ID,
aggregation: [["count"]],
breakout: [
[
"field",
PEOPLE.SOURCE,
{ "base-type": "type/Text", "source-field": ORDERS.USER_ID },
],
[
"field",
PRODUCTS.CATEGORY,
{ "base-type": "type/Text", "source-field": ORDERS.PRODUCT_ID },
],
],
},
database: 1,
};
const pivotQuestionDetails = {
name: "Pivot Table Test",
query: testQuery.query,
display: "pivot",
};
const pivotDashboardDetails = {
name: "Pivot Dashboard Test",
enable_embedding: true,
embedding_params: {},
};
H.createQuestionAndDashboard({
questionDetails: pivotQuestionDetails,
dashboardDetails: pivotDashboardDetails,
}).then(({ body: { dashboard_id } }) => {
H.visitDashboard(dashboard_id);
H.openLegacyStaticEmbeddingModal({
resource: "dashboard",
resourceId: dashboard_id,
activeTab: "parameters",
previewMode: "preview",
});
H.modal().within(() => {
cy.findByRole("tab", { name: "Look and Feel" }).click();
cy.log("wait until we are at the night theme");
cy.findByLabelText("Dark").click({ force: true });
H.getIframeBody()
.findByTestId("embed-frame")
.invoke("attr", "data-embed-theme")
.should((embedTheme) => {
expect(embedTheme).to.eq("night");
});
H.getIframeBody().findByTestId("pivot-table").should("be.visible");
H.getIframeBody().within(() => {
cy.findAllByTestId("pivot-table-cell").should(
"have.length.greaterThan",
0,
);
cy.log("dashcard should have dark background");
cy.findByTestId("dashcard").should(
"have.css",
"background-color",
"rgb(7, 23, 34)",
);
cy.log("pivot table cell background should be transparent");
cy.findAllByRole("grid")
.first()
.findAllByTestId("pivot-table-cell")
.first()
.should("have.css", "background-color", "rgba(48, 61, 70, 0.1)");
cy.log("pivot table cell color should be white");
cy.findByText("Row totals")
.should("be.visible")
.should("have.css", "color", "rgba(255, 255, 255, 0.95)");
});
});
});
});
});
function openFilterOptions(name) {
H.filterWidget().contains(name).click();
}
function getDashboardFilter(name) {
return cy
.findByTestId("edit-dashboard-parameters-widget-container")
.findByText(name);
}
function assertRequiredEnabledForName({ name, enabled }) {
getDashboardFilter(name).click();
H.getRequiredToggle().should(enabled ? "be.enabled" : "not.be.enabled");
}