From 7854e07a622b50a291aedc35adc5ebf40edd773e Mon Sep 17 00:00:00 2001 From: Oisin Coveney <oisin@metabase.com> Date: Fri, 14 Jun 2024 13:32:50 +0300 Subject: [PATCH] fix: Ensure interactive embedding CTA matches instance features (#44197) --- .../metabase-enterprise/embedding/index.js | 3 + .../embedding/selectors.ts | 8 +++ frontend/src/metabase/plugins/index.ts | 1 + .../InteractiveEmbeddingCTA.tsx | 9 ++- .../InteractiveEmbeddingCTA.unit.spec.tsx | 66 ------------------- .../test/common.unit.spec.tsx | 22 +++++++ .../test/enterprise.unit.spec.tsx | 31 +++++++++ .../test/premium.unit.spec.tsx | 34 ++++++++++ .../InteractiveEmbeddingCTA/test/setup.tsx | 43 ++++++++++++ 9 files changed, 148 insertions(+), 69 deletions(-) create mode 100644 enterprise/frontend/src/metabase-enterprise/embedding/selectors.ts delete mode 100644 frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/InteractiveEmbeddingCTA.unit.spec.tsx create mode 100644 frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/test/common.unit.spec.tsx create mode 100644 frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/test/enterprise.unit.spec.tsx create mode 100644 frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/test/premium.unit.spec.tsx create mode 100644 frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/test/setup.tsx diff --git a/enterprise/frontend/src/metabase-enterprise/embedding/index.js b/enterprise/frontend/src/metabase-enterprise/embedding/index.js index 1aff5fdf262..7bcab6a3770 100644 --- a/enterprise/frontend/src/metabase-enterprise/embedding/index.js +++ b/enterprise/frontend/src/metabase-enterprise/embedding/index.js @@ -4,6 +4,7 @@ import { PLUGIN_ADMIN_SETTINGS_UPDATES, PLUGIN_EMBEDDING, } from "metabase/plugins"; +import { isInteractiveEmbeddingEnabled } from "metabase-enterprise/embedding/selectors"; import { hasPremiumFeature } from "metabase-enterprise/settings"; import { EmbeddingAppOriginDescription } from "./components/EmbeddingAppOriginDescription"; @@ -16,6 +17,8 @@ const SLUG = "embedding-in-other-applications/full-app"; if (hasPremiumFeature("embedding")) { PLUGIN_EMBEDDING.isEnabled = () => true; + PLUGIN_EMBEDDING.isInteractiveEmbeddingEnabled = + isInteractiveEmbeddingEnabled; PLUGIN_ADMIN_SETTINGS_UPDATES.push(sections => { return { diff --git a/enterprise/frontend/src/metabase-enterprise/embedding/selectors.ts b/enterprise/frontend/src/metabase-enterprise/embedding/selectors.ts new file mode 100644 index 00000000000..9dccf899607 --- /dev/null +++ b/enterprise/frontend/src/metabase-enterprise/embedding/selectors.ts @@ -0,0 +1,8 @@ +import { getPlan } from "metabase/common/utils/plan"; +import { getSetting } from "metabase/selectors/settings"; +import type { EnterpriseState } from "metabase-enterprise/settings/types"; + +export const isInteractiveEmbeddingEnabled = (state: EnterpriseState) => { + const plan = getPlan(getSetting(state, "token-features")); + return plan === "pro-cloud" || plan === "pro-self-hosted"; +}; diff --git a/frontend/src/metabase/plugins/index.ts b/frontend/src/metabase/plugins/index.ts index b83b085595d..c22d2adf224 100644 --- a/frontend/src/metabase/plugins/index.ts +++ b/frontend/src/metabase/plugins/index.ts @@ -445,6 +445,7 @@ export const PLUGIN_MODEL_PERSISTENCE = { export const PLUGIN_EMBEDDING = { isEnabled: () => false, + isInteractiveEmbeddingEnabled: (_state: State) => false, }; export const PLUGIN_CONTENT_VERIFICATION = { diff --git a/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/InteractiveEmbeddingCTA.tsx b/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/InteractiveEmbeddingCTA.tsx index a4ca42d8c83..9acde8d74ec 100644 --- a/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/InteractiveEmbeddingCTA.tsx +++ b/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/InteractiveEmbeddingCTA.tsx @@ -3,7 +3,8 @@ import { t } from "ttag"; import { getPlan } from "metabase/common/utils/plan"; import Link from "metabase/core/components/Link"; import { useSelector } from "metabase/lib/redux"; -import { getIsPaidPlan, getSetting } from "metabase/selectors/settings"; +import { PLUGIN_EMBEDDING } from "metabase/plugins"; +import { getSetting } from "metabase/selectors/settings"; import { Text, Group, Stack, Box } from "metabase/ui"; import { @@ -14,12 +15,14 @@ import { } from "./InteractiveEmbeddingCTA.styled"; const useCTAText = () => { - const isPaidPlan = useSelector(getIsPaidPlan); + const isInteractiveEmbeddingEnabled = useSelector( + PLUGIN_EMBEDDING.isInteractiveEmbeddingEnabled, + ); const plan = useSelector(state => getPlan(getSetting(state, "token-features")), ); - if (isPaidPlan) { + if (isInteractiveEmbeddingEnabled) { return { showProBadge: false, description: t`Your plan allows you to use Interactive Embedding create interactive embedding experiences with drill-through and more.`, diff --git a/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/InteractiveEmbeddingCTA.unit.spec.tsx b/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/InteractiveEmbeddingCTA.unit.spec.tsx deleted file mode 100644 index f577d594007..00000000000 --- a/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/InteractiveEmbeddingCTA.unit.spec.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import userEvent from "@testing-library/user-event"; -import { Route } from "react-router"; - -import { screen, renderWithProviders } from "__support__/ui"; -import { checkNotNull } from "metabase/lib/types"; -import { createMockTokenStatus } from "metabase-types/api/mocks"; -import { - createMockSettingsState, - createMockState, -} from "metabase-types/store/mocks"; - -import { InteractiveEmbeddingCTA } from "./InteractiveEmbeddingCTA"; - -const setup = ({ isPaidPlan }: { isPaidPlan: boolean }) => { - const { history } = renderWithProviders( - <Route path="*" component={InteractiveEmbeddingCTA}></Route>, - { - storeInitialState: createMockState({ - settings: createMockSettingsState({ - "token-status": createMockTokenStatus({ valid: isPaidPlan }), - }), - }), - withRouter: true, - }, - ); - - return { - history: checkNotNull(history), - }; -}; -describe("InteractiveEmbeddingCTA", () => { - it("renders correctly for paid plan", async () => { - const { history } = setup({ isPaidPlan: true }); - - expect(screen.getByText("Interactive Embedding")).toBeInTheDocument(); - expect(screen.queryByText("Pro")).not.toBeInTheDocument(); - expect( - screen.getByText( - "Your plan allows you to use Interactive Embedding create interactive embedding experiences with drill-through and more.", - ), - ).toBeInTheDocument(); - - await userEvent.click(screen.getByTestId("interactive-embedding-cta")); - - expect(history.getCurrentLocation().pathname).toEqual( - "/admin/settings/embedding-in-other-applications/full-app", - ); - }); - - it("renders correctly for OSS", () => { - setup({ isPaidPlan: false }); - - expect(screen.getByText("Interactive Embedding")).toBeInTheDocument(); - expect(screen.getByText("Pro")).toBeInTheDocument(); - expect( - screen.getByText( - "Give your customers the full power of Metabase in your own app, with SSO, advanced permissions, customization, and more.", - ), - ).toBeInTheDocument(); - - expect(screen.getByTestId("interactive-embedding-cta")).toHaveAttribute( - "href", - "https://www.metabase.com/product/embedded-analytics?utm_source=oss&utm_media=static-embed-popover", - ); - }); -}); diff --git a/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/test/common.unit.spec.tsx b/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/test/common.unit.spec.tsx new file mode 100644 index 00000000000..2e1442567ad --- /dev/null +++ b/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/test/common.unit.spec.tsx @@ -0,0 +1,22 @@ +import { screen } from "__support__/ui"; + +import { setup } from "./setup"; + +describe("InteractiveEmbeddingCTA", () => { + it("should display a CTA to the product page when plan is OSS", () => { + setup(); + + expect(screen.getByText("Interactive Embedding")).toBeInTheDocument(); + expect(screen.getByText("Pro")).toBeInTheDocument(); + expect( + screen.getByText( + "Give your customers the full power of Metabase in your own app, with SSO, advanced permissions, customization, and more.", + ), + ).toBeInTheDocument(); + + expect(screen.getByTestId("interactive-embedding-cta")).toHaveAttribute( + "href", + "https://www.metabase.com/product/embedded-analytics?utm_source=oss&utm_media=static-embed-popover", + ); + }); +}); diff --git a/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/test/enterprise.unit.spec.tsx b/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/test/enterprise.unit.spec.tsx new file mode 100644 index 00000000000..cf291789523 --- /dev/null +++ b/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/test/enterprise.unit.spec.tsx @@ -0,0 +1,31 @@ +import { screen } from "__support__/ui"; + +import { type InteractiveEmbeddingCTASetupOptions, setup } from "./setup"; + +const setupEnterprise = ( + opts?: Partial<InteractiveEmbeddingCTASetupOptions>, +) => { + setup({ + ...opts, + hasEnterprisePlugins: true, + }); +}; + +describe("InteractiveEmbeddingCTA", () => { + it("should display a CTA to the product page when plan is starter", () => { + setupEnterprise(); + + expect(screen.getByText("Interactive Embedding")).toBeInTheDocument(); + expect(screen.getByText("Pro")).toBeInTheDocument(); + expect( + screen.getByText( + "Give your customers the full power of Metabase in your own app, with SSO, advanced permissions, customization, and more.", + ), + ).toBeInTheDocument(); + + expect(screen.getByTestId("interactive-embedding-cta")).toHaveAttribute( + "href", + "https://www.metabase.com/product/embedded-analytics?utm_source=oss&utm_media=static-embed-popover", + ); + }); +}); diff --git a/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/test/premium.unit.spec.tsx b/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/test/premium.unit.spec.tsx new file mode 100644 index 00000000000..a5bf30fd66a --- /dev/null +++ b/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/test/premium.unit.spec.tsx @@ -0,0 +1,34 @@ +import userEvent from "@testing-library/user-event"; + +import { screen } from "__support__/ui"; +import { createMockTokenFeatures } from "metabase-types/api/mocks"; + +import { type InteractiveEmbeddingCTASetupOptions, setup } from "./setup"; + +const setupPremium = (opts?: Partial<InteractiveEmbeddingCTASetupOptions>) => { + return setup({ + ...opts, + tokenFeatures: createMockTokenFeatures({ embedding: true }), + hasEnterprisePlugins: true, + }); +}; + +describe("InteractiveEmbeddingCTA", () => { + it("should display a link to the embedding settings when plan is pro", async () => { + const { history } = setupPremium(); + + expect(screen.getByText("Interactive Embedding")).toBeInTheDocument(); + expect(screen.queryByText("Pro")).not.toBeInTheDocument(); + expect( + screen.getByText( + "Your plan allows you to use Interactive Embedding create interactive embedding experiences with drill-through and more.", + ), + ).toBeInTheDocument(); + + await userEvent.click(screen.getByTestId("interactive-embedding-cta")); + + expect(history.getCurrentLocation().pathname).toEqual( + "/admin/settings/embedding-in-other-applications/full-app", + ); + }); +}); diff --git a/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/test/setup.tsx b/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/test/setup.tsx new file mode 100644 index 00000000000..b2abca71c05 --- /dev/null +++ b/frontend/src/metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA/test/setup.tsx @@ -0,0 +1,43 @@ +import { Route } from "react-router"; + +import { setupEnterprisePlugins } from "__support__/enterprise"; +import { mockSettings } from "__support__/settings"; +import { renderWithProviders } from "__support__/ui"; +import { checkNotNull } from "metabase/lib/types"; +import { InteractiveEmbeddingCTA } from "metabase/public/components/EmbedModal/SelectEmbedTypePane/InteractiveEmbeddingCTA"; +import type { TokenFeatures } from "metabase-types/api"; +import { createMockTokenFeatures } from "metabase-types/api/mocks"; +import { createMockState } from "metabase-types/store/mocks"; + +export type InteractiveEmbeddingCTASetupOptions = { + tokenFeatures?: TokenFeatures; + hasEnterprisePlugins?: boolean; + isPaidPlan?: boolean; +}; + +export const setup = ({ + tokenFeatures = createMockTokenFeatures(), + hasEnterprisePlugins = false, +}: InteractiveEmbeddingCTASetupOptions = {}) => { + const settings = mockSettings({ "token-features": tokenFeatures }); + + const state = createMockState({ + settings, + }); + + if (hasEnterprisePlugins) { + setupEnterprisePlugins(); + } + + const { history } = renderWithProviders( + <Route path="*" component={InteractiveEmbeddingCTA}></Route>, + { + storeInitialState: state, + withRouter: true, + }, + ); + + return { + history: checkNotNull(history), + }; +}; -- GitLab