fix(grid): continue load-all loop after jumped page reload

This commit is contained in:
lxk955
2026-10-16 15:06:16 +08:00
committed by GitHub
parent 6267839596
commit a51013073f
2 changed files with 118 additions and 9 deletions
+19 -9
View File
@@ -3554,6 +3554,7 @@ let loadAllRowsLoopActive = false;
// Set when a "load all" reloads a jumped-to discrete page from the first row;
// the replacing result must still land the view on the last row (#11940).
let loadAllRowsReloadPending = false;
let loadAllRowsReloadLimit: number | undefined;
const loadAllRowsActive = ref(false);
// Tracks whether the current loading cycle was triggered by a refresh/rollback
// (as opposed to a normal paginate). Used to decide whether to auto-redirect
@@ -4103,6 +4104,7 @@ function startLoadAllRows(segment: { offset: number; limit: number }, reloadFrom
loadAllRowsActive.value = true;
loadAllRowsLoopActive = !reloadFromFirstRow;
loadAllRowsReloadPending = reloadFromFirstRow;
loadAllRowsReloadLimit = reloadFromFirstRow ? segment.limit : undefined;
infiniteScrollAllLoaded = false;
// A reload-from-first-row run replaces the page through the normal (non-
// append) execution path; the append watcher must not treat it as one of
@@ -4195,6 +4197,7 @@ function changePageSize(size: number) {
loadAllRowsActive.value = false;
loadAllRowsLoopActive = false;
loadAllRowsReloadPending = false;
loadAllRowsReloadLimit = undefined;
infiniteScrollPositions = new WeakMap();
resetGridVerticalScroll(true);
emit("paginate", 0, normalizedSize, currentWhereInput(), effectiveOrderBy());
@@ -5134,6 +5137,7 @@ function resetInfiniteScrollState() {
loadAllRowsActive.value = false;
loadAllRowsLoopActive = false;
loadAllRowsReloadPending = false;
loadAllRowsReloadLimit = undefined;
infiniteScrollPositions = new WeakMap();
resetGridVerticalScroll(true);
}
@@ -11837,21 +11841,25 @@ watch(
filterValueSuggestionLoader.reset({ clearCache: true });
// A non-append result replaces the whole data set, so a running "load all" is over.
const reloadedFromFirstRow = loadAllRowsReloadPending;
const reloadLimit = loadAllRowsReloadLimit ?? infiniteScrollMaxRows.value;
loadAllRowsReloadPending = false;
loadAllRowsReloadLimit = undefined;
loadAllRowsActive.value = false;
loadAllRowsLoopActive = false;
if (reloadedFromFirstRow) {
// A capped/truncated reload must not claim completeness — that would
// block the #10752 continuation past the cap; only an uncapped full
// reload marks the end (#11940).
if (!props.result.truncated && props.result.has_more !== true) {
infiniteScrollAllLoaded = true;
if (props.result.has_more === true && !props.result.truncated) {
loadAllRowsActive.value = true;
loadAllRowsLoopActive = true;
nextTick(() => {
if (loadAllRowsLoopActive && !finishOrContinueLoadAllRun(0, reloadLimit)) selectAndRevealLastLoadedRow();
});
} else {
if (!props.result.truncated && props.result.has_more !== true && (props.result.has_more === false || props.result.rows.length < reloadLimit)) {
infiniteScrollAllLoaded = true;
}
selectAndRevealLastLoadedRow();
}
selectAndRevealLastLoadedRow();
} else {
// The replacement also invalidates the all-loaded marker: a filter change or
// page jump swaps in a fresh first page whose remaining segments must be
// re-derived instead of being silently skipped.
infiniteScrollAllLoaded = false;
}
if (getResetScrollAfterResult()) {
@@ -13246,6 +13254,8 @@ onUnmounted(() => {
onSqlPreviewResizeEnd();
finishCellSelection();
loadAllRowsLoopActive = false;
loadAllRowsReloadPending = false;
loadAllRowsReloadLimit = undefined;
clearTimeout(highlightedColumnTimer);
stopLoadingElapsedTimer();
});
@@ -567,6 +567,105 @@ describe("DataGrid load-all on a jumped-to discrete page (#11940)", () => {
expect(Math.max(...rowNumbers)).toBeLessThanOrEqual(30);
expect(Math.min(...rowNumbers)).toBe(1);
});
it("continues chunking past the cap when total row count is unknown and has_more is true (#12418)", async () => {
// Jumped to page 9 (offset 80), unknown total, result cap is 5.
const { host, paginate, settingsStore, gridState } = mountGrid({ pageOffset: 80 });
settingsStore.updateEditorSettings({ dataGridRenderMode: "dom", queryResultMaxRowsEnabled: true, queryResultMaxRows: 5 });
gridState.value = {
context: "table-data",
pageLimit: 10,
pageOffset: 80,
totalRowCount: undefined,
result: { columns: ["id"], rows: [[1]], has_more: true, affected_rows: 0, execution_time_ms: 0 },
};
await settle();
host.querySelector<HTMLButtonElement>(`button[aria-label="${i18n.global.t("grid.loadAllAndGoToLastRow")}"]`)!.click();
await settle();
// 1st request: reloads from first row (offset: 0, limit: 5, append: false)
expect(paginate).toHaveBeenCalledOnce();
expect(paginate.mock.calls[0]?.slice(0, 2)).toEqual([0, 5]);
expect(paginate.mock.calls[0]?.[4]).toBe(false);
// Backend returns 5 rows with has_more: true
gridState.value = {
context: "table-data",
pageLimit: 5,
pageOffset: 0,
totalRowCount: undefined,
result: {
columns: ["id"],
rows: Array.from({ length: 5 }, (_, index) => [index + 1]),
has_more: true,
affected_rows: 0,
execution_time_ms: 0,
},
};
await settle();
// 2nd request: continues past the cap via append (offset: 5, limit: 5, append: true)
expect(paginate).toHaveBeenCalledTimes(2);
expect(paginate.mock.calls[1]?.slice(0, 2)).toEqual([5, 5]);
expect(paginate.mock.calls[1]?.[4]).toBe(true);
// Second chunk lands with 3 rows (tail) and has_more: false
gridState.value = {
context: "table-data",
pageLimit: 5,
pageOffset: 0,
totalRowCount: undefined,
result: {
columns: ["id"],
rows: Array.from({ length: 8 }, (_, index) => [index + 1]),
has_more: false,
appended_from_row_count: 5,
affected_rows: 0,
execution_time_ms: 0,
},
};
await settle();
// Run terminates after tail chunk
expect(paginate).toHaveBeenCalledTimes(2);
const footer = host.textContent || "";
expect(footer).toMatch(/all loaded/i);
});
it("does not report all loaded if reload lands at cap with unknown total and missing has_more (#12418)", async () => {
const { host, settingsStore, gridState } = mountGrid({ pageOffset: 80 });
settingsStore.updateEditorSettings({ dataGridRenderMode: "dom", queryResultMaxRowsEnabled: true, queryResultMaxRows: 5 });
gridState.value = {
context: "table-data",
pageLimit: 10,
pageOffset: 80,
totalRowCount: undefined,
result: { columns: ["id"], rows: [[1]], has_more: false, affected_rows: 0, execution_time_ms: 0 },
};
await settle();
host.querySelector<HTMLButtonElement>(`button[aria-label="${i18n.global.t("grid.loadAllAndGoToLastRow")}"]`)!.click();
await settle();
// Reload returned exactly 5 rows (the cap), but has_more is undefined
gridState.value = {
context: "table-data",
pageLimit: 5,
pageOffset: 0,
totalRowCount: undefined,
result: {
columns: ["id"],
rows: Array.from({ length: 5 }, (_, index) => [index + 1]),
has_more: undefined,
affected_rows: 0,
execution_time_ms: 0,
},
};
await settle();
const footer = host.textContent || "";
expect(footer).not.toMatch(/all loaded/i);
});
});
describe("DataGrid multi-chunk load-all run", () => {