DashboardScene: Get rid of panel edit route (#80605)

* Wip: get rod of panel edit route

* Cleanup unused code

* Test update

* Simplify url sync for inspect and vie/edit panel

* Update navigating back to dashboard from edit panel

* DashboardScene:  Panel inspect improvements (#80655)

Improve inspect, andle view pane end edit mode inspection

* Url sync fixes

* Test update
pull/80726/head
Dominik Prokop 1 year ago committed by GitHub
parent dd7259b77e
commit 1de876c354
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 49
      public/app/features/dashboard-scene/inspect/PanelInspectDrawer.tsx
  2. 22
      public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts
  3. 36
      public/app/features/dashboard-scene/pages/PanelEditPage.tsx
  4. 76
      public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx
  5. 19
      public/app/features/dashboard-scene/panel-edit/PanelEditorRenderer.tsx
  6. 49
      public/app/features/dashboard-scene/panel-edit/PanelEditorUrlSync.ts
  7. 7
      public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx
  8. 34
      public/app/features/dashboard-scene/scene/DashboardScene.tsx
  9. 33
      public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx
  10. 72
      public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.ts
  11. 2
      public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx
  12. 11
      public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx
  13. 12
      public/app/features/dashboard-scene/scene/keyboardShortcuts.ts
  14. 8
      public/app/features/dashboard-scene/utils/urlBuilders.ts
  15. 5
      public/app/features/dashboard-scene/utils/utils.ts
  16. 9
      public/app/plugins/datasource/dashboard/datasource.ts
  17. 6
      public/app/routes/routes.tsx

@ -15,6 +15,9 @@ import { Alert, Drawer, Tab, TabsBar } from '@grafana/ui';
import { getDataSourceWithInspector } from 'app/features/dashboard/components/Inspector/hooks';
import { supportsDataQuery } from 'app/features/dashboard/components/PanelEditor/utils';
import { getDashboardUrl } from '../utils/urlBuilders';
import { getDashboardSceneFor } from '../utils/utils';
import { InspectDataTab } from './InspectDataTab';
import { InspectJsonTab } from './InspectJsonTab';
import { InspectMetaDataTab } from './InspectMetaDataTab';
@ -24,7 +27,7 @@ import { SceneInspectTab } from './types';
interface PanelInspectDrawerState extends SceneObjectState {
tabs?: SceneInspectTab[];
panelRef: SceneObjectRef<VizPanel>;
panelRef?: SceneObjectRef<VizPanel>;
pluginNotLoaded?: boolean;
canEdit?: boolean;
}
@ -47,8 +50,7 @@ export class PanelInspectDrawer extends SceneObjectBase<PanelInspectDrawerState>
*/
async buildTabs(retry: number) {
const panelRef = this.state.panelRef;
const panel = panelRef.resolve();
const plugin = panel.getPlugin();
const plugin = panelRef?.resolve()?.getPlugin();
const tabs: SceneInspectTab[] = [];
if (!plugin) {
@ -59,31 +61,46 @@ export class PanelInspectDrawer extends SceneObjectBase<PanelInspectDrawerState>
}
}
if (supportsDataQuery(plugin)) {
const data = sceneGraph.getData(panel);
if (panelRef) {
if (supportsDataQuery(plugin)) {
const data = sceneGraph.getData(panelRef.resolve());
tabs.push(new InspectDataTab({ panelRef }));
tabs.push(new InspectStatsTab({ panelRef }));
tabs.push(new InspectQueryTab({ panelRef }));
tabs.push(new InspectDataTab({ panelRef }));
tabs.push(new InspectStatsTab({ panelRef }));
tabs.push(new InspectQueryTab({ panelRef }));
const dsWithInspector = await getDataSourceWithInspector(data.state.data);
if (dsWithInspector) {
tabs.push(new InspectMetaDataTab({ panelRef, dataSource: dsWithInspector }));
const dsWithInspector = await getDataSourceWithInspector(data.state.data);
if (dsWithInspector) {
tabs.push(new InspectMetaDataTab({ panelRef, dataSource: dsWithInspector }));
}
}
}
tabs.push(new InspectJsonTab({ panelRef, onClose: this.onClose }));
tabs.push(new InspectJsonTab({ panelRef, onClose: this.onClose }));
}
this.setState({ tabs });
}
getDrawerTitle() {
const panel = this.state.panelRef.resolve();
return sceneGraph.interpolate(panel, `Inspect: ${panel.state.title}`);
const panel = this.state.panelRef?.resolve();
if (panel) {
return sceneGraph.interpolate(panel, `Inspect: ${panel.state.title}`);
}
return `Inspect panel`;
}
onClose = () => {
locationService.partial({ inspect: null, inspectTab: null });
const dashboard = getDashboardSceneFor(this);
locationService.push(
getDashboardUrl({
uid: dashboard.state.uid,
currentQueryParams: locationService.getLocation().search,
updateQuery: {
inspect: null,
inspectTab: null,
},
})
);
};
}

@ -9,10 +9,9 @@ import { buildNavModel } from 'app/features/folders/state/navModel';
import { store } from 'app/store/store';
import { DashboardDTO, DashboardMeta, DashboardRoutes } from 'app/types';
import { buildPanelEditScene, PanelEditor } from '../panel-edit/PanelEditor';
import { PanelEditor } from '../panel-edit/PanelEditor';
import { DashboardScene } from '../scene/DashboardScene';
import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
import { getVizPanelKeyForPanelId, findVizPanelByKey } from '../utils/utils';
export interface DashboardScenePageState {
dashboard?: DashboardScene;
@ -96,25 +95,6 @@ export class DashboardScenePageStateManager extends StateManagerBase<DashboardSc
}
}
public async loadPanelEdit(uid: string, panelId: string) {
try {
const dashboard = await this.loadScene(uid);
const panel = findVizPanelByKey(dashboard, getVizPanelKeyForPanelId(parseInt(panelId, 10)));
if (!panel) {
this.setState({ isLoading: false, loadError: 'Panel not found' });
return;
}
const panelEditor = buildPanelEditScene(dashboard, panel);
panelEditor.startUrlSync();
this.setState({ isLoading: false, panelEditor });
} catch (err) {
this.setState({ isLoading: false, loadError: String(err) });
}
}
private async loadScene(uid: string): Promise<DashboardScene> {
const fromCache = this.cache[uid];
if (fromCache) {

@ -1,36 +0,0 @@
// Libraries
import React, { useEffect } from 'react';
import { PageLayoutType } from '@grafana/data';
import { Page } from 'app/core/components/Page/Page';
import PageLoader from 'app/core/components/PageLoader/PageLoader';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { getDashboardScenePageStateManager } from './DashboardScenePageStateManager';
export interface Props extends GrafanaRouteComponentProps<{ uid: string; panelId: string }> {}
export function PanelEditPage({ match }: Props) {
const stateManager = getDashboardScenePageStateManager();
const { panelEditor, isLoading, loadError } = stateManager.useState();
useEffect(() => {
stateManager.loadPanelEdit(match.params.uid, match.params.panelId);
return () => {
stateManager.clearState();
};
}, [stateManager, match.params.uid, match.params.panelId]);
if (!panelEditor) {
return (
<Page layout={PageLayoutType.Canvas}>
{isLoading && <PageLoader />}
{loadError && <h2>{loadError}</h2>}
</Page>
);
}
return <panelEditor.Component model={panelEditor} />;
}
export default PanelEditPage;

@ -3,7 +3,6 @@ import * as H from 'history';
import { NavIndex } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import {
getUrlSyncManager,
SceneFlexItem,
SceneFlexLayout,
SceneGridItem,
@ -11,19 +10,20 @@ import {
SceneObjectBase,
SceneObjectRef,
SceneObjectState,
sceneUtils,
SplitLayout,
VizPanel,
} from '@grafana/scenes';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { DashboardScene } from '../scene/DashboardScene';
import { DashboardModelCompatibilityWrapper } from '../utils/DashboardModelCompatibilityWrapper';
import { getDashboardUrl } from '../utils/urlBuilders';
import {
findVizPanelByKey,
getDashboardSceneFor,
getPanelIdForVizPanel,
getVizPanelKeyForPanelId,
} from '../utils/utils';
import { PanelDataPane } from './PanelDataPane/PanelDataPane';
import { PanelEditorRenderer } from './PanelEditorRenderer';
import { PanelEditorUrlSync } from './PanelEditorUrlSync';
import { PanelOptionsPane } from './PanelOptionsPane';
import { VizPanelManager } from './VizPanelManager';
@ -31,49 +31,26 @@ export interface PanelEditorState extends SceneObjectState {
body: SceneObject;
controls?: SceneObject[];
isDirty?: boolean;
/** Panel to inspect */
inspectPanelKey?: string;
/** Scene object that handles the current drawer */
overlay?: SceneObject;
dashboardRef: SceneObjectRef<DashboardScene>;
sourcePanelRef: SceneObjectRef<VizPanel>;
panelId: number;
panelRef: SceneObjectRef<VizPanelManager>;
}
export class PanelEditor extends SceneObjectBase<PanelEditorState> {
static Component = PanelEditorRenderer;
/**
* Handles url sync
*/
protected _urlSync = new PanelEditorUrlSync(this);
public constructor(state: PanelEditorState) {
super(state);
this.addActivationHandler(() => this._activationHandler());
}
private _activationHandler() {
const oldDashboardWrapper = new DashboardModelCompatibilityWrapper(this.state.dashboardRef.resolve());
// @ts-expect-error
getDashboardSrv().setCurrent(oldDashboardWrapper);
// Deactivation logic
return () => {
getUrlSyncManager().cleanUp(this);
};
}
public startUrlSync() {
getUrlSyncManager().initSync(this);
public getUrlKey() {
return this.state.panelId.toString();
}
public getPageNav(location: H.Location, navIndex: NavIndex) {
const dashboard = getDashboardSceneFor(this);
return {
text: 'Edit panel',
parentItem: this.state.dashboardRef.resolve().getPageNav(location, navIndex),
parentItem: dashboard.getPageNav(location, navIndex),
};
}
@ -95,8 +72,8 @@ export class PanelEditor extends SceneObjectBase<PanelEditorState> {
};
private _commitChanges() {
const dashboard = this.state.dashboardRef.resolve();
const sourcePanel = this.state.sourcePanelRef.resolve();
const dashboard = getDashboardSceneFor(this);
const sourcePanel = findVizPanelByKey(dashboard.state.body, getVizPanelKeyForPanelId(this.state.panelId));
if (!dashboard.state.isEditing) {
dashboard.onEnterEditMode();
@ -104,41 +81,38 @@ export class PanelEditor extends SceneObjectBase<PanelEditorState> {
const panelMngr = this.state.panelRef.resolve();
if (sourcePanel.parent instanceof SceneGridItem) {
sourcePanel.parent.setState({ body: panelMngr.state.panel.clone() });
if (sourcePanel!.parent instanceof SceneGridItem) {
sourcePanel!.parent.setState({ body: panelMngr.state.panel.clone() });
}
// preserve time range and variables state
dashboard.setState({
$timeRange: this.state.$timeRange?.clone(),
$variables: this.state.$variables?.clone(),
isDirty: true,
});
}
private _navigateBackToDashboard() {
const dashboard = getDashboardSceneFor(this);
locationService.push(
getDashboardUrl({
uid: this.state.dashboardRef.resolve().state.uid,
uid: dashboard.state.uid,
currentQueryParams: locationService.getLocation().search,
updateQuery: {
editPanel: null,
// Clean the PanelEditor data pane tab query param
tab: null,
},
})
);
}
}
export function buildPanelEditScene(dashboard: DashboardScene, panel: VizPanel): PanelEditor {
export function buildPanelEditScene(panel: VizPanel): PanelEditor {
const panelClone = panel.clone();
const vizPanelMgr = new VizPanelManager(panelClone);
const dashboardStateCloned = sceneUtils.cloneSceneObjectState(dashboard.state);
return new PanelEditor({
dashboardRef: dashboard.getRef(),
sourcePanelRef: panel.getRef(),
panelId: getPanelIdForVizPanel(panel),
panelRef: vizPanelMgr.getRef(),
controls: dashboardStateCloned.controls,
$variables: dashboardStateCloned.$variables,
$timeRange: dashboardStateCloned.$timeRange,
body: new SplitLayout({
direction: 'row',
primary: new SplitLayout({

@ -1,26 +1,24 @@
import { css } from '@emotion/css';
import React from 'react';
import { useLocation } from 'react-router-dom';
import { GrafanaTheme2, PageLayoutType } from '@grafana/data';
import { GrafanaTheme2 } from '@grafana/data';
import { SceneComponentProps } from '@grafana/scenes';
import { Button, useStyles2 } from '@grafana/ui';
import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';
import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbar/NavToolbarSeparator';
import { Page } from 'app/core/components/Page/Page';
import { useSelector } from 'app/types/store';
import { getDashboardSceneFor } from '../utils/utils';
import { PanelEditor } from './PanelEditor';
export function PanelEditorRenderer({ model }: SceneComponentProps<PanelEditor>) {
const { body, controls, overlay } = model.useState();
const dashboard = getDashboardSceneFor(model);
const { body } = model.useState();
const { controls } = dashboard.useState();
const styles = useStyles2(getStyles);
const location = useLocation();
const navIndex = useSelector((state) => state.navIndex);
const pageNav = model.getPageNav(location, navIndex);
return (
<Page navId="scenes" pageNav={pageNav} layout={PageLayoutType.Custom}>
<>
<AppChromeUpdate actions={getToolbarActions(model)} />
<div className={styles.canvasContent}>
{controls && (
@ -34,8 +32,7 @@ export function PanelEditorRenderer({ model }: SceneComponentProps<PanelEditor>)
<body.Component model={body} />
</div>
</div>
{overlay && <overlay.Component model={overlay} />}
</Page>
</>
);
}

@ -1,49 +0,0 @@
import { AppEvents } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import { SceneObjectUrlSyncHandler, SceneObjectUrlValues } from '@grafana/scenes';
import appEvents from 'app/core/app_events';
import { PanelInspectDrawer } from '../inspect/PanelInspectDrawer';
import { findVizPanelByKey } from '../utils/utils';
import { PanelEditor, PanelEditorState } from './PanelEditor';
export class PanelEditorUrlSync implements SceneObjectUrlSyncHandler {
constructor(private _scene: PanelEditor) {}
getKeys(): string[] {
return ['inspect'];
}
getUrlState(): SceneObjectUrlValues {
const state = this._scene.state;
return {
inspect: state.inspectPanelKey,
};
}
updateFromUrl(values: SceneObjectUrlValues): void {
const { inspectPanelKey } = this._scene.state;
const update: Partial<PanelEditorState> = {};
// Handle inspect object state
if (typeof values.inspect === 'string') {
const panel = findVizPanelByKey(this._scene, values.inspect);
if (!panel) {
appEvents.emit(AppEvents.alertError, ['Panel not found']);
locationService.partial({ inspect: null });
return;
}
update.inspectPanelKey = values.inspect;
update.overlay = new PanelInspectDrawer({ panelRef: panel.getRef() });
} else if (inspectPanelKey) {
update.inspectPanelKey = undefined;
update.overlay = undefined;
}
if (Object.keys(update).length > 0) {
this._scene.setState(update);
}
}
}

@ -540,10 +540,11 @@ describe('VizPanelManager', () => {
describe('dashboard queries', () => {
it('should update queries', () => {
const { scene, panel } = setupTest('panel-3');
scene.setState({
editPanel: buildPanelEditScene(panel),
});
const panelEditScene = buildPanelEditScene(scene, panel);
const vizPanelManager = panelEditScene.state.panelRef.resolve();
const vizPanelManager = scene.state.editPanel!.state.panelRef.resolve();
vizPanelManager.activate();
vizPanelManager.state.panel.state.$data?.activate();

@ -1,7 +1,7 @@
import * as H from 'history';
import { Unsubscribable } from 'rxjs';
import { CoreApp, DataQueryRequest, NavIndex, NavModelItem } from '@grafana/data';
import { CoreApp, DataQueryRequest, NavIndex, NavModelItem, locationUtil } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import {
getUrlSyncManager,
@ -25,6 +25,7 @@ import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { VariablesChanged } from 'app/features/variables/types';
import { DashboardMeta } from 'app/types';
import { PanelEditor } from '../panel-edit/PanelEditor';
import { DashboardSceneRenderer } from '../scene/DashboardSceneRenderer';
import { SaveDashboardDrawer } from '../serialization/SaveDashboardDrawer';
import { DashboardEditView } from '../settings/utils';
@ -73,6 +74,9 @@ export interface DashboardSceneState extends SceneObjectState {
viewPanelScene?: ViewPanelScene;
/** Edit view */
editview?: DashboardEditView;
/** Edit panel */
editPanel?: PanelEditor;
/** Scene object that handles the current drawer or modal */
overlay?: SceneObject;
}
@ -169,8 +173,21 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
this.stopTrackingChanges();
// Stop url sync before updating url
this.stopUrlSync();
// Now we can update url
locationService.replace({ pathname: this._initialUrlState?.pathname, search: this._initialUrlState?.search });
// Now we can update urls
// We are updating url and removing editview and editPanel.
// The initial url may be including edit view, edit panel or inspect query params if the user pasted the url,
// hence we need to cleanup those query params to get back to the dashboard view. Otherwise url sync can trigger overlays.
locationService.replace(
locationUtil.getUrlForPartial(this._initialUrlState!, {
editPanel: null,
editview: null,
inspect: null,
inspectTab: null,
})
);
// locationService.replace({ pathname: this._initialUrlState?.pathname, search: this._initialUrlState?.search });
// Update state and disable editing
this.setState({ ...this._initialState, isEditing: false });
// and start url sync again
@ -188,14 +205,14 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
};
public getPageNav(location: H.Location, navIndex: NavIndex) {
const { meta, viewPanelScene } = this.state;
const { meta, viewPanelScene, editPanel } = this.state;
let pageNav: NavModelItem = {
text: this.state.title,
url: getDashboardUrl({
uid: this.state.uid,
currentQueryParams: location.search,
updateQuery: { viewPanel: null, inspect: null, editview: null },
updateQuery: { viewPanel: null, inspect: null, editview: null, editPanel: null, tab: null },
}),
};
@ -220,6 +237,13 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
};
}
if (editPanel) {
pageNav = {
text: 'Edit panel',
parentItem: pageNav,
};
}
return pageNav;
}

@ -13,7 +13,7 @@ import { DashboardScene } from './DashboardScene';
import { NavToolbarActions } from './NavToolbarActions';
export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardScene>) {
const { controls, overlay, editview } = model.useState();
const { controls, overlay, editview, editPanel } = model.useState();
const styles = useStyles2(getStyles);
const location = useLocation();
const navIndex = useSelector((state) => state.navIndex);
@ -27,23 +27,26 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
return (
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Custom}>
<CustomScrollbar autoHeightMin={'100%'}>
<div className={styles.canvasContent}>
<NavToolbarActions dashboard={model} />
{editPanel && <editPanel.Component model={editPanel} />}
{!editPanel && (
<CustomScrollbar autoHeightMin={'100%'}>
<div className={styles.canvasContent}>
<NavToolbarActions dashboard={model} />
{controls && (
<div className={styles.controls}>
{controls.map((control) => (
<control.Component key={control.state.key} model={control} />
))}
<SceneDebugger scene={model} key={'scene-debugger'} />
{controls && (
<div className={styles.controls}>
{controls.map((control) => (
<control.Component key={control.state.key} model={control} />
))}
<SceneDebugger scene={model} key={'scene-debugger'} />
</div>
)}
<div className={cx(styles.body)}>
<bodyToRender.Component model={bodyToRender} />
</div>
)}
<div className={cx(styles.body)}>
<bodyToRender.Component model={bodyToRender} />
</div>
</div>
</CustomScrollbar>
</CustomScrollbar>
)}
{overlay && <overlay.Component model={overlay} />}
</Page>
);

@ -2,12 +2,13 @@ import { Unsubscribable } from 'rxjs';
import { AppEvents } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import { SceneObjectUrlSyncHandler, SceneObjectUrlValues } from '@grafana/scenes';
import { SceneObjectBase, SceneObjectState, SceneObjectUrlSyncHandler, SceneObjectUrlValues } from '@grafana/scenes';
import appEvents from 'app/core/app_events';
import { PanelInspectDrawer } from '../inspect/PanelInspectDrawer';
import { buildPanelEditScene } from '../panel-edit/PanelEditor';
import { createDashboardEditViewFor } from '../settings/utils';
import { findVizPanelByKey, isPanelClone } from '../utils/utils';
import { findVizPanelByKey, getDashboardSceneFor, isPanelClone } from '../utils/utils';
import { DashboardScene, DashboardSceneState } from './DashboardScene';
import { ViewPanelScene } from './ViewPanelScene';
@ -19,7 +20,7 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
constructor(private _scene: DashboardScene) {}
getKeys(): string[] {
return ['inspect', 'viewPanel', 'editview'];
return ['inspect', 'viewPanel', 'editPanel', 'editview'];
}
getUrlState(): SceneObjectUrlValues {
@ -28,11 +29,12 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
inspect: state.inspectPanelKey,
viewPanel: state.viewPanelScene?.getUrlKey(),
editview: state.editview?.getUrlKey(),
editPanel: state.editPanel?.getUrlKey() || undefined,
};
}
updateFromUrl(values: SceneObjectUrlValues): void {
const { inspectPanelKey, viewPanelScene, meta, isEditing } = this._scene.state;
const { inspectPanelKey, viewPanelScene, meta, isEditing, editPanel } = this._scene.state;
const update: Partial<DashboardSceneState> = {};
if (typeof values.editview === 'string' && meta.canEdit) {
@ -50,7 +52,7 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
// Handle inspect object state
if (typeof values.inspect === 'string') {
const panel = findVizPanelByKey(this._scene, values.inspect);
let panel = findVizPanelByKey(this._scene, values.inspect);
if (!panel) {
appEvents.emit(AppEvents.alertError, ['Panel not found']);
locationService.partial({ inspect: null });
@ -58,7 +60,9 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
}
update.inspectPanelKey = values.inspect;
update.overlay = new PanelInspectDrawer({ panelRef: panel.getRef() });
update.overlay = new PanelInspectDrawer({
$behaviors: [new ResolveInspectPanelByKey({ panelKey: values.inspect })],
});
} else if (inspectPanelKey) {
update.inspectPanelKey = undefined;
update.overlay = undefined;
@ -80,10 +84,26 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
}
update.viewPanelScene = new ViewPanelScene({ panelRef: panel.getRef() });
} else if (viewPanelScene) {
} else if (viewPanelScene && values.viewPanel === null) {
update.viewPanelScene = undefined;
}
// Handle edit panel state
if (typeof values.editPanel === 'string') {
const panel = findVizPanelByKey(this._scene, values.editPanel);
if (!panel) {
return;
}
// If we are not in editing (for example after full page reload)
if (!isEditing) {
this._scene.onEnterEditMode();
}
update.editPanel = buildPanelEditScene(panel);
} else if (editPanel && values.editPanel === null) {
update.editPanel = undefined;
}
if (Object.keys(update).length > 0) {
this._scene.setState(update);
}
@ -101,3 +121,41 @@ export class DashboardSceneUrlSync implements SceneObjectUrlSyncHandler {
}
}
}
interface ResolveInspectPanelByKeyState extends SceneObjectState {
panelKey: string;
}
class ResolveInspectPanelByKey extends SceneObjectBase<ResolveInspectPanelByKeyState> {
constructor(state: ResolveInspectPanelByKeyState) {
super(state);
this.addActivationHandler(this._onActivate);
}
private _onActivate = () => {
const parent = this.parent;
if (!parent || !(parent instanceof PanelInspectDrawer)) {
throw new Error('ResolveInspectPanelByKey must be attached to a PanelInspectDrawer');
}
const dashboard = getDashboardSceneFor(parent);
if (!dashboard) {
return;
}
const panelId = this.state.panelKey;
let panel = findVizPanelByKey(dashboard, panelId);
if (dashboard.state.editPanel) {
panel = dashboard.state.editPanel.state.panelRef.resolve().state.panel;
}
if (dashboard.state.viewPanelScene && dashboard.state.viewPanelScene.state.body) {
panel = dashboard.state.viewPanelScene.state.body;
}
if (panel) {
parent.setState({ panelRef: panel.getRef() });
}
};
}

@ -73,7 +73,7 @@ describe('panelMenuBehavior', () => {
// verify view panel url keeps url params and adds viewPanel=<panel-key>
expect(menu.state.items?.[0].href).toBe('/d/dash-1?from=now-5m&to=now&viewPanel=panel-12');
// verify edit url keeps url time range
expect(menu.state.items?.[1].href).toBe('/d/dash-1/panel-edit/12?from=now-5m&to=now');
expect(menu.state.items?.[1].href).toBe('/d/dash-1?from=now-5m&to=now&editPanel=12');
// verify share
expect(menu.state.items?.[2].text).toBe('Share');
// verify explore url

@ -17,7 +17,7 @@ import { addDataTrailPanelAction } from 'app/features/trails/dashboardIntegratio
import { ShareModal } from '../sharing/ShareModal';
import { DashboardInteractions } from '../utils/interactions';
import { getDashboardUrl, getInspectUrl, getViewPanelUrl, tryGetExploreUrlForPanel } from '../utils/urlBuilders';
import { getEditPanelUrl, getInspectUrl, getViewPanelUrl, tryGetExploreUrlForPanel } from '../utils/urlBuilders';
import { getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils';
import { DashboardScene } from './DashboardScene';
@ -34,7 +34,6 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
const panel = menu.parent as VizPanel;
const plugin = panel.getPlugin();
const location = locationService.getLocation();
const items: PanelMenuItem[] = [];
const moreSubMenu: PanelMenuItem[] = [];
const inspectSubMenu: PanelMenuItem[] = [];
@ -57,12 +56,8 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
text: t('panel.header-menu.edit', `Edit`),
iconClassName: 'eye',
shortcut: 'e',
onClick: () => () => DashboardInteractions.panelMenuItemClicked('edit'),
href: getDashboardUrl({
uid: dashboard.state.uid,
subPath: `/panel-edit/${panelId}`,
currentQueryParams: location.search,
}),
onClick: () => DashboardInteractions.panelMenuItemClicked('edit'),
href: getEditPanelUrl(panelId),
});
}

@ -5,7 +5,7 @@ import { KeybindingSet } from 'app/core/services/KeybindingSet';
import { ShareModal } from '../sharing/ShareModal';
import { dashboardSceneGraph } from '../utils/dashboardSceneGraph';
import { getDashboardUrl, getInspectUrl, getViewPanelUrl, tryGetExploreUrlForPanel } from '../utils/urlBuilders';
import { getEditPanelUrl, getInspectUrl, getViewPanelUrl, tryGetExploreUrlForPanel } from '../utils/urlBuilders';
import { getPanelIdForVizPanel } from '../utils/utils';
import { DashboardScene } from './DashboardScene';
@ -30,13 +30,9 @@ export function setupKeyboardShortcuts(scene: DashboardScene) {
const sceneRoot = vizPanel.getRoot();
if (sceneRoot instanceof DashboardScene) {
const panelId = getPanelIdForVizPanel(vizPanel);
locationService.push(
getDashboardUrl({
uid: sceneRoot.state.uid,
subPath: `/panel-edit/${panelId}`,
currentQueryParams: location.search,
})
);
if (!scene.state.editPanel) {
locationService.push(getEditPanelUrl(panelId));
}
}
}),
});

@ -67,8 +67,14 @@ export function getViewPanelUrl(vizPanel: VizPanel) {
return locationUtil.getUrlForPartial(locationService.getLocation(), { viewPanel: vizPanel.state.key });
}
export function getEditPanelUrl(panelId: number) {
return locationUtil.getUrlForPartial(locationService.getLocation(), { editPanel: panelId });
}
export function getInspectUrl(vizPanel: VizPanel, inspectTab?: InspectTab) {
return locationUtil.getUrlForPartial(locationService.getLocation(), { inspect: vizPanel.state.key, inspectTab });
const inspect = vizPanel.state.key?.replace('-view', '');
return locationUtil.getUrlForPartial(locationService.getLocation(), { inspect, inspectTab });
}
export function tryGetExploreUrlForPanel(vizPanel: VizPanel): Promise<string | undefined> {

@ -9,7 +9,6 @@ import {
} from '@grafana/scenes';
import { initialIntervalVariableModelState } from 'app/features/variables/interval/reducer';
import { PanelEditor } from '../panel-edit/PanelEditor';
import { DashboardScene } from '../scene/DashboardScene';
export function getVizPanelKeyForPanelId(panelId: number) {
@ -166,10 +165,6 @@ export function getQueryRunnerFor(sceneObject: SceneObject | undefined): SceneQu
export function getDashboardSceneFor(sceneObject: SceneObject): DashboardScene {
const root = sceneObject.getRoot();
if (root instanceof PanelEditor) {
return root.state.dashboardRef.resolve();
}
if (root instanceof DashboardScene) {
return root;
}

@ -8,7 +8,6 @@ import {
TestDataSourceResponse,
} from '@grafana/data';
import { SceneDataProvider, SceneDataTransformer, SceneObject } from '@grafana/scenes';
import { PanelEditor } from 'app/features/dashboard-scene/panel-edit/PanelEditor';
import {
findVizPanelByKey,
getQueryRunnerFor,
@ -84,13 +83,7 @@ export class DashboardDatasource extends DataSourceApi<DashboardQuery> {
}
private findSourcePanel(scene: SceneObject, panelId: number) {
let sceneToSearch = scene.getRoot();
if (sceneToSearch instanceof PanelEditor) {
sceneToSearch = sceneToSearch.state.dashboardRef.resolve();
}
return findVizPanelByKey(sceneToSearch, getVizPanelKeyForPanelId(panelId));
return findVizPanelByKey(scene, getVizPanelKeyForPanelId(panelId));
}
testDatasource(): Promise<TestDataSourceResponse> {

@ -38,12 +38,6 @@ export function getAppRoutes(): RouteDescriptor[] {
() => import(/* webpackChunkName: "DashboardPageProxy" */ '../features/dashboard/containers/DashboardPageProxy')
),
},
{
path: '/d/:uid/panel-edit/:panelId',
component: SafeDynamicImport(
() => import(/* webpackChunkName: "scenes"*/ 'app/features/dashboard-scene/pages/PanelEditPage')
),
},
{
path: '/d/:uid/:slug?',
pageClass: 'page-dashboard',

Loading…
Cancel
Save