mirror of
https://github.com/grafana/grafana.git
synced 2026-09-05 04:40:13 -05:00
FrameGraphTopTable: Use standard Table component (#66411)
* FrameGraphTopTable: Use standard Table component * Simplify * Fix test * Update tests * Fixing test
This commit is contained in:
@@ -39,11 +39,11 @@ describe('FlameGraphContainer', () => {
|
|||||||
|
|
||||||
it('should update search when row selected in top table', async () => {
|
it('should update search when row selected in top table', async () => {
|
||||||
render(<FlameGraphContainerWithProps />);
|
render(<FlameGraphContainerWithProps />);
|
||||||
await userEvent.click((await screen.findAllByRole('row'))[1]);
|
await userEvent.click((await screen.findAllByTitle('Highlight symbol'))[0]);
|
||||||
expect(screen.getByDisplayValue('net/http.HandlerFunc.ServeHTTP')).toBeInTheDocument();
|
expect(screen.getByDisplayValue('net/http.HandlerFunc.ServeHTTP')).toBeInTheDocument();
|
||||||
await userEvent.click(screen.getAllByRole('row')[2]);
|
await userEvent.click((await screen.findAllByTitle('Highlight symbol'))[1]);
|
||||||
expect(screen.getByDisplayValue('total')).toBeInTheDocument();
|
expect(screen.getByDisplayValue('total')).toBeInTheDocument();
|
||||||
await userEvent.click(screen.getAllByRole('row')[2]);
|
await userEvent.click((await screen.findAllByTitle('Highlight symbol'))[1]);
|
||||||
expect(screen.queryByDisplayValue('total')).not.toBeInTheDocument();
|
expect(screen.queryByDisplayValue('total')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ const FlameGraphHeader = ({
|
|||||||
{ value: SelectedView.TopTable, label: 'Top Table', description: 'Only show top table' },
|
{ value: SelectedView.TopTable, label: 'Top Table', description: 'Only show top table' },
|
||||||
{ value: SelectedView.FlameGraph, label: 'Flame Graph', description: 'Only show flame graph' },
|
{ value: SelectedView.FlameGraph, label: 'Flame Graph', description: 'Only show flame graph' },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (containerWidth >= MIN_WIDTH_TO_SHOW_BOTH_TOPTABLE_AND_FLAMEGRAPH) {
|
if (containerWidth >= MIN_WIDTH_TO_SHOW_BOTH_TOPTABLE_AND_FLAMEGRAPH) {
|
||||||
viewOptions.push({
|
viewOptions.push({
|
||||||
value: SelectedView.Both,
|
value: SelectedView.Both,
|
||||||
@@ -47,6 +48,14 @@ const FlameGraphHeader = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onResetView = () => {
|
||||||
|
setTopLevelIndex(0);
|
||||||
|
setSelectedBarIndex(0);
|
||||||
|
setRangeMin(0);
|
||||||
|
setRangeMax(1);
|
||||||
|
setSearch('');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<div className={styles.leftContainer}>
|
<div className={styles.leftContainer}>
|
||||||
@@ -57,22 +66,11 @@ const FlameGraphHeader = ({
|
|||||||
setSearch(v.currentTarget.value);
|
setSearch(v.currentTarget.value);
|
||||||
}}
|
}}
|
||||||
placeholder={'Search..'}
|
placeholder={'Search..'}
|
||||||
width={24}
|
width={44}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button type={'button'} variant="secondary" onClick={onResetView}>
|
||||||
type={'button'}
|
Reset view
|
||||||
variant={'secondary'}
|
|
||||||
size={'md'}
|
|
||||||
onClick={() => {
|
|
||||||
setTopLevelIndex(0);
|
|
||||||
setSelectedBarIndex(0);
|
|
||||||
setRangeMin(0);
|
|
||||||
setRangeMax(1);
|
|
||||||
setSearch('');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Reset View
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,265 +0,0 @@
|
|||||||
import { css, cx } from '@emotion/css';
|
|
||||||
import React, { CSSProperties, useCallback, useMemo } from 'react';
|
|
||||||
import { SortByFn, useSortBy, useAbsoluteLayout, useTable, CellProps } from 'react-table';
|
|
||||||
import { FixedSizeList } from 'react-window';
|
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
|
||||||
import { Icon, useStyles2, CustomScrollbar } from '@grafana/ui';
|
|
||||||
|
|
||||||
import { TOP_TABLE_COLUMN_WIDTH } from '../../constants';
|
|
||||||
import { ColumnTypes, TopTableData, TopTableValue } from '../types';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
data: TopTableData[];
|
|
||||||
search: string;
|
|
||||||
setSearch: (search: string) => void;
|
|
||||||
setTopLevelIndex: (level: number) => void;
|
|
||||||
setSelectedBarIndex: (bar: number) => void;
|
|
||||||
setRangeMin: (range: number) => void;
|
|
||||||
setRangeMax: (range: number) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const FlameGraphTopTable = ({
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
data,
|
|
||||||
search,
|
|
||||||
setSearch,
|
|
||||||
setTopLevelIndex,
|
|
||||||
setSelectedBarIndex,
|
|
||||||
setRangeMin,
|
|
||||||
setRangeMax,
|
|
||||||
}: Props) => {
|
|
||||||
const styles = useStyles2((theme) => getStyles(theme));
|
|
||||||
|
|
||||||
const sortSymbols: SortByFn<object> = (a, b, column) => {
|
|
||||||
return a.values[column].localeCompare(b.values[column]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortUnits: SortByFn<object> = (a, b, column) => {
|
|
||||||
return a.values[column].value.toString().localeCompare(b.values[column].value.toString(), 'en', { numeric: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
const columns = useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
accessor: ColumnTypes.Symbol.toLowerCase(),
|
|
||||||
header: ColumnTypes.Symbol,
|
|
||||||
cell: SymbolCell,
|
|
||||||
sortType: sortSymbols,
|
|
||||||
width: width - TOP_TABLE_COLUMN_WIDTH * 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessor: ColumnTypes.Self.toLowerCase(),
|
|
||||||
header: ColumnTypes.Self,
|
|
||||||
cell: UnitCell,
|
|
||||||
sortType: sortUnits,
|
|
||||||
width: TOP_TABLE_COLUMN_WIDTH,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessor: ColumnTypes.Total.toLowerCase(),
|
|
||||||
header: ColumnTypes.Total,
|
|
||||||
cell: UnitCell,
|
|
||||||
sortType: sortUnits,
|
|
||||||
width: TOP_TABLE_COLUMN_WIDTH,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[width]
|
|
||||||
);
|
|
||||||
|
|
||||||
const options = useMemo(
|
|
||||||
() => ({
|
|
||||||
columns,
|
|
||||||
data,
|
|
||||||
initialState: {
|
|
||||||
sortBy: [
|
|
||||||
{
|
|
||||||
id: ColumnTypes.Self.toLowerCase(),
|
|
||||||
desc: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
[columns, data]
|
|
||||||
);
|
|
||||||
|
|
||||||
const rowClicked = useCallback(
|
|
||||||
(symbol: string) => {
|
|
||||||
if (search === symbol) {
|
|
||||||
setSearch('');
|
|
||||||
} else {
|
|
||||||
setSearch(symbol);
|
|
||||||
// Reset selected level in flamegraph when selecting row in top table
|
|
||||||
setTopLevelIndex(0);
|
|
||||||
setSelectedBarIndex(0);
|
|
||||||
setRangeMin(0);
|
|
||||||
setRangeMax(1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[search, setRangeMax, setRangeMin, setSearch, setTopLevelIndex, setSelectedBarIndex]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { headerGroups, rows, prepareRow } = useTable(options, useSortBy, useAbsoluteLayout);
|
|
||||||
|
|
||||||
const renderRow = React.useCallback(
|
|
||||||
({ index, style }: { index: number; style: CSSProperties }) => {
|
|
||||||
let row = rows[index];
|
|
||||||
prepareRow(row);
|
|
||||||
|
|
||||||
const symbol = row.values[ColumnTypes.Symbol.toLowerCase()];
|
|
||||||
const classNames = cx(symbol === search && styles.matchedRow, styles.row);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
{...row.getRowProps({ style })}
|
|
||||||
className={classNames}
|
|
||||||
onClick={() => {
|
|
||||||
rowClicked(symbol);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{row.cells.map((cell) => {
|
|
||||||
const { key, ...cellProps } = cell.getCellProps();
|
|
||||||
if (cellProps.style) {
|
|
||||||
cellProps.style.minWidth = cellProps.style.width;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div key={key} className={styles.cell} {...cellProps}>
|
|
||||||
{cell.render('cell')}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[rows, prepareRow, search, styles.matchedRow, styles.row, styles.cell, rowClicked]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.table(height)} data-testid="topTable">
|
|
||||||
{headerGroups.map((headerGroup) => {
|
|
||||||
const { key, ...headerGroupProps } = headerGroup.getHeaderGroupProps();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={key} className={styles.header} {...headerGroupProps}>
|
|
||||||
{headerGroup.headers.map((column) => {
|
|
||||||
const { key, ...headerProps } = column.getHeaderProps(
|
|
||||||
column.canSort ? column.getSortByToggleProps() : undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={key} className={styles.headerCell} {...headerProps}>
|
|
||||||
{column.render('header')}
|
|
||||||
{column.isSorted && <Icon name={column.isSortedDesc ? 'arrow-down' : 'arrow-up'} />}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
{rows.length > 0 ? (
|
|
||||||
<CustomScrollbar hideVerticalTrack={true}>
|
|
||||||
<FixedSizeList
|
|
||||||
height={height}
|
|
||||||
itemCount={rows.length}
|
|
||||||
itemSize={38}
|
|
||||||
width={'100%'}
|
|
||||||
style={{ overflow: 'hidden auto' }}
|
|
||||||
>
|
|
||||||
{renderRow}
|
|
||||||
</FixedSizeList>
|
|
||||||
</CustomScrollbar>
|
|
||||||
) : (
|
|
||||||
<div style={{ height: height }} className={styles.noData}>
|
|
||||||
No data
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const SymbolCell = ({ cell: { value } }: CellProps<TopTableValue, string>) => {
|
|
||||||
return <div>{value}</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const UnitCell = ({ cell: { value } }: CellProps<TopTableValue, TopTableValue>) => {
|
|
||||||
return <div>{value.unitValue}</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => ({
|
|
||||||
table: (height: number) => {
|
|
||||||
return css`
|
|
||||||
background-color: ${theme.colors.background.primary};
|
|
||||||
height: ${height}px;
|
|
||||||
overflow: scroll;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
},
|
|
||||||
header: css`
|
|
||||||
height: 38px;
|
|
||||||
|
|
||||||
& > :nth-child(2),
|
|
||||||
& > :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};
|
|
||||||
color: ${theme.colors.primary.text};
|
|
||||||
padding: ${theme.spacing(1)};
|
|
||||||
`,
|
|
||||||
matchedRow: css`
|
|
||||||
& > :nth-child(1),
|
|
||||||
& > :nth-child(2),
|
|
||||||
& > :nth-child(3) {
|
|
||||||
background-color: ${theme.colors.background.secondary} !important;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
row: css`
|
|
||||||
border-top: 1px solid ${theme.components.panel.borderColor};
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: ${theme.colors.emphasize(theme.colors.background.primary, 0.03)};
|
|
||||||
}
|
|
||||||
& > :nth-child(2),
|
|
||||||
& > :nth-child(3) {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
& > :nth-child(3) {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
cell: css`
|
|
||||||
border-right: 1px solid ${theme.components.panel.borderColor};
|
|
||||||
padding: ${theme.spacing(1)};
|
|
||||||
|
|
||||||
div {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
overflow: visible;
|
|
||||||
width: auto !important;
|
|
||||||
box-shadow: 0 0 2px ${theme.colors.primary.main};
|
|
||||||
background-color: ${theme.colors.emphasize(theme.colors.background.primary, 0.03)};
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
noData: css`
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default FlameGraphTopTable;
|
|
||||||
+2
-2
@@ -45,7 +45,7 @@ describe('FlameGraphTopTableContainer', () => {
|
|||||||
|
|
||||||
render(<FlameGraphTopTableContainerWithProps />);
|
render(<FlameGraphTopTableContainerWithProps />);
|
||||||
const rows = screen.getAllByRole('row');
|
const rows = screen.getAllByRole('row');
|
||||||
expect(rows).toHaveLength(17); // + 1 for the columnHeaders
|
expect(rows).toHaveLength(16);
|
||||||
|
|
||||||
const columnHeaders = screen.getAllByRole('columnheader');
|
const columnHeaders = screen.getAllByRole('columnheader');
|
||||||
expect(columnHeaders).toHaveLength(3);
|
expect(columnHeaders).toHaveLength(3);
|
||||||
@@ -54,7 +54,7 @@ describe('FlameGraphTopTableContainer', () => {
|
|||||||
expect(columnHeaders[2].textContent).toEqual('Total');
|
expect(columnHeaders[2].textContent).toEqual('Total');
|
||||||
|
|
||||||
const cells = screen.getAllByRole('cell');
|
const cells = screen.getAllByRole('cell');
|
||||||
expect(cells).toHaveLength(48); // 16 rows
|
expect(cells).toHaveLength(45); // 16 rows
|
||||||
expect(cells[0].textContent).toEqual('net/http.HandlerFunc.ServeHTTP');
|
expect(cells[0].textContent).toEqual('net/http.HandlerFunc.ServeHTTP');
|
||||||
expect(cells[1].textContent).toEqual('31.7 K');
|
expect(cells[1].textContent).toEqual('31.7 K');
|
||||||
expect(cells[2].textContent).toEqual('31.7 Bil');
|
expect(cells[2].textContent).toEqual('31.7 Bil');
|
||||||
|
|||||||
+107
-61
@@ -1,15 +1,22 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React, { useMemo } from 'react';
|
import React from 'react';
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
|
|
||||||
import { CoreApp, DisplayValue } from '@grafana/data';
|
import {
|
||||||
import { useStyles2 } from '@grafana/ui';
|
applyFieldOverrides,
|
||||||
|
ArrayVector,
|
||||||
|
CoreApp,
|
||||||
|
DataFrame,
|
||||||
|
DataLinkClickEvent,
|
||||||
|
Field,
|
||||||
|
FieldType,
|
||||||
|
} from '@grafana/data';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
import { Table, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
import { PIXELS_PER_LEVEL } from '../../constants';
|
import { PIXELS_PER_LEVEL, TOP_TABLE_COLUMN_WIDTH } from '../../constants';
|
||||||
import { FlameGraphDataContainer } from '../FlameGraph/dataTransform';
|
import { FlameGraphDataContainer } from '../FlameGraph/dataTransform';
|
||||||
import { SelectedView, TableData, TopTableData } from '../types';
|
import { SelectedView, TableData } from '../types';
|
||||||
|
|
||||||
import FlameGraphTopTable from './FlameGraphTopTable';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
data: FlameGraphDataContainer;
|
data: FlameGraphDataContainer;
|
||||||
@@ -38,68 +45,107 @@ const FlameGraphTopTableContainer = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
const styles = useStyles2(() => getStyles(selectedView, app));
|
const styles = useStyles2(() => getStyles(selectedView, app));
|
||||||
|
|
||||||
const topTable = useMemo(() => {
|
const onSymbolClick = (symbol: string) => {
|
||||||
// Group the data by label
|
if (search === symbol) {
|
||||||
// TODO: should be by filename + funcName + linenumber?
|
setSearch('');
|
||||||
let table: { [key: string]: TableData } = {};
|
} else {
|
||||||
for (let i = 0; i < data.data.length; i++) {
|
setSearch(symbol);
|
||||||
const value = data.getValue(i);
|
// Reset selected level in flamegraph when selecting row in top table
|
||||||
const self = data.getSelf(i);
|
setTopLevelIndex(0);
|
||||||
const label = data.getLabel(i);
|
setSelectedBarIndex(0);
|
||||||
table[label] = table[label] || {};
|
setRangeMin(0);
|
||||||
table[label].self = table[label].self ? table[label].self + self : self;
|
setRangeMax(1);
|
||||||
table[label].total = table[label].total ? table[label].total + value : value;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let topTable: TopTableData[] = [];
|
const initialSortBy = [{ displayName: 'Self', desc: true }];
|
||||||
for (let key in table) {
|
|
||||||
const selfUnit = handleUnits(data.valueDisplayProcessor(table[key].self), data.getUnitTitle());
|
|
||||||
const valueUnit = handleUnits(data.valueDisplayProcessor(table[key].total), data.getUnitTitle());
|
|
||||||
|
|
||||||
topTable.push({
|
|
||||||
symbol: key,
|
|
||||||
self: { value: table[key].self, unitValue: selfUnit },
|
|
||||||
total: { value: table[key].total, unitValue: valueUnit },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return topTable;
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={styles.topTableContainer} data-testid="topTable">
|
||||||
{topTable && (
|
<AutoSizer style={{ width: '100%', height: PIXELS_PER_LEVEL * totalLevels + 'px' }}>
|
||||||
<div className={styles.topTableContainer}>
|
{({ width, height }) => {
|
||||||
<AutoSizer style={{ width: '100%', height: PIXELS_PER_LEVEL * totalLevels + 'px' }}>
|
if (width < 3 || height < 3) {
|
||||||
{({ width, height }) => (
|
return null;
|
||||||
<FlameGraphTopTable
|
}
|
||||||
width={width}
|
|
||||||
height={height}
|
const frame = buildTableDataFrame(data, width, onSymbolClick);
|
||||||
data={topTable}
|
return <Table initialSortBy={initialSortBy} data={frame} width={width} height={height} />;
|
||||||
search={search}
|
}}
|
||||||
setSearch={setSearch}
|
</AutoSizer>
|
||||||
setTopLevelIndex={setTopLevelIndex}
|
</div>
|
||||||
setSelectedBarIndex={setSelectedBarIndex}
|
|
||||||
setRangeMin={setRangeMin}
|
|
||||||
setRangeMax={setRangeMax}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</AutoSizer>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleUnits(displayValue: DisplayValue, unit: string) {
|
function buildTableDataFrame(
|
||||||
let unitValue = displayValue.text + displayValue.suffix;
|
data: FlameGraphDataContainer,
|
||||||
if (unit === 'Count') {
|
width: number,
|
||||||
if (!displayValue.suffix) {
|
onSymbolClick: (str: string) => void
|
||||||
// Makes sure we don't show 123undefined or something like that if suffix isn't defined
|
): DataFrame {
|
||||||
unitValue = displayValue.text;
|
// Group the data by label
|
||||||
}
|
// TODO: should be by filename + funcName + linenumber?
|
||||||
|
let table: { [key: string]: TableData } = {};
|
||||||
|
for (let i = 0; i < data.data.length; i++) {
|
||||||
|
const value = data.getValue(i);
|
||||||
|
const self = data.getSelf(i);
|
||||||
|
const label = data.getLabel(i);
|
||||||
|
table[label] = table[label] || {};
|
||||||
|
table[label].self = table[label].self ? table[label].self + self : self;
|
||||||
|
table[label].total = table[label].total ? table[label].total + value : value;
|
||||||
}
|
}
|
||||||
return unitValue;
|
|
||||||
|
const symbolField = {
|
||||||
|
type: FieldType.string,
|
||||||
|
name: 'Symbol',
|
||||||
|
values: new ArrayVector(),
|
||||||
|
config: {
|
||||||
|
custom: { width: width - TOP_TABLE_COLUMN_WIDTH * 2 },
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
title: 'Highlight symbol',
|
||||||
|
url: '',
|
||||||
|
onClick: (e: DataLinkClickEvent) => {
|
||||||
|
const field: Field = e.origin.field;
|
||||||
|
const value = field.values.get(e.origin.rowIndex);
|
||||||
|
onSymbolClick(value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const selfField = {
|
||||||
|
type: FieldType.number,
|
||||||
|
name: 'Self',
|
||||||
|
values: new ArrayVector(),
|
||||||
|
config: { unit: data.selfField.config.unit, custom: { width: TOP_TABLE_COLUMN_WIDTH } },
|
||||||
|
};
|
||||||
|
|
||||||
|
const totalField = {
|
||||||
|
type: FieldType.number,
|
||||||
|
name: 'Total',
|
||||||
|
values: new ArrayVector(),
|
||||||
|
config: { unit: data.valueField.config.unit, custom: { width: TOP_TABLE_COLUMN_WIDTH } },
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let key in table) {
|
||||||
|
symbolField.values.add(key);
|
||||||
|
selfField.values.add(table[key].self);
|
||||||
|
totalField.values.add(table[key].total);
|
||||||
|
}
|
||||||
|
|
||||||
|
const frame = { fields: [symbolField, selfField, totalField], length: symbolField.values.length };
|
||||||
|
|
||||||
|
const dataFrames = applyFieldOverrides({
|
||||||
|
data: [frame],
|
||||||
|
fieldConfig: {
|
||||||
|
defaults: {},
|
||||||
|
overrides: [],
|
||||||
|
},
|
||||||
|
replaceVariables: (value: string) => value,
|
||||||
|
theme: config.theme2,
|
||||||
|
});
|
||||||
|
|
||||||
|
return dataFrames[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStyles = (selectedView: SelectedView, app: CoreApp) => {
|
const getStyles = (selectedView: SelectedView, app: CoreApp) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user