Skip to content

fix(inventory): Active Commitments + Coverage honor Main Header chips#881

Merged
cristim merged 3 commits into
feat/multicloud-web-frontendfrom
fix/866-inventory-honor-global-filter
Jun 1, 2026
Merged

fix(inventory): Active Commitments + Coverage honor Main Header chips#881
cristim merged 3 commits into
feat/multicloud-web-frontendfrom
fix/866-inventory-honor-global-filter

Conversation

@cristim
Copy link
Copy Markdown
Member

@cristim cristim commented May 30, 2026

Summary

QA Main Header 2.7+2.14, 2.8+2.15: The Main Header global Provider/Account chips did not propagate into the Inventory & Coverage subpages. Active Commitments and Coverage stayed unfiltered regardless of chip selection.

Fix

Mirrors the chip-subscription pattern from PR #741 (Purchases) and PR #747 (Home):

  • wireChipSubscriptions() in frontend/src/inventory.ts ties provider + account chip changes to a queueMicrotask-coalesced reload.
  • isInventoryTabActive() guard prevents fires when the user is on another top-level tab.
  • Re-subscribes on every Inventory tab-switch and tears down the previous pair so handlers don't stack.

Backend: internal/api/handler_inventory.go Active Commitments + Coverage handlers accept provider= and account_id= query params and filter on the dual-column semantics matching analytics_postgres.go (after PR #741).

Filter-aware empty state: "No active commitments for <filter>" when chips exclude all data, instead of the generic stub.

Files changed

  • frontend/src/inventory.ts
  • frontend/src/api/inventory.ts
  • frontend/src/__tests__/inventory.test.ts
  • frontend/src/__tests__/api-inventory.test.ts
  • internal/api/handler_inventory.go
  • internal/api/handler_inventory_test.go

Test plan

  • 37 frontend inventory tests pass (12 new chip-subscription tests + filter-aware empty-state coverage).
  • 1356 backend api tests pass.
  • Manual: open Inventory & Coverage, pick Account in Main Header, confirm both Active Commitments and Coverage tabs narrow to the selected account.

Note on test setup

The chip-subscription describe block explicitly resets module-scoped currentSubSection via switchInventorySubSection('active-commitments') after loadInventory() to avoid bleed from earlier tests that switch to coverage. The inactive-tab test removes the .active class after fixing the sub-section.

Closes #866.

Summary by CodeRabbit

  • New Features

    • Provider filtering applied across inventory views (active commitments and coverage)
    • Chip filters now dynamically trigger inventory updates when provider or account changes
  • Improvements

    • Load only the visible inventory sub-section to reduce unnecessary fetches
    • Empty-state messaging now reflects active filters
    • Prevent duplicate subscriptions and coalesce rapid chip updates
  • Tests

    • Expanded test coverage for chip behavior and filter query handling

Mirrors PR #741 (Purchases) and PR #747 (Home): wireChipSubscriptions
ties provider + account chip changes to a queueMicrotask-coalesced
reload, guarded by isInventoryTabActive(). Active Commitments and
Coverage handlers in internal/api/handler_inventory.go accept provider
and account_id query params and filter on dual-column semantics
matching analytics_postgres.go.

Filter-aware empty state: "No active commitments for <filter>" when
chips exclude all data, instead of the generic "No active commitments"
stub.

Test setup fixes: chip-subscription describe block now explicitly
resets module-scoped currentSubSection via switchInventorySubSection,
and the inactive-tab test removes the .active class after fixing the
sub-section.

Closes #866.
@cristim cristim added triaged Item has been triaged priority/p2 Backlog-worthy severity/medium Moderate harm urgency/this-sprint Within the current sprint impact/many Affects most users effort/s Hours type/bug Defect labels May 30, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5b1a8be3-30b0-4e17-801c-9233c6fa59b5

📥 Commits

Reviewing files that changed from the base of the PR and between 72faef9 and 2db50bc.

📒 Files selected for processing (2)
  • internal/api/handler_inventory.go
  • internal/api/handler_inventory_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/api/handler_inventory.go

📝 Walkthrough

Walkthrough

Propagates topbar provider/account chips into Inventory and Coverage: adds a shared API filter contract, backend provider/account scoping, frontend scoped loading and scope-aware empty states, and chip subscription wiring with visibility checks and microtask coalescing.

Changes

Provider/Account Filter Propagation

Layer / File(s) Summary
API Filter Contract
frontend/src/api/inventory.ts, frontend/src/__tests__/api-inventory.test.ts
InventoryFilter interface (provider?, accountID?) introduced; listActiveCommitments and getCoverageBreakdown accept filter objects and build optional account_id and provider query parameters; tests expanded for no-filter, provider-only, account-only, and combined filters.
Backend Provider & Account Scoping
internal/api/handler_inventory.go, internal/api/handler_inventory_test.go
fetchCommitmentRecords applies in-memory provider filtering; getCoverageBreakdown scopes recommendations via buildCoverageRecFilter and aggregateOnDemandByKey helpers so on-demand aggregation respects provider and account_id; tests added for provider and combined provider+account behavior.
Frontend Scoped Loading & Rendering
frontend/src/inventory.ts, frontend/src/__tests__/inventory.test.ts
loadActiveCommitments and loadCoverageBreakdown read current provider/account from state and pass them to API calls; renderActiveCommitmentsTable and empty-state message generation updated to be filter-aware.
Chip Subscription Wiring with Visibility Guard
frontend/src/inventory.ts, frontend/src/__tests__/inventory.test.ts
Adds wireChipSubscriptions() to subscribe to provider/account chip changes, detect Inventory tab visibility, coalesce rapid updates via queueMicrotask, and trigger conditional refetch of only the active sub-tab; tests validate registration, per-tab refetching, inactive-tab guard, update coalescing, and filter-aware empty-state text.

Sequence Diagram

sequenceDiagram
  participant TopbarChips
  participant wireChipSubscriptions
  participant isInventoryTabActive
  participant queueMicrotask
  participant loadActiveCommitments
  participant loadCoverageBreakdown
  TopbarChips->>wireChipSubscriptions: provider/account change
  wireChipSubscriptions->>isInventoryTabActive: isInventoryTabActive()
  alt tab active
    wireChipSubscriptions->>queueMicrotask: schedule callback
    queueMicrotask->>loadActiveCommitments: refetch if active sub-tab = commitments
    queueMicrotask->>loadCoverageBreakdown: refetch if active sub-tab = coverage
  else tab inactive
    wireChipSubscriptions-->>wireChipSubscriptions: skip refetch
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • LeanerCloud/CUDly#760: Introduces the /inventory/coverage endpoint and client; this PR extends that endpoint and client to accept and apply provider/account query filters.

Poem

"I nibble through chips and queues tonight,
Provider, account in gentle sight,
Microtasks pair changes tight,
Refetch only where tabs are bright,
Inventory sings — filters just right." 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: enabling Main Header chips (Provider and Account filters) to propagate into the Inventory subpages (Active Commitments and Coverage).
Linked Issues check ✅ Passed The pull request fully addresses all requirements from issue #866: implements chip propagation for both Active Commitments and Coverage, adds filter-aware empty states, includes comprehensive frontend tests, and uses the specified chip-subscription pattern with tab-active guards and queueMicrotask coalescing.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #866 requirements: frontend chip subscription logic, API signature updates to accept filters, backend filtering implementation, and corresponding test coverage with no extraneous modifications.
Docstring Coverage ✅ Passed Docstring coverage is 89.47% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/866-inventory-honor-global-filter

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

@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 30, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

✅ 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.

…ocyclo budget

Pull the recommendation-aggregation loop into aggregateOnDemandByKey so
that getCoverageBreakdown stays within the cyclomatic-10 limit enforced
by the pre-commit gocyclo hook (was 12, now 9). Pure extraction: no
behaviour change.
@cristim
Copy link
Copy Markdown
Member Author

cristim commented Jun 1, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

✅ 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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/api/handler_inventory.go (1)

157-197: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Apply account_id chip scoping to on-demand (recommendations) coverage
In internal/api/handler_inventory.go, getCoverageBreakdown fetches recommendations with h.scheduler.ListRecommendations(ctx, config.RecommendationFilter{}) (no AccountIDs / account_id), then only narrows by allowed-accounts and aggregates by provider—so when the account chip is active, coverage combines account-scoped covered spend with unscoped on-demand gaps, producing misleading per-service coverage (issue #866).

Scope recommendations the same way as commitments: pass params["account_id"] into RecommendationFilter.AccountIDs (using RecommendationRecord.CloudAccountID) or filter the returned recs to the selected account before calling aggregateOnDemandByKey. Add/extend a test to cover the account_id chip intersection (not just allowed-accounts).

🤖 Prompt for 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.

In `@internal/api/handler_inventory.go` around lines 157 - 197, The
recommendations query is not scoped to the selected account chip, mixing
account-scoped commitments with unscoped on-demand gaps; when calling
h.scheduler.ListRecommendations use a config.RecommendationFilter populated with
AccountIDs from params["account_id"] (or, if easier, after ListRecommendations
filter the returned recs by RecommendationRecord.CloudAccountID ==
params["account_id"]) before calling aggregateOnDemandByKey; update tests to
assert the account_id chip intersection (coverage combines only recommendations
for the selected account) in addition to existing allowed-accounts checks.
🧹 Nitpick comments (1)
internal/api/handler_inventory_test.go (1)

171-214: ⚡ Quick win

The new provider-filter test for listActiveCommitments is correct and well-scoped.

Consider adding a sibling test through getCoverageBreakdown that passes provider= (and account_id=) so the aggregateOnDemandByKey provider filtering — and the covered/on-demand account-scope consistency raised on handler_inventory.go — are locked down by a test rather than only validated indirectly.

🤖 Prompt for 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.

In `@internal/api/handler_inventory_test.go` around lines 171 - 214, Add a sibling
unit test for getCoverageBreakdown that explicitly passes the provider= and
account_id= query parameters to exercise the provider-filtering path; set up
MockConfigStore purchase history and cloud accounts similar to
TestHandler_listActiveCommitments_ProviderFilter, call
handler.getCoverageBreakdown with those params, and assert that
aggregateOnDemandByKey applies the provider filter and that covered vs on-demand
results are consistent at the account scope (use getCoverageBreakdown,
aggregateOnDemandByKey, and Handler methods/structs from handler_inventory.go to
locate the logic to verify).
🤖 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.

Outside diff comments:
In `@internal/api/handler_inventory.go`:
- Around line 157-197: The recommendations query is not scoped to the selected
account chip, mixing account-scoped commitments with unscoped on-demand gaps;
when calling h.scheduler.ListRecommendations use a config.RecommendationFilter
populated with AccountIDs from params["account_id"] (or, if easier, after
ListRecommendations filter the returned recs by
RecommendationRecord.CloudAccountID == params["account_id"]) before calling
aggregateOnDemandByKey; update tests to assert the account_id chip intersection
(coverage combines only recommendations for the selected account) in addition to
existing allowed-accounts checks.

---

Nitpick comments:
In `@internal/api/handler_inventory_test.go`:
- Around line 171-214: Add a sibling unit test for getCoverageBreakdown that
explicitly passes the provider= and account_id= query parameters to exercise the
provider-filtering path; set up MockConfigStore purchase history and cloud
accounts similar to TestHandler_listActiveCommitments_ProviderFilter, call
handler.getCoverageBreakdown with those params, and assert that
aggregateOnDemandByKey applies the provider filter and that covered vs on-demand
results are consistent at the account scope (use getCoverageBreakdown,
aggregateOnDemandByKey, and Handler methods/structs from handler_inventory.go to
locate the logic to verify).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4b48140c-0139-4a88-92ae-d5e99383c836

📥 Commits

Reviewing files that changed from the base of the PR and between 4956d66 and 72faef9.

📒 Files selected for processing (6)
  • frontend/src/__tests__/api-inventory.test.ts
  • frontend/src/__tests__/inventory.test.ts
  • frontend/src/api/inventory.ts
  • frontend/src/inventory.ts
  • internal/api/handler_inventory.go
  • internal/api/handler_inventory_test.go

…ageBreakdown

Before this change `getCoverageBreakdown` fetched recs via
`ListRecommendations(ctx, RecommendationFilter{})` — i.e. across every
account — even when the Main Header `account_id` chip was active. The
covered side honoured the chip (via `fetchCommitmentRecords` →
`GetPurchaseHistory(acc)`), so coverage% mixed account-scoped
commitments with cross-account on-demand gaps and over-reported the
remaining gap for the selected account.

Plumb `params["account_id"]` into `RecommendationFilter.AccountIDs` via
a tiny `buildCoverageRecFilter` helper. Pulling the construction into a
helper keeps `getCoverageBreakdown` at gocyclo=9 — the budget headroom
the PR #881 extraction already reserved is preserved.

Adds `TestHandler_getCoverageBreakdown_ProviderAndAccountChip` which
seeds multi-account, multi-provider commitments + recs, calls the
handler with both chips set, and asserts the mock scheduler is invoked
with `AccountIDs: []string{"acc-1"}` (the filter being passed is the
exact regression signal) plus the covered/on-demand sums match only the
selected-account+aws rows. Locks down both the F1 account-scope
consistency and the F2 provider+account chip intersection through
`getCoverageBreakdown`.

CR follow-up on PR #881; refs issue #866.
@cristim
Copy link
Copy Markdown
Member Author

cristim commented Jun 1, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

✅ 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 merged commit 1b99460 into feat/multicloud-web-frontend Jun 1, 2026
5 checks passed
cristim added a commit that referenced this pull request Jun 1, 2026
…871) (#906)

RI Exchange was the last Inventory sub-tab that ignored the Main Header
Provider/Account chips; Active Commitments and Coverage already honor them
(PR #866/#881). QA 2.9.

Frontend (riexchange.ts):
- loadRIExchange is now provider-aware. AWS shows convertible RIs + reshape
  recs + history; Azure shows exchangeable VM reservations + history with a
  provider-aware "reshape not available" note; GCP shows a "RI Exchange isn't
  available for GCP" empty state across all sections and calls no list
  endpoint.
- The convertible-RI list is scoped to the single-selected account chip and
  renders scoped, provider-aware empty states (named account/subscription).
- An in-progress exchange modal is closed synchronously on any filter change
  so the user can't act on a now-hidden RI (mutating workflow).
- New Azure table built via DOM/textContent (no innerHTML).

Backend (handler_ri_exchange.go):
- listConvertibleRIs accepts ?account_id= and returns an empty list when the
  chip selects an account other than the running ambient AWS account; a real
  STS failure fails closed. Azure already scopes by subscription_id; GCP has
  no endpoint (frontend short-circuits to empty).

Tests: frontend chip-scoping, GCP not-available state, Azure path, and
selection-clear on filter change; backend account-scope mismatch returns
empty and resolve-error fails closed.
@cristim cristim deleted the fix/866-inventory-honor-global-filter branch June 3, 2026 21:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/many Affects most users priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged type/bug Defect urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant