Skip to content

feat(recommendations): All Savings Plans tri-state in service column-filter (#137)#181

Open
cristim wants to merge 2 commits intofeat/multicloud-web-frontendfrom
fix/issue-137-all-sp-filter
Open

feat(recommendations): All Savings Plans tri-state in service column-filter (#137)#181
cristim wants to merge 2 commits intofeat/multicloud-web-frontendfrom
fix/issue-137-all-sp-filter

Conversation

@cristim
Copy link
Copy Markdown
Member

@cristim cristim commented Apr 28, 2026

Summary

Closes #137. Restores the one-click "filter to all Savings Plans" affordance that PR #123 removed when it split the single savings-plans service slug into four per-plan-type slugs (savings-plans-{compute,ec2instance,sagemaker,database}). The original issue proposed adding <option value="all-sp"> to the optgroup — but Bundle B replaced the legacy service-filter dropdown with the per-column popover (checkbox list of distinct service values), so the equivalent affordance for a checkbox UI is a tri-state group toggle.

What changed

In frontend/src/recommendations.ts:buildPopoverContent, when the column is service AND 2+ SP slugs are in the distinct values, render a new "All Savings Plans" tri-state checkbox between the existing (All) row and the per-value list:

  • checked: every SP slug is in the active filter
  • indeterminate: a strict subset of SP slugs is in the filter
  • unchecked: no SP slug is in the filter
  • click ON: ticks every SP box and commits the resulting filter (preserving any non-SP boxes already ticked)
  • click OFF: unticks every SP box; if no other slugs were ticked, the filter is cleared (treated as "no narrowing")

Per-plan-type checkboxes remain individually selectable for narrowing — the group toggle is purely a bulk-select shortcut.

The toggle does NOT render in non-service columns or when fewer than 2 SP slugs are present.

New utility

isSavingsPlanService(service) in frontend/src/lib/purchase-compatibility.tsstartsWith('savings-plans'), mirror of Go's common.IsSavingsPlan (pkg/common/types.go). startsWith form so a future plan type added on the backend is picked up automatically.

Tests

7 new tests in recommendations.test.ts pinning:

  • group toggle renders when 2+ SP slugs present
  • does NOT render for non-service columns
  • does NOT render when 0 or 1 SP slugs present
  • click ON commits filter with all 3 SP slug values
  • click OFF (when all SPs were filter-active) clears the filter
  • resyncs to indeterminate when only some SPs are filter-active
  • individual SP checkbox change commits the partial-SP filter

All 1363 frontend tests pass. TypeScript typecheck + webpack build clean.

Conflict notice

The small isSavingsPlanService predicate is also added by PR #180 (issue #132 — bulk-buy across SP plan types). Whichever lands first, the second hits a trivial 3-line conflict in lib/purchase-compatibility.ts; either version of the export is correct to keep.

Test plan

  • CodeRabbit review clean
  • CI green
  • Verify in browser: open service column filter on a recs view that has 2+ SP plan-type entries, click "All Savings Plans", confirm all SP rows pass the filter and non-SP rows don't.

🤖 Generated with claude-flow

Summary by CodeRabbit

  • New Features

    • Added an "All Savings Plans" tri-state group toggle to the Service column filter popover, letting users select/deselect all savings-plan types at once; the toggle appears only when multiple savings-plan types exist and reflects checked/indeterminate/unchecked states.
  • Tests

    • Added end-to-end test coverage validating the group toggle’s visibility, tri-state behavior, synchronization, and individual checkbox interactions.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

Warning

Rate limit exceeded

@cristim has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 9 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ba80cf78-a16f-48eb-8104-b486432ac64e

📥 Commits

Reviewing files that changed from the base of the PR and between 8af52c4 and f690c70.

📒 Files selected for processing (3)
  • frontend/src/__tests__/recommendations.test.ts
  • frontend/src/lib/purchase-compatibility.ts
  • frontend/src/recommendations.ts
📝 Walkthrough

Walkthrough

Adds an "All Savings Plans" tri-state group toggle to the Recommendations service-column filter popover when 2+ savings-plan service slugs exist, plus an exported isSavingsPlanService predicate. Includes tests validating rendering conditions, tri-state behavior, and committed filter values.

Changes

Cohort / File(s) Summary
Tests
frontend/src/__tests__/recommendations.test.ts
Adds ~130 lines of tests for the "All Savings Plans" tri-state group toggle: conditional rendering (2+ SP slugs), checked/unchecked/indeterminate states, commits to state.setRecommendationsColumnFilter for full/partial/cleared selections, and individual checkbox changes.
UI Logic & State
frontend/src/recommendations.ts
Introduces the SP-group tri-state toggle in the service-column filter popover; derives tri-state from savings-plan checkboxes; applies group changes by setting all SP checkbox values and commits filter updates; resynchronizes tri-state on popover open/rebind.
Utility
frontend/src/lib/purchase-compatibility.ts
Adds exported predicate isSavingsPlanService(service: string): boolean that identifies services with the savings-plans prefix to group related SP slugs without a hardcoded list.

Sequence Diagram(s)

sequenceDiagram
    participant U as User (UI)
    participant P as Popover (service filter)
    participant S as State (setRecommendationsColumnFilter)
    participant R as RecommendationsLoader
    participant B as Backend
    U->>P: open/popover renders checkboxes + SP-group toggle
    P->>S: toggle ON -> setRecommendationsColumnFilter(all SP slugs)
    S->>R: reload recommendations with SP filter
    R->>B: request filtered recommendations
    B-->>R: returns filtered results
    R-->>U: render updated list
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

  • PR #160: Implements the column-filter popover and per-service checkbox filtering that this change extends with the SP-group tri-state toggle.
  • PR #63: Touches handling/format of savings-plan service identifiers (hyphenation) which relates to the new isSavingsPlanService predicate.

Poem

🐰 A tiny toggle, three states to sing,
I group all the plans and give filters a spring.
Click once to gather, again to clear snow—
Indeterminate hums when just some are aglow. 🥕✨

🚥 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 'feat(recommendations): All Savings Plans tri-state in service column-filter (#137)' clearly and specifically describes the main change: adding a tri-state toggle for All Savings Plans in the service column filter.
Linked Issues check ✅ Passed The PR fully implements issue #137 requirements: adds an 'All Savings Plans' aggregate filter option that was removed in PR #123, provides tri-state semantics (checked/indeterminate/unchecked) for bulk selection, preserves four per-plan-type selections, and includes comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly related to issue #137: new test suite for the tri-state toggle, new isSavingsPlanService utility for identifying savings plans slugs, and enhanced filter popover logic. No unrelated or extraneous changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 80.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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-137-all-sp-filter

Review rate limit: 0/5 reviews remaining, refill in 6 minutes and 9 seconds.

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

@cristim
Copy link
Copy Markdown
Member Author

cristim commented Apr 28, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/__tests__/recommendations.test.ts`:
- Around line 1173-1181: The tests rely on mocked column-filter state but only
clear call history; add an explicit reset or set a known return for the
service-filter mock so this suite doesn't inherit prior state. In the beforeEach
alongside (api.getRecommendations, state.getRecommendations,
state.getVisibleRecommendations) either call
(state.getRecommendationsColumnFilters as jest.Mock).mockReset() or set a
deterministic return value with (state.getRecommendationsColumnFilters as
jest.Mock).mockReturnValue([]) to ensure consistent SP-popover ordering.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4f9c2dfa-cc6d-41bb-80be-42b3ff082d9c

📥 Commits

Reviewing files that changed from the base of the PR and between 3e655cb and b607cf7.

📒 Files selected for processing (3)
  • frontend/src/__tests__/recommendations.test.ts
  • frontend/src/lib/purchase-compatibility.ts
  • frontend/src/recommendations.ts

Comment thread frontend/src/__tests__/recommendations.test.ts
cristim added a commit that referenced this pull request Apr 28, 2026
Addresses CodeRabbit actionable on PR #181.

The Issue #137 SP tri-state describe block's `beforeEach` only set
the api / state.getRecommendations / state.getVisibleRecommendations
mocks; it relied on the parent beforeEach's `jest.clearAllMocks()`
+ the global `state.getRecommendationsColumnFilters` mock default
(empty filters) to keep tests order-independent. But specific tests
in the suite override that mock with `{ service: { values: [...] } }`
to drive the resync path — `clearAllMocks()` clears call history but
leaves `mockReturnValue` set. A test that runs after one of those
override-tests would then see the prior test's filter state during
its popover build / resync, producing flaky orderings of the SP
checkbox tri-state.

Fix: explicitly reset the `state.getRecommendationsColumnFilters`
mock to `{}` in the SP-suite beforeEach so every test starts from
a known-empty filter state regardless of run order. Mirror of how
the parent beforeEach pins the other state accessors.

All 1363 frontend tests pass; this is a test-isolation hardening,
no production-code change.
@cristim
Copy link
Copy Markdown
Member Author

cristim commented Apr 28, 2026

@coderabbitai review

Addressed the actionable: SP-suite beforeEach now explicitly resets getRecommendationsColumnFilters to {} so a prior test's mockReturnValue (which jest.clearAllMocks() doesn't unset) can't leak into a later test's popover-build resync.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

@cristim Thanks for the follow-up! The explicit getRecommendationsColumnFilters.mockReturnValue({}) (or equivalent reset) in beforeEach is exactly the right approach — jest.clearAllMocks() resets call history but intentionally leaves mockReturnValue/mockImplementation intact (that's jest.resetAllMocks() territory), so the manual reset is the correct and idiomatic fix to prevent inter-test state leakage in the SP suite.

Triggering an incremental review now to pick up the latest commits.

✅ 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 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 Apr 28, 2026
@cristim
Copy link
Copy Markdown
Member Author

cristim commented Apr 29, 2026

CR loop closed: original review's actionable was addressed in commit 8af52c4b and CR positively ACKed the fix ("exactly the right approach"). No new findings since. PR is OPEN, MERGEABLE, CI green twice. Ready for human merge whenever convenient.

Note: PR #180 (issue #132) introduces the same isSavingsPlanService predicate in frontend/src/lib/purchase-compatibility.ts — whichever lands second will hit a trivial 3-line rebase conflict; either version of the export is correct to keep.

cristim added 2 commits April 30, 2026 00:58
…filter

Closes #137.

PR #123 split the single 'savings-plans' service slug into four per-
plan-type slugs (savings-plans-{compute,ec2instance,sagemaker,database}),
removing the operator's ability to one-click "show me all SP recs"
from the Recommendations service-filter. Bundle B replaced the old
service-filter dropdown with the per-column popover (checkbox list of
distinct service values), so the previous fix proposed in the issue —
adding an `<option value="all-sp">` to the optgroup — doesn't apply
to the current UI. The equivalent affordance for a checkbox popover
is a tri-state group toggle.

This adds an "All Savings Plans" tri-state checkbox between the
existing `(All)` row and the per-value list in the service column
popover, conditional on 2+ SP slugs being present in the distinct
values. Behaviour:

- **checked**: every SP slug is in the active filter
- **indeterminate**: a strict subset of SP slugs is in the filter
- **unchecked**: no SP slug is in the filter
- **click while checked → unchecked**: unticks every SP box, commits
  the resulting filter (clears it if no other slugs were ticked)
- **click while unchecked/indeterminate → checked**: ticks every SP
  box, commits the filter with the SP slug values appended to any
  already-checked non-SP boxes
- The four per-plan-type checkboxes remain individually selectable
  for narrowing — the group toggle is purely a bulk-select shortcut

The per-plan-type filtering downstream is unchanged: applyColumnFilters
still receives the array of slug values from the existing
`{ kind: 'set', values: [...] }` filter shape, so the rec-load path
sees per-plan-type values exactly as it did before.

The group toggle does NOT render in:
- Non-service columns (provider, account, region, term, etc.)
- The service column when fewer than 2 SP slugs are present (no
  benefit to a "group toggle" with one box, and zero with none)

Implementation:
- New shared predicate `isSavingsPlanService` in
  `frontend/src/lib/purchase-compatibility.ts` mirroring
  `common.IsSavingsPlan` (`pkg/common/types.go`). `startsWith` form so
  a future plan type added on the backend is picked up automatically
  without a frontend edit.
- New `updateSPTriState()` helper in `buildPopoverContent`,
  symmetric to the existing `updateAllTriState()`, scoped to SP slugs.
- `commit()` now updates both tri-states; click handler on spBox
  ticks/unticks the SP boxes and re-uses the same `commit()` flow.
- `resyncOpenPopover` recomputes the SP tri-state from current filter
  state on every anchor re-bind.

Tests (7 new in `recommendations.test.ts`):
- group toggle renders when 2+ SP slugs present
- group toggle does NOT render for non-service columns
- group toggle does NOT render when 0 or 1 SP slugs present
- clicking ON commits filter with all 3 SP slug values
- clicking OFF (when all SPs were already filtered) clears the filter
- group toggle resyncs to indeterminate when only some SPs are active
- individual SP checkbox change commits the partial-SP filter

All 1363 frontend tests still pass (added 7 over the prior 1356).

Note: the small `isSavingsPlanService` predicate is also added by PR
first, the second will hit a trivial 3-line conflict in
`purchase-compatibility.ts`; either version of the export is correct
to keep.
Addresses CodeRabbit actionable on PR #181.

The Issue #137 SP tri-state describe block's `beforeEach` only set
the api / state.getRecommendations / state.getVisibleRecommendations
mocks; it relied on the parent beforeEach's `jest.clearAllMocks()`
+ the global `state.getRecommendationsColumnFilters` mock default
(empty filters) to keep tests order-independent. But specific tests
in the suite override that mock with `{ service: { values: [...] } }`
to drive the resync path — `clearAllMocks()` clears call history but
leaves `mockReturnValue` set. A test that runs after one of those
override-tests would then see the prior test's filter state during
its popover build / resync, producing flaky orderings of the SP
checkbox tri-state.

Fix: explicitly reset the `state.getRecommendationsColumnFilters`
mock to `{}` in the SP-suite beforeEach so every test starts from
a known-empty filter state regardless of run order. Mirror of how
the parent beforeEach pins the other state accessors.

All 1363 frontend tests pass; this is a test-isolation hardening,
no production-code change.
@cristim cristim force-pushed the fix/issue-137-all-sp-filter branch from 8af52c4 to f690c70 Compare April 29, 2026 22:59
@cristim
Copy link
Copy Markdown
Member Author

cristim commented Apr 29, 2026

Rebased onto current feat/multicloud-web-frontend (which now includes #193's per-account override seed work and #200's override-aware list filtering, plus the trivial isSavingsPlanService predicate that landed via PR #180).

Conflicts resolved:

  • frontend/src/recommendations.ts imports: combined upstream's paymentOptionsFor + CompatPayment + AccountServiceOverride with this PR's isSavingsPlanService import. Single import block from ./lib/purchase-compatibility carries both groups now.
  • frontend/src/lib/purchase-compatibility.ts: auto-merged cleanly — isSavingsPlanService already exists on the base branch (added by PR feat(recommendations): re-enable bulk-buy across SP plan types (#132) #180's first commit), so this PR's addition was already there.
  • frontend/src/__tests__/recommendations.test.ts: auto-merged cleanly — the new "All Savings Plans group toggle" tests appended after upstream's existing test blocks without overlap.

The popover-rendering changes in buildPopoverContent (the actual feature: tri-state group toggle in the service column filter) were untouched by upstream — that code path wasn't part of #193's override work.

Tests: npx tsc --noEmit clean, npx jest 1394/1394 passing.

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

@cristim Thanks for the detailed rebase summary! A few quick observations:

  • isSavingsPlanService conflict resolved exactly as anticipated — since PR #180 landed first, the predicate was already on the base branch and the merge was a no-op for this PR's addition. No duplicate export risk.
  • Single import block consolidation in frontend/src/recommendations.ts is the right outcome — combining paymentOptionsFor, CompatPayment, AccountServiceOverride, and isSavingsPlanService into one ./lib/purchase-compatibility import keeps the import surface tidy.
  • 1394/1394 tests passing + clean TypeScript after the rebase is a strong green signal; the upstream override work (#193, #200) didn't intersect with the buildPopoverContent tri-state logic as expected.

Triggering an incremental review to pick up the rebased commits now.

✅ 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