diff --git a/enterprise/frontend/src/metabase-enterprise/embedding/index.js b/enterprise/frontend/src/metabase-enterprise/embedding/index.js index 1aff5fdf262b0c69fc6700a90ea29f2449e9d047..7bcab6a37702421624cee3394a7703b191ff769d 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 0000000000000000000000000000000000000000..9dccf899607eac838c4dde45865c18e975d496f0 --- /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 b83b085595d23b8081a1fd549b62b0bc59f6037e..c22d2adf22437e6bbd7213c9dd2ac6afe9318a69 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 a4ca42d8c83c64c9bfaa96212030b548d153602d..9acde8d74ec99966ec9fe71d80b2ce86ff38718d 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 f577d5940077150bb7ae73f13bcb7205d77765e3..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..2e1442567ad2371b38488e203c8d2ed2a10ab9ef --- /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 0000000000000000000000000000000000000000..cf29178952370747f56f95eff009a686d146e9ee --- /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 0000000000000000000000000000000000000000..a5bf30fd66a7ca183162621cdacdff6615230461 --- /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 0000000000000000000000000000000000000000..b2abca71c05a6898c71f8562f5d8b5012da07ee4 --- /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), + }; +};