Schema V2: Support v2 custom home dashboards (#99748)
This commit is contained in:
@@ -154,6 +154,22 @@ describe('DashboardScenePageStateManager v1', () => {
|
||||
status: 500,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw when v2 custom home dashboard is provided', async () => {
|
||||
setBackendSrv({
|
||||
get: () => Promise.resolve({ dashboard: customHomeDashboardV2Spec, meta: {} }),
|
||||
} as unknown as BackendSrv);
|
||||
|
||||
const loader = new DashboardScenePageStateManager({});
|
||||
await loader.loadDashboard({ uid: '', route: DashboardRoutes.Home });
|
||||
|
||||
expect(loader.state.dashboard).toBeUndefined();
|
||||
expect(loader.state.loadError).toEqual({
|
||||
message: 'v2 dashboard spec is not supported. Enable useV2DashboardsAPI feature toggle',
|
||||
messageId: undefined,
|
||||
status: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('New dashboards', () => {
|
||||
@@ -423,13 +439,12 @@ describe('DashboardScenePageStateManager v2', () => {
|
||||
});
|
||||
|
||||
describe('Home dashboard', () => {
|
||||
// TODO: Unskip when redirect is implemented in v2 API
|
||||
it.skip('should handle home dashboard redirect', async () => {
|
||||
it('should handle home dashboard redirect', async () => {
|
||||
setBackendSrv({
|
||||
get: () => Promise.resolve({ redirectUri: '/d/asd' }),
|
||||
} as unknown as BackendSrv);
|
||||
|
||||
const loader = new DashboardScenePageStateManager({});
|
||||
const loader = new DashboardScenePageStateManagerV2({});
|
||||
await loader.loadDashboard({ uid: '', route: DashboardRoutes.Home });
|
||||
|
||||
expect(loader.state.dashboard).toBeUndefined();
|
||||
@@ -455,6 +470,45 @@ describe('DashboardScenePageStateManager v2', () => {
|
||||
status: 500,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not transform v2 custom home dashboard spec', async () => {
|
||||
setBackendSrv({
|
||||
get: () =>
|
||||
Promise.resolve({
|
||||
dashboard: customHomeDashboardV2Spec,
|
||||
meta: {
|
||||
canSave: false,
|
||||
canEdit: true,
|
||||
canAdmin: false,
|
||||
canStar: false,
|
||||
canDelete: false,
|
||||
slug: '',
|
||||
url: '',
|
||||
expires: '0001-01-01T00:00:00Z',
|
||||
created: '0001-01-01T00:00:00Z',
|
||||
updated: '0001-01-01T00:00:00Z',
|
||||
updatedBy: '',
|
||||
createdBy: '',
|
||||
version: 0,
|
||||
hasAcl: false,
|
||||
isFolder: false,
|
||||
folderId: 0,
|
||||
folderUid: '',
|
||||
folderTitle: 'General',
|
||||
folderUrl: '',
|
||||
provisioned: false,
|
||||
provisionedExternalId: '',
|
||||
annotationsPermissions: null,
|
||||
},
|
||||
}),
|
||||
} as unknown as BackendSrv);
|
||||
|
||||
const loader = new DashboardScenePageStateManagerV2({});
|
||||
await loader.loadDashboard({ uid: '', route: DashboardRoutes.Home });
|
||||
|
||||
expect(loader.state.dashboard?.getInitialSaveModel()).toEqual(customHomeDashboardV2Spec);
|
||||
expect(loader.state.loadError).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('New dashboards', () => {
|
||||
@@ -593,3 +647,78 @@ describe('DashboardScenePageStateManager v2', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const customHomeDashboardV2Spec = {
|
||||
title: 'Home Dashboard v2 schema',
|
||||
cursorSync: 'Off',
|
||||
preload: false,
|
||||
editable: true,
|
||||
links: [],
|
||||
tags: [],
|
||||
timeSettings: {
|
||||
timezone: 'browser',
|
||||
from: 'now-6h',
|
||||
to: 'now',
|
||||
autoRefresh: '',
|
||||
autoRefreshIntervals: ['5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d'],
|
||||
quickRanges: ['5m', '15m', '1h', '6h', '12h', '24h', '2d', '7d', '30d'],
|
||||
hideTimepicker: false,
|
||||
weekStart: '',
|
||||
fiscalYearStartMonth: 0,
|
||||
},
|
||||
variables: [],
|
||||
elements: {
|
||||
text_panel: {
|
||||
kind: 'Panel',
|
||||
spec: {
|
||||
id: 0,
|
||||
title: 'Welcome',
|
||||
description: 'Welcome to the home dashboard!',
|
||||
links: [],
|
||||
data: {
|
||||
kind: 'QueryGroup',
|
||||
spec: {
|
||||
queries: [],
|
||||
transformations: [],
|
||||
queryOptions: {},
|
||||
},
|
||||
},
|
||||
vizConfig: {
|
||||
kind: 'text',
|
||||
spec: {
|
||||
pluginVersion: '',
|
||||
options: {
|
||||
mode: 'markdown',
|
||||
content: '# Welcome to the home dashboard!\n\n## Example of v2 schema home dashboard',
|
||||
},
|
||||
fieldConfig: {
|
||||
defaults: {},
|
||||
overrides: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
annotations: [],
|
||||
layout: {
|
||||
kind: 'GridLayout',
|
||||
spec: {
|
||||
items: [
|
||||
{
|
||||
kind: 'GridLayoutItem',
|
||||
spec: {
|
||||
x: 6,
|
||||
y: 0,
|
||||
width: 12,
|
||||
height: 6,
|
||||
element: {
|
||||
kind: 'ElementReference',
|
||||
name: 'text_panel',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -10,11 +10,18 @@ import { startMeasure, stopMeasure } from 'app/core/utils/metrics';
|
||||
import { AnnoKeyFolder } from 'app/features/apiserver/types';
|
||||
import { ResponseTransformers } from 'app/features/dashboard/api/ResponseTransformers';
|
||||
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
|
||||
import { isDashboardV2Spec } from 'app/features/dashboard/api/utils';
|
||||
import { dashboardLoaderSrv, DashboardLoaderSrvV2 } from 'app/features/dashboard/services/DashboardLoaderSrv';
|
||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { emitDashboardViewEvent } from 'app/features/dashboard/state/analyticsProcessor';
|
||||
import { trackDashboardSceneLoaded } from 'app/features/dashboard/utils/tracking';
|
||||
import { DashboardDTO, DashboardRoutes } from 'app/types';
|
||||
import {
|
||||
DashboardDataDTO,
|
||||
DashboardDTO,
|
||||
DashboardRoutes,
|
||||
HomeDashboardRedirectDTO,
|
||||
isRedirectResponse,
|
||||
} from 'app/types';
|
||||
|
||||
import { PanelEditor } from '../panel-edit/PanelEditor';
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
@@ -68,6 +75,10 @@ export interface LoadDashboardOptions {
|
||||
};
|
||||
}
|
||||
|
||||
export type HomeDashboardDTO = DashboardDTO & {
|
||||
dashboard: DashboardDataDTO | DashboardV2Spec;
|
||||
};
|
||||
|
||||
interface DashboardScenePageStateManagerLike<T> {
|
||||
fetchDashboard(options: LoadDashboardOptions): Promise<T | null>;
|
||||
getDashboardFromCache(cacheKey: string): T | null;
|
||||
@@ -167,6 +178,11 @@ abstract class DashboardScenePageStateManagerBase<T>
|
||||
private async loadScene(options: LoadDashboardOptions): Promise<DashboardScene | null> {
|
||||
this.setState({ dashboard: undefined, isLoading: true });
|
||||
const rsp = await this.fetchDashboard(options);
|
||||
|
||||
if (!rsp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.transformResponseToScene(rsp, options);
|
||||
}
|
||||
|
||||
@@ -235,12 +251,6 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag
|
||||
return scene;
|
||||
}
|
||||
|
||||
if (rsp?.redirectUri) {
|
||||
const newUrl = locationUtil.stripBaseFromUrl(rsp.redirectUri);
|
||||
locationService.replace(newUrl);
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new Error('Dashboard not found');
|
||||
}
|
||||
|
||||
@@ -271,7 +281,7 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag
|
||||
}
|
||||
}
|
||||
|
||||
let rsp: DashboardDTO;
|
||||
let rsp: DashboardDTO | HomeDashboardRedirectDTO;
|
||||
|
||||
try {
|
||||
switch (route) {
|
||||
@@ -280,10 +290,16 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag
|
||||
|
||||
break;
|
||||
case DashboardRoutes.Home:
|
||||
rsp = await getBackendSrv().get('/api/dashboards/home');
|
||||
rsp = await getBackendSrv().get<HomeDashboardDTO | HomeDashboardRedirectDTO>('/api/dashboards/home');
|
||||
|
||||
if (rsp.redirectUri) {
|
||||
return rsp;
|
||||
if (isRedirectResponse(rsp)) {
|
||||
const newUrl = locationUtil.stripBaseFromUrl(rsp.redirectUri);
|
||||
locationService.replace(newUrl);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isDashboardV2Spec(rsp.dashboard)) {
|
||||
throw new Error('v2 dashboard spec is not supported. Enable useV2DashboardsAPI feature toggle');
|
||||
}
|
||||
|
||||
if (rsp?.meta) {
|
||||
@@ -453,13 +469,6 @@ export class DashboardScenePageStateManagerV2 extends DashboardScenePageStateMan
|
||||
return scene;
|
||||
}
|
||||
|
||||
// TOD)[schema v2]: Figure out redirect utl
|
||||
// if (rsp?.redirectUri) {
|
||||
// const newUrl = locationUtil.stripBaseFromUrl(rsp.redirectUri);
|
||||
// locationService.replace(newUrl);
|
||||
// return null;
|
||||
// }
|
||||
|
||||
throw new Error('Dashboard not found');
|
||||
}
|
||||
|
||||
@@ -487,17 +496,25 @@ export class DashboardScenePageStateManagerV2 extends DashboardScenePageStateMan
|
||||
rsp = await buildNewDashboardSaveModelV2(urlFolderUid);
|
||||
break;
|
||||
case DashboardRoutes.Home:
|
||||
// throw new Error('Method not implemented.');
|
||||
const dto = await getBackendSrv().get<DashboardDTO>('/api/dashboards/home');
|
||||
const dto = await getBackendSrv().get<HomeDashboardDTO | HomeDashboardRedirectDTO>('/api/dashboards/home');
|
||||
|
||||
if (isRedirectResponse(dto)) {
|
||||
const newUrl = locationUtil.stripBaseFromUrl(dto.redirectUri);
|
||||
locationService.replace(newUrl);
|
||||
return null;
|
||||
}
|
||||
|
||||
rsp = ResponseTransformers.ensureV2Response(dto);
|
||||
|
||||
// if custom home dashboard is v2 spec already, ignore the spec transformation
|
||||
if (isDashboardV2Spec(dto.dashboard)) {
|
||||
rsp.spec = dto.dashboard;
|
||||
}
|
||||
|
||||
rsp.access.canSave = false;
|
||||
rsp.access.canShare = false;
|
||||
rsp.access.canStar = false;
|
||||
|
||||
// if (rsp.redirectUri) {
|
||||
// return rsp;
|
||||
// }
|
||||
|
||||
break;
|
||||
case DashboardRoutes.Public: {
|
||||
return await this.dashboardLoader.loadDashboard('public', '', uid);
|
||||
|
||||
@@ -103,13 +103,8 @@ export function ensureV2Response(
|
||||
if (isDashboardResource(dto)) {
|
||||
accessMeta = dto.access;
|
||||
annotationsMeta = {
|
||||
[AnnoKeyCreatedBy]: dto.metadata.annotations?.[AnnoKeyCreatedBy],
|
||||
[AnnoKeyUpdatedBy]: dto.metadata.annotations?.[AnnoKeyUpdatedBy],
|
||||
[AnnoKeyUpdatedTimestamp]: dto.metadata.annotations?.[AnnoKeyUpdatedTimestamp],
|
||||
[AnnoKeyFolder]: dto.metadata.annotations?.[AnnoKeyFolder],
|
||||
[AnnoKeySlug]: dto.metadata.annotations?.[AnnoKeySlug],
|
||||
...dto.metadata.annotations,
|
||||
[AnnoKeyDashboardGnetId]: dashboard.gnetId ?? undefined,
|
||||
[AnnoKeyDashboardIsSnapshot]: dto.metadata.annotations?.[AnnoKeyDashboardIsSnapshot],
|
||||
};
|
||||
creationTimestamp = dto.metadata.creationTimestamp;
|
||||
labelsMeta = {
|
||||
|
||||
@@ -24,6 +24,8 @@ import {
|
||||
DashboardDTO,
|
||||
DashboardInitPhase,
|
||||
DashboardRoutes,
|
||||
HomeDashboardRedirectDTO,
|
||||
isRedirectResponse,
|
||||
StoreState,
|
||||
ThunkDispatch,
|
||||
ThunkResult,
|
||||
@@ -69,10 +71,10 @@ async function fetchDashboard(
|
||||
}
|
||||
|
||||
// load home dash
|
||||
const dashDTO: DashboardDTO = await backendSrv.get('/api/dashboards/home');
|
||||
const dashDTO = await backendSrv.get<DashboardDTO | HomeDashboardRedirectDTO>('/api/dashboards/home');
|
||||
|
||||
// if user specified a custom home dashboard redirect to that
|
||||
if (dashDTO.redirectUri) {
|
||||
if (isRedirectResponse(dashDTO)) {
|
||||
const newUrl = locationUtil.stripBaseFromUrl(dashDTO.redirectUri);
|
||||
locationService.replace(newUrl);
|
||||
return null;
|
||||
|
||||
@@ -3,8 +3,11 @@ import { Dashboard, DataSourceRef } from '@grafana/schema';
|
||||
import { ObjectMeta } from 'app/features/apiserver/types';
|
||||
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||
|
||||
export interface HomeDashboardRedirectDTO {
|
||||
redirectUri: string;
|
||||
}
|
||||
|
||||
export interface DashboardDTO {
|
||||
redirectUri?: string;
|
||||
dashboard: DashboardDataDTO;
|
||||
meta: DashboardMeta;
|
||||
}
|
||||
@@ -140,3 +143,7 @@ export interface DashboardState {
|
||||
}
|
||||
|
||||
export const DASHBOARD_FROM_LS_KEY = 'DASHBOARD_FROM_LS_KEY';
|
||||
|
||||
export function isRedirectResponse(dto: DashboardDTO | HomeDashboardRedirectDTO): dto is HomeDashboardRedirectDTO {
|
||||
return 'redirectUri' in dto;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user