Dashboard V2: Snapshot variables and read snapshot (#98463)

* Introduce DashboardScenePageStateManagerLike interface

* Implement dash loader for handling v2 api

* Transformation improvements

* Update response transformer test

* v2 schema: Remove defaultOptionEnabled from ds variable schema

* v2 schema: Make annotations filter optional

* WIP render dashboard from v2 schema

* Force dashbaord scene for v2 api

* V2 schema -> scene meta transformation

* v2 api: Handle home dashboard

* Use correct api client in DashboardScenePage

* Correctly use v2 dashboard scene serializer

* Remove unnecesary type assertions

* Handle v2 dashboard not found

* Fix type

* Fix test

* Some more tests fix

* snapshot

* Add dashboard id annotation

* Nits

* Nits

* Rename v2 api

* Enable snapshot variables

* Support getSnapshotUrl() for v2 serializer

* fix

* Make metadata available in the serializer

* Test

* Decouple meta info

* State Manager: Extract snapshot loading into loadSnapshot

* Fix tests

* Add test for snapshot url

* Don't expose dashboardLoaderSrvV2

* Remove TODO

* Bubble up loading snapshot error to error boundary

* Fix test

---------

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
Haris Rozajac
2025-01-09 08:14:41 -07:00
committed by GitHub
parent 78fda78a56
commit 6b03adbbb0
11 changed files with 218 additions and 90 deletions

View File

@ -49,6 +49,7 @@ export const AnnoKeyRepoTimestamp = 'grafana.app/repoTimestamp';
export const AnnoKeySavedFromUI = 'grafana.app/saved-from-ui';
export const AnnoKeyDashboardNotFound = 'grafana.app/dashboard-not-found';
export const AnnoKeyDashboardIsSnapshot = 'grafana.app/dashboard-is-snapshot';
export const AnnoKeyDashboardSnapshotOriginalUrl = 'grafana.app/dashboard-snapshot-original-url';
export const AnnoKeyDashboardIsNew = 'grafana.app/dashboard-is-new';
export const AnnoKeyDashboardGnetId = 'grafana.app/dashboard-gnet-id';
@ -77,6 +78,7 @@ type GrafanaClientAnnotations = {
[AnnoKeySavedFromUI]?: string;
[AnnoKeyDashboardNotFound]?: boolean;
[AnnoKeyDashboardIsSnapshot]?: boolean;
[AnnoKeyDashboardSnapshotOriginalUrl]?: string;
[AnnoKeyDashboardIsNew]?: boolean;
// TODO: This should be provided by the API

View File

@ -8,6 +8,7 @@ import {
import store from 'app/core/store';
import { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
import { getDashboardSnapshotSrv } from 'app/features/dashboard/services/SnapshotSrv';
import { DASHBOARD_FROM_LS_KEY, DashboardRoutes } from 'app/types';
import { DashboardScene } from '../scene/DashboardScene';
@ -26,6 +27,10 @@ jest.mock('app/features/dashboard/api/dashboard_api', () => ({
describe('DashboardScenePageStateManager v1', () => {
afterEach(() => {
store.delete(DASHBOARD_FROM_LS_KEY);
setBackendSrv({
get: jest.fn(),
} as unknown as BackendSrv);
});
describe('when fetching/loading a dashboard', () => {
@ -379,21 +384,16 @@ describe('DashboardScenePageStateManager v2', () => {
});
it('should use DashboardScene creator to initialize the snapshot scene', async () => {
const getDashSpy = jest.fn();
setupDashboardAPI(
{
access: {},
apiVersion: 'v2alpha1',
kind: 'DashboardWithAccessInfo',
metadata: {
name: 'fake-dash',
creationTimestamp: '',
resourceVersion: '1',
},
spec: { ...defaultDashboardV2Spec() },
jest.spyOn(getDashboardSnapshotSrv(), 'getSnapshot').mockResolvedValue({
// getSnapshot will return v1 dashboard
// but ResponseTransformer in DashboardLoaderSrv will convert it to v2
dashboard: {
uid: 'fake-dash',
title: 'Fake dashboard',
schemaVersion: 40,
},
getDashSpy
);
meta: { isSnapshot: true },
});
const loader = new DashboardScenePageStateManagerV2({});
await loader.loadSnapshot('fake-slug');

View File

@ -82,6 +82,7 @@ abstract class DashboardScenePageStateManagerBase<T>
abstract fetchDashboard(options: LoadDashboardOptions): Promise<T | null>;
abstract reloadDashboard(params: LoadDashboardOptions['params']): Promise<void>;
abstract transformResponseToScene(rsp: T | null, options: LoadDashboardOptions): DashboardScene | null;
abstract loadSnapshotScene(slug: string): Promise<DashboardScene>;
protected cache: Record<string, DashboardScene> = {};
@ -102,17 +103,6 @@ abstract class DashboardScenePageStateManagerBase<T>
}
}
private async loadSnapshotScene(slug: string): Promise<DashboardScene> {
const rsp = await dashboardLoaderSrv.loadDashboard('snapshot', slug, '');
if (rsp?.dashboard) {
const scene = transformSaveModelToScene(rsp);
return scene;
}
throw new Error('Snapshot not found');
}
public async loadDashboard(options: LoadDashboardOptions) {
try {
startMeasure(LOAD_SCENE_MEASUREMENT);
@ -222,6 +212,18 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag
throw new Error('Dashboard not found');
}
public async loadSnapshotScene(slug: string): Promise<DashboardScene> {
const rsp = await dashboardLoaderSrv.loadSnapshot(slug);
if (rsp?.dashboard) {
const scene = transformSaveModelToScene(rsp);
return scene;
}
throw new Error('Snapshot not found');
}
public async fetchDashboard({
uid,
route,
@ -370,6 +372,17 @@ export class DashboardScenePageStateManagerV2 extends DashboardScenePageStateMan
> {
private dashboardLoader = new DashboardLoaderSrvV2();
public async loadSnapshotScene(slug: string): Promise<DashboardScene> {
const rsp = await this.dashboardLoader.loadSnapshot(slug);
if (rsp?.spec) {
const scene = transformSaveModelSchemaV2ToScene(rsp);
return scene;
}
throw new Error('Snapshot not found');
}
transformResponseToScene(
rsp: DashboardWithAccessInfo<DashboardV2Spec> | null,
options: LoadDashboardOptions

View File

@ -31,6 +31,7 @@ import { ScrollRefElement } from 'app/core/components/NativeScrollbar';
import { LS_PANEL_COPY_KEY } from 'app/core/constants';
import { getNavModel } from 'app/core/selectors/navModel';
import store from 'app/core/store';
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
import { SaveDashboardAsOptions } from 'app/features/dashboard/components/SaveDashboard/types';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
@ -173,8 +174,10 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
private _scrollRef?: ScrollRefElement;
private _prevScrollPos?: number;
// TODO: use feature toggle to allow v2 serializer
private _serializer: DashboardSceneSerializerLike<Dashboard | DashboardV2Spec> = getDashboardSceneSerializer();
private _serializer: DashboardSceneSerializerLike<
Dashboard | DashboardV2Spec,
DashboardMeta | DashboardWithAccessInfo<DashboardV2Spec>['metadata']
> = getDashboardSceneSerializer();
public constructor(state: Partial<DashboardSceneState>) {
super({
@ -649,8 +652,14 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
};
/** Hacky temp function until we refactor transformSaveModelToScene a bit */
public setInitialSaveModel(saveModel?: Dashboard | DashboardV2Spec) {
setInitialSaveModel(model?: Dashboard, meta?: DashboardMeta): void;
setInitialSaveModel(model?: DashboardV2Spec, meta?: DashboardWithAccessInfo<DashboardV2Spec>['metadata']): void;
public setInitialSaveModel(
saveModel?: Dashboard | DashboardV2Spec,
meta?: DashboardMeta | DashboardWithAccessInfo<DashboardV2Spec>['metadata']
): void {
this._serializer.initialSaveModel = saveModel;
this._serializer.metadata = meta;
}
public getTrackingInformation() {

View File

@ -13,6 +13,7 @@ import {
defaultPanelSpec,
defaultTimeSettingsSpec,
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen';
import { AnnoKeyDashboardSnapshotOriginalUrl } from 'app/features/apiserver/types';
import { DASHBOARD_SCHEMA_VERSION } from 'app/features/dashboard/state/DashboardMigrator';
import { buildPanelEditScene } from '../panel-edit/PanelEditor';
@ -622,9 +623,18 @@ describe('DashboardSceneSerializer', () => {
).toThrow('Method not implemented.');
});
it('should throw on getSnapshotUrl', () => {
it('should allow retrieving snapshot url', () => {
const serializer = new V2DashboardSerializer();
expect(() => serializer.getSnapshotUrl()).toThrow('Method not implemented.');
serializer.metadata = {
name: 'dashboard-test',
resourceVersion: '1',
creationTimestamp: '2023-01-01T00:00:00Z',
annotations: {
[AnnoKeyDashboardSnapshotOriginalUrl]: 'originalUrl/snapshot',
},
};
expect(serializer.getSnapshotUrl()).toBe('originalUrl/snapshot');
});
});
});

View File

@ -1,13 +1,15 @@
import { config } from '@grafana/runtime';
import { Dashboard } from '@grafana/schema';
import { DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen';
import { AnnoKeyDashboardSnapshotOriginalUrl } from 'app/features/apiserver/types';
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
import { SaveDashboardAsOptions } from 'app/features/dashboard/components/SaveDashboard/types';
import {
getPanelPluginCounts,
getV1SchemaVariables,
getV2SchemaVariables,
} from 'app/features/dashboard/utils/tracking';
import { SaveDashboardResponseDTO } from 'app/types';
import { DashboardMeta, SaveDashboardResponseDTO } from 'app/types';
import { getRawDashboardChanges, getRawDashboardV2Changes } from '../saving/getDashboardChanges';
import { DashboardChangeInfo } from '../saving/shared';
@ -16,11 +18,12 @@ import { DashboardScene } from '../scene/DashboardScene';
import { transformSceneToSaveModel } from './transformSceneToSaveModel';
import { transformSceneToSaveModelSchemaV2 } from './transformSceneToSaveModelSchemaV2';
export interface DashboardSceneSerializerLike<T> {
export interface DashboardSceneSerializerLike<T, M> {
/**
* The save model which the dashboard scene was originally created from
*/
initialSaveModel?: T;
metadata?: M;
getSaveModel: (s: DashboardScene) => T;
getSaveAsModel: (s: DashboardScene, options: SaveDashboardAsOptions) => T;
getDashboardChangesFromScene: (
@ -45,8 +48,9 @@ interface DashboardTrackingInfo {
settings_livenow?: boolean;
}
export class V1DashboardSerializer implements DashboardSceneSerializerLike<Dashboard> {
export class V1DashboardSerializer implements DashboardSceneSerializerLike<Dashboard, DashboardMeta> {
initialSaveModel?: Dashboard;
metadata?: DashboardMeta;
getSaveModel(s: DashboardScene) {
return transformSceneToSaveModel(s);
@ -121,8 +125,11 @@ export class V1DashboardSerializer implements DashboardSceneSerializerLike<Dashb
}
}
export class V2DashboardSerializer implements DashboardSceneSerializerLike<DashboardV2Spec> {
export class V2DashboardSerializer
implements DashboardSceneSerializerLike<DashboardV2Spec, DashboardWithAccessInfo<DashboardV2Spec>['metadata']>
{
initialSaveModel?: DashboardV2Spec;
metadata?: DashboardWithAccessInfo<DashboardV2Spec>['metadata'];
getSaveModel(s: DashboardScene) {
return transformSceneToSaveModelSchemaV2(s);
@ -187,12 +194,14 @@ export class V2DashboardSerializer implements DashboardSceneSerializerLike<Dashb
}
getSnapshotUrl() {
throw new Error('v2 schema: Method not implemented.');
return undefined;
return this.metadata?.annotations?.[AnnoKeyDashboardSnapshotOriginalUrl];
}
}
export function getDashboardSceneSerializer(): DashboardSceneSerializerLike<Dashboard | DashboardV2Spec> {
export function getDashboardSceneSerializer(): DashboardSceneSerializerLike<
Dashboard | DashboardV2Spec,
DashboardMeta | DashboardWithAccessInfo<DashboardV2Spec>['metadata']
> {
if (config.featureToggles.useV2DashboardsAPI) {
return new V2DashboardSerializer();
}

View File

@ -37,6 +37,7 @@ import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
import { getQueryRunnerFor } from '../utils/utils';
import { validateVariable, validateVizPanel } from '../v2schema/test-helpers';
import { SnapshotVariable } from './custom-variables/SnapshotVariable';
import { transformSaveModelSchemaV2ToScene } from './transformSaveModelSchemaV2ToScene';
import { transformCursorSynctoEnum } from './transformToV2TypesUtils';
@ -316,6 +317,44 @@ describe('transformSaveModelSchemaV2ToScene', () => {
expect(getQueryRunnerFor(vizPanels[0])?.state.datasource?.uid).toBe(MIXED_DATASOURCE_NAME);
});
describe('When creating a snapshot dashboard scene', () => {
it('should initialize a dashboard scene with SnapshotVariables', () => {
const snapshot: DashboardWithAccessInfo<DashboardV2Spec> = {
...defaultDashboard,
metadata: {
...defaultDashboard.metadata,
annotations: {
...defaultDashboard.metadata.annotations,
'grafana.app/dashboard-is-snapshot': true,
},
},
};
const scene = transformSaveModelSchemaV2ToScene(snapshot);
// check variables were converted to snapshot variables
expect(scene.state.$variables?.state.variables).toHaveLength(8);
expect(scene.state.$variables?.getByName('customVar')).toBeInstanceOf(SnapshotVariable);
expect(scene.state.$variables?.getByName('adhocVar')).toBeInstanceOf(AdHocFiltersVariable);
expect(scene.state.$variables?.getByName('intervalVar')).toBeInstanceOf(SnapshotVariable);
// custom snapshot
const customSnapshot = scene.state.$variables?.getByName('customVar') as SnapshotVariable;
expect(customSnapshot.state.value).toBe('option1');
expect(customSnapshot.state.text).toBe('option1');
expect(customSnapshot.state.isReadOnly).toBe(true);
// adhoc snapshot
const adhocSnapshot = scene.state.$variables?.getByName('adhocVar') as AdHocFiltersVariable;
const adhocVariable = snapshot.spec.variables[7] as AdhocVariableKind;
expect(adhocSnapshot.state.filters).toEqual(adhocVariable.spec.filters);
expect(adhocSnapshot.state.readOnly).toBe(true);
// interval snapshot
const intervalSnapshot = scene.state.$variables?.getByName('intervalVar') as SnapshotVariable;
expect(intervalSnapshot.state.value).toBe('1m');
expect(intervalSnapshot.state.text).toBe('1m');
expect(intervalSnapshot.state.isReadOnly).toBe(true);
});
});
describe('meta', () => {
describe('initializes meta based on k8s resource', () => {
it('handles undefined access values', () => {

View File

@ -59,6 +59,7 @@ import {
AnnoKeyUpdatedBy,
AnnoKeyUpdatedTimestamp,
AnnoKeyDashboardIsNew,
AnnoKeyDashboardIsSnapshot,
} from 'app/features/apiserver/types';
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
@ -140,6 +141,7 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
updated: metadata.annotations?.[AnnoKeyUpdatedTimestamp],
updatedBy: metadata.annotations?.[AnnoKeyUpdatedBy],
folderUid: metadata.annotations?.[AnnoKeyFolder],
isSnapshot: Boolean(metadata.annotations?.[AnnoKeyDashboardIsSnapshot]),
// UI-only metadata, ref: DashboardModel.initMeta
showSettings: Boolean(dto.access.canEdit),
@ -184,7 +186,7 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
weekStart: dashboard.timeSettings.weekStart,
UNSAFE_nowDelay: dashboard.timeSettings.nowDelay,
}),
$variables: getVariables(dashboard),
$variables: getVariables(dashboard, meta.isSnapshot ?? false),
$behaviors: [
new behaviors.CursorSync({
sync: transformCursorSyncV2ToV1(dashboard.cursorSync),
@ -220,7 +222,7 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
}),
});
dashboardScene.setInitialSaveModel(dto.spec);
dashboardScene.setInitialSaveModel(dto.spec, dto.metadata);
return dashboardScene;
}
@ -399,15 +401,12 @@ export function createPanelDataProvider(panelKind: PanelKind): SceneDataProvider
});
}
function getVariables(dashboard: DashboardV2Spec): SceneVariableSet | undefined {
function getVariables(dashboard: DashboardV2Spec, isSnapshot: boolean): SceneVariableSet | undefined {
let variables: SceneVariableSet | undefined;
if (dashboard.variables.length) {
if (false) {
// FIXME: isSnapshot is not added to the schema yet
//if (dashboard.meta?.isSnapshot) {
// in the old model we use .meta.isSnapshot but meta is not persisted
// variables = createVariablesForSnapshot(dashboard);
if (isSnapshot) {
variables = createVariablesForSnapshot(dashboard);
} else {
variables = createVariablesForDashboard(dashboard);
}

View File

@ -22,10 +22,12 @@ import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLay
export function setupLoadDashboardMock(rsp: DeepPartial<DashboardDTO>, spy?: jest.Mock) {
const loadDashboardMock = (spy || jest.fn()).mockResolvedValue(rsp);
const loadSnapshotMock = (spy || jest.fn()).mockResolvedValue(rsp);
// disabling type checks since this is a test util
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
setDashboardLoaderSrv({
loadDashboard: loadDashboardMock,
loadSnapshot: loadSnapshotMock,
} as unknown as DashboardLoaderSrv);
return loadDashboardMock;
}

View File

@ -17,6 +17,8 @@ import {
AnnoKeyCreatedBy,
AnnoKeyDashboardGnetId,
AnnoKeyDashboardId,
AnnoKeyDashboardIsSnapshot,
AnnoKeyDashboardSnapshotOriginalUrl,
AnnoKeyFolder,
AnnoKeySlug,
AnnoKeyUpdatedBy,
@ -56,17 +58,51 @@ export function ensureV2Response(
const variables = getVariables(dashboard.templating?.list || []);
const annotations = getAnnotations(dashboard.annotations?.list || []);
const accessAndMeta = isDashboardResource(dto)
? {
...dto.access,
created: dto.metadata.creationTimestamp,
createdBy: dto.metadata.annotations?.[AnnoKeyCreatedBy],
updatedBy: dto.metadata.annotations?.[AnnoKeyUpdatedBy],
updated: dto.metadata.annotations?.[AnnoKeyUpdatedTimestamp],
folderUid: dto.metadata.annotations?.[AnnoKeyFolder],
slug: dto.metadata.annotations?.[AnnoKeySlug],
}
: dto.meta;
let accessMeta: DashboardWithAccessInfo<DashboardV2Spec>['access'];
let annotationsMeta: DashboardWithAccessInfo<DashboardV2Spec>['metadata']['annotations'];
let creationTimestamp;
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],
[AnnoKeyDashboardId]: dashboard.id ?? undefined,
[AnnoKeyDashboardGnetId]: dashboard.gnetId ?? undefined,
[AnnoKeyDashboardIsSnapshot]: dto.metadata.annotations?.[AnnoKeyDashboardIsSnapshot],
};
creationTimestamp = dto.metadata.creationTimestamp;
} else {
accessMeta = {
url: dto.meta.url,
slug: dto.meta.slug,
canSave: dto.meta.canSave,
canEdit: dto.meta.canEdit,
canDelete: dto.meta.canDelete,
canShare: dto.meta.canShare,
canStar: dto.meta.canStar,
canAdmin: dto.meta.canAdmin,
annotationsPermissions: dto.meta.annotationsPermissions,
};
annotationsMeta = {
[AnnoKeyCreatedBy]: dto.meta.createdBy,
[AnnoKeyUpdatedBy]: dto.meta.updatedBy,
[AnnoKeyUpdatedTimestamp]: dto.meta.updated,
[AnnoKeyFolder]: dto.meta.folderUid,
[AnnoKeySlug]: dto.meta.slug,
[AnnoKeyDashboardId]: dashboard.id ?? undefined,
[AnnoKeyDashboardGnetId]: dashboard.gnetId ?? undefined,
[AnnoKeyDashboardIsSnapshot]: dto.meta.isSnapshot,
};
creationTimestamp = dto.meta.created;
}
if (annotationsMeta?.[AnnoKeyDashboardIsSnapshot]) {
annotationsMeta[AnnoKeyDashboardSnapshotOriginalUrl] = dashboard.snapshot?.originalUrl;
}
const spec: DashboardV2Spec = {
title: dashboard.title,
@ -101,31 +137,13 @@ export function ensureV2Response(
apiVersion: 'v2alpha1',
kind: 'DashboardWithAccessInfo',
metadata: {
creationTimestamp: accessAndMeta.created || '', // TODO verify this empty string is valid
creationTimestamp: creationTimestamp || '', // TODO verify this empty string is valid
name: dashboard.uid,
resourceVersion: dashboard.version?.toString() || '0',
annotations: {
[AnnoKeyCreatedBy]: accessAndMeta.createdBy,
[AnnoKeyUpdatedBy]: accessAndMeta.updatedBy,
[AnnoKeyUpdatedTimestamp]: accessAndMeta.updated,
[AnnoKeyFolder]: accessAndMeta.folderUid,
[AnnoKeySlug]: accessAndMeta.slug,
[AnnoKeyDashboardId]: dashboard.id ?? undefined,
[AnnoKeyDashboardGnetId]: dashboard.gnetId ?? undefined,
},
annotations: annotationsMeta,
},
spec,
access: {
url: accessAndMeta.url || '',
canAdmin: accessAndMeta.canAdmin,
canDelete: accessAndMeta.canDelete,
canEdit: accessAndMeta.canEdit,
canSave: accessAndMeta.canSave,
canShare: accessAndMeta.canShare,
canStar: accessAndMeta.canStar,
slug: accessAndMeta.slug,
annotationsPermissions: accessAndMeta.annotationsPermissions,
},
access: accessMeta,
};
}

View File

@ -10,6 +10,7 @@ import {
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen';
import { backendSrv } from 'app/core/services/backend_srv';
import impressionSrv from 'app/core/services/impression_srv';
import { getMessageFromError } from 'app/core/utils/errors';
import kbn from 'app/core/utils/kbn';
import { AnnoKeyDashboardIsSnapshot, AnnoKeyDashboardNotFound } from 'app/features/apiserver/types';
import { getDashboardScenePageStateManager } from 'app/features/dashboard-scene/pages/DashboardScenePageStateManager';
@ -42,6 +43,7 @@ abstract class DashboardLoaderSrvBase<T> implements DashboardLoaderSrvLike<T> {
uid: string | undefined,
params?: UrlQueryMap
): Promise<T>;
abstract loadSnapshot(slug: string): Promise<T>;
protected loadScriptedDashboard(file: string) {
const url = 'public/dashboards/' + file.replace(/\.(?!js)/, '/') + '?' + new Date().getTime();
@ -144,12 +146,6 @@ export class DashboardLoaderSrv extends DashboardLoaderSrvBase<DashboardDTO> {
if (type === 'script' && slug) {
promise = this.loadScriptedDashboard(slug);
} else if (type === 'snapshot' && slug) {
promise = getDashboardSnapshotSrv()
.getSnapshot(slug)
.catch(() => {
return this._dashboardLoadFailed('Snapshot not found', true);
});
} else if (type === 'public' && uid) {
promise = backendSrv
.getPublicDashboardByUid(uid)
@ -213,6 +209,24 @@ export class DashboardLoaderSrv extends DashboardLoaderSrvBase<DashboardDTO> {
return promise;
}
loadSnapshot(slug: string): Promise<DashboardDTO> {
const promise = getDashboardSnapshotSrv()
.getSnapshot(slug)
.catch(() => {
return this._dashboardLoadFailed('Snapshot not found', true);
});
promise.then((result: DashboardDTO) => {
if (result.meta.dashboardNotFound !== true) {
impressionSrv.addDashboardImpression(result.dashboard.uid);
}
return result;
});
return promise;
}
}
export class DashboardLoaderSrvV2 extends DashboardLoaderSrvBase<DashboardWithAccessInfo<DashboardV2Spec>> {
@ -257,13 +271,6 @@ export class DashboardLoaderSrvV2 extends DashboardLoaderSrvBase<DashboardWithAc
if (type === 'script' && slug) {
promise = this.loadScriptedDashboard(slug).then((r) => ResponseTransformers.ensureV2Response(r));
} else if (type === 'snapshot' && slug) {
promise = getDashboardSnapshotSrv()
.getSnapshot(slug)
.then((r) => ResponseTransformers.ensureV2Response(r))
.catch(() => {
return this._dashboardLoadFailed('Snapshot not found', true);
});
} else if (type === 'public' && uid) {
promise = backendSrv
.getPublicDashboardByUid(uid)
@ -327,6 +334,26 @@ export class DashboardLoaderSrvV2 extends DashboardLoaderSrvBase<DashboardWithAc
return promise;
}
loadSnapshot(slug: string): Promise<DashboardWithAccessInfo<DashboardV2Spec>> {
const promise = getDashboardSnapshotSrv()
.getSnapshot(slug)
.then((r) => ResponseTransformers.ensureV2Response(r))
.catch((e) => {
const msg = getMessageFromError(e);
throw new Error(`Failed to load snapshot: ${msg}`);
});
promise.then((result: DashboardWithAccessInfo<DashboardV2Spec>) => {
if (result.metadata.annotations?.[AnnoKeyDashboardNotFound] !== true) {
impressionSrv.addDashboardImpression(result.metadata.name);
}
return result;
});
return promise;
}
}
let dashboardLoaderSrv = new DashboardLoaderSrv();