Skip to content

fix(savings-history): correct KPI units ($/mo) + Hourly/Monthly/Yearly toggle - #758

Merged
cristim merged 1 commit into
feat/multicloud-web-frontendfrom
fix/750-savings-history-units
May 27, 2026
Merged

cristim merged 1 commit into
feat/multicloud-web-frontendfrom
fix/750-savings-history-units

Conversation

@cristim

@cristim cristim commented May 27, 2026 •

Copy link
Copy Markdown
Member

Summary

Savings History on the Purchases page showed wrong unit labels and lacked a unit toggle:

  • Period Savings: $1.15 (no unit)
  • Avg Hourly Savings: $1.15/hr
  • Peak Savings: $1.15/hr

The underlying values are actually monthly (~$1.15/mo for the single t4g.nano example). $1.15/hr would imply ~$840/mo -- clearly wrong.

Root cause

renderSavingsStats blindly appended /hr to all three KPIs, even though every field in the API response is monthly (total_savings, total_period_savings, average_savings_per_period, peak_savings).

Fix

  • Default display switched to /mo (matches API).
  • Added #savings-unit dropdown (Hourly / Monthly / Yearly).
  • New helpers: SavingsUnit, convertFromMonthly, unitSuffix, unitLabel, getSelectedUnit.
  • renderSavingsStats + renderSavingsChart convert and label per the selected unit.
  • Avg card heading is now dynamic (Avg Hourly Savings / Avg Monthly Savings / Avg Yearly Savings).
  • Stored-property dedup on the dropdown change handler to avoid the listener-stacking pattern (per memory feedback_event_listener_dedup).

Files changed

  • frontend/src/modules/savings-history.ts
  • frontend/src/index.html
  • frontend/src/__tests__/savings-history.test.ts

Test plan

  • Existing /hr assertions updated to /mo; 14 new tests cover unit helpers, each toggle, heading updates, dedup, and chart tooltip.
  • All savings-history tests pass.
  • Manual: pick Hourly/Monthly/Yearly from the dropdown; confirm all three KPIs + the chart switch units consistently.

Closes #750.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added unit selector dropdown to the Savings History page, allowing you to switch between hourly, monthly, and yearly views
    • Savings metric values now automatically convert and display in the selected unit
    • Average and peak savings labels dynamically update to reflect your chosen unit
    • Chart visualizations refresh instantly when changing units for real-time updates

Review Change Stack

…arly toggle

Peak Savings showed /hr while the underlying value was monthly,
making "$1.15/hr" appear as ~$840/mo when it was actually ~$1.15/mo.
Fixed the label and added a unit dropdown that converts all three
KPIs (Period / Avg / Peak) consistently.

Closes #750.
@cristim cristim added triaged Item has been triaged priority/p2 Backlog-worthy severity/medium Moderate harm urgency/this-sprint Within the current sprint impact/all-users Affects every user effort/s Hours type/bug Defect labels May 27, 2026
@coderabbitai

coderabbitai Bot commented May 27, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR adds a unit selector dropdown to the Savings History widget, allowing users to view KPI totals (Period, Avg, Peak) in hourly, monthly, or yearly formats. The changes introduce unit conversion helpers, update markup to include the selector, modify stats and chart rendering to accept and apply the selected unit, wire dropdown changes to reload the chart, and extend test coverage to validate the new behavior.

Changes

Add unit selector and conversion to Savings History

Layer / File(s) Summary
Unit type and conversion helpers
frontend/src/modules/savings-history.ts
Exports SavingsUnit type, convertFromMonthly() function, and unitSuffix() / unitLabel() formatters. Includes a dropdown reader that retrieves the current selection from #savings-unit and defaults to monthly (the API's canonical unit).
UI markup: unit dropdown and label updates
frontend/src/index.html
Adds a #savings-unit dropdown with hourly/monthly/yearly options (monthly selected) in the Savings History controls. Updates "Avg Hourly Savings" label to "Avg Monthly Savings" with dedicated label ids for both average and peak savings.
Test fixtures and helper imports
frontend/src/__tests__/savings-history.test.ts
Imports new unit conversion helpers. Updates test DOM fixture to include the unit dropdown and changes KPI placeholder values from hourly-formatted to monthly-formatted defaults.
Stats rendering with unit conversion
frontend/src/modules/savings-history.ts
renderSavingsStats() reads the selected unit from dropdown, converts monthly API values to the display unit using convertFromMonthly(), and updates KPI headings and values with the appropriate unit suffix and label.
Chart rendering with unit conversion
frontend/src/modules/savings-history.ts
renderSavingsChart() accepts a unit parameter, converts per-period data buckets from monthly to the selected unit, updates the y-axis title dynamically, and adjusts tooltip formatting so cumulative values remain unsuffixed while per-period values include the unit suffix.
Unit selection event wiring
frontend/src/modules/savings-history.ts
initSavingsHistory() retrieves the unit dropdown element and wires a change listener with stored handler reference to prevent duplicate listeners. Unit changes trigger loadSavingsHistory() to reload the chart with the new unit.
Existing test assertion updates
frontend/src/__tests__/savings-history.test.ts
Updates existing assertions to expect monthly-formatted KPI values, tooltip callbacks, and peak savings calculations instead of hourly formats.
New comprehensive test coverage for Issue #750
frontend/src/__tests__/savings-history.test.ts
Adds extensive tests for unit conversion helpers, unit dropdown behavior (verifying KPI updates when unit changes), event wiring (confirming reload triggers and preventing handler duplication), and tooltip/dataset label formatting across units.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🐰 A dropdown blooms with hourly, monthly, yearly delight,
Stats and charts now dance in any unit's light!
Monthly canonical truth shines through the display,
Converting savings just the right way—hip hooray! 📊✨

🚥 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 summarizes the main changes: fixing KPI units to monthly and adding a unit toggle feature.
Linked Issues check ✅ Passed All coding requirements from #750 are met: KPI units corrected to monthly, hourly/monthly/yearly dropdown added, and unit conversion helpers implemented with full test coverage.
Out of Scope Changes check ✅ Passed All changes align with issue #750 objectives; no out-of-scope modifications detected beyond the stated unit correction and dropdown feature.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% 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/750-savings-history-units

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 commented May 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@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: 1

🧹 Nitpick comments (1)
frontend/src/__tests__/savings-history.test.ts (1)

1259-1259: ⚡ Quick win

Replace fixed 50ms sleeps with a deterministic async flush.

Using hardcoded setTimeout(..., 50) makes tests slower and can still be timing-sensitive. Prefer a reusable next-tick flush helper.

♻️ Proposed refactor
+const flushAsync = () => new Promise((resolve) => setTimeout(resolve, 0));
+
 ...
-      await new Promise(resolve => setTimeout(resolve, 50));
+      await flushAsync();
       expect(getSavingsAnalytics).toHaveBeenCalled();
 ...
-      await new Promise(resolve => setTimeout(resolve, 50));
+      await flushAsync();
       // Should fire exactly once (no stacked duplicate listeners)
       expect(getSavingsAnalytics).toHaveBeenCalledTimes(1);

Also applies to: 1273-1273

🤖 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 `@frontend/src/__tests__/savings-history.test.ts` at line 1259, Replace the
brittle setTimeout-based waits in the test (the lines using await new
Promise(resolve => setTimeout(resolve, 50))) with a deterministic microtask
flush helper (e.g., add or import a flushPromises / waitForNextTick function
that returns Promise.resolve().then(() => setImmediate-like microtask
processing) or uses process.nextTick/queueMicrotask) and call that helper
instead of the 50ms sleep in savings-history.test.ts (both occurrences around
the current lines referencing the 50ms sleep). Ensure you add the helper to the
test utilities or import it into the test file and replace both instances so
tests no longer rely on timing sleeps.
🤖 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 289-293: The Period Savings rendering uses
periodSavingsEl.textContent = formatCurrency(displayTotal) which omits the unit
suffix; update this to append the same unit string used for Avg/Peak so KPI
units are consistent (e.g., formatCurrency(displayTotal) + ' ' + unitSuffix).
Locate where Avg/Peak derive their suffix (the rate/unit variable used elsewhere
in savings-history.ts) and reuse that variable (instead of hardcoding) when
setting periodSavingsEl.textContent so the display reflects
Hourly/Monthly/Yearly selections.

---

Nitpick comments:
In `@frontend/src/__tests__/savings-history.test.ts`:
- Line 1259: Replace the brittle setTimeout-based waits in the test (the lines
using await new Promise(resolve => setTimeout(resolve, 50))) with a
deterministic microtask flush helper (e.g., add or import a flushPromises /
waitForNextTick function that returns Promise.resolve().then(() =>
setImmediate-like microtask processing) or uses process.nextTick/queueMicrotask)
and call that helper instead of the 50ms sleep in savings-history.test.ts (both
occurrences around the current lines referencing the 50ms sleep). Ensure you add
the helper to the test utilities or import it into the test file and replace
both instances so tests no longer rely on timing sleeps.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5e2ac99b-fe63-447a-9ae5-d2aa56957d0c

📥 Commits

Reviewing files that changed from the base of the PR and between f5b76eb and 67e2584.

📒 Files selected for processing (3)
  • frontend/src/__tests__/savings-history.test.ts
  • frontend/src/index.html
  • frontend/src/modules/savings-history.ts

Comment on lines 289 to 293
if (periodSavingsEl) {
// Period Savings is the cumulative total over the selected date range
// (no per-unit rate suffix -- it is already a dollar total).
periodSavingsEl.textContent = formatCurrency(displayTotal);
}

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add unit suffix to Period Savings to keep KPI units consistent.

Line 292 renders a converted value (displayTotal) without any unit suffix. Since this number changes when Hourly/Monthly/Yearly is selected, the KPI becomes ambiguous and inconsistent with Avg/Peak.

💡 Suggested fix
     if (periodSavingsEl) {
         // Period Savings is the cumulative total over the selected date range
-        // (no per-unit rate suffix -- it is already a dollar total).
-        periodSavingsEl.textContent = formatCurrency(displayTotal);
+        periodSavingsEl.textContent = `${formatCurrency(displayTotal)}${suffix}`;
     }
🤖 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 `@frontend/src/modules/savings-history.ts` around lines 289 - 293, The Period
Savings rendering uses periodSavingsEl.textContent =
formatCurrency(displayTotal) which omits the unit suffix; update this to append
the same unit string used for Avg/Peak so KPI units are consistent (e.g.,
formatCurrency(displayTotal) + ' ' + unitSuffix). Locate where Avg/Peak derive
their suffix (the rate/unit variable used elsewhere in savings-history.ts) and
reuse that variable (instead of hardcoding) when setting
periodSavingsEl.textContent so the display reflects Hourly/Monthly/Yearly
selections.

@cristim
cristim merged commit 82b5d38 into feat/multicloud-web-frontend May 27, 2026
5 checks passed
@coderabbitai

coderabbitai Bot commented May 27, 2026 •

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 1 file(s) based on 1 unresolved review comment.

A stacked PR containing fixes has been created.

  • Stacked PR: #763
  • Files modified:
  • frontend/src/modules/savings-history.ts

Time taken: 3m 46s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/all-users Affects every user 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