From 51fc3624aadbf7bb010cbf64ecdc50c1a1a034f2 Mon Sep 17 00:00:00 2001 From: "Mahatthana (Kelvin) Nomsawadi" <mahatthana.n@gmail.com> Date: Thu, 15 Dec 2022 03:06:55 +0700 Subject: [PATCH] Fix can't change locked parameter names in full-app embedding (#27179) * Fix can't change locked parameter names in full-app embedding * Remove unused param * Add tests --- .../widgets/AdvancedSettingsPane.jsx | 5 +- .../components/widgets/EmbedModalContent.jsx | 23 ++- .../widgets/EmbedModalContent.unit.spec.tsx | 169 ++++++++++++++++++ 3 files changed, 191 insertions(+), 6 deletions(-) create mode 100644 frontend/src/metabase/public/components/widgets/EmbedModalContent.unit.spec.tsx diff --git a/frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx b/frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx index a9fff200042..96d585b15a7 100644 --- a/frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx +++ b/frontend/src/metabase/public/components/widgets/AdvancedSettingsPane.jsx @@ -31,8 +31,6 @@ const AdvancedSettingsPane = ({ displayOptions, onChangeDisplayOptions, onUnpublish, - pane, - onChangePane, previewParameters, parameterValues, onChangeParameterValue, @@ -73,6 +71,9 @@ const AdvancedSettingsPane = ({ /> <h3>{parameter.name}</h3> <Select + buttonProps={{ + "aria-label": parameter.name, + }} className="ml-auto bg-white" value={embeddingParams[parameter.slug] || "disabled"} onChange={e => diff --git a/frontend/src/metabase/public/components/widgets/EmbedModalContent.jsx b/frontend/src/metabase/public/components/widgets/EmbedModalContent.jsx index 1759b793774..f19f135103b 100644 --- a/frontend/src/metabase/public/components/widgets/EmbedModalContent.jsx +++ b/frontend/src/metabase/public/components/widgets/EmbedModalContent.jsx @@ -4,6 +4,7 @@ import { connect } from "react-redux"; import { titleize } from "inflection"; import { t } from "ttag"; +import _ from "underscore"; import Icon from "metabase/components/Icon"; import { @@ -40,7 +41,7 @@ class EmbedModalContent extends Component { this.state = { pane: "preview", embedType: null, - embeddingParams: props.resource.embedding_params || {}, + embeddingParams: getDefaultEmbeddingParams(props), displayOptions, parameterValues: {}, }; @@ -73,8 +74,7 @@ class EmbedModalContent extends Component { }; handleDiscard = () => { - const { resource } = this.props; - this.setState({ embeddingParams: resource.embedding_params || {} }); + this.setState({ embeddingParams: getDefaultEmbeddingParams(this.props) }); }; getPreviewParameters(resourceParameters, embeddingParams) { @@ -235,7 +235,20 @@ class EmbedModalContent extends Component { } } -export default connect(mapStateToProps)(EmbedModalContent); +function getDefaultEmbeddingParams(props) { + const { resource, resourceParameters } = props; + + return filterValidResourceParameters( + resource.embedding_params || {}, + resourceParameters, + ); +} + +function filterValidResourceParameters(embeddingParams, resourceParameters) { + const validParameters = resourceParameters.map(parameter => parameter.slug); + + return _.pick(embeddingParams, validParameters); +} export const EmbedTitle = ({ type, onClick }) => ( <a className="flex align-center" onClick={onClick}> @@ -244,3 +257,5 @@ export const EmbedTitle = ({ type, onClick }) => ( {type} </a> ); + +export default connect(mapStateToProps)(EmbedModalContent); diff --git a/frontend/src/metabase/public/components/widgets/EmbedModalContent.unit.spec.tsx b/frontend/src/metabase/public/components/widgets/EmbedModalContent.unit.spec.tsx new file mode 100644 index 00000000000..45d0aedf105 --- /dev/null +++ b/frontend/src/metabase/public/components/widgets/EmbedModalContent.unit.spec.tsx @@ -0,0 +1,169 @@ +import React from "react"; +import { screen, waitFor } from "@testing-library/react"; +import _ from "underscore"; +import { renderWithProviders } from "__support__/ui"; +import EmbedModalContent from "./EmbedModalContent"; + +describe("EmbedModalContent", () => { + it("should render", () => { + renderWithConfiguredProviders( + <EmbedModalContent + resource={{}} + resourceParameters={[]} + getPublicUrl={jest.fn()} + />, + ); + + expect(screen.getByText("Sharing")).toBeInTheDocument(); + expect(screen.getByText("Public link")).toBeInTheDocument(); + expect(screen.getByText("Public embed")).toBeInTheDocument(); + expect(screen.getByText("Embed in your application")).toBeInTheDocument(); + }); + + it("should render parameters", () => { + const parameters = [ + { name: "My param", slug: "my_param", type: "category" }, + ]; + + renderWithConfiguredProviders( + <EmbedModalContent + resource={{}} + resourceParameters={parameters} + getPublicUrl={jest.fn()} + />, + ); + + openEmbedModal(); + expect(screen.getByText("My param")).toBeInTheDocument(); + expect(screen.getByLabelText("My param")).toHaveTextContent("Disabled"); + }); + + it("should render unsaved parameters", () => { + const parameters = [ + { name: "My param", slug: "my_param", type: "category" }, + ]; + + renderWithConfiguredProviders( + <EmbedModalContent + resource={{}} + resourceParameters={parameters} + getPublicUrl={jest.fn()} + />, + ); + + openEmbedModal(); + expect(screen.getByText("My param")).toBeInTheDocument(); + expect(screen.getByLabelText("My param")).toHaveTextContent("Disabled"); + }); + + it("should render saved parameters", () => { + const resource = { + embedding_params: { + my_param: "locked", + }, + }; + const parameters = [ + { name: "My param", slug: "my_param", type: "category" }, + ]; + + renderWithConfiguredProviders( + <EmbedModalContent + resource={resource} + resourceParameters={parameters} + getPublicUrl={jest.fn()} + />, + ); + + openEmbedModal(); + expect(screen.getByText("My param")).toBeInTheDocument(); + expect(screen.getByLabelText("My param")).toHaveTextContent("Locked"); + }); + + it("should only render valid parameters", () => { + const resource = { + embedding_params: { + old_param: "locked", + }, + }; + const parameters = [ + { name: "My param", slug: "my_param", type: "category" }, + ]; + + renderWithConfiguredProviders( + <EmbedModalContent + resource={resource} + resourceParameters={parameters} + getPublicUrl={jest.fn()} + />, + ); + + openEmbedModal(); + expect(screen.getByText("My param")).toBeInTheDocument(); + expect(screen.getByLabelText("My param")).toHaveTextContent("Disabled"); + }); + + it("should update a card with only valid parameters", async () => { + const resource = { + embedding_params: { + old_param: "locked", + }, + }; + const parameters = [ + { name: "My param", slug: "my_param", type: "category" }, + ]; + const onUpdateEmbeddingParams = jest.fn(); + + renderWithConfiguredProviders( + <EmbedModalContent + resource={resource} + resourceParameters={parameters} + onUpdateEmbeddingParams={onUpdateEmbeddingParams} + onUpdateEnableEmbedding={jest.fn()} + getPublicUrl={jest.fn()} + />, + ); + + openEmbedModal(); + expect(screen.getByText("My param")).toBeInTheDocument(); + expect(screen.getByLabelText("My param")).toHaveTextContent("Disabled"); + screen.getByLabelText("My param").click(); + screen.getByText("Locked").click(); + screen.getByRole("button", { name: "Publish" }).click(); + await waitFor(() => + expect(onUpdateEmbeddingParams).toHaveBeenCalledWith({ + my_param: "locked", + }), + ); + }); +}); + +const storeInitialState = { + currentUser: { + is_superuser: true, + }, + settings: { + values: { + "enable-embedding": true, + "embedding-secret-key": "my_super_secret_key", + }, + }, +}; + +function renderWithConfiguredProviders(element: JSX.Element) { + renderWithProviders(element, { + storeInitialState, + reducers: { + settings: (state: Record<string, any> = storeInitialState.settings) => { + return state; + }, + }, + }); +} + +function openEmbedModal() { + screen + .getByRole("button", { + name: "Set up", + }) + .click(); +} -- GitLab