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);
|
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}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user