Panel inspect: Horizontal scrolling in Data table (#22245)

* First try at horizontal scrolling

* move width logic to table

* Update packages/grafana-ui/src/components/Table/Table.tsx

Co-Authored-By: Dominik Prokop <dominik.prokop@grafana.com>

* wrap table with memo

* fix typo

* re add field

* WIP: Table scrolling troubles

* Annother approach

* Think it's working

* Removed unnessary change

* Table: Added custom scrollbar for horizontal scrolling

* Removed console log and fixed test

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
Peter Holmberg 2020-02-28 09:26:20 +01:00 committed by GitHub
parent 066d5cf4da
commit 3f7b058bea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 43 additions and 25 deletions

View File

@ -1,4 +1,4 @@
import React, { useMemo } from 'react'; import React, { FC, memo, useMemo } from 'react';
import { DataFrame, Field } from '@grafana/data'; import { DataFrame, Field } from '@grafana/data';
import { useSortBy, useTable, useBlockLayout, Cell } from 'react-table'; import { useSortBy, useTable, useBlockLayout, Cell } from 'react-table';
import { FixedSizeList } from 'react-window'; import { FixedSizeList } from 'react-window';
@ -10,22 +10,25 @@ import { getTableStyles } from './styles';
import { TableCell } from './TableCell'; import { TableCell } from './TableCell';
import { Icon } from '../Icon/Icon'; import { Icon } from '../Icon/Icon';
import { getTextAlign } from './utils'; import { getTextAlign } from './utils';
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
export interface Props { export interface Props {
data: DataFrame; data: DataFrame;
width: number; width: number;
height: number; height: number;
/** Minimal column width specified in pixels */
columnMinWidth?: number;
onCellClick?: TableFilterActionCallback; onCellClick?: TableFilterActionCallback;
} }
export const Table = ({ data, height, onCellClick, width }: Props) => { export const Table: FC<Props> = memo(({ data, height, onCellClick, width, columnMinWidth }) => {
const theme = useTheme(); const theme = useTheme();
const [ref, headerRowMeasurements] = useMeasure(); const [ref, headerRowMeasurements] = useMeasure();
const tableStyles = getTableStyles(theme); const tableStyles = getTableStyles(theme);
const { getTableProps, headerGroups, rows, prepareRow } = useTable( const { getTableProps, headerGroups, rows, prepareRow } = useTable(
{ {
columns: useMemo(() => getColumns(data, width), [data]), columns: useMemo(() => getColumns(data, width, columnMinWidth ?? 150), [data, width, columnMinWidth]),
data: useMemo(() => getTableRows(data), [data]), data: useMemo(() => getTableRows(data), [data]),
}, },
useSortBy, useSortBy,
@ -53,28 +56,39 @@ export const Table = ({ data, height, onCellClick, width }: Props) => {
[prepareRow, rows] [prepareRow, rows]
); );
let totalWidth = 0;
for (const headerGroup of headerGroups) {
for (const header of headerGroup.headers) {
totalWidth += header.width as number;
}
}
return ( return (
<div {...getTableProps()} className={tableStyles.table}> <div {...getTableProps()} className={tableStyles.table}>
<div> <CustomScrollbar>
{headerGroups.map((headerGroup: any) => ( <div>
<div className={tableStyles.thead} {...headerGroup.getHeaderGroupProps()} ref={ref}> {headerGroups.map((headerGroup: any) => (
{headerGroup.headers.map((column: any) => <div className={tableStyles.thead} {...headerGroup.getHeaderGroupProps()} ref={ref}>
renderHeaderCell(column, tableStyles.headerCell, data.fields[column.index]) {headerGroup.headers.map((column: any) =>
)} renderHeaderCell(column, tableStyles.headerCell, data.fields[column.index])
</div> )}
))} </div>
</div> ))}
<FixedSizeList </div>
height={height - headerRowMeasurements.height} <FixedSizeList
itemCount={rows.length} height={height - headerRowMeasurements.height}
itemSize={tableStyles.rowHeight} itemCount={rows.length}
width={width} itemSize={tableStyles.rowHeight}
> width={totalWidth ?? width}
{RenderRow} style={{ overflow: 'hidden auto' }}
</FixedSizeList> >
{RenderRow}
</FixedSizeList>
</CustomScrollbar>
</div> </div>
); );
}; });
function renderHeaderCell(column: any, className: string, field: Field) { function renderHeaderCell(column: any, className: string, field: Field) {
const headerProps = column.getHeaderProps(column.getSortByToggleProps()); const headerProps = column.getHeaderProps(column.getSortByToggleProps());

View File

@ -31,8 +31,10 @@ export const getTableStyles = stylesFactory(
cellHeightInner: bodyFontSize * lineHeight, cellHeightInner: bodyFontSize * lineHeight,
rowHeight: cellHeight + 2, rowHeight: cellHeight + 2,
table: css` table: css`
height: 100%;
width: 100%;
overflow: auto; overflow: auto;
border-spacing: 0; display: flex;
`, `,
thead: css` thead: css`
label: thead; label: thead;

View File

@ -40,12 +40,13 @@ describe('Table utils', () => {
}); });
it('Should distribute width and use field config width', () => { it('Should distribute width and use field config width', () => {
const columns = getColumns(getData(), 1000); const columns = getColumns(getData(), 1000, 120);
expect(columns[0].width).toBe(450); expect(columns[0].width).toBe(450);
expect(columns[1].width).toBe(100); expect(columns[1].width).toBe(100);
}); });
}); });
describe('getTextAlign', () => { describe('getTextAlign', () => {
it('Should use textAlign from custom', () => { it('Should use textAlign from custom', () => {
const data = getData(); const data = getData();

View File

@ -42,7 +42,7 @@ export function getTextAlign(field: Field): TextAlignProperty {
return 'left'; return 'left';
} }
export function getColumns(data: DataFrame, availableWidth: number): Column[] { export function getColumns(data: DataFrame, availableWidth: number, columnMinWidth: number): Column[] {
const columns: Column[] = []; const columns: Column[] = [];
let fieldCountWithoutWidth = data.fields.length; let fieldCountWithoutWidth = data.fields.length;
@ -67,7 +67,7 @@ export function getColumns(data: DataFrame, availableWidth: number): Column[] {
const sharedWidth = availableWidth / fieldCountWithoutWidth; const sharedWidth = availableWidth / fieldCountWithoutWidth;
for (const column of columns) { for (const column of columns) {
if (!column.width) { if (!column.width) {
column.width = sharedWidth; column.width = Math.max(sharedWidth, columnMinWidth);
} }
} }

View File

@ -219,6 +219,7 @@ export class PanelInspector extends PureComponent<Props, State> {
if (width === 0) { if (width === 0) {
return null; return null;
} }
return ( return (
<div style={{ width, height }}> <div style={{ width, height }}>
<Table width={width} height={height} data={processed[selected]} /> <Table width={width} height={height} data={processed[selected]} />