mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
parent
dd7259b77e
commit
1de876c354
@ -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…
Reference in New Issue
Block a user