Skip to content

feat(recommendations): usage_history + per-row sparkline (closes #239) - #801

Merged
cristim merged 2 commits into
feat/multicloud-web-frontendfrom
fix/239-wave2
Jun 3, 2026
Merged

feat(recommendations): usage_history + per-row sparkline (closes #239)#801
cristim merged 2 commits into
feat/multicloud-web-frontendfrom
fix/239-wave2

Conversation

@cristim

@cristim cristim commented May 28, 2026

Copy link
Copy Markdown
Member

Summary

  • Backend (AWS): new providers/aws/recommendations/usage_history.go adds GetDailyUsagePcts (CE GetReservationCoverage with DAILY granularity, last 7 days) and AttachDailyUsageHistory (batches by unique (service, region, resourceType) tuple so N recs for the same SKU fire one CE call). Wired into GetRecommendationsForService after the existing rec fetch; propagated through scheduler.convertRecommendations and the RecommendationRecord + common.Recommendation types.
  • Frontend: adds a usage_history column ("Coverage 7d") to the rec table rendering a 56x20px inline SVG polyline (renderUsageSparkline). Visual-only column (sortable: false) gets a plain non-interactive header; degrades to for null/absent data so pre-feat(recommendations): populate usage_history + surface per-row sparkline in rec list #239 cached rows and non-AWS providers are unaffected.
  • Tests: 5 Go tests (daily points, nil-on-no-data, empty-input guard, end-to-end attach, empty-region skip) + 8 Jest tests (null/undefined/empty degrade, single-point circle, 7-point polyline, 100%/0% y-axis mapping, full-width x-axis span).

Test plan

  • All Go tests pass: go test ./...
  • Frontend TS clean: cd frontend && npx tsc --noEmit
  • Frontend tests pass: cd frontend && npx jest (327 pass, 0 fail)
  • On a real AWS account, a collection run produces recs with non-empty usage_history for EC2/RDS/ElastiCache/OpenSearch
  • The rec table shows a sparkline in the "Coverage (7d)" column for AWS recs; non-AWS recs show "—"
  • Pre-feat(recommendations): populate usage_history + surface per-row sparkline in rec list #239 cached rows (no usage_history field) render "—" without errors
  • Column is toggleable via the column-visibility menu and can be hidden

Summary by CodeRabbit

  • New Features

    • Added a "Coverage (7d)" sparkline column to recommendations showing 7-day RI-coverage percentages; missing data displays as an em-dash. Column is read-only (no sort/filter UI).
    • API/UX now surface an optional usage_history field so the UI can render the sparkline when available.
  • Tests

    • Added comprehensive unit tests for sparkline rendering and for backend daily-usage enrichment, including edge cases and accessibility checks.

@cristim cristim added triaged Item has been triaged priority/p2 Backlog-worthy severity/medium Moderate harm urgency/this-quarter Within the quarter impact/many Affects most users effort/m Days type/feat New capability labels May 28, 2026
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ba452b5c-57ce-4cd1-b7a4-ec246e63f2d8

📥 Commits

Reviewing files that changed from the base of the PR and between 5566669 and d614fd0.

📒 Files selected for processing (2)
  • frontend/src/__tests__/recommendations.test.ts
  • frontend/src/recommendations.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/recommendations.ts
  • frontend/src/tests/recommendations.test.ts

📝 Walkthrough

Walkthrough

Adds a "Coverage (7d)" sparkline column: backend fetches 7-day daily RI-coverage from AWS Cost Explorer and persists it; frontend types, table model, and rendering gain a usage_history path with an exported renderUsageSparkline SVG generator and associated tests.

Changes

Usage history sparkline visualization

Layer / File(s) Summary
Frontend data model and type updates
frontend/src/api/types.ts, frontend/src/types.ts, frontend/src/state.ts
Recommendation, LocalRecommendation, and RecommendationsColumnId are extended with an optional usage_history field/variant.
Backend data model and scheduler wiring
pkg/common/types.go, internal/config/types.go, internal/scheduler/scheduler.go
Add UsageHistory []float64 to domain/config types and map it through convertRecommendations into persisted recommendation records.
AWS Cost Explorer daily usage fetching
providers/aws/recommendations/usage_history.go, providers/aws/recommendations/client.go
Add GetDailyUsagePcts to fetch daily reservation-coverage percentages and AttachDailyUsageHistory to batch by (service,region,resourceType) and enrich recommendations; integrate into GetRecommendationsForService.
AWS usage history unit tests
providers/aws/recommendations/usage_history_test.go
Tests validate ordered daily-point output, nil-on-no-data, input short-circuiting, batching behavior, and region skipping.
Frontend sparkline rendering and table integration
frontend/src/recommendations.ts
Expand ColumnDef with visual kind and sortable flag; add renderUsageSparkline(pcts) to emit inline SVG (polyline or circle) or em-dash; treat visual columns as non-filterable/non-numeric and render non-sortable headers.
Frontend tests for sparkline and column integration
frontend/src/__tests__/recommendations.test.ts
Update column counts/toggleable keys and add comprehensive renderUsageSparkline tests (fallbacks, SVG geometry, aria-labels, clamping, injection safety).

Sequence Diagram(s)

sequenceDiagram
  participant GetRecommendationsForService
  participant AttachDailyUsageHistory
  participant groupRecsByTuple
  participant GetDailyUsagePcts
  participant CostExplorer
  GetRecommendationsForService->>AttachDailyUsageHistory: recs list
  AttachDailyUsageHistory->>groupRecsByTuple: build tuple index
  loop per tuple
    AttachDailyUsageHistory->>GetDailyUsagePcts: service, region, resourceType
    GetDailyUsagePcts->>CostExplorer: GetReservationCoverage
    CostExplorer-->>GetDailyUsagePcts: coverage periods
    GetDailyUsagePcts-->>AttachDailyUsageHistory: daily percentages or nil
  end
  AttachDailyUsageHistory->>AttachDailyUsageHistory: assign UsageHistory to matching recs
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Suggested labels

urgency/this-sprint, effort/l

Poem

🐰 Seven tiny dots in a row,
Coverage up, coverage low,
From Cost Explorer's cloudy stream,
Sparkline whispers build the dream,
A rabbit hops, and charts aglow.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.95% 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 and concisely describes the main change: adding usage_history data and rendering per-row sparklines in recommendations, with a specific issue reference (#239).
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/239-wave2

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

@cristim

cristim commented May 28, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

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

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

Backend (AWS): add GetDailyUsagePcts / AttachDailyUsageHistory in
providers/aws/recommendations/usage_history.go. Calls CE
GetReservationCoverage with DAILY granularity over the last 7 days,
batches by unique (service, region, resourceType) tuple so N recs
for the same SKU fire only one CE call. Wired into
GetRecommendationsForService after the existing rec fetch.
Propagated through scheduler.convertRecommendations and the
RecommendationRecord + common.Recommendation types.

Frontend: add usage_history column (Coverage 7d) to the rec table
with a 56x20px inline SVG polyline (renderUsageSparkline). Visual-
only column (sortable:false) renders a plain non-interactive header.
Degrades to "—" for null/absent data so pre-#239 cached rows and
non-AWS providers are unaffected.

Tests: 5 Go tests (daily points, nil-on-no-data, empty-input guard,
end-to-end attach, empty-region skip) + 8 Jest tests (null/undefined/
empty degrade, single-point circle, 7-point polyline, 100%/0% y-axis
mapping, full-width x-axis span). All 327 frontend tests pass.
@cristim

cristim commented Jun 1, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 1, 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.

🧹 Nitpick comments (1)
frontend/src/recommendations.ts (1)

2413-2432: ⚡ Quick win

Expose sparkline values to assistive tech.

The SVG is marked aria-hidden, so non-empty cells have no accessible text alternative. Add an accessible label on the SVG (or equivalent text in the cell).

♿ Suggested patch
 export function renderUsageSparkline(pcts: number[] | null | undefined): string {
   if (!pcts || pcts.length === 0) return '—';
+  const clamped = pcts.map((v) => {
+    if (!Number.isFinite(v)) return 0;
+    return Math.max(0, Math.min(100, v));
+  });
   const w = 56;
   const h = 20;
   const pad = 1;
   const innerH = h - 2 * pad;
-  const n = pcts.length;
+  const n = clamped.length;
+  const aria = `RI coverage last ${n} days: ${clamped.map((v) => `${v.toFixed(1)}%`).join(', ')}`;
   if (n === 1) {
-    const cy = pad + innerH * (1 - pcts[0]! / 100);
-    return `<svg width="${w}" height="${h}" viewBox="0 0 ${w} ${h}" aria-hidden="true" class="usage-sparkline"><circle cx="${(w / 2).toFixed(1)}" cy="${cy.toFixed(1)}" r="2.5" fill="currentColor"/></svg>`;
+    const cy = pad + innerH * (1 - clamped[0]! / 100);
+    return `<svg width="${w}" height="${h}" viewBox="0 0 ${w} ${h}" role="img" aria-label="${aria}" class="usage-sparkline"><circle cx="${(w / 2).toFixed(1)}" cy="${cy.toFixed(1)}" r="2.5" fill="currentColor"/></svg>`;
   }
-  const points = pcts
+  const points = clamped
     .map((p, i) => {
       const x = (i / (n - 1)) * w;
       const y = pad + innerH * (1 - p / 100);
       return `${x.toFixed(1)},${y.toFixed(1)}`;
     })
     .join(' ');
-  return `<svg width="${w}" height="${h}" viewBox="0 0 ${w} ${h}" aria-hidden="true" class="usage-sparkline"><polyline points="${points}" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round"/></svg>`;
+  return `<svg width="${w}" height="${h}" viewBox="0 0 ${w} ${h}" role="img" aria-label="${aria}" class="usage-sparkline"><polyline points="${points}" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round"/></svg>`;
 }
🤖 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/recommendations.ts` around lines 2413 - 2432, The SVGs produced
by renderUsageSparkline are marked aria-hidden so screen readers get no
information; update renderUsageSparkline to provide an accessible label for
non-empty pcts by removing aria-hidden and adding either a <title> element or an
aria-label/role="img" that conveys the sparkline values (e.g., "Usage: 10%, 20%,
30%") for both the single-point branch (circle) and the multi-point branch
(polyline), while keeping the empty-case return '—' unchanged.
🤖 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.

Nitpick comments:
In `@frontend/src/recommendations.ts`:
- Around line 2413-2432: The SVGs produced by renderUsageSparkline are marked
aria-hidden so screen readers get no information; update renderUsageSparkline to
provide an accessible label for non-empty pcts by removing aria-hidden and
adding either a <title> element or an aria-label/role="img" that conveys the
sparkline values (e.g., "Usage: 10%, 20%, 30%") for both the single-point branch
(circle) and the multi-point branch (polyline), while keeping the empty-case
return '—' unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c6d2426f-cd7e-4e57-b6a9-2c0ccd10c6bb

📥 Commits

Reviewing files that changed from the base of the PR and between 80c20d2 and 5566669.

📒 Files selected for processing (11)
  • frontend/src/__tests__/recommendations.test.ts
  • frontend/src/api/types.ts
  • frontend/src/recommendations.ts
  • frontend/src/state.ts
  • frontend/src/types.ts
  • internal/config/types.go
  • internal/scheduler/scheduler.go
  • pkg/common/types.go
  • providers/aws/recommendations/client.go
  • providers/aws/recommendations/usage_history.go
  • providers/aws/recommendations/usage_history_test.go

…nderUsageSparkline

Replace aria-hidden="true" with role="img" and an aria-label conveying
the coverage values (e.g. "RI coverage last 7 days: 80.0%, ...") on both
the single-point and polyline SVG branches, so screen readers receive
meaningful information.

Also clamp non-finite inputs (NaN, Infinity, -Infinity) to 0 before
computing geometry and the label, preventing invalid y-coordinates.

Addresses CodeRabbit nitpick on PR #801.
@cristim

cristim commented Jun 1, 2026

Copy link
Copy Markdown
Member Author

Addressed the CodeRabbit nitpick from the 2026-06-01T18:34Z review.

Finding fixed: renderUsageSparkline accessibility (lines 2413-2432)

The SVG elements were marked aria-hidden="true", giving screen readers no information about the sparkline content. Fixed by:

  • Replacing aria-hidden="true" with role="img" and an aria-label that conveys the coverage values (e.g. "RI coverage last 7 days: 80.0%, 90.0%, ...") on both the single-point (circle) and multi-point (polyline) branches.
  • Clamping non-finite inputs (NaN, Infinity, -Infinity) to 0 before computing geometry and the label, preventing invalid SVG coordinates.

Tests updated: added role="img" / aria-label / not.aria-hidden assertions to the existing single-point and polyline tests, plus two new tests for label content and non-finite clamping (329 tests pass, TypeScript clean).

Commit: d614fd0 on fix/239-wave2.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

@cristim: Great job addressing the accessibility finding! The changes look thorough:

  • Accessibility: Replacing aria-hidden="true" with role="img" + descriptive aria-label on both SVG branches is exactly the right approach — screen readers can now announce the coverage values meaningfully.
  • Non-finite clamping: Guarding against NaN/Infinity/-Infinity before geometry computation prevents malformed SVG coordinates, which is a solid robustness improvement.
  • Test coverage: Adding assertions for role="img", aria-label, the absence of aria-hidden, and dedicated tests for label content and non-finite inputs gives good confidence the fix is well-anchored.

Triggering a review of the latest commit.

✅ 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 Jun 1, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 1, 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 56a7797 into feat/multicloud-web-frontend Jun 3, 2026
5 checks passed
@cristim
cristim deleted the fix/239-wave2 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

effort/m Days impact/many Affects most users priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged type/feat New capability urgency/this-quarter Within the quarter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant