Skip to content

fix(purchases): wire topbar filter chips to all three Purchases consumers (refs #701) - #741

Merged
cristim merged 2 commits into
feat/multicloud-web-frontendfrom
fix/701-global-filter-v2
May 27, 2026
Merged

fix(purchases): wire topbar filter chips to all three Purchases consumers (refs #701)#741
cristim merged 2 commits into
feat/multicloud-web-frontendfrom
fix/701-global-filter-v2

Conversation

@cristim

@cristim cristim commented May 26, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #701 follow-up (PR #716 left several consumers unwired; QA rows 305-307 still failed).

Root causes and fixes

Bug Root cause Fix
4.2 Provider filter — Purchase History/Approval Queue don't react setupHistoryHandlers() was exported from history.ts but never called in app.ts app.ts: import + call setupHistoryHandlers() after initSavingsHistory()
4.4 Account filter — Savings History chart disappears QueryHistory/QueryBreakdown SQL matched account_id (VARCHAR 12-digit ARN tail) only; topbar chip sends cloud_accounts.id (UUID) analytics_postgres.go: added OR cloud_account_id::text = $3 to WHERE in both queries
4.4/4.5 Empty-state UX — chart hides with no explanation showEmptyState() always showed generic copy savings-history.ts: buildFilterDesc() helper + filter-aware copy ("No savings data for the selected filter (AWS)")

Test plan

  • Backend internal/api: 1275 pass, incl. 2 new UUID filter tests
  • Frontend: 63 suites, 1998 pass
  • setupHistoryHandlers subscription tests
  • Filter-aware empty-state tests
  • Manual: select Provider, Account, Provider+Account on Purchases page — Approval queue, Purchase History, and Savings History chart should all filter

Files changed (9, +261/-12): frontend/src/app.ts, internal/api/analytics_postgres.go, frontend/src/modules/savings-history.ts, plus tests.

Summary by CodeRabbit

  • New Features

    • Empty-state messaging now shows active filters and provides a help prompt to broaden filters when no savings history appears.
    • Error states display distinct failure messaging with retry guidance when history fails to load.
    • Account filtering accepts UUID identifiers and treats the "all" provider as unfiltered in messaging.
  • Bug Fixes

    • History views (including Purchase History and Approval Queue) now reload correctly when provider or account filters change.

Review Change Stack

…mers (#701)

Three sub-flows of the global filter on the Purchases page still failed
after PR #716:

1. Provider filter (4.2): selecting a provider did nothing to Purchase
   History or Approval Queue because setupHistoryHandlers() was exported
   from history.ts but never called in app.ts.

2. Account filter (4.4): the Savings History chart disappeared when an
   account UUID was selected because the analytics SQL only matched the
   legacy account_id VARCHAR(20) column, not cloud_account_id UUID FK.
   The topbar chip always sends cloud_accounts.id (a UUID), so the WHERE
   clause returned 0 rows and the chart fell into the empty-state branch.

3. Empty-state UX (4.4/4.5): showEmptyState() always showed "No savings
   history data available yet." regardless of whether a filter caused the
   empty result, giving no signal to the user.

Changes:
- frontend/src/app.ts: import and call setupHistoryHandlers() after
  initSavingsHistory() so provider/account chip changes reload Purchase
  History and Approval Queue.
- internal/api/analytics_postgres.go: add OR cloud_account_id::text = $3
  to the WHERE clause in QueryHistory and QueryBreakdown so UUID-based
  account filtering includes rows written by current purchase flows.
- frontend/src/modules/savings-history.ts: add buildFilterDesc() helper
  and update showEmptyState() to show "No savings data for the selected
  filter (AWS)" when a filter is active vs the original message when not.
- Tests: new Go tests for UUID account filter path; new TS tests for
  setupHistoryHandlers subscriptions and filter-aware empty-state copy;
  update history mocks in index/sidebar-anchors/purchase-execution-toast
  tests to include setupHistoryHandlers.
@cristim cristim added triaged Item has been triaged priority/p1 Next up; this sprint severity/medium Moderate harm urgency/this-sprint Within the current sprint impact/many Affects most users labels May 26, 2026
@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b2ec5ccb-f6df-4b95-9a7c-f806e0fe3459

📥 Commits

Reviewing files that changed from the base of the PR and between 9a54389 and 2083142.

📒 Files selected for processing (3)
  • frontend/src/__tests__/savings-history.test.ts
  • frontend/src/modules/savings-history.ts
  • internal/api/analytics_postgres_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/modules/savings-history.ts
  • internal/api/analytics_postgres_test.go

📝 Walkthrough

Walkthrough

Backend analytics queries now accept UUID account IDs by matching either legacy account_id or cloud_account_id::text. Frontend wires setupHistoryHandlers() into app init, updates savings-history to show filter-aware empty/error messages, and expands test mocks and suites to cover these behaviors.

Changes

Issue #701: UUID Account ID Support with Filter Awareness

Layer / File(s) Summary
Backend Analytics UUID Account ID Filtering
internal/api/analytics_postgres.go, internal/api/analytics_postgres_test.go
QueryHistory and QueryBreakdown WHERE clauses now match account_id or cloud_account_id::text; inline docs and tests verify UUID binding and aggregations.
Frontend History Handler Subscriptions & Test Mocks
frontend/src/app.ts, frontend/src/__tests__/history.test.ts, frontend/src/__tests__/index.test.ts, frontend/src/__tests__/purchase-execution-toast.test.ts, frontend/src/__tests__/sidebar-anchors.test.ts
Import and call setupHistoryHandlers() during initialization; tests/mocks updated to expose and verify setupHistoryHandlers registers provider/account subscriptions that trigger loadHistory / api.getHistory.
Savings History Filter-Aware Empty/Error States
frontend/src/modules/savings-history.ts, frontend/src/__tests__/savings-history.test.ts
Builds filterDesc from provider/account chips, passes it to empty-state renderer; adds showErrorState(...) and tests for filter-aware empty copy, error copy, and provider "all" sentinel handling; test DOM fixture updated.

Sequence Diagram(s)

sequenceDiagram
  participant App as App Initialization
  participant Setup as setupHistoryHandlers
  participant State as State Manager
  participant Loader as loadHistory
  participant API as api.getHistory
  App->>Setup: call setupHistoryHandlers()
  Setup->>State: state.subscribeProvider(callback)
  Setup->>State: state.subscribeAccount(callback)
  State-->>Setup: callbacks registered
  State->>Loader: trigger callback on filter change
  Loader->>API: api.getHistory()
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Suggested labels

effort/m, type/bug

Poem

🐰 A tale of filters, old and new
UUID accounts now pass on through,
Handlers wake when chips are pressed,
Empty states tell which filter's guessed,
Charts and tests all hop anew.

🚥 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 'fix(purchases): wire topbar filter chips to all three Purchases consumers (refs #701)' directly summarizes the main technical change: wiring filter chips to Purchase History, Approval Queue, and Savings History consumers.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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/701-global-filter-v2

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

@cristim

cristim commented May 26, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 26, 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 26, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 26, 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: 3

🤖 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/modules/savings-history.ts`:
- Around line 121-125: The function buildFilterDesc currently treats provider
=== 'all' as an active filter; update buildFilterDesc to treat the "all"
sentinel as unfiltered by skipping adding provider to parts when provider is
falsy or equals 'all' (case-insensitive), e.g. check provider &&
provider.toLowerCase() !== 'all' before pushing provider.toUpperCase(); keep the
existing accountIDs behavior (push accountIDs[0] if present) so empty-state
copy/rendering becomes correct.
- Around line 64-67: The catch block currently calls showEmptyState, which
misleads users on API failures; implement a new showErrorState(chartContainer,
emptyEl, statsEl, message) that hides chartContainer and statsEl, shows emptyEl
with an explicit "Failed to load savings history." heading and help text
containing the provided message, and destroys the global savingsChart if
present; then replace the catch path to call showErrorState(chartContainer,
emptyEl, statsEl, error instanceof Error ? error.message : String(error))
instead of showEmptyState to surface real fetch failures.

In `@internal/api/analytics_postgres_test.go`:
- Around line 182-184: The test's mock.ExpectQuery currently only matches the
SELECT prefix so it won't catch removal of the account filter; update the
ExpectQuery regex to include the WHERE fragment that asserts the dual-column
account filter (e.g., the condition that checks cloud_account_id::text and
cloud_account_external_id::text equals $3) so the mock fails if that filter is
removed; modify the ExpectQuery call (the one chained with
WithArgs(pgxmock.AnyArg(), pgxmock.AnyArg(), uuid).WillReturnRows(rows)) to
assert the WHERE clause explicitly in the regex.
🪄 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: 2a96a6fd-3e49-4727-bec4-3e617d42fc0b

📥 Commits

Reviewing files that changed from the base of the PR and between 6fcf6a6 and 9a54389.

📒 Files selected for processing (9)
  • frontend/src/__tests__/history.test.ts
  • frontend/src/__tests__/index.test.ts
  • frontend/src/__tests__/purchase-execution-toast.test.ts
  • frontend/src/__tests__/savings-history.test.ts
  • frontend/src/__tests__/sidebar-anchors.test.ts
  • frontend/src/app.ts
  • frontend/src/modules/savings-history.ts
  • internal/api/analytics_postgres.go
  • internal/api/analytics_postgres_test.go

Comment thread frontend/src/modules/savings-history.ts
Comment thread frontend/src/modules/savings-history.ts
Comment thread internal/api/analytics_postgres_test.go Outdated
Three CodeRabbit findings addressed:

1. (Major, line 67) Use explicit error state on fetch failure: add
   showErrorState() that sets distinct heading/help copy ("Failed to
   load savings history. / Please retry.") instead of the no-data
   empty state, so users can tell a filter-scope empty from a real
   outage. Update the catch path to call showErrorState with the
   extracted error message.

2. (Minor, line 125) Treat the 'all' provider sentinel as unfiltered
   in buildFilterDesc: skip pushing provider.toUpperCase() when the
   value is 'all' (case-insensitive), so the empty-state heading reads
   "No savings history data available yet." rather than "No savings
   data for the selected filter (ALL)." when no real provider chip is
   selected.

3. (Minor, line 184) Tighten SQL expectations in
   TestQueryHistory_CloudAccountIDFilter and
   TestQueryBreakdown_CloudAccountIDFilter: prefix regexes with (?s)
   and extend to include the dual-column account filter WHERE fragment,
   so removing that clause would now cause the mock expectations to
   fail instead of passing silently.

Tests: all 66 savings-history frontend tests pass; Go analytics tests
pass (including the two tightened CloudAccountID tests).
@cristim

cristim commented May 27, 2026

Copy link
Copy Markdown
Member Author

Addressed all three CR findings from the 2026-05-27 09:48Z review (commit 2083142):

F1 (Major, savings-history.ts:67) - Error state on fetch failure
Added showErrorState() that sets a "Failed to load savings history." heading and "Please retry. ()" help text. The catch path now calls it with the extracted error message instead of delegating to showEmptyState. Avoids misleading users with a "no data" message when the API call actually failed.

F2 (Minor, savings-history.ts:125) - 'all' sentinel treated as unfiltered
buildFilterDesc now guards with provider && provider.toLowerCase() !== 'all' so the 'all' sentinel (no real provider chip selected) does not produce "ALL" in the empty-state heading. The account clause is unaffected.

F3 (Minor, analytics_postgres_test.go:184) - Tighter SQL expectations
Both TestQueryHistory_CloudAccountIDFilter and TestQueryBreakdown_CloudAccountIDFilter now match the full dual-column account filter fragment: (?s)SELECT ... AND \(\$3 = '' OR account_id = \$3 OR cloud_account_id::text = \$3\). Removing the WHERE clause would now cause both mocks to fail.

Tests: 66 frontend savings-history tests pass; all Go ./internal/api/... tests pass.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

@cristim Thanks for the thorough write-up on all three fixes! The changes look well-reasoned:

  • F1 — Separating showErrorState() from showEmptyState() is the right call; users get accurate feedback instead of a misleading "no data" message on API failures.
  • F2 — Guarding against the 'all' sentinel prevents the spurious "ALL" label in the empty-state heading.
  • F3 — The tighter regex anchoring the full dual-column filter fragment makes the SQL contract explicit and the tests much more meaningful.

Triggering a fresh review now.

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

@cristim
cristim merged commit cb735f2 into feat/multicloud-web-frontend May 27, 2026
5 checks passed
cristim added a commit that referenced this pull request May 27, 2026
The Home page Savings-over-time chart showed "No purchase history yet"
for accounts that DID have purchases when selected via the topbar
Account filter. Root cause: three issues compounded:

1. Backend SQL (fixed by PR #741): QueryHistory matched only the
   legacy account_id VARCHAR(20) column, not cloud_account_id UUID FK.
   The topbar chip sends cloud_accounts.id (a UUID), so all rows
   returned 0 and the chart fell into the empty-state branch.

2. Missing coalescing: setupDashboardHandlers subscribed directly with
   () => void loadDashboard(), causing two concurrent loadDashboard()
   calls on provider change (topbar fires both account-cleared and
   provider-changed synchronously). Added queueMicrotask coalescing
   matching the scheduleReload pattern in modules/savings-history.ts.

3. Missing home-tab guard: subscriptions fired even when the user was
   on a different tab, wasting API calls. Added isHomeTabActive() guard
   matching isPurchasesTabActive() in modules/savings-history.ts.

4. Non-informative empty state: when the API returned 0 data points
   due to an active filter, the chart showed the generic "No purchase
   history yet" message rather than indicating the filter caused the
   empty result. Added buildTrendFilterDesc() and filter-aware
   empty.textContent, mirroring showEmptyState() in savings-history.ts.

Tests: 7 new tests covering subscription wiring, active-tab guard,
coalescing, account_id forwarding, and filter-aware empty-state copy.
All 63 frontend test suites pass; all Go tests pass.

Mirrors PR #741 fix for the Purchases page (same filter-not-wired
pattern). Verified by selecting each account in turn and confirming
the chart reflects only that account's history.

Refs QA row 384, step 2.3.
cristim added a commit that referenced this pull request Jun 1, 2026
…#881)

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

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.

* refactor(api/inventory): extract getCoverageBreakdown helper to fit gocyclo 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.

* fix(api/inventory): scope recommendations to account chip in getCoverageBreakdown

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
cristim deleted the fix/701-global-filter-v2 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

impact/many Affects most users priority/p1 Next up; this sprint severity/medium Moderate harm triaged Item has been triaged urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant