diff --git a/public/app/features/dashboard-scene/inspect/PanelInspectDrawer.tsx b/public/app/features/dashboard-scene/inspect/PanelInspectDrawer.tsx index 30f4f93292e..3b957301c13 100644 --- a/public/app/features/dashboard-scene/inspect/PanelInspectDrawer.tsx +++ b/public/app/features/dashboard-scene/inspect/PanelInspectDrawer.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; + panelRef?: SceneObjectRef; pluginNotLoaded?: boolean; canEdit?: boolean; } @@ -47,8 +50,7 @@ export class PanelInspectDrawer extends SceneObjectBase */ 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 } } - 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, + }, + }) + ); }; } diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts index 971d0599a00..931417d1a10 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts @@ -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 { const fromCache = this.cache[uid]; if (fromCache) { diff --git a/public/app/features/dashboard-scene/pages/PanelEditPage.tsx b/public/app/features/dashboard-scene/pages/PanelEditPage.tsx deleted file mode 100644 index dc35c4fd807..00000000000 --- a/public/app/features/dashboard-scene/pages/PanelEditPage.tsx +++ /dev/null @@ -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 ( - - {isLoading && } - {loadError &&

{loadError}

} -
- ); - } - - return ; -} - -export default PanelEditPage; diff --git a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx index 61f4c955407..d58aaf303c9 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx @@ -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; - sourcePanelRef: SceneObjectRef; + panelId: number; panelRef: SceneObjectRef; } export class PanelEditor extends SceneObjectBase { 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 { }; 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 { 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({ diff --git a/public/app/features/dashboard-scene/panel-edit/PanelEditorRenderer.tsx b/public/app/features/dashboard-scene/panel-edit/PanelEditorRenderer.tsx index 681930cd583..9f479fe01b5 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelEditorRenderer.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelEditorRenderer.tsx @@ -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) { - 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 ( - + <>
{controls && ( @@ -34,8 +32,7 @@ export function PanelEditorRenderer({ model }: SceneComponentProps)
- {overlay && } -
+ ); } diff --git a/public/app/features/dashboard-scene/panel-edit/PanelEditorUrlSync.ts b/public/app/features/dashboard-scene/panel-edit/PanelEditorUrlSync.ts deleted file mode 100644 index d6b5d51e4eb..00000000000 --- a/public/app/features/dashboard-scene/panel-edit/PanelEditorUrlSync.ts +++ /dev/null @@ -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 = {}; - - // 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); - } - } -} diff --git a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx index 418b3a8dd82..ddfd3598680 100644 --- a/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/VizPanelManager.test.tsx @@ -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(); diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index e0ca58c59b9..f47b1460988 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -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 { 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 { }; 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 { }; } + if (editPanel) { + pageNav = { + text: 'Edit panel', + parentItem: pageNav, + }; + } + return pageNav; } diff --git a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx index 08f39caa97c..0f0403d61f9 100644 --- a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx @@ -13,7 +13,7 @@ import { DashboardScene } from './DashboardScene'; import { NavToolbarActions } from './NavToolbarActions'; export function DashboardSceneRenderer({ model }: SceneComponentProps) { - 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 - -
- + {editPanel && } + {!editPanel && ( + +
+ - {controls && ( -
- {controls.map((control) => ( - - ))} - + {controls && ( +
+ {controls.map((control) => ( + + ))} + +
+ )} +
+
- )} -
-
-
- + + )} {overlay && } ); diff --git a/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.ts b/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.ts index abd5539e57f..0297c4d54bf 100644 --- a/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.ts +++ b/public/app/features/dashboard-scene/scene/DashboardSceneUrlSync.ts @@ -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 = {}; 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 { + 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() }); + } + }; +} diff --git a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx index 1053fc43cc0..58472bc82e5 100644 --- a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx +++ b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx @@ -73,7 +73,7 @@ describe('panelMenuBehavior', () => { // verify view panel url keeps url params and adds viewPanel= 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 diff --git a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx index a7346644071..2f1e2a3a080 100644 --- a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx +++ b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx @@ -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), }); } diff --git a/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts b/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts index c3f0c6f1806..8273663b3a1 100644 --- a/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts +++ b/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts @@ -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)); + } } }), }); diff --git a/public/app/features/dashboard-scene/utils/urlBuilders.ts b/public/app/features/dashboard-scene/utils/urlBuilders.ts index 247ac35929b..f0777326813 100644 --- a/public/app/features/dashboard-scene/utils/urlBuilders.ts +++ b/public/app/features/dashboard-scene/utils/urlBuilders.ts @@ -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 { diff --git a/public/app/features/dashboard-scene/utils/utils.ts b/public/app/features/dashboard-scene/utils/utils.ts index b30d0a58752..a73a56e8e14 100644 --- a/public/app/features/dashboard-scene/utils/utils.ts +++ b/public/app/features/dashboard-scene/utils/utils.ts @@ -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; } diff --git a/public/app/plugins/datasource/dashboard/datasource.ts b/public/app/plugins/datasource/dashboard/datasource.ts index 9548f300b23..8bbda970969 100644 --- a/public/app/plugins/datasource/dashboard/datasource.ts +++ b/public/app/plugins/datasource/dashboard/datasource.ts @@ -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 { } 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 { diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx index 2255864ac83..73cb9df113b 100644 --- a/public/app/routes/routes.tsx +++ b/public/app/routes/routes.tsx @@ -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',