fix(grid): hydrate large values across result changes
This commit is contained in:
4 files changed
+99
-22
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { appendLargeValueCells, canUseTableDataLargeValuePreview, largeValueCellMap, remapLargeValueCells, TABLE_DATA_CELL_PREVIEW_SIZE, TABLE_DATA_PREVIEW_CONTENT_MAX_BYTES, tableDataLargeValuePreviewOptions } from "@/lib/dataGrid/dataGridLargeValues";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { appendLargeValueCells, canUseTableDataLargeValuePreview, createResultScopedPendingRequests, largeValueCellMap, remapLargeValueCells, TABLE_DATA_CELL_PREVIEW_SIZE, TABLE_DATA_PREVIEW_CONTENT_MAX_BYTES, tableDataLargeValuePreviewOptions } from "@/lib/dataGrid/dataGridLargeValues";
|
||||
import { buildDataGridCellDetail } from "@/lib/dataGrid/dataGridDetail";
|
||||
import type { ColumnInfo } from "@/types/database";
|
||||
|
||||
@@ -14,6 +14,14 @@ function column(name: string, dataType: string, isPrimaryKey = false): ColumnInf
|
||||
};
|
||||
}
|
||||
|
||||
function deferred<T>() {
|
||||
let resolve!: (value: T) => void;
|
||||
const promise = new Promise<T>((resolvePromise) => {
|
||||
resolve = resolvePromise;
|
||||
});
|
||||
return { promise, resolve };
|
||||
}
|
||||
|
||||
describe("data grid large-value metadata", () => {
|
||||
it("enables bounded table previews only for supported databases with stable keys", () => {
|
||||
const columns = [column("id", "bigint", true), column("payload", "longtext")];
|
||||
@@ -97,6 +105,46 @@ describe("data grid large-value metadata", () => {
|
||||
expect(largeValueCellMap(result).get("4:2")).toEqual(result.large_value_cells[0]);
|
||||
});
|
||||
|
||||
it("deduplicates pending requests only within the same result", async () => {
|
||||
const requests = createResultScopedPendingRequests<boolean>();
|
||||
const result = {};
|
||||
const load = deferred<boolean>();
|
||||
const request = vi.fn(() => load.promise);
|
||||
|
||||
const first = requests.run("0:1", result, request);
|
||||
const second = requests.run("0:1", result, request);
|
||||
await Promise.resolve();
|
||||
|
||||
expect(second).toBe(first);
|
||||
expect(request).toHaveBeenCalledOnce();
|
||||
|
||||
load.resolve(true);
|
||||
await expect(first).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it("keeps the newer result request after the previous result settles", async () => {
|
||||
const requests = createResultScopedPendingRequests<boolean>();
|
||||
const previousLoad = deferred<boolean>();
|
||||
const currentLoad = deferred<boolean>();
|
||||
const previousResult = {};
|
||||
const currentResult = {};
|
||||
const currentRequest = vi.fn(() => currentLoad.promise);
|
||||
|
||||
const previous = requests.run("0:1", previousResult, () => previousLoad.promise);
|
||||
const current = requests.run("0:1", currentResult, currentRequest);
|
||||
await Promise.resolve();
|
||||
|
||||
previousLoad.resolve(false);
|
||||
await expect(previous).resolves.toBe(false);
|
||||
|
||||
const deduplicatedCurrent = requests.run("0:1", currentResult, currentRequest);
|
||||
expect(deduplicatedCurrent).toBe(current);
|
||||
expect(currentRequest).toHaveBeenCalledOnce();
|
||||
|
||||
currentLoad.resolve(true);
|
||||
await expect(current).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it("marks a backend-bounded value as truncated even when the preview is shorter than the UI limit", () => {
|
||||
const detail = buildDataGridCellDetail({
|
||||
rowIndex: 0,
|
||||
|
||||
@@ -31,6 +31,27 @@ export function largeValueCellMap(result: Pick<QueryResult, "large_value_cells">
|
||||
return new Map((result.large_value_cells ?? []).map((cell) => [largeValueCellKey(cell.row_index, cell.column_index), cell]));
|
||||
}
|
||||
|
||||
export function createResultScopedPendingRequests<T>() {
|
||||
const pending = new Map<string, { result: object; promise: Promise<T> }>();
|
||||
|
||||
return {
|
||||
run(key: string, result: object, request: () => Promise<T>): Promise<T> {
|
||||
const current = pending.get(key);
|
||||
if (current?.result === result) return current.promise;
|
||||
|
||||
let entry: { result: object; promise: Promise<T> };
|
||||
const promise = Promise.resolve()
|
||||
.then(request)
|
||||
.finally(() => {
|
||||
if (pending.get(key) === entry) pending.delete(key);
|
||||
});
|
||||
entry = { result, promise };
|
||||
pending.set(key, entry);
|
||||
return promise;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function appendLargeValueCells(previous: QueryResult["large_value_cells"], segment: QueryResult["large_value_cells"], rowOffset: number, appendedRowCount: number): QueryResult["large_value_cells"] {
|
||||
const appended = (segment ?? []).filter((cell) => cell.row_index < appendedRowCount).map((cell) => ({ ...cell, row_index: cell.row_index + rowOffset }));
|
||||
const combined = [...(previous ?? []), ...appended];
|
||||
|
||||
Reference in new issue
Block a user