This is an automated email from the ASF dual-hosted git repository.

enzomartellucci pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 80ec241108 feat(dashboard): "embed code" option on dashboard share tab 
(#33163)
80ec241108 is described below

commit 80ec241108c50f477008b0f180c3713229914a24
Author: Richard Fogaca Nienkotter 
<[email protected]>
AuthorDate: Tue Nov 18 08:48:43 2025 -0300

    feat(dashboard): "embed code" option on dashboard share tab (#33163)
    
    Co-authored-by: richardfn <[email protected]>
    Co-authored-by: Enzo Martellucci <[email protected]>
---
 .../SliceHeaderControls.test.tsx                   |  37 +++++++
 .../components/SliceHeaderControls/index.tsx       |   2 +
 .../menu/ShareMenuItems/ShareMenuItems.test.tsx    | 115 +++++++++++++++++++++
 .../components/menu/ShareMenuItems/index.tsx       |  80 ++++++++++----
 superset-frontend/src/dashboard/types.ts           |   1 +
 5 files changed, 216 insertions(+), 19 deletions(-)

diff --git 
a/superset-frontend/src/dashboard/components/SliceHeaderControls/SliceHeaderControls.test.tsx
 
b/superset-frontend/src/dashboard/components/SliceHeaderControls/SliceHeaderControls.test.tsx
index 1d3a531219..2cfb971c87 100644
--- 
a/superset-frontend/src/dashboard/components/SliceHeaderControls/SliceHeaderControls.test.tsx
+++ 
b/superset-frontend/src/dashboard/components/SliceHeaderControls/SliceHeaderControls.test.tsx
@@ -579,3 +579,40 @@ test('Dataset drill info API call is not made when user 
lacks drill permissions'
 
   expect(mockCachedSupersetGet).not.toHaveBeenCalled();
 });
+
+test('Should show "Embed code" in Share menu when feature flag is enabled and 
chart has data', async () => {
+  window.featureFlags = {
+    EMBEDDABLE_CHARTS: true,
+  };
+  const props = createProps();
+  renderWrapper(props);
+  openMenu();
+  userEvent.hover(screen.getByText('Share'));
+  expect(await screen.findByText('Embed code')).toBeInTheDocument();
+});
+
+test('Should NOT show "Embed code" in Share menu when feature flag is 
disabled', async () => {
+  window.featureFlags = {
+    EMBEDDABLE_CHARTS: false,
+  };
+  const props = createProps();
+  renderWrapper(props);
+  openMenu();
+  userEvent.hover(screen.getByText('Share'));
+  expect(
+    await screen.findByText('Copy permalink to clipboard'),
+  ).toBeInTheDocument();
+  expect(screen.queryByText('Embed code')).not.toBeInTheDocument();
+});
+
+test('Should pass formData to Share menu for embed code feature', () => {
+  window.featureFlags = {
+    EMBEDDABLE_CHARTS: true,
+  };
+  const props = createProps();
+  const { container } = renderWrapper(props);
+
+  expect(container).toBeInTheDocument();
+  openMenu();
+  expect(screen.getByText('Share')).toBeInTheDocument();
+});
diff --git 
a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx 
b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
index aaa9b9ceb7..564a4fb745 100644
--- a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
+++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
@@ -471,6 +471,8 @@ const SliceHeaderControls = (
     addSuccessToast,
     addDangerToast,
     title: t('Share'),
+    latestQueryFormData: props.formData,
+    maxWidth: `${theme.sizeUnit * 100}px`,
   });
 
   if (isFeatureEnabled(FeatureFlag.DrillToDetail) && canDrillToDetail) {
diff --git 
a/superset-frontend/src/dashboard/components/menu/ShareMenuItems/ShareMenuItems.test.tsx
 
b/superset-frontend/src/dashboard/components/menu/ShareMenuItems/ShareMenuItems.test.tsx
index c0a84448df..33f3d60494 100644
--- 
a/superset-frontend/src/dashboard/components/menu/ShareMenuItems/ShareMenuItems.test.tsx
+++ 
b/superset-frontend/src/dashboard/components/menu/ShareMenuItems/ShareMenuItems.test.tsx
@@ -220,3 +220,118 @@ test('Click on "Share dashboard by email" and fail', 
async () => {
     );
   });
 });
