mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DashboardScene: Panel edit use new splitter and new conditional data and options pane logic (#82377)
Rebase
This commit is contained in:
@@ -144,7 +144,7 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
const currentTab = tabs.find((t) => t.tabId === tab);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.dataPane}>
|
||||
<TabsBar hideBorder={true} className={styles.tabsBar}>
|
||||
{tabs.map((t, index) => {
|
||||
return (
|
||||
@@ -161,12 +161,19 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
<Container>{currentTab && <currentTab.Component model={currentTab} />}</Container>
|
||||
</TabContent>
|
||||
</CustomScrollbar>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
dataPane: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
minHeight: 0,
|
||||
height: '100%',
|
||||
}),
|
||||
tabContent: css({
|
||||
padding: theme.spacing(2),
|
||||
border: `1px solid ${theme.colors.border.weak}`,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { PanelPlugin, PanelPluginMeta, PluginType } from '@grafana/data';
|
||||
import { SceneFlexItem, SceneGridItem, SceneGridLayout, SplitLayout, VizPanel } from '@grafana/scenes';
|
||||
import { SceneGridItem, SceneGridLayout, VizPanel } from '@grafana/scenes';
|
||||
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
import { activateFullSceneTree } from '../utils/test-utils';
|
||||
|
||||
import { PanelDataPane } from './PanelDataPane/PanelDataPane';
|
||||
import { buildPanelEditScene } from './PanelEditor';
|
||||
|
||||
let pluginToLoad: PanelPlugin | undefined;
|
||||
@@ -47,8 +46,7 @@ describe('PanelEditor', () => {
|
||||
|
||||
const deactivate = activateFullSceneTree(scene);
|
||||
|
||||
const vizManager = editScene.state.panelRef.resolve();
|
||||
vizManager.state.panel.setState({ title: 'changed title' });
|
||||
editScene.state.vizManager.state.panel.setState({ title: 'changed title' });
|
||||
|
||||
deactivate();
|
||||
|
||||
@@ -72,7 +70,7 @@ describe('PanelEditor', () => {
|
||||
|
||||
activateFullSceneTree(scene);
|
||||
|
||||
expect(((editScene.state.body as SplitLayout).state.primary as SplitLayout).state.secondary).toBeUndefined();
|
||||
expect(editScene.state.dataPane).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should exist if panel is supporting querying', () => {
|
||||
@@ -88,10 +86,7 @@ describe('PanelEditor', () => {
|
||||
});
|
||||
|
||||
activateFullSceneTree(scene);
|
||||
const secondaryPane = ((editScene.state.body as SplitLayout).state.primary as SplitLayout).state.secondary;
|
||||
|
||||
expect(secondaryPane).toBeInstanceOf(SceneFlexItem);
|
||||
expect((secondaryPane as SceneFlexItem).state.body).toBeInstanceOf(PanelDataPane);
|
||||
expect(editScene.state.dataPane).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,17 +2,7 @@ import * as H from 'history';
|
||||
|
||||
import { NavIndex } from '@grafana/data';
|
||||
import { config, locationService } from '@grafana/runtime';
|
||||
import {
|
||||
SceneFlexItem,
|
||||
SceneFlexLayout,
|
||||
SceneGridItem,
|
||||
SceneObject,
|
||||
SceneObjectBase,
|
||||
SceneObjectRef,
|
||||
SceneObjectState,
|
||||
SplitLayout,
|
||||
VizPanel,
|
||||
} from '@grafana/scenes';
|
||||
import { SceneGridItem, SceneObject, SceneObjectBase, SceneObjectState, VizPanel } from '@grafana/scenes';
|
||||
|
||||
import {
|
||||
findVizPanelByKey,
|
||||
@@ -27,11 +17,12 @@ import { PanelOptionsPane } from './PanelOptionsPane';
|
||||
import { VizPanelManager } from './VizPanelManager';
|
||||
|
||||
export interface PanelEditorState extends SceneObjectState {
|
||||
body: SceneObject;
|
||||
controls?: SceneObject[];
|
||||
isDirty?: boolean;
|
||||
panelId: number;
|
||||
panelRef: SceneObjectRef<VizPanelManager>;
|
||||
optionsPane?: PanelOptionsPane;
|
||||
dataPane?: PanelDataPane;
|
||||
vizManager: VizPanelManager;
|
||||
}
|
||||
|
||||
export class PanelEditor extends SceneObjectBase<PanelEditorState> {
|
||||
@@ -42,14 +33,41 @@ export class PanelEditor extends SceneObjectBase<PanelEditorState> {
|
||||
public constructor(state: PanelEditorState) {
|
||||
super(state);
|
||||
|
||||
this.addActivationHandler(() => {
|
||||
return () => {
|
||||
if (!this._discardChanges) {
|
||||
this.commitChanges();
|
||||
}
|
||||
};
|
||||
});
|
||||
this.addActivationHandler(this._activationHandler.bind(this));
|
||||
}
|
||||
|
||||
private _activationHandler() {
|
||||
const panelManager = this.state.vizManager;
|
||||
const panel = panelManager.state.panel;
|
||||
|
||||
this._subs.add(
|
||||
panelManager.subscribeToState((n, p) => {
|
||||
if (n.panel.state.pluginId !== p.panel.state.pluginId) {
|
||||
this._initDataPane(n.panel.state.pluginId);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this._initDataPane(panel.state.pluginId);
|
||||
|
||||
return () => {
|
||||
if (!this._discardChanges) {
|
||||
this.commitChanges();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private _initDataPane(pluginId: string) {
|
||||
const skipDataQuery = config.panels[pluginId].skipDataQuery;
|
||||
|
||||
if (!skipDataQuery && !this.state.dataPane) {
|
||||
this.setState({ dataPane: new PanelDataPane(this.state.vizManager) });
|
||||
} else if (this.state.dataPane) {
|
||||
locationService.partial({ tab: null }, true);
|
||||
this.setState({ dataPane: undefined });
|
||||
}
|
||||
}
|
||||
|
||||
public getUrlKey() {
|
||||
return this.state.panelId.toString();
|
||||
}
|
||||
@@ -76,10 +94,20 @@ export class PanelEditor extends SceneObjectBase<PanelEditorState> {
|
||||
dashboard.onEnterEditMode();
|
||||
}
|
||||
|
||||
const panelMngr = this.state.panelRef.resolve();
|
||||
|
||||
if (sourcePanel!.parent instanceof SceneGridItem) {
|
||||
sourcePanel!.parent.setState({ body: panelMngr.state.panel.clone() });
|
||||
sourcePanel!.parent.setState({ body: this.state.vizManager.state.panel.clone() });
|
||||
}
|
||||
}
|
||||
|
||||
public toggleOptionsPane(withOpenVizPicker?: boolean) {
|
||||
if (this.state.optionsPane) {
|
||||
this.setState({ optionsPane: undefined });
|
||||
} else {
|
||||
this.setState({
|
||||
optionsPane: new PanelOptionsPane({
|
||||
isVizPickerOpen: withOpenVizPicker,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,78 +118,7 @@ export function buildPanelEditScene(panel: VizPanel): PanelEditor {
|
||||
|
||||
return new PanelEditor({
|
||||
panelId: getPanelIdForVizPanel(panel),
|
||||
panelRef: vizPanelMgr.getRef(),
|
||||
body: new SplitLayout({
|
||||
direction: 'row',
|
||||
initialSize: 0.75,
|
||||
primary: new SplitLayout({
|
||||
direction: 'column',
|
||||
$behaviors: [conditionalDataPaneBehavior],
|
||||
primary: new SceneFlexLayout({
|
||||
direction: 'column',
|
||||
minHeight: 200,
|
||||
children: [vizPanelMgr],
|
||||
}),
|
||||
primaryPaneStyles: {
|
||||
minHeight: 0,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
secondaryPaneStyles: {
|
||||
minHeight: 0,
|
||||
},
|
||||
}),
|
||||
secondary: new SceneFlexItem({
|
||||
body: new PanelOptionsPane(vizPanelMgr),
|
||||
width: '100%',
|
||||
}),
|
||||
primaryPaneStyles: {
|
||||
minWidth: '0',
|
||||
},
|
||||
secondaryPaneStyles: {
|
||||
minWidth: '0',
|
||||
},
|
||||
}),
|
||||
optionsPane: new PanelOptionsPane({}),
|
||||
vizManager: vizPanelMgr,
|
||||
});
|
||||
}
|
||||
|
||||
// This function is used to conditionally add the data pane to the panel editor,
|
||||
// depending on the type of a panel being edited.
|
||||
function conditionalDataPaneBehavior(scene: SplitLayout) {
|
||||
const dashboard = getDashboardSceneFor(scene);
|
||||
|
||||
const editor = dashboard.state.editPanel;
|
||||
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const panelManager = editor.state.panelRef.resolve();
|
||||
const panel = panelManager.state.panel;
|
||||
|
||||
const getDataPane = () =>
|
||||
new SceneFlexItem({
|
||||
body: new PanelDataPane(panelManager),
|
||||
});
|
||||
|
||||
if (!config.panels[panel.state.pluginId].skipDataQuery) {
|
||||
scene.setState({
|
||||
secondary: getDataPane(),
|
||||
});
|
||||
}
|
||||
|
||||
const sub = panelManager.subscribeToState((n, p) => {
|
||||
const hadDataSupport = !config.panels[p.panel.state.pluginId].skipDataQuery;
|
||||
const willHaveDataSupport = !config.panels[n.panel.state.pluginId].skipDataQuery;
|
||||
|
||||
if (hadDataSupport && !willHaveDataSupport) {
|
||||
locationService.partial({ tab: null }, true);
|
||||
scene.setState({ secondary: undefined });
|
||||
} else if (!hadDataSupport && willHaveDataSupport) {
|
||||
scene.setState({ secondary: getDataPane() });
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
sub.unsubscribe();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,16 +3,17 @@ import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { SceneComponentProps } from '@grafana/scenes';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import { Splitter, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { NavToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { getDashboardSceneFor } from '../utils/utils';
|
||||
|
||||
import { PanelEditor } from './PanelEditor';
|
||||
import { VisualizationButton } from './PanelOptionsPane';
|
||||
|
||||
export function PanelEditorRenderer({ model }: SceneComponentProps<PanelEditor>) {
|
||||
const dashboard = getDashboardSceneFor(model);
|
||||
const { body } = model.useState();
|
||||
const { optionsPane, vizManager, dataPane } = model.useState();
|
||||
const { controls } = dashboard.useState();
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
@@ -25,10 +26,34 @@ export function PanelEditorRenderer({ model }: SceneComponentProps<PanelEditor>)
|
||||
{controls.map((control) => (
|
||||
<control.Component key={control.state.key} model={control} />
|
||||
))}
|
||||
{!optionsPane && (
|
||||
<VisualizationButton
|
||||
pluginId={vizManager.state.panel.state.pluginId}
|
||||
onOpen={() => model.toggleOptionsPane(true)}
|
||||
isOpen={false}
|
||||
onTogglePane={() => model.toggleOptionsPane()}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.body}>
|
||||
<body.Component model={body} />
|
||||
<Splitter
|
||||
direction="row"
|
||||
dragPosition="end"
|
||||
initialSize={0.75}
|
||||
primaryPaneStyles={{ paddingBottom: !dataPane ? 16 : 0 }}
|
||||
>
|
||||
<Splitter
|
||||
direction="column"
|
||||
primaryPaneStyles={{ minHeight: 0, paddingRight: !optionsPane ? 16 : 0 }}
|
||||
secondaryPaneStyles={{ minHeight: 0, overflow: 'hidden' }}
|
||||
dragPosition="start"
|
||||
>
|
||||
<vizManager.Component model={vizManager} />
|
||||
{dataPane && <dataPane.Component model={dataPane} />}
|
||||
</Splitter>
|
||||
{optionsPane && <optionsPane.Component model={optionsPane} />}
|
||||
</Splitter>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -11,8 +11,8 @@ import { getPanelFrameCategory2 } from 'app/features/dashboard/components/PanelE
|
||||
import { getVisualizationOptions2 } from 'app/features/dashboard/components/PanelEditor/getVisualizationOptions';
|
||||
import { getAllPanelPluginMeta } from 'app/features/panel/state/util';
|
||||
|
||||
import { PanelEditor } from './PanelEditor';
|
||||
import { PanelVizTypePicker } from './PanelVizTypePicker';
|
||||
import { VizPanelManager } from './VizPanelManager';
|
||||
|
||||
export interface PanelOptionsPaneState extends SceneObjectState {
|
||||
isVizPickerOpen?: boolean;
|
||||
@@ -21,15 +21,16 @@ export interface PanelOptionsPaneState extends SceneObjectState {
|
||||
}
|
||||
|
||||
export class PanelOptionsPane extends SceneObjectBase<PanelOptionsPaneState> {
|
||||
public panelManager: VizPanelManager;
|
||||
|
||||
public constructor(panelMgr: VizPanelManager) {
|
||||
public constructor(state: Partial<PanelOptionsPaneState>) {
|
||||
super({
|
||||
searchQuery: '',
|
||||
listMode: OptionFilter.All,
|
||||
...state,
|
||||
});
|
||||
}
|
||||
|
||||
this.panelManager = panelMgr;
|
||||
public getVizManager() {
|
||||
return sceneGraph.getAncestor(this, PanelEditor).state.vizManager;
|
||||
}
|
||||
|
||||
onToggleVizPicker = () => {
|
||||
@@ -44,11 +45,14 @@ export class PanelOptionsPane extends SceneObjectBase<PanelOptionsPaneState> {
|
||||
this.setState({ listMode });
|
||||
};
|
||||
|
||||
onCollapsePane = () => {};
|
||||
onCollapsePane = () => {
|
||||
const editor = sceneGraph.getAncestor(this, PanelEditor);
|
||||
editor.toggleOptionsPane();
|
||||
};
|
||||
|
||||
static Component = ({ model }: SceneComponentProps<PanelOptionsPane>) => {
|
||||
const { isVizPickerOpen, searchQuery, listMode } = model.useState();
|
||||
const { panelManager } = model;
|
||||
const panelManager = model.getVizManager();
|
||||
const { panel } = panelManager.state;
|
||||
const dataObject = sceneGraph.getData(panel);
|
||||
const { data } = dataObject.useState();
|
||||
|
||||
@@ -506,11 +506,9 @@ describe('VizPanelManager', () => {
|
||||
describe('change transformations', () => {
|
||||
it('should update and reprocess transformations', () => {
|
||||
const { scene, panel } = setupTest('panel-3');
|
||||
scene.setState({
|
||||
editPanel: buildPanelEditScene(panel),
|
||||
});
|
||||
scene.setState({ editPanel: buildPanelEditScene(panel) });
|
||||
|
||||
const vizPanelManager = scene.state.editPanel!.state.panelRef.resolve();
|
||||
const vizPanelManager = scene.state.editPanel!.state.vizManager;
|
||||
vizPanelManager.activate();
|
||||
vizPanelManager.state.panel.state.$data?.activate();
|
||||
|
||||
@@ -560,11 +558,9 @@ describe('VizPanelManager', () => {
|
||||
describe('dashboard queries', () => {
|
||||
it('should update queries', () => {
|
||||
const { scene, panel } = setupTest('panel-3');
|
||||
scene.setState({
|
||||
editPanel: buildPanelEditScene(panel),
|
||||
});
|
||||
scene.setState({ editPanel: buildPanelEditScene(panel) });
|
||||
|
||||
const vizPanelManager = scene.state.editPanel!.state.panelRef.resolve();
|
||||
const vizPanelManager = scene.state.editPanel!.state.vizManager;
|
||||
vizPanelManager.activate();
|
||||
vizPanelManager.state.panel.state.$data?.activate();
|
||||
|
||||
@@ -578,6 +574,7 @@ describe('VizPanelManager', () => {
|
||||
panelId: panelWithTransformations.id,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(vizPanelManager.panelData).toBeInstanceOf(SceneDataTransformer);
|
||||
expect(vizPanelManager.queryRunner.state.queries[0].panelId).toEqual(panelWithTransformations.id);
|
||||
|
||||
|
||||
@@ -71,8 +71,7 @@ describe('getSaveDashboardChange', () => {
|
||||
dashboard.onEnterEditMode();
|
||||
dashboard.setState({ editPanel: editScene });
|
||||
|
||||
const vizManager = editScene.state.panelRef.resolve();
|
||||
vizManager.state.panel.setState({ title: 'changed title' });
|
||||
editScene.state.vizManager.state.panel.setState({ title: 'changed title' });
|
||||
|
||||
const result = getSaveDashboardChange(dashboard, false, true);
|
||||
const panelSaveModel = result.changedSaveModel.panels![0];
|
||||
|
||||
@@ -147,7 +147,7 @@ class ResolveInspectPanelByKey extends SceneObjectBase<ResolveInspectPanelByKeyS
|
||||
let panel = findVizPanelByKey(dashboard, panelId);
|
||||
|
||||
if (dashboard.state.editPanel) {
|
||||
panel = dashboard.state.editPanel.state.panelRef.resolve().state.panel;
|
||||
panel = dashboard.state.editPanel.state.vizManager.state.panel;
|
||||
}
|
||||
|
||||
if (dashboard.state.viewPanelScene && dashboard.state.viewPanelScene.state.body) {
|
||||
|
||||
Reference in New Issue
Block a user