fix(mongo): stabilize estimated-total pagination
This commit is contained in:
1 parent
96038d2b8a
commit
8f3fd0ebfb
23 files changed
+462
-124
No files matched your search
+1
-2
@@ -63,8 +63,7 @@ function createBaseOptions(overrides: Partial<DrawCanvasDataGridOptions> = {}):
|
||||
cellIsSelected: () => false,
|
||||
cellCanHover: () => true,
|
||||
infiniteScrollEnabled: false,
|
||||
pageSize: 100,
|
||||
currentPage: 1,
|
||||
pageOffset: 0,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { canGoNextDocumentPage, resolveDocumentQueryTotals } from "@/lib/document/documentQueryTotals";
|
||||
import { canGoNextDocumentPage, exactTotalFromIncompleteDocumentPage, isSameDocumentQueryTotalCountRequest, resolveDocumentQueryTotals, type DocumentQueryTotalCountRequest } from "@/lib/document/documentQueryTotals";
|
||||
|
||||
describe("document query totals", () => {
|
||||
it("uses exact totals as the pagination bound", () => {
|
||||
@@ -21,4 +21,49 @@ describe("document query totals", () => {
|
||||
expect(canGoNextDocumentPage({ page: 999_999, pageSize: 10, rowCount: 10 })).toBe(true);
|
||||
expect(canGoNextDocumentPage({ page: 1_000_000, pageSize: 10, rowCount: 3 })).toBe(false);
|
||||
});
|
||||
|
||||
it("treats a short page as an exact total even when the backend estimate is inexact", () => {
|
||||
expect(exactTotalFromIncompleteDocumentPage({ page: 0, pageSize: 500, rowCount: 0 })).toBe(0);
|
||||
expect(exactTotalFromIncompleteDocumentPage({ page: 0, pageSize: 500, rowCount: 1 })).toBe(1);
|
||||
expect(resolveDocumentQueryTotals(1, false, { page: 0, pageSize: 500, rowCount: 1 })).toEqual({
|
||||
total: 1,
|
||||
totalIsExact: true,
|
||||
paginationTotal: 1,
|
||||
});
|
||||
expect(resolveDocumentQueryTotals(50, false, { page: 1, pageSize: 500, rowCount: 12 })).toEqual({
|
||||
total: 512,
|
||||
totalIsExact: true,
|
||||
paginationTotal: 512,
|
||||
});
|
||||
expect(resolveDocumentQueryTotals(658_320, false, { page: 0, pageSize: 500, rowCount: 500 })).toEqual({
|
||||
total: 658_320,
|
||||
totalIsExact: false,
|
||||
paginationTotal: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not infer an exact total from an empty page after the first page", () => {
|
||||
expect(exactTotalFromIncompleteDocumentPage({ page: 1, pageSize: 500, rowCount: 0 })).toBeUndefined();
|
||||
expect(resolveDocumentQueryTotals(658_320, false, { page: 1, pageSize: 500, rowCount: 0 })).toEqual({
|
||||
total: 658_320,
|
||||
totalIsExact: false,
|
||||
paginationTotal: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("matches exact-count requests across identity, filter, and generation", () => {
|
||||
const request: DocumentQueryTotalCountRequest = {
|
||||
connectionId: "connection-a",
|
||||
database: "database-a",
|
||||
collection: "collection-a",
|
||||
filter: '{"active":true}',
|
||||
generation: 3,
|
||||
};
|
||||
expect(isSameDocumentQueryTotalCountRequest(request, { ...request })).toBe(true);
|
||||
expect(isSameDocumentQueryTotalCountRequest(request, { ...request, connectionId: "connection-b" })).toBe(false);
|
||||
expect(isSameDocumentQueryTotalCountRequest(request, { ...request, database: "database-b" })).toBe(false);
|
||||
expect(isSameDocumentQueryTotalCountRequest(request, { ...request, collection: "collection-b" })).toBe(false);
|
||||
expect(isSameDocumentQueryTotalCountRequest(request, { ...request, filter: undefined })).toBe(false);
|
||||
expect(isSameDocumentQueryTotalCountRequest(request, { ...request, generation: 4 })).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -70,8 +70,7 @@ export interface DrawCanvasDataGridOptions {
|
||||
cellIsSelected: (rowIndex: number, visibleColIdx: number) => boolean;
|
||||
cellCanHover: (row: CanvasDataGridRow, actualColIdx: number) => boolean;
|
||||
infiniteScrollEnabled: boolean;
|
||||
pageSize: number;
|
||||
currentPage: number;
|
||||
pageOffset: number;
|
||||
frozenColumnCount?: number;
|
||||
columnAligns?: readonly ("left" | "right")[];
|
||||
rightAlignedActionCell?: CanvasRightAlignedActionCell | null;
|
||||
@@ -267,8 +266,7 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
|
||||
cellIsSelected,
|
||||
cellCanHover,
|
||||
infiniteScrollEnabled,
|
||||
pageSize,
|
||||
currentPage,
|
||||
pageOffset,
|
||||
frozenColumnCount = 0,
|
||||
columnAligns,
|
||||
rightAlignedActionCell,
|
||||
@@ -362,13 +360,18 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
|
||||
ctx.font = item.status === "new" || item.status === "edited" || item.status === "draft" ? semiboldFont : normalFont;
|
||||
ctx.textAlign = "center";
|
||||
const textY = alignCanvasPixel(y + rowTextOffsetY, dpr);
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(0, y, rowNumberWidth, CANVAS_DATA_GRID_ROW_HEIGHT);
|
||||
ctx.clip();
|
||||
if (item.isDraft) {
|
||||
ctx.fillText("*", rowNumberTextX, textY);
|
||||
} else if (infiniteScrollEnabled) {
|
||||
ctx.fillText(String(item.displayIndex + 1), rowNumberTextX, textY);
|
||||
} else {
|
||||
ctx.fillText(String(item.displayIndex + 1 + pageSize * (currentPage - 1)), rowNumberTextX, textY);
|
||||
ctx.fillText(String(item.displayIndex + 1 + pageOffset), rowNumberTextX, textY);
|
||||
}
|
||||
ctx.restore();
|
||||
ctx.font = normalFont;
|
||||
|
||||
ctx.strokeStyle = theme.border;
|
||||
|
||||
@@ -29,6 +29,13 @@ export interface CanFetchNextDataGridSegmentOptions {
|
||||
allRowsLoaded?: boolean;
|
||||
}
|
||||
|
||||
export type DataGridInexactTotalRowCountMode = "at-least" | "estimated";
|
||||
|
||||
export function dataGridTotalRowCountLabelKey(totalRowCountIsExact: boolean, inexactMode: DataGridInexactTotalRowCountMode): "grid.totalRowCount" | "grid.totalRowCountAtLeast" | "grid.totalRowCountEstimated" {
|
||||
if (totalRowCountIsExact) return "grid.totalRowCount";
|
||||
return inexactMode === "estimated" ? "grid.totalRowCountEstimated" : "grid.totalRowCountAtLeast";
|
||||
}
|
||||
|
||||
export function resolveDataGridPaginationTotal(options: { paginationTotalRowCount?: number; serverKnownTotalRowCount?: number; totalRowCountIsExact: boolean }): number | undefined {
|
||||
if (options.paginationTotalRowCount !== undefined) return options.paginationTotalRowCount;
|
||||
return options.totalRowCountIsExact ? options.serverKnownTotalRowCount : undefined;
|
||||
|
||||
@@ -4,7 +4,46 @@ export interface DocumentQueryTotals {
|
||||
paginationTotal: number | undefined;
|
||||
}
|
||||
|
||||
export function resolveDocumentQueryTotals(total: number, totalIsExact: boolean): DocumentQueryTotals {
|
||||
export interface DocumentQueryTotalsPageOptions {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
rowCount?: number;
|
||||
}
|
||||
|
||||
export interface DocumentQueryTotalCountRequest {
|
||||
connectionId: string;
|
||||
database: string;
|
||||
collection: string;
|
||||
filter: string | undefined;
|
||||
generation: number;
|
||||
}
|
||||
|
||||
export function isSameDocumentQueryTotalCountRequest(left: DocumentQueryTotalCountRequest, right: DocumentQueryTotalCountRequest): boolean {
|
||||
return left.connectionId === right.connectionId && left.database === right.database && left.collection === right.collection && left.filter === right.filter && left.generation === right.generation;
|
||||
}
|
||||
|
||||
/** When the current page is short, the true total is exactly offset + rowCount. */
|
||||
export function exactTotalFromIncompleteDocumentPage(options: DocumentQueryTotalsPageOptions): number | undefined {
|
||||
const page = options.page ?? 0;
|
||||
const pageSize = options.pageSize;
|
||||
const rowCount = options.rowCount;
|
||||
if (!Number.isInteger(page) || page < 0 || typeof pageSize !== "number" || !Number.isInteger(pageSize) || pageSize <= 0 || typeof rowCount !== "number" || !Number.isInteger(rowCount) || rowCount < 0 || rowCount >= pageSize || (page > 0 && rowCount === 0)) {
|
||||
return undefined;
|
||||
}
|
||||
return page * pageSize + rowCount;
|
||||
}
|
||||
|
||||
export function resolveDocumentQueryTotals(total: number, totalIsExact: boolean, pageOptions?: DocumentQueryTotalsPageOptions): DocumentQueryTotals {
|
||||
if (!totalIsExact) {
|
||||
const exactFromPage = exactTotalFromIncompleteDocumentPage(pageOptions ?? {});
|
||||
if (typeof exactFromPage === "number") {
|
||||
return {
|
||||
total: exactFromPage,
|
||||
totalIsExact: true,
|
||||
paginationTotal: exactFromPage,
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
total,
|
||||
totalIsExact,
|
||||
|
||||
Reference in new issue
Block a user