import { dateTime, UrlQueryMap } from '@grafana/data'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { config } from '@grafana/runtime'; import { SceneComponentProps, sceneGraph, SceneObjectBase, SceneObjectRef, VizPanel } from '@grafana/scenes'; import { TimeZone } from '@grafana/schema'; import { Alert, ClipboardButton, Field, FieldSet, Icon, Input, Switch } from '@grafana/ui'; import { t, Trans } from 'app/core/internationalization'; import { createDashboardShareUrl, createShortLink, getShareUrlParams } from 'app/core/utils/shortLinks'; import { ThemePicker } from 'app/features/dashboard/components/ShareModal/ThemePicker'; import { getTrackingSource, shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils'; import { getDashboardUrl } from '../utils/getDashboardUrl'; import { DashboardInteractions } from '../utils/interactions'; import { getDashboardSceneFor } from '../utils/utils'; import { SceneShareTabState, ShareView } from './types'; export interface ShareLinkTabState extends SceneShareTabState, ShareOptions { panelRef?: SceneObjectRef; } export interface ShareLinkConfiguration { useLockedTime: boolean; useShortUrl: boolean; selectedTheme: string; } interface ShareOptions extends ShareLinkConfiguration { shareUrl: string; imageUrl: string; isBuildUrlLoading: boolean; } export class ShareLinkTab extends SceneObjectBase implements ShareView { public tabId = shareDashboardType.link; static Component = ShareLinkTabRenderer; constructor(state: Partial) { super({ ...state, useLockedTime: state.useLockedTime ?? true, useShortUrl: state.useShortUrl ?? false, selectedTheme: state.selectedTheme ?? 'current', shareUrl: '', imageUrl: '', isBuildUrlLoading: false, }); this.addActivationHandler(() => { this.buildUrl(); }); this.onToggleLockedTime = this.onToggleLockedTime.bind(this); this.onUrlShorten = this.onUrlShorten.bind(this); this.onThemeChange = this.onThemeChange.bind(this); } buildUrl = async (queryOptions?: UrlQueryMap) => { this.setState({ isBuildUrlLoading: true }); const { panelRef, useLockedTime: useAbsoluteTimeRange, useShortUrl, selectedTheme } = this.state; const dashboard = getDashboardSceneFor(this); const panel = panelRef?.resolve(); const opts = { useAbsoluteTimeRange, theme: selectedTheme, useShortUrl }; let shareUrl = createDashboardShareUrl(dashboard, opts, panel); if (useShortUrl) { shareUrl = await createShortLink(shareUrl); } const timeRange = sceneGraph.getTimeRange(panel ?? dashboard); const urlParamsUpdate = getShareUrlParams(opts, timeRange, panel); // the image panel solo route uses panelId instead of viewPanel let imageQueryParams = urlParamsUpdate; if (panel) { delete imageQueryParams.viewPanel; imageQueryParams.panelId = panel.state.key; // force solo route to use scenes imageQueryParams['__feature.dashboardSceneSolo'] = true; } const imageUrl = getDashboardUrl({ uid: dashboard.state.uid, currentQueryParams: location.search, updateQuery: { ...urlParamsUpdate, ...queryOptions, panelId: panel?.state.key }, absolute: true, soloRoute: true, render: true, timeZone: getRenderTimeZone(timeRange.getTimeZone()), }); this.setState({ shareUrl, imageUrl, isBuildUrlLoading: false }); }; public getTabLabel() { return t('share-modal.tab-title.link', 'Link'); } async onToggleLockedTime() { const useLockedTime = !this.state.useLockedTime; this.setState({ useLockedTime }); await this.buildUrl(); } async onUrlShorten() { const useShortUrl = !this.state.useShortUrl; this.setState({ useShortUrl }); await this.buildUrl(); } async onThemeChange(value: string) { this.setState({ selectedTheme: value }); await this.buildUrl(); } getShareUrl = () => { return this.state.shareUrl; }; onCopy = () => { DashboardInteractions.shareLinkCopied({ currentTimeRange: this.state.useLockedTime, theme: this.state.selectedTheme, shortenURL: this.state.useShortUrl, shareResource: getTrackingSource(this.state.panelRef), }); }; } function ShareLinkTabRenderer({ model }: SceneComponentProps) { const state = model.useState(); const { panelRef } = state; const dashboard = getDashboardSceneFor(model); const panel = panelRef?.resolve(); const timeRange = sceneGraph.getTimeRange(panel ?? dashboard); const isRelativeTime = timeRange.state.to === 'now' ? true : false; const { useLockedTime, useShortUrl, selectedTheme, shareUrl, imageUrl } = state; const selectors = e2eSelectors.pages.SharePanelModal; const isDashboardSaved = Boolean(dashboard.state.uid); const lockTimeRangeLabel = t('share-modal.link.time-range-label', `Lock time range`); const lockTimeRangeDescription = t( 'share-modal.link.time-range-description', `Transforms the current relative time range to an absolute time range` ); const shortenURLTranslation = t('share-modal.link.shorten-url', `Shorten URL`); const linkURLTranslation = t('share-modal.link.link-url', `Link URL`); return ( <>

Create a direct link to this dashboard or panel, customized with the options below.

Copy } />
{panel && config.rendererAvailable && ( <> {isDashboardSaved && (
  Direct link rendered image
)} {!isDashboardSaved && ( To render a panel image, you must save the dashboard first. )} )} {panel && !config.rendererAvailable && ( To render a panel image, you must install the{' '} Grafana image renderer plugin . Please contact your Grafana administrator to install the plugin. )} ); } function getRenderTimeZone(timeZone: TimeZone): string { const utcOffset = 'UTC' + encodeURIComponent(dateTime().format('Z')); if (timeZone === 'utc') { return 'UTC'; } if (timeZone === 'browser') { if (!window.Intl) { return utcOffset; } const dateFormat = window.Intl.DateTimeFormat(); const options = dateFormat.resolvedOptions(); if (!options.timeZone) { return utcOffset; } return options.timeZone; } return timeZone; }