feat(cluster): cluster plan usage card + Usage tab [#1297] - #1559
feat(cluster): cluster plan usage card + Usage tab [#1297]#1559dawsontoth wants to merge 7 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
|
|
||
| export type UsageMetricKey = keyof UsageMetrics; | ||
|
|
||
| export interface UsageRateLimits { |
There was a problem hiding this comment.
Can we normalize these limits in the endpoint before presenting them as regional throughput ceilings? central-manager's rateLimitsFromPlan currently copies raw Plan.planLimits fields. That makes this page wrong in two supported cases: managed fabric-block-dedicated-unlimited-{2..5} plans return -1, so rowsFrom displays -1 / -1 B; and the existing plan UI defines read/read-bandwidth/real-time/TLS regional rates as the plan value multiplied by Region.purchasedBlockMultiplier, so a tier with multiplier >1 will show a lower number here. Please have the usage endpoint return effective per-region values with explicit unlimited/unknown semantics, consume that normalized shape here, and add coverage for both cases. Multiplying activeBlockCount in Studio is not safe because a mixed cohort after a plan change can contain more live blocks than the current region tier.
| } | ||
|
|
||
| /** "Standard plan · renews Aug 12" — shared by the card + tab. */ | ||
| export function usageSubtitle(data: ClusterUsage): string { |
There was a problem hiding this comment.
Could we make this subtitle conditional on the regions being uniform? The endpoint sorts regions alphabetically and supports a different current plan per region, while renewsAt is the earliest expiry among active regions. As written, a Europe/Standard + US/Enterprise cluster can display Standard plan · renews Aug 12 as a cluster-wide statement even when the US region renews later. The current copy is accurate for one region; for multiple regions, only show the plan when all names match and label the date next renewal (or omit these summary facts), then cover heterogeneous plans/expiries in the card test.
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
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
22 new cases (
vitest, jsdom) covering the meter's three ceiling states and guards, the card's single- vs multi-region branches and its four hide conditions, and the tab's collapse/expand, status badges, shared-vs-per-region plan info, and empty/loading states. Full cluster-feature suite: 105 passing.tsc,oxlint,dprintclean.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