Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 72 additions & 34 deletions apps/opik-frontend/src/shared/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import DataTableWrapper, {
} from "@/shared/DataTable/DataTableWrapper";
import DataTableBody, {
DataTableBodyProps,
RowVirtualizationConfig,
} from "@/shared/DataTable/DataTableBody";
import DataTableSkeletonBody from "@/shared/DataTable/DataTableSkeletonBody";
import {
Expand All @@ -54,6 +55,12 @@ import {
} from "@/shared/PageBodyStickyContainer/PageBodyStickyContainer";
import { useObserveResizeNode } from "@/hooks/useObserveResizeNode";
import useCustomRowClick from "@/shared/DataTable/useCustomRowClick";
import useColumnVirtualization, {
ColumnVirtualizationConfig,
ColumnSpacerCell,
isColumnSpacer,
sliceColumnWindow,
} from "@/shared/DataTable/columnVirtualization";

declare module "@tanstack/react-table" {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down Expand Up @@ -139,6 +146,8 @@ interface DataTableProps<TData, TValue> {
stickyHeader?: boolean;
TableWrapper?: React.FC<DataTableWrapperProps>;
TableBody?: React.FC<DataTableBodyProps<TData>>;
columnVirtualization?: ColumnVirtualizationConfig;
rowVirtualization?: RowVirtualizationConfig;
meta?: Omit<
TableMeta<TData>,
"columnsStatistic" | "rowHeight" | "rowHeightStyle"
Expand Down Expand Up @@ -171,6 +180,8 @@ const DataTable = <TData, TValue>({
autoWidth = false,
TableWrapper = DataTableWrapper,
TableBody = DataTableBody,
columnVirtualization,
rowVirtualization,
stickyHeader = false,
meta,
getSubRows,
Expand Down Expand Up @@ -254,6 +265,10 @@ const DataTable = <TData, TValue>({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [headers, columnSizing]);

const columnWindow = useColumnVirtualization(table, columnVirtualization, {
supported: !isFunction(renderCustomRow),
});

const [tableHeight, setTableHeight] = useState(0);
const [hasHorizontalScroll, setHasHorizontalScroll] = useState(false);
const { ref: tableRef } = useObserveResizeNode<HTMLTableElement>((node) => {
Expand Down Expand Up @@ -311,7 +326,13 @@ const DataTable = <TData, TValue>({
}
: {})}
>
{cells.map((cell) => renderCell(row, cell))}
{sliceColumnWindow(cells, columnWindow).map((cell) =>
isColumnSpacer(cell) ? (
<ColumnSpacerCell key={cell.id} spacer={cell} />
) : (
renderCell(row, cell)
),
)}
</TableRow>
);
};
Expand Down Expand Up @@ -420,7 +441,7 @@ const DataTable = <TData, TValue>({
}}
>
<colgroup>
{cols.map((i) => (
{sliceColumnWindow(cols, columnWindow).map((i) => (
<col key={i.id} style={{ width: `${i.size}px` }} />
))}
</colgroup>
Expand All @@ -441,51 +462,68 @@ const DataTable = <TData, TValue>({
!isLastRow && "!border-b-0",
)}
>
{headerGroup.headers.map((header) => {
return (
<TableHead
key={header.id}
data-header-id={header.id}
style={{
zIndex: TABLE_HEADER_Z_INDEX + (isLastRow ? 0 : 1),
...getCommonPinningStyles({
{sliceColumnWindow(headerGroup.headers, columnWindow).map(
(header) => {
if (isColumnSpacer(header)) {
return (
<ColumnSpacerCell
key={header.id}
spacer={header}
isHeader
/>
);
}

return (
<TableHead
key={header.id}
data-header-id={header.id}
style={{
zIndex:
TABLE_HEADER_Z_INDEX + (isLastRow ? 0 : 1),
...getCommonPinningStyles({
column: header.column,
isHeader: true,
isLastHeaderRow: isLastRow,
lastRightPinnedColumnId,
}),
}}
className={getCommonPinningClasses({
column: header.column,
isHeader: true,
isLastHeaderRow: isLastRow,
lastRightPinnedColumnId,
}),
}}
className={getCommonPinningClasses({
column: header.column,
isHeader: true,
lastLeftPinnedColumnId,
})}
colSpan={header.colSpan}
>
{header.isPlaceholder
? ""
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
{isResizable ? (
<DataTableColumnResizer header={header} />
) : null}
</TableHead>
);
})}
lastLeftPinnedColumnId,
})}
colSpan={header.colSpan}
>
{header.isPlaceholder
? ""
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
{isResizable ? (
<DataTableColumnResizer header={header} />
) : null}
</TableHead>
);
},
)}
</TableRow>
);
})}
</TableHeader>
{showSkeleton ? (
<DataTableSkeletonBody table={table} />
<DataTableSkeletonBody
table={table}
columnWindow={columnWindow}
/>
) : (
<TableBody
table={table}
renderRow={renderRow}
renderNoData={renderNoData}
showLoadingOverlay={showLoadingOverlay}
rowVirtualization={rowVirtualization}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default tables ignore row virtualization

When TableBody is the default DataTableBody, it accepts rowVirtualization but always renders table.getRowModel().rows.map(renderRow), so DataTable callers using rowVirtualization={{ enabled: true }} without DataTableVirtualBody render every row — should we select DataTableVirtualBody automatically?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`apps/opik-frontend/src/shared/DataTable/DataTable.tsx` around lines 521-527, fix the
`showSkeleton`/`TableBody` rendering logic so `rowVirtualization.enabled` actually
virtualizes rows when the default body is used. Automatically select the
virtualization-capable body (such as `DataTableVirtualBody`) or otherwise route
rendering through it, while preserving any explicitly supplied custom `TableBody`
behavior.

/>
)}
</Table>
Expand Down
5 changes: 5 additions & 0 deletions apps/opik-frontend/src/shared/DataTable/DataTableBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ import { TableBody } from "@/ui/table";
import { Row, Table } from "@tanstack/react-table";
import { cn } from "@/lib/utils";

export type RowVirtualizationConfig = {
enabled?: boolean;
};

export type DataTableBodyProps<TData> = {
table: Table<TData>;
renderRow: (row: Row<TData>) => React.ReactNode | null;
renderNoData: () => React.ReactNode | null;
showLoadingOverlay?: boolean;
rowVirtualization?: RowVirtualizationConfig;
};

export const DataTableBody = <TData,>({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import { TableBody, TableCell, TableRow } from "@/ui/table";
import { Skeleton } from "@/ui/skeleton";
import { ROW_HEIGHT } from "@/types/shared";
import { ROW_HEIGHT_MAP } from "@/constants/shared";
import {
ColumnSpacerCell,
ColumnWindow,
isColumnSpacer,
sliceColumnWindow,
} from "@/shared/DataTable/columnVirtualization";

const MAX_SKELETON_ROWS = 50;

Expand All @@ -15,12 +21,18 @@ const SCREEN_FRACTION: Record<ROW_HEIGHT, number> = {

type DataTableSkeletonBodyProps<TData> = {
table: Table<TData>;
columnWindow: ColumnWindow;
};

const DataTableSkeletonBody = <TData,>({
table,
columnWindow,
}: DataTableSkeletonBodyProps<TData>) => {
const columns = table.getAllLeafColumns();
const visibleColumns = table.getVisibleLeafColumns();
const columns = useMemo(
() => sliceColumnWindow(visibleColumns, columnWindow),
[visibleColumns, columnWindow],
);
const rowHeightEnum = table.options.meta?.rowHeight ?? ROW_HEIGHT.small;
const rowHeightStyle = table.options.meta?.rowHeightStyle;
const defaultHeight = ROW_HEIGHT_MAP[rowHeightEnum].height as string;
Expand All @@ -39,13 +51,17 @@ const DataTableSkeletonBody = <TData,>({
<TableBody>
{Array.from({ length: count }, (_, rowIndex) => (
<TableRow key={rowIndex}>
{columns.map((column) => (
<TableCell key={column.id}>
<div className="flex items-center p-2" style={rowHeightStyle}>
<Skeleton className="size-full" />
</div>
</TableCell>
))}
{columns.map((column) =>
isColumnSpacer(column) ? (
<ColumnSpacerCell key={column.id} spacer={column} />
) : (
<TableCell key={column.id}>
<div className="flex items-center p-2" style={rowHeightStyle}>
<Skeleton className="size-full" />
</div>
</TableCell>
),
)}
</TableRow>
))}
</TableBody>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useEffect } from "react";
import React, { useCallback, useEffect } from "react";
import { useVirtualizer } from "@tanstack/react-virtual";
import first from "lodash/first";
import last from "lodash/last";

import { TableBody } from "@/ui/table";
import { DataTableBodyProps } from "@/shared/DataTable/DataTableBody";
import usePageBodyScrollContainer from "@/contexts/usePageBodyScrollContainer";
import { observeOwnAxisOffset } from "@/shared/DataTable/virtualizerOptions";
import { cn } from "@/lib/utils";

const ROW_BORDER_SIZE = 1;
Expand All @@ -18,11 +19,13 @@ export const DataTableVirtualBody = <TData,>({
renderRow,
renderNoData,
showLoadingOverlay = false,
rowVirtualization,
}: DataTableBodyProps<TData>) => {
const { scrollContainer, tableOffset } = usePageBodyScrollContainer();
const { height } = table.options.meta?.rowHeightStyle ?? { height: "44" };

const rows = table.getRowModel().rows ?? [];
const enabled = rowVirtualization?.enabled ?? true;
const rows = table.getRowModel().rows;
const virtualRowHeight = parseInt(height as string, 10) + ROW_BORDER_SIZE;
const overscan = Math.max(
MIN_OVER_SCAN_ROWS,
Expand All @@ -32,13 +35,20 @@ export const DataTableVirtualBody = <TData,>({
),
);

const getItemKey = useCallback(
(index: number) => rows[index]?.id ?? index,
[rows],
);

const { getVirtualItems, measure } = useVirtualizer({
enabled,
count: rows.length,
getScrollElement: () => scrollContainer,
getItemKey: (index: number) => rows[index].id ?? index,
getItemKey,
estimateSize: () => virtualRowHeight,
paddingStart: tableOffset,
overscan,
observeElementOffset: observeOwnAxisOffset,
});
const virtualRows = getVirtualItems();
const firsRowHeight = (first(virtualRows)?.index ?? 0) * virtualRowHeight;
Expand Down Expand Up @@ -75,7 +85,11 @@ export const DataTableVirtualBody = <TData,>({
<TableBody
className={cn(showLoadingOverlay && "comet-table-body-loading-overlay")}
>
{rows?.length ? renderVirtualRows() : renderNoData()}
{rows.length
? enabled
? renderVirtualRows()
: rows.map(renderRow)
: renderNoData()}
</TableBody>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from "react";

import { ColumnSpacer } from "@/shared/DataTable/columnVirtualization/columnWindow";

type ColumnSpacerCellProps = {
spacer: ColumnSpacer;
isHeader?: boolean;
};

const ColumnSpacerCell: React.FC<ColumnSpacerCellProps> = ({
spacer,
isHeader,
}) => {
const style = { padding: 0, border: 0, width: `${spacer.size}px` };

return isHeader ? (
<th aria-hidden style={style} />
) : (
<td aria-hidden style={style} />
);
};

export default ColumnSpacerCell;
Loading
Loading