FlameGraph: Simplify the data needed for context menu and item focusing (#69006)

This commit is contained in:
Andrej Ocenas 2023-05-25 18:26:14 +02:00 committed by GitHub
parent 81264e4a77
commit 4908045fc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 161 additions and 237 deletions

View File

@ -1,7 +1,7 @@
import { fireEvent, render, screen } from '@testing-library/react';
import React, { useState } from 'react';
import { CoreApp, createDataFrame } from '@grafana/data';
import { createDataFrame } from '@grafana/data';
import { SelectedView } from '../types';
@ -11,17 +11,20 @@ import { data } from './testData/dataNestedSet';
import 'jest-canvas-mock';
jest.mock('react-use', () => ({
useMeasure: () => {
const ref = React.useRef();
return [ref, { width: 1600 }];
},
}));
jest.mock('react-use', () => {
const reactUse = jest.requireActual('react-use');
return {
...reactUse,
useMeasure: () => {
const ref = React.useRef();
return [ref, { width: 1600 }];
},
};
});
describe('FlameGraph', () => {
const FlameGraphWithProps = () => {
const [topLevelIndex, setTopLevelIndex] = useState(0);
const [selectedBarIndex, setSelectedBarIndex] = useState(0);
const [focusedItemIndex, setFocusedItemIndex] = useState<number>();
const [rangeMin, setRangeMin] = useState(0);
const [rangeMax, setRangeMax] = useState(1);
const [search] = useState('');
@ -34,18 +37,17 @@ describe('FlameGraph', () => {
return (
<FlameGraph
data={container}
app={CoreApp.Explore}
levels={levels}
topLevelIndex={topLevelIndex}
selectedBarIndex={selectedBarIndex}
rangeMin={rangeMin}
rangeMax={rangeMax}
search={search}
setTopLevelIndex={setTopLevelIndex}
setSelectedBarIndex={setSelectedBarIndex}
setRangeMin={setRangeMin}
setRangeMax={setRangeMax}
selectedView={selectedView}
onItemFocused={(itemIndex) => {
setFocusedItemIndex(itemIndex);
}}
focusedItemIndex={focusedItemIndex}
textAlign={'left'}
/>
);

View File

@ -21,11 +21,10 @@ import uFuzzy from '@leeoniya/ufuzzy';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useMeasure } from 'react-use';
import { CoreApp } from '@grafana/data';
import { useStyles2 } from '@grafana/ui';
import { PIXELS_PER_LEVEL } from '../../constants';
import { SelectedView, ContextMenuData, TextAlign } from '../types';
import { SelectedView, ClickedItemData, TextAlign } from '../types';
import FlameGraphContextMenu from './FlameGraphContextMenu';
import FlameGraphMetadata from './FlameGraphMetadata';
@ -35,36 +34,30 @@ import { getBarX, getRectDimensionsForLevel, renderRect } from './rendering';
type Props = {
data: FlameGraphDataContainer;
app: CoreApp;
levels: LevelItem[][];
topLevelIndex: number;
selectedBarIndex: number;
rangeMin: number;
rangeMax: number;
search: string;
setTopLevelIndex: (level: number) => void;
setSelectedBarIndex: (bar: number) => void;
setRangeMin: (range: number) => void;
setRangeMax: (range: number) => void;
selectedView: SelectedView;
style?: React.CSSProperties;
onItemFocused: (itemIndex: number) => void;
focusedItemIndex?: number;
textAlign: TextAlign;
};
const FlameGraph = ({
data,
app,
levels,
topLevelIndex,
selectedBarIndex,
rangeMin,
rangeMax,
search,
setTopLevelIndex,
setSelectedBarIndex,
setRangeMin,
setRangeMax,
selectedView,
onItemFocused,
focusedItemIndex,
textAlign,
}: Props) => {
const styles = useStyles2(getStyles);
@ -74,7 +67,8 @@ const FlameGraph = ({
const graphRef = useRef<HTMLCanvasElement>(null);
const tooltipRef = useRef<HTMLDivElement>(null);
const [tooltipItem, setTooltipItem] = useState<LevelItem>();
const [contextMenuData, setContextMenuData] = useState<ContextMenuData>();
const [clickedItemData, setClickedItemData] = useState<ClickedItemData>();
const [ufuzzy] = useState(() => {
return new uFuzzy();
@ -120,22 +114,12 @@ const FlameGraph = ({
// sometimes we collapse multiple bars into single rect.
const dimensions = getRectDimensionsForLevel(data, level, levelIndex, totalTicks, rangeMin, pixelsPerTick);
for (const rect of dimensions) {
const focusedLevel = focusedItemIndex ? data.getLevel(focusedItemIndex) : 0;
// Render each rectangle based on the computed dimensions
renderRect(
ctx,
rect,
totalTicks,
rangeMin,
rangeMax,
search,
levelIndex,
topLevelIndex,
foundLabels,
textAlign
);
renderRect(ctx, rect, totalTicks, rangeMin, rangeMax, search, levelIndex, focusedLevel, foundLabels, textAlign);
}
}
}, [data, levels, wrapperWidth, totalTicks, rangeMin, rangeMax, search, topLevelIndex, foundLabels, textAlign]);
}, [data, levels, wrapperWidth, totalTicks, rangeMin, rangeMax, search, focusedItemIndex, foundLabels, textAlign]);
useEffect(() => {
if (graphRef.current) {
@ -153,15 +137,22 @@ const FlameGraph = ({
// if clicking on a block in the canvas
if (barIndex !== -1 && !isNaN(levelIndex) && !isNaN(barIndex)) {
setContextMenuData({ e, levelIndex, barIndex });
const item = levels[levelIndex][barIndex];
setClickedItemData({
posY: e.clientY,
posX: e.clientX,
itemIndex: item.itemIndex,
label: data.getLabel(item.itemIndex),
start: item.start,
});
} else {
// if clicking on the canvas but there is no block beneath the cursor
setContextMenuData(undefined);
setClickedItemData(undefined);
}
};
graphRef.current!.onmousemove = (e) => {
if (tooltipRef.current && contextMenuData === undefined) {
if (tooltipRef.current && clickedItemData === undefined) {
setTooltipItem(undefined);
const pixelsPerTick = graphRef.current!.clientWidth / totalTicks / (rangeMax - rangeMin);
const { levelIndex, barIndex } = convertPixelCoordinatesToBarCoordinates(
@ -197,16 +188,13 @@ const FlameGraph = ({
levels,
rangeMin,
rangeMax,
topLevelIndex,
totalTicks,
wrapperWidth,
setTopLevelIndex,
setRangeMin,
setRangeMax,
selectedView,
setSelectedBarIndex,
setContextMenuData,
contextMenuData,
setClickedItemData,
clickedItemData,
]);
// hide context menu if outside the flame graph canvas is clicked
@ -214,38 +202,31 @@ const FlameGraph = ({
const handleOnClick = (e: MouseEvent) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
if ((e.target as HTMLElement).parentElement?.id !== 'flameGraphCanvasContainer') {
setContextMenuData(undefined);
setClickedItemData(undefined);
}
};
window.addEventListener('click', handleOnClick);
return () => window.removeEventListener('click', handleOnClick);
}, [setContextMenuData]);
}, [setClickedItemData]);
return (
<div className={styles.graph} ref={sizeRef}>
<FlameGraphMetadata
data={data}
levels={levels}
topLevelIndex={topLevelIndex}
selectedBarIndex={selectedBarIndex}
totalTicks={totalTicks}
/>
<FlameGraphMetadata data={data} focusedItemIndex={focusedItemIndex} totalTicks={totalTicks} />
<div className={styles.canvasContainer} id="flameGraphCanvasContainer">
<canvas ref={graphRef} data-testid="flameGraph" />
</div>
<FlameGraphTooltip tooltipRef={tooltipRef} item={tooltipItem} data={data} totalTicks={totalTicks} />
{contextMenuData && (
{clickedItemData && (
<FlameGraphContextMenu
data={data}
contextMenuData={contextMenuData!}
levels={levels}
totalTicks={totalTicks}
graphRef={graphRef}
setContextMenuData={setContextMenuData}
setTopLevelIndex={setTopLevelIndex}
setSelectedBarIndex={setSelectedBarIndex}
setRangeMin={setRangeMin}
setRangeMax={setRangeMax}
itemData={clickedItemData}
onMenuItemClick={() => {
setClickedItemData(undefined);
}}
onItemFocus={(itemIndex) => {
setRangeMin(clickedItemData.start / totalTicks);
setRangeMax((clickedItemData.start + data.getValue(clickedItemData.itemIndex)) / totalTicks);
onItemFocused(itemIndex);
}}
/>
)}
</div>

View File

@ -2,78 +2,47 @@ import React from 'react';
import { MenuItem, ContextMenu } from '@grafana/ui';
import { ContextMenuData } from '../types';
import { FlameGraphDataContainer, LevelItem } from './dataTransform';
import { ClickedItemData } from '../types';
type Props = {
contextMenuData: ContextMenuData;
data: FlameGraphDataContainer;
levels: LevelItem[][];
totalTicks: number;
graphRef: React.RefObject<HTMLCanvasElement>;
setContextMenuData: (event: ContextMenuData | undefined) => void;
setTopLevelIndex: (level: number) => void;
setSelectedBarIndex: (bar: number) => void;
setRangeMin: (range: number) => void;
setRangeMax: (range: number) => void;
itemData: ClickedItemData;
onMenuItemClick: () => void;
onItemFocus: (itemIndex: number) => void;
};
const FlameGraphContextMenu = ({
contextMenuData,
graphRef,
totalTicks,
levels,
setContextMenuData,
setTopLevelIndex,
setSelectedBarIndex,
setRangeMin,
setRangeMax,
data,
}: Props) => {
const clickedItem = levels[contextMenuData.levelIndex][contextMenuData.barIndex];
const renderMenuItems = () => {
const FlameGraphContextMenu = ({ itemData, onMenuItemClick, onItemFocus }: Props) => {
function renderItems() {
return (
<>
<MenuItem
label="Focus block"
icon={'eye'}
onClick={() => {
if (graphRef.current && contextMenuData) {
setTopLevelIndex(contextMenuData.levelIndex);
setSelectedBarIndex(contextMenuData.barIndex);
setRangeMin(clickedItem.start / totalTicks);
setRangeMax((clickedItem.start + data.getValue(clickedItem.itemIndex)) / totalTicks);
setContextMenuData(undefined);
}
onItemFocus(itemData.itemIndex);
onMenuItemClick();
}}
/>
<MenuItem
label="Copy function name"
icon={'copy'}
onClick={() => {
if (graphRef.current && contextMenuData) {
navigator.clipboard.writeText(data.getLabel(clickedItem.itemIndex)).then(() => {
setContextMenuData(undefined);
});
}
navigator.clipboard.writeText(itemData.label).then(() => {
onMenuItemClick();
});
}}
/>
</>
);
};
}
return (
<div data-testid="contextMenu">
{contextMenuData.e.clientX && contextMenuData.e.clientY && (
<ContextMenu
renderMenuItems={() => renderMenuItems()}
x={contextMenuData.e.clientX + 10}
y={contextMenuData.e.clientY}
focusOnOpen={false}
></ContextMenu>
)}
<ContextMenu
renderMenuItems={renderItems}
x={itemData.posX + 10}
y={itemData.posY}
focusOnOpen={false}
></ContextMenu>
</div>
);
};

View File

@ -22,7 +22,7 @@ describe('should get metadata correctly', () => {
const container = new FlameGraphDataContainer(
makeDataFrame({ value: [1_624_078_250], level: [1], label: ['1'], self: [0] }, 'bytes')
);
const metadata = getMetadata(container, { itemIndex: 0, start: 0 }, 8_624_078_250);
const metadata = getMetadata(container, 0, 8_624_078_250);
expect(metadata).toEqual({
percentValue: 18.83,
unitTitle: 'RAM',
@ -35,7 +35,7 @@ describe('should get metadata correctly', () => {
const container = new FlameGraphDataContainer(
makeDataFrame({ value: [1_624_078_250], level: [1], label: ['1'], self: [0] }, 'none')
);
const metadata = getMetadata(container, { itemIndex: 0, start: 0 }, 8_624_078_250);
const metadata = getMetadata(container, 0, 8_624_078_250);
expect(metadata).toEqual({
percentValue: 18.83,
unitTitle: 'Count',
@ -48,7 +48,7 @@ describe('should get metadata correctly', () => {
const container = new FlameGraphDataContainer(
makeDataFrame({ value: [1_624_078_250], level: [1], label: ['1'], self: [0] })
);
const metadata = getMetadata(container, { itemIndex: 0, start: 0 }, 8_624_078_250);
const metadata = getMetadata(container, 0, 8_624_078_250);
expect(metadata).toEqual({
percentValue: 18.83,
unitTitle: 'Count',
@ -61,7 +61,7 @@ describe('should get metadata correctly', () => {
const container = new FlameGraphDataContainer(
makeDataFrame({ value: [1_624_078_250], level: [1], label: ['1'], self: [0] }, 'short')
);
const metadata = getMetadata(container, { itemIndex: 0, start: 0 }, 8_624_078_250);
const metadata = getMetadata(container, 0, 8_624_078_250);
expect(metadata).toEqual({
percentValue: 18.83,
unitTitle: 'Count',
@ -74,7 +74,7 @@ describe('should get metadata correctly', () => {
const container = new FlameGraphDataContainer(
makeDataFrame({ value: [1_624_078_250], level: [1], label: ['1'], self: [0] }, 'ns')
);
const metadata = getMetadata(container, { itemIndex: 0, start: 0 }, 8_624_078_250);
const metadata = getMetadata(container, 0, 8_624_078_250);
expect(metadata).toEqual({
percentValue: 18.83,
unitTitle: 'Time',

View File

@ -5,29 +5,23 @@ import { useStyles2 } from '@grafana/ui';
import { Metadata } from '../types';
import { FlameGraphDataContainer, LevelItem } from './dataTransform';
import { FlameGraphDataContainer } from './dataTransform';
type Props = {
data: FlameGraphDataContainer;
levels: LevelItem[][];
topLevelIndex: number;
selectedBarIndex: number;
totalTicks: number;
focusedItemIndex?: number;
};
const FlameGraphMetadata = React.memo(({ data, levels, topLevelIndex, selectedBarIndex, totalTicks }: Props) => {
const FlameGraphMetadata = React.memo(({ data, focusedItemIndex, totalTicks }: Props) => {
const styles = useStyles2(getStyles);
if (levels[topLevelIndex] && levels[topLevelIndex][selectedBarIndex]) {
const bar = levels[topLevelIndex][selectedBarIndex];
const metadata = getMetadata(data, bar, totalTicks);
const metadataText = `${metadata?.unitValue} (${metadata?.percentValue}%) of ${metadata?.samples} total samples (${metadata?.unitTitle})`;
return <>{<div className={styles.metadata}>{metadataText}</div>}</>;
}
return <></>;
const metadata = getMetadata(data, focusedItemIndex || 0, totalTicks);
const metadataText = `${metadata?.unitValue} (${metadata?.percentValue}%) of ${metadata?.samples} total samples (${metadata?.unitTitle})`;
return <>{<div className={styles.metadata}>{metadataText}</div>}</>;
});
export const getMetadata = (data: FlameGraphDataContainer, bar: LevelItem, totalTicks: number): Metadata => {
const displayValue = data.getValueDisplay(bar.itemIndex);
export const getMetadata = (data: FlameGraphDataContainer, itemIndex: number, totalTicks: number): Metadata => {
const displayValue = data.getValueDisplay(itemIndex);
const percentValue = Math.round(10000 * (displayValue.numeric / totalTicks)) / 100;
let unitValue = displayValue.text + displayValue.suffix;

View File

@ -21,6 +21,7 @@ describe('getRectDimensionsForLevel', () => {
{
width: 999,
height: 22,
itemIndex: 0,
x: 0,
y: 22,
collapsed: false,
@ -42,9 +43,29 @@ describe('getRectDimensionsForLevel', () => {
);
const result = getRectDimensionsForLevel(container, level, 2, 100, 0, 10);
expect(result).toEqual([
{ width: 999, height: 22, x: 0, y: 44, collapsed: false, ticks: 100, label: '1', unitLabel: '100' },
{ width: 499, height: 22, x: 1000, y: 44, collapsed: false, ticks: 50, label: '2', unitLabel: '50' },
{ width: 499, height: 22, x: 1500, y: 44, collapsed: false, ticks: 50, label: '3', unitLabel: '50' },
{ width: 999, height: 22, x: 0, y: 44, collapsed: false, ticks: 100, label: '1', unitLabel: '100', itemIndex: 0 },
{
width: 499,
height: 22,
x: 1000,
y: 44,
collapsed: false,
ticks: 50,
label: '2',
unitLabel: '50',
itemIndex: 1,
},
{
width: 499,
height: 22,
x: 1500,
y: 44,
collapsed: false,
ticks: 50,
label: '3',
unitLabel: '50',
itemIndex: 2,
},
]);
});
@ -59,8 +80,8 @@ describe('getRectDimensionsForLevel', () => {
);
const result = getRectDimensionsForLevel(container, level, 2, 100, 0, 1);
expect(result).toEqual([
{ width: 99, height: 22, x: 0, y: 44, collapsed: false, ticks: 100, label: '1', unitLabel: '100' },
{ width: 3, height: 22, x: 100, y: 44, collapsed: true, ticks: 3, label: '2', unitLabel: '2' },
{ width: 99, height: 22, x: 0, y: 44, collapsed: false, ticks: 100, label: '1', unitLabel: '100', itemIndex: 0 },
{ width: 3, height: 22, x: 100, y: 44, collapsed: true, ticks: 3, label: '2', unitLabel: '2', itemIndex: 1 },
]);
});
});

View File

@ -21,6 +21,7 @@ type RectData = {
ticks: number;
label: string;
unitLabel: string;
itemIndex: number;
};
/**
@ -67,6 +68,7 @@ export function getRectDimensionsForLevel(
ticks: curBarTicks,
label: data.getLabel(item.itemIndex),
unitLabel: unit,
itemIndex: item.itemIndex,
});
}
return coordinatesLevel;

View File

@ -3,6 +3,7 @@ import React, { useEffect, useMemo, useState } from 'react';
import { useMeasure } from 'react-use';
import { DataFrame, CoreApp, GrafanaTheme2 } from '@grafana/data';
import { config, reportInteraction } from '@grafana/runtime';
import { useStyles2, useTheme2 } from '@grafana/ui';
import { MIN_WIDTH_TO_SHOW_BOTH_TOPTABLE_AND_FLAMEGRAPH } from '../constants';
@ -19,8 +20,8 @@ type Props = {
};
const FlameGraphContainer = (props: Props) => {
const [topLevelIndex, setTopLevelIndex] = useState(0);
const [selectedBarIndex, setSelectedBarIndex] = useState(0);
const [focusedItemIndex, setFocusedItemIndex] = useState<number>();
const [rangeMin, setRangeMin] = useState(0);
const [rangeMax, setRangeMax] = useState(1);
const [search, setSearch] = useState('');
@ -56,8 +57,7 @@ const FlameGraphContainer = (props: Props) => {
}, [selectedView, setSelectedView, containerWidth]);
useEffect(() => {
setTopLevelIndex(0);
setSelectedBarIndex(0);
setFocusedItemIndex(undefined);
setRangeMin(0);
setRangeMax(1);
}, [props.data]);
@ -68,15 +68,16 @@ const FlameGraphContainer = (props: Props) => {
<div ref={sizeRef} className={styles.container}>
<FlameGraphHeader
app={props.app}
setTopLevelIndex={setTopLevelIndex}
setSelectedBarIndex={setSelectedBarIndex}
setRangeMin={setRangeMin}
setRangeMax={setRangeMax}
search={search}
setSearch={setSearch}
selectedView={selectedView}
setSelectedView={setSelectedView}
containerWidth={containerWidth}
onReset={() => {
setRangeMin(0);
setRangeMax(1);
setFocusedItemIndex(undefined);
}}
textAlign={textAlign}
onTextAlignChange={setTextAlign}
/>
@ -87,31 +88,35 @@ const FlameGraphContainer = (props: Props) => {
data={dataContainer}
app={props.app}
totalLevels={levels.length}
selectedView={selectedView}
search={search}
setSearch={setSearch}
setTopLevelIndex={setTopLevelIndex}
setSelectedBarIndex={setSelectedBarIndex}
setRangeMin={setRangeMin}
setRangeMax={setRangeMax}
onSymbolClick={(symbol) => {
if (search === symbol) {
setSearch('');
} else {
reportInteraction('grafana_flamegraph_table_item_selected', {
app: props.app,
grafana_version: config.buildInfo.version,
});
setSearch(symbol);
// Reset selected level in flamegraph when selecting row in top table
setRangeMin(0);
setRangeMax(1);
}
}}
/>
)}
{selectedView !== SelectedView.TopTable && (
<FlameGraph
data={dataContainer}
app={props.app}
levels={levels}
topLevelIndex={topLevelIndex}
selectedBarIndex={selectedBarIndex}
rangeMin={rangeMin}
rangeMax={rangeMax}
search={search}
setTopLevelIndex={setTopLevelIndex}
setSelectedBarIndex={setSelectedBarIndex}
setRangeMin={setRangeMin}
setRangeMax={setRangeMax}
selectedView={selectedView}
onItemFocused={(itemIndex) => setFocusedItemIndex(itemIndex)}
focusedItemIndex={focusedItemIndex}
textAlign={textAlign}
/>
)}

View File

@ -18,13 +18,12 @@ describe('FlameGraphHeader', () => {
app={CoreApp.Explore}
search={search}
setSearch={setSearch}
setTopLevelIndex={jest.fn()}
setSelectedBarIndex={jest.fn()}
setRangeMin={jest.fn()}
setRangeMax={jest.fn()}
selectedView={selectedView}
setSelectedView={setSelectedView}
containerWidth={1600}
onReset={() => {
setSearch('');
}}
onTextAlignChange={jest.fn()}
textAlign={'left'}
/>

View File

@ -15,14 +15,11 @@ import { SelectedView, TextAlign } from './types';
type Props = {
app: CoreApp;
search: string;
setTopLevelIndex: (level: number) => void;
setSelectedBarIndex: (bar: number) => void;
setRangeMin: (range: number) => void;
setRangeMax: (range: number) => void;
setSearch: (search: string) => void;
selectedView: SelectedView;
setSelectedView: (view: SelectedView) => void;
containerWidth: number;
onReset: () => void;
textAlign: TextAlign;
onTextAlignChange: (align: TextAlign) => void;
};
@ -30,14 +27,11 @@ type Props = {
const FlameGraphHeader = ({
app,
search,
setTopLevelIndex,
setSelectedBarIndex,
setRangeMin,
setRangeMax,
setSearch,
selectedView,
setSelectedView,
containerWidth,
onReset,
textAlign,
onTextAlignChange,
}: Props) => {
@ -50,16 +44,6 @@ const FlameGraphHeader = ({
});
}
const onResetView = () => {
setTopLevelIndex(0);
setSelectedBarIndex(0);
setRangeMin(0);
setRangeMax(1);
// We could set only one and wait them to sync but there is no need to debounce this.
setSearch('');
setLocalSearch('');
};
const [localSearch, setLocalSearch] = useSearchInput(search, setSearch);
return (
@ -75,7 +59,16 @@ const FlameGraphHeader = ({
width={44}
/>
</div>
<Button type={'button'} variant="secondary" onClick={onResetView}>
<Button
type={'button'}
variant="secondary"
onClick={() => {
onReset();
// We could set only one and wait them to sync but there is no need to debounce this.
setSearch('');
setLocalSearch('');
}}
>
Reset view
</Button>
</div>

View File

@ -1,19 +1,15 @@
import { render, screen } from '@testing-library/react';
import React, { useState } from 'react';
import React from 'react';
import { CoreApp, createDataFrame } from '@grafana/data';
import { FlameGraphDataContainer, nestedSetToLevels } from '../FlameGraph/dataTransform';
import { data } from '../FlameGraph/testData/dataNestedSet';
import { SelectedView } from '../types';
import FlameGraphTopTableContainer from './FlameGraphTopTableContainer';
describe('FlameGraphTopTableContainer', () => {
const FlameGraphTopTableContainerWithProps = () => {
const [search, setSearch] = useState('');
const [selectedView, _] = useState(SelectedView.Both);
const flameGraphData = createDataFrame(data);
const container = new FlameGraphDataContainer(flameGraphData);
const levels = nestedSetToLevels(container);
@ -23,13 +19,7 @@ describe('FlameGraphTopTableContainer', () => {
data={container}
app={CoreApp.Explore}
totalLevels={levels.length}
selectedView={selectedView}
search={search}
setSearch={setSearch}
setTopLevelIndex={jest.fn()}
setSelectedBarIndex={jest.fn()}
setRangeMin={jest.fn()}
setRangeMax={jest.fn()}
onSymbolClick={jest.fn()}
/>
);
};

View File

@ -8,52 +8,18 @@ import { Table, TableSortByFieldState, useStyles2 } from '@grafana/ui';
import { PIXELS_PER_LEVEL, TOP_TABLE_COLUMN_WIDTH } from '../../constants';
import { FlameGraphDataContainer } from '../FlameGraph/dataTransform';
import { SelectedView, TableData } from '../types';
import { TableData } from '../types';
type Props = {
data: FlameGraphDataContainer;
app: CoreApp;
totalLevels: number;
selectedView: SelectedView;
search: string;
setSearch: (search: string) => void;
setTopLevelIndex: (level: number) => void;
setSelectedBarIndex: (bar: number) => void;
setRangeMin: (range: number) => void;
setRangeMax: (range: number) => void;
onSymbolClick: (symbol: string) => void;
};
const FlameGraphTopTableContainer = ({
data,
app,
totalLevels,
selectedView,
search,
setSearch,
setTopLevelIndex,
setSelectedBarIndex,
setRangeMin,
setRangeMax,
}: Props) => {
const FlameGraphTopTableContainer = ({ data, app, totalLevels, onSymbolClick }: Props) => {
const styles = useStyles2(getStyles);
const onSymbolClick = (symbol: string) => {
if (search === symbol) {
setSearch('');
} else {
reportInteraction('grafana_flamegraph_table_item_selected', {
app,
grafana_version: config.buildInfo.version,
});
setSearch(symbol);
// Reset selected level in flamegraph when selecting row in top table
setTopLevelIndex(0);
setSelectedBarIndex(0);
setRangeMin(0);
setRangeMax(1);
}
};
const [sort, setSort] = useState<TableSortByFieldState[]>([{ displayName: 'Self', desc: true }]);
return (

View File

@ -1,7 +1,9 @@
export type ContextMenuData = {
e: MouseEvent;
levelIndex: number;
barIndex: number;
export type ClickedItemData = {
posX: number;
posY: number;
itemIndex: number;
label: string;
start: number;
};
export type Metadata = {