Skip to content

fix(home/filter): wire Account chip to dashboard chart (QA 2.3) - #747

Merged
cristim merged 1 commit into
feat/multicloud-web-frontendfrom
fix/qa-home-account-filter
May 27, 2026
Merged

fix(home/filter): wire Account chip to dashboard chart (QA 2.3)#747
cristim merged 1 commit into
feat/multicloud-web-frontendfrom
fix/qa-home-account-filter

Conversation

@cristim

@cristim cristim commented May 27, 2026

Copy link
Copy Markdown
Member

Summary

QA verification step 2.3 (Home page Global filter, "Select one Account at a time") flagged that the Savings-over-time chart shows "No purchase history yet" even when the selected account DOES have purchases.

Root cause (compound)

  1. Backend SQL (already fixed by PR fix(purchases): wire topbar filter chips to all three Purchases consumers (refs #701) #741): QueryHistory only matched account_id (legacy VARCHAR), not cloud_account_id (UUID FK). The topbar chip sends a UUID, so the historic query returned 0 rows for any account.
  2. Missing subscription coalescing: setupDashboardHandlers subscribed directly with () => void loadDashboard(), causing two concurrent loadDashboard() calls on provider change (the topbar fires account-cleared + provider-changed synchronously). Added queueMicrotask coalescing to dedupe within a tick.
  3. Missing home-tab guard: Filter subscriptions fired even when the user was on a different tab. Added isHomeTabActive() (mirrors isPurchasesTabActive() from savings-history.ts).
  4. Non-informative empty state: When the API legitimately returned 0 points due to an active filter, the chart fell back to the generic "No purchase history yet" message. Added buildTrendFilterDesc() for a filter-aware empty-state message (same pattern as PR fix(purchases): wire topbar filter chips to all three Purchases consumers (refs #701) #741's Purchases chart fix).

Files changed

  • frontend/src/dashboard.ts
  • frontend/src/__tests__/dashboard.test.ts

Test plan

  • 7 new tests covering all four root-cause cases.
  • All 63 frontend suites pass; all Go tests pass.
  • Manual verification post-deploy: pick each Account chip on the Home page, confirm KPIs and chart update to reflect only that account's purchases.

Refs QA row 384, step 2.3.

Summary by CodeRabbit

  • Performance - Optimized dashboard loading to prevent unnecessary reloads when navigating away from the Home tab.
  • UX Improvements - Savings trend empty-state messaging now adapts dynamically based on active account filters.

Review Change Stack

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 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 effort/s Hours type/bug Defect labels May 27, 2026
@coderabbitai

coderabbitai Bot commented May 27, 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: 952ec544-8c6d-46bf-b83c-fed5bf20d395

📥 Commits

Reviewing files that changed from the base of the PR and between 8f3e320 and 07d19b4.

📒 Files selected for processing (2)
  • frontend/src/__tests__/dashboard.test.ts
  • frontend/src/dashboard.ts

📝 Walkthrough

Walkthrough

Dashboard reload behavior now coalesces provider/account filter changes into a single queued call via queueMicrotask(), skips reloads when the Home tab is inactive, and displays filter-aware empty-state text when the savings trend chart returns no analytics data.

Changes

Dashboard reload coalescing and empty-state filtering

Layer / File(s) Summary
Home tab visibility guard
frontend/src/dashboard.ts
New isHomeTabActive() DOM-based function detects whether the Home tab is currently visible to control conditional reload behavior.
Dashboard reload coalescing via microtask
frontend/src/dashboard.ts
scheduleDashboardReload() mechanism replaces direct filter subscriptions, using queueMicrotask() to coalesce synchronous provider/account changes into a single queued loadDashboard() call and suppressing reloads when Home tab is inactive.
Savings trend empty-state with filter description
frontend/src/dashboard.ts
buildTrendFilterDesc() helper constructs user-facing descriptions of active filters; empty-state text selection now reflects filter presence, showing either "selected filter" or "no purchase history yet" messaging, with current provider captured for context.
QA test suite for dashboard filter and reload behavior
frontend/src/__tests__/dashboard.test.ts
Imports setupDashboardHandlers and adds comprehensive test coverage validating provider/account subscription registration, Home-tab-conditional refetching, change coalescing, account passthrough to getSavingsAnalytics, and filter-aware vs. generic empty-state messaging.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • LeanerCloud/CUDly#498: Implements the exact Home savings-over-time widget reload fix—provider/account subscriptions, conditional Home-only refetches, coalesced reloads, and account passthrough to getSavingsAnalytics.
  • LeanerCloud/CUDly#503: Applies the same dashboard reload and filter-passthrough pattern to the Purchases savings-history module, addressing the same code-level bug across different widgets.

Possibly related PRs

  • LeanerCloud/CUDly#190: Modifies dashboard filter-change flow around provider/account updates and loadDashboard() ordering, with related dashboard.test.ts coverage of refetch and reload sequencing.

Poem

🐰 A filtered reload, coalesced with care,
Microtasks dance through the midnight air—
Home tab glows bright, the empty state knows
Which filters are set where the data stream flows! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% 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: wiring the Account chip to the dashboard chart, a core fix for the identified QA issue.
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/qa-home-account-filter

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

@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 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 3931e9e into feat/multicloud-web-frontend May 27, 2026
5 checks passed
cristim added a commit that referenced this pull request May 27, 2026
The rebase onto origin/feat/multicloud-web-frontend (which merged the
buildTrendFilterDesc removal in PR #747) left an orphaned closing brace
at frontend/src/dashboard.ts:624 that TypeScript rejected. Drop the
brace so the file compiles cleanly on the rebased base.
cristim added a commit that referenced this pull request May 27, 2026
* fix(home/chart): x-axis spans selected timeframe window (QA 3.1)

The Savings-over-time chart on the Home page snapped the x-axis to
the single purchase date regardless of the selected timeframe (7d/30d/
90d/All). Now the x-axis always spans [now - window, now], purchases
are positioned by their actual date within that window, and empty
windows render labelled axes instead of a "No purchase history" stub.

Refs QA row 405, step 3.1.

* fixup: remove stray closing brace left after rebase

The rebase onto origin/feat/multicloud-web-frontend (which merged the
buildTrendFilterDesc removal in PR #747) left an orphaned closing brace
at frontend/src/dashboard.ts:624 that TypeScript rejected. Drop the
brace so the file compiles cleanly on the rebased base.

* fix(home/chart): align savings-trend-empty assertions + lift All-range 365d cap

Finding 1 (conflicting empty-state assertions): the QA 3.1 test at line 779
asserted that a successful-but-empty response hides #savings-trend-empty
(show blank axes), while the QA 2.3 tests at lines 981-995 asserted the
opposite (show a friendly empty-state message). QA 2.3 is the more-recent,
intentional policy: show the empty-state with account context when a filter
is active, and a generic "No purchase history yet" message when no filter is
active. Production code updated to implement this policy with an early-return
on empty data_points; QA 3.1 test updated and annotated to match.

Finding 2 (All-range 365d cap): the 'all' range was hardcoded to 365 days
both in the days computation and in the API start param, silently truncating
history older than a year. Changed to 3650 days (10-year sentinel that
parseDateRange accepts as a valid RFC3339 start), which lets the backend
return the full available history. The x-axis interval for 'all' is now
'weekly' (appropriate for a multi-year span).

Finding 3 (chart/KPI filter parity): loadSavingsTrendChart previously passed
account_ids only when exactly one account was selected, causing the chart to
show all-accounts data while the KPI tiles above it filtered to the selected
subset. Changed the guard from `=== 1` to `> 0` so any non-empty selection
is forwarded. Added a regression test asserting that multi-account filters
are passed through. Provider filtering is not yet forwarded (backend analytics
handler has no provider param); this is documented in a comment.

* fix(home/chart): drop provider name from empty state + omit start on All range

Finding 1: the empty-state message showed "No savings history for <provider>"
when a provider filter was active, but the analytics endpoint does not accept
a provider param yet (tracked in #764), so the query always returns
all-provider data. Mentioning the provider implied a scope that was never
applied. Drop the provider branch; show "No purchase history yet." until #764
lands. Add inline comment and regression test.

Finding 2: the All range capped history at 10 years by computing
now - 3650d as the start param. The backend parseDateRange defaults a
missing start to (end - 7d), so we cannot omit it — instead send the
Unix epoch sentinel "1970-01-01T00:00:00Z" to get truly unbounded history.
Add regression test asserting the epoch constant reaches the API.
cristim added a commit that referenced this pull request Jun 1, 2026
)

The chart re-rendered only on initial mount; chip changes were silently
ignored. Subscriptions now refilter the Service chart on provider /
account changes, mirroring PR #747's pattern.

Closes #867.
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/qa-home-account-filter branch June 3, 2026 21:55
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/p1 Next up; this sprint 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