feat(FN-4716): complete Step 2 — invert reliability headline to success rate

Fusion-Task-Id: FN-4716
Fusion-Task-Lineage: 3893e8c0-ee1f-4d32-8e18-fd747b0d955d
This commit is contained in:
Fusion (runfusion.ai)
2026-05-16 00:04:47 -07:00
committed by gsxdsm
parent 196b7e439f
commit 414e62d39d
3 changed files with 46 additions and 10 deletions

View File

@@ -0,0 +1,5 @@
---
"@runfusion/fusion": patch
---
Reliability view: headline now shows in-review success rate (e.g. 100.0% when no bounces) instead of the raw failure rate. API field `inReviewFailureRate7d` is unchanged.

View File

@@ -71,13 +71,18 @@ export function ReliabilityView() {
return () => clearInterval(pollInterval); return () => clearInterval(pollInterval);
}, [load]); }, [load]);
const failureRate = data?.headline.inReviewFailureRate7d;
const reliabilityRate = useMemo(() => {
if (failureRate === null || failureRate === undefined) return null;
return Math.max(0, Math.min(1, 1 - failureRate));
}, [failureRate]);
const headlineColorVar = useMemo(() => { const headlineColorVar = useMemo(() => {
const rate = data?.headline.inReviewFailureRate7d; if (reliabilityRate === null) return "var(--text-muted)";
if (rate === null || rate === undefined) return "var(--text-muted)"; if (reliabilityRate >= 0.95) return "var(--color-success)";
if (rate < 0.05) return "var(--color-success)"; if (reliabilityRate >= 0.9) return "var(--color-warning)";
if (rate < 0.1) return "var(--color-warning)";
return "var(--color-error)"; return "var(--color-error)";
}, [data]); }, [reliabilityRate]);
const totalEntered = useMemo(() => (data?.perDay ?? []).reduce((sum, row) => sum + row.tasksEnteredInReview, 0), [data?.perDay]); const totalEntered = useMemo(() => (data?.perDay ?? []).reduce((sum, row) => sum + row.tasksEnteredInReview, 0), [data?.perDay]);
const totalBounced = useMemo(() => (data?.perDay ?? []).reduce((sum, row) => sum + row.tasksBouncedToInProgress, 0), [data?.perDay]); const totalBounced = useMemo(() => (data?.perDay ?? []).reduce((sum, row) => sum + row.tasksBouncedToInProgress, 0), [data?.perDay]);
@@ -104,15 +109,17 @@ export function ReliabilityView() {
<button className="btn btn-danger btn-sm" onClick={() => setShowResetConfirm(true)}>Reset stats</button> <button className="btn btn-danger btn-sm" onClick={() => setShowResetConfirm(true)}>Reset stats</button>
</div> </div>
<div className="reliability-headline" style={{ color: headlineColorVar }}> <div className="reliability-headline" style={{ color: headlineColorVar }}>
{data?.headline.inReviewFailureRate7d === null || data?.headline.inReviewFailureRate7d === undefined {failureRate === null || failureRate === undefined
? `Insufficient data — ${data?.headline.reason ?? "unknown"}` ? `Insufficient data — ${data?.headline.reason ?? "unknown"}`
: formatPercent(data.headline.inReviewFailureRate7d)} : formatPercent(reliabilityRate ?? 0)}
</div> </div>
{reliabilityRate !== null ? <div className="reliability-muted">In-review success rate (last 7d)</div> : null}
{data?.resetAt ? <div className="reliability-muted">{`Counting since ${formatDateTime(data.resetAt)}`}</div> : null} {data?.resetAt ? <div className="reliability-muted">{`Counting since ${formatDateTime(data.resetAt)}`}</div> : null}
<details className="reliability-details"> <details className="reliability-details">
<summary>Details</summary> <summary>Details</summary>
<div className="reliability-details-content"> <div className="reliability-details-content">
<div>{`${totalBounced} bounced / ${totalEntered} entered (last ${data?.windowDays ?? 7}d)`}</div> <div>{`${totalBounced} bounced / ${totalEntered} entered (last ${data?.windowDays ?? 7}d)`}</div>
{failureRate !== null && failureRate !== undefined ? <div>{`Failure rate: ${formatPercent(failureRate)}`}</div> : null}
<div>{`Window: ${windowStartLabel} → ${formatDateTime(data?.generatedAt)}`}</div> <div>{`Window: ${windowStartLabel} → ${formatDateTime(data?.generatedAt)}`}</div>
{data?.resetAt ? <div>{`Reset baseline: ${formatDateTime(data.resetAt)}`}</div> : null} {data?.resetAt ? <div>{`Reset baseline: ${formatDateTime(data.resetAt)}`}</div> : null}
{data?.headline.reason ? <div>{`Reason: ${data.headline.reason}`}</div> : null} {data?.headline.reason ? <div>{`Reason: ${data.headline.reason}`}</div> : null}

View File

@@ -41,7 +41,7 @@ describe("ReliabilityView", () => {
vi.spyOn(globalThis, "fetch").mockResolvedValue({ ok: true, json: async () => baseResponse } as Response); vi.spyOn(globalThis, "fetch").mockResolvedValue({ ok: true, json: async () => baseResponse } as Response);
render(<ReliabilityView />); render(<ReliabilityView />);
await waitFor(() => expect(screen.getByText("20.0%")).toBeInTheDocument()); await waitFor(() => expect(screen.getByText("80.0%")).toBeInTheDocument());
fireEvent.click(screen.getByText("Details")); fireEvent.click(screen.getByText("Details"));
expect(screen.getByText("2 bounced / 10 entered (last 7d)")).toBeInTheDocument(); expect(screen.getByText("2 bounced / 10 entered (last 7d)")).toBeInTheDocument();
}); });
@@ -64,7 +64,7 @@ describe("ReliabilityView", () => {
.mockResolvedValueOnce({ ok: true, json: async () => ({ ...baseResponse, resetAt: "2026-05-13T01:00:00.000Z" }) } as Response); .mockResolvedValueOnce({ ok: true, json: async () => ({ ...baseResponse, resetAt: "2026-05-13T01:00:00.000Z" }) } as Response);
render(<ReliabilityView />); render(<ReliabilityView />);
await waitFor(() => expect(screen.getByText("20.0%")).toBeInTheDocument()); await waitFor(() => expect(screen.getByText("80.0%")).toBeInTheDocument());
fireEvent.click(screen.getByRole("button", { name: "Reset stats" })); fireEvent.click(screen.getByRole("button", { name: "Reset stats" }));
expect(screen.getByText("Reset reliability stats?")).toBeInTheDocument(); expect(screen.getByText("Reset reliability stats?")).toBeInTheDocument();
@@ -80,7 +80,7 @@ describe("ReliabilityView", () => {
vi.spyOn(globalThis, "fetch").mockResolvedValue({ ok: true, json: async () => baseResponse } as Response); vi.spyOn(globalThis, "fetch").mockResolvedValue({ ok: true, json: async () => baseResponse } as Response);
render(<ReliabilityView />); render(<ReliabilityView />);
await waitFor(() => expect(screen.getByText("20.0%")).toBeInTheDocument()); await waitFor(() => expect(screen.getByText("80.0%")).toBeInTheDocument());
fireEvent.click(screen.getAllByText("More stats")[0] as HTMLElement); fireEvent.click(screen.getAllByText("More stats")[0] as HTMLElement);
expect(screen.getByText("P50 raw: 60000 ms")).toBeInTheDocument(); expect(screen.getByText("P50 raw: 60000 ms")).toBeInTheDocument();
expect(screen.getByText("Sample count: 3")).toBeInTheDocument(); expect(screen.getByText("Sample count: 3")).toBeInTheDocument();
@@ -98,6 +98,30 @@ describe("ReliabilityView", () => {
expect(computed.height).not.toBe(""); expect(computed.height).not.toBe("");
}); });
it("FN-4716: renders 100.0% when zero bounces", async () => {
vi.spyOn(globalThis, "fetch").mockResolvedValue({
ok: true,
json: async () => ({
...baseResponse,
headline: { inReviewFailureRate7d: 0 },
perDay: [
{
date: "2026-05-13",
tasksEnteredInReview: 5,
tasksBouncedToInProgress: 0,
postMergeAuditFailures: null,
fileScopeInvariantFailures: null,
recoverAlreadyMergedReviewTasksRecoveries: null,
hasSamples: true,
},
],
}),
} as Response);
render(<ReliabilityView />);
await waitFor(() => expect(screen.getByText("100.0%")).toBeInTheDocument());
});
it("renders null headline reason gracefully", async () => { it("renders null headline reason gracefully", async () => {
vi.spyOn(globalThis, "fetch").mockResolvedValue({ vi.spyOn(globalThis, "fetch").mockResolvedValue({
ok: true, ok: true,