Skip to content

ux(frontend): group SP plan types in bulk-buy fan-out modal (closes #249) - #826

Merged
cristim merged 4 commits into
feat/multicloud-web-frontendfrom
fix/249-wave9
Jun 3, 2026
Merged

cristim merged 4 commits into
feat/multicloud-web-frontendfrom
fix/249-wave9

Conversation

@cristim

@cristim cristim commented May 28, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Adds a collapsible <details> breakdown inside each mixed-SP fan-out bucket section, listing per-plan-type commitment/upfront/savings subtotals
  • Collapsed by default so the modal stays compact for the common single-plan-type case; native chevron expands on demand
  • Non-SP buckets and single-plan-type SP buckets are unaffected

Test plan

  • cd frontend && npx tsc --noEmit clean (0 errors)
  • npx jest 2143 pass, 0 fail across 65 suites (2144 total, 1 pre-existing skip)
  • New test: "mixed-SP fan-out section renders per-plan-type breakdown (closes ux(frontend): group Savings Plans in bulk-buy view for better usability #249)" asserts <details class="fanout-sp-plan-types"> present with "+2 plan types" summary, per-plan-type rows labelled "Savings Plans (Compute)" / "Savings Plans (SageMaker)", and EC2 bucket has no breakdown
  • feedback_innerhtml_xss.md applied: all new DOM writes use textContent
  • feedback_event_listener_dedup.md: no new addEventListener calls in this change

Summary by CodeRabbit

  • New Features

    • Collapsible plan-type breakdown added to the purchase fan-out modal for Savings Plans buckets with multiple concrete plan types, showing per-plan-type rows with counts and aggregated financial summaries (commitments, upfront, and period-scaled savings).
  • Tests

    • Added tests verifying mixed Savings Plans and non-SP buckets render correctly.
    • Added regression test ensuring umbrella Savings Plans identifiers are excluded from per-plan-type counts so the breakdown only appears when 2+ concrete plan types remain.

)

When a mixed-SP bucket (2+ plan types) lands in the fan-out modal,
append a collapsible <details> section listing per-plan-type subtotals.
Collapsed by default to keep the modal compact; the native chevron lets
operators inspect the Compute/SageMaker/EC2 Instance/Database split
before submitting. Non-SP and single-plan-type SP buckets are unaffected.

Adds one DOM integration test asserting the "+2 plan types" summary and
per-plan-type rows render for a mixed-SP + EC2 fan-out scenario.
@cristim cristim added triaged Item has been triaged priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/eventually No deadline impact/many Affects most users effort/s Hours type/feat New capability labels May 28, 2026
@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

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: b6502216-b414-4735-972b-165583c96448

📥 Commits

Reviewing files that changed from the base of the PR and between 1bd1c5c and 9eef9d2.

📒 Files selected for processing (1)
  • frontend/src/lib/purchase-compatibility.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/lib/purchase-compatibility.ts

📝 Walkthrough

Walkthrough

This PR renders a collapsed per-plan-type <details> in Savings Plans fan-out buckets when 2+ concrete plan types exist, exports UMBRELLA_SLUGS for consistent filtering, and adds tests verifying mixed SP rendering and a regression that umbrella slugs are excluded from the plan-type count.

Changes

Savings Plans Per-Plan-Type Breakdown

Layer / File(s) Summary
Expose UMBRELLA_SLUGS
frontend/src/lib/purchase-compatibility.ts
UMBRELLA_SLUGS is exported so other modules can detect umbrella Savings Plans slugs; existing label logic continues to reference the set.
Per-plan-type details renderer
frontend/src/recommendations.ts
renderFanOutBucketSection imports UMBRELLA_SLUGS, groups b.recs by r.service excluding umbrella slugs, and when 2+ distinct plan types exist appends a collapsed <details> (details.fanout-sp-plan-types) showing +N plan types and per-plan-type aggregated rows (commitments, upfront, period-scaled savings).
Tests: mixed SP and umbrella-slug regression
frontend/src/__tests__/recommendations.test.ts
Adds a test that mixed SP (Compute + SageMaker) plus non-SP (EC2) renders the details with “+2 plan types” and exactly two per-plan-type rows; adds a regression test ensuring umbrella slugs are filtered so the details is not rendered when fewer than two concrete plan types remain.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • LeanerCloud/CUDly#723: Both PRs align on Savings Plans “umbrella” slug handling (savingsplans) by exposing/using UMBRELLA_SLUGS for consistent filtering.
  • LeanerCloud/CUDly#180: Earlier Savings Plans bucketing/labeling work that this change builds on for per-plan-type aggregation and display.

Suggested labels

priority/p2, urgency/this-quarter

Poem

🐰 Hopping in with tests to say,
Savings Plans now group by way,
Compute and SageMaker show as two,
Umbrella slugs quietly skew,
Bulk-buy modal — tidy and true! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 accurately and concisely describes the main change: grouping Savings Plan (SP) plan types in the bulk-buy fan-out modal, and references the resolved issue #249.
Linked Issues check ✅ Passed The code changes fully implement the requested feature from #249: a UI-side aggregator groups Savings Plans in the bulk-buy modal by plan type with collapsible details, providing per-plan-type subtotals while preserving underlying service distinctions.
Out of Scope Changes check ✅ Passed All changes directly support the linked issue #249 objective: test coverage for the feature, the fan-out grouping implementation, and exporting the UMBRELLA_SLUGS constant for filtering.

✏️ 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/249-wave9

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

@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.

@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

🤖 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/recommendations.ts`:
- Around line 3559-3574: The code currently builds byPlanType from b.recs using
r.service and counts all keys, which lets umbrella SP slugs inflate the "+N plan
types" and row rendering; change the logic that populates and later reads
byPlanType (used in the size check, summaryEl.textContent and subsequent row
rendering) to first filter out umbrella/umbrella-like slugs (e.g., detect via
the project's umbrella slug pattern or a helper like isUmbrellaSlug) so only
concrete plan-type slugs from b.recs (r.service) are added to byPlanType and
used for the size >= 2 check, the summary count, and the rows rendered. Ensure
the same filtered map is used for the later rendering block noted at 3576-3589.
🪄 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: 3713d5ec-e330-43c0-b4fe-09d2c02ef064

📥 Commits

Reviewing files that changed from the base of the PR and between 4956d66 and c5970de.

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

Comment thread frontend/src/recommendations.ts
…count

UMBRELLA_SLUGS ("savings-plans", "savingsplans") represent the SP family
as a whole, not a concrete plan type. Including them in byPlanType inflated
the "+N plan types" count and caused a spurious non-concrete row to render
in the collapsible breakdown. Filter them out before populating the map so
the size check and rendered rows only reflect real plan types.

Exports UMBRELLA_SLUGS from purchase-compatibility for reuse, adds a
regression test covering the mixed umbrella + concrete slug case.
@cristim

cristim commented May 30, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
frontend/src/lib/purchase-compatibility.ts (1)

112-112: ⚡ Quick win

Consider making the exported Set immutable.

Since UMBRELLA_SLUGS is exported as a constant collection, consumers could accidentally mutate it via .add() or .delete(). Use ReadonlySet<string> to prevent unintended modifications.

🔒 Proposed fix to make the Set read-only
-export const UMBRELLA_SLUGS = new Set<string>([SAVINGS_PLANS_BUCKET_KEY, 'savingsplans']);
+export const UMBRELLA_SLUGS: ReadonlySet<string> = new Set<string>([SAVINGS_PLANS_BUCKET_KEY, 'savingsplans']);
🤖 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/lib/purchase-compatibility.ts` at line 112, UMBRELLA_SLUGS is
currently a mutable Set and consumers can call .add()/.delete(); change its
declaration to a ReadonlySet by typing it as ReadonlySet<string> (e.g.,
UMBRELLA_SLUGS: ReadonlySet<string> = new Set([...])) so TypeScript prevents
mutation, and ensure no code calls mutating methods on UMBRELLA_SLUGS (update
any such usages to create a new Set before mutating).
🤖 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/__tests__/recommendations.test.ts`:
- Around line 3517-3525: The test selects the first ".fanout-bucket" whose h4
includes "Savings Plans", which is ambiguous; update the lookup so it
deterministically targets the AWS Savings Plans bucket (e.g., find the bucket
whose header contains both "Savings Plans" and "AWS" or whose bucket element has
an AWS-specific data attribute), or alternatively iterate and assert the
behavior for every SP bucket found; update the variable spSection selection
logic (the Array.from(...).find(...) call) and keep the subsequent assertion on
details.fanout-sp-plan-types the same.

---

Nitpick comments:
In `@frontend/src/lib/purchase-compatibility.ts`:
- Line 112: UMBRELLA_SLUGS is currently a mutable Set and consumers can call
.add()/.delete(); change its declaration to a ReadonlySet by typing it as
ReadonlySet<string> (e.g., UMBRELLA_SLUGS: ReadonlySet<string> = new Set([...]))
so TypeScript prevents mutation, and ensure no code calls mutating methods on
UMBRELLA_SLUGS (update any such usages to create a new Set before mutating).
🪄 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: 45f128ed-a825-4d43-922e-dbe32e411b50

📥 Commits

Reviewing files that changed from the base of the PR and between c5970de and 024272b.

📒 Files selected for processing (3)
  • frontend/src/__tests__/recommendations.test.ts
  • frontend/src/lib/purchase-compatibility.ts
  • frontend/src/recommendations.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/recommendations.ts

Comment thread frontend/src/__tests__/recommendations.test.ts Outdated
…826)

Switch find+find from .find() to .filter()+loop so the umbrella-slug
exclusion regression is asserted against every SP fanout-bucket, not
just the first match, preventing false-positive passes with multi-provider
SP fixtures.
@cristim

cristim commented May 30, 2026

Copy link
Copy Markdown
Member Author

Addressed CR finding in commit 1bd1c5c (test hardening: filter+loop over all SP buckets instead of find+first-match). Finding 1 (recommendations.ts umbrella slug filter) was already addressed in 024272b. @coderabbitai review

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

@cristim Thanks for the update — confirming both findings acknowledged:

  • Finding 1 (umbrella slug filter in recommendations.ts) → addressed in 024272b0e ✅
  • Test hardening (filter+loop over all SP buckets vs. find+first-match) → addressed in 1bd1c5cc5 ✅

Triggering a review of the latest 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.

@cristim

cristim commented Jun 1, 2026

Copy link
Copy Markdown
Member Author

All CR findings addressed: umbrella slug filter in 024272b, SP bucket test hardening in 1bd1c5c. @coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

@cristim Thanks for confirming — both fixes are accounted for:

  • Umbrella slug filter (recommendations.ts) → 024272b0e ✅
  • SP bucket test hardening (filter+loop over all SP buckets) → 1bd1c5cc5 ✅

Triggering a review of the latest 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.

)

Prevents accidental mutation via .add()/.delete() on the exported Set.
All existing consumers use only .has(), so the narrowed type is safe.
@cristim

cristim commented Jun 1, 2026

Copy link
Copy Markdown
Member Author

Fixed the remaining nitpick from CR #826 (second review pass, 2026-05-30):

  • purchase-compatibility.ts line 112: typed UMBRELLA_SLUGS as ReadonlySet<string> to prevent accidental mutation via .add()/.delete(). All consumer call sites use only .has(), so the narrowed type is a safe drop-in. Committed as 9eef9d2.

All 2144 frontend tests pass; build clean.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

@cristim Thanks for the update — confirming the fix is accounted for:

  • UMBRELLA_SLUGS typed as ReadonlySet<string> (purchase-compatibility.ts line 112) → 9eef9d268 ✅ — prevents accidental .add()/.delete() mutations; all call sites only use .has(), so it's a safe narrowing.

Triggering a review of the latest 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.

@cristim
cristim merged commit 87f739a into feat/multicloud-web-frontend Jun 3, 2026
5 checks passed
@cristim
cristim deleted the fix/249-wave9 branch June 3, 2026 20:47
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/p3 Polish / idea / may never ship severity/low Minor harm triaged Item has been triaged type/feat New capability urgency/eventually No deadline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant