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>
pull/77042/head^2
kay delaney 2 years ago committed by GitHub
parent 2598ff7c93
commit 4a5b8643ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      public/app/features/dashboard-scene/sharing/ShareModal.tsx
  2. 75
      public/app/features/dashboard-scene/sharing/SharePanelEmbedTab.tsx
  3. 85
      public/app/features/dashboard/components/ShareModal/ShareEmbed.tsx
  4. 14
      public/app/features/dashboard/components/ShareModal/utils.ts
  5. 1
      public/locales/de-DE/grafana.json
  6. 1
      public/locales/en-US/grafana.json
  7. 1
      public/locales/es-ES/grafana.json
  8. 1
      public/locales/fr-FR/grafana.json
  9. 1
      public/locales/pseudo-LOCALE/grafana.json
  10. 1
      public/locales/zh-Hans/grafana.json

@ -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,62 +10,40 @@ 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;
const iframeHtml = buildIframeHtml(useCurrentTimeRange, dashboard.uid, selectedTheme, panel);
this.setState({ iframeHtml });
};
});
onIframeHtmlChange = (event: FormEvent<HTMLTextAreaElement>) => {
this.setState({ iframeHtml: event.currentTarget.value });
};
useEffect(() => {
const newIframeHtml = buildIframe(useCurrentTimeRange, dashboard.uid, selectedTheme, panel, range);
setIframeHtml(newIframeHtml);
}, [selectedTheme, useCurrentTimeRange, dashboard, panel, range, buildIframe]);
onUseCurrentTimeRangeChange = () => {
this.setState(
{
useCurrentTimeRange: !this.state.useCurrentTimeRange,
},
this.buildIframeHtml
);
const onIframeHtmlChange = (event: FormEvent<HTMLTextAreaElement>) => {
setIframeHtml(event.currentTarget.value);
};
onThemeChange = (value: string) => {
this.setState({ selectedTheme: value }, this.buildIframeHtml);
const onUseCurrentTimeRangeChange = () => {
setUseCurrentTimeRange((useCurTimeRange) => !useCurTimeRange);
};
getIframeHtml = () => {
return this.state.iframeHtml;
const onThemeChange = (value: string) => {
setSelectedTheme(value);
};
render() {
const { useCurrentTimeRange, selectedTheme, iframeHtml } = this.state;
const isRelativeTime = this.props.dashboard ? this.props.dashboard.time.to === 'now' : false;
const isRelativeTime = dashboard.time.to === 'now';
const timeRangeDescription = isRelativeTime
? t(
'share-modal.embed.time-range-description',
@ -77,13 +57,9 @@ export class ShareEmbed extends PureComponent<Props, State> {
<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}
/>
<Switch id="share-current-time-range" value={useCurrentTimeRange} onChange={onUseCurrentTimeRangeChange} />
</Field>
<ThemePicker selectedTheme={selectedTheme} onChange={this.onThemeChange} />
<ThemePicker selectedTheme={selectedTheme} onChange={onThemeChange} />
<Field
label={t('share-modal.embed.html', 'Embed HTML')}
description={t(
@ -96,15 +72,14 @@ export class ShareEmbed extends PureComponent<Props, State> {
id="share-panel-embed-embed-html-textarea"
rows={5}
value={iframeHtml}
onChange={this.onIframeHtmlChange}
onChange={onIframeHtmlChange}
/>
</Field>
<Modal.ButtonRow>
<ClipboardButton icon="copy" variant="primary" getText={this.getIframeHtml}>
<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…
Cancel
Save