mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 02:00:46 +08:00
Simplify compare verdict to fail, success, and pass rate.
Treat missing+mismatch as fail, match as success, and added as a special filter; show three right-aligned sheet badges. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
357b831f2d
commit
bb00ec91f4
6 changed files with 189 additions and 148 deletions
|
|
@ -364,17 +364,20 @@ const en = {
|
|||
resultEmpty: "No matching diff rows",
|
||||
resultFilterPh: "Filter key / values…",
|
||||
kindAll: "All",
|
||||
kindDiff: "Failures only",
|
||||
kindAdded: "Extra only",
|
||||
kindDiff: "Fail",
|
||||
kindAdded: "Added",
|
||||
kindRemoved: "Missing only",
|
||||
kindChanged: "Mismatch only",
|
||||
kindUnchanged: "Match only",
|
||||
kindAddedShort: "Extra",
|
||||
kindUnchanged: "Pass",
|
||||
kindAddedShort: "Added",
|
||||
kindRemovedShort: "Missing",
|
||||
kindChangedShort: "Mismatch",
|
||||
kindUnchangedShort: "Match",
|
||||
kindUnchangedShort: "Pass",
|
||||
kindFail: "Fail",
|
||||
kindPass: "Pass",
|
||||
kindSuccess: "Pass",
|
||||
successCount: "Pass",
|
||||
passRateShort: "Pass %",
|
||||
verdictPass: "Pass",
|
||||
verdictFail: "Fail",
|
||||
failCount: "Failed rows",
|
||||
|
|
|
|||
|
|
@ -364,17 +364,20 @@ const zh = {
|
|||
resultEmpty: "无匹配失败/结果行",
|
||||
resultFilterPh: "筛选身份 / 字段值…",
|
||||
kindAll: "全部",
|
||||
kindDiff: "仅失败",
|
||||
kindAdded: "仅多余",
|
||||
kindDiff: "失败",
|
||||
kindAdded: "新增",
|
||||
kindRemoved: "仅缺失",
|
||||
kindChanged: "仅不一致",
|
||||
kindUnchanged: "仅一致",
|
||||
kindAddedShort: "多余",
|
||||
kindUnchanged: "成功",
|
||||
kindAddedShort: "新增",
|
||||
kindRemovedShort: "缺失",
|
||||
kindChangedShort: "不一致",
|
||||
kindUnchangedShort: "一致",
|
||||
kindUnchangedShort: "成功",
|
||||
kindFail: "失败",
|
||||
kindPass: "通过",
|
||||
kindPass: "成功",
|
||||
kindSuccess: "成功",
|
||||
successCount: "成功",
|
||||
passRateShort: "通过率",
|
||||
verdictPass: "通过",
|
||||
verdictFail: "不通过",
|
||||
failCount: "失败条目",
|
||||
|
|
|
|||
|
|
@ -10263,8 +10263,8 @@ html.login-page--paused .login-page__flare {
|
|||
|
||||
.bs-cmp-nav__legend {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1.6em);
|
||||
gap: 2px;
|
||||
grid-template-columns: repeat(3, minmax(1.6em, auto));
|
||||
gap: 4px 6px;
|
||||
justify-items: end;
|
||||
margin-top: 6px;
|
||||
margin-left: auto;
|
||||
|
|
@ -10273,6 +10273,40 @@ html.login-page--paused .login-page__flare {
|
|||
font-weight: 700;
|
||||
}
|
||||
|
||||
.bs-cmp-nav__stats {
|
||||
display: inline-grid;
|
||||
grid-template-columns: repeat(3, minmax(1.6em, auto));
|
||||
gap: 2px 6px;
|
||||
justify-items: end;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.bs-cmp-nav__num {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: #475569;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.bs-cmp-nav__num--fail.is-hot {
|
||||
color: #f87171;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.bs-cmp-nav__num--ok.is-hot {
|
||||
color: #4ade80;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.bs-cmp-nav__num--rate {
|
||||
color: #93c5fd;
|
||||
opacity: 0.95;
|
||||
min-width: 2.6em;
|
||||
}
|
||||
|
||||
.bs-cmp-nav__list {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
|
|
@ -10336,44 +10370,6 @@ html.login-page--paused .login-page__flare {
|
|||
min-width: 0;
|
||||
}
|
||||
|
||||
.bs-cmp-nav__stats {
|
||||
display: inline-grid;
|
||||
grid-template-columns: repeat(4, 1.6em);
|
||||
gap: 2px;
|
||||
justify-items: end;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.bs-cmp-nav__num {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: #475569;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.bs-cmp-nav__num--removed.is-hot {
|
||||
color: #f87171;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.bs-cmp-nav__num--added.is-hot {
|
||||
color: #4ade80;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.bs-cmp-nav__num--changed.is-hot {
|
||||
color: #fbbf24;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.bs-cmp-nav__num--unchanged {
|
||||
color: #94a3b8;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.bs-cmp-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
|
|
@ -795,17 +795,51 @@ export function BizComparePage() {
|
|||
return kind;
|
||||
};
|
||||
|
||||
const verdictLabel = (kind: string) => {
|
||||
if (kind === "unchanged") return t("bizCompare.kindPass");
|
||||
return t("bizCompare.kindFail");
|
||||
/** Table verdict: fail = missing+mismatch; success = match; added is special. */
|
||||
const rowVerdict = (kind: string): { label: string; tone: "fail" | "pass" | "added" } => {
|
||||
if (kind === "added") return { label: t("bizCompare.kindAddedShort"), tone: "added" };
|
||||
if (kind === "unchanged") return { label: t("bizCompare.kindSuccess"), tone: "pass" };
|
||||
return { label: t("bizCompare.kindFail"), tone: "fail" };
|
||||
};
|
||||
|
||||
const failFieldsLabel = (d: DiffRow) => {
|
||||
if (d.kind === "added" || d.kind === "removed") return t("bizCompare.failWholeRow");
|
||||
if (d.kind === "added") return t("bizCompare.kindAddedShort");
|
||||
if (d.kind === "removed") return t("bizCompare.failWholeRow");
|
||||
const names = failFieldNames(d);
|
||||
return names.length ? names.join(" · ") : t("bizCompare.failFieldsEmpty");
|
||||
};
|
||||
|
||||
const sheetFailOf = (c: {
|
||||
fail_count?: number;
|
||||
diff_count?: number;
|
||||
removed?: number;
|
||||
changed?: number;
|
||||
}) =>
|
||||
Number(
|
||||
c.fail_count ??
|
||||
c.diff_count ??
|
||||
Number(c.removed || 0) + Number(c.changed || 0),
|
||||
);
|
||||
|
||||
const sheetSuccessOf = (c: { success_count?: number; unchanged?: number }) =>
|
||||
Number(c.success_count ?? c.unchanged ?? 0);
|
||||
|
||||
const sheetPassRateOf = (c: {
|
||||
pass_rate?: number;
|
||||
fail_count?: number;
|
||||
diff_count?: number;
|
||||
removed?: number;
|
||||
changed?: number;
|
||||
success_count?: number;
|
||||
unchanged?: number;
|
||||
}) => {
|
||||
if (c.pass_rate !== undefined && c.pass_rate !== null) return Number(c.pass_rate);
|
||||
const fail = sheetFailOf(c);
|
||||
const ok = sheetSuccessOf(c);
|
||||
const judged = fail + ok;
|
||||
return judged ? Math.round((ok / judged) * 1000) / 10 : 100;
|
||||
};
|
||||
|
||||
const summary = runDetail?.summary || {};
|
||||
const resultPages = Math.max(1, Math.ceil(resultTotal / Math.max(1, resultPageSize)));
|
||||
const sheetCards = useMemo(() => {
|
||||
|
|
@ -818,20 +852,25 @@ export function BizComparePage() {
|
|||
unchanged?: number;
|
||||
before_count?: number;
|
||||
after_count?: number;
|
||||
fail_count?: number;
|
||||
success_count?: number;
|
||||
diff_count?: number;
|
||||
pass_rate?: number;
|
||||
}>;
|
||||
// Diffs first so ops can scan quickly when many sheets
|
||||
// Failures first so ops can scan quickly when many sheets
|
||||
return [...raw].sort((a, b) => {
|
||||
const da = Number(a.diff_count || 0);
|
||||
const db = Number(b.diff_count || 0);
|
||||
const da = sheetFailOf(a);
|
||||
const db = sheetFailOf(b);
|
||||
if (da !== db) return db - da;
|
||||
return String(a.metric_id).localeCompare(String(b.metric_id));
|
||||
});
|
||||
}, [summary.sheet_cards]);
|
||||
const sheetFailCount = sheetCards.filter((c) => Number(c.diff_count || 0) > 0).length;
|
||||
const sheetFailCount = sheetCards.filter((c) => sheetFailOf(c) > 0).length;
|
||||
const activeSheetCard = sheetCards.find((c) => c.metric_id === resultSheetId) || sheetCards[0];
|
||||
|
||||
const activeFail = sheetFailOf(activeSheetCard || {});
|
||||
const activeSuccess = sheetSuccessOf(activeSheetCard || {});
|
||||
const activePassRate = sheetPassRateOf(activeSheetCard || {});
|
||||
const activeAdded = Number(activeSheetCard?.added || 0);
|
||||
useEffect(() => {
|
||||
const syncFs = () => {
|
||||
const el = boardRef.current;
|
||||
|
|
@ -2247,51 +2286,55 @@ export function BizComparePage() {
|
|||
</div>
|
||||
<div className="bs-cmp-nav__legend" aria-hidden>
|
||||
<span
|
||||
className="bs-cmp-nav__num bs-cmp-nav__num--removed is-hot"
|
||||
title={t("bizCompare.missCount")}
|
||||
className="bs-cmp-nav__num bs-cmp-nav__num--fail is-hot"
|
||||
title={t("bizCompare.kindFail")}
|
||||
>
|
||||
-
|
||||
{t("bizCompare.kindFail")}
|
||||
</span>
|
||||
<span
|
||||
className="bs-cmp-nav__num bs-cmp-nav__num--added is-hot"
|
||||
title={t("bizCompare.extraCount")}
|
||||
className="bs-cmp-nav__num bs-cmp-nav__num--ok is-hot"
|
||||
title={t("bizCompare.kindSuccess")}
|
||||
>
|
||||
+
|
||||
{t("bizCompare.kindSuccess")}
|
||||
</span>
|
||||
<span
|
||||
className="bs-cmp-nav__num bs-cmp-nav__num--changed is-hot"
|
||||
title={t("bizCompare.mismatchCount")}
|
||||
className="bs-cmp-nav__num bs-cmp-nav__num--rate"
|
||||
title={t("bizCompare.passRateShort")}
|
||||
>
|
||||
~
|
||||
</span>
|
||||
<span
|
||||
className="bs-cmp-nav__num bs-cmp-nav__num--unchanged"
|
||||
title={t("bizCompare.matchCount")}
|
||||
>
|
||||
=
|
||||
%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bs-cmp-nav__list" role="tablist">
|
||||
{(sheetCards.length
|
||||
? sheetCards
|
||||
: runSheets.map((s) => ({
|
||||
metric_id: s.metric_id,
|
||||
mode: s.mode,
|
||||
added: s.summary?.added,
|
||||
removed: s.summary?.removed,
|
||||
changed: s.summary?.changed,
|
||||
unchanged: s.summary?.unchanged,
|
||||
before_count: s.summary?.before_count,
|
||||
after_count: s.summary?.after_count,
|
||||
diff_count:
|
||||
Number(s.summary?.added || 0) +
|
||||
Number(s.summary?.removed || 0) +
|
||||
Number(s.summary?.changed || 0),
|
||||
pass_rate: s.summary?.pass_rate,
|
||||
}))
|
||||
: runSheets.map((s) => {
|
||||
const removed = Number(s.summary?.removed || 0);
|
||||
const changed = Number(s.summary?.changed || 0);
|
||||
const unchanged = Number(s.summary?.unchanged || 0);
|
||||
const fail = removed + changed;
|
||||
const judged = fail + unchanged;
|
||||
return {
|
||||
metric_id: s.metric_id,
|
||||
mode: s.mode,
|
||||
added: s.summary?.added,
|
||||
removed,
|
||||
changed,
|
||||
unchanged,
|
||||
before_count: s.summary?.before_count,
|
||||
after_count: s.summary?.after_count,
|
||||
fail_count: fail,
|
||||
success_count: unchanged,
|
||||
diff_count: fail,
|
||||
pass_rate: judged
|
||||
? Math.round((unchanged / judged) * 1000) / 10
|
||||
: 100,
|
||||
};
|
||||
})
|
||||
).map((c) => {
|
||||
const dirty = Number(c.diff_count || 0);
|
||||
const fail = sheetFailOf(c);
|
||||
const ok = sheetSuccessOf(c);
|
||||
const rate = sheetPassRateOf(c);
|
||||
const active = resultSheetId === c.metric_id;
|
||||
return (
|
||||
<button
|
||||
|
|
@ -2300,40 +2343,26 @@ export function BizComparePage() {
|
|||
role="tab"
|
||||
aria-selected={active}
|
||||
className={`bs-cmp-nav__item${active ? " is-active" : ""}${
|
||||
dirty > 0 ? " has-diff" : " is-clean"
|
||||
fail > 0 ? " has-diff" : " is-clean"
|
||||
}`}
|
||||
onClick={() => setResultSheetId(c.metric_id)}
|
||||
title={c.metric_id}
|
||||
title={`${c.metric_id} · ${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">{c.metric_id}</span>
|
||||
<span
|
||||
className="bs-cmp-nav__stats"
|
||||
title={`${t("bizCompare.missCount")} ${Number(c.removed || 0)} · ${t("bizCompare.extraCount")} ${Number(c.added || 0)} · ${t("bizCompare.mismatchCount")} ${Number(c.changed || 0)} · ${t("bizCompare.matchCount")} ${Number(c.unchanged || 0)}`}
|
||||
>
|
||||
<span className="bs-cmp-nav__stats">
|
||||
<span
|
||||
className={`bs-cmp-nav__num bs-cmp-nav__num--removed${
|
||||
Number(c.removed || 0) > 0 ? " is-hot" : ""
|
||||
className={`bs-cmp-nav__num bs-cmp-nav__num--fail${
|
||||
fail > 0 ? " is-hot" : ""
|
||||
}`}
|
||||
>
|
||||
{Number(c.removed || 0)}
|
||||
{fail}
|
||||
</span>
|
||||
<span
|
||||
className={`bs-cmp-nav__num bs-cmp-nav__num--added${
|
||||
Number(c.added || 0) > 0 ? " is-hot" : ""
|
||||
}`}
|
||||
>
|
||||
{Number(c.added || 0)}
|
||||
<span className="bs-cmp-nav__num bs-cmp-nav__num--ok is-hot">
|
||||
{ok}
|
||||
</span>
|
||||
<span
|
||||
className={`bs-cmp-nav__num bs-cmp-nav__num--changed${
|
||||
Number(c.changed || 0) > 0 ? " is-hot" : ""
|
||||
}`}
|
||||
>
|
||||
{Number(c.changed || 0)}
|
||||
</span>
|
||||
<span className="bs-cmp-nav__num bs-cmp-nav__num--unchanged">
|
||||
{Number(c.unchanged || 0)}
|
||||
<span className="bs-cmp-nav__num bs-cmp-nav__num--rate">
|
||||
{rate}%
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
|
|
@ -2344,9 +2373,7 @@ export function BizComparePage() {
|
|||
|
||||
<div className="bs-cmp-main">
|
||||
<div
|
||||
className={`bs-cmp-strip${
|
||||
Number(activeSheetCard?.diff_count || 0) > 0 ? " is-warn" : " is-ok"
|
||||
}`}
|
||||
className={`bs-cmp-strip${activeFail > 0 ? " is-warn" : " is-ok"}`}
|
||||
>
|
||||
<div className="bs-cmp-strip__sheet">
|
||||
<span className="bs-cmp-strip__sheet-tag">
|
||||
|
|
@ -2359,9 +2386,8 @@ export function BizComparePage() {
|
|||
{activeSheetCard?.metric_id || "—"}
|
||||
</strong>
|
||||
<span className="muted bs-cmp-strip__sheet-mode">
|
||||
{Number(activeSheetCard?.diff_count || 0) > 0
|
||||
? t("bizCompare.kindFail")
|
||||
: t("bizCompare.kindPass")}
|
||||
{activeFail > 0 ? t("bizCompare.kindFail") : t("bizCompare.kindPass")}
|
||||
{` · ${t("bizCompare.passRateShort")} ${activePassRate}%`}
|
||||
{activeSheetCard?.mode === "presence"
|
||||
? ` · ${t("bizCompare.presenceShort")}`
|
||||
: ""}
|
||||
|
|
@ -2370,11 +2396,9 @@ export function BizComparePage() {
|
|||
<div className="bs-cmp-strip__kinds" role="group">
|
||||
{(
|
||||
[
|
||||
["diff", Number(activeSheetCard?.diff_count || 0), "diff"],
|
||||
["removed", activeSheetCard?.removed ?? 0, "removed"],
|
||||
["added", activeSheetCard?.added ?? 0, "added"],
|
||||
["changed", activeSheetCard?.changed ?? 0, "changed"],
|
||||
["unchanged", activeSheetCard?.unchanged ?? 0, "unchanged"],
|
||||
["diff", activeFail, "diff"],
|
||||
["added", activeAdded, "added"],
|
||||
["unchanged", activeSuccess, "unchanged"],
|
||||
["all", null, "all"],
|
||||
] as const
|
||||
).map(([id, n, cls]) => (
|
||||
|
|
@ -2390,7 +2414,9 @@ export function BizComparePage() {
|
|||
? t("bizCompare.kindDiff")
|
||||
: id === "all"
|
||||
? t("bizCompare.kindAll")
|
||||
: kindLabel(id)}
|
||||
: id === "added"
|
||||
? t("bizCompare.kindAddedShort")
|
||||
: t("bizCompare.kindSuccess")}
|
||||
{n !== null ? (
|
||||
<>
|
||||
{" "}
|
||||
|
|
@ -2473,23 +2499,24 @@ export function BizComparePage() {
|
|||
const post = pickSideRow(
|
||||
d.after as Record<string, unknown> | null | undefined,
|
||||
);
|
||||
const isFail = d.kind !== "unchanged";
|
||||
const verdict = rowVerdict(d.kind);
|
||||
const isFail = verdict.tone === "fail";
|
||||
return (
|
||||
<tr key={i} className={`bs-cmp-row bs-cmp-row--${d.kind}`}>
|
||||
<td className="bs-cmp-col-kind">
|
||||
<span
|
||||
className={`bs-cmp-badge bs-cmp-badge--${
|
||||
isFail ? "fail" : "pass"
|
||||
}`}
|
||||
>
|
||||
{verdictLabel(d.kind)}
|
||||
</span>
|
||||
<span className={`bs-cmp-badge bs-cmp-badge--${d.kind}`}>
|
||||
{kindLabel(d.kind)}
|
||||
<span className={`bs-cmp-badge bs-cmp-badge--${verdict.tone}`}>
|
||||
{verdict.label}
|
||||
</span>
|
||||
{isFail ? (
|
||||
<span className={`bs-cmp-badge bs-cmp-badge--${d.kind}`}>
|
||||
{kindLabel(d.kind)}
|
||||
</span>
|
||||
) : null}
|
||||
</td>
|
||||
<td className="bs-cmp-fail-cell">
|
||||
{isFail ? failFieldsLabel(d) : t("bizCompare.failFieldsEmpty")}
|
||||
{verdict.tone === "pass"
|
||||
? t("bizCompare.failFieldsEmpty")
|
||||
: failFieldsLabel(d)}
|
||||
</td>
|
||||
{resultColumns.keys.map((k) => (
|
||||
<td key={k} className="bs-cmp-key-cell">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue