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

msyavuz pushed a commit to branch msyavuz/fix/console-errors
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 8a298b0439d490cd690c16b92695743b9902b45f
Author: Mehmet Salih Yavuz <salih.ya...@proton.me>
AuthorDate: Wed Jul 16 00:05:30 2025 +0300

    fix(SliceHeaderControls): Menu items
---
 .../src/components/Chart/DrillDetail/index.ts      |   1 +
 .../Chart/DrillDetail/useDrillDetailMenuItems.tsx  | 269 ++++++++++++++++
 .../components/SliceHeaderControls/index.tsx       | 356 +++++++++++----------
 .../components/menu/ShareMenuItems/index.tsx       |  40 +--
 4 files changed, 484 insertions(+), 182 deletions(-)

diff --git a/superset-frontend/src/components/Chart/DrillDetail/index.ts 
b/superset-frontend/src/components/Chart/DrillDetail/index.ts
index cf154680be..7911551479 100644
--- a/superset-frontend/src/components/Chart/DrillDetail/index.ts
+++ b/superset-frontend/src/components/Chart/DrillDetail/index.ts
@@ -18,3 +18,4 @@
  */
 
 export { default as DrillDetailMenuItems } from './DrillDetailMenuItems';
+export { useDrillDetailMenuItems } from './useDrillDetailMenuItems';
diff --git 
a/superset-frontend/src/components/Chart/DrillDetail/useDrillDetailMenuItems.tsx
 
b/superset-frontend/src/components/Chart/DrillDetail/useDrillDetailMenuItems.tsx
new file mode 100644
index 0000000000..f89d949770
--- /dev/null
+++ 
b/superset-frontend/src/components/Chart/DrillDetail/useDrillDetailMenuItems.tsx
@@ -0,0 +1,269 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+  Dispatch,
+  ReactNode,
+  SetStateAction,
+  useCallback,
+  useMemo,
+} from 'react';
+import { isEmpty } from 'lodash';
+import {
+  Behavior,
+  BinaryQueryObjectFilterClause,
+  css,
+  extractQueryFields,
+  getChartMetadataRegistry,
+  QueryFormData,
+  removeHTMLTags,
+  styled,
+  t,
+} from '@superset-ui/core';
+import { useSelector } from 'react-redux';
+import { MenuItem } from '@superset-ui/core/components/Menu';
+import { RootState } from 'src/dashboard/types';
+import { getSubmenuYOffset } from '../utils';
+import { MenuItemTooltip } from '../DisabledMenuItemTooltip';
+import { useMenuItemWithTruncation } from '../MenuItemWithTruncation';
+
+const DRILL_TO_DETAIL = t('Drill to detail');
+const DRILL_TO_DETAIL_BY = t('Drill to detail by');
+const DISABLED_REASONS = {
+  DATABASE: t(
+    'Drill to detail is disabled for this database. Change the database 
settings to enable it.',
+  ),
+  NO_AGGREGATIONS: t(
+    'Drill to detail is disabled because this chart does not group data by 
dimension value.',
+  ),
+  NO_FILTERS: t(
+    'Right-click on a dimension value to drill to detail by that value.',
+  ),
+  NOT_SUPPORTED: t(
+    'Drill to detail by value is not yet supported for this chart type.',
+  ),
+};
+
+function getDisabledMenuItem(
+  children: ReactNode,
+  menuKey: string,
+  ...rest: unknown[]
+): MenuItem {
+  return {
+    disabled: true,
+    key: menuKey,
+    label: (
+      <div
+        css={css`
+          white-space: normal;
+          max-width: 160px;
+        `}
+      >
+        {children}
+      </div>
+    ),
+    ...rest,
+  };
+}
+
+const Filter = ({
+  children,
+  stripHTML = false,
+}: {
+  children: ReactNode;
+  stripHTML: boolean;
+}) => {
+  const content =
+    stripHTML && typeof children === 'string'
+      ? removeHTMLTags(children)
+      : children;
+  return <span>{content}</span>;
+};
+
+const StyledFilter = styled(Filter)`
+  ${({ theme }) => `
+     font-weight: ${theme.fontWeightStrong};
+     color: ${theme.colorPrimary};
+   `}
+`;
+
+export type DrillDetailMenuItemsArgs = {
+  formData: QueryFormData;
+  filters?: BinaryQueryObjectFilterClause[];
+  setFilters: Dispatch<SetStateAction<BinaryQueryObjectFilterClause[]>>;
+  isContextMenu?: boolean;
+  contextMenuY?: number;
+  onSelection?: () => void;
+  onClick?: (event: MouseEvent) => void;
+  submenuIndex?: number;
+  setShowModal: (show: boolean) => void;
+  key?: string;
+  forceSubmenuRender?: boolean;
+};
+
+export const useDrillDetailMenuItems = ({
+  formData,
+  filters = [],
+  isContextMenu = false,
+  contextMenuY = 0,
+  onSelection = () => null,
+  onClick = () => null,
+  submenuIndex = 0,
+  setFilters,
+  setShowModal,
+  key,
+  ...props
+}: DrillDetailMenuItemsArgs) => {
+  const drillToDetailDisabled = useSelector<RootState, boolean | undefined>(
+    ({ datasources }) =>
+      datasources[formData.datasource]?.database?.disable_drill_to_detail,
+  );
+
+  const openModal = useCallback(
+    (filters, event) => {
+      onClick(event);
+      onSelection();
+      setFilters(filters);
+      setShowModal(true);
+    },
+    [onClick, onSelection],
+  );
+
+  // Check for Behavior.DRILL_TO_DETAIL to tell if plugin handles the 
`contextmenu`
+  // event for dimensions.  If it doesn't, tell the user that drill to detail 
by
+  // dimension is not supported.  If it does, and the `contextmenu` handler 
didn't
+  // pass any filters, tell the user that they didn't select a dimension.
+  const handlesDimensionContextMenu = useMemo(
+    () =>
+      getChartMetadataRegistry()
+        .get(formData.viz_type)
+        ?.behaviors.find(behavior => behavior === Behavior.DrillToDetail),
+    [formData.viz_type],
+  );
+
+  // Check metrics to see if chart's current configuration lacks
+  // aggregations, in which case Drill to Detail should be disabled.
+  const noAggregations = useMemo(() => {
+    const { metrics } = extractQueryFields(formData);
+    return isEmpty(metrics);
+  }, [formData]);
+
+  // Ensure submenu doesn't appear offscreen
+  const submenuYOffset = useMemo(
+    () =>
+      getSubmenuYOffset(
+        contextMenuY,
+        filters.length > 1 ? filters.length + 1 : filters.length,
+        submenuIndex,
+      ),
+    [contextMenuY, filters.length, submenuIndex],
+  );
+
+  let drillDisabled;
+  let drillByDisabled;
+  if (drillToDetailDisabled) {
+    drillDisabled = DISABLED_REASONS.DATABASE;
+    drillByDisabled = DISABLED_REASONS.DATABASE;
+  } else if (handlesDimensionContextMenu) {
+    if (noAggregations) {
+      drillDisabled = DISABLED_REASONS.NO_AGGREGATIONS;
+      drillByDisabled = DISABLED_REASONS.NO_AGGREGATIONS;
+    } else if (!filters?.length) {
+      drillByDisabled = DISABLED_REASONS.NO_FILTERS;
+    }
+  } else {
+    drillByDisabled = DISABLED_REASONS.NOT_SUPPORTED;
+  }
+
+  const drillToDetailMenuItem: MenuItem = drillDisabled
+    ? getDisabledMenuItem(
+        <>
+          {DRILL_TO_DETAIL}
+          <MenuItemTooltip title={drillDisabled} />
+        </>,
+        'drill-to-detail-disabled',
+        props,
+      )
+    : {
+        key: 'drill-to-detail',
+        label: DRILL_TO_DETAIL,
+        onClick: openModal.bind(null, []),
+        ...props,
+      };
+
+  const getMenuItemWithTruncation = useMenuItemWithTruncation();
+
+  const drillToDetailByMenuItem: MenuItem = drillByDisabled
+    ? getDisabledMenuItem(
+        <>
+          {DRILL_TO_DETAIL_BY}
+          <MenuItemTooltip title={drillByDisabled} />
+        </>,
+        'drill-to-detail-by-disabled',
+        props,
+      )
+    : {
+        key: key || 'drill-to-detail-by',
+        label: DRILL_TO_DETAIL_BY,
+        children: [
+          ...filters.map((filter, i) => ({
+            key: `drill-detail-filter-${i}`,
+            label: getMenuItemWithTruncation({
+              tooltipText: `${DRILL_TO_DETAIL_BY} ${filter.formattedVal}`,
+              onClick: openModal.bind(null, [filter]),
+              key: `drill-detail-filter-${i}`,
+              children: (
+                <>
+                  {`${DRILL_TO_DETAIL_BY} `}
+                  <StyledFilter stripHTML>{filter.formattedVal}</StyledFilter>
+                </>
+              ),
+            }),
+          })),
+          filters.length > 1 && {
+            key: 'drill-detail-filter-all',
+            label: getMenuItemWithTruncation({
+              tooltipText: `${DRILL_TO_DETAIL_BY} ${t('all')}`,
+              onClick: openModal.bind(null, filters),
+              key: 'drill-detail-filter-all',
+              children: (
+                <>
+                  {`${DRILL_TO_DETAIL_BY} `}
+                  <StyledFilter stripHTML={false}>{t('all')}</StyledFilter>
+                </>
+              ),
+            }),
+          },
+        ].filter(Boolean) as MenuItem[],
+        onClick: openModal.bind(null, filters),
+        forceSubmenuRender: true,
+        popupOffset: [0, submenuYOffset],
+        popupClassName: 'chart-context-submenu',
+        ...props,
+      };
+  if (isContextMenu) {
+    return {
+      drillToDetailMenuItem,
+      drillToDetailByMenuItem,
+    };
+  }
+  return {
+    drillToDetailMenuItem,
+  };
+};
diff --git 
a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx 
b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
index 5d9293a1c4..6af7069fe2 100644
--- a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
+++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
@@ -41,20 +41,20 @@ import {
   QueryFormData,
 } from '@superset-ui/core';
 import { useSelector } from 'react-redux';
-import { Menu } from '@superset-ui/core/components/Menu';
+import { Menu, MenuItem } from '@superset-ui/core/components/Menu';
 import {
   NoAnimationDropdown,
   Tooltip,
   Button,
   ModalTrigger,
 } from '@superset-ui/core/components';
-import ShareMenuItems from 'src/dashboard/components/menu/ShareMenuItems';
+import { useShareMenuItems } from 
'src/dashboard/components/menu/ShareMenuItems';
 import downloadAsImage from 'src/utils/downloadAsImage';
 import { getSliceHeaderTooltip } from 
'src/dashboard/util/getSliceHeaderTooltip';
 import { Icons } from '@superset-ui/core/components/Icons';
 import ViewQueryModal from 'src/explore/components/controls/ViewQueryModal';
 import { ResultsPaneOnDashboard } from 'src/explore/components/DataTablesPane';
-import { DrillDetailMenuItems } from 'src/components/Chart/DrillDetail';
+import { useDrillDetailMenuItems } from 'src/components/Chart/DrillDetail';
 import { LOG_ACTIONS_CHART_DOWNLOAD_AS_IMAGE } from 'src/logger/LogUtils';
 import { MenuKeys, RootState } from 'src/dashboard/types';
 import DrillDetailModal from 
'src/components/Chart/DrillDetail/DrillDetailModal';
@@ -329,174 +329,194 @@ const SliceHeaderControls = (
     animationDuration: '0s',
   };
 
-  const menu = (
-    <Menu
-      onClick={handleMenuClick}
-      data-test={`slice_${slice.slice_id}-menu`}
-      id={`slice_${slice.slice_id}-menu`}
-      selectable={false}
-    >
-      <Menu.Item
-        key={MenuKeys.ForceRefresh}
-        disabled={props.chartStatus === 'loading'}
-        style={{ height: 'auto', lineHeight: 'initial' }}
-        data-test="refresh-chart-menu-item"
-      >
-        {t('Force refresh')}
-        <RefreshTooltip data-test="dashboard-slice-refresh-tooltip">
-          {refreshTooltip}
-        </RefreshTooltip>
-      </Menu.Item>
-
-      <Menu.Item key={MenuKeys.Fullscreen}>{fullscreenLabel}</Menu.Item>
-
-      <Menu.Divider />
-
-      {slice.description && (
-        <Menu.Item key={MenuKeys.ToggleChartDescription}>
-          {props.isDescriptionExpanded
-            ? t('Hide chart description')
-            : t('Show chart description')}
-        </Menu.Item>
-      )}
-
-      {canExplore && (
-        <Menu.Item
-          key={MenuKeys.ExploreChart}
-          data-test-edit-chart-name={slice.slice_name}
-        >
-          <Tooltip title={getSliceHeaderTooltip(props.slice.slice_name)}>
-            {t('Edit chart')}
-          </Tooltip>
-        </Menu.Item>
-      )}
+  const newMenuItems: MenuItem[] = [
+    {
+      key: MenuKeys.ForceRefresh,
+      label: (
+        <>
+          {t('Force refresh')}
+          <RefreshTooltip data-test="dashboard-slice-refresh-tooltip">
+            {refreshTooltip}
+          </RefreshTooltip>
+        </>
+      ),
+      disabled: props.chartStatus === 'loading',
+      style: { height: 'auto', lineHeight: 'initial' },
+      ...{ 'data-test': 'refresh-chart-menu-item' }, // Typescript hack to get 
around MenuItem type
+    },
+    {
+      key: MenuKeys.Fullscreen,
+      label: fullscreenLabel,
+    },
+    {
+      type: 'divider',
+    },
+  ];
+
+  if (slice.description) {
+    newMenuItems.push({
+      key: MenuKeys.ToggleChartDescription,
+      label: props.isDescriptionExpanded
+        ? t('Hide chart description')
+        : t('Show chart description'),
+    });
+  }
 
-      {canEditCrossFilters && (
-        <Menu.Item key={MenuKeys.CrossFilterScoping}>
-          {t('Cross-filtering scoping')}
-        </Menu.Item>
-      )}
+  if (canExplore) {
+    newMenuItems.push({
+      key: MenuKeys.ExploreChart,
+      label: (
+        <Tooltip title={getSliceHeaderTooltip(props.slice.slice_name)}>
+          {t('Edit chart')}
+        </Tooltip>
+      ),
+      ...{ 'data-test-edit-chart-name': slice.slice_name },
+    });
+  }
 
-      {(canExplore || canEditCrossFilters) && <Menu.Divider />}
-
-      {(canExplore || canViewQuery) && (
-        <Menu.Item key={MenuKeys.ViewQuery}>
-          <ModalTrigger
-            triggerNode={
-              <div data-test="view-query-menu-item">{t('View query')}</div>
-            }
-            modalTitle={t('View query')}
-            modalBody={<ViewQueryModal latestQueryFormData={props.formData} />}
-            draggable
-            resizable
-            responsive
-            ref={queryMenuRef}
-          />
-        </Menu.Item>
-      )}
+  if (canEditCrossFilters) {
+    newMenuItems.push({
+      key: MenuKeys.CrossFilterScoping,
+      label: t('Cross-filtering scoping'),
+    });
+  }
 
-      {(canExplore || canViewTable) && (
-        <Menu.Item key={MenuKeys.ViewResults}>
-          <ViewResultsModalTrigger
-            canExplore={props.supersetCanExplore}
-            exploreUrl={props.exploreUrl}
-            triggerNode={
-              <div data-test="view-query-menu-item">{t('View as table')}</div>
-            }
-            modalRef={resultsMenuRef}
-            modalTitle={t('Chart Data: %s', slice.slice_name)}
-            modalBody={
-              <ResultsPaneOnDashboard
-                queryFormData={props.formData}
-                queryForce={false}
-                dataSize={20}
-                isRequest
-                isVisible
-                canDownload={!!props.supersetCanCSV}
-              />
-            }
-          />
-        </Menu.Item>
-      )}
+  if (canExplore || canEditCrossFilters) {
+    newMenuItems.push({ type: 'divider' });
+  }
 
-      {isFeatureEnabled(FeatureFlag.DrillToDetail) && canDrillToDetail && (
-        <DrillDetailMenuItems
-          setFilters={setFilters}
-          filters={modalFilters}
-          formData={props.formData}
-          key={MenuKeys.DrillToDetail}
-          setShowModal={setDrillModalIsOpen}
+  if (canExplore || canViewQuery) {
+    newMenuItems.push({
+      key: MenuKeys.ViewQuery,
+      label: (
+        <ModalTrigger
+          triggerNode={
+            <div data-test="view-query-menu-item">{t('View query')}</div>
+          }
+          modalTitle={t('View query')}
+          modalBody={<ViewQueryModal latestQueryFormData={props.formData} />}
+          draggable
+          resizable
+          responsive
+          ref={queryMenuRef}
         />
-      )}
+      ),
+    });
+  }
 
-      {(slice.description || canExplore) && <Menu.Divider />}
-
-      {supersetCanShare && (
-        <ShareMenuItems
-          dashboardId={dashboardId}
-          dashboardComponentId={componentId}
-          copyMenuItemTitle={t('Copy permalink to clipboard')}
-          emailMenuItemTitle={t('Share chart by email')}
-          emailSubject={t('Superset chart')}
-          emailBody={t('Check out this chart: ')}
-          addSuccessToast={addSuccessToast}
-          addDangerToast={addDangerToast}
-          title={t('Share')}
+  if (canExplore || canViewTable) {
+    newMenuItems.push({
+      key: MenuKeys.ViewResults,
+      label: (
+        <ViewResultsModalTrigger
+          canExplore={props.supersetCanExplore}
+          exploreUrl={props.exploreUrl}
+          triggerNode={
+            <div data-test="view-query-menu-item">{t('View as table')}</div>
+          }
+          modalRef={resultsMenuRef}
+          modalTitle={t('Chart Data: %s', slice.slice_name)}
+          modalBody={
+            <ResultsPaneOnDashboard
+              queryFormData={props.formData}
+              queryForce={false}
+              dataSize={20}
+              isRequest
+              isVisible
+              canDownload={!!props.supersetCanCSV}
+            />
+          }
         />
-      )}
+      ),
+    });
+  }
+
+  const { drillToDetailMenuItem, drillToDetailByMenuItem } =
+    useDrillDetailMenuItems({
+      formData: props.formData,
+      filters: modalFilters,
+      setFilters,
+      setShowModal: setDrillModalIsOpen,
+      key: MenuKeys.DrillToDetail,
+    });
+
+  const shareMenuItems = useShareMenuItems({
+    dashboardId,
+    dashboardComponentId: componentId,
+    copyMenuItemTitle: t('Copy permalink to clipboard'),
+    emailMenuItemTitle: t('Share chart by email'),
+    emailSubject: t('Superset chart'),
+    emailBody: t('Check out this chart: '),
+    addSuccessToast,
+    addDangerToast,
+    title: t('Share'),
+  });
+
+  if (isFeatureEnabled(FeatureFlag.DrillToDetail) && canDrillToDetail) {
+    newMenuItems.push(drillToDetailMenuItem);
+    if (drillToDetailByMenuItem) {
+      newMenuItems.push(drillToDetailByMenuItem);
+    }
+  }
+
+  if (slice.description || canExplore) {
+    newMenuItems.push({ type: 'divider' });
+  }
+
+  if (supersetCanShare) {
+    newMenuItems.push(shareMenuItems);
+  }
+
+  if (props.supersetCanCSV) {
+    newMenuItems.push({
+      type: 'submenu',
+      key: MenuKeys.Download,
+      label: t('Download'),
+      children: [
+        {
+          key: MenuKeys.ExportCsv,
+          label: t('Export to .CSV'),
+          icon: <Icons.FileOutlined css={dropdownIconsStyles} />,
+        },
+        ...(isPivotTable
+          ? [
+              {
+                key: MenuKeys.ExportPivotCsv,
+                label: t('Export to Pivoted .CSV'),
+                icon: <Icons.FileOutlined css={dropdownIconsStyles} />,
+              },
+            ]
+          : []),
+        {
+          key: MenuKeys.ExportXlsx,
+          label: t('Export to Excel'),
+          icon: <Icons.FileOutlined css={dropdownIconsStyles} />,
+        },
+        ...(isFeatureEnabled(FeatureFlag.AllowFullCsvExport) &&
+        props.supersetCanCSV &&
+        isTable
+          ? [
+              {
+                key: MenuKeys.ExportFullCsv,
+                label: t('Export to full .CSV'),
+                icon: <Icons.FileOutlined css={dropdownIconsStyles} />,
+              },
+              {
+                key: MenuKeys.ExportFullXlsx,
+                label: t('Export to full Excel'),
+                icon: <Icons.FileOutlined css={dropdownIconsStyles} />,
+              },
+            ]
+          : []),
+        {
+          key: MenuKeys.DownloadAsImage,
+          label: t('Download as image'),
+          icon: <Icons.FileImageOutlined css={dropdownIconsStyles} />,
+        },
+      ],
+    });
+  }
 
-      {props.supersetCanCSV && (
-        <Menu.SubMenu title={t('Download')} key={MenuKeys.Download}>
-          <Menu.Item
-            key={MenuKeys.ExportCsv}
-            icon={<Icons.FileOutlined css={dropdownIconsStyles} />}
-          >
-            {t('Export to .CSV')}
-          </Menu.Item>
-          {isPivotTable && (
-            <Menu.Item
-              key={MenuKeys.ExportPivotCsv}
-              icon={<Icons.FileOutlined css={dropdownIconsStyles} />}
-            >
-              {t('Export to Pivoted .CSV')}
-            </Menu.Item>
-          )}
-          <Menu.Item
-            key={MenuKeys.ExportXlsx}
-            icon={<Icons.FileOutlined css={dropdownIconsStyles} />}
-          >
-            {t('Export to Excel')}
-          </Menu.Item>
-
-          {isFeatureEnabled(FeatureFlag.AllowFullCsvExport) &&
-            props.supersetCanCSV &&
-            isTable && (
-              <>
-                <Menu.Item
-                  key={MenuKeys.ExportFullCsv}
-                  icon={<Icons.FileOutlined css={dropdownIconsStyles} />}
-                >
-                  {t('Export to full .CSV')}
-                </Menu.Item>
-                <Menu.Item
-                  key={MenuKeys.ExportFullXlsx}
-                  icon={<Icons.FileOutlined css={dropdownIconsStyles} />}
-                >
-                  {t('Export to full Excel')}
-                </Menu.Item>
-              </>
-            )}
-
-          <Menu.Item
-            key={MenuKeys.DownloadAsImage}
-            icon={<Icons.FileImageOutlined css={dropdownIconsStyles} />}
-          >
-            {t('Download as image')}
-          </Menu.Item>
-        </Menu.SubMenu>
-      )}
-    </Menu>
-  );
   return (
     <>
       {isFullSize && (
@@ -508,7 +528,15 @@ const SliceHeaderControls = (
         />
       )}
       <NoAnimationDropdown
-        popupRender={() => menu}
+        popupRender={() => (
+          <Menu
+            onClick={handleMenuClick}
+            data-test={`slice_${slice.slice_id}-menu`}
+            id={`slice_${slice.slice_id}-menu`}
+            selectable={false}
+            items={newMenuItems}
+          />
+        )}
         overlayStyle={dropdownOverlayStyle}
         trigger={['click']}
         placement="bottomRight"
diff --git 
a/superset-frontend/src/dashboard/components/menu/ShareMenuItems/index.tsx 
b/superset-frontend/src/dashboard/components/menu/ShareMenuItems/index.tsx
index 9d663958e3..d42a733284 100644
--- a/superset-frontend/src/dashboard/components/menu/ShareMenuItems/index.tsx
+++ b/superset-frontend/src/dashboard/components/menu/ShareMenuItems/index.tsx
@@ -19,7 +19,7 @@
 import { ComponentProps, RefObject } from 'react';
 import copyTextToClipboard from 'src/utils/copy';
 import { t, logging } from '@superset-ui/core';
-import { Menu } from '@superset-ui/core/components/Menu';
+import { Menu, MenuItem } from '@superset-ui/core/components/Menu';
 import { getDashboardPermalink } from 'src/utils/urlUtils';
 import { MenuKeys, RootState } from 'src/dashboard/types';
 import { shallowEqual, useSelector } from 'react-redux';
@@ -42,7 +42,7 @@ interface ShareMenuItemProps extends ComponentProps<typeof 
Menu.SubMenu> {
   disabled?: boolean;
 }
 
-const ShareMenuItems = (props: ShareMenuItemProps) => {
+export const useShareMenuItems = (props: ShareMenuItemProps): MenuItem => {
   const {
     copyMenuItemTitle,
     emailMenuItemTitle,
@@ -54,6 +54,7 @@ const ShareMenuItems = (props: ShareMenuItemProps) => {
     dashboardComponentId,
     title,
     disabled,
+    children,
     ...rest
   } = props;
   const { dataMask, activeTabs } = useSelector(
@@ -96,20 +97,23 @@ const ShareMenuItems = (props: ShareMenuItemProps) => {
     }
   }
 
-  return (
-    <Menu.SubMenu
-      title={title}
-      key={MenuKeys.Share}
-      disabled={disabled}
-      {...rest}
-    >
-      <Menu.Item key={MenuKeys.CopyLink} onClick={() => onCopyLink()}>
-        {copyMenuItemTitle}
-      </Menu.Item>
-      <Menu.Item key={MenuKeys.ShareByEmail} onClick={() => onShareByEmail()}>
-        {emailMenuItemTitle}
-      </Menu.Item>
-    </Menu.SubMenu>
-  );
+  return {
+    type: 'submenu',
+    label: title,
+    key: MenuKeys.Share,
+    disabled,
+    children: [
+      {
+        key: MenuKeys.CopyLink,
+        label: copyMenuItemTitle,
+        onClick: () => onCopyLink,
+      },
+      {
+        key: MenuKeys.ShareByEmail,
+        label: emailMenuItemTitle,
+        onClick: () => onShareByEmail,
+      },
+    ],
+    ...rest,
+  };
 };
-export default ShareMenuItems;

Reply via email to