Revert "Schema v2: Reason about new dashboard based on UID (#98800)" (#98877)

This reverts commit 72846e3431.
This commit is contained in:
Dominik Prokop 2025-01-13 13:07:45 +01:00 committed by GitHub
parent 6f02d2d73d
commit 99e1780527
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 73 additions and 26 deletions

View File

@ -50,6 +50,7 @@ export const AnnoKeySavedFromUI = 'grafana.app/saved-from-ui';
export const AnnoKeyDashboardNotFound = 'grafana.app/dashboard-not-found'; export const AnnoKeyDashboardNotFound = 'grafana.app/dashboard-not-found';
export const AnnoKeyDashboardIsSnapshot = 'grafana.app/dashboard-is-snapshot'; export const AnnoKeyDashboardIsSnapshot = 'grafana.app/dashboard-is-snapshot';
export const AnnoKeyDashboardSnapshotOriginalUrl = 'grafana.app/dashboard-snapshot-original-url'; 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'; export const AnnoKeyDashboardGnetId = 'grafana.app/dashboard-gnet-id';
// Annotations provided by the API // Annotations provided by the API
@ -78,6 +79,7 @@ type GrafanaClientAnnotations = {
[AnnoKeyDashboardNotFound]?: boolean; [AnnoKeyDashboardNotFound]?: boolean;
[AnnoKeyDashboardIsSnapshot]?: boolean; [AnnoKeyDashboardIsSnapshot]?: boolean;
[AnnoKeyDashboardSnapshotOriginalUrl]?: string; [AnnoKeyDashboardSnapshotOriginalUrl]?: string;
[AnnoKeyDashboardIsNew]?: boolean;
// TODO: This should be provided by the API // TODO: This should be provided by the API
// This is the dashboard ID for the Gcom API. This set when a dashboard is created through importing a dashboard from Grafana.com. // This is the dashboard ID for the Gcom API. This set when a dashboard is created through importing a dashboard from Grafana.com.

View File

@ -93,7 +93,6 @@ export class PanelInspectDrawer extends SceneObjectBase<PanelInspectDrawerState>
onClose = () => { onClose = () => {
const dashboard = getDashboardSceneFor(this); const dashboard = getDashboardSceneFor(this);
const meta = dashboard.state.meta; const meta = dashboard.state.meta;
const isNew = dashboard.state.uid === '';
locationService.push( locationService.push(
getDashboardUrl({ getDashboardUrl({
@ -104,7 +103,7 @@ export class PanelInspectDrawer extends SceneObjectBase<PanelInspectDrawerState>
inspect: null, inspect: null,
inspectTab: null, inspectTab: null,
}, },
isHomeDashboard: !meta.url && !meta.slug && !isNew, isHomeDashboard: !meta.url && !meta.slug && !meta.isNew,
}) })
); );
}; };

View File

@ -136,12 +136,13 @@ describe('DashboardScenePageStateManager v1', () => {
}); });
describe('New dashboards', () => { describe('New dashboards', () => {
it('Should have new empty model and should not be cached', async () => { it('Should have new empty model with meta.isNew and should not be cached', async () => {
const loader = new DashboardScenePageStateManager({}); const loader = new DashboardScenePageStateManager({});
await loader.loadDashboard({ uid: '', route: DashboardRoutes.New }); await loader.loadDashboard({ uid: '', route: DashboardRoutes.New });
const dashboard = loader.state.dashboard!; const dashboard = loader.state.dashboard!;
expect(dashboard.state.meta.isNew).toBe(true);
expect(dashboard.state.isEditing).toBe(undefined); expect(dashboard.state.isEditing).toBe(undefined);
expect(dashboard.state.isDirty).toBe(false); expect(dashboard.state.isDirty).toBe(false);
@ -433,12 +434,13 @@ describe('DashboardScenePageStateManager v2', () => {
}); });
describe('New dashboards', () => { describe('New dashboards', () => {
it('Should have new empty model and should not be cached', async () => { it('Should have new empty model with meta.isNew and should not be cached', async () => {
const loader = new DashboardScenePageStateManagerV2({}); const loader = new DashboardScenePageStateManagerV2({});
await loader.loadDashboard({ uid: '', route: DashboardRoutes.New }); await loader.loadDashboard({ uid: '', route: DashboardRoutes.New });
const dashboard = loader.state.dashboard!; const dashboard = loader.state.dashboard!;
expect(dashboard.state.meta.isNew).toBe(true);
expect(dashboard.state.isEditing).toBe(undefined); expect(dashboard.state.isEditing).toBe(undefined);
expect(dashboard.state.isDirty).toBe(false); expect(dashboard.state.isDirty).toBe(false);

View File

@ -237,7 +237,7 @@ describe('PanelAlertTabContent', () => {
}), }),
]; ];
renderAlertTab(dashboard, dashboard); renderAlertTab(dashboard);
const defaults = await clickNewButton(); const defaults = await clickNewButton();
@ -264,7 +264,7 @@ describe('PanelAlertTabContent', () => {
}), }),
]; ];
renderAlertTab(dashboard, dashboard); renderAlertTab(dashboard);
const defaults = await clickNewButton(); const defaults = await clickNewButton();
expect(defaults.queries[0].model).toEqual({ expect(defaults.queries[0].model).toEqual({
@ -290,7 +290,7 @@ describe('PanelAlertTabContent', () => {
}), }),
]; ];
renderAlertTab(dashboard, dashboard); renderAlertTab(dashboard);
const defaults = await clickNewButton(); const defaults = await clickNewButton();
expect(defaults.queries[0].model).toEqual({ expect(defaults.queries[0].model).toEqual({
@ -310,7 +310,7 @@ describe('PanelAlertTabContent', () => {
it('Will render alerts belonging to panel and a button to create alert from panel queries', async () => { it('Will render alerts belonging to panel and a button to create alert from panel queries', async () => {
dashboard.panels = [panel]; dashboard.panels = [panel];
renderAlertTab(dashboard, dashboard); renderAlertTab(dashboard);
const rows = await ui.row.findAll(); const rows = await ui.row.findAll();
expect(rows).toHaveLength(2); expect(rows).toHaveLength(2);
@ -334,8 +334,8 @@ describe('PanelAlertTabContent', () => {
}); });
}); });
function renderAlertTab(dashboard: DashboardModel, dto: DashboardDataDTO) { function renderAlertTab(dashboard: DashboardModel) {
const model = createModel(dashboard, dto); const model = createModel(dashboard);
renderAlertTabContent(model); renderAlertTabContent(model);
} }
@ -353,8 +353,8 @@ async function clickNewButton() {
return defaults; return defaults;
} }
function createModel(dashboard: DashboardModel, dto: DashboardDataDTO) { function createModel(dashboard: DashboardModel) {
const scene = createDashboardSceneFromDashboardModel(dashboard, dto); const scene = createDashboardSceneFromDashboardModel(dashboard, {} as DashboardDataDTO);
const vizPanel = findVizPanelByKey(scene, getVizPanelKeyForPanelId(34))!; const vizPanel = findVizPanelByKey(scene, getVizPanelKeyForPanelId(34))!;
const model = new PanelDataAlertingTab({ panelRef: vizPanel.getRef() }); const model = new PanelDataAlertingTab({ panelRef: vizPanel.getRef() });
jest.spyOn(utils, 'getDashboardSceneFor').mockReturnValue(scene); jest.spyOn(utils, 'getDashboardSceneFor').mockReturnValue(scene);

View File

@ -110,6 +110,27 @@ describe('DashboardScene', () => {
}); });
}); });
describe('Given new dashboard in edit mode', () => {
it('when saving it should clear isNew state', () => {
const scene = buildTestScene({
meta: { isNew: true },
});
scene.activate();
scene.onEnterEditMode();
scene.saveCompleted({} as Dashboard, {
id: 1,
slug: 'slug',
uid: 'dash-1',
url: 'sss',
version: 2,
status: 'aaa',
});
expect(scene.state.meta.isNew).toBeFalsy();
});
});
describe('Given scene in edit mode', () => { describe('Given scene in edit mode', () => {
let scene: DashboardScene; let scene: DashboardScene;
let deactivateScene: () => void; let deactivateScene: () => void;

View File

@ -112,7 +112,7 @@ export interface DashboardSceneState extends SceneObjectState {
/** True when user made a change */ /** True when user made a change */
isDirty?: boolean; isDirty?: boolean;
/** meta flags */ /** meta flags */
meta: Omit<DashboardMeta, 'isNew'>; meta: DashboardMeta;
/** Version of the dashboard */ /** Version of the dashboard */
version?: number; version?: number;
/** Panel to inspect */ /** Panel to inspect */
@ -200,7 +200,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
private _activationHandler() { private _activationHandler() {
let prevSceneContext = window.__grafanaSceneContext; let prevSceneContext = window.__grafanaSceneContext;
const isNew = this.state.uid === '';
window.__grafanaSceneContext = this; window.__grafanaSceneContext = this;
this._initializePanelSearch(); this._initializePanelSearch();
@ -210,7 +210,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
this._changeTracker.startTrackingChanges(); this._changeTracker.startTrackingChanges();
} }
if (isNew) { if (this.state.meta.isNew) {
this.onEnterEditMode(); this.onEnterEditMode();
this.setState({ isDirty: true }); this.setState({ isDirty: true });
} }
@ -284,6 +284,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
url: result.url, url: result.url,
slug: result.slug, slug: result.slug,
folderUid: folderUid, folderUid: folderUid,
isNew: false,
version: result.version, version: result.version,
}, },
}); });
@ -412,7 +413,6 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
public getPageNav(location: H.Location, navIndex: NavIndex) { public getPageNav(location: H.Location, navIndex: NavIndex) {
const { meta, viewPanelScene, editPanel, title, uid } = this.state; const { meta, viewPanelScene, editPanel, title, uid } = this.state;
const isNew = uid === '';
if (meta.dashboardNotFound) { if (meta.dashboardNotFound) {
return { text: 'Not found' }; return { text: 'Not found' };
@ -425,7 +425,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
slug: meta.slug, slug: meta.slug,
currentQueryParams: location.search, currentQueryParams: location.search,
updateQuery: { viewPanel: null, inspect: null, editview: null, editPanel: null, tab: null, shareView: null }, updateQuery: { viewPanel: null, inspect: null, editview: null, editPanel: null, tab: null, shareView: null },
isHomeDashboard: !meta.url && !meta.slug && !isNew && !meta.isSnapshot, isHomeDashboard: !meta.url && !meta.slug && !meta.isNew && !meta.isSnapshot,
isSnapshot: meta.isSnapshot, isSnapshot: meta.isSnapshot,
}), }),
}; };

View File

@ -68,7 +68,6 @@ export function ToolbarActions({ dashboard }: Props) {
const isEditedPanelDirty = usePanelEditDirty(editPanel); const isEditedPanelDirty = usePanelEditDirty(editPanel);
const isEditingLibraryPanel = editPanel && isLibraryPanel(editPanel.state.panelRef.resolve()); const isEditingLibraryPanel = editPanel && isLibraryPanel(editPanel.state.panelRef.resolve());
const isNotFound = Boolean(meta.dashboardNotFound); const isNotFound = Boolean(meta.dashboardNotFound);
const isNew = uid === '';
const hasCopiedPanel = store.exists(LS_PANEL_COPY_KEY); const hasCopiedPanel = store.exists(LS_PANEL_COPY_KEY);
// Means we are not in settings view, fullscreen panel or edit panel // Means we are not in settings view, fullscreen panel or edit panel
const isShowingDashboard = !editview && !isViewingPanel && !isEditingPanel; const isShowingDashboard = !editview && !isViewingPanel && !isEditingPanel;
@ -468,7 +467,7 @@ export function ToolbarActions({ dashboard }: Props) {
toolbarActions.push({ toolbarActions.push({
group: 'main-buttons', group: 'main-buttons',
condition: isEditing && !isNew && isShowingDashboard, condition: isEditing && !meta.isNew && isShowingDashboard,
render: () => ( render: () => (
<Button <Button
onClick={() => dashboard.exitEditMode({ skipConfirm: false })} onClick={() => dashboard.exitEditMode({ skipConfirm: false })}
@ -562,7 +561,7 @@ export function ToolbarActions({ dashboard }: Props) {
condition: isEditing && !isEditingLibraryPanel && (meta.canSave || canSaveAs), condition: isEditing && !isEditingLibraryPanel && (meta.canSave || canSaveAs),
render: () => { render: () => {
// if we only can save // if we only can save
if (isNew) { if (meta.isNew) {
return ( return (
<Button <Button
onClick={() => { onClick={() => {

View File

@ -155,7 +155,7 @@ export class V2DashboardSerializer
); );
const hasFolderChanges = scene.getInitialState()?.meta.folderUid !== scene.state.meta.folderUid; const hasFolderChanges = scene.getInitialState()?.meta.folderUid !== scene.state.meta.folderUid;
const isNew = scene.getInitialState()?.uid === ''; const isNew = scene.getInitialState()?.meta.isNew;
return { return {
...changeInfo, ...changeInfo,

View File

@ -9,7 +9,7 @@ import {
defaultTimeSettingsSpec, defaultTimeSettingsSpec,
GroupByVariableKind, GroupByVariableKind,
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen'; } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen';
import { AnnoKeyFolder } from 'app/features/apiserver/types'; import { AnnoKeyDashboardIsNew, AnnoKeyFolder } from 'app/features/apiserver/types';
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types'; import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { DashboardDTO } from 'app/types'; import { DashboardDTO } from 'app/types';
@ -129,6 +129,7 @@ export async function buildNewDashboardSaveModelV2(
creationTimestamp: '0', creationTimestamp: '0',
annotations: { annotations: {
[AnnoKeyFolder]: '', [AnnoKeyFolder]: '',
[AnnoKeyDashboardIsNew]: true,
}, },
}, },
}; };

View File

@ -26,6 +26,7 @@ import {
TextVariableKind, TextVariableKind,
} from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen'; } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/dashboard.gen';
import { handyTestingSchema } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/examples'; import { handyTestingSchema } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha0/examples';
import { AnnoKeyDashboardIsNew } from 'app/features/apiserver/types';
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types'; import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
@ -470,5 +471,26 @@ describe('transformSaveModelSchemaV2ToScene', () => {
expect(scene.state.meta.canDelete).toBe(true); expect(scene.state.meta.canDelete).toBe(true);
}); });
}); });
describe('is new dashboard handling', () => {
it('handles undefined is new dashbaord annotation', () => {
const scene = transformSaveModelSchemaV2ToScene(defaultDashboard);
expect(scene.state.meta.isNew).toBe(false);
});
it('handles defined is new dashbaord annotation', () => {
const dashboard: DashboardWithAccessInfo<DashboardV2Spec> = {
...defaultDashboard,
metadata: {
...defaultDashboard.metadata,
annotations: {
...defaultDashboard.metadata.annotations,
[AnnoKeyDashboardIsNew]: true,
},
},
};
const scene = transformSaveModelSchemaV2ToScene(dashboard);
expect(scene.state.meta.isNew).toBe(true);
});
});
}); });
}); });

View File

@ -58,6 +58,7 @@ import {
AnnoKeyFolder, AnnoKeyFolder,
AnnoKeyUpdatedBy, AnnoKeyUpdatedBy,
AnnoKeyUpdatedTimestamp, AnnoKeyUpdatedTimestamp,
AnnoKeyDashboardIsNew,
AnnoKeyDashboardIsSnapshot, AnnoKeyDashboardIsSnapshot,
} from 'app/features/apiserver/types'; } from 'app/features/apiserver/types';
import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types'; import { DashboardWithAccessInfo } from 'app/features/dashboard/api/types';
@ -148,6 +149,7 @@ export function transformSaveModelSchemaV2ToScene(dto: DashboardWithAccessInfo<D
hasUnsavedFolderChange: false, hasUnsavedFolderChange: false,
dashboardNotFound: Boolean(dto.metadata.annotations?.[AnnoKeyDashboardNotFound]), dashboardNotFound: Boolean(dto.metadata.annotations?.[AnnoKeyDashboardNotFound]),
version: parseInt(metadata.resourceVersion, 10), version: parseInt(metadata.resourceVersion, 10),
isNew: Boolean(dto.metadata.annotations?.[AnnoKeyDashboardIsNew]),
}; };
// Ref: DashboardModel.initMeta // Ref: DashboardModel.initMeta

View File

@ -175,7 +175,6 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel,
let variables: SceneVariableSet | undefined; let variables: SceneVariableSet | undefined;
let annotationLayers: SceneDataLayerProvider[] = []; let annotationLayers: SceneDataLayerProvider[] = [];
let alertStatesLayer: AlertStatesDataLayer | undefined; let alertStatesLayer: AlertStatesDataLayer | undefined;
const uid = dto.uid;
if (oldModel.templating?.list?.length) { if (oldModel.templating?.list?.length) {
if (oldModel.meta.isSnapshot) { if (oldModel.meta.isSnapshot) {
@ -229,16 +228,15 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel,
addPanelsOnLoadBehavior, addPanelsOnLoadBehavior,
new DashboardScopesFacade({ new DashboardScopesFacade({
reloadOnParamsChange: config.featureToggles.reloadDashboardsOnParamsChange && oldModel.meta.reloadOnParamsChange, reloadOnParamsChange: config.featureToggles.reloadDashboardsOnParamsChange && oldModel.meta.reloadOnParamsChange,
uid, uid: oldModel.uid,
}), }),
new DashboardReloadBehavior({ new DashboardReloadBehavior({
reloadOnParamsChange: config.featureToggles.reloadDashboardsOnParamsChange && oldModel.meta.reloadOnParamsChange, reloadOnParamsChange: config.featureToggles.reloadDashboardsOnParamsChange && oldModel.meta.reloadOnParamsChange,
uid, uid: oldModel.uid,
version: oldModel.version, version: oldModel.version,
}), }),
]; ];
const dashboardScene = new DashboardScene({ const dashboardScene = new DashboardScene({
uid,
description: oldModel.description, description: oldModel.description,
editable: oldModel.editable, editable: oldModel.editable,
preload: dto.preload ?? false, preload: dto.preload ?? false,
@ -248,6 +246,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel,
meta: oldModel.meta, meta: oldModel.meta,
tags: oldModel.tags || [], tags: oldModel.tags || [],
title: oldModel.title, title: oldModel.title,
uid: oldModel.uid,
version: oldModel.version, version: oldModel.version,
body: new DefaultGridLayoutManager({ body: new DefaultGridLayoutManager({
grid: new SceneGridLayout({ grid: new SceneGridLayout({