+
+test('Should show "Embed code" menu item when feature flag is enabled and 
chart has data', () => {
+  window.featureFlags = {
+    EMBEDDABLE_CHARTS: true,
+  };
+  const props = createProps();
+  const propsWithFormData = {
+    ...props,
+    latestQueryFormData: {
+      datasource: '1__table',
+      viz_type: 'table',
+    },
+  };
+  render(
+    <MenuWrapper
+      onClick={jest.fn()}
+      selectable={false}
+      data-test="main-menu"
+      forceSubMenuRender
+      shareProps={propsWithFormData}
+    />,
+    { useRedux: true },
+  );
+  expect(screen.getByText('Embed code')).toBeInTheDocument();
+});
+
+test('Should NOT show "Embed code" when feature flag is disabled', () => {
+  window.featureFlags = {
+    EMBEDDABLE_CHARTS: false,
+  };
+  const props = createProps();
+  const propsWithFormData = {
+    ...props,
+    latestQueryFormData: {
+      datasource: '1__table',
+      viz_type: 'table',
+    },
+  };
+  render(
+    <MenuWrapper
+      onClick={jest.fn()}
+      selectable={false}
+      data-test="main-menu"
+      forceSubMenuRender
+      shareProps={propsWithFormData}
+    />,
+    { useRedux: true },
+  );
+  expect(screen.queryByText('Embed code')).not.toBeInTheDocument();
+});
+
+test('Should NOT show "Embed code" when chart has no data', () => {
+  window.featureFlags = {
+    EMBEDDABLE_CHARTS: true,
+  };
+  const props = createProps();
+  render(
+    <MenuWrapper
+      onClick={jest.fn()}
+      selectable={false}
+      data-test="main-menu"
+      forceSubMenuRender
+      shareProps={props}
+    />,
+    { useRedux: true },
+  );
+  expect(screen.queryByText('Embed code')).not.toBeInTheDocument();
+});
+
+test('Should NOT show "Embed code" when latestQueryFormData is empty object', 
() => {
+  window.featureFlags = {
+    EMBEDDABLE_CHARTS: true,
+  };
+  const props = createProps();
+  const propsWithEmptyFormData = {
+    ...props,
+    latestQueryFormData: {},
+  };
+  render(
+    <MenuWrapper
+      onClick={jest.fn()}
+      selectable={false}
+      data-test="main-menu"
+      forceSubMenuRender
+      shareProps={propsWithEmptyFormData}
+    />,
+    { useRedux: true },
+  );
+  expect(screen.queryByText('Embed code')).not.toBeInTheDocument();
+});
+
+test('Should render "Embed code" with data-test attribute', () => {
+  window.featureFlags = {
+    EMBEDDABLE_CHARTS: true,
+  };
+  const props = createProps();
+  const propsWithFormData = {
+    ...props,
+    latestQueryFormData: {
+      datasource: '1__table',
+      viz_type: 'table',
+    },
+  };
+  render(
+    <MenuWrapper
+      onClick={jest.fn()}
+      selectable={false}
+      data-test="main-menu"
+      forceSubMenuRender
+      shareProps={propsWithFormData}
+    />,
+    { useRedux: true },
+  );
+  expect(screen.getByTestId('embed-code-button')).toBeInTheDocument();
+});
diff --git 
a/superset-frontend/src/dashboard/components/menu/ShareMenuItems/index.tsx 
b/superset-frontend/src/dashboard/components/menu/ShareMenuItems/index.tsx
index 5659ec2e9b..5430d5b2c3 100644
--- a/superset-frontend/src/dashboard/components/menu/ShareMenuItems/index.tsx
+++ b/superset-frontend/src/dashboard/components/menu/ShareMenuItems/index.tsx
@@ -18,9 +18,17 @@
  */
 import { ComponentProps, RefObject } from 'react';
 import copyTextToClipboard from 'src/utils/copy';
-import { t, logging } from '@superset-ui/core';
+import {
+  t,
+  logging,
+  FeatureFlag,
+  isFeatureEnabled,
+  LatestQueryFormData,
+} from '@superset-ui/core';
 import { Menu, MenuItem } from '@superset-ui/core/components/Menu';
 import { getDashboardPermalink } from 'src/utils/urlUtils';
+import EmbedCodeContent from 'src/explore/components/EmbedCodeContent';
+import { ModalTrigger } from '@superset-ui/core/components';
 import { MenuKeys, RootState } from 'src/dashboard/types';
 import { shallowEqual, useSelector } from 'react-redux';
 import { hasStatefulCharts } from 'src/dashboard/util/chartStateConverter';
@@ -32,17 +40,19 @@ export interface ShareMenuItemProps
   emailMenuItemTitle: string;
   emailSubject: string;
   emailBody: string;
-  addDangerToast: Function;
-  addSuccessToast: Function;
+  addDangerToast: (message: string) => void;
+  addSuccessToast: (message: string) => void;
   dashboardId: string | number;
   dashboardComponentId?: string;
-  copyMenuItemRef?: RefObject<any>;
-  shareByEmailMenuItemRef?: RefObject<any>;
+  latestQueryFormData?: LatestQueryFormData;
+  maxWidth?: string;
+  copyMenuItemRef?: RefObject<HTMLElement>;
+  shareByEmailMenuItemRef?: RefObject<HTMLElement>;
   selectedKeys?: string[];
-  setOpenKeys?: Function;
+  setOpenKeys?: (keys: string[] | undefined) => void;
   title: string;
   disabled?: boolean;
-  [key: string]: any;
+  [key: string]: unknown;
 }
 
 export const useShareMenuItems = (props: ShareMenuItemProps): MenuItem => {
@@ -55,10 +65,17 @@ export const useShareMenuItems = (props: 
ShareMenuItemProps): MenuItem => {
     addSuccessToast,
     dashboardId,
     dashboardComponentId,
+    latestQueryFormData,
+    maxWidth,
     title,
     disabled,
     ...rest
   } = props;
+  const sliceExists = !!(
+    latestQueryFormData && Object.keys(latestQueryFormData).length > 0
+  );
+  const isEmbedCodeEnabled = isFeatureEnabled(FeatureFlag.EmbeddableCharts);
+
   const { dataMask, activeTabs, chartStates, sliceEntities } = useSelector(
     (state: RootState) => ({
       dataMask: state.dataMask,
@@ -109,23 +126,48 @@ export const useShareMenuItems = (props: 
ShareMenuItemProps): MenuItem => {
     }
   }
 
+  const children: MenuItem[] = [
+    {
+      key: MenuKeys.CopyLink,
+      label: copyMenuItemTitle,
+      onClick: onCopyLink,
+    },
+    {
+      key: MenuKeys.ShareByEmail,
+      label: emailMenuItemTitle,
+      onClick: onShareByEmail,
+    },
+  ];
+
+  // Add embed code option if feature is enabled and chart data exists
+  if (isEmbedCodeEnabled && sliceExists) {
+    children.push({
+      key: MenuKeys.EmbedCode,
+      label: (
+        <ModalTrigger
+          triggerNode={
+            <span data-test="embed-code-button">{t('Embed code')}</span>
+          }
+          modalTitle={t('Embed code')}
+          modalBody={
+            <EmbedCodeContent
+              formData={latestQueryFormData}
+              addDangerToast={addDangerToast}
+            />
+          }
+          maxWidth={maxWidth}
+          responsive
+        />
+      ),
+    });
+  }
+
   return {
     ...rest,
     type: 'submenu',
     label: title,
     key: MenuKeys.Share,
     disabled,
-    children: [
-      {
-        key: MenuKeys.CopyLink,
-        label: copyMenuItemTitle,
-        onClick: onCopyLink,
-      },
-      {
-        key: MenuKeys.ShareByEmail,
-        label: emailMenuItemTitle,
-        onClick: onShareByEmail,
-      },
-    ],
+    children,
   };
 };
diff --git a/superset-frontend/src/dashboard/types.ts 
b/superset-frontend/src/dashboard/types.ts
index 1848a708e5..e1a589acdb 100644
--- a/superset-frontend/src/dashboard/types.ts
+++ b/superset-frontend/src/dashboard/types.ts
@@ -309,4 +309,5 @@ export enum MenuKeys {
   ManageEmbedded = 'manage_embedded',
   ManageEmailReports = 'manage_email_reports',
   ExportPivotXlsx = 'export_pivot_xlsx',
+  EmbedCode = 'embed_code',
 }

Reply via email to