Skip to content

Commit 5216008

Browse files
authored
fix: DH-20319: Stuck to bottom of table not sticking (#2547)
- I believe the functionality stopped working after 9f87985, where an added `setState` call at the end of `componentDidUpdate` would overwrite any scroll position state state changes made in `checkStickyScroll()` - Now the relevant scroll position state changes are made in the same `setState` call at the end of `componentDidUpdate` - Added an e2e test to test the stuck to bottom scrolling functionality
1 parent 94a2e44 commit 5216008

19 files changed

Lines changed: 171 additions & 50 deletions

packages/grid/src/Grid.tsx

Lines changed: 65 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -516,12 +516,11 @@ class Grid extends PureComponent<GridProps, GridState> {
516516
// apply on mount, so that it works with a static model
517517
// https://github.com/deephaven/web-client-ui/issues/581
518518
const { isStuckToBottom, isStuckToRight } = this.props;
519-
if (isStuckToBottom) {
520-
this.scrollToBottom();
521-
}
522-
if (isStuckToRight) {
523-
this.scrollToRight();
524-
}
519+
const { top, left } = this.getStickyScrollPosition(
520+
isStuckToBottom,
521+
isStuckToRight
522+
);
523+
this.setState({ top, left });
525524
}
526525

527526
componentDidUpdate(prevProps: GridProps, prevState: GridState): void {
@@ -611,7 +610,14 @@ class Grid extends PureComponent<GridProps, GridState> {
611610

612611
this.requestUpdateCanvas();
613612

614-
this.checkStickyScroll();
613+
if (this.needToUpdateScroll()) {
614+
const { top, left } = this.getStickyScrollPosition(
615+
isStickyBottom,
616+
isStickyRight
617+
);
618+
updatedState.top = top;
619+
updatedState.left = left;
620+
}
615621

616622
if (this.validateSelection()) {
617623
this.checkSelectionChange(prevState);
@@ -776,29 +782,41 @@ class Grid extends PureComponent<GridProps, GridState> {
776782
this.setState({ isStuckToBottom: false });
777783
}
778784

779-
/**
780-
* Scrolls to bottom, if not already at bottom
781-
*/
782-
scrollToBottom(): void {
783-
if (!this.metrics) return;
784-
const { bottomVisible, rowCount, top, lastTop } = this.metrics;
785-
if ((bottomVisible < rowCount - 1 && bottomVisible > 0) || top > lastTop) {
786-
this.setState({ top: lastTop });
787-
}
788-
}
785+
getStickyScrollPosition(
786+
isStickyBottom: boolean,
787+
isStickyRight: boolean
788+
): { top: VisibleIndex; left: VisibleIndex } {
789+
// eslint-disable-next-line react/destructuring-assignment
790+
if (!this.metrics) return { left: this.state.left, top: this.state.top };
789791

790-
/**
791-
* Scrolls to right, if not already at right
792-
*/
793-
scrollToRight(): void {
794-
if (!this.metrics) return;
795-
const { rightVisible, columnCount, left, lastLeft } = this.metrics;
792+
const {
793+
bottomVisible,
794+
rowCount,
795+
top,
796+
lastTop,
797+
rightVisible,
798+
columnCount,
799+
left,
800+
lastLeft,
801+
} = this.metrics;
802+
803+
let newTop = top;
804+
let newLeft = left;
805+
806+
if (
807+
isStickyBottom &&
808+
((bottomVisible < rowCount - 1 && bottomVisible > 0) || top > lastTop)
809+
) {
810+
newTop = lastTop;
811+
}
796812
if (
797-
(rightVisible < columnCount - 1 && rightVisible > 0) ||
798-
left > lastLeft
813+
isStickyRight &&
814+
((rightVisible < columnCount - 1 && rightVisible > 0) || left > lastLeft)
799815
) {
800-
this.setState({ left: lastLeft });
816+
newLeft = lastLeft;
801817
}
818+
819+
return { top: newTop, left: newLeft };
802820
}
803821

804822
/**
@@ -931,35 +949,32 @@ class Grid extends PureComponent<GridProps, GridState> {
931949
/**
932950
* Compares the current metrics with the previous metrics to see if we need to scroll when it is stuck to the bottom or the right
933951
*/
934-
checkStickyScroll(): void {
935-
if (!this.metrics) {
936-
return;
952+
needToUpdateScroll(): boolean {
953+
if (!this.metrics || !this.prevMetrics) {
954+
return false;
937955
}
938956

939-
if (this.prevMetrics) {
940-
const { rowCount, columnCount, height, width } = this.metrics;
941-
const {
942-
rowCount: prevRowCount,
943-
columnCount: prevColumnCount,
944-
height: prevHeight,
945-
width: prevWidth,
946-
} = this.prevMetrics;
947-
948-
if (prevRowCount !== rowCount || height !== prevHeight) {
949-
const { isStuckToBottom } = this.state;
950-
if (isStuckToBottom) {
951-
this.scrollToBottom();
952-
}
957+
const { rowCount, columnCount, height, width } = this.metrics;
958+
const {
959+
rowCount: prevRowCount,
960+
columnCount: prevColumnCount,
961+
height: prevHeight,
962+
width: prevWidth,
963+
} = this.prevMetrics;
964+
965+
if (prevRowCount !== rowCount || height !== prevHeight) {
966+
const { isStuckToBottom } = this.state;
967+
if (isStuckToBottom) {
968+
return true;
953969
}
954-
955-
if (prevColumnCount !== columnCount || width !== prevWidth) {
956-
const { isStuckToRight } = this.state;
957-
if (isStuckToRight) {
958-
this.scrollToRight();
959-
}
970+
}
971+
if (prevColumnCount !== columnCount || width !== prevWidth) {
972+
const { isStuckToRight } = this.state;
973+
if (isStuckToRight) {
974+
return true;
960975
}
961976
}
962-
this.prevMetrics = this.metrics;
977+
return false;
963978
}
964979

965980
updateMetrics(state = this.state): GridMetrics {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from deephaven import empty_table, function_generated_table
2+
from deephaven.execution_context import get_exec_ctx
3+
4+
# Creates a table that can be shrunk and grown in size
5+
def create_shrink_grow_table():
6+
ctx = get_exec_ctx()
7+
i = 50
8+
9+
def make_table():
10+
return empty_table(i).update(["X = i"])
11+
12+
def shrink_table():
13+
nonlocal i
14+
i = 30;
15+
16+
def grow_table():
17+
nonlocal i
18+
i = 70;
19+
20+
table = function_generated_table(
21+
table_generator=make_table,
22+
refresh_interval_ms=1000,
23+
exec_ctx=ctx
24+
)
25+
26+
return table, shrink_table, grow_table

tests/docker-scripts/data/app.d/test.app

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ name=Web UI Test Application
66
file_0=common_figures.py
77
file_1=common_tables.py
88
file_2=multiselect_tables.py
9+
file_3=table_generators.py

tests/table-scroll.spec.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {
44
openTableOption,
55
openTable,
66
gotoPage,
7+
pasteInMonaco,
8+
generateId,
79
} from './utils';
810

911
// Scroll the table by scrolling the mouse wheel, whilst moving the mouse up and down to test hovering functionality
@@ -129,3 +131,80 @@ test('scroll expanded rollup while moving mouse over rows', async ({
129131
});
130132
});
131133
});
134+
135+
test('stuck to bottom scroll growing table', async ({ page }) => {
136+
// Need to generate a new shrink grow table for each test
137+
const id = generateId();
138+
const tableName = `_${id}_shrink_grow_table`;
139+
const growFunction = `func_${id}`;
140+
const consoleInput = page.locator('.console-input');
141+
142+
await pasteInMonaco(
143+
consoleInput,
144+
`${tableName}, _, ${growFunction} = create_shrink_grow_table()`
145+
);
146+
await page.keyboard.press('Enter');
147+
await waitForLoadingDone(page);
148+
149+
// Open the newly created table
150+
const openButton = page.getByRole('button', { name: tableName, exact: true });
151+
await openButton.click();
152+
await waitForLoadingDone(page);
153+
154+
const gridCanvas = page.locator('.iris-grid .grid-wrapper');
155+
await gridCanvas.click({ position: { x: 10, y: 80 } });
156+
// Scroll to end of table to get stuck at bottom
157+
await page.mouse.wheel(0, 1000);
158+
await expect(page.locator('.iris-grid-column')).toHaveScreenshot();
159+
160+
// Add more rows to the table and take another screenshot
161+
await pasteInMonaco(consoleInput, `${growFunction}()`);
162+
await page.keyboard.press('Enter');
163+
164+
// No reliable way to know when the added rows have loaded, so just wait
165+
await page.waitForTimeout(2000);
166+
await expect(page.locator('.iris-grid-column')).toHaveScreenshot();
167+
});
168+
169+
test('stuck to bottom scroll shrinking and growing table', async ({ page }) => {
170+
// Need to generate a new shrink grow table for each test
171+
const id = generateId();
172+
const tableName = `_${id}_shrink_grow_table`;
173+
const growFunction = `grow_${id}`;
174+
const shrinkFunction = `shrink_${id}`;
175+
const consoleInput = page.locator('.console-input');
176+
177+
await pasteInMonaco(
178+
consoleInput,
179+
`${tableName}, ${shrinkFunction}, ${growFunction} = create_shrink_grow_table()`
180+
);
181+
await page.keyboard.press('Enter');
182+
await waitForLoadingDone(page);
183+
184+
// Open the newly created table
185+
const openButton = page.getByRole('button', { name: tableName, exact: true });
186+
await openButton.click();
187+
await waitForLoadingDone(page);
188+
189+
const gridCanvas = page.locator('.iris-grid .grid-wrapper');
190+
await gridCanvas.click({ position: { x: 10, y: 80 } });
191+
// Scroll to end of table to get stuck at bottom
192+
await page.mouse.wheel(0, 1000);
193+
await expect(page.locator('.iris-grid-column')).toHaveScreenshot();
194+
195+
// Shrink the table and take another screenshot
196+
await pasteInMonaco(consoleInput, `${shrinkFunction}()`);
197+
await page.keyboard.press('Enter');
198+
199+
// No reliable way to know when the removed rows have loaded, so just wait
200+
await page.waitForTimeout(2000);
201+
await expect(page.locator('.iris-grid-column')).toHaveScreenshot();
202+
203+
// Add more rows to the table and take another screenshot
204+
await pasteInMonaco(consoleInput, `${growFunction}()`);
205+
await page.keyboard.press('Enter');
206+
207+
// No reliable way to know when the added rows have loaded, so just wait
208+
await page.waitForTimeout(2000);
209+
await expect(page.locator('.iris-grid-column')).toHaveScreenshot();
210+
});
8.44 KB
Loading
17.3 KB
Loading
6.3 KB
Loading
8.96 KB
Loading
17.7 KB
Loading
6.72 KB
Loading

0 commit comments

Comments
 (0)