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',
}