mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Data Trails: Sticky main metric graph (#84389)
* WIP * Refactor code a bit so we can sticky the main graph and tabs * Make sure it works in Firefox. Avoid annoying warnings in breakdown tab. Update pin metrics graph label * Small copy change Co-authored-by: Darren Janeczek <38694490+darrenjaneczek@users.noreply.github.com> --------- Co-authored-by: Darren Janeczek <38694490+darrenjaneczek@users.noreply.github.com>
This commit is contained in:
parent
e394110f44
commit
6241386a96
@ -240,7 +240,8 @@ export function buildAllLayout(options: Array<SelectableValue<string>>, queryDef
|
||||
new SceneCSSGridLayout({
|
||||
templateColumns: '1fr',
|
||||
autoRows: '200px',
|
||||
children: children,
|
||||
// Clone children since a scene object can only have one parent at a time
|
||||
children: children.map((c) => c.clone()),
|
||||
}),
|
||||
],
|
||||
});
|
||||
@ -323,9 +324,7 @@ function getLabelValue(frame: DataFrame) {
|
||||
}
|
||||
|
||||
export function buildBreakdownActionScene() {
|
||||
return new SceneFlexItem({
|
||||
body: new BreakdownScene({}),
|
||||
});
|
||||
return new BreakdownScene({});
|
||||
}
|
||||
|
||||
interface SelectLabelActionState extends SceneObjectState {
|
||||
|
@ -3,7 +3,6 @@ import React from 'react';
|
||||
import {
|
||||
QueryVariable,
|
||||
SceneComponentProps,
|
||||
SceneFlexItem,
|
||||
sceneGraph,
|
||||
SceneObjectBase,
|
||||
SceneObjectState,
|
||||
@ -130,7 +129,5 @@ export class MetricOverviewScene extends SceneObjectBase<MetricOverviewSceneStat
|
||||
}
|
||||
|
||||
export function buildMetricOverviewScene() {
|
||||
return new SceneFlexItem({
|
||||
body: new MetricOverviewScene({}),
|
||||
});
|
||||
return new MetricOverviewScene({});
|
||||
}
|
||||
|
@ -1,9 +1,5 @@
|
||||
import { SceneFlexItem } from '@grafana/scenes';
|
||||
|
||||
import { MetricSelectScene } from '../MetricSelectScene';
|
||||
|
||||
export function buildRelatedMetricsScene() {
|
||||
return new SceneFlexItem({
|
||||
body: new MetricSelectScene({}),
|
||||
});
|
||||
return new MetricSelectScene({});
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { SceneObjectState, SceneObjectBase, SceneComponentProps, VizPanel, SceneQueryRunner } from '@grafana/scenes';
|
||||
import { Field, RadioButtonGroup, useStyles2, Stack } from '@grafana/ui';
|
||||
import { RadioButtonGroup } from '@grafana/ui';
|
||||
|
||||
import { trailDS } from '../shared';
|
||||
import { getMetricSceneFor, getTrailSettings } from '../utils';
|
||||
import { getMetricSceneFor } from '../utils';
|
||||
|
||||
import { AutoQueryDef } from './types';
|
||||
|
||||
@ -66,43 +65,10 @@ export class AutoVizPanel extends SceneObjectBase<AutoVizPanelState> {
|
||||
|
||||
public static Component = ({ model }: SceneComponentProps<AutoVizPanel>) => {
|
||||
const { panel } = model.useState();
|
||||
const { queryDef } = getMetricSceneFor(model).state;
|
||||
const { showQuery } = getTrailSettings(model).useState();
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
if (!panel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!showQuery) {
|
||||
return <panel.Component model={panel} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<Stack gap={2}>
|
||||
<Field label="Query">
|
||||
<div>{queryDef && queryDef.queries.map((query, index) => <div key={index}>{query.expr}</div>)}</div>
|
||||
</Field>
|
||||
</Stack>
|
||||
<div className={styles.panel}>
|
||||
<panel.Component model={panel} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function getStyles() {
|
||||
return {
|
||||
wrapper: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
}),
|
||||
panel: css({
|
||||
position: 'relative',
|
||||
flexGrow: 1,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
@ -172,7 +172,7 @@ export class DataTrail extends SceneObjectBase<DataTrailState> {
|
||||
}
|
||||
|
||||
static Component = ({ model }: SceneComponentProps<DataTrail>) => {
|
||||
const { controls, topScene, history } = model.useState();
|
||||
const { controls, topScene, history, settings } = model.useState();
|
||||
const styles = useStyles2(getStyles);
|
||||
const showHeaderForFirstTimeUsers = getTrailStore().recent.length < 2;
|
||||
|
||||
@ -185,6 +185,7 @@ export class DataTrail extends SceneObjectBase<DataTrailState> {
|
||||
{controls.map((control) => (
|
||||
<control.Component key={control.state.key} model={control} />
|
||||
))}
|
||||
<settings.Component model={settings} />
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.body}>{topScene && <topScene.Component model={topScene} />}</div>
|
||||
|
@ -6,31 +6,20 @@ import { SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana
|
||||
import { Dropdown, Switch, ToolbarButton, useStyles2 } from '@grafana/ui';
|
||||
|
||||
export interface DataTrailSettingsState extends SceneObjectState {
|
||||
showQuery?: boolean;
|
||||
showAdvanced?: boolean;
|
||||
multiValueVars?: boolean;
|
||||
stickyMainGraph?: boolean;
|
||||
isOpen?: boolean;
|
||||
}
|
||||
|
||||
export class DataTrailSettings extends SceneObjectBase<DataTrailSettingsState> {
|
||||
constructor(state: Partial<DataTrailSettingsState>) {
|
||||
super({
|
||||
showQuery: state.showQuery ?? false,
|
||||
showAdvanced: state.showAdvanced ?? false,
|
||||
stickyMainGraph: state.stickyMainGraph ?? true,
|
||||
isOpen: state.isOpen ?? false,
|
||||
});
|
||||
}
|
||||
|
||||
public onToggleShowQuery = () => {
|
||||
this.setState({ showQuery: !this.state.showQuery });
|
||||
};
|
||||
|
||||
public onToggleAdvanced = () => {
|
||||
this.setState({ showAdvanced: !this.state.showAdvanced });
|
||||
};
|
||||
|
||||
public onToggleMultiValue = () => {
|
||||
this.setState({ multiValueVars: !this.state.multiValueVars });
|
||||
public onToggleStickyMainGraph = () => {
|
||||
this.setState({ stickyMainGraph: !this.state.stickyMainGraph });
|
||||
};
|
||||
|
||||
public onToggleOpen = (isOpen: boolean) => {
|
||||
@ -38,7 +27,7 @@ export class DataTrailSettings extends SceneObjectBase<DataTrailSettingsState> {
|
||||
};
|
||||
|
||||
static Component = ({ model }: SceneComponentProps<DataTrailSettings>) => {
|
||||
const { showQuery, showAdvanced, multiValueVars, isOpen } = model.useState();
|
||||
const { stickyMainGraph, isOpen } = model.useState();
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const renderPopover = () => {
|
||||
@ -47,12 +36,8 @@ export class DataTrailSettings extends SceneObjectBase<DataTrailSettingsState> {
|
||||
<div className={styles.popover} onClick={(evt) => evt.stopPropagation()}>
|
||||
<div className={styles.heading}>Settings</div>
|
||||
<div className={styles.options}>
|
||||
<div>Multi value variables</div>
|
||||
<Switch value={multiValueVars} onChange={model.onToggleMultiValue} />
|
||||
<div>Advanced options</div>
|
||||
<Switch value={showAdvanced} onChange={model.onToggleAdvanced} />
|
||||
<div>Show query</div>
|
||||
<Switch value={showQuery} onChange={model.onToggleShowQuery} />
|
||||
<div>Always keep selected metric graph in-view</div>
|
||||
<Switch value={stickyMainGraph} onChange={model.onToggleStickyMainGraph} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
92
public/app/features/trails/MetricGraphScene.tsx
Normal file
92
public/app/features/trails/MetricGraphScene.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { DashboardCursorSync, GrafanaTheme2 } from '@grafana/data';
|
||||
import {
|
||||
behaviors,
|
||||
SceneComponentProps,
|
||||
SceneFlexItem,
|
||||
SceneFlexLayout,
|
||||
SceneObject,
|
||||
SceneObjectBase,
|
||||
SceneObjectState,
|
||||
} from '@grafana/scenes';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { AutoVizPanel } from './AutomaticMetricQueries/AutoVizPanel';
|
||||
import { MetricActionBar } from './MetricScene';
|
||||
import { getTrailSettings } from './utils';
|
||||
|
||||
export const MAIN_PANEL_MIN_HEIGHT = 280;
|
||||
export const MAIN_PANEL_MAX_HEIGHT = '40%';
|
||||
|
||||
export interface MetricGraphSceneState extends SceneObjectState {
|
||||
topView: SceneFlexLayout;
|
||||
selectedTab?: SceneObject;
|
||||
}
|
||||
|
||||
export class MetricGraphScene extends SceneObjectBase<MetricGraphSceneState> {
|
||||
public constructor(state: Partial<MetricGraphSceneState>) {
|
||||
super({
|
||||
topView: state.topView ?? buildGraphTopView(),
|
||||
...state,
|
||||
});
|
||||
}
|
||||
|
||||
public static Component = ({ model }: SceneComponentProps<MetricGraphScene>) => {
|
||||
const { topView, selectedTab } = model.useState();
|
||||
const { stickyMainGraph } = getTrailSettings(model).useState();
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={stickyMainGraph ? styles.sticky : styles.nonSticky}>
|
||||
<topView.Component model={topView} />
|
||||
</div>
|
||||
{selectedTab && <selectedTab.Component model={selectedTab} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
container: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
position: 'relative',
|
||||
}),
|
||||
sticky: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
background: theme.isLight ? theme.colors.background.primary : theme.colors.background.canvas,
|
||||
position: 'sticky',
|
||||
top: '70px',
|
||||
zIndex: 10,
|
||||
}),
|
||||
nonSticky: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function buildGraphTopView() {
|
||||
const bodyAutoVizPanel = new AutoVizPanel({});
|
||||
|
||||
return new SceneFlexLayout({
|
||||
direction: 'column',
|
||||
$behaviors: [new behaviors.CursorSync({ key: 'metricCrosshairSync', sync: DashboardCursorSync.Crosshair })],
|
||||
children: [
|
||||
new SceneFlexItem({
|
||||
minHeight: MAIN_PANEL_MIN_HEIGHT,
|
||||
maxHeight: MAIN_PANEL_MAX_HEIGHT,
|
||||
body: bodyAutoVizPanel,
|
||||
}),
|
||||
new SceneFlexItem({
|
||||
ySizing: 'content',
|
||||
body: new MetricActionBar({}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
@ -1,21 +1,18 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { DashboardCursorSync, GrafanaTheme2 } from '@grafana/data';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import {
|
||||
SceneObjectState,
|
||||
SceneObjectBase,
|
||||
SceneComponentProps,
|
||||
SceneFlexLayout,
|
||||
SceneFlexItem,
|
||||
SceneObjectUrlSyncConfig,
|
||||
SceneObjectUrlValues,
|
||||
sceneGraph,
|
||||
SceneVariableSet,
|
||||
QueryVariable,
|
||||
behaviors,
|
||||
} from '@grafana/scenes';
|
||||
import { ToolbarButton, Box, Stack, Icon, TabsBar, Tab, useStyles2 } from '@grafana/ui';
|
||||
import { ToolbarButton, Stack, Icon, TabsBar, Tab, useStyles2, Box } from '@grafana/ui';
|
||||
|
||||
import { getExploreUrl } from '../../core/utils/explore';
|
||||
|
||||
@ -23,8 +20,8 @@ import { buildBreakdownActionScene } from './ActionTabs/BreakdownScene';
|
||||
import { buildMetricOverviewScene } from './ActionTabs/MetricOverviewScene';
|
||||
import { buildRelatedMetricsScene } from './ActionTabs/RelatedMetricsScene';
|
||||
import { getAutoQueriesForMetric } from './AutomaticMetricQueries/AutoQueryEngine';
|
||||
import { AutoVizPanel } from './AutomaticMetricQueries/AutoVizPanel';
|
||||
import { AutoQueryDef, AutoQueryInfo } from './AutomaticMetricQueries/types';
|
||||
import { MAIN_PANEL_MAX_HEIGHT, MAIN_PANEL_MIN_HEIGHT, MetricGraphScene } from './MetricGraphScene';
|
||||
import { ShareTrailButton } from './ShareTrailButton';
|
||||
import { useBookmarkState } from './TrailStore/useBookmarkState';
|
||||
import {
|
||||
@ -40,7 +37,7 @@ import {
|
||||
import { getDataSource, getTrailFor } from './utils';
|
||||
|
||||
export interface MetricSceneState extends SceneObjectState {
|
||||
body: SceneFlexLayout;
|
||||
body: MetricGraphScene;
|
||||
metric: string;
|
||||
actionView?: string;
|
||||
|
||||
@ -55,7 +52,7 @@ export class MetricScene extends SceneObjectBase<MetricSceneState> {
|
||||
const autoQuery = state.autoQuery ?? getAutoQueriesForMetric(state.metric);
|
||||
super({
|
||||
$variables: state.$variables ?? getVariableSet(state.metric),
|
||||
body: state.body ?? buildGraphScene(),
|
||||
body: state.body ?? new MetricGraphScene({}),
|
||||
autoQuery,
|
||||
queryDef: state.queryDef ?? autoQuery.main,
|
||||
...state,
|
||||
@ -93,13 +90,13 @@ export class MetricScene extends SceneObjectBase<MetricSceneState> {
|
||||
|
||||
if (actionViewDef && actionViewDef.value !== this.state.actionView) {
|
||||
// reduce max height for main panel to reduce height flicker
|
||||
body.state.children[0].setState({ maxHeight: MAIN_PANEL_MIN_HEIGHT });
|
||||
body.setState({ children: [...body.state.children.slice(0, 2), actionViewDef.getScene()] });
|
||||
body.state.topView.state.children[0].setState({ maxHeight: MAIN_PANEL_MIN_HEIGHT });
|
||||
body.setState({ selectedTab: actionViewDef.getScene() });
|
||||
this.setState({ actionView: actionViewDef.value });
|
||||
} else {
|
||||
// restore max height
|
||||
body.state.children[0].setState({ maxHeight: MAIN_PANEL_MAX_HEIGHT });
|
||||
body.setState({ children: body.state.children.slice(0, 2) });
|
||||
body.state.topView.state.children[0].setState({ maxHeight: MAIN_PANEL_MAX_HEIGHT });
|
||||
body.setState({ selectedTab: undefined });
|
||||
this.setState({ actionView: undefined });
|
||||
}
|
||||
}
|
||||
@ -208,6 +205,7 @@ function getStyles(theme: GrafanaTheme2) {
|
||||
[theme.breakpoints.up(theme.breakpoints.values.md)]: {
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 16,
|
||||
zIndex: 2,
|
||||
},
|
||||
}),
|
||||
@ -231,26 +229,3 @@ function getVariableSet(metric: string) {
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
const MAIN_PANEL_MIN_HEIGHT = 280;
|
||||
const MAIN_PANEL_MAX_HEIGHT = '40%';
|
||||
|
||||
function buildGraphScene() {
|
||||
const bodyAutoVizPanel = new AutoVizPanel({});
|
||||
|
||||
return new SceneFlexLayout({
|
||||
direction: 'column',
|
||||
$behaviors: [new behaviors.CursorSync({ key: 'metricCrosshairSync', sync: DashboardCursorSync.Crosshair })],
|
||||
children: [
|
||||
new SceneFlexItem({
|
||||
minHeight: MAIN_PANEL_MIN_HEIGHT,
|
||||
maxHeight: MAIN_PANEL_MAX_HEIGHT,
|
||||
body: bodyAutoVizPanel,
|
||||
}),
|
||||
new SceneFlexItem({
|
||||
ySizing: 'content',
|
||||
body: new MetricActionBar({}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user