Sharing: Export dashboard as image match user's window width (#108827)

This commit is contained in:
Nathan Marrs
2025-07-30 08:59:39 -07:00
committed by GitHub
parent ae15ba562e
commit af6ac6f1d6
4 changed files with 104 additions and 3 deletions
@@ -0,0 +1,52 @@
import { test, expect } from '@grafana/plugin-e2e';
const DASHBOARD_UID = 'ZqZnVvFZz';
test.use({
featureToggles: {
scenes: true,
newDashboardSharingComponent: true,
sharingDashboardImage: true, // Enable the export image feature
},
});
test.describe(
'Export as Image',
{
tag: ['@dashboards'],
},
() => {
test('Show renderer not available message when plugin not installed', async ({
gotoDashboardPage,
page,
selectors,
}) => {
// Navigate to a dashboard
const dashboardPage = await gotoDashboardPage({
uid: DASHBOARD_UID,
});
// Open the export dropdown
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.DashNav.NewExportButton.arrowMenu).click();
// Click export as image option
await dashboardPage
.getByGrafanaSelector(selectors.pages.Dashboard.DashNav.NewExportButton.Menu.exportAsImage)
.click();
// Verify we're on the export image view
await expect(page).toHaveURL(/.*shareView=image/);
// Verify the "renderer not available" alert is displayed
const rendererAlert = page.getByRole('status');
await expect(rendererAlert).toBeVisible();
await expect(rendererAlert).toContainText(/Image renderer plugin not installed/i);
await expect(rendererAlert).toContainText(
/To render an image, you must install the Grafana image renderer plugin/i
);
// Verify the generate button is NOT present when renderer is unavailable
await expect(page.getByRole('button', { name: /Generate image/i })).not.toBeVisible();
});
}
);
@@ -52,6 +52,7 @@ export default function ExportMenu({ dashboard }: { dashboard: DashboardScene })
menuItems.push({
shareId: shareDashboardType.image,
testId: newExportButtonSelector.exportAsImage,
icon: 'camera',
label: t('share-dashboard.menu.export-image-title', 'Export as image'),
renderCondition: Boolean(config.featureToggles.sharingDashboardImage),
@@ -96,6 +96,13 @@ describe('Dashboard Export Image Utils', () => {
const fetchMock = jest.fn().mockReturnValue(of({ ok: true, data: mockBlob }));
(getBackendSrv as jest.Mock).mockReturnValue({ fetch: fetchMock });
// Mock window.innerWidth
Object.defineProperty(window, 'innerWidth', {
writable: true,
configurable: true,
value: 1280,
});
const dashboard = {
state: {
uid: 'test-uid',
@@ -119,7 +126,7 @@ describe('Dashboard Export Image Utils', () => {
absolute: true,
updateQuery: {
height: -1,
width: 1000,
width: 1280,
scale: 2,
kiosk: true,
hideNav: true,
@@ -128,5 +135,46 @@ describe('Dashboard Export Image Utils', () => {
},
});
});
it('should fallback to config width when window.innerWidth is not available', async () => {
config.rendererAvailable = true;
config.rendererDefaultImageWidth = 1500;
const mockBlob = new Blob(['test'], { type: 'image/png' });
const fetchMock = jest.fn().mockReturnValue(of({ ok: true, data: mockBlob }));
(getBackendSrv as jest.Mock).mockReturnValue({ fetch: fetchMock });
// Ensure window.innerWidth is undefined
Object.defineProperty(window, 'innerWidth', {
writable: true,
configurable: true,
value: undefined,
});
const dashboard = {
state: {
uid: 'test-uid',
},
} as DashboardScene;
const result = await generateDashboardImage({ dashboard, scale: 1 });
expect(result.error).toBeUndefined();
expect(result.blob).toBe(mockBlob);
expect(getDashboardUrl).toHaveBeenCalledWith({
uid: 'test-uid',
currentQueryParams: '',
render: true,
absolute: true,
updateQuery: {
height: -1,
width: 1500, // Should use config value
scale: 1,
kiosk: true,
hideNav: true,
orgId: '1',
fullPageImage: true,
},
});
});
});
});
@@ -28,7 +28,7 @@ export interface ImageGenerationResult {
*/
export async function generateDashboardImage({
dashboard,
scale = config.rendererDefaultImageScale || 1,
scale = config.rendererDefaultImageScale || 2,
}: ImageGenerationOptions): Promise<ImageGenerationResult> {
try {
// Check if renderer plugin is available
@@ -46,7 +46,7 @@ export async function generateDashboardImage({
absolute: true,
updateQuery: {
height: -1, // image renderer will scroll through the dashboard and set the appropriate height
width: config.rendererDefaultImageWidth || 1000,
width: window.innerWidth || config.rendererDefaultImageWidth || 1000,
scale,
kiosk: true,
hideNav: true,