import { Link } from "react-router-dom";
import {
AT_RISK_LEFT,
FAST_BURN,
SLO_TARGETS,
targetLabel,
type BudgetStatus,
type RoutineBudget,
type SloReport,
} from "./sloMath";
const STATUS_CLASS: Record<BudgetStatus, string> = {
exhausted: "run-status failed",
"fast-burn": "run-status failed",
"at-risk": "run-status running",
ok: "run-status success",
};
const STATUS_LABEL: Record<BudgetStatus, string> = {
exhausted: "EXHAUSTED",
"fast-burn": "FAST BURN",
"at-risk": "AT RISK",
ok: "OK",
};
function fmtBurn(burn: number | null): string {
return burn === null ? "—" : `${burn.toFixed(1)}×`;
}
function fmtAllowed(allowed: number): string {
return allowed.toFixed(allowed < 10 ? 1 : 0);
}
export interface SloBudgetProps {
report: SloReport;
target: number;
onTargetChange: (target: number) => void;
}
/**
* SLO error budget: each routine's spent share of the failures a success-rate target allows over
* the last 7 days, with 24h and 7d burn rates — exhausted and fast-burning routines first.
*/
export function SloBudget({ report, target, onTargetChange }: SloBudgetProps) {
const { budgets, exhaustedCount, partial } = report;
if (budgets.length === 0) return null;
const flagged = budgets.filter((b) => b.status !== "ok").length;
return (
<details className="table-wrap adherence" open={flagged > 0}>
<summary className="adherence-hd">
<span className="filter-label">ERROR BUDGET</span>
<span className="cell-meta">
SLO {targetLabel(target)} · 7d · {exhaustedCount} exhausted · {flagged} need attention
{partial ? " · window partial" : ""}
</span>
</summary>
<p className="adherence-note">
<label htmlFor="slo-target-select">SLO TARGET </label>
<select
id="slo-target-select"
className="filter-select"
value={target}
onChange={(e) => onTargetChange(Number(e.target.value))}
>
{SLO_TARGETS.map((t) => (
<option key={t} value={t}>
{targetLabel(t)}
</option>
))}
</select>{" "}
The budget is the failures the target allows over finished runs in the last 7 days. Burn rate is the
failure rate over the allowed rate: 1× spends the budget exactly at the window end. FAST BURN means the
last 24h burn is {FAST_BURN}× or more. AT RISK means less than {Math.round(AT_RISK_LEFT * 100)}% of the
budget is left.
{partial ? " The fetched sample does not reach back 7 days, so the window is incomplete." : ""}
</p>
<table>
<thead>
<tr>
<th>ROUTINE</th>
<th>STATUS</th>
<th>FAILURES / ALLOWED</th>
<th>BUDGET LEFT</th>
<th>BURN 24H</th>
<th>BURN 7D</th>
</tr>
</thead>
<tbody>
{budgets.map((b) => (
<BudgetRow key={b.routineId} budget={b} />
))}
</tbody>
</table>
</details>
);
}
function BudgetRow({ budget: b }: { budget: RoutineBudget }) {
// Overspend shows as EXHAUSTED and in the burn rates; a negative share (-900% on a 2-run routine) only confuses.
const left = Math.max(0, b.budgetLeft);
return (
<tr>
<td>
<Link to={`/routines?history=${encodeURIComponent(b.routineId)}`}>{b.routineTitle}</Link>
</td>
<td>
<span className={STATUS_CLASS[b.status]}>{STATUS_LABEL[b.status]}</span>
</td>
<td className="cell-meta">
{b.failures} / {fmtAllowed(b.allowed)} of {b.runs} runs
</td>
<td className="cell-meta">
<meter
min={0}
max={1}
low={AT_RISK_LEFT}
high={0.5}
optimum={1}
value={left}
aria-label={`${b.routineTitle} budget left`}
/>{" "}
{Math.round(left * 100)}%
</td>
<td className="cell-meta">{fmtBurn(b.burn24h)}</td>
<td className="cell-meta">{fmtBurn(b.burn7d)}</td>
</tr>
);
}