-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Keeps preview across different tabs #38271
Changes from all commits
1c22b0c
e4de1e1
dc3fb41
60b3d2a
1f4a7db
0cadbea
22bc55f
76447fa
8b848a8
574b62a
bc52e46
fe2a439
35029f0
2b8172c
88ce7d0
661f6a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,9 @@ import { | |
downloadAndAssert, | ||
assertSheetRowsCount, | ||
modal, | ||
getIframeBody, | ||
describeEE, | ||
setTokenFeatures, | ||
dashboardParametersContainer, | ||
goToTab, | ||
} from "e2e/support/helpers"; | ||
|
@@ -23,7 +26,7 @@ import { | |
mapParameters, | ||
} from "./shared/embedding-dashboard"; | ||
|
||
const { ORDERS, PEOPLE, PRODUCTS } = SAMPLE_DATABASE; | ||
const { ORDERS, PEOPLE, PRODUCTS, ORDERS_ID } = SAMPLE_DATABASE; | ||
|
||
describe("scenarios > embedding > dashboard parameters", () => { | ||
beforeEach(() => { | ||
|
@@ -471,6 +474,112 @@ describe("scenarios > embedding > dashboard parameters with defaults", () => { | |
}); | ||
}); | ||
|
||
describeEE("scenarios > embedding > dashboard appearance", () => { | ||
beforeEach(() => { | ||
restore(); | ||
cy.signInAsAdmin(); | ||
setTokenFeatures("all"); | ||
}); | ||
|
||
it("should not rerender the static embed preview unnecessarily (metabase#38271)", () => { | ||
const textFilter = { | ||
id: "3", | ||
name: "Text filter", | ||
slug: "filter-text", | ||
type: "string/contains", | ||
}; | ||
|
||
const dashboardDetails = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @npretto I added this filter, since I found out today that having a parameter will cause the appearance page preview to rerender every time we change any settings |
||
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, | ||
}, | ||
}; | ||
cy.createQuestionAndDashboard({ | ||
questionDetails, | ||
dashboardDetails, | ||
}).then(({ body: { dashboard_id } }) => { | ||
visitDashboard(dashboard_id); | ||
}); | ||
|
||
cy.intercept( | ||
"GET", | ||
"api/preview_embed/dashboard/*", | ||
cy.spy().as("previewEmbedSpy"), | ||
).as("previewEmbed"); | ||
|
||
openStaticEmbeddingModal({ | ||
activeTab: "parameters", | ||
previewMode: "preview", | ||
// EE users don't have to accept terms | ||
acceptTerms: false, | ||
}); | ||
|
||
cy.wait("@previewEmbed"); | ||
|
||
modal().within(() => { | ||
cy.findByRole("tab", { name: "Appearance" }).click(); | ||
cy.get("@previewEmbedSpy").should("have.callCount", 1); | ||
|
||
cy.log("Assert dashboard theme"); | ||
getIframeBody() | ||
.findByTestId("embed-frame") | ||
.should("not.have.class", "Theme--transparent"); | ||
// We're getting an input element which is 0x0 in size | ||
cy.findByLabelText("Transparent").click({ force: true }); | ||
getIframeBody() | ||
.findByTestId("embed-frame") | ||
.should("have.class", "Theme--transparent"); | ||
cy.get("@previewEmbedSpy").should("have.callCount", 1); | ||
|
||
cy.log("Assert dashboard title"); | ||
getIframeBody().findByText(dashboardDetails.name).should("exist"); | ||
// We're getting an input element which is 0x0 in size | ||
cy.findByLabelText("Dashboard title").click({ force: true }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of finding the element with that label, can we click the label? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a good point. Do you have an example where we click the labels? I looked up testing-library, and they could only get the input using various matchers e.g. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that's ok, I tried with https://testing-playground.com/ and it only prompted by findByText |
||
getIframeBody().findByText(dashboardDetails.name).should("not.exist"); | ||
cy.get("@previewEmbedSpy").should("have.callCount", 1); | ||
|
||
cy.log("Assert dashboard border"); | ||
getIframeBody() | ||
.findByTestId("embed-frame") | ||
.should("have.css", "border-top-width", "1px"); | ||
// We're getting an input element which is 0x0 in size | ||
cy.findByLabelText("Border").click({ force: true }); | ||
getIframeBody() | ||
.findByTestId("embed-frame") | ||
.should("have.css", "border-top-width", "0px"); | ||
cy.get("@previewEmbedSpy").should("have.callCount", 1); | ||
|
||
cy.log("Assert font"); | ||
getIframeBody().should("have.css", "font-family", "Lato, sans-serif"); | ||
cy.findByLabelText("Font").click(); | ||
}); | ||
|
||
// Since the select popover is rendered outside of the modal, we need to exit the modal context first. | ||
popover().findByText("Oswald").click(); | ||
modal().within(() => { | ||
getIframeBody().should("have.css", "font-family", "Oswald, sans-serif"); | ||
cy.get("@previewEmbedSpy").should("have.callCount", 1); | ||
}); | ||
}); | ||
}); | ||
|
||
function openFilterOptions(name) { | ||
filterWidget().contains(name).click(); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,7 @@ import { | |
|
||
const { ORDERS, PRODUCTS } = SAMPLE_DATABASE; | ||
|
||
describe("scenarios > embedding > questions ", () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
describe("scenarios > embedding > questions", () => { | ||
beforeEach(() => { | ||
restore(); | ||
cy.signInAsAdmin(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ import { | |
addOrUpdateDashboardCard, | ||
dashboardHeader, | ||
modal, | ||
getIframeBody, | ||
} from "e2e/support/helpers"; | ||
import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database"; | ||
|
||
|
@@ -52,7 +53,9 @@ const dashboardDetails = { | |
|
||
describe("issue 37914", () => { | ||
beforeEach(() => { | ||
cy.intercept("GET", "/api/embed/dashboard/**").as("getEmbed"); | ||
cy.intercept("GET", "/api/preview_embed/dashboard/**").as( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The preview didn't seem to work in Cypress before. I've fixed that in |
||
"previewDashboard", | ||
); | ||
|
||
restore(); | ||
cy.signInAsAdmin(); | ||
|
@@ -115,7 +118,7 @@ describe("issue 37914", () => { | |
cy.findByText("Preview").click(); | ||
|
||
// Makes it less likely to flake. | ||
cy.wait("@getEmbed"); | ||
cy.wait("@previewDashboard"); | ||
|
||
getIframeBody().within(() => { | ||
cy.log( | ||
|
@@ -142,32 +145,3 @@ describe("issue 37914", () => { | |
}); | ||
}); | ||
}); | ||
|
||
// Code grabbed from https://www.cypress.io/blog/2020/02/12/working-with-iframes-in-cypress | ||
const getIframeDocument = () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We seem to already have an existing util which does exactly this. So I'm removing a one-off util. |
||
return ( | ||
cy | ||
.get("iframe") | ||
// Cypress yields jQuery element, which has the real | ||
// DOM element under property "0". | ||
// From the real DOM iframe element we can get | ||
// the "document" element, it is stored in "contentDocument" property | ||
// Cypress "its" command can access deep properties using dot notation | ||
// https://on.cypress.io/its | ||
.its("0.contentDocument") | ||
.should("exist") | ||
); | ||
}; | ||
|
||
const getIframeBody = () => { | ||
// get the document | ||
return ( | ||
getIframeDocument() | ||
// automatically retries until body is loaded | ||
.its("body") | ||
.should("not.be.undefined") | ||
// wraps "body" DOM element to allow | ||
// chaining more Cypress commands, like ".find(...)" | ||
.then(cy.wrap) | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,9 @@ import { ORDERS_ID } from "metabase-types/api/mocks/presets"; | |
describeEE("issue 26988", () => { | ||
beforeEach(() => { | ||
restore(); | ||
cy.intercept("GET", "/api/embed/dashboard/*").as("dashboard"); | ||
cy.intercept("GET", "/api/preview_embed/dashboard/*").as( | ||
"previewDashboard", | ||
); | ||
|
||
cy.signInAsAdmin(); | ||
setTokenFeatures("all"); | ||
|
@@ -40,7 +42,7 @@ describeEE("issue 26988", () => { | |
acceptTerms: false, | ||
}); | ||
|
||
cy.wait("@dashboard"); | ||
cy.wait("@previewDashboard"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The preview didn't seem to work in Cypress before. I've fixed that in |
||
getIframeBody().should("have.css", "font-family", `Lato, sans-serif`); | ||
|
||
cy.findByLabelText("Playing with appearance options") | ||
|
@@ -49,13 +51,11 @@ describeEE("issue 26988", () => { | |
.click(); | ||
popover().findByText("Oswald").click(); | ||
|
||
cy.wait("@dashboard"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we're not rerendering the embed preview when changing appearance settings anymore. There would be only one |
||
getIframeBody().should("have.css", "font-family", `Oswald, sans-serif`); | ||
|
||
cy.get("@font-control").click(); | ||
popover().findByText("Slabo 27px").click(); | ||
|
||
cy.wait("@dashboard"); | ||
getIframeBody().should( | ||
"have.css", | ||
"font-family", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,8 +42,12 @@ export const DashboardControls = ComposedComponent => | |
this.loadDashboardParams(); | ||
} | ||
|
||
componentDidUpdate() { | ||
this.updateDashboardParams(); | ||
componentDidUpdate(prevProps) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've tried to not do 2-way sync between React state <-> URL and using the URL as a source of truth instead, but that failed a lot of tests. So, the safest option at this point is to do 2-way sync. |
||
if (prevProps.location !== this.props.location) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. location can change from 2 actions
For the first, it won't trigger the render loop because we'll set the same values to redux state. |
||
this.syncUrlHashToState(); | ||
} else { | ||
this.syncStateToUrlHash(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Other than the URL update, we can call the same function that will try to sync the state to the URL |
||
} | ||
this._showNav(!this.state.isFullscreen); | ||
} | ||
|
||
|
@@ -72,7 +76,16 @@ export const DashboardControls = ComposedComponent => | |
this.setHideParameters(options.hide_parameters); | ||
}; | ||
|
||
updateDashboardParams = () => { | ||
syncUrlHashToState() { | ||
const { location } = this.props; | ||
|
||
const { refresh, fullscreen, theme } = parseHashOptions(location.hash); | ||
this.setRefreshPeriod(refresh); | ||
this.setFullscreen(fullscreen); | ||
this.setTheme(theme); | ||
} | ||
|
||
syncStateToUrlHash = () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename |
||
const { location, replace } = this.props; | ||
|
||
const options = parseHashOptions(location.hash); | ||
|
@@ -224,7 +237,6 @@ export const DashboardControls = ComposedComponent => | |
hasNightModeToggle={this.state.theme !== "transparent"} | ||
setRefreshElapsedHook={this.setRefreshElapsedHook} | ||
loadDashboardParams={this.loadDashboardParams} | ||
updateDashboardParams={this.updateDashboardParams} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this was unused. You can verify by using a text search of |
||
onNightModeChange={this.setNightMode} | ||
onFullscreenChange={this.setFullscreen} | ||
onRefreshPeriodChange={this.setRefreshPeriod} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,7 +26,7 @@ window.METABASE = true; | |
// used for detecting if we're previewing an embed | ||
export const IFRAMED_IN_SELF = (function () { | ||
try { | ||
return window.self !== window.top && window.top.METABASE; | ||
return window.self !== window.parent && window.parent.METABASE; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This was failing in Cypress because there are 3 frames in Cypress
But in our application, there are only 2 frames
For this particular code, when running in Cypress Changing it to |
||
} catch (e) { | ||
return false; | ||
} | ||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -1,5 +1,6 @@ | ||||
import type { ReactNode } from "react"; | ||||
import { useState } from "react"; | ||||
import { useEffect, useState } from "react"; | ||||
|
||||
import { withRouter } from "react-router"; | ||||
import { connect } from "react-redux"; | ||||
import cx from "classnames"; | ||||
|
@@ -24,6 +25,8 @@ import type { | |||
} from "metabase-types/api"; | ||||
import type { State } from "metabase-types/store"; | ||||
|
||||
import { useDispatch } from "metabase/lib/redux"; | ||||
import { setOptions } from "metabase/redux/embed"; | ||||
import type Question from "metabase-lib/Question"; | ||||
import { getValuePopulatedParameters } from "metabase-lib/parameters/utils/parameter-values"; | ||||
|
||||
|
@@ -104,6 +107,11 @@ function EmbedFrame({ | |||
initializeIframeResizer(() => setInnerScroll(false)); | ||||
}); | ||||
|
||||
const dispatch = useDispatch(); | ||||
useEffect(() => { | ||||
dispatch(setOptions(location)); | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is needed for the font change to work, since font is being get from the selector which reads This essentially works the same way when we first load the embedding
|
||||
}, [dispatch, location]); | ||||
|
||||
const { | ||||
bordered = isWithinIframe(), | ||||
titled = true, | ||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a new test to ensure: