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:
committed by
gsxdsm
parent
196b7e439f
commit
414e62d39d
5
.changeset/fn-4716-reliability-success-rate.md
Normal file
5
.changeset/fn-4716-reliability-success-rate.md
Normal 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.
|
||||
@@ -71,13 +71,18 @@ export function ReliabilityView() {
|
||||
return () => clearInterval(pollInterval);
|
||||
}, [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 rate = data?.headline.inReviewFailureRate7d;
|
||||
if (rate === null || rate === undefined) return "var(--text-muted)";
|
||||
if (rate < 0.05) return "var(--color-success)";
|
||||
if (rate < 0.1) return "var(--color-warning)";
|
||||
if (reliabilityRate === null) return "var(--text-muted)";
|
||||
if (reliabilityRate >= 0.95) return "var(--color-success)";
|
||||
if (reliabilityRate >= 0.9) return "var(--color-warning)";
|
||||
return "var(--color-error)";
|
||||
}, [data]);
|
||||
}, [reliabilityRate]);
|
||||
|
||||
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]);
|
||||
@@ -104,15 +109,17 @@ export function ReliabilityView() {
|
||||
<button className="btn btn-danger btn-sm" onClick={() => setShowResetConfirm(true)}>Reset stats</button>
|
||||
</div>
|
||||
<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"}`
|
||||
: formatPercent(data.headline.inReviewFailureRate7d)}
|
||||
: formatPercent(reliabilityRate ?? 0)}
|
||||
</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}
|
||||
<details className="reliability-details">
|
||||
<summary>Details</summary>
|
||||
<div className="reliability-details-content">
|
||||
<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>
|
||||
{data?.resetAt ? <div>{`Reset baseline: ${formatDateTime(data.resetAt)}`}</div> : null}
|
||||
{data?.headline.reason ? <div>{`Reason: ${data.headline.reason}`}</div> : null}
|
||||
|
||||
@@ -41,7 +41,7 @@ describe("ReliabilityView", () => {
|
||||
vi.spyOn(globalThis, "fetch").mockResolvedValue({ ok: true, json: async () => baseResponse } as Response);
|
||||
render(<ReliabilityView />);
|
||||
|
||||
await waitFor(() => expect(screen.getByText("20.0%")).toBeInTheDocument());
|
||||
await waitFor(() => expect(screen.getByText("80.0%")).toBeInTheDocument());
|
||||
fireEvent.click(screen.getByText("Details"));
|
||||
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);
|
||||
|
||||
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" }));
|
||||
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);
|
||||
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);
|
||||
expect(screen.getByText("P50 raw: 60000 ms")).toBeInTheDocument();
|
||||
expect(screen.getByText("Sample count: 3")).toBeInTheDocument();
|
||||
@@ -98,6 +98,30 @@ describe("ReliabilityView", () => {
|
||||
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 () => {
|
||||
vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
||||
ok: true,
|
||||
|
||||
Reference in New Issue
Block a user