DashboardScene: Use numeric panel ids in url (#74005)

* DashboardScene: Use numeric panel ids in url

* Rename fix

* Fixed tests
This commit is contained in:
Torkel Ödegaard 2023-08-30 10:09:47 +02:00 committed by GitHub
parent dc6675cade
commit 0563b51098
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 54 additions and 51 deletions

View File

@ -133,7 +133,7 @@ describe('DashboardScenePage', () => {
expect(await screen.findByTitle('Panel A')).toBeInTheDocument();
act(() => locationService.partial({ viewPanel: 'panel-2' }));
act(() => locationService.partial({ viewPanel: '2' }));
expect(screen.queryByTitle('Panel A')).not.toBeInTheDocument();
expect(await screen.findByTitle('Panel B')).toBeInTheDocument();

View File

@ -6,20 +6,20 @@ describe('DashboardScene', () => {
describe('Given a standard scene', () => {
it('Should set inspectPanelKey when url has inspect key', () => {
const scene = buildTestScene();
scene.urlSync?.updateFromUrl({ inspect: 'panel-2' });
expect(scene.state.inspectPanelKey).toBe('panel-2');
scene.urlSync?.updateFromUrl({ inspect: '2' });
expect(scene.state.inspectPanelId).toBe('2');
});
it('Should handle inspect key that is not found', () => {
const scene = buildTestScene();
scene.urlSync?.updateFromUrl({ inspect: '12321' });
expect(scene.state.inspectPanelKey).toBe(undefined);
expect(scene.state.inspectPanelId).toBe(undefined);
});
it('Should set viewPanelKey when url has viewPanel', () => {
const scene = buildTestScene();
scene.urlSync?.updateFromUrl({ viewPanel: 'panel-2' });
expect(scene.state.viewPanelKey).toBe('panel-2');
scene.urlSync?.updateFromUrl({ viewPanel: '2' });
expect(scene.state.viewPanelId).toBe('2');
});
});

View File

@ -16,8 +16,7 @@ import {
import { DashboardSceneRenderer } from '../scene/DashboardSceneRenderer';
import { SaveDashboardDrawer } from '../serialization/SaveDashboardDrawer';
import { findVizPanel } from '../utils/findVizPanel';
import { forceRenderChildren } from '../utils/utils';
import { findVizPanelById, forceRenderChildren } from '../utils/utils';
import { DashboardSceneUrlSync } from './DashboardSceneUrlSync';
@ -29,10 +28,10 @@ export interface DashboardSceneState extends SceneObjectState {
controls?: SceneObject[];
isEditing?: boolean;
isDirty?: boolean;
/** Scene object key for object to inspect */
inspectPanelKey?: string;
/** Scene object key for object to view in fullscreen */
viewPanelKey?: string;
/** Panel to inspect */
inspectPanelId?: string;
/** Panel to view in full screen */
viewPanelId?: string;
/** Scene object that handles the current drawer */
drawer?: SceneObject;
}
@ -129,7 +128,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
url: locationUtil.getUrlForPartial(location, { viewPanel: null, inspect: null }),
};
if (this.state.viewPanelKey) {
if (this.state.viewPanelId) {
pageNav = {
text: 'View panel',
parentItem: pageNav,
@ -142,8 +141,8 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
/**
* Returns the body (layout) or the full view panel
*/
public getBodyToRender(viewPanelKey?: string): SceneObject {
const viewPanel = findVizPanel(this, viewPanelKey);
public getBodyToRender(viewPanelId?: string): SceneObject {
const viewPanel = findVizPanelById(this, viewPanelId);
return viewPanel ?? this.state.body;
}

View File

@ -11,11 +11,11 @@ import { DashboardScene } from './DashboardScene';
import { NavToolbarActions } from './NavToolbarActions';
export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardScene>) {
const { controls, viewPanelKey, drawer } = model.useState();
const { controls, viewPanelId, drawer } = model.useState();
const styles = useStyles2(getStyles);
const location = useLocation();
const pageNav = model.getPageNav(location);
const bodyToRender = model.getBodyToRender(viewPanelKey);
const bodyToRender = model.getBodyToRender(viewPanelId);
return (
<Page navId="scenes" pageNav={pageNav} layout={PageLayoutType.Custom}>

View File

@ -4,7 +4,7 @@ import { SceneObjectUrlSyncHandler, SceneObjectUrlValues } from '@grafana/scenes
import appEvents from 'app/core/app_events';
import { PanelInspectDrawer } from '../inspect/PanelInspectDrawer';
import { findVizPanel } from '../utils/findVizPanel';
import { findVizPanelById } from '../utils/utils';
import { DashboardScene, DashboardSceneState } from './DashboardScene';
@ -17,41 +17,41 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
getUrlState(): SceneObjectUrlValues {
const state = this._scene.state;
return { inspect: state.inspectPanelKey, viewPanel: state.viewPanelKey };
return { inspect: state.inspectPanelId, viewPanel: state.viewPanelId };
}
updateFromUrl(values: SceneObjectUrlValues): void {
const { inspectPanelKey, viewPanelKey } = this._scene.state;
const { inspectPanelId, viewPanelId } = this._scene.state;
const update: Partial<DashboardSceneState> = {};
// Handle inspect object state
if (typeof values.inspect === 'string') {
const panel = findVizPanel(this._scene, values.inspect);
const panel = findVizPanelById(this._scene, values.inspect);
if (!panel) {
appEvents.emit(AppEvents.alertError, ['Panel not found']);
locationService.partial({ inspect: null });
return;
}
update.inspectPanelKey = values.inspect;
update.inspectPanelId = values.inspect;
update.drawer = new PanelInspectDrawer(panel);
} else if (inspectPanelKey) {
update.inspectPanelKey = undefined;
} else if (inspectPanelId) {
update.inspectPanelId = undefined;
update.drawer = undefined;
}
// Handle view panel state
if (typeof values.viewPanel === 'string') {
const panel = findVizPanel(this._scene, values.viewPanel);
const panel = findVizPanelById(this._scene, values.viewPanel);
if (!panel) {
appEvents.emit(AppEvents.alertError, ['Panel not found']);
locationService.partial({ viewPanel: null });
return;
}
update.viewPanelKey = values.viewPanel;
} else if (viewPanelKey) {
update.viewPanelKey = undefined;
update.viewPanelId = values.viewPanel;
} else if (viewPanelId) {
update.viewPanelId = undefined;
}
if (Object.keys(update).length > 0) {

View File

@ -13,7 +13,7 @@ interface Props {
}
export const NavToolbarActions = React.memo<Props>(({ dashboard }) => {
const { actions = [], isEditing, viewPanelKey, isDirty, uid } = dashboard.useState();
const { actions = [], isEditing, viewPanelId, isDirty, uid } = dashboard.useState();
const toolbarActions = (actions ?? []).map((action) => <action.Component key={action.state.key} model={action} />);
if (uid) {
@ -29,7 +29,7 @@ export const NavToolbarActions = React.memo<Props>(({ dashboard }) => {
toolbarActions.push(<NavToolbarSeparator leftActionsSeparator key="separator" />);
if (viewPanelKey) {
if (viewPanelId) {
toolbarActions.push(
<Button
onClick={() => locationService.partial({ viewPanel: null })}

View File

@ -3,6 +3,8 @@ import { locationService } from '@grafana/runtime';
import { VizPanel, VizPanelMenu } from '@grafana/scenes';
import { t } from 'app/core/internationalization';
import { getPanelIdForVizPanel } from '../utils/utils';
/**
* Behavior is called when VizPanelMenu is activated (ie when it's opened).
*/
@ -22,7 +24,7 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
iconClassName: 'eye',
shortcut: 'v',
// Hm... need the numeric id to be url compatible?
href: locationUtil.getUrlForPartial(location, { viewPanel: panel.state.key }),
href: locationUtil.getUrlForPartial(location, { viewPanel: getPanelIdForVizPanel(panel) }),
});
items.push({
@ -30,7 +32,7 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
iconClassName: 'info-circle',
shortcut: 'i',
// Hm... need the numeric id to be url compatible?
href: locationUtil.getUrlForPartial(location, { inspect: panel.state.key }),
href: locationUtil.getUrlForPartial(location, { inspect: getPanelIdForVizPanel(panel) }),
});
menu.setState({ items });

View File

@ -3,7 +3,7 @@ import { Dashboard, defaultDashboard, FieldConfigSource, Panel } from '@grafana/
import { sortedDeepCloneWithoutNulls } from 'app/core/utils/object';
import { DashboardScene } from '../scene/DashboardScene';
import { getPanelIdForVizPanelKey } from '../utils/utils';
import { getPanelIdForVizPanel } from '../utils/utils';
export function transformSceneToSaveModel(scene: DashboardScene): Dashboard {
const state = scene.state;
@ -40,7 +40,7 @@ function gridItemToPanel(gridItem: SceneGridItem): Panel {
}
const panel: Panel = {
id: getPanelIdForVizPanelKey(vizPanel.state.key!),
id: getPanelIdForVizPanel(vizPanel),
type: vizPanel.state.pluginId,
title: vizPanel.state.title,
gridPos: {

View File

@ -1,14 +0,0 @@
import { sceneGraph, SceneObject, VizPanel } from '@grafana/scenes';
export function findVizPanel(scene: SceneObject, key: string | undefined): VizPanel | null {
if (!key) {
return null;
}
const obj = sceneGraph.findObject(scene, (obj) => obj.state.key === key);
if (obj instanceof VizPanel) {
return obj;
}
return null;
}

View File

@ -1,11 +1,27 @@
import { SceneDeactivationHandler, SceneObject } from '@grafana/scenes';
import { SceneDeactivationHandler, sceneGraph, SceneObject, VizPanel } from '@grafana/scenes';
export function getVizPanelKeyForPanelId(panelId: number) {
return `panel-${panelId}`;
}
export function getPanelIdForVizPanelKey(key: string) {
return parseInt(key.replace('panel-', ''), 10);
export function getPanelIdForVizPanel(panel: VizPanel): number {
return parseInt(panel.state.key!.replace('panel-', ''), 10);
}
export function findVizPanelById(scene: SceneObject, id: string | undefined): VizPanel | null {
if (!id) {
return null;
}
const panelId = parseInt(id, 10);
const key = getVizPanelKeyForPanelId(panelId);
const obj = sceneGraph.findObject(scene, (obj) => obj.state.key === key);
if (obj instanceof VizPanel) {
return obj;
}
return null;
}
/**