feat(sql-file): show byte-based import progress

Closes #9440
This commit is contained in:
t8y2 committed 2026-09-17 18:37:56 +08:00
1 parent fb25a66d7a
commit be251d596e
14 files changed
+497 -43

No files matched your search

+34
View File
@@ -0,0 +1,34 @@
import { describe, expect, it } from "vitest";
import { formatSqlFileBytes, sqlFileProgressPercent } from "./sqlFileProgress";
describe("SQL file byte progress", () => {
it.each(["running", "statementDone", "statementFailed", "error", "cancelled", "Running", "Error", "Cancelled"])("uses bytes without reporting completion for %s", (status) => {
expect(sqlFileProgressPercent(status, 250, 1000)).toBe(25);
expect(sqlFileProgressPercent(status, 1000, 1000)).toBe(99);
expect(sqlFileProgressPercent(status, 1200, 1000)).toBe(99);
});
it.each(["done", "Done"])("completes empty files and legacy events only after %s", (status) => {
expect(sqlFileProgressPercent(status, 0, 0)).toBe(100);
expect(sqlFileProgressPercent(status)).toBe(100);
});
it("does not invent a percentage for missing or invalid totals", () => {
expect(sqlFileProgressPercent("running")).toBeNull();
expect(sqlFileProgressPercent("running", 50)).toBeNull();
expect(sqlFileProgressPercent("running", undefined, 100)).toBeNull();
expect(sqlFileProgressPercent("running", 0, 0)).toBeNull();
expect(sqlFileProgressPercent("running", -1, 100)).toBeNull();
expect(sqlFileProgressPercent("running", 1, Number.NaN)).toBeNull();
expect(sqlFileProgressPercent("running", Number.POSITIVE_INFINITY, 100)).toBeNull();
expect(sqlFileProgressPercent("running", 0, 100)).toBe(0);
});
it("formats byte counts for small and large files", () => {
expect(formatSqlFileBytes(0)).toBe("0 B");
expect(formatSqlFileBytes(512)).toBe("512 B");
expect(formatSqlFileBytes(1536)).toBe("1.5 KB");
expect(formatSqlFileBytes(5 * 1024 ** 3)).toBe("5.0 GB");
expect(formatSqlFileBytes(1024 ** 4)).toBe("1.0 TB");
});
});
@@ -0,0 +1,16 @@
export function sqlFileProgressPercent(status: string, bytesRead?: number, totalBytes?: number): number | null {
if (status.toLowerCase() === "done") return 100;
if (bytesRead === undefined || totalBytes === undefined || !Number.isFinite(bytesRead) || !Number.isFinite(totalBytes) || bytesRead < 0 || totalBytes <= 0) return null;
return Math.min(99, Math.floor((bytesRead / totalBytes) * 100));
}
export function formatSqlFileBytes(bytes: number): string {
const units = ["B", "KB", "MB", "GB", "TB"];
let value = Math.max(0, bytes);
let unitIndex = 0;
while (value >= 1024 && unitIndex < units.length - 1) {
value /= 1024;
unitIndex += 1;
}
return `${unitIndex === 0 ? value : value.toFixed(1)} ${units[unitIndex]}`;
}