Skip to content

feat(coverage): per-provider coverage breakdowns (V1) replace placeholder stub - #760

Merged
cristim merged 2 commits into
feat/multicloud-web-frontendfrom
feat/754-coverage-per-provider-breakdowns
May 27, 2026
Merged

feat(coverage): per-provider coverage breakdowns (V1) replace placeholder stub#760
cristim merged 2 commits into
feat/multicloud-web-frontendfrom
feat/754-coverage-per-provider-breakdowns

Conversation

@cristim

@cristim cristim commented May 27, 2026

Copy link
Copy Markdown
Member

Summary

Inventory & Coverage > Coverage sub-tab previously showed a placeholder:

Per-provider coverage breakdowns are coming soon. Your overall coverage rate is on the Home dashboard.

V1 replaces the stub with real per-provider, per-service breakdowns.

What V1 ships

  • New endpoint GET /api/inventory/coverage returning CoverageBreakdownResponse (per-provider sections, each with a list of service rows: covered, on-demand, coverage_pct).
  • Frontend section in inventory.ts renders each provider that has data; per-provider empty state when a provider has no usage.
  • Bar visualisation per row showing covered vs on-demand.
  • Refresh button + period selector wired.

Data sources

No new cloud-provider SDK calls. Coverage % derived from data already in the system:

  • Covered = MonthlyCost on active commitments (from purchase history)
  • On-demand = Savings on current recommendations (the gap the user could be covering)

coverage_pct is *float64 (Go) / number|null (TS) -- null when both sides are 0, NOT coerced to 0. Matches the feedback_nullable_not_zero pattern.

Files changed (11)

Backend:

  • internal/api/types.go -- new types CoverageServiceRow, ProviderCoverageSection, CoverageBreakdownResponse
  • internal/api/handler_inventory.go -- getCoverageBreakdown, buildCoverageBreakdown, coveragePct, splitProviderService
  • internal/api/router.go -- route + handler wrapper
  • internal/api/handler_inventory_test.go -- 5 new tests

Frontend:

  • frontend/src/api/types.ts, frontend/src/api/inventory.ts, frontend/src/api/index.ts -- typed API client
  • frontend/src/inventory.ts -- loadCoverageBreakdown + render
  • frontend/src/index.html -- container structure (#coverage-providers, #coverage-refresh-btn)
  • frontend/src/__tests__/inventory.test.ts -- 6 new render tests (full, empty providers, null coverage, error, refresh)
  • frontend/src/__tests__/api-inventory.test.ts -- 2 new API tests

Test plan

  • 1332 backend tests pass (10 new)
  • 21 frontend tests pass (8 new)
  • Manual post-deploy: open Coverage tab with multi-provider data; confirm per-provider sections render and empty providers show their own empty state.

Deferred to follow-up

  • Per-account drilldown within each provider.
  • Real-time coverage from CE GetReservationCoverage / Azure Consumption / GCP equivalent (current V1 derives from purchase history + open recommendations; good first cut but real-time API would refine).
  • Forecasting / what-if simulations.
  • Cross-cloud unified view.

Closes #754.

Summary by CodeRabbit

Release Notes

  • New Features

    • Inventory Coverage tab now displays per-provider coverage breakdown showing covered and on-demand metrics by service.
    • Added coverage percentage calculations and visual indicators for each provider.
    • Implemented refresh functionality to update coverage data on demand.
  • Tests

    • Added comprehensive test coverage for the new coverage breakdown feature.

Review Change Stack

…stub

The Coverage sub-tab on Inventory & Coverage showed "Per-provider
coverage breakdowns are coming soon." Now renders per-provider sections
(AWS, Azure, GCP) each with a per-service table showing covered monthly
spend, on-demand gap, coverage %, and a proportional bar indicator.
Providers with no usage data show "No usage detected for <Provider>"
instead of an empty table.

Backend: extended handler_inventory.go with getCoverageBreakdown
(GET /api/inventory/coverage). Aggregates per-(provider,service) from
two existing data sources already in the system: active-commitment
MonthlyCost (covered) and recommendation Savings (on-demand gap).
No new AWS/Azure/GCP API calls needed. coverage_pct is *float64 (null
when both sides are zero) to distinguish "no usage" from "0% covered".

Frontend: replaced placeholder div in index.html with a header card +
#coverage-providers container. inventory.ts now exports
loadCoverageBreakdown(), which renders provider cards using DOM
construction (no innerHTML). Wired into switchInventorySubSection and
a refresh button.

Closes #754.
@cristim cristim added triaged Item has been triaged priority/p2 Backlog-worthy severity/medium Moderate harm urgency/this-quarter Within the quarter impact/all-users Affects every user effort/l Weeks type/feat New capability labels May 27, 2026
@cristim

cristim commented May 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@cristim, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 51 minutes and 2 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 38ea107d-afeb-4431-8e53-53afb6b20acd

📥 Commits

Reviewing files that changed from the base of the PR and between ab6dccf and d96bfc9.

📒 Files selected for processing (4)
  • frontend/src/__tests__/inventory.test.ts
  • frontend/src/inventory.ts
  • internal/api/handler_inventory.go
  • internal/api/handler_per_account_perms_test.go
📝 Walkthrough

Walkthrough

This PR implements the coverage breakdown feature for the inventory & coverage sub-tab. It adds a new backend endpoint (GET /api/inventory/coverage) that aggregates committed and on-demand usage by provider and service, computes per-service and per-provider coverage percentages, and exposes the data via TypeScript client adapters to a fully interactive frontend UI with provider cards, service rows, and a refresh button.

Changes

Coverage Breakdown Feature

Layer / File(s) Summary
API Response Types
frontend/src/api/types.ts, internal/api/types.go, frontend/src/api/index.ts
CoverageServiceRow, ProviderCoverageSection, and CoverageBreakdownResponse types establish the contract for per-provider breakdown responses, with nullable coverage percentages to represent "no data" vs. "0%" states. Both frontend and backend define these interfaces; barrel re-exports add them to the public API surface.
Backend Coverage Handler
internal/api/handler_inventory.go, internal/api/router.go, internal/api/handler_inventory_test.go
New getCoverageBreakdown() handler aggregates committed usage from purchase history (filtered by view:purchases permission) and on-demand gaps from scheduler recommendations (best-effort fallback on error). buildCoverageBreakdown() merges the aggregated data, computes per-service and per-provider coverage %, sorts services alphabetically for stability, and emits nil coverage/services for providers with no data. GET /api/inventory/coverage route wires the endpoint. Unit tests cover single-provider, empty-provider, nil-vs-zero, and on-demand-only cases; integration test validates auth and the full response shape.
Frontend API Client
frontend/src/api/inventory.ts, frontend/src/__tests__/api-inventory.test.ts
getCoverageBreakdown() function calls apiRequest for /inventory/coverage and returns the typed response. Tests verify the request path and response envelope structure including the providers array.
Frontend UI and End-to-End Tests
frontend/src/index.html, frontend/src/inventory.ts, frontend/src/__tests__/inventory.test.ts
HTML markup replaces the "coming soon" placeholder with a Coverage card, Refresh button, and coverage-providers container. loadCoverageBreakdown() fetches data, renders per-provider cards with service rows showing covered/on-demand amounts and coverage %, handles skeleton loading, error states, empty-provider messages ("No usage detected"), and idempotent refresh. Sub-tab router maps the coverage tab to the new loader. E2E tests build the full DOM, mock the API, verify provider card rendering, test null-service and null-coverage-pct edge cases, assert error display, and confirm refresh re-fetches and updates in place.

Sequence Diagram

sequenceDiagram
  participant User
  participant Frontend as Frontend UI
  participant API as /api/inventory/coverage
  participant Store as PurchaseStore
  participant Scheduler as RecommendationScheduler
  
  User->>Frontend: Switch to Coverage sub-tab
  Frontend->>Frontend: loadCoverageBreakdown()
  Frontend->>API: GET /api/inventory/coverage
  API->>Store: Fetch active commitments (view:purchases)
  Store-->>API: Commitment data by provider/service
  API->>Scheduler: Fetch recommendations
  Scheduler-->>API: On-demand gaps (best-effort)
  API->>API: buildCoverageBreakdown() aggregates & computes %
  API-->>Frontend: CoverageBreakdownResponse
  Frontend->>Frontend: Render provider cards with services
  Frontend->>Frontend: Display coverage % per service & provider
  User->>Frontend: Click refresh button
  Frontend->>API: GET /api/inventory/coverage (reload)
  API-->>Frontend: Updated response
  Frontend->>Frontend: Update provider sections in-place
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A rabbit hops through cloud data with glee,
Counting commitments and gaps, coverage to see—
Per-provider breakdowns now bloom on the screen,
With refresh buttons gleaming, a UI so clean!
Service by service, the percentages shine,
No more "coming soon," the coverage is mine. 🐰☁️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: replacing the coverage placeholder stub with a V1 implementation of per-provider coverage breakdowns.
Linked Issues check ✅ Passed The PR implements all coding-related acceptance criteria from issue #754: per-provider breakdowns, empty-state handling, refresh functionality, and service-level drilldown.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #754 objectives. No unrelated modifications to other features, performance optimizations, refactorings, or deferred items are present in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/754-coverage-per-provider-breakdowns

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim

cristim commented May 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/inventory.ts`:
- Around line 319-323: The last table header is created as an empty string which
is inaccessible; update the header creation loop in inventory.ts (the block that
builds headerRow) to use a meaningful label such as "Coverage" or "Coverage bar"
instead of '' and/or add an explicit aria-label (e.g.,
th.setAttribute('aria-label', 'Coverage bar')) on the created <th> so screen
readers can identify the coverage bar column; locate the header generation loop
that iterates over ['Service', 'Covered/mo', 'On-demand gap/mo', 'Coverage %',
''] and replace the empty entry with the chosen non-empty label and optional
aria-label on the <th>.

In `@internal/api/handler_inventory.go`:
- Around line 154-164: Recommendations returned by
h.scheduler.ListRecommendations are aggregated into onDemandByKey without
applying the same account-access scope used for commitments; restrict
aggregation to only recommendations that match the allowed-account scope (the
same filter applied to commitments) by either passing the allowed-accounts into
ListRecommendations (instead of config.RecommendationFilter{}), or
post-filtering recs by account before the loop (e.g., skip recs whose account is
not in the allowed-account set), then sum onDemandByKey using
rec.Provider+":"+rec.Service and rec.Savings only for those allowed recs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: afb41bee-1981-4414-8662-edf2a3409fbe

📥 Commits

Reviewing files that changed from the base of the PR and between f5b76eb and ab6dccf.

📒 Files selected for processing (11)
  • frontend/src/__tests__/api-inventory.test.ts
  • frontend/src/__tests__/inventory.test.ts
  • frontend/src/api/index.ts
  • frontend/src/api/inventory.ts
  • frontend/src/api/types.ts
  • frontend/src/index.html
  • frontend/src/inventory.ts
  • internal/api/handler_inventory.go
  • internal/api/handler_inventory_test.go
  • internal/api/router.go
  • internal/api/types.go

Comment thread frontend/src/inventory.ts Outdated
Comment thread internal/api/handler_inventory.go
…abel empty coverage <th>

getCoverageBreakdown called ListRecommendations with no account scope, so a
restricted user received on-demand gap figures computed from recommendations
belonging to accounts outside their allowed_accounts list. Apply
filterRecommendationsByAllowedAccounts on the recs slice (same pattern used by
getRecommendations and getDashboardSummary) before accumulating onDemandByKey.

Regression tests added in TestPerAccountPerms_CoverageBreakdown_* (negative:
scoped user sees only account-A on-demand gap; positive: admin aggregates both).

Also replaces the empty-string last header label in buildServiceTable with
"Coverage bar" and adds aria-label="Coverage bar" on that <th> so screen
readers can announce the column purpose unambiguously.

Addresses two unresolved CodeRabbit findings on PR #760.
@cristim

cristim commented May 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/l Weeks impact/all-users Affects every user priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged type/feat New capability urgency/this-quarter Within the quarter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant