fix(api/dashboard): populate ServiceSavings.CurrentSavings from active commitments - #926
Conversation
|
@coderabbitai review |
|
Warning Review limit reached
More reviews will be available in 13 minutes and 28 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 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
✅ Actions performedReview triggered.
|
…ve commitments ServiceSavings.CurrentSavings was declared in types.go but never written, causing the Home page chart's green "Current Savings" bars to always render as zero. Root cause: summarizeRecommendationsWithCoverage only filled PotentialSavings; no code path ever aggregated active purchase history into the per-service map. Fix: extract aggregateActiveCommitmentsPerService (the shared primitive that applies the same isActiveCommitment gate as calculateCommitmentMetrics) and have calculateCommitmentMetrics derive its committedMonthly total from the map it returns. getDashboardSummary merges the per-service map into byService after the recommendations reducer runs. Tests added: - TestAggregateActiveCommitmentsPerService: two active services, one expired+one active, all expired - TestHandler_getDashboardSummary_CurrentSavingsPopulated: EC2+RDS active commitments with an expired EC2 row that must be excluded - TestHandler_getDashboardSummary_CurrentSavingsJSON: wire-shape assertion on the current_savings field Note: if a fix/committed-monthly PR adds status filtering or unit normalisation to calculateCommitmentMetrics, the aggregateActiveCommitmentsPerService primitive is the single place to update for consistent gating on both the KPI total and the per-service breakdown.
c067469 to
b0820dd
Compare
|
Rebased on The two PRs touched the same
Verified: @coderabbitai review |
|
🧠 Learnings used✅ Action performedReview finished.
|
closes #1031) summarizeRecommendationsWithCoverage incorrectly wrote CurrentSavings to the coverage-scaled potential amount (same as PotentialSavings). Because getDashboardSummary only overwrites CurrentSavings for services present in the purchase-history result, services with recommendations but no active purchases shipped a non-zero current_savings equal to potential_savings. This was wrong: the frontend renders current_savings as committed/realized savings from active purchase history, so an uncommitted service must ship 0. The partial fix in #926 (aggregateActiveCommitmentsPerService + purchase- history overwrite) was correct in concept but left the incorrect assignment in summarizeRecommendationsWithCoverage active, masking the bug for services that had no purchases. Fix: remove svc.CurrentSavings += scaled from summarizeRecommendationsWithCoverage. CurrentSavings is getDashboardSummary's responsibility (via the overwrite loop that calls aggregateActiveCommitmentsPerService). The function comment and the three affected test assertions/functions are updated accordingly. Regression test added: TestHandler_getDashboardSummary_CurrentSavingsZeroWhenNoCommitments asserts current_savings: 0 for services with recommendations but no purchases.
closes #1031) summarizeRecommendationsWithCoverage incorrectly wrote CurrentSavings to the coverage-scaled potential amount (same as PotentialSavings). Because getDashboardSummary only overwrites CurrentSavings for services present in the purchase-history result, services with recommendations but no active purchases shipped a non-zero current_savings equal to potential_savings. This was wrong: the frontend renders current_savings as committed/realized savings from active purchase history, so an uncommitted service must ship 0. The partial fix in #926 (aggregateActiveCommitmentsPerService + purchase- history overwrite) was correct in concept but left the incorrect assignment in summarizeRecommendationsWithCoverage active, masking the bug for services that had no purchases. Fix: remove svc.CurrentSavings += scaled from summarizeRecommendationsWithCoverage. CurrentSavings is getDashboardSummary's responsibility (via the overwrite loop that calls aggregateActiveCommitmentsPerService). The function comment and the three affected test assertions/functions are updated accordingly. Regression test added: TestHandler_getDashboardSummary_CurrentSavingsZeroWhenNoCommitments asserts current_savings: 0 for services with recommendations but no purchases.
closes #1031) summarizeRecommendationsWithCoverage incorrectly wrote CurrentSavings to the coverage-scaled potential amount (same as PotentialSavings). Because getDashboardSummary only overwrites CurrentSavings for services present in the purchase-history result, services with recommendations but no active purchases shipped a non-zero current_savings equal to potential_savings. This was wrong: the frontend renders current_savings as committed/realized savings from active purchase history, so an uncommitted service must ship 0. The partial fix in #926 (aggregateActiveCommitmentsPerService + purchase- history overwrite) was correct in concept but left the incorrect assignment in summarizeRecommendationsWithCoverage active, masking the bug for services that had no purchases. Fix: remove svc.CurrentSavings += scaled from summarizeRecommendationsWithCoverage. CurrentSavings is getDashboardSummary's responsibility (via the overwrite loop that calls aggregateActiveCommitmentsPerService). The function comment and the three affected test assertions/functions are updated accordingly. Regression test added: TestHandler_getDashboardSummary_CurrentSavingsZeroWhenNoCommitments asserts current_savings: 0 for services with recommendations but no purchases.
closes #1031) (#1041) * fix(api): current_savings zero for services with no active commitments (closes #1031) summarizeRecommendationsWithCoverage incorrectly wrote CurrentSavings to the coverage-scaled potential amount (same as PotentialSavings). Because getDashboardSummary only overwrites CurrentSavings for services present in the purchase-history result, services with recommendations but no active purchases shipped a non-zero current_savings equal to potential_savings. This was wrong: the frontend renders current_savings as committed/realized savings from active purchase history, so an uncommitted service must ship 0. The partial fix in #926 (aggregateActiveCommitmentsPerService + purchase- history overwrite) was correct in concept but left the incorrect assignment in summarizeRecommendationsWithCoverage active, masking the bug for services that had no purchases. Fix: remove svc.CurrentSavings += scaled from summarizeRecommendationsWithCoverage. CurrentSavings is getDashboardSummary's responsibility (via the overwrite loop that calls aggregateActiveCommitmentsPerService). The function comment and the three affected test assertions/functions are updated accordingly. Regression test added: TestHandler_getDashboardSummary_CurrentSavingsZeroWhenNoCommitments asserts current_savings: 0 for services with recommendations but no purchases. * fix(api/dashboard): YTD month accuracy, store error log, fresh slice, first-service dedup * fix(test): update CurrentSavingsZeroWhenNoCommitments to mock GetActivePurchaseHistory getDashboardSummary now calls GetActivePurchaseHistory (active-only, uncapped) instead of GetAllPurchaseHistory to aggregate commitment KPIs (see fetchCommitmentPurchases). The regression test was written before this change and still mocked the old method, causing a testify panic on unexpected method call. Replace the GetAllPurchaseHistory mock with GetActivePurchaseHistory using three mock.Anything matchers (asOf, accountUUIDs, accountExternalIDsByProvider), matching the pattern already used in the sibling CurrentSavingsPopulated and CurrentSavingsJSON tests. * lint(api): fix rangeValCopy and godot findings in handler_dashboard.go golangci-lint (gocritic, godot) flagged three issues introduced by the dashboard commits: - Line 179: range-value copy of config.RecommendationRecord (312 bytes) per iteration; switch to index-based _rvc pattern consistent with the rest of handler_dashboard.go. - Line 229: godot: comment block before summarizeRecommendationsWithCoverage ended in ")" not "."; reword to end in period. - Line 703: godot: calculateCurrentCoverage doc comment missing trailing period. No behavioural change.
Problem
ServiceSavings.CurrentSavingswas declared ininternal/api/types.gobut never populated. The frontend atfrontend/src/dashboard.tsreadsbyService[s]?.current_savingsfor the green bar dataset, so every service rendered as $0.summarizeRecommendationsWithCoverageonly filledPotentialSavings; no code path ever aggregated active purchase history into the per-service map.Changes
aggregateActiveCommitmentsPerService(new): pure function that sumsEstimatedSavingsof active (non-expired) rows from purchase history, grouped byService. This is the shared primitive for both the KPI total and the per-service chart so both paths apply the sameisActiveCommitmentgate.calculateCommitmentMetrics: now returns a fourth value --savingsByService map[string]float64-- and derivescommittedMonthlyfrom the per-service map (previously summed inline). No change to the returned KPI values.getDashboardSummary: after the recommendations reducer runs, merges the per-service commitment map intobyService, settingentry.CurrentSavingsfor each service.Tests added
Three new tests, 9 new sub-cases total:
TestAggregateActiveCommitmentsPerService: two active services accumulate independently; one expired + one active row for the same service; all-expired returns empty map.TestHandler_getDashboardSummary_CurrentSavingsPopulated: EC2 + RDS active commitments with an expired EC2 row that must not be counted; asserts bothPotentialSavingsandCurrentSavingsare correct in the response struct.TestHandler_getDashboardSummary_CurrentSavingsJSON: asserts thecurrent_savingsfield carries the active purchase'sEstimatedSavingsthrough the response.All existing
TestHandler_calculateCommitmentMetricscases updated for the new 4-return-value signature.Coordination with sibling PRs
If a
fix/committed-monthlyPR adds status filtering (e.g. only counting"succeeded"rows) or unit normalisation tocalculateCommitmentMetrics,aggregateActiveCommitmentsPerServiceis the single place to update. Both the KPI total and the per-service breakdown flow through it. This PR should merge after any such sibling so the gating is consistent from day one -- or the sibling can build on top of this primitive.Test run