Improve compare progress for large BGP sheets.

Show pending check items instead of a fake 100% pass, publish sheet results as each finishes, and use keyset chunk loads with rows-loaded progress.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-10-08 14:17:54 +08:00
parent c5efbbd016
commit 9adf9f2764
5 changed files with 255 additions and 62 deletions

View file

@ -500,6 +500,8 @@ const en = {
runStatusFailed: "Failed",
runStatusDoneSec: "Done {{s}}s",
runProgress: "{{phase}} · sheet {{i}}/{{n}} · {{sheet}} · {{s}}s elapsed",
runRowsLoaded: "Loaded {{side}} {{n}} rows",
sheetPending: "Pending",
ranWithDuration: "Compare finished ({{s}}s)",
unchangedNotStored: "Success rows were counted but not stored. Set “Store success rows” to sample and re-run for spot-check.",
unchangedSampleHint: "{{total}} success rows total; browsing a sample of {{listed}} (hydrated from source tables for cutover spot-check). Pass rate uses all {{total}}.",

View file

@ -499,6 +499,8 @@ const zh = {
runStatusFailed: "失败",
runStatusDoneSec: "完成 {{s}}s",
runProgress: "{{phase}} · 表 {{i}}/{{n}} · {{sheet}} · 已用 {{s}}s",
runRowsLoaded: "已加载 {{side}} {{n}} 行",
sheetPending: "等待中",
ranWithDuration: "比对完成(耗时 {{s}} 秒)",
unchangedNotStored: "成功行仅统计数量未落库。可在任务配置将「成功行保存」改为抽样后重新比对(抽查用)。",
unchangedSampleHint: "成功共 {{total}} 条,明细抽样 {{listed}} 条(从原表补全显示,割接抽查用)。通过率按全部 {{total}} 计。",

View file

@ -12152,6 +12152,15 @@ body:has(.bs-cmp-board:fullscreen) {
box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.22);
}
.bs-cmp-nav__item.is-pending .bs-cmp-nav__dot {
background: #64748b;
box-shadow: 0 0 0 2px rgba(100, 116, 139, 0.25);
}
.bs-cmp-nav__item.is-pending .bs-cmp-nav__name {
opacity: 0.75;
}
.bs-cmp-nav__name {
font-size: 12px;
font-weight: 600;

View file

@ -1198,7 +1198,8 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
Number(c.success_count ?? c.unchanged ?? 0);
const sheetPassRateOf = (c: {
pass_rate?: number;
pass_rate?: number | null;
status?: string;
fail_count?: number;
diff_count?: number;
removed?: number;
@ -1206,7 +1207,10 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
success_count?: number;
unchanged?: number;
added?: number;
}) => {
}): number | null => {
const st = String(c.status || "");
if (st === "pending" || st === "running" || st === "queued") return null;
if (c.pass_rate === null) return null;
// Always recompute from fail/success so added never skews pass rate
const fail = sheetFailOf(c);
const ok = sheetSuccessOf(c);
@ -1222,6 +1226,7 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
title?: string;
metric_id: string;
mode?: string;
status?: string;
added?: number;
removed?: number;
changed?: number;
@ -1231,10 +1236,13 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
fail_count?: number;
success_count?: number;
diff_count?: number;
pass_rate?: number;
pass_rate?: number | null;
}>;
// Failures first so ops can scan quickly when many sheets
// Failures first; keep pending/running at the end so completed fails stay visible
return [...raw].sort((a, b) => {
const pa = ["pending", "running", "queued"].includes(String(a.status || "")) ? 1 : 0;
const pb = ["pending", "running", "queued"].includes(String(b.status || "")) ? 1 : 0;
if (pa !== pb) return pa - pb;
const da = sheetFailOf(a);
const db = sheetFailOf(b);
if (da !== db) return db - da;
@ -1245,6 +1253,9 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
const activeFail = sheetFailOf(activeSheetCard || {});
const activeSuccess = sheetSuccessOf(activeSheetCard || {});
const activePassRate = sheetPassRateOf(activeSheetCard || {});
const activeSheetPending = ["pending", "running", "queued"].includes(
String(activeSheetCard?.status || ""),
);
const activeAdded = Number(activeSheetCard?.added || 0);
const showFailCol =
kindFilter === "diff" || kindFilter === "all" || kindFilter === "added";
@ -1868,6 +1879,8 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
before_count?: number;
after_count?: number;
diff_rows?: number;
load_side?: string;
rows_loaded?: number;
};
// Poll active compare runs so the modal can be closed and reopened safely.
@ -3020,6 +3033,14 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
s: String(Math.round(Number(runProgress.elapsed_ms || 0) / 1000)),
})}
</span>
{Number(runProgress.rows_loaded || 0) > 0 ? (
<span className="muted">
{t("bizCompare.runRowsLoaded", {
side: String(runProgress.load_side || "—"),
n: String(runProgress.rows_loaded || 0),
})}
</span>
) : null}
{runDetail.message ? (
<span className="muted bs-cmp-progress__msg">{String(runDetail.message)}</span>
) : null}
@ -3098,11 +3119,14 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
const unchanged = Number(s.summary?.unchanged || 0);
const fail = removed + changed;
const judged = fail + unchanged;
const st = String((s as any).status || "done");
const pending = ["pending", "running", "queued"].includes(st);
return {
sheet_id: sheetIdentity(s),
title: sheetLabel(s),
metric_id: s.metric_id,
mode: s.mode,
status: st,
added: s.summary?.added,
removed,
changed,
@ -3112,9 +3136,11 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
fail_count: fail,
success_count: unchanged,
diff_count: fail,
pass_rate: judged
? Math.round((unchanged / judged) * 1000) / 10
: 100,
pass_rate: pending
? null
: judged
? Math.round((unchanged / judged) * 1000) / 10
: 100,
};
})
).map((c) => {
@ -3124,6 +3150,9 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
const id = sheetIdentity(c);
const label = sheetLabel(c);
const active = resultSheetId === id;
const pending = ["pending", "running", "queued"].includes(
String((c as any).status || ""),
);
return (
<button
key={id}
@ -3131,10 +3160,14 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
role="tab"
aria-selected={active}
className={`bs-cmp-nav__item${active ? " is-active" : ""}${
fail > 0 ? " has-diff" : " is-clean"
pending ? " is-pending" : fail > 0 ? " has-diff" : " is-clean"
}`}
onClick={() => setResultSheetId(id)}
title={`${label} · ${t("bizCompare.kindFail")} ${fail} · ${t("bizCompare.kindSuccess")} ${ok} · ${t("bizCompare.passRateShort")} ${rate}%`}
title={
pending
? `${label} · ${t("bizCompare.sheetPending")}`
: `${label} · ${t("bizCompare.kindFail")} ${fail} · ${t("bizCompare.kindSuccess")} ${ok} · ${t("bizCompare.passRateShort")} ${rate}%`
}
>
<span className="bs-cmp-nav__dot" aria-hidden />
<span className="bs-cmp-nav__name">{label}</span>
@ -3144,13 +3177,13 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
fail > 0 ? " is-hot" : ""
}`}
>
{fail}
{pending ? "—" : fail}
</span>
<span className="bs-cmp-nav__num bs-cmp-nav__num--ok is-hot">
{ok}
{pending ? "—" : ok}
</span>
<span className="bs-cmp-nav__num bs-cmp-nav__num--rate">
{rate}%
{pending || rate === null ? "…" : `${rate}%`}
</span>
</span>
</button>
@ -3161,7 +3194,13 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
<div className="bs-cmp-main">
<div
className={`bs-cmp-strip${activeFail > 0 ? " is-warn" : " is-ok"}`}
className={`bs-cmp-strip${
activeSheetPending
? " is-pending"
: activeFail > 0
? " is-warn"
: " is-ok"
}`}
>
<div className="bs-cmp-strip__sheet">
<span className="bs-cmp-strip__sheet-tag">
@ -3174,18 +3213,28 @@ export function BizComparePage({ pageMode = "all" }: { pageMode?: BizComparePage
{sheetLabel(activeSheetCard)}
</strong>
<span className="muted bs-cmp-strip__sheet-mode">
{activeFail > 0 ? t("bizCompare.kindFail") : t("bizCompare.kindPass")}
{activeSheetPending
? t("bizCompare.sheetPending")
: activeFail > 0
? t("bizCompare.kindFail")
: t("bizCompare.kindPass")}
{activeSheetCard?.mode === "presence"
? ` · ${t("bizCompare.presenceShort")}`
: ""}
{` · ${activeSheetCard?.before_count ?? 0}→${activeSheetCard?.after_count ?? 0}`}
{activeSheetPending
? ""
: ` · ${activeSheetCard?.before_count ?? 0}→${activeSheetCard?.after_count ?? 0}`}
</span>
</div>
<div className="bs-cmp-strip__pass" title={t("bizCompare.passRate")}>
<span className="bs-cmp-strip__pass-label">
{t("bizCompare.passRate")}
</span>
<span className="bs-cmp-strip__pass-value">{activePassRate}%</span>
<span className="bs-cmp-strip__pass-value">
{activeSheetPending || activePassRate === null
? "…"
: `${activePassRate}%`}
</span>
</div>
<div className="bs-cmp-strip__kinds" role="group">
{(