mirror of
https://github.com/grafana/grafana.git
synced 2024-11-29 04:04:00 -06:00
ShareModal: Implement panel embed tab for scenes (#77062)
* ShareModal: Implement panel embed tab for scenes * Fix url generation * Locale --------- Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
parent
2598ff7c93
commit
4a5b8643ae
@ -11,6 +11,7 @@ import { getDashboardSceneFor } from '../utils/utils';
|
||||
|
||||
import { ShareExportTab } from './ShareExportTab';
|
||||
import { ShareLinkTab } from './ShareLinkTab';
|
||||
import { SharePanelEmbedTab } from './SharePanelEmbedTab';
|
||||
import { ShareSnapshotTab } from './ShareSnapshotTab';
|
||||
import { ModalSceneObjectLike, SceneShareTab } from './types';
|
||||
|
||||
@ -49,6 +50,10 @@ export class ShareModal extends SceneObjectBase<ShareModalState> implements Moda
|
||||
tabs.push(new ShareSnapshotTab({ panelRef, dashboardRef, modalRef: this.getRef() }));
|
||||
}
|
||||
|
||||
if (panelRef) {
|
||||
tabs.push(new SharePanelEmbedTab({ panelRef, dashboardRef }));
|
||||
}
|
||||
|
||||
this.setState({ tabs });
|
||||
|
||||
// if (panel) {
|
||||
|
@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
|
||||
import { TimeRange } from '@grafana/data';
|
||||
import { SceneComponentProps, sceneGraph, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { ShareEmbed } from 'app/features/dashboard/components/ShareModal/ShareEmbed';
|
||||
import { buildParams } from 'app/features/dashboard/components/ShareModal/utils';
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { getDashboardUrl } from '../utils/urlBuilders';
|
||||
import { getPanelIdForVizPanel } from '../utils/utils';
|
||||
|
||||
import { SceneShareTabState } from './types';
|
||||
|
||||
export interface SharePanelEmbedTabState extends SceneShareTabState {
|
||||
panelRef: SceneObjectRef<VizPanel>;
|
||||
dashboardRef: SceneObjectRef<DashboardScene>;
|
||||
}
|
||||
|
||||
export class SharePanelEmbedTab extends SceneObjectBase<SharePanelEmbedTabState> {
|
||||
static Component = SharePanelEmbedTabRenderer;
|
||||
|
||||
public constructor(state: SharePanelEmbedTabState) {
|
||||
super(state);
|
||||
}
|
||||
|
||||
public getTabLabel() {
|
||||
return t('share-modal.tab-title.panel-embed', 'Embed');
|
||||
}
|
||||
}
|
||||
|
||||
function SharePanelEmbedTabRenderer({ model }: SceneComponentProps<SharePanelEmbedTab>) {
|
||||
const { panelRef, dashboardRef } = model.useState();
|
||||
const p = panelRef.resolve();
|
||||
|
||||
const dash = dashboardRef.resolve();
|
||||
const { uid: dashUid } = dash.useState();
|
||||
const id = getPanelIdForVizPanel(p);
|
||||
const timeRangeState = sceneGraph.getTimeRange(p);
|
||||
|
||||
return (
|
||||
<ShareEmbed
|
||||
panel={{
|
||||
id,
|
||||
timeFrom:
|
||||
typeof timeRangeState.state.value.raw.from === 'string' ? timeRangeState.state.value.raw.from : undefined,
|
||||
}}
|
||||
range={timeRangeState.state.value}
|
||||
dashboard={{ uid: dashUid ?? '', time: timeRangeState.state.value }}
|
||||
buildIframe={buildIframe}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function buildIframe(
|
||||
useCurrentTimeRange: boolean,
|
||||
dashboardUid: string,
|
||||
selectedTheme?: string,
|
||||
panel?: { timeFrom?: string; id: number },
|
||||
range?: TimeRange
|
||||
) {
|
||||
const params = buildParams({ useCurrentTimeRange, selectedTheme, panel, range });
|
||||
const panelId = params.get('editPanel') ?? params.get('viewPanel') ?? '';
|
||||
params.set('panelId', panelId);
|
||||
params.delete('editPanel');
|
||||
params.delete('viewPanel');
|
||||
|
||||
const soloUrl = getDashboardUrl({
|
||||
absolute: true,
|
||||
soloRoute: true,
|
||||
uid: dashboardUid,
|
||||
currentQueryParams: params.toString(),
|
||||
});
|
||||
return `<iframe src="${soloUrl}" width="450" height="200" frameborder="0"></iframe>`;
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
import React, { FormEvent, PureComponent } from 'react';
|
||||
import React, { FormEvent, useEffect, useState } from 'react';
|
||||
import { useEffectOnce } from 'react-use';
|
||||
|
||||
import { reportInteraction } from '@grafana/runtime/src';
|
||||
import { RawTimeRange, TimeRange } from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { ClipboardButton, Field, Modal, Switch, TextArea } from '@grafana/ui';
|
||||
import { t, Trans } from 'app/core/internationalization';
|
||||
|
||||
@ -8,103 +10,76 @@ import { ThemePicker } from './ThemePicker';
|
||||
import { ShareModalTabProps } from './types';
|
||||
import { buildIframeHtml } from './utils';
|
||||
|
||||
interface Props extends ShareModalTabProps {}
|
||||
|
||||
interface State {
|
||||
useCurrentTimeRange: boolean;
|
||||
selectedTheme: string;
|
||||
iframeHtml: string;
|
||||
interface Props extends Omit<ShareModalTabProps, 'panel' | 'dashboard'> {
|
||||
panel?: { timeFrom?: string; id: number };
|
||||
dashboard: { uid: string; time: RawTimeRange };
|
||||
range?: TimeRange;
|
||||
buildIframe?: typeof buildIframeHtml;
|
||||
}
|
||||
|
||||
export class ShareEmbed extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
useCurrentTimeRange: true,
|
||||
selectedTheme: 'current',
|
||||
iframeHtml: '',
|
||||
};
|
||||
}
|
||||
export function ShareEmbed({ panel, dashboard, range, buildIframe = buildIframeHtml }: Props) {
|
||||
const [useCurrentTimeRange, setUseCurrentTimeRange] = useState(true);
|
||||
const [selectedTheme, setSelectedTheme] = useState('current');
|
||||
const [iframeHtml, setIframeHtml] = useState('');
|
||||
|
||||
componentDidMount() {
|
||||
useEffectOnce(() => {
|
||||
reportInteraction('grafana_dashboards_embed_share_viewed');
|
||||
this.buildIframeHtml();
|
||||
}
|
||||
});
|
||||
|
||||
buildIframeHtml = () => {
|
||||
const { panel, dashboard } = this.props;
|
||||
const { useCurrentTimeRange, selectedTheme } = this.state;
|
||||
useEffect(() => {
|
||||
const newIframeHtml = buildIframe(useCurrentTimeRange, dashboard.uid, selectedTheme, panel, range);
|
||||
setIframeHtml(newIframeHtml);
|
||||
}, [selectedTheme, useCurrentTimeRange, dashboard, panel, range, buildIframe]);
|
||||
|
||||
const iframeHtml = buildIframeHtml(useCurrentTimeRange, dashboard.uid, selectedTheme, panel);
|
||||
this.setState({ iframeHtml });
|
||||
const onIframeHtmlChange = (event: FormEvent<HTMLTextAreaElement>) => {
|
||||
setIframeHtml(event.currentTarget.value);
|
||||
};
|
||||
|
||||
onIframeHtmlChange = (event: FormEvent<HTMLTextAreaElement>) => {
|
||||
this.setState({ iframeHtml: event.currentTarget.value });
|
||||
const onUseCurrentTimeRangeChange = () => {
|
||||
setUseCurrentTimeRange((useCurTimeRange) => !useCurTimeRange);
|
||||
};
|
||||
|
||||
onUseCurrentTimeRangeChange = () => {
|
||||
this.setState(
|
||||
{
|
||||
useCurrentTimeRange: !this.state.useCurrentTimeRange,
|
||||
},
|
||||
this.buildIframeHtml
|
||||
);
|
||||
const onThemeChange = (value: string) => {
|
||||
setSelectedTheme(value);
|
||||
};
|
||||
|
||||
onThemeChange = (value: string) => {
|
||||
this.setState({ selectedTheme: value }, this.buildIframeHtml);
|
||||
};
|
||||
const isRelativeTime = dashboard.time.to === 'now';
|
||||
const timeRangeDescription = isRelativeTime
|
||||
? t(
|
||||
'share-modal.embed.time-range-description',
|
||||
'Transforms the current relative time range to an absolute time range'
|
||||
)
|
||||
: '';
|
||||
|
||||
getIframeHtml = () => {
|
||||
return this.state.iframeHtml;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { useCurrentTimeRange, selectedTheme, iframeHtml } = this.state;
|
||||
const isRelativeTime = this.props.dashboard ? this.props.dashboard.time.to === 'now' : false;
|
||||
|
||||
const timeRangeDescription = isRelativeTime
|
||||
? t(
|
||||
'share-modal.embed.time-range-description',
|
||||
'Transforms the current relative time range to an absolute time range'
|
||||
)
|
||||
: '';
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className="share-modal-info-text">
|
||||
<Trans i18nKey="share-modal.embed.info">Generate HTML for embedding an iframe with this panel.</Trans>
|
||||
</p>
|
||||
<Field label={t('share-modal.embed.time-range', 'Current time range')} description={timeRangeDescription}>
|
||||
<Switch
|
||||
id="share-current-time-range"
|
||||
value={useCurrentTimeRange}
|
||||
onChange={this.onUseCurrentTimeRangeChange}
|
||||
/>
|
||||
</Field>
|
||||
<ThemePicker selectedTheme={selectedTheme} onChange={this.onThemeChange} />
|
||||
<Field
|
||||
label={t('share-modal.embed.html', 'Embed HTML')}
|
||||
description={t(
|
||||
'share-modal.embed.html-description',
|
||||
'The HTML code below can be pasted and included in another web page. Unless anonymous access is enabled, the user viewing that page need to be signed into Grafana for the graph to load.'
|
||||
)}
|
||||
>
|
||||
<TextArea
|
||||
data-testid="share-embed-html"
|
||||
id="share-panel-embed-embed-html-textarea"
|
||||
rows={5}
|
||||
value={iframeHtml}
|
||||
onChange={this.onIframeHtmlChange}
|
||||
/>
|
||||
</Field>
|
||||
<Modal.ButtonRow>
|
||||
<ClipboardButton icon="copy" variant="primary" getText={this.getIframeHtml}>
|
||||
<Trans i18nKey="share-modal.embed.copy">Copy to clipboard</Trans>
|
||||
</ClipboardButton>
|
||||
</Modal.ButtonRow>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<p className="share-modal-info-text">
|
||||
<Trans i18nKey="share-modal.embed.info">Generate HTML for embedding an iframe with this panel.</Trans>
|
||||
</p>
|
||||
<Field label={t('share-modal.embed.time-range', 'Current time range')} description={timeRangeDescription}>
|
||||
<Switch id="share-current-time-range" value={useCurrentTimeRange} onChange={onUseCurrentTimeRangeChange} />
|
||||
</Field>
|
||||
<ThemePicker selectedTheme={selectedTheme} onChange={onThemeChange} />
|
||||
<Field
|
||||
label={t('share-modal.embed.html', 'Embed HTML')}
|
||||
description={t(
|
||||
'share-modal.embed.html-description',
|
||||
'The HTML code below can be pasted and included in another web page. Unless anonymous access is enabled, the user viewing that page need to be signed into Grafana for the graph to load.'
|
||||
)}
|
||||
>
|
||||
<TextArea
|
||||
data-testid="share-embed-html"
|
||||
id="share-panel-embed-embed-html-textarea"
|
||||
rows={5}
|
||||
value={iframeHtml}
|
||||
onChange={onIframeHtmlChange}
|
||||
/>
|
||||
</Field>
|
||||
<Modal.ButtonRow>
|
||||
<ClipboardButton icon="copy" variant="primary" getText={() => iframeHtml}>
|
||||
<Trans i18nKey="share-modal.embed.copy">Copy to clipboard</Trans>
|
||||
</ClipboardButton>
|
||||
</Modal.ButtonRow>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import { PanelModel } from '../../state';
|
||||
export interface BuildParamsArgs {
|
||||
useCurrentTimeRange: boolean;
|
||||
selectedTheme?: string;
|
||||
panel?: PanelModel;
|
||||
panel?: { timeFrom?: string; id: number };
|
||||
search?: string;
|
||||
range?: TimeRange;
|
||||
orgId?: number;
|
||||
@ -82,10 +82,11 @@ export function buildSoloUrl(
|
||||
useCurrentTimeRange: boolean,
|
||||
dashboardUid: string,
|
||||
selectedTheme?: string,
|
||||
panel?: PanelModel
|
||||
panel?: { timeFrom?: string; id: number },
|
||||
range?: TimeRange
|
||||
) {
|
||||
const baseUrl = buildBaseUrl();
|
||||
const params = buildParams({ useCurrentTimeRange, selectedTheme, panel });
|
||||
const params = buildParams({ useCurrentTimeRange, selectedTheme, panel, range });
|
||||
|
||||
let soloUrl = baseUrl.replace(config.appSubUrl + '/dashboard/', config.appSubUrl + '/dashboard-solo/');
|
||||
soloUrl = soloUrl.replace(config.appSubUrl + '/d/', config.appSubUrl + '/d-solo/');
|
||||
@ -121,10 +122,11 @@ export function buildIframeHtml(
|
||||
useCurrentTimeRange: boolean,
|
||||
dashboardUid: string,
|
||||
selectedTheme?: string,
|
||||
panel?: PanelModel
|
||||
panel?: { timeFrom?: string; id: number },
|
||||
range?: TimeRange
|
||||
) {
|
||||
let soloUrl = buildSoloUrl(useCurrentTimeRange, dashboardUid, selectedTheme, panel);
|
||||
return '<iframe src="' + soloUrl + '" width="450" height="200" frameborder="0"></iframe>';
|
||||
let soloUrl = buildSoloUrl(useCurrentTimeRange, dashboardUid, selectedTheme, panel, range);
|
||||
return `<iframe src="${soloUrl}" width="450" height="200" frameborder="0"></iframe>`;
|
||||
}
|
||||
|
||||
export function getLocalTimeZone() {
|
||||
|
@ -1090,6 +1090,7 @@
|
||||
"export": "Exportieren",
|
||||
"library-panel": "Bibliotheks-Panel",
|
||||
"link": "Link",
|
||||
"panel-embed": "",
|
||||
"snapshot": "Schnappschuss"
|
||||
},
|
||||
"theme-picker": {
|
||||
|
@ -1090,6 +1090,7 @@
|
||||
"export": "Export",
|
||||
"library-panel": "Library panel",
|
||||
"link": "Link",
|
||||
"panel-embed": "Embed",
|
||||
"snapshot": "Snapshot"
|
||||
},
|
||||
"theme-picker": {
|
||||
|
@ -1096,6 +1096,7 @@
|
||||
"export": "Exportar",
|
||||
"library-panel": "Panel de librería",
|
||||
"link": "Enlace",
|
||||
"panel-embed": "",
|
||||
"snapshot": "Instantánea"
|
||||
},
|
||||
"theme-picker": {
|
||||
|
@ -1096,6 +1096,7 @@
|
||||
"export": "Exporter",
|
||||
"library-panel": "Panneau de bibliothèque",
|
||||
"link": "Lien",
|
||||
"panel-embed": "",
|
||||
"snapshot": "Instantané"
|
||||
},
|
||||
"theme-picker": {
|
||||
|
@ -1090,6 +1090,7 @@
|
||||
"export": "Ēχpőřŧ",
|
||||
"library-panel": "Ŀįþřäřy päʼnęľ",
|
||||
"link": "Ŀįʼnĸ",
|
||||
"panel-embed": "Ēmþęđ",
|
||||
"snapshot": "Ŝʼnäpşĥőŧ"
|
||||
},
|
||||
"theme-picker": {
|
||||
|
@ -1084,6 +1084,7 @@
|
||||
"export": "导出",
|
||||
"library-panel": "库面板",
|
||||
"link": "链接",
|
||||
"panel-embed": "",
|
||||
"snapshot": "快照"
|
||||
},
|
||||
"theme-picker": {
|
||||
|
Loading…
Reference in New Issue
Block a user