mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Flame Graph: Fix for dashboard scrolling (#56555)
* Flamegraph dash scrolling * Separate scroll for top table and for flame graph * Custom scroll behavior for explore vs vs dash etc and sticky flame graph header * Final UI tweaks * Update tests
This commit is contained in:
parent
f184f9211c
commit
5e27a6f276
@ -1,7 +1,7 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { DataFrame, GrafanaTheme2 } from '@grafana/data';
|
||||
import { DataFrame, GrafanaTheme2, CoreApp } from '@grafana/data';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
|
||||
import FlameGraphContainer from '../../plugins/panel/flamegraph/components/FlameGraphContainer';
|
||||
@ -15,7 +15,7 @@ export const FlameGraphExploreContainer = (props: Props) => {
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<FlameGraphContainer data={props.dataFrames[0]} />
|
||||
<FlameGraphContainer data={props.dataFrames[0]} app={CoreApp.Explore} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -24,7 +24,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
container: css`
|
||||
background: ${theme.colors.background.primary};
|
||||
display: flow-root;
|
||||
padding: ${theme.spacing(1)};
|
||||
padding: 0 ${theme.spacing(1)} ${theme.spacing(1)} ${theme.spacing(1)};
|
||||
border: 1px solid ${theme.components.panel.borderColor};
|
||||
border-radius: ${theme.shape.borderRadius(1)};
|
||||
`,
|
||||
|
@ -2,7 +2,7 @@ import { screen } from '@testing-library/dom';
|
||||
import { render } from '@testing-library/react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { DataFrameView, MutableDataFrame } from '@grafana/data';
|
||||
import { CoreApp, DataFrameView, MutableDataFrame } from '@grafana/data';
|
||||
|
||||
import { SelectedView } from '../types';
|
||||
|
||||
@ -33,6 +33,7 @@ describe('FlameGraph', () => {
|
||||
return (
|
||||
<FlameGraph
|
||||
data={flameGraphData}
|
||||
app={CoreApp.Explore}
|
||||
levels={levels}
|
||||
topLevelIndex={topLevelIndex}
|
||||
rangeMin={rangeMin}
|
||||
|
@ -20,7 +20,7 @@ import { css } from '@emotion/css';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useMeasure } from 'react-use';
|
||||
|
||||
import { DataFrame, FieldType } from '@grafana/data';
|
||||
import { CoreApp, DataFrame, FieldType } from '@grafana/data';
|
||||
|
||||
import { PIXELS_PER_LEVEL } from '../../constants';
|
||||
import { TooltipData, SelectedView } from '../types';
|
||||
@ -31,6 +31,8 @@ import { getBarX, getRectDimensionsForLevel, renderRect } from './rendering';
|
||||
|
||||
type Props = {
|
||||
data: DataFrame;
|
||||
app: CoreApp;
|
||||
flameGraphHeight?: number;
|
||||
levels: ItemWithStart[][];
|
||||
topLevelIndex: number;
|
||||
rangeMin: number;
|
||||
@ -45,6 +47,8 @@ type Props = {
|
||||
|
||||
const FlameGraph = ({
|
||||
data,
|
||||
app,
|
||||
flameGraphHeight,
|
||||
levels,
|
||||
topLevelIndex,
|
||||
rangeMin,
|
||||
@ -55,7 +59,7 @@ const FlameGraph = ({
|
||||
setRangeMax,
|
||||
selectedView,
|
||||
}: Props) => {
|
||||
const styles = getStyles(selectedView);
|
||||
const styles = getStyles(selectedView, app, flameGraphHeight);
|
||||
const totalTicks = data.fields[1].values.get(0);
|
||||
const valueField =
|
||||
data.fields.find((f) => f.name === 'value') ?? data.fields.find((f) => f.type === FieldType.number);
|
||||
@ -173,11 +177,15 @@ const FlameGraph = ({
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (selectedView: SelectedView) => ({
|
||||
const getStyles = (selectedView: SelectedView, app: CoreApp, flameGraphHeight: number | undefined) => ({
|
||||
graph: css`
|
||||
cursor: pointer;
|
||||
float: left;
|
||||
overflow: scroll;
|
||||
width: ${selectedView === SelectedView.FlameGraph ? '100%' : '50%'};
|
||||
${app !== CoreApp.Explore
|
||||
? `height: calc(${flameGraphHeight}px - 44px)`
|
||||
: ''}; // 44px to adjust for header pushing content down
|
||||
`,
|
||||
});
|
||||
|
||||
|
@ -2,7 +2,7 @@ import '@testing-library/jest-dom';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { MutableDataFrame } from '@grafana/data';
|
||||
import { CoreApp, MutableDataFrame } from '@grafana/data';
|
||||
|
||||
import { MIN_WIDTH_TO_SHOW_BOTH_TOPTABLE_AND_FLAMEGRAPH } from '../constants';
|
||||
|
||||
@ -29,7 +29,7 @@ describe('FlameGraphContainer', () => {
|
||||
},
|
||||
};
|
||||
|
||||
return <FlameGraphContainer data={flameGraphData} />;
|
||||
return <FlameGraphContainer data={flameGraphData} app={CoreApp.Explore} />;
|
||||
};
|
||||
|
||||
it('should render without error', async () => {
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useMeasure } from 'react-use';
|
||||
|
||||
import { DataFrame, DataFrameView } from '@grafana/data';
|
||||
import { DataFrame, DataFrameView, CoreApp } from '@grafana/data';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { MIN_WIDTH_TO_SHOW_BOTH_TOPTABLE_AND_FLAMEGRAPH } from '../constants';
|
||||
import { MIN_WIDTH_TO_SHOW_BOTH_TOPTABLE_AND_FLAMEGRAPH, PIXELS_PER_LEVEL } from '../constants';
|
||||
|
||||
import FlameGraph from './FlameGraph/FlameGraph';
|
||||
import { Item, nestedSetToLevels } from './FlameGraph/dataTransform';
|
||||
@ -13,6 +15,11 @@ import { SelectedView } from './types';
|
||||
|
||||
type Props = {
|
||||
data: DataFrame;
|
||||
app: CoreApp;
|
||||
// Height for flame graph when not used in explore.
|
||||
// This needs to be different to explore flame graph height as we
|
||||
// use panels with user adjustable heights in dashboards etc.
|
||||
flameGraphHeight?: number;
|
||||
};
|
||||
|
||||
const FlameGraphContainer = (props: Props) => {
|
||||
@ -34,6 +41,8 @@ const FlameGraphContainer = (props: Props) => {
|
||||
return nestedSetToLevels(dataView);
|
||||
}, [props.data]);
|
||||
|
||||
const styles = useStyles2(() => getStyles(props.app, PIXELS_PER_LEVEL * levels.length));
|
||||
|
||||
// If user resizes window with both as the selected view
|
||||
useEffect(() => {
|
||||
if (
|
||||
@ -46,8 +55,9 @@ const FlameGraphContainer = (props: Props) => {
|
||||
}, [selectedView, setSelectedView, containerWidth]);
|
||||
|
||||
return (
|
||||
<div ref={sizeRef}>
|
||||
<div ref={sizeRef} className={styles.container}>
|
||||
<FlameGraphHeader
|
||||
app={props.app}
|
||||
setTopLevelIndex={setTopLevelIndex}
|
||||
setRangeMin={setRangeMin}
|
||||
setRangeMax={setRangeMax}
|
||||
@ -61,6 +71,7 @@ const FlameGraphContainer = (props: Props) => {
|
||||
{selectedView !== SelectedView.FlameGraph && (
|
||||
<FlameGraphTopTableContainer
|
||||
data={props.data}
|
||||
app={props.app}
|
||||
totalLevels={levels.length}
|
||||
selectedView={selectedView}
|
||||
search={search}
|
||||
@ -74,6 +85,8 @@ const FlameGraphContainer = (props: Props) => {
|
||||
{selectedView !== SelectedView.TopTable && (
|
||||
<FlameGraph
|
||||
data={props.data}
|
||||
app={props.app}
|
||||
flameGraphHeight={props.flameGraphHeight}
|
||||
levels={levels}
|
||||
topLevelIndex={topLevelIndex}
|
||||
rangeMin={rangeMin}
|
||||
@ -89,4 +102,10 @@ const FlameGraphContainer = (props: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (app: CoreApp, height: number) => ({
|
||||
container: css`
|
||||
height: ${app === CoreApp.Explore ? height + 'px' : '100%'};
|
||||
`,
|
||||
});
|
||||
|
||||
export default FlameGraphContainer;
|
||||
|
@ -3,6 +3,8 @@ import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { CoreApp } from '@grafana/data';
|
||||
|
||||
import FlameGraphHeader from './FlameGraphHeader';
|
||||
import { SelectedView } from './types';
|
||||
|
||||
@ -13,6 +15,7 @@ describe('FlameGraphHeader', () => {
|
||||
|
||||
return (
|
||||
<FlameGraphHeader
|
||||
app={CoreApp.Explore}
|
||||
search={search}
|
||||
setSearch={setSearch}
|
||||
setTopLevelIndex={jest.fn()}
|
||||
|
@ -1,13 +1,15 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { Button, Input, useStyles, RadioButtonGroup } from '@grafana/ui';
|
||||
import { GrafanaTheme2, CoreApp } from '@grafana/data';
|
||||
import { Button, Input, RadioButtonGroup, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { MIN_WIDTH_TO_SHOW_BOTH_TOPTABLE_AND_FLAMEGRAPH } from '../constants';
|
||||
|
||||
import { SelectedView } from './types';
|
||||
|
||||
type Props = {
|
||||
app: CoreApp;
|
||||
search: string;
|
||||
setTopLevelIndex: (level: number) => void;
|
||||
setRangeMin: (range: number) => void;
|
||||
@ -19,6 +21,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const FlameGraphHeader = ({
|
||||
app,
|
||||
search,
|
||||
setTopLevelIndex,
|
||||
setRangeMin,
|
||||
@ -28,7 +31,7 @@ const FlameGraphHeader = ({
|
||||
setSelectedView,
|
||||
containerWidth,
|
||||
}: Props) => {
|
||||
const styles = useStyles(getStyles);
|
||||
const styles = useStyles2((theme) => getStyles(theme, app));
|
||||
|
||||
let viewOptions: Array<{ value: string; label: string; description: string }> = [
|
||||
{ value: SelectedView.TopTable, label: 'Top Table', description: 'Only show top table' },
|
||||
@ -83,11 +86,15 @@ const FlameGraphHeader = ({
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = () => ({
|
||||
const getStyles = (theme: GrafanaTheme2, app: CoreApp) => ({
|
||||
header: css`
|
||||
display: flow-root;
|
||||
padding: 0 0 20px 0;
|
||||
width: 100%;
|
||||
background: ${theme.colors.background.primary};
|
||||
top: 0;
|
||||
height: 50px;
|
||||
z-index: ${theme.zIndex.navbarFixed};
|
||||
${app === CoreApp.Explore ? 'position: sticky; margin-bottom: 8px; padding-top: 9px' : ''};
|
||||
`,
|
||||
inputContainer: css`
|
||||
float: left;
|
||||
|
@ -203,6 +203,11 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
& > :nth-child(3) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
// needed to keep header row height fixed so header row does not resize with browser
|
||||
& > :nth-child(3) {
|
||||
position: relative !important;
|
||||
}
|
||||
`,
|
||||
headerCell: css`
|
||||
background-color: ${theme.colors.background.secondary};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { DataFrameView, MutableDataFrame } from '@grafana/data';
|
||||
import { CoreApp, DataFrameView, MutableDataFrame } from '@grafana/data';
|
||||
|
||||
import { Item, nestedSetToLevels } from '../FlameGraph/dataTransform';
|
||||
import { data } from '../FlameGraph/testData/dataNestedSet';
|
||||
@ -21,6 +21,7 @@ describe('FlameGraphTopTableContainer', () => {
|
||||
return (
|
||||
<FlameGraphTopTableContainer
|
||||
data={flameGraphData}
|
||||
app={CoreApp.Explore}
|
||||
totalLevels={levels.length}
|
||||
selectedView={selectedView}
|
||||
search={search}
|
||||
|
@ -2,7 +2,7 @@ import { css } from '@emotion/css';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
|
||||
import { createTheme, DataFrame, Field, FieldType, getDisplayProcessor } from '@grafana/data';
|
||||
import { CoreApp, createTheme, DataFrame, Field, FieldType, getDisplayProcessor } from '@grafana/data';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { PIXELS_PER_LEVEL } from '../../constants';
|
||||
@ -12,6 +12,7 @@ import FlameGraphTopTable from './FlameGraphTopTable';
|
||||
|
||||
type Props = {
|
||||
data: DataFrame;
|
||||
app: CoreApp;
|
||||
totalLevels: number;
|
||||
selectedView: SelectedView;
|
||||
search: string;
|
||||
@ -23,6 +24,7 @@ type Props = {
|
||||
|
||||
const FlameGraphTopTableContainer = ({
|
||||
data,
|
||||
app,
|
||||
totalLevels,
|
||||
selectedView,
|
||||
search,
|
||||
@ -31,7 +33,7 @@ const FlameGraphTopTableContainer = ({
|
||||
setRangeMin,
|
||||
setRangeMax,
|
||||
}: Props) => {
|
||||
const styles = useStyles2(() => getStyles(selectedView));
|
||||
const styles = useStyles2(() => getStyles(selectedView, app));
|
||||
const [topTable, setTopTable] = useState<TopTableData[]>();
|
||||
const valueField =
|
||||
data.fields.find((f) => f.name === 'value') ?? data.fields.find((f) => f.type === FieldType.number);
|
||||
@ -122,7 +124,7 @@ const FlameGraphTopTableContainer = ({
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (selectedView: SelectedView) => {
|
||||
const getStyles = (selectedView: SelectedView, app: CoreApp) => {
|
||||
const marginRight = '20px';
|
||||
|
||||
return {
|
||||
@ -131,6 +133,7 @@ const getStyles = (selectedView: SelectedView) => {
|
||||
float: left;
|
||||
margin-right: ${marginRight};
|
||||
width: ${selectedView === SelectedView.TopTable ? '100%' : `calc(50% - ${marginRight})`};
|
||||
${app !== CoreApp.Explore ? 'height: calc(100% - 44px)' : ''}; // 44px to adjust for header pushing content down
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
import { PanelPlugin, PanelProps } from '@grafana/data';
|
||||
import { CoreApp, PanelPlugin, PanelProps } from '@grafana/data';
|
||||
|
||||
import FlameGraphContainer from './components/FlameGraphContainer';
|
||||
|
||||
export const FlameGraphPanel: React.FunctionComponent<PanelProps> = (props) => {
|
||||
return <FlameGraphContainer data={props.data.series[0]} />;
|
||||
return <FlameGraphContainer data={props.data.series[0]} app={CoreApp.Unknown} flameGraphHeight={props.height} />;
|
||||
};
|
||||
|
||||
export const plugin = new PanelPlugin(FlameGraphPanel);
|
||||
|
Loading…
Reference in New Issue
Block a user