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