mirror of
https://github.com/grafana/grafana.git
synced 2025-02-09 23:16:16 -06:00
Panel: Embed URL is now correctly generated for a panel in the home dashboard (#44706)
* user essentials mob! 🔱 * user essentials mob! 🔱 * user essentials mob! 🔱 * user essentials mob! 🔱 * user essentials mob! 🔱 * user essentials mob! 🔱 * user essentials mob! 🔱 * user essentials mob! 🔱 * user essentials mob! 🔱 * user essentials mob! 🔱 * user essentials mob! 🔱 * user essentials mob! 🔱 * user essentials mob! 🔱 * user essentials mob! 🔱 * user essentials mob! 🔱 * test tidyup * Add comment for route Co-authored-by: kay delaney <kay@grafana.com> Co-authored-by: Alexandra Vargas <alexa1866@gmail.com> Co-authored-by: joshhunt <josh@trtr.co> Co-authored-by: Muaaz Saleem <muaazsaleem@Muaazs-MacBook-Pro.local>
This commit is contained in:
parent
924deda589
commit
0cb3037b55
@ -0,0 +1,132 @@
|
||||
import React from 'react';
|
||||
import { ShareEmbed } from './ShareEmbed';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import config from 'app/core/config';
|
||||
import { DashboardModel, PanelModel } from '../../state';
|
||||
|
||||
jest.mock('app/features/dashboard/services/TimeSrv', () => ({
|
||||
getTimeSrv: () => ({
|
||||
timeRange: () => {
|
||||
return { from: new Date(1000), to: new Date(2000) };
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('app/core/services/context_srv', () => ({
|
||||
contextSrv: {
|
||||
sidemenu: true,
|
||||
user: {},
|
||||
isSignedIn: false,
|
||||
isGrafanaAdmin: false,
|
||||
isEditor: false,
|
||||
hasEditPermissionFolders: false,
|
||||
},
|
||||
}));
|
||||
|
||||
function mockLocationHref(href: string) {
|
||||
const location = window.location;
|
||||
|
||||
let search = '';
|
||||
const searchPos = href.indexOf('?');
|
||||
if (searchPos >= 0) {
|
||||
search = href.substring(searchPos);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
delete window.location;
|
||||
(window as any).location = {
|
||||
...location,
|
||||
href,
|
||||
origin: new URL(href).origin,
|
||||
search,
|
||||
};
|
||||
}
|
||||
|
||||
describe('ShareEmbed', () => {
|
||||
let originalBootData: any;
|
||||
|
||||
beforeAll(() => {
|
||||
originalBootData = config.bootData;
|
||||
config.appUrl = 'http://dashboards.grafana.com/';
|
||||
|
||||
config.bootData = {
|
||||
user: {
|
||||
orgId: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
config.bootData = originalBootData;
|
||||
});
|
||||
|
||||
it('generates the correct embed url for a dashboard', () => {
|
||||
const mockDashboard = new DashboardModel({
|
||||
uid: 'mockDashboardUid',
|
||||
});
|
||||
const mockPanel = new PanelModel({
|
||||
id: 'mockPanelId',
|
||||
});
|
||||
mockLocationHref(`http://dashboards.grafana.com/d/${mockDashboard.uid}?orgId=1`);
|
||||
render(<ShareEmbed dashboard={mockDashboard} panel={mockPanel} />);
|
||||
|
||||
const embedUrl = screen.getByTestId('share-embed-html');
|
||||
expect(embedUrl).toBeInTheDocument();
|
||||
expect(embedUrl).toHaveTextContent(
|
||||
`http://dashboards.grafana.com/d-solo/${mockDashboard.uid}?orgId=1&from=1000&to=2000&panelId=${mockPanel.id}`
|
||||
);
|
||||
});
|
||||
|
||||
it('generates the correct embed url for a dashboard set to the homepage in the grafana config', () => {
|
||||
mockLocationHref('http://dashboards.grafana.com/?orgId=1');
|
||||
const mockDashboard = new DashboardModel({
|
||||
uid: 'mockDashboardUid',
|
||||
});
|
||||
const mockPanel = new PanelModel({
|
||||
id: 'mockPanelId',
|
||||
});
|
||||
render(<ShareEmbed dashboard={mockDashboard} panel={mockPanel} />);
|
||||
|
||||
const embedUrl = screen.getByTestId('share-embed-html');
|
||||
expect(embedUrl).toBeInTheDocument();
|
||||
expect(embedUrl).toHaveTextContent(
|
||||
`http://dashboards.grafana.com/d-solo/${mockDashboard.uid}?orgId=1&from=1000&to=2000&panelId=${mockPanel.id}`
|
||||
);
|
||||
});
|
||||
|
||||
it('generates the correct embed url for a snapshot', () => {
|
||||
const mockSlug = 'mockSlug';
|
||||
mockLocationHref(`http://dashboards.grafana.com/dashboard/snapshot/${mockSlug}?orgId=1`);
|
||||
const mockDashboard = new DashboardModel({
|
||||
uid: 'mockDashboardUid',
|
||||
});
|
||||
const mockPanel = new PanelModel({
|
||||
id: 'mockPanelId',
|
||||
});
|
||||
render(<ShareEmbed dashboard={mockDashboard} panel={mockPanel} />);
|
||||
|
||||
const embedUrl = screen.getByTestId('share-embed-html');
|
||||
expect(embedUrl).toBeInTheDocument();
|
||||
expect(embedUrl).toHaveTextContent(
|
||||
`http://dashboards.grafana.com/dashboard-solo/snapshot/${mockSlug}?orgId=1&from=1000&to=2000&panelId=${mockPanel.id}`
|
||||
);
|
||||
});
|
||||
|
||||
it('generates the correct embed url for a scripted dashboard', () => {
|
||||
const mockSlug = 'scripted.js';
|
||||
mockLocationHref(`http://dashboards.grafana.com/dashboard/script/${mockSlug}?orgId=1`);
|
||||
const mockDashboard = new DashboardModel({
|
||||
uid: 'mockDashboardUid',
|
||||
});
|
||||
const mockPanel = new PanelModel({
|
||||
id: 'mockPanelId',
|
||||
});
|
||||
render(<ShareEmbed dashboard={mockDashboard} panel={mockPanel} />);
|
||||
|
||||
const embedUrl = screen.getByTestId('share-embed-html');
|
||||
expect(embedUrl).toBeInTheDocument();
|
||||
expect(embedUrl).toHaveTextContent(
|
||||
`http://dashboards.grafana.com/dashboard-solo/script/${mockSlug}?orgId=1&from=1000&to=2000&panelId=${mockPanel.id}`
|
||||
);
|
||||
});
|
||||
});
|
@ -34,10 +34,10 @@ export class ShareEmbed extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
buildIframeHtml = () => {
|
||||
const { panel } = this.props;
|
||||
const { panel, dashboard } = this.props;
|
||||
const { useCurrentTimeRange, selectedTheme } = this.state;
|
||||
|
||||
const iframeHtml = buildIframeHtml(useCurrentTimeRange, selectedTheme, panel);
|
||||
const iframeHtml = buildIframeHtml(useCurrentTimeRange, dashboard.uid, selectedTheme, panel);
|
||||
this.setState({ iframeHtml });
|
||||
};
|
||||
|
||||
@ -92,6 +92,7 @@ export class ShareEmbed extends PureComponent<Props, State> {
|
||||
the user viewing that page need to be signed into Grafana for the graph to load."
|
||||
>
|
||||
<TextArea
|
||||
data-testid="share-embed-html"
|
||||
id="share-panel-embed-embed-html-textarea"
|
||||
rows={5}
|
||||
value={iframeHtml}
|
||||
|
@ -6,7 +6,7 @@ import { Props, ShareLink, State } from './ShareLink';
|
||||
import { initTemplateSrv } from '../../../../../test/helpers/initTemplateSrv';
|
||||
import { variableAdapters } from '../../../variables/adapters';
|
||||
import { createQueryVariableAdapter } from '../../../variables/query/adapter';
|
||||
import { PanelModel } from '../../state';
|
||||
import { DashboardModel, PanelModel } from '../../state';
|
||||
import { getDefaultTimeRange } from '@grafana/data';
|
||||
|
||||
jest.mock('app/features/dashboard/services/TimeSrv', () => ({
|
||||
@ -31,6 +31,7 @@ function mockLocationHref(href: string) {
|
||||
(window as any).location = {
|
||||
...location,
|
||||
href,
|
||||
origin: new URL(href).origin,
|
||||
search,
|
||||
};
|
||||
}
|
||||
@ -189,3 +190,39 @@ describe('ShareModal', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when default_home_dashboard_path is set in the grafana config', () => {
|
||||
let originalBootData: any;
|
||||
|
||||
beforeAll(() => {
|
||||
originalBootData = config.bootData;
|
||||
config.appUrl = 'http://dashboards.grafana.com/';
|
||||
|
||||
config.bootData = {
|
||||
user: {
|
||||
orgId: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
config.bootData = originalBootData;
|
||||
});
|
||||
|
||||
it('should render the correct link', async () => {
|
||||
const mockDashboard = new DashboardModel({
|
||||
uid: 'mockDashboardUid',
|
||||
});
|
||||
const mockPanel = new PanelModel({
|
||||
id: 'mockPanelId',
|
||||
});
|
||||
mockLocationHref('http://dashboards.grafana.com/?orgId=1');
|
||||
const test: ShallowWrapper<Props, State, ShareLink> = shallow(
|
||||
<ShareLink dashboard={mockDashboard} panel={mockPanel} />
|
||||
);
|
||||
await test.instance().buildUrl();
|
||||
expect(test.state().imageUrl).toBe(
|
||||
`http://dashboards.grafana.com/render/d-solo/${mockDashboard.uid}?orgId=1&from=1000&to=2000&panelId=${mockPanel.id}&width=1000&height=500&tz=UTC`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -51,11 +51,11 @@ export class ShareLink extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
buildUrl = async () => {
|
||||
const { panel } = this.props;
|
||||
const { panel, dashboard } = this.props;
|
||||
const { useCurrentTimeRange, useShortUrl, selectedTheme } = this.state;
|
||||
|
||||
const shareUrl = await buildShareUrl(useCurrentTimeRange, selectedTheme, panel, useShortUrl);
|
||||
const imageUrl = buildImageUrl(useCurrentTimeRange, selectedTheme, panel);
|
||||
const imageUrl = buildImageUrl(useCurrentTimeRange, dashboard.uid, selectedTheme, panel);
|
||||
|
||||
this.setState({ shareUrl, imageUrl });
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { config } from '@grafana/runtime';
|
||||
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
import { createShortLink } from 'app/core/utils/shortLinks';
|
||||
import { dateTime, PanelModel, TimeRange, urlUtil } from '@grafana/data';
|
||||
import { dateTime, locationUtil, PanelModel, TimeRange, urlUtil } from '@grafana/data';
|
||||
|
||||
export interface BuildParamsArgs {
|
||||
useCurrentTimeRange: boolean;
|
||||
@ -68,13 +68,24 @@ export async function buildShareUrl(
|
||||
return shareUrl;
|
||||
}
|
||||
|
||||
export function buildSoloUrl(useCurrentTimeRange: boolean, selectedTheme?: string, panel?: PanelModel) {
|
||||
export function buildSoloUrl(
|
||||
useCurrentTimeRange: boolean,
|
||||
dashboardUid: string,
|
||||
selectedTheme?: string,
|
||||
panel?: PanelModel
|
||||
) {
|
||||
const baseUrl = buildBaseUrl();
|
||||
const params = buildParams({ useCurrentTimeRange, selectedTheme, panel });
|
||||
|
||||
let soloUrl = baseUrl.replace(config.appSubUrl + '/dashboard/', config.appSubUrl + '/dashboard-solo/');
|
||||
soloUrl = soloUrl.replace(config.appSubUrl + '/d/', config.appSubUrl + '/d-solo/');
|
||||
|
||||
// For handling the case when default_home_dashboard_path is set in the grafana config
|
||||
const strippedUrl = locationUtil.stripBaseFromUrl(baseUrl);
|
||||
if (strippedUrl === '/') {
|
||||
soloUrl = `${config.appUrl}d-solo/${dashboardUid}`;
|
||||
}
|
||||
|
||||
const panelId = params.get('editPanel') ?? params.get('viewPanel') ?? '';
|
||||
params.set('panelId', panelId);
|
||||
params.delete('editPanel');
|
||||
@ -83,17 +94,26 @@ export function buildSoloUrl(useCurrentTimeRange: boolean, selectedTheme?: strin
|
||||
return urlUtil.appendQueryToUrl(soloUrl, params.toString());
|
||||
}
|
||||
|
||||
export function buildImageUrl(useCurrentTimeRange: boolean, selectedTheme?: string, panel?: PanelModel) {
|
||||
let soloUrl = buildSoloUrl(useCurrentTimeRange, selectedTheme, panel);
|
||||
|
||||
export function buildImageUrl(
|
||||
useCurrentTimeRange: boolean,
|
||||
dashboardUid: string,
|
||||
selectedTheme?: string,
|
||||
panel?: PanelModel
|
||||
) {
|
||||
let soloUrl = buildSoloUrl(useCurrentTimeRange, dashboardUid, selectedTheme, panel);
|
||||
let imageUrl = soloUrl.replace(config.appSubUrl + '/dashboard-solo/', config.appSubUrl + '/render/dashboard-solo/');
|
||||
imageUrl = imageUrl.replace(config.appSubUrl + '/d-solo/', config.appSubUrl + '/render/d-solo/');
|
||||
imageUrl += '&width=1000&height=500' + getLocalTimeZone();
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
export function buildIframeHtml(useCurrentTimeRange: boolean, selectedTheme?: string, panel?: PanelModel) {
|
||||
let soloUrl = buildSoloUrl(useCurrentTimeRange, selectedTheme, panel);
|
||||
export function buildIframeHtml(
|
||||
useCurrentTimeRange: boolean,
|
||||
dashboardUid: string,
|
||||
selectedTheme?: string,
|
||||
panel?: PanelModel
|
||||
) {
|
||||
let soloUrl = buildSoloUrl(useCurrentTimeRange, dashboardUid, selectedTheme, panel);
|
||||
return '<iframe src="' + soloUrl + '" width="450" height="200" frameborder="0"></iframe>';
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,15 @@ export function getAppRoutes(): RouteDescriptor[] {
|
||||
() => import(/* webpackChunkName: "SoloPanelPage" */ '../features/dashboard/containers/SoloPanelPage')
|
||||
),
|
||||
},
|
||||
// This route handles embedding of snapshot/scripted dashboard panels
|
||||
{
|
||||
path: '/dashboard-solo/:type/:slug',
|
||||
pageClass: 'dashboard-solo',
|
||||
routeName: DashboardRoutes.Normal,
|
||||
component: SafeDynamicImport(
|
||||
() => import(/* webpackChunkName: "SoloPanelPage" */ '../features/dashboard/containers/SoloPanelPage')
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/d-solo/:uid',
|
||||
pageClass: 'dashboard-solo',
|
||||
|
@ -9,6 +9,14 @@ describe('isSoloRoute', () => {
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('then it should return true for a dashboard-solo route', () => {
|
||||
expect(
|
||||
isSoloRoute(
|
||||
'http://localhost:3000/render/dashboard-solo/4vEk45n7k/dash?orgId=1&from=1629329071059&to=1629350671060&panelId=5&width=1000&height=500&tz=Europe%2FStockholm'
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when called without a solo route', () => {
|
||||
|
@ -1,3 +1,3 @@
|
||||
export function isSoloRoute(path: string): boolean {
|
||||
return path?.toLowerCase().includes('/d-solo/');
|
||||
return /(d-solo|dashboard-solo)/.test(path?.toLowerCase());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user