Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7a9f687
Test fork commit
nelsondrew Apr 9, 2025
1cec095
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 Apr 16, 2025
696d5d9
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 Apr 25, 2025
672c16f
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 Apr 29, 2025
3df058e
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 Apr 29, 2025
dc5820d
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 May 1, 2025
dce9fe3
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 May 4, 2025
7f51dca
Merge branch 'master' of https://github.com/apache/superset
amaannawab923 May 11, 2025
18b944f
embeddable charts
amaannawab923 May 13, 2025
7ecfb4b
Using dimensions of app
amaannawab923 May 13, 2025
3f3fc34
Resize glitch
amaannawab923 May 13, 2025
be5fab4
Embedded chart migration & tables
amaannawab923 May 13, 2025
3d72912
Immediate hydration
amaannawab923 May 14, 2025
d39b43e
Resolving ts errors for datasources
amaannawab923 May 14, 2025
9215c2a
Removing log
amaannawab923 May 14, 2025
5250941
Apache headers
amaannawab923 May 14, 2025
c92f484
Embedded get endpoint for chart
amaannawab923 May 14, 2025
6808c67
embed chart post end point
amaannawab923 May 14, 2025
d7b804b
router correction
amaannawab923 May 14, 2025
f8220a1
Delete embedded chart api
amaannawab923 May 14, 2025
c999231
Embed chart controls
amaannawab923 May 14, 2025
196aa28
hydrate embedded improvement
amaannawab923 May 14, 2025
4290d34
dashboard info correction
amaannawab923 May 14, 2025
ce3958b
view . py changes to support both dashboards & charts
amaannawab923 May 14, 2025
e3a65ba
Enabling embedding in config.py
amaannawab923 May 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,14 @@ export interface SliceHeaderExtension {
dashboardId: number;
}

type ResourceType = 'dashboard' | 'chart';

/**
* Interface for extensions to Embed Modal
*/
export interface DashboardEmbedModalExtensions {
dashboardId: string;
show: boolean;
resourceId: string;
resourceType: ResourceType;
onHide: () => void;
}

Expand Down
7 changes: 6 additions & 1 deletion superset-frontend/src/components/Chart/chartReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { ChartState } from 'src/explore/types';
import { getFormDataFromControls } from 'src/explore/controlUtils';
import { HYDRATE_EXPLORE } from 'src/explore/actions/hydrateExplore';
import { now } from 'src/utils/dates';
import { HYDRATE_EMBEDDED } from 'src/embedded/embeddedChart/hydrateEmbedded';
import * as actions from './chartAction';

export const chart: ChartState = {
Expand Down Expand Up @@ -192,7 +193,11 @@ export default function chartReducer(
delete charts[key];
return charts;
}
if (action.type === HYDRATE_DASHBOARD || action.type === HYDRATE_EXPLORE) {
if (
action.type === HYDRATE_DASHBOARD ||
action.type === HYDRATE_EXPLORE ||
action.type === HYDRATE_EMBEDDED
) {
return { ...action.data.charts };
}
if (action.type === DatasourcesAction.SetDatasources) {
Expand Down
4 changes: 3 additions & 1 deletion superset-frontend/src/dashboard/actions/datasources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import { Dispatch } from 'redux';
import { SupersetClient } from '@superset-ui/core';
import { Datasource, RootState } from 'src/dashboard/types';
import { HydrateEmbeddedAction } from 'src/embedded/embeddedChart/hydrateEmbedded';

// update datasources index for Dashboard
export enum DatasourcesAction {
Expand All @@ -35,7 +36,8 @@ export type DatasourcesActionPayload =
type: DatasourcesAction.SetDatasource;
key: Datasource['uid'];
datasource: Datasource;
};
}
| HydrateEmbeddedAction;

export function setDatasources(datasources: Datasource[] | null) {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,15 @@ const setMockApiNotFound = () => {
};

const setup = () => {
render(<DashboardEmbedModal {...defaultProps} />, { useRedux: true });
render(
<DashboardEmbedModal
resourceId={defaultProps.dashboardId}
show
onHide={mockOnHide}
resourceType="dashboard"
/>,
{ useRedux: true },
);
resetMockApi();
};

Expand All @@ -79,7 +87,15 @@ test('renders loading state', async () => {
});

test('renders the modal default content', async () => {
render(<DashboardEmbedModal {...defaultProps} />, { useRedux: true });
render(
<DashboardEmbedModal
resourceId={defaultProps.dashboardId}
show
onHide={mockOnHide}
resourceType="dashboard"
/>,
{ useRedux: true },
);
expect(await screen.findByText('Settings')).toBeInTheDocument();
expect(
screen.getByText(new RegExp(/Allowed Domains/, 'i')),
Expand Down
77 changes: 49 additions & 28 deletions superset-frontend/src/dashboard/components/EmbeddedModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,30 @@ import Button from 'src/components/Button';
import { Input } from 'src/components/Input';
import { useToasts } from 'src/components/MessageToasts/withToasts';
import { FormItem } from 'src/components/Form';
import { EmbeddedDashboard } from 'src/dashboard/types';

const extensionsRegistry = getExtensionsRegistry();

type ResourceType = 'dashboard' | 'chart';

type Props = {
dashboardId: string;
resourceId: string;
resourceType: ResourceType;
show: boolean;
onHide: () => void;
};

type EmbeddedApiPayload = { allowed_domains: string[] };

type EmbeddedResource = {
uuid: string;
allowed_domains: string[];
changed_on: string;
changed_by: {
first_name: string;
last_name: string;
};
};

const stringToList = (stringyList: string): string[] =>
stringyList.split(/(?:\s|,)+/).filter(x => x);

Expand All @@ -52,22 +64,26 @@ const ButtonRow = styled.div`
justify-content: flex-end;
`;

export const DashboardEmbedControls = ({ dashboardId, onHide }: Props) => {
export const ResourceEmbedControls = ({
resourceId,
resourceType,
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
const [embedded, setEmbedded] = useState<EmbeddedDashboard | null>(null); // the embedded dashboard config
const [ready, setReady] = useState(true);
const [loading, setLoading] = useState(false);
const [embedded, setEmbedded] = useState<EmbeddedResource | null>(null);
const [allowedDomains, setAllowedDomains] = useState<string>('');

const endpoint = `/api/v1/dashboard/${dashboardId}/embedded`;
// whether saveable changes have been made to the config
const endpoint = `/api/v1/${resourceType}/${resourceId}/embedded`;

const isDirty =
!embedded ||
stringToList(allowedDomains).join() !== embedded.allowed_domains.join();

const enableEmbedded = useCallback(() => {
setLoading(true);
makeApi<EmbeddedApiPayload, { result: EmbeddedDashboard }>({
makeApi<EmbeddedApiPayload, { result: EmbeddedResource }>({
method: 'POST',
endpoint,
})({
Expand All @@ -82,9 +98,7 @@ export const DashboardEmbedControls = ({ dashboardId, onHide }: Props) => {
err => {
console.error(err);
addDangerToast(
t(
t('Sorry, something went wrong. The changes could not be saved.'),
),
t('Sorry, something went wrong. The changes could not be saved.'),
);
},
)
Expand Down Expand Up @@ -126,13 +140,12 @@ export const DashboardEmbedControls = ({ dashboardId, onHide }: Props) => {

useEffect(() => {
setReady(false);
makeApi<{}, { result: EmbeddedDashboard }>({
makeApi<{}, { result: EmbeddedResource }>({
method: 'GET',
endpoint,
})({})
.catch(err => {
if ((err as SupersetApiError).status === 404) {
// 404 just means the dashboard isn't currently embedded
return { result: null };
}
addDangerToast(t('Sorry, something went wrong. Please try again.'));
Expand All @@ -143,7 +156,7 @@ export const DashboardEmbedControls = ({ dashboardId, onHide }: Props) => {
setEmbedded(result);
setAllowedDomains(result ? result.allowed_domains.join(', ') : '');
});
}, [dashboardId]);
}, [resourceId, resourceType]);

if (!ready) {
return <Loading />;
Expand All @@ -166,18 +179,26 @@ export const DashboardEmbedControls = ({ dashboardId, onHide }: Props) => {
<DocsConfigDetails embeddedId={embedded.uuid} />
) : (
<p>
{t(
'This dashboard is ready to embed. In your application, pass the following id to the SDK:',
)}
{resourceType === 'chart'
? t(
`This chart is ready to embed. In your application, pass the following id to the SDK:`,
)
: t(
`This dashboard is ready to embed. In your application, pass the following id to the SDK:`,
)}
<br />
<code>{embedded.uuid}</code>
</p>
)
) : (
<p>
{t(
'Configure this dashboard to embed it into an external web application.',
)}
{resourceType === 'chart'
? t(
`Configure this chart to embed it into an external web application.`,
)
: t(
`Configure this dashboard to embed it into an external web application.`,
)}
</p>
)}
<p>
Expand All @@ -194,7 +215,7 @@ export const DashboardEmbedControls = ({ dashboardId, onHide }: Props) => {
{t('Allowed Domains (comma separated)')}{' '}
<InfoTooltipWithTrigger
tooltip={t(
'A list of domain names that can embed this dashboard. Leaving this field empty will allow embedding from any domain.',
'A list of domain names that can embed this content. Leaving this field empty will allow embedding from any domain.',
)}
/>
</label>
Expand Down Expand Up @@ -239,17 +260,17 @@ export const DashboardEmbedControls = ({ dashboardId, onHide }: Props) => {
);
};

const DashboardEmbedModal = (props: Props) => {
const EmbedModal = (props: Props) => {
const { show, onHide } = props;
const DashboardEmbedModalExtension = extensionsRegistry.get('embedded.modal');
const EmbedModalExtension = extensionsRegistry.get('embedded.modal');

return DashboardEmbedModalExtension ? (
<DashboardEmbedModalExtension {...props} />
return EmbedModalExtension ? (
<EmbedModalExtension {...props} />
) : (
<Modal show={show} onHide={onHide} title={t('Embed')} hideFooter>
<DashboardEmbedControls {...props} />
<ResourceEmbedControls {...props} />
</Modal>
);
};

export default DashboardEmbedModal;
export default EmbedModal;
3 changes: 2 additions & 1 deletion superset-frontend/src/dashboard/components/Header/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,8 @@ const Header = () => {
<DashboardEmbedModal
show={showingEmbedModal}
onHide={hideEmbedModal}
dashboardId={dashboardInfo.id}
resourceId={dashboardInfo.id}
resourceType="dashboard"
/>
)}
<Global
Expand Down
40 changes: 39 additions & 1 deletion superset-frontend/src/dashboard/components/SliceHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,20 @@
import {
forwardRef,
ReactNode,
useCallback,
useContext,
useEffect,
useRef,
useState,
} from 'react';
import { css, getExtensionsRegistry, styled, t } from '@superset-ui/core';
import {
css,
FeatureFlag,
getExtensionsRegistry,
isFeatureEnabled,
styled,
t,
} from '@superset-ui/core';
import { useUiConfig } from 'src/components/UiConfigContext';
import { Tooltip } from 'src/components/Tooltip';
import { useSelector } from 'react-redux';
Expand All @@ -36,6 +44,8 @@ import { Icons } from 'src/components/Icons';
import { RootState } from 'src/dashboard/types';
import { getSliceHeaderTooltip } from 'src/dashboard/util/getSliceHeaderTooltip';
import { DashboardPageIdContext } from 'src/dashboard/containers/DashboardPage';
import { findPermission } from 'src/utils/findPermission';
import EmbedModal from '../EmbeddedModal';

const extensionsRegistry = getExtensionsRegistry();

Expand Down Expand Up @@ -195,6 +205,23 @@ const SliceHeader = forwardRef<HTMLDivElement, SliceHeaderProps>(

const exploreUrl = `/explore/?dashboard_page_id=${dashboardPageId}&slice_id=${slice.slice_id}`;

const [showingEmbedModal, setShowingEmbedModal] = useState(false);

const showEmbedModal = useCallback(() => {
setShowingEmbedModal(true);
}, []);

const hideEmbedModal = useCallback(() => {
setShowingEmbedModal(false);
}, []);

// @ts-ignore
const user: User = useSelector<RootState>(state => state.user);

const userCanCurate =
isFeatureEnabled(FeatureFlag.EmbeddedSuperset) &&
findPermission('can_set_embedded', 'Dashboard', user.roles);

return (
<ChartHeaderStyles data-test="slice-header" ref={ref}>
<div className="header-title" ref={headerRef}>
Expand Down Expand Up @@ -265,6 +292,16 @@ const SliceHeader = forwardRef<HTMLDivElement, SliceHeaderProps>(
{!uiConfig.hideChartControls && (
<FiltersBadge chartId={slice.slice_id} />
)}

{userCanCurate && (
<EmbedModal
show={showingEmbedModal}
onHide={hideEmbedModal}
resourceId={String(slice.slice_id)}
resourceType="chart"
/>
)}

{!uiConfig.hideChartControls && (
<SliceHeaderControls
slice={slice}
Expand Down Expand Up @@ -295,6 +332,7 @@ const SliceHeader = forwardRef<HTMLDivElement, SliceHeaderProps>(
formData={formData}
exploreUrl={exploreUrl}
crossFiltersEnabled={isCrossFiltersEnabled}
showEmbedModal={showEmbedModal}
/>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const createProps = (viz_type = VizType.Sunburst) =>
},
exploreUrl: '/explore',
defaultOpen: true,
showEmbedModal: () => {},
}) as SliceHeaderControlsProps;

const renderWrapper = (
Expand Down
Loading
Loading