Skip to content

feat(frontend/dashboard): Potential Monthly Savings card mirrors Recommendations-page range (closes #293)#295

Merged
cristim merged 1 commit into
feat/multicloud-web-frontendfrom
feat/dashboard-savings-range-293
May 5, 2026
Merged

feat(frontend/dashboard): Potential Monthly Savings card mirrors Recommendations-page range (closes #293)#295
cristim merged 1 commit into
feat/multicloud-web-frontendfrom
feat/dashboard-savings-range-293

Conversation

@cristim
Copy link
Copy Markdown
Member

@cristim cristim commented May 5, 2026

Summary

  • Dashboard's "Potential Monthly Savings" card was showing summary.potential_monthly_savings — a flat sum across every variant of every cell that overstates achievable savings by ~6x (same bug class as ux(frontend/recs): Potential Monthly Savings card sums all variants — should mirror Recommended range #272 on the Recommendations page)
  • Fix: loadDashboard now fetches /api/recommendations concurrently alongside the existing summary + upcoming calls, computes the per-cell range client-side using groupRecsByCell / pageLevelRange / formatSavingsRange (same helpers the Recommendations page uses), and renders savingsMin – savingsMax on the card
  • formatSavingsRange is exported from recommendations.ts to enable reuse

Behaviour change

Before: $X where X is the sum of every variant of every cell (overcounted)
After: $min – $max (or $X if min == max) — matches the Recommendations-page card

Implementation notes

  • Frontend-only: /api/dashboard/summary is left unchanged. A future backend PR can move range computation server-side
  • Failure isolation via Promise.allSettled: if /api/recommendations fails, the savings card falls back to $0; Active Commitments, Current Coverage, YTD Savings still render from the summary response
  • No selection narrowing on Dashboard — Dashboard is read-only (no checkboxes), card always reflects the full range

Test plan

  • npm run typecheck — clean
  • npm test -- --testPathPattern=dashboard — 21/21 pass (+3 new tests)
    • Potential Monthly Savings card renders per-cell range (#293) — asserts range shown, flat sum NOT shown
    • failure-isolation: rec fetch failure leaves other cards rendered (#293) — asserts other cards intact, savings falls back to $0
    • legacy summary.potential_monthly_savings is no longer read for the savings card (#293) — asserts $700 summary value not shown when recs give $300-$400
  • Full suite — 1464/1464 pass

Summary by CodeRabbit

  • New Features

    • Dashboard now derives "Potential Monthly Savings" from recommendation data for more accurate calculations.
    • Dashboard continues to display all cards even if recommendation data fails to load.
  • Tests

    • Added tests to verify savings calculations and dashboard resilience when recommendations are unavailable.

…mmendations-page range (closes #293)

Dashboard's "Potential Monthly Savings" card previously read
summary.potential_monthly_savings — a flat sum across every variant of
every cell that overstates achievable savings by ~6x (the same bug class
#272 closed on the Recommendations page).

Frontend-only fix: loadDashboard now fetches /api/recommendations
concurrently alongside /api/dashboard/summary and /api/upcoming via
Promise.allSettled (failure of any individual fetch is isolated — the
dashboard still renders with graceful fallbacks). The per-cell range is
computed client-side via the existing groupRecsByCell / pageLevelRange /
formatSavingsRange helpers already used by the Recommendations page.
formatSavingsRange is newly exported from recommendations.ts to enable reuse.

If getRecommendations fails, the savings card falls back to $0 and the
other three cards (Active Commitments, Current Coverage, YTD Savings)
render normally from the summary response.

The backend change to /dashboard/summary is deferred; a future PR can
move the range computation server-side if needed.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f35d5a5c-6cc3-44f7-8815-4463f9dfd2bd

📥 Commits

Reviewing files that changed from the base of the PR and between bb619f2 and f7b500d.

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

📝 Walkthrough

Walkthrough

The dashboard now fetches recommendations and computes "Potential Monthly Savings" from per-cell recommendation ranges instead of using the static API summary value, with error isolation via Promise.allSettled and comprehensive test coverage.

Changes

Dashboard Savings Range Computation

Layer / File(s) Summary
Export & Type Updates
frontend/src/recommendations.ts, frontend/src/dashboard.ts
formatSavingsRange is exported from recommendations module. Dashboard imports LocalRecommendation type and recommendation helper functions (groupRecsByCell, pageLevelRange, formatSavingsRange).
Data Fetching
frontend/src/dashboard.ts
loadDashboard switches to Promise.allSettled to fetch summary, upcoming purchases, and recommendations independently, deriving typed recs from fulfilled results or empty list on failure.
Rendering Logic
frontend/src/dashboard.ts
renderDashboardSummary accepts recommendations list and computes "Potential Monthly Savings" from per-cell ranges via groupRecsByCellpageLevelRangeformatSavingsRange, replacing the legacy data.potential_monthly_savings value.
Mocking & Test Coverage
frontend/src/__tests__/dashboard.test.ts
Mock implementations for recommendations helpers and api.getRecommendations are added. Tests verify savings card renders computed ranges, that recommendation fetch failures are isolated, and that legacy summary values are no longer used.

Sequence Diagram

sequenceDiagram
    participant Dashboard as Dashboard Component
    participant API as API Layer
    participant RecHelpers as Recommendation Helpers
    participant UI as Rendered UI

    Dashboard->>API: Promise.allSettled([summary, upcoming, recommendations])
    API-->>Dashboard: { summary, recommendations }
    
    alt Recommendations Fetch Success
        Dashboard->>RecHelpers: groupRecsByCell(recs)
        RecHelpers-->>Dashboard: grouped cells
        Dashboard->>RecHelpers: pageLevelRange(grouped)
        RecHelpers-->>Dashboard: {min, max}
        Dashboard->>RecHelpers: formatSavingsRange(min, max)
        RecHelpers-->>Dashboard: "$X–$Y"
        Dashboard->>UI: render with computed range
    else Recommendations Fetch Failure
        Dashboard->>UI: render savings card with $0 fallback
    end
    
    Dashboard->>UI: render other cards from summary (isolated)
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related Issues

  • LeanerCloud/CUDly#279: Dashboard computes per-cell savings ranges client-side from recommendation data instead of using backend summary totals.
  • LeanerCloud/CUDly#272: Changes how "Potential Monthly Savings" is computed and displayed, shifting from backend summary to per-cell recommendation-derived ranges.
  • LeanerCloud/CUDly#293: Primary issue for this PR; implements frontend fix to fetch recommendations and render per-cell savings ranges with failure isolation.
  • LeanerCloud/CUDly#225: Implements the same per-cell and overall-range logic described in the retrieved issue using groupRecsByCell, pageLevelRange, and formatSavingsRange.

Possibly Related PRs

  • LeanerCloud/CUDly#253: Both PRs reuse the same recommendation helpers (groupRecsByCell, pageLevelRange, formatSavingsRange) and add test coverage for per-cell savings range rendering.
  • LeanerCloud/CUDly#276: Both PRs switch "Potential Monthly Savings" display from API summary totals to client-side per-cell range computation using the same recommendation helper chain.
  • LeanerCloud/CUDly#277: Both PRs modify frontend recommendation-related code including type definitions and helper exports that the main PR relies on.

Suggested Labels

urgency/this-quarter, effort/m

Poem

🐰 Recommendations now flow through the dash,
Per-cell savings ranges computed in a flash!
No more static sums from the API's deep store—
The frontend computes ranges, we wanted much more! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% 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 clearly and specifically describes the main change: making the Dashboard's "Potential Monthly Savings" card derive its display from Recommendations-page range calculations rather than a flat summary value.
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 feat/dashboard-savings-range-293

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

@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/feat New capability labels May 5, 2026
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 5, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 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.

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/feat New capability urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant