mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2026-10-31 08:34:01 -05:00
This commit is contained in:
1 parent
6302a70316
commit
e2e588c324
4 files changed
+148
-10
No files matched your search
@@ -41,6 +41,20 @@ import { FeatureSet } from './features';
|
||||
import { createGridColumns, GRID_STATE } from './utils';
|
||||
|
||||
|
||||
// The row count above which a grid is worth virtualising. It scales with
|
||||
// the visible column count rather than being a flat row count, since
|
||||
// render cost tracks total cells (rows * cols), not rows alone: formula
|
||||
// and bounds from VIBVEL47's PR #10146. A grid reporting no columns yet
|
||||
// gets a middling default rather than the 25 row floor, so that a grid
|
||||
// still settling its columns is not virtualised on the strength of a
|
||||
// momentary zero.
|
||||
export function getVirtualiseThreshold(visibleColCount) {
|
||||
if(!visibleColCount) return 100;
|
||||
|
||||
return Math.min(400, Math.max(25, Math.round(700 / visibleColCount)));
|
||||
}
|
||||
|
||||
|
||||
export default function DataGridView({
|
||||
field, viewHelperProps, accessPath, dataDispatch, containerClassName
|
||||
}) {
|
||||
@@ -119,10 +133,26 @@ export default function DataGridView({
|
||||
)
|
||||
).includes(true);
|
||||
|
||||
// Virtualising a small grid buys nothing (there's no offscreen window to
|
||||
// skip rendering) but still pays for measureElement's per-row
|
||||
// getBoundingClientRect on every mount/remeasure. That remeasure is
|
||||
// exactly what fires when a dialog tab holding the grid is hidden via
|
||||
// `display: none` and then shown again, since the scroll viewport
|
||||
// momentarily measures 0 and the virtualizer's ResizeObserver treats
|
||||
// that as a real resize. Below the threshold we skip virtualisation
|
||||
// entirely and render every row in normal document flow, so showing a
|
||||
// hidden tab is a pure CSS toggle again.
|
||||
const visibleColCount = table.getVisibleLeafColumns().length;
|
||||
const virtualiseThreshold = viewHelperProps.virtualiseThreshold ??
|
||||
getVirtualiseThreshold(visibleColCount);
|
||||
const shouldVirtualise = rows.length > virtualiseThreshold;
|
||||
|
||||
const virtualizer = useVirtualizer({
|
||||
count: rows.length,
|
||||
getScrollElement: () => tableEleRef.current,
|
||||
estimateSize: () => 50,
|
||||
// Measuring only happens through the row `ref` below, which is left out
|
||||
// in static mode; that, not this option, is what turns it off there.
|
||||
measureElement:
|
||||
typeof window !== 'undefined' &&
|
||||
navigator.userAgent.indexOf('Firefox') === -1
|
||||
@@ -149,22 +179,29 @@ export default function DataGridView({
|
||||
ref={tableEleRef} table={table} data-test="data-grid-view"
|
||||
tableClassName='DataGridView-table'>
|
||||
<PgReactTableHeader table={table} />
|
||||
<PgReactTableBody style={{
|
||||
height: virtualizer.getTotalSize() + 'px'
|
||||
}}>
|
||||
<PgReactTableBody style={
|
||||
shouldVirtualise ? {height: virtualizer.getTotalSize() + 'px'} : undefined
|
||||
}>
|
||||
{
|
||||
virtualizer.getVirtualItems().map((virtualRow) => {
|
||||
(
|
||||
shouldVirtualise
|
||||
? virtualizer.getVirtualItems()
|
||||
: rows.map((_row, index) => ({index, start: 0}))
|
||||
).map((virtualRow) => {
|
||||
const row = rows[virtualRow.index];
|
||||
return (
|
||||
<PgReactTableRow
|
||||
key={row.id}
|
||||
data-index={virtualRow.index}
|
||||
ref={node => virtualizer.measureElement(node)}
|
||||
style={{
|
||||
// This should always be a `style` as it changes on
|
||||
// scroll.
|
||||
transform: `translateY(${virtualRow.start}px)`,
|
||||
}}
|
||||
ref={shouldVirtualise ? node => virtualizer.measureElement(node) : undefined}
|
||||
className={shouldVirtualise ? undefined : 'pgrt-row--static'}
|
||||
style={
|
||||
shouldVirtualise ? {
|
||||
// This should always be a `style` as it changes
|
||||
// on scroll.
|
||||
transform: `translateY(${virtualRow.start}px)`,
|
||||
} : undefined
|
||||
}
|
||||
>
|
||||
<GridRow
|
||||
rowId={virtualRow.index} isResizing={isResizing}
|
||||
|
||||
@@ -99,6 +99,14 @@ const StyledDiv = styled('div')(({theme})=>({
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
|
||||
// Opted out of the virtualizer's absolute positioning for grids
|
||||
// small enough that virtualisation isn't used. Keeps the row in
|
||||
// normal document flow so a hidden/shown dialog tab is a pure CSS
|
||||
// toggle instead of triggering a virtualizer remeasure.
|
||||
'&.pgrt-row--static': {
|
||||
position: 'static',
|
||||
},
|
||||
|
||||
'& .pgrt-row-content': {
|
||||
display: 'flex',
|
||||
minHeight: 0,
|
||||
|
||||
@@ -172,6 +172,65 @@ describe('SchemaView', ()=>{
|
||||
await user.type(ctrl.container.querySelectorAll('[name="field5"]')[1], 'rval51');
|
||||
expect(ctrl.container.querySelector('[data-test="notifier-message"]')).toHaveTextContent('Field5 in FieldColl must be unique.');
|
||||
});
|
||||
|
||||
it('does not virtualise a small grid, rendering rows in static flow', async ()=>{
|
||||
await simulateValidData();
|
||||
|
||||
const dataRows = ctrl.container.querySelectorAll('[data-test="data-table-row"]');
|
||||
expect(dataRows.length).toBe(2);
|
||||
|
||||
// Every row should be fully mounted and opted out of the
|
||||
// virtualizer's absolute positioning, so a hidden dialog tab is a
|
||||
// pure CSS toggle rather than something the virtualizer has to
|
||||
// remeasure when the tab is shown again.
|
||||
const pgrtRows = ctrl.container.querySelectorAll('.pgrt-row');
|
||||
expect(pgrtRows.length).toBe(2);
|
||||
pgrtRows.forEach((rowEl)=>{
|
||||
expect(rowEl.classList.contains('pgrt-row--static')).toBe(true);
|
||||
expect(rowEl.style.transform).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
it('virtualises a large grid, mounting only a window of rows', async ()=>{
|
||||
// Clearing the default threshold would need several hundred rows,
|
||||
// which is slow enough to time out on the Windows runners, so the
|
||||
// threshold is pinned low instead and the scaling of the default
|
||||
// is covered directly by getVirtualiseThreshold's own tests.
|
||||
const manyRows = Array.from({length: 60}, (_, i)=>(
|
||||
{field3: i, field4: 'field4val', field5: `field5val${i}`}
|
||||
));
|
||||
|
||||
await ctrlMount({
|
||||
viewHelperProps: {mode: 'create', virtualiseThreshold: 25},
|
||||
getInitData: ()=>Promise.resolve({fieldcoll: manyRows}),
|
||||
});
|
||||
|
||||
const pgrtRows = ctrl.container.querySelectorAll('.pgrt-row');
|
||||
expect(pgrtRows.length).toBeGreaterThan(0);
|
||||
expect(pgrtRows.length).toBeLessThan(manyRows.length);
|
||||
pgrtRows.forEach((rowEl)=>{
|
||||
expect(rowEl.classList.contains('pgrt-row--static')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not virtualise a grid holding exactly the threshold', async ()=>{
|
||||
// Virtualisation starts only above the threshold, so a grid of
|
||||
// exactly that many rows must still render every row statically.
|
||||
const rowsAtThreshold = Array.from({length: 5}, (_, i)=>(
|
||||
{field3: i, field4: 'field4val', field5: `field5val${i}`}
|
||||
));
|
||||
|
||||
await ctrlMount({
|
||||
viewHelperProps: {mode: 'create', virtualiseThreshold: 5},
|
||||
getInitData: ()=>Promise.resolve({fieldcoll: rowsAtThreshold}),
|
||||
});
|
||||
|
||||
const pgrtRows = ctrl.container.querySelectorAll('.pgrt-row');
|
||||
expect(pgrtRows.length).toBe(rowsAtThreshold.length);
|
||||
pgrtRows.forEach((rowEl)=>{
|
||||
expect(rowEl.classList.contains('pgrt-row--static')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('SQL tab', ()=>{
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2026, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import { getVirtualiseThreshold }
|
||||
from 'sources/SchemaView/DataGridView/grid';
|
||||
|
||||
describe('getVirtualiseThreshold', ()=>{
|
||||
it('scales inversely with the visible column count', ()=>{
|
||||
expect(getVirtualiseThreshold(4)).toBe(175);
|
||||
expect(getVirtualiseThreshold(7)).toBe(100);
|
||||
expect(getVirtualiseThreshold(14)).toBe(50);
|
||||
});
|
||||
|
||||
it('never goes above 400, however few the columns', ()=>{
|
||||
expect(getVirtualiseThreshold(1)).toBe(400);
|
||||
expect(getVirtualiseThreshold(2)).toBe(350);
|
||||
});
|
||||
|
||||
it('never goes below 25, however many the columns', ()=>{
|
||||
expect(getVirtualiseThreshold(28)).toBe(25);
|
||||
expect(getVirtualiseThreshold(200)).toBe(25);
|
||||
});
|
||||
|
||||
it('uses a middling default when no columns are reported yet', ()=>{
|
||||
expect(getVirtualiseThreshold(0)).toBe(100);
|
||||
expect(getVirtualiseThreshold(undefined)).toBe(100);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user