mirror of
https://github.com/grafana/grafana.git
synced 2025-08-03 01:32:13 +08:00
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:
@ -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
|
||||
|
@ -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');
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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', () => {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
Reference in New Issue
Block a user