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"
}