diff --git a/public/app/features/dashboard-scene/sharing/panel-share/SharePanelInternally.test.tsx b/public/app/features/dashboard-scene/sharing/panel-share/SharePanelInternally.test.tsx index a02ed3e5a1d..8f26a824e19 100644 --- a/public/app/features/dashboard-scene/sharing/panel-share/SharePanelInternally.test.tsx +++ b/public/app/features/dashboard-scene/sharing/panel-share/SharePanelInternally.test.tsx @@ -30,6 +30,7 @@ describe('SharePanelInternally', () => { expect(screen.getByPlaceholderText('1')).toBeDisabled(); // Scale factor input expect(screen.getByRole('button', { name: /generate image/i })).toBeDisabled(); expect(screen.getByRole('button', { name: /download image/i })).toBeDisabled(); + expect(screen.getByRole('button', { name: /copy image link/i })).toBeDisabled(); }); it('should enable all image generation inputs when renderer is available', async () => { @@ -55,6 +56,19 @@ describe('SharePanelInternally', () => { expect(screen.getByRole('button', { name: /generate image/i })).toBeEnabled(); expect(screen.getByRole('button', { name: /download image/i })).toBeDisabled(); + expect(screen.getByRole('button', { name: /copy image link/i })).toBeEnabled(); + }); + + it('should copy image link when copy image link button is clicked', async () => { + document.execCommand = jest.fn(); + + config.rendererAvailable = true; + buildAndRenderScenario(); + + const copyImageLinkButton = screen.getByRole('button', { name: /copy image link/i }); + await userEvent.click(copyImageLinkButton); + + expect(document.execCommand).toHaveBeenCalledWith('copy'); }); }); diff --git a/public/app/features/dashboard-scene/sharing/panel-share/SharePanelPreview.tsx b/public/app/features/dashboard-scene/sharing/panel-share/SharePanelPreview.tsx index 57ea1bde7af..2fdc7bb89b2 100644 --- a/public/app/features/dashboard-scene/sharing/panel-share/SharePanelPreview.tsx +++ b/public/app/features/dashboard-scene/sharing/panel-share/SharePanelPreview.tsx @@ -8,7 +8,7 @@ import { lastValueFrom } from 'rxjs'; import { GrafanaTheme2, UrlQueryMap } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { config, getBackendSrv } from '@grafana/runtime'; -import { Button, Field, FieldSet, Icon, Input, Stack, Text, Tooltip, useStyles2 } from '@grafana/ui'; +import { Button, ClipboardButton, Field, FieldSet, Icon, Input, Stack, Text, Tooltip, useStyles2 } from '@grafana/ui'; import { DashboardInteractions } from '../../utils/interactions'; import { ImagePreview } from '../components/ImagePreview'; @@ -196,6 +196,16 @@ export function SharePanelPreview({ title, imageUrl, buildUrl, disabled, theme } > Download image + imageUrl} + onClipboardCopy={() => DashboardInteractions.copyImageUrlClicked({ shareResource: 'panel' })} + > + Copy image link + {disabled && ( diff --git a/public/app/features/dashboard-scene/utils/interactions.ts b/public/app/features/dashboard-scene/utils/interactions.ts index 9990c4e197e..67615b083d1 100644 --- a/public/app/features/dashboard-scene/utils/interactions.ts +++ b/public/app/features/dashboard-scene/utils/interactions.ts @@ -187,6 +187,9 @@ export const DashboardInteractions = { downloadDashboardImageClicked: (properties?: Record) => { reportDashboardInteraction('dashboard_image_downloaded', properties); }, + copyImageUrlClicked: (properties?: Record) => { + reportDashboardInteraction('dashboard_image_url_copied', properties); + }, }; const reportDashboardInteraction: typeof reportInteraction = (name, properties) => { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 95d771db840..c9dfbd95d55 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -9347,6 +9347,7 @@ }, "share-panel": { "config-description": "Create a personalized, direct link to share your panel within your organization, with the following customization settings:", + "copy-image-link": "Copy image link", "download-image": "Download image", "render-image": "Generate image" }