You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by am...@apache.org on 2026/09/01 16:21:55 UTC

(superset) 05/15: feat(embedded): expose Embed chart from the chart menu

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

amaannawab923 pushed a commit to branch feat/embedded-chart
in repository https://gitbox.apache.org/repos/asf/superset.git

commit cccd6c260bbb42254700d0268a7cfed701cdb7e6
Author: amaannawab923 <am...@gmail.com>
AuthorDate: Tue Sep 1 18:44:10 2026 +0530

    feat(embedded): expose Embed chart from the chart menu
    
    Generalises the embed modal with an optional resourceType (defaulting to
    dashboard, so existing call sites are unchanged) and adds an Embed chart
    item to the chart header menu, gated on EMBEDDED_SUPERSET and the
    can_set_embedded permission on Chart.
---
 .../dashboard/components/EmbeddedModal/index.tsx   | 11 +++++++--
 .../components/SliceHeaderControls/index.tsx       | 28 ++++++++++++++++++++++
 2 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/superset-frontend/src/dashboard/components/EmbeddedModal/index.tsx b/superset-frontend/src/dashboard/components/EmbeddedModal/index.tsx
index 4c9c7c7d9a0..a77c623b614 100644
--- a/superset-frontend/src/dashboard/components/EmbeddedModal/index.tsx
+++ b/superset-frontend/src/dashboard/components/EmbeddedModal/index.tsx
@@ -44,6 +44,9 @@ const extensionsRegistry = getExtensionsRegistry();
 
 type Props = {
   dashboardId: string;
+  // Which resource the id refers to. Defaults to 'dashboard' so existing
+  // call sites are unaffected; charts reuse the same controls.
+  resourceType?: 'dashboard' | 'chart';
   show: boolean;
   onHide: () => void;
 };
@@ -59,7 +62,11 @@ const ButtonRow = styled.div`
   justify-content: flex-end;
 `;
 
-export const DashboardEmbedControls = ({ dashboardId, onHide }: Props) => {
+export const DashboardEmbedControls = ({
+  dashboardId,
+  resourceType = 'dashboard',
+  onHide,
+}: Props) => {
   const { addInfoToast, addDangerToast } = useToasts();
   const [ready, setReady] = useState(true); // whether we have initialized yet
   const [loading, setLoading] = useState(false); // whether we are currently doing an async thing
@@ -67,7 +74,7 @@ export const DashboardEmbedControls = ({ dashboardId, onHide }: Props) => {
   const [allowedDomains, setAllowedDomains] = useState<string>('');
   const [showDeactivateConfirm, setShowDeactivateConfirm] = useState(false);
 
-  const endpoint = `/api/v1/dashboard/${dashboardId}/embedded`;
+  const endpoint = `/api/v1/${resourceType}/${dashboardId}/embedded`;
   // whether saveable changes have been made to the config
   const isDirty =
     !embedded ||
diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
index 2a9a882898c..9360b660057 100644
--- a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
+++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
@@ -64,6 +64,8 @@ import {
   LOG_ACTIONS_CHART_DOWNLOAD_AS_PDF,
 } from 'src/logger/LogUtils';
 import { MenuKeys, RootState } from 'src/dashboard/types';
+import { findPermission } from 'src/utils/findPermission';
+import DashboardEmbedModal from 'src/dashboard/components/EmbeddedModal';
 import DrillDetailModal from 'src/components/Chart/DrillDetail/DrillDetailModal';
 import { openInNewTab } from 'src/utils/navigationUtils';
 import { usePermissions } from 'src/hooks/usePermissions';
@@ -173,6 +175,13 @@ const SliceHeaderControls = (
   props: SliceHeaderControlsPropsWithRouter | SliceHeaderControlsProps,
 ) => {
   const [drillModalIsOpen, setDrillModalIsOpen] = useState(false);
+  const [embedModalIsOpen, setEmbedModalIsOpen] = useState(false);
+  const user = useSelector((state: RootState) => state.user);
+  // Mirrors the dashboard's `userCanCurate`: embedding must be enabled and the
+  // user must hold the chart-level set_embedded permission.
+  const canEmbed =
+    isFeatureEnabled(FeatureFlag.EmbeddedSuperset) &&
+    findPermission('can_set_embedded', 'Chart', user?.roles);
   // setting openKeys undefined falls back to uncontrolled behaviour
   const [isDropdownVisible, setIsDropdownVisible] = useState(false);
   const [openScopingModal, scopingModal] = useCrossFiltersScopingModal(
@@ -263,6 +272,9 @@ const SliceHeaderControls = (
         refreshChart();
         props.addSuccessToast(t('Data refreshed'));
         break;
+      case MenuKeys.ManageEmbedded:
+        setEmbedModalIsOpen(true);
+        break;
       case MenuKeys.ToggleChartDescription:
         // eslint-disable-next-line no-unused-expressions
         props.toggleExpandSlice?.(props.slice.slice_id);
@@ -513,6 +525,14 @@ const SliceHeaderControls = (
       key: MenuKeys.Fullscreen,
       label: fullscreenLabel,
     },
+    ...(canEmbed
+      ? [
+          {
+            key: MenuKeys.ManageEmbedded,
+            label: t('Embed chart'),
+          },
+        ]
+      : []),
     {
       type: 'divider',
     },
@@ -806,6 +826,14 @@ const SliceHeaderControls = (
         dataset={datasetWithVerboseMap}
       />
       {canEditCrossFilters && scopingModal}
+      {canEmbed && (
+        <DashboardEmbedModal
+          show={embedModalIsOpen}
+          onHide={() => setEmbedModalIsOpen(false)}
+          dashboardId={String(slice.slice_id)}
+          resourceType="chart"
+        />
+      )}
       {isFullSize && <Global styles={fullscreenStyles(theme)} />}
     </>
   );