Flamegraph: Fix theme propagation (#76064)

This commit is contained in:
Andrej Ocenas 2023-10-10 10:57:39 +02:00 committed by GitHub
parent f41565626a
commit 9ec61f5570
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 93 additions and 129 deletions

View File

@ -1,7 +1,7 @@
import { fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import { createDataFrame, createTheme } from '@grafana/data';
import { createDataFrame } from '@grafana/data';
import { ColorScheme } from '../types';
@ -48,7 +48,6 @@ describe('FlameGraph', () => {
onFocusPillClick={onFocusPillClick}
onSandwichPillClick={onSandwichPillClick}
colorScheme={ColorScheme.ValueBased}
getTheme={() => createTheme({ colors: { mode: 'dark' } })}
/>
);
return {

View File

@ -20,7 +20,6 @@ import { css } from '@emotion/css';
import React, { MouseEvent as ReactMouseEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useMeasure } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
import { Icon } from '@grafana/ui';
import { PIXELS_PER_LEVEL } from '../constants';
@ -48,7 +47,6 @@ type Props = {
onFocusPillClick: () => void;
onSandwichPillClick: () => void;
colorScheme: ColorScheme | ColorSchemeDiff;
getTheme: () => GrafanaTheme2;
};
const FlameGraph = ({
@ -66,7 +64,6 @@ const FlameGraph = ({
onFocusPillClick,
onSandwichPillClick,
colorScheme,
getTheme,
}: Props) => {
const styles = getStyles();
@ -108,7 +105,6 @@ const FlameGraph = ({
totalColorTicks: data.isDiffFlamegraph() ? totalProfileTicks : totalViewTicks,
totalTicksRight: totalProfileTicksRight,
wrapperWidth,
getTheme,
});
const onGraphClick = useCallback(
@ -186,7 +182,6 @@ const FlameGraph = ({
return (
<div className={styles.graph}>
<FlameGraphMetadata
getTheme={getTheme}
data={data}
focusedItem={focusedItemData}
sandwichedLabel={sandwichItem}
@ -220,13 +215,7 @@ const FlameGraph = ({
/>
</div>
</div>
<FlameGraphTooltip
getTheme={getTheme}
position={mousePosition}
item={tooltipItem}
data={data}
totalTicks={totalViewTicks}
/>
<FlameGraphTooltip position={mousePosition} item={tooltipItem} data={data} totalTicks={totalViewTicks} />
{clickedItemData && (
<FlameGraphContextMenu
itemData={clickedItemData}

View File

@ -2,8 +2,6 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { createTheme } from '@grafana/data';
import FlameGraphMetadata from './FlameGraphMetadata';
import { textToDataContainer } from './testHelpers';
@ -23,7 +21,6 @@ function setup(props: Partial<React.ComponentProps<typeof FlameGraphMetadata>> =
totalTicks={17}
onFocusPillClick={onFocusPillClick}
onSandwichPillClick={onSandwichPillClick}
getTheme={() => createTheme({ colors: { mode: 'dark' } })}
{...props}
/>
);

View File

@ -2,7 +2,7 @@ import { css } from '@emotion/css';
import React, { ReactNode } from 'react';
import { getValueFormat, GrafanaTheme2 } from '@grafana/data';
import { Icon, IconButton } from '@grafana/ui';
import { Icon, IconButton, useStyles2 } from '@grafana/ui';
import { ClickedItemData } from '../types';
@ -15,12 +15,11 @@ type Props = {
onSandwichPillClick: () => void;
focusedItem?: ClickedItemData;
sandwichedLabel?: string;
getTheme: () => GrafanaTheme2;
};
const FlameGraphMetadata = React.memo(
({ data, focusedItem, totalTicks, sandwichedLabel, onFocusPillClick, onSandwichPillClick, getTheme }: Props) => {
const styles = getStyles(getTheme());
({ data, focusedItem, totalTicks, sandwichedLabel, onFocusPillClick, onSandwichPillClick }: Props) => {
const styles = useStyles2(getStyles);
const parts: ReactNode[] = [];
const ticksVal = getValueFormat('short')(totalTicks);

View File

@ -2,7 +2,7 @@ import { css } from '@emotion/css';
import React from 'react';
import { DisplayValue, getValueFormat, GrafanaTheme2 } from '@grafana/data';
import { InteractiveTable, Portal, VizTooltipContainer } from '@grafana/ui';
import { InteractiveTable, Portal, useStyles2, VizTooltipContainer } from '@grafana/ui';
import { FlameGraphDataContainer, LevelItem } from './dataTransform';
@ -11,11 +11,10 @@ type Props = {
totalTicks: number;
position?: { x: number; y: number };
item?: LevelItem;
getTheme: () => GrafanaTheme2;
};
const FlameGraphTooltip = ({ data, item, totalTicks, position, getTheme }: Props) => {
const styles = getStyles(getTheme());
const FlameGraphTooltip = ({ data, item, totalTicks, position }: Props) => {
const styles = useStyles2(getStyles);
if (!(item && position)) {
return null;

View File

@ -3,6 +3,7 @@ import { RefObject, useEffect, useMemo, useState } from 'react';
import color from 'tinycolor2';
import { GrafanaTheme2 } from '@grafana/data';
import { useTheme2 } from '@grafana/ui';
import {
BAR_BORDER_WIDTH,
@ -41,7 +42,6 @@ type RenderOptions = {
totalTicksRight: number | undefined;
colorScheme: ColorScheme | ColorSchemeDiff;
focusedItemData?: ClickedItemData;
getTheme: () => GrafanaTheme2;
};
export function useFlameRender(options: RenderOptions) {
@ -59,7 +59,6 @@ export function useFlameRender(options: RenderOptions) {
totalTicksRight,
colorScheme,
focusedItemData,
getTheme,
} = options;
const foundLabels = useMemo(() => {
if (search) {
@ -79,7 +78,7 @@ export function useFlameRender(options: RenderOptions) {
}, [search, data]);
const ctx = useSetupCanvas(canvasRef, wrapperWidth, levels.length);
const theme = getTheme();
const theme = useTheme2();
useEffect(() => {
if (!ctx) {

View File

@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useMeasure } from 'react-use';
import { DataFrame, GrafanaTheme2 } from '@grafana/data';
import { ThemeContext } from '@grafana/ui';
import FlameGraph from './FlameGraph/FlameGraph';
import { FlameGraphDataContainer } from './FlameGraph/dataTransform';
@ -133,75 +134,76 @@ const FlameGraphContainer = ({
}
return (
<div ref={sizeRef} className={styles.container}>
<FlameGraphHeader
search={search}
setSearch={setSearch}
selectedView={selectedView}
setSelectedView={(view) => {
setSelectedView(view);
onViewSelected?.(view);
}}
containerWidth={containerWidth}
onReset={() => {
resetFocus();
resetSandwich();
}}
textAlign={textAlign}
onTextAlignChange={(align) => {
setTextAlign(align);
onTextAlignSelected?.(align);
}}
showResetButton={Boolean(focusedItemData || sandwichItem)}
colorScheme={colorScheme}
onColorSchemeChange={setColorScheme}
stickyHeader={Boolean(stickyHeader)}
extraHeaderElements={extraHeaderElements}
vertical={vertical}
isDiffMode={Boolean(dataContainer.isDiffFlamegraph())}
getTheme={getTheme}
/>
// We add the theme context to bridge the gap if this is rendered in non grafana environment where the context
// isn't already provided.
<ThemeContext.Provider value={theme}>
<div ref={sizeRef} className={styles.container}>
<FlameGraphHeader
search={search}
setSearch={setSearch}
selectedView={selectedView}
setSelectedView={(view) => {
setSelectedView(view);
onViewSelected?.(view);
}}
containerWidth={containerWidth}
onReset={() => {
resetFocus();
resetSandwich();
}}
textAlign={textAlign}
onTextAlignChange={(align) => {
setTextAlign(align);
onTextAlignSelected?.(align);
}}
showResetButton={Boolean(focusedItemData || sandwichItem)}
colorScheme={colorScheme}
onColorSchemeChange={setColorScheme}
stickyHeader={Boolean(stickyHeader)}
extraHeaderElements={extraHeaderElements}
vertical={vertical}
isDiffMode={Boolean(dataContainer.isDiffFlamegraph())}
/>
<div className={styles.body}>
{selectedView !== SelectedView.FlameGraph && (
<FlameGraphTopTableContainer
data={dataContainer}
onSymbolClick={onSymbolClick}
height={selectedView === SelectedView.TopTable ? 600 : undefined}
search={search}
sandwichItem={sandwichItem}
onSandwich={setSandwichItem}
onSearch={setSearch}
onTableSort={onTableSort}
getTheme={getTheme}
vertical={vertical}
/>
)}
<div className={styles.body}>
{selectedView !== SelectedView.FlameGraph && (
<FlameGraphTopTableContainer
data={dataContainer}
onSymbolClick={onSymbolClick}
height={selectedView === SelectedView.TopTable ? 600 : undefined}
search={search}
sandwichItem={sandwichItem}
onSandwich={setSandwichItem}
onSearch={setSearch}
onTableSort={onTableSort}
vertical={vertical}
/>
)}
{selectedView !== SelectedView.TopTable && (
<FlameGraph
getTheme={getTheme}
data={dataContainer}
rangeMin={rangeMin}
rangeMax={rangeMax}
search={search}
setRangeMin={setRangeMin}
setRangeMax={setRangeMax}
onItemFocused={(data) => setFocusedItemData(data)}
focusedItemData={focusedItemData}
textAlign={textAlign}
sandwichItem={sandwichItem}
onSandwich={(label: string) => {
resetFocus();
setSandwichItem(label);
}}
onFocusPillClick={resetFocus}
onSandwichPillClick={resetSandwich}
colorScheme={colorScheme}
/>
)}
{selectedView !== SelectedView.TopTable && (
<FlameGraph
data={dataContainer}
rangeMin={rangeMin}
rangeMax={rangeMax}
search={search}
setRangeMin={setRangeMin}
setRangeMax={setRangeMax}
onItemFocused={(data) => setFocusedItemData(data)}
focusedItemData={focusedItemData}
textAlign={textAlign}
sandwichItem={sandwichItem}
onSandwich={(label: string) => {
resetFocus();
setSandwichItem(label);
}}
onFocusPillClick={resetFocus}
onSandwichPillClick={resetSandwich}
colorScheme={colorScheme}
/>
)}
</div>
</div>
</div>
</ThemeContext.Provider>
);
};

View File

@ -3,8 +3,6 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { createTheme } from '@grafana/data';
import FlameGraphHeader from './FlameGraphHeader';
import { ColorScheme, SelectedView } from './types';
@ -28,7 +26,6 @@ describe('FlameGraphHeader', () => {
showResetButton={true}
colorScheme={ColorScheme.ValueBased}
onColorSchemeChange={onSchemeChange}
getTheme={() => createTheme({ colors: { mode: 'dark' } })}
stickyHeader={false}
isDiffMode={false}
{...props}

View File

@ -4,7 +4,7 @@ import useDebounce from 'react-use/lib/useDebounce';
import usePrevious from 'react-use/lib/usePrevious';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { Button, Dropdown, Input, Menu, RadioButtonGroup } from '@grafana/ui';
import { Button, Dropdown, Input, Menu, RadioButtonGroup, useStyles2 } from '@grafana/ui';
import { byPackageGradient, byValueGradient, diffColorBlindGradient, diffDefaultGradient } from './FlameGraph/colors';
import { MIN_WIDTH_TO_SHOW_BOTH_TOPTABLE_AND_FLAMEGRAPH } from './constants';
@ -25,7 +25,6 @@ type Props = {
stickyHeader: boolean;
vertical?: boolean;
isDiffMode: boolean;
getTheme: () => GrafanaTheme2;
extraHeaderElements?: React.ReactNode;
};
@ -46,9 +45,8 @@ const FlameGraphHeader = ({
extraHeaderElements,
vertical,
isDiffMode,
getTheme,
}: Props) => {
const styles = getStyles(getTheme(), stickyHeader);
const styles = useStyles2(getStyles, stickyHeader);
const [localSearch, setLocalSearch] = useSearchInput(search, setSearch);
const suffix =
@ -95,12 +93,7 @@ const FlameGraphHeader = ({
aria-label={'Reset focus and sandwich state'}
/>
)}
<ColorSchemeButton
value={colorScheme}
onChange={onColorSchemeChange}
isDiffMode={isDiffMode}
getTheme={getTheme}
/>
<ColorSchemeButton value={colorScheme} onChange={onColorSchemeChange} isDiffMode={isDiffMode} />
<RadioButtonGroup<TextAlign>
size="sm"
disabled={selectedView === SelectedView.TopTable}
@ -124,12 +117,11 @@ const FlameGraphHeader = ({
type ColorSchemeButtonProps = {
value: ColorScheme | ColorSchemeDiff;
onChange: (colorScheme: ColorScheme | ColorSchemeDiff) => void;
getTheme: () => GrafanaTheme2;
isDiffMode: boolean;
};
function ColorSchemeButton(props: ColorSchemeButtonProps) {
// TODO: probably create separate getStyles
const styles = getStyles(props.getTheme(), false);
const styles = useStyles2(getStyles, false);
let menu = (
<Menu>
<Menu.Item label="By package name" onClick={() => props.onChange(ColorScheme.PackageBased)} />

View File

@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react';
import userEvents from '@testing-library/user-event';
import React from 'react';
import { createDataFrame, createTheme } from '@grafana/data';
import { createDataFrame } from '@grafana/data';
import { FlameGraphDataContainer } from '../FlameGraph/dataTransform';
import { data } from '../FlameGraph/testData/dataNestedSet';
@ -22,7 +22,6 @@ describe('FlameGraphTopTableContainer', () => {
onSymbolClick={jest.fn()}
onSearch={onSearch}
onSandwich={onSandwich}
getTheme={() => createTheme({ colors: { mode: 'dark' } })}
/>
);

View File

@ -18,6 +18,8 @@ import {
TableCustomCellOptions,
TableFieldOptions,
TableSortByFieldState,
useStyles2,
useTheme2,
} from '@grafana/ui';
import { FlameGraphDataContainer } from '../FlameGraph/dataTransform';
@ -33,23 +35,11 @@ type Props = {
onSearch: (str: string) => void;
onSandwich: (str?: string) => void;
onTableSort?: (sort: string) => void;
getTheme: () => GrafanaTheme2;
vertical?: boolean;
};
const FlameGraphTopTableContainer = React.memo(
({
data,
onSymbolClick,
height,
search,
onSearch,
sandwichItem,
onSandwich,
onTableSort,
getTheme,
vertical,
}: Props) => {
({ data, onSymbolClick, height, search, onSearch, sandwichItem, onSandwich, onTableSort, vertical }: Props) => {
const table = useMemo(() => {
// Group the data by label, we show only one row per label and sum the values
// TODO: should be by filename + funcName + linenumber?
@ -73,7 +63,9 @@ const FlameGraphTopTableContainer = React.memo(
// so we don't show potentially thousands of rows at once which can hinder performance (the table is virtualized
// so with some max height it handles it fine)
const tableHeight = vertical ? Math.min(Object.keys(table).length * rowHeight, 800) : 0;
const styles = getStyles(tableHeight, getTheme());
const styles = useStyles2(getStyles, tableHeight);
const theme = useTheme2();
const [sort, setSort] = useState<TableSortByFieldState[]>([{ displayName: 'Self', desc: true }]);
@ -92,7 +84,7 @@ const FlameGraphTopTableContainer = React.memo(
onSymbolClick,
onSearch,
onSandwich,
getTheme,
theme,
search,
sandwichItem
);
@ -126,7 +118,7 @@ function buildTableDataFrame(
onSymbolClick: (str: string) => void,
onSearch: (str: string) => void,
onSandwich: (str?: string) => void,
getTheme: () => GrafanaTheme2,
theme: GrafanaTheme2,
search?: string,
sandwichItem?: string
): DataFrame {
@ -218,7 +210,7 @@ function buildTableDataFrame(
overrides: [],
},
replaceVariables: (value: string) => value,
theme: getTheme(),
theme,
});
return dataFrames[0];
@ -327,7 +319,7 @@ function ActionCell(props: ActionCellProps) {
);
}
const getStyles = (height: number, theme: GrafanaTheme2) => {
const getStyles = (theme: GrafanaTheme2, height: number) => {
return {
topTableContainer: css`
label: topTableContainer;