diff --git a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx index 24a0314c676..3a9cea22c9f 100644 --- a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx +++ b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx @@ -1,15 +1,15 @@ import { + getTimeZone, InterpolateFunction, LinkModel, PanelMenuItem, PanelPlugin, PluginExtensionPanelContext, PluginExtensionPoints, - getTimeZone, urlUtil, } from '@grafana/data'; import { config, getPluginLinkExtensions, locationService } from '@grafana/runtime'; -import { LocalValueVariable, SceneGridRow, VizPanel, VizPanelMenu, sceneGraph } from '@grafana/scenes'; +import { LocalValueVariable, sceneGraph, SceneGridRow, VizPanel, VizPanelMenu } from '@grafana/scenes'; import { DataQuery, OptionsWithLegend } from '@grafana/schema'; import appEvents from 'app/core/app_events'; import { t } from 'app/core/internationalization'; @@ -21,7 +21,9 @@ import { createExtensionSubMenu } from 'app/features/plugins/extensions/utils'; import { addDataTrailPanelAction } from 'app/features/trails/Integrations/dashboardIntegration'; import { ShowConfirmModalEvent } from 'app/types/events'; +import { ShareDrawer } from '../sharing/ShareDrawer/ShareDrawer'; import { ShareModal } from '../sharing/ShareModal'; +import { SharePanelInternally } from '../sharing/panel-share/SharePanelInternally'; import { DashboardInteractions } from '../utils/interactions'; import { getEditPanelUrl, getInspectUrl, getViewPanelUrl, tryGetExploreUrlForPanel } from '../utils/urlBuilders'; import { getDashboardSceneFor, getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils'; @@ -79,15 +81,42 @@ export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) { }); } - items.push({ - text: t('panel.header-menu.share', `Share`), - iconClassName: 'share-alt', - onClick: () => { - DashboardInteractions.panelMenuItemClicked('share'); - dashboard.showModal(new ShareModal({ panelRef: panel.getRef() })); - }, - shortcut: 'p s', - }); + if (config.featureToggles.newDashboardSharingComponent) { + const subMenu: PanelMenuItem[] = []; + subMenu.push({ + text: t('share-panel.menu.share-link-title', 'Share link'), + iconClassName: 'link', + shortcut: 'p u', + onClick: () => { + const drawer = new ShareDrawer({ + title: t('share-panel.drawer.share-link-title', 'Link settings'), + body: new SharePanelInternally({ panelRef: panel.getRef() }), + }); + + dashboard.showModal(drawer); + }, + }); + + items.push({ + type: 'submenu', + text: t('panel.header-menu.share', 'Share'), + iconClassName: 'share-alt', + subMenu, + onClick: (e) => { + e.preventDefault(); + }, + }); + } else { + items.push({ + text: t('panel.header-menu.share', 'Share'), + iconClassName: 'share-alt', + onClick: () => { + DashboardInteractions.panelMenuItemClicked('share'); + dashboard.showModal(new ShareModal({ panelRef: panel.getRef() })); + }, + shortcut: 'p s', + }); + } if (dashboard.state.isEditing && !isRepeat && !isEditingPanel) { moreSubMenu.push({ diff --git a/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts b/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts index 45be3d96f33..eac3fb92a80 100644 --- a/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts +++ b/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts @@ -1,10 +1,13 @@ import { SetPanelAttentionEvent } from '@grafana/data'; -import { locationService } from '@grafana/runtime'; +import { config, locationService } from '@grafana/runtime'; import { sceneGraph, VizPanel } from '@grafana/scenes'; import appEvents from 'app/core/app_events'; +import { t } from 'app/core/internationalization'; import { KeybindingSet } from 'app/core/services/KeybindingSet'; +import { ShareDrawer } from '../sharing/ShareDrawer/ShareDrawer'; import { ShareModal } from '../sharing/ShareModal'; +import { SharePanelInternally } from '../sharing/panel-share/SharePanelInternally'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; import { getEditPanelUrl, getInspectUrl, getViewPanelUrl, tryGetExploreUrlForPanel } from '../utils/urlBuilders'; import { getPanelIdForVizPanel } from '../utils/utils'; @@ -45,12 +48,26 @@ export function setupKeyboardShortcuts(scene: DashboardScene) { }); // Panel share - keybindings.addBinding({ - key: 'p s', - onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => { - scene.showModal(new ShareModal({ panelRef: vizPanel.getRef() })); - }), - }); + if (config.featureToggles.newDashboardSharingComponent) { + keybindings.addBinding({ + key: 'p u', + onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => { + const drawer = new ShareDrawer({ + title: t('share-panel.drawer.share-link-title', 'Link settings'), + body: new SharePanelInternally({ panelRef: vizPanel.getRef() }), + }); + + scene.showModal(drawer); + }), + }); + } else { + keybindings.addBinding({ + key: 'p s', + onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => { + scene.showModal(new ShareModal({ panelRef: vizPanel.getRef() })); + }), + }); + } // Panel inspect keybindings.addBinding({ diff --git a/public/app/features/dashboard-scene/sharing/ShareButton/share-internally/ShareInternally.tsx b/public/app/features/dashboard-scene/sharing/ShareButton/share-internally/ShareInternally.tsx index 628766afcad..031f0a5ad83 100644 --- a/public/app/features/dashboard-scene/sharing/ShareButton/share-internally/ShareInternally.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareButton/share-internally/ShareInternally.tsx @@ -2,12 +2,12 @@ import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; import { SceneComponentProps, SceneObjectRef, VizPanel } from '@grafana/scenes'; -import { Alert, ClipboardButton, Divider, Label, Spinner, Stack, Switch, Text, useStyles2 } from '@grafana/ui'; +import { Alert, ClipboardButton, Divider, Text, useStyles2 } from '@grafana/ui'; import { t, Trans } from 'app/core/internationalization'; -import { ThemePicker } from 'app/features/dashboard/components/ShareModal/ThemePicker'; +import ShareInternallyConfiguration from '../../ShareInternallyConfiguration'; import { ShareLinkTab } from '../../ShareLinkTab'; -import { getShareLinkConfiguration } from '../utils'; +import { getShareLinkConfiguration, updateShareLinkConfiguration } from '../utils'; export class ShareInternally extends ShareLinkTab { static Component = ShareInternallyRenderer; @@ -20,6 +20,39 @@ export class ShareInternally extends ShareLinkTab { useShortUrl, selectedTheme: theme, }); + + this.onToggleLockedTime = this.onToggleLockedTime.bind(this); + this.onUrlShorten = this.onUrlShorten.bind(this); + this.onThemeChange = this.onThemeChange.bind(this); + } + + async onToggleLockedTime() { + const useLockedTime = !this.state.useLockedTime; + updateShareLinkConfiguration({ + useAbsoluteTimeRange: useLockedTime, + useShortUrl: this.state.useShortUrl, + theme: this.state.selectedTheme, + }); + await super.onToggleLockedTime(); + } + + async onUrlShorten() { + const useShortUrl = !this.state.useShortUrl; + updateShareLinkConfiguration({ + useShortUrl, + useAbsoluteTimeRange: this.state.useLockedTime, + theme: this.state.selectedTheme, + }); + await super.onUrlShorten(); + } + + async onThemeChange(value: string) { + updateShareLinkConfiguration({ + theme: value, + useShortUrl: this.state.useShortUrl, + useAbsoluteTimeRange: this.state.useLockedTime, + }); + await super.onThemeChange(value); } } @@ -42,41 +75,15 @@ function ShareInternallyRenderer({ model }: SceneComponentProps - - - - - - - - - - - - - - - {isBuildUrlLoading && } - + void; + useShortUrl: boolean; + onUrlShorten: () => void; + selectedTheme: string; + onChangeTheme: (v: string) => void; + isLoading: boolean; +} + +export default function ShareInternallyConfiguration({ + useLockedTime, + onToggleLockedTime, + useShortUrl, + onUrlShorten, + onChangeTheme, + selectedTheme, + isLoading, +}: Props) { + return ( + + + + + + + + + + + + + + + {isLoading && } + + ); +} diff --git a/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx b/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx index 7a04d62a09a..65461f1d3bf 100644 --- a/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx @@ -13,7 +13,6 @@ import { DashboardInteractions } from '../utils/interactions'; import { getDashboardUrl } from '../utils/urlBuilders'; import { getDashboardSceneFor } from '../utils/utils'; -import { updateShareLinkConfiguration } from './ShareButton/utils'; import { SceneShareTabState } from './types'; export interface ShareLinkTabState extends SceneShareTabState, ShareOptions { panelRef?: SceneObjectRef; @@ -50,6 +49,10 @@ export class ShareLinkTab extends SceneObjectBase { this.addActivationHandler(() => { this.buildUrl(); }); + + this.onToggleLockedTime = this.onToggleLockedTime.bind(this); + this.onUrlShorten = this.onUrlShorten.bind(this); + this.onThemeChange = this.onThemeChange.bind(this); } async buildUrl() { @@ -94,37 +97,22 @@ export class ShareLinkTab extends SceneObjectBase { return t('share-modal.tab-title.link', 'Link'); } - onToggleLockedTime = async () => { + async onToggleLockedTime() { const useLockedTime = !this.state.useLockedTime; - updateShareLinkConfiguration({ - useAbsoluteTimeRange: useLockedTime, - useShortUrl: this.state.useShortUrl, - theme: this.state.selectedTheme, - }); this.setState({ useLockedTime }); await this.buildUrl(); - }; + } - onUrlShorten = async () => { + async onUrlShorten() { const useShortUrl = !this.state.useShortUrl; this.setState({ useShortUrl }); - updateShareLinkConfiguration({ - useShortUrl, - useAbsoluteTimeRange: this.state.useLockedTime, - theme: this.state.selectedTheme, - }); await this.buildUrl(); - }; + } - onThemeChange = async (value: string) => { + async onThemeChange(value: string) { this.setState({ selectedTheme: value }); - updateShareLinkConfiguration({ - theme: value, - useShortUrl: this.state.useShortUrl, - useAbsoluteTimeRange: this.state.useLockedTime, - }); await this.buildUrl(); - }; + } getShareUrl = () => { return this.state.shareUrl; diff --git a/public/app/features/dashboard-scene/sharing/panel-share/SharePanelInternally.tsx b/public/app/features/dashboard-scene/sharing/panel-share/SharePanelInternally.tsx new file mode 100644 index 00000000000..040625c68b2 --- /dev/null +++ b/public/app/features/dashboard-scene/sharing/panel-share/SharePanelInternally.tsx @@ -0,0 +1,113 @@ +import { css } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { SceneComponentProps, SceneObjectRef, VizPanel } from '@grafana/scenes'; +import { Alert, ClipboardButton, Divider, LinkButton, Stack, Text, useStyles2 } from '@grafana/ui'; +import { t, Trans } from 'app/core/internationalization'; + +import { getDashboardSceneFor } from '../../utils/utils'; +import ShareInternallyConfiguration from '../ShareInternallyConfiguration'; +import { ShareLinkTab } from '../ShareLinkTab'; + +export class SharePanelInternally extends ShareLinkTab { + static Component = SharePanelInternallyRenderer; + + constructor({ panelRef }: { panelRef?: SceneObjectRef }) { + super({ + panelRef, + }); + } +} + +function SharePanelInternallyRenderer({ model }: SceneComponentProps) { + const styles = useStyles2(getStyles); + const { useLockedTime, useShortUrl, selectedTheme, isBuildUrlLoading, imageUrl } = model.useState(); + const dashboard = getDashboardSceneFor(model); + const isDashboardSaved = Boolean(dashboard.state.uid); + + return ( + <> +
+ + + Create a personalized, direct link to share your panel within your organization, with the following + customization settings: + + +
+ model.onToggleLockedTime()} + useShortUrl={useShortUrl} + onUrlShorten={() => model.onUrlShorten()} + selectedTheme={selectedTheme} + onChangeTheme={(t) => model.onThemeChange(t)} + isLoading={isBuildUrlLoading} + /> + + +
+ + + Copy link + + + Render image + + +
+ {!isDashboardSaved && ( + + + To render a panel image, you must save the dashboard first. + + + )} + {!config.rendererAvailable && ( + + + To render a panel image, you must install the{' '} + + Grafana image renderer plugin + + . Please contact your Grafana administrator to install the plugin. + + + )} +
+ + ); +} + +const getStyles = (theme: GrafanaTheme2) => ({ + configDescription: css({ + marginBottom: theme.spacing(2), + }), + buttonsContainer: css({ + marginTop: theme.spacing(2), + }), +}); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 8e8afab441c..236d6c3b367 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -953,6 +953,10 @@ "short-url-label": "Shorten link", "time-range-description": "Change the current relative time range to an absolute time range", "time-range-label": "Lock time range" + }, + "share-panel": { + "config-description": "Create a personalized, direct link to share your panel within your organization, with the following customization settings:", + "render-image": "Render image" } }, "login": { @@ -2009,6 +2013,14 @@ "copy-button": "Copy to Clipboard" } }, + "share-panel": { + "drawer": { + "share-link-title": "Link settings" + }, + "menu": { + "share-link-title": "Share link" + } + }, "share-playlist": { "checkbox-description": "Panel heights will be adjusted to fit screen size", "checkbox-label": "Autofit", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 66924c0e8e3..cc4fca17083 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -953,6 +953,10 @@ "short-url-label": "Ŝĥőřŧęʼn ľįʼnĸ", "time-range-description": "Cĥäʼnģę ŧĥę čūřřęʼnŧ řęľäŧįvę ŧįmę řäʼnģę ŧő äʼn äþşőľūŧę ŧįmę řäʼnģę", "time-range-label": "Ŀőčĸ ŧįmę řäʼnģę" + }, + "share-panel": { + "config-description": "Cřęäŧę ä pęřşőʼnäľįžęđ, đįřęčŧ ľįʼnĸ ŧő şĥäřę yőūř päʼnęľ ŵįŧĥįʼn yőūř őřģäʼnįžäŧįőʼn, ŵįŧĥ ŧĥę ƒőľľőŵįʼnģ čūşŧőmįžäŧįőʼn şęŧŧįʼnģş:", + "render-image": "Ŗęʼnđęř įmäģę" } }, "login": { @@ -2009,6 +2013,14 @@ "copy-button": "Cőpy ŧő Cľįpþőäřđ" } }, + "share-panel": { + "drawer": { + "share-link-title": "Ŀįʼnĸ şęŧŧįʼnģş" + }, + "menu": { + "share-link-title": "Ŝĥäřę ľįʼnĸ" + } + }, "share-playlist": { "checkbox-description": "Päʼnęľ ĥęįģĥŧş ŵįľľ þę äđĵūşŧęđ ŧő ƒįŧ şčřęęʼn şįžę", "checkbox-label": "Åūŧőƒįŧ",