feat(cluster): cluster plan usage card + Usage tab [#1297] - #1559
feat(cluster): cluster plan usage card + Usage tab [#1297]#1559dawsontoth wants to merge 9 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a new cluster usage monitoring feature, adding a "Usage" tab and a "Plan usage" overview card to the cluster home page. It includes the UsageMeter component to render progress bars for metered metrics, alongside mock data for various usage metrics, rate limits, and per-instance resources. Feedback on the PR suggests making the UsageMeter component more robust by guarding against division by zero and treating negative limits as unlimited.
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Backend endpoint that will replace the placeholder data here: HarperFast/central-manager#503 |
Treat a negative limit as unlimited (server sends -1 for that) and skip the percentage calc when limit is 0, so the bar never divides by zero (NaN) — per gemini-code-assist review on #1559. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
UI-only mockup with placeholder data for the cluster usage-vs-plan display. Pending the central-manager GET /Cluster/:id/usage endpoint that will supply real data — swapping the SAMPLE constants for the response keeps the components and layout unchanged. - ClusterUsageCard on the managed cluster overview: Reads / Writes / Storage / Compute, with a "View all usage" link into the tab. - Usage item in the cluster sub-nav rail (managed clusters only) and a Usage tab with the full breakdown: metered-this-cycle meters, rate-limit ceilings, and per-instance resources. - Shared UsageMeter primitive: used/limit bar, ">=90%" amber warning, and hatched "Unlimited" state for plan limits of -1. Framing is "used X of Y this cycle": purchased blocks auto-renew and re-bill on exhaustion for paid tiers, while for the free tier the same bar doubles as the hard-limit warning. Only the 8 "this cycle" metrics have real usage counters server-side; per-minute rate limits, TLS handshakes, and per-instance resources are limits/provisioning only (shown as info, never as meters). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Treat a negative limit as unlimited (server sends -1 for that) and skip the percentage calc when limit is 0, so the bar never divides by zero (NaN) — per gemini-code-assist review on #1559. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the mockup's placeholder SAMPLE data with a real useClusterUsage query against the central-manager endpoint (HarperFast/central-manager#503). Components and layout are unchanged — only the data source. - getClusterUsage.ts: typed ClusterUsage response, query hook, and a usageSubtitle helper (plan name / cycle renewal / freshness). - ClusterUsageCard: renders totals; hides while loading, on error, for self-hosted, or when there's no active plan/usage. - Usage tab: loading spinner, self-hosted + no-usage empty states, and the metered / rate-limit / per-instance sections from live data. The endpoint isn't on stage yet (CM #503 in review), so today the query errors and the UI degrades gracefully (card hidden, tab shows the empty state); it lights up once the endpoint deploys. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Track the central-manager contract redesign (HarperFast/central-manager#503): usage is metered per region, not cluster-wide. - getClusterUsage: per-metric value is now { used, limit, unlimited, limitKnown }; regions carry region name + regionIds + exhausted + block counts; add mostConstrained; drop the misleading asOf freshness. - UsageMeter: render three ceiling states — finite %, hatched Unlimited, and '—' for an unresolved limit (never show unknown as Unlimited). Shared METRIC_LABEL/FORMAT/ORDER + toMeter() helper. - Overview card: single region → the four headline meters; multiple regions → the most-constrained region×metric (no cluster-wide average). - Usage tab: one section per region (name, plan, renews/exhausted badge, the eight meters, rate limits, per-instance resources). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…dth + IDs Follows the central-manager contract update (HarperFast/central-manager#503) and a UX pass: - Region status: 'active' | 'exhausted' | 'lapsed' (was a bool); the Usage tab renders exhausted vs lapsed as distinct badges, and totals are { used } only. - Usage tab: each region is a collapsible grouped card (lapsed collapsed by default); rate limits + per-instance resources hoist into one shared 'Plan limits & resources' card when uniform across regions (was repeated per region). - Completeness pass vs the new-cluster plan modal: add the Read/Write/Real-time bandwidth (per-minute bytes) rate rows, and surface region IDs / plan ID subtly in the card headers. - Overview card: tightened multi-region 'most constrained' callout. - Storage/memory render in decimal GB to match the plan's stated disk; extra bottom padding on the tab. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Now that the endpoint contract is locked (HarperFast/central-manager#503 approved), pin the UI behaviour that matters: - UsageMeter: the three ceiling states (finite %, hatched Unlimited, '—' for an unresolved plan), the >=90% warning, capping at 100%, and that a zero or negative limit can't render NaN/Infinity or masquerade as Unlimited. - ClusterUsageCard: four headline meters for a single region vs the most-constrained callout for several, the Usage-tab link, and that it renders nothing while loading, on error, for self-managed, or with no regions. - Usage tab: every metered metric, region/plan ids in the headers, collapse and re-expand, exhausted vs lapsed badges (lapsed collapsed by default), the shared 'Plan limits & resources' card when uniform (per-region when not), and the self-hosted / no-usage / loading states. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7839585 to
0c152ba
Compare
|
Now verified end-to-end against live stage data — HarperFast/central-manager#503 is deployed to dev + stage, so this is no longer running on placeholders.
Overview card — real plan name and renewal ( Usage tab — the region card keyed on the real region ( Also in this push:
Out of draft and ready for review. |
Two review comments from the #1559 review, both customer-facing on a billing surface: - ClusterUsageCard: `mostConstrained.region` is nullable, so an unnamed region rendered the literal string "null · Reads" on the overview card. Falls back to 'Region', matching how the Usage tab already guards the identical field. - Usage tab: a request that came back with nothing was folded into the empty state, telling the user "No usage has been recorded for the current cycle yet" when the truth is we never loaded it — on a billing surface that reads as a zero bill. Now renders a distinct error instead. The review suggested keying that on `isError`. Verifying it against stage showed `isError` alone is not enough: when a retry is paused (browser offline) react-query parks the query at pending/paused, so `isLoading` and `isError` are BOTH false with no data, and the page still claimed an empty cycle. Keyed on `!data` instead, which covers the errored, paused and never-enabled cases alike. Because it keys on absent data rather than the error flag, a failed *background* refetch keeps rendering the cached numbers. Tests: 5 new cases (110 passing in the cluster suite). The two bug reproductions were confirmed to fail against the pre-fix source, printing exactly the reported strings ("null · Reads", "No usage has been recorded ..."). Verified in the browser against live stage data: the meters and overview card render unchanged, and forcing the usage request to fail now shows the error where it previously claimed an empty cycle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Acted on both review comments from @cb1kenobi's pass — pushed in afd6fec, threads resolved.
Verification: both bug reproductions were confirmed to fail against the pre-fix source, printing exactly the reported strings. Verified in the browser on live stage data — meters and overview card render unchanged, and the forced failure now shows "Couldn't load usage data — refresh to try again." where it previously claimed an empty cycle. 5 new tests (cluster suite 110 passing); full suite 1881 passing, 🤖 Generated with Claude Code |
kriszyp
left a comment
There was a problem hiding this comment.
This is awesome, going to be great to have this!
A couple of inline comments to consider.
🤖 Reviewed with GPT 5.6
…r region Addresses @kriszyp's review: **Rate ceilings** (`getClusterUsage.ts:31`) were rendered straight from `Plan.planLimits`, which is wrong on two supported cases: the `fabric-block-dedicated-unlimited-{2..5}` plans store -1 for every limit and it reached the card as a literal "-1" / "-1 B", and the per-minute figures are per BLOCK, so a region on a wider distribution tier was quoted a LOWER ceiling than it has. central-manager now returns effective per-region values as `{ value, unlimited, known }` (the three states the metered values already use), and the tab renders them: a number, "Unlimited", or "—". A ceiling the plan doesn't declare still gets no row. Per-instance resources drop non-positive values too, matching the purchase-time plan panel. The endpoint is the only thing that can scale these safely, so a response that predates the normalization (bare numbers) reads as "—" rather than as a real, understated ceiling. **Subtitle** (`getClusterUsage.ts:105`) asserted `regions[0]`'s plan and the cluster's `renewsAt` as cluster-wide facts. Each region can be on its own plan and `renewsAt` is the EARLIEST expiry among the active ones, so a Europe/Standard + US/Enterprise cluster claimed "Standard plan · renews Aug 12" for both. The plan is now named only when every region agrees, and the date reads "next renewal" once there's more than one region. 9 tests (8 of which fail against the previous source); cluster suite 119, full suite 1890 passing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Picked this up and addressed both of @kriszyp's comments — pushed in Rate ceilings — normalized in the endpoint, as asked: HarperFast/central-manager#635. Both cases you named check out. The four Subtitle now states the plan only when every region names the same one, and labels the date "next renewal" once there's more than one region. Your Europe/Standard + US/Enterprise example reads Deploy order: studio degrades safely against the endpoint that's live today — a pre-normalization response arrives as bare numbers and, with no flags to trust, renders "—" instead of a hard number. Verified on stage: the tab renders against the currently-deployed endpoint with every ceiling as "—", and with the normalized shape simulated locally against the same live cluster all three states render ( Verification: 9 new studio tests and 6 new central-manager ones; 8 of the 9 and 5 of the 6 were confirmed to fail against the pre-fix sources. Cluster suite 119 passing, full studio suite 1890; central-manager unit suite 33. Also tightened per-instance resources to drop non-positive values, so a future sentinel there can't render as "-1 GB". 🤖 Generated with Claude Code |
Cluster usage-vs-plan display (#1297)
Adds a Plan usage card to the managed cluster overview and a Usage tab, backed by the central-manager endpoint from HarperFast/central-manager#503 (
GET /Cluster/:id/usage, approved and merged).Closes #1297.
Verified against live data
The endpoint is deployed to dev + stage, and both surfaces render real cluster data (screenshots below): actual plan name and renewal date, real limits, and real storage sourced from
InstanceMonitoring.Model
Quota is enforced per region, never cluster-wide (exhaustion, renewal, billing and the signed license ceiling are all per-region), so the UI meters per region rather than summing into one misleading bar.
Overview card
Usage tab
These are the effective per-region ceilings, not the raw plan row: the endpoint resolves the
-1sentinel and scales the tier-dependent ones byRegion.purchasedBlockMultiplier(HarperFast/central-manager#635), so each reads as a number, Unlimited, or "—".Meter states
Finite ceiling → used/limit bar, amber at ≥90%. Plan limit
-1→ hatched "Unlimited". Unresolved plan → "—" (never the reassuring "Unlimited" — this is a billing surface). A zero/negative limit can't produceNaN/Infinity.Completeness
Cross-checked field-by-field against the new-cluster plan modal: everything it shows is here, plus used-vs-limit rather than limits alone. Only Cloud Instance Type is out of scope (needs a CM field; dedicated/GPU only).
Tests
36 cases across the three usage surfaces (
vitest, jsdom): the meter's three ceiling states and guards, the card's single- vs multi-region branches, its four hide conditions and its subtitle-uniformity rules, and the tab's collapse/expand, status badges, shared-vs-per-region plan info, rate-ceiling states, and empty/error/loading states. Cluster-feature suite 119 passing, full suite 1890 passing.tsc,oxlint,dprintclean.Depends on
HarperFast/central-manager#635 — the endpoint change that makes
rateLimitseffective per-region values. This branch degrades safely without it (ceilings render "—" rather than an understated per-block number), so the two can ship in either order, but real ceilings appear only once #635 is deployed.Follow-up
Regenerate the SDK types from #503's OpenAPI to drop the hand-typed response (works fine as-is; a regen is a large unrelated diff, so it belongs on its own).
🤖 Generated with Claude Code