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)} />}
</>
);