TableRT sorta working
This commit is contained in:
@@ -843,9 +843,6 @@
|
||||
}
|
||||
},
|
||||
"packages/grafana-ui/src/components/Table/TableRT/Table.tsx": {
|
||||
"@typescript-eslint/consistent-type-assertions": {
|
||||
"count": 1
|
||||
},
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { CSSProperties, UIEventHandler, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { CSSProperties, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import * as React from 'react';
|
||||
import { Cell, Row, TableState, HeaderGroup } from 'react-table';
|
||||
import { VariableSizeList } from 'react-window';
|
||||
import { List, useListRef } from 'react-window';
|
||||
import { Subscription, debounceTime } from 'rxjs';
|
||||
|
||||
import {
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
import { TableCellDisplayMode, TableCellHeight } from '@grafana/schema';
|
||||
|
||||
import { useTheme2 } from '../../../themes/ThemeContext';
|
||||
import CustomScrollbar from '../../CustomScrollbar/CustomScrollbar';
|
||||
import { usePanelContext } from '../../PanelChrome';
|
||||
import { TableCell } from '../Cells/TableCell';
|
||||
import {
|
||||
@@ -42,14 +41,10 @@ interface RowsListProps {
|
||||
data: DataFrame;
|
||||
rows: Row[];
|
||||
enableSharedCrosshair: boolean;
|
||||
headerHeight: number;
|
||||
rowHeight: number;
|
||||
itemCount: number;
|
||||
pageIndex: number;
|
||||
listHeight: number;
|
||||
width: number;
|
||||
cellHeight?: TableCellHeight;
|
||||
listRef: React.RefObject<VariableSizeList>;
|
||||
tableState: TableState;
|
||||
tableStyles: TableStyles;
|
||||
nestedDataField?: Field;
|
||||
@@ -70,11 +65,8 @@ export const RowsList = (props: RowsListProps) => {
|
||||
const {
|
||||
data,
|
||||
rows,
|
||||
headerHeight,
|
||||
footerPaginationEnabled,
|
||||
rowHeight,
|
||||
itemCount,
|
||||
pageIndex,
|
||||
tableState,
|
||||
prepareRow,
|
||||
onCellFilterAdded,
|
||||
@@ -84,7 +76,6 @@ export const RowsList = (props: RowsListProps) => {
|
||||
tableStyles,
|
||||
nestedDataField,
|
||||
listHeight,
|
||||
listRef,
|
||||
enableSharedCrosshair = false,
|
||||
initialRowIndex = undefined,
|
||||
headerGroups,
|
||||
@@ -95,11 +86,25 @@ export const RowsList = (props: RowsListProps) => {
|
||||
setInspectCell,
|
||||
} = props;
|
||||
|
||||
const listRef = useListRef(null);
|
||||
const [rowHighlightIndex, setRowHighlightIndex] = useState<number | undefined>(initialRowIndex);
|
||||
if (initialRowIndex === undefined && rowHighlightIndex !== undefined) {
|
||||
setRowHighlightIndex(undefined);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (rowHighlightIndex !== undefined) {
|
||||
// TODO can we do this without a setTimeout?
|
||||
setTimeout(() => {
|
||||
listRef.current?.scrollToRow({
|
||||
index: rowHighlightIndex,
|
||||
align: 'center',
|
||||
behavior: 'instant',
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [rowHighlightIndex, listRef]);
|
||||
|
||||
const theme = useTheme2();
|
||||
const panelContext = usePanelContext();
|
||||
|
||||
@@ -234,15 +239,6 @@ export const RowsList = (props: RowsListProps) => {
|
||||
};
|
||||
}, [data, enableSharedCrosshair, footerPaginationEnabled, onDataHoverEvent, panelContext]);
|
||||
|
||||
let scrollTop: number | undefined = undefined;
|
||||
if (rowHighlightIndex !== undefined) {
|
||||
const firstMatchedRowIndex = rows.findIndex((row) => row.index === rowHighlightIndex);
|
||||
|
||||
if (firstMatchedRowIndex !== -1) {
|
||||
scrollTop = headerHeight + (firstMatchedRowIndex - 1) * rowHeight;
|
||||
}
|
||||
}
|
||||
|
||||
const rowIndexForPagination = useCallback(
|
||||
(index: number) => {
|
||||
return tableState.pageIndex * tableState.pageSize + index;
|
||||
@@ -316,6 +312,17 @@ export const RowsList = (props: RowsListProps) => {
|
||||
);
|
||||
style.height = bbox.height;
|
||||
}
|
||||
|
||||
// some disgusting code to mutate the style object to convert transform to top
|
||||
// this is all so that hover behaviour is maintained
|
||||
// using transform creates new stacking contexts which means hover states don't overlay correctly
|
||||
const yPos = style.transform?.match(/translateY\((.*)\)/)?.[1];
|
||||
|
||||
style = {
|
||||
...style,
|
||||
top: yPos,
|
||||
transform: undefined,
|
||||
};
|
||||
const { key, ...rowProps } = row.getRowProps({ style, ...additionalProps });
|
||||
|
||||
return (
|
||||
@@ -414,36 +421,17 @@ export const RowsList = (props: RowsListProps) => {
|
||||
return tableStyles.rowHeight;
|
||||
};
|
||||
|
||||
const handleScroll: UIEventHandler = (event) => {
|
||||
const { scrollTop } = event.currentTarget;
|
||||
|
||||
if (listRef.current !== null) {
|
||||
listRef.current.scrollTo(scrollTop);
|
||||
}
|
||||
};
|
||||
|
||||
// It's a hack for text wrapping.
|
||||
// VariableSizeList component didn't know that we manually set row height.
|
||||
// So we need to reset the list when the rows high changes.
|
||||
useEffect(() => {
|
||||
if (listRef.current) {
|
||||
listRef.current.resetAfterIndex(0);
|
||||
}
|
||||
}, [rows, listRef]);
|
||||
|
||||
return (
|
||||
<CustomScrollbar onScroll={handleScroll} hideHorizontalTrack={true} scrollTop={scrollTop}>
|
||||
<VariableSizeList
|
||||
key={`${rowHeight}${pageIndex}`}
|
||||
height={listHeight}
|
||||
itemCount={itemCount}
|
||||
itemSize={getItemSize}
|
||||
width={'100%'}
|
||||
ref={listRef}
|
||||
style={{ overflow: undefined }}
|
||||
>
|
||||
{({ index, style }) => RenderRow({ index, style, rowHighlightIndex })}
|
||||
</VariableSizeList>
|
||||
</CustomScrollbar>
|
||||
<List
|
||||
rowProps={{}}
|
||||
rowHeight={getItemSize}
|
||||
rowCount={itemCount}
|
||||
listRef={listRef}
|
||||
style={{
|
||||
height: listHeight,
|
||||
width,
|
||||
}}
|
||||
rowComponent={({ index, style }) => RenderRow({ index, style, rowHighlightIndex })}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
useSortBy,
|
||||
useTable,
|
||||
} from 'react-table';
|
||||
import { VariableSizeList } from 'react-window';
|
||||
|
||||
import { FieldType, ReducerID, getRowUniqueId, getFieldMatcher } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
@@ -16,12 +15,10 @@ import { Trans } from '@grafana/i18n';
|
||||
import { TableCellHeight } from '@grafana/schema';
|
||||
|
||||
import { useTheme2 } from '../../../themes/ThemeContext';
|
||||
import { CustomScrollbar } from '../../CustomScrollbar/CustomScrollbar';
|
||||
import { Pagination } from '../../Pagination/Pagination';
|
||||
import { TableCellInspector } from '../TableCellInspector';
|
||||
import { useFixScrollbarContainer, useResetVariableListSizeCache } from '../hooks';
|
||||
import { getInitialState, useTableStateReducer } from '../reducer';
|
||||
import { FooterItem, GrafanaTableState, InspectCell, TableRTProps as Props } from '../types';
|
||||
import { FooterItem, InspectCell, TableRTProps as Props } from '../types';
|
||||
import {
|
||||
getColumns,
|
||||
sortCaseInsensitive,
|
||||
@@ -70,7 +67,6 @@ export const Table = memo((props: Props) => {
|
||||
replaceVariables,
|
||||
} = props;
|
||||
|
||||
const listRef = useRef<VariableSizeList>(null);
|
||||
const tableDivRef = useRef<HTMLDivElement>(null);
|
||||
const variableSizeListScrollbarRef = useRef<HTMLDivElement>(null);
|
||||
const theme = useTheme2();
|
||||
@@ -200,7 +196,6 @@ export const Table = memo((props: Props) => {
|
||||
toggleAllRowsExpanded,
|
||||
} = useTable(options, useFilters, useSortBy, useAbsoluteLayout, useResizeColumns, useExpanded, usePagination);
|
||||
|
||||
const extendedState = state as GrafanaTableState;
|
||||
toggleAllRowsExpandedRef.current = toggleAllRowsExpanded;
|
||||
|
||||
/*
|
||||
@@ -270,9 +265,6 @@ export const Table = memo((props: Props) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data]);
|
||||
|
||||
useResetVariableListSizeCache(extendedState, listRef, data, hasUniqueId);
|
||||
useFixScrollbarContainer(variableSizeListScrollbarRef, tableDivRef);
|
||||
|
||||
const onNavigate = useCallback(
|
||||
(toPage: number) => {
|
||||
gotoPage(toPage - 1);
|
||||
@@ -340,60 +332,51 @@ export const Table = memo((props: Props) => {
|
||||
ref={tableDivRef}
|
||||
style={{ width, height }}
|
||||
>
|
||||
<CustomScrollbar hideVerticalTrack={true}>
|
||||
<div className={tableStyles.tableContentWrapper(totalColumnsWidth)}>
|
||||
{!noHeader && (
|
||||
<HeaderRow headerGroups={headerGroups} showTypeIcons={showTypeIcons} tableStyles={tableStyles} />
|
||||
)}
|
||||
{itemCount > 0 ? (
|
||||
<div
|
||||
data-testid={selectors.components.Panels.Visualization.Table.body}
|
||||
ref={variableSizeListScrollbarRef}
|
||||
>
|
||||
<RowsList
|
||||
headerGroups={headerGroups}
|
||||
data={data}
|
||||
rows={rows}
|
||||
width={width}
|
||||
cellHeight={cellHeight}
|
||||
headerHeight={headerHeight}
|
||||
rowHeight={tableStyles.rowHeight}
|
||||
itemCount={itemCount}
|
||||
pageIndex={state.pageIndex}
|
||||
listHeight={listHeight}
|
||||
listRef={listRef}
|
||||
tableState={state}
|
||||
prepareRow={prepareRow}
|
||||
timeRange={timeRange}
|
||||
onCellFilterAdded={onCellFilterAdded}
|
||||
nestedDataField={nestedDataField}
|
||||
tableStyles={tableStyles}
|
||||
footerPaginationEnabled={Boolean(enablePagination)}
|
||||
enableSharedCrosshair={enableSharedCrosshair}
|
||||
initialRowIndex={initialRowIndex}
|
||||
longestField={longestField}
|
||||
textWrapField={textWrapField}
|
||||
getActions={getActions}
|
||||
replaceVariables={replaceVariables}
|
||||
setInspectCell={setInspectCell}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ height: height - headerHeight, width }} className={tableStyles.noData}>
|
||||
{noValuesDisplayText}
|
||||
</div>
|
||||
)}
|
||||
{footerItems && (
|
||||
<FooterRow
|
||||
isPaginationVisible={Boolean(enablePagination)}
|
||||
footerValues={footerItems}
|
||||
footerGroups={footerGroups}
|
||||
totalColumnsWidth={totalColumnsWidth}
|
||||
<div className={tableStyles.tableContentWrapper(totalColumnsWidth)}>
|
||||
{!noHeader && (
|
||||
<HeaderRow headerGroups={headerGroups} showTypeIcons={showTypeIcons} tableStyles={tableStyles} />
|
||||
)}
|
||||
{itemCount > 0 ? (
|
||||
<div data-testid={selectors.components.Panels.Visualization.Table.body} ref={variableSizeListScrollbarRef}>
|
||||
<RowsList
|
||||
headerGroups={headerGroups}
|
||||
data={data}
|
||||
rows={rows}
|
||||
width={width}
|
||||
cellHeight={cellHeight}
|
||||
itemCount={itemCount}
|
||||
listHeight={listHeight}
|
||||
tableState={state}
|
||||
prepareRow={prepareRow}
|
||||
timeRange={timeRange}
|
||||
onCellFilterAdded={onCellFilterAdded}
|
||||
nestedDataField={nestedDataField}
|
||||
tableStyles={tableStyles}
|
||||
footerPaginationEnabled={Boolean(enablePagination)}
|
||||
enableSharedCrosshair={enableSharedCrosshair}
|
||||
initialRowIndex={initialRowIndex}
|
||||
longestField={longestField}
|
||||
textWrapField={textWrapField}
|
||||
getActions={getActions}
|
||||
replaceVariables={replaceVariables}
|
||||
setInspectCell={setInspectCell}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</CustomScrollbar>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ height: height - headerHeight, width }} className={tableStyles.noData}>
|
||||
{noValuesDisplayText}
|
||||
</div>
|
||||
)}
|
||||
{footerItems && (
|
||||
<FooterRow
|
||||
isPaginationVisible={Boolean(enablePagination)}
|
||||
footerValues={footerItems}
|
||||
footerGroups={footerGroups}
|
||||
totalColumnsWidth={totalColumnsWidth}
|
||||
tableStyles={tableStyles}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{paginationEl}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -111,6 +111,7 @@ export function useTableStyles(theme: GrafanaTheme2, cellHeightOption: TableCell
|
||||
width: '100%',
|
||||
overflow: 'auto',
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
flexDirection: 'column',
|
||||
}),
|
||||
thead: css({
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import * as React from 'react';
|
||||
import { VariableSizeList } from 'react-window';
|
||||
|
||||
import { DataFrame } from '@grafana/data';
|
||||
|
||||
import { GrafanaTableState } from './types';
|
||||
|
||||
/**
|
||||
To have the custom vertical scrollbar always visible (https://github.com/grafana/grafana/issues/52136),
|
||||
we need to bring the element from the VariableSizeList scope to the outer Table container scope,
|
||||
because the VariableSizeList scope has overflow. By moving scrollbar to container scope we will have
|
||||
it always visible since the entire width is in view.
|
||||
Select the scrollbar element from the VariableSizeList scope
|
||||
*/
|
||||
export function useFixScrollbarContainer(
|
||||
variableSizeListScrollbarRef: React.RefObject<HTMLDivElement>,
|
||||
tableDivRef: React.RefObject<HTMLDivElement>
|
||||
) {
|
||||
useEffect(() => {
|
||||
if (variableSizeListScrollbarRef.current && tableDivRef.current) {
|
||||
const listVerticalScrollbarHTML = variableSizeListScrollbarRef.current.querySelector('.track-vertical');
|
||||
|
||||
// Select Table custom scrollbars
|
||||
const tableScrollbarView = tableDivRef.current.firstChild;
|
||||
|
||||
//If they exist, move the scrollbar element to the Table container scope
|
||||
if (tableScrollbarView && listVerticalScrollbarHTML) {
|
||||
listVerticalScrollbarHTML.remove();
|
||||
if (tableScrollbarView instanceof HTMLElement) {
|
||||
tableScrollbarView.querySelector(':scope > .track-vertical')?.remove();
|
||||
tableScrollbarView.append(listVerticalScrollbarHTML);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
react-table caches the height of cells, so we need to reset them when expanding/collapsing rows.
|
||||
We use `lastExpandedOrCollapsedIndex` since collapsed rows disappear from `expandedIndexes` but still keep their expanded
|
||||
height.
|
||||
*/
|
||||
export function useResetVariableListSizeCache(
|
||||
extendedState: GrafanaTableState,
|
||||
listRef: React.RefObject<VariableSizeList>,
|
||||
data: DataFrame,
|
||||
hasUniqueId: boolean
|
||||
) {
|
||||
// Make sure we trigger the reset when keys change in any way
|
||||
const expandedRowsRepr = JSON.stringify(Object.keys(extendedState.expanded));
|
||||
|
||||
useEffect(() => {
|
||||
// By default, reset all rows
|
||||
let resetIndex = 0;
|
||||
|
||||
// If we have unique field, extendedState.expanded keys are not row indexes but IDs so instead of trying to search
|
||||
// for correct index we just reset the whole table.
|
||||
if (!hasUniqueId) {
|
||||
// If we don't have we reset from the last changed index.
|
||||
if (Number.isFinite(extendedState.lastExpandedOrCollapsedIndex)) {
|
||||
resetIndex = extendedState.lastExpandedOrCollapsedIndex!;
|
||||
}
|
||||
|
||||
// Account for paging.
|
||||
resetIndex =
|
||||
extendedState.pageIndex === 0
|
||||
? resetIndex - 1
|
||||
: resetIndex - extendedState.pageIndex - extendedState.pageIndex * extendedState.pageSize;
|
||||
}
|
||||
|
||||
listRef.current?.resetAfterIndex(Math.max(resetIndex, 0));
|
||||
return;
|
||||
}, [
|
||||
extendedState.lastExpandedOrCollapsedIndex,
|
||||
extendedState.pageSize,
|
||||
extendedState.pageIndex,
|
||||
listRef,
|
||||
data,
|
||||
expandedRowsRepr,
|
||||
hasUniqueId,
|
||||
]);
|
||||
}
|
||||
Reference in New Issue
Block a user