feat(frontend/recs): group rows by cell with savings range + collapse/expand (closes #225, closes #226) by cristim · Pull Request #253 · LeanerCloud/cloud-commitments-cli · GitHub
Skip to content

feat(frontend/recs): group rows by cell with savings range + collapse/expand (closes #225, closes #226) - #253

Merged
cristim merged 2 commits into
feat/multicloud-web-frontendfrom
feat/issues-225-226-cell-grouping
May 4, 2026
Merged

cristim merged 2 commits into
feat/multicloud-web-frontendfrom
feat/issues-225-226-cell-grouping

Conversation

@cristim

@cristim cristim commented May 4, 2026 •

Copy link
Copy Markdown
Member

Summary

Closes #225 and #226. This PR merges both issues into a single feature since they're tightly coupled (cell grouping is the visual anchor that makes savings range legible).

Design choices (UX call — flag anything to change in CR comments)

  1. Collapsed by default — cleaner first impression. Most users only need to see the best option per resource, not all 6 variants on first load.
  2. Summary row layout — provider + account + service in the first 4 columns; a single wide content cell (colspan) with the chevron, resource identity, and range. This keeps the identity readable without wrapping. The identity cell has colspan=8 (the remaining columns after the 4 identity ones).
  3. Expand-All button location — in the filter-status bar (above the table), not in the bottom sticky toolbar. Keeps it near the table context rather than near the purchase controls.
  4. Sort operates at the cell level — cells sort by the selected variant's column value if one is selected; otherwise by the max value for numeric columns (e.g. max-savings). String columns are uniform within a cell by definition (they're the cell key).
  5. Single-variant cells render flat — no group overhead (no summary row, no chevron, no indentation). Per feat(frontend/recs): group rec rows by physical-resource cell so variants are visually adjacent #226's acceptance criteria.
  6. CSS approach — variant rows use padding-left: 2rem on all cells except the checkbox column. Summary rows have a slightly distinct background (#f3f6fb). No new CSS framework or component library.
  7. css-modules.d.ts addition — fixes a pre-existing TypeScript 5.9 strict-mode error on the import './styles.css' side-effect import in index.ts. The base branch had this error but CI passed because of bundler resolution; adding the declaration makes local tsc --noEmit clean as well.

Out of scope

Test plan

  • All 1452 existing tests still pass (npm test)
  • TypeScript --noEmit clean
  • npm run build compiles without errors
  • 16 new tests added:

🤖 Generated with claude-flow

Summary by CodeRabbit

Release Notes

  • New Features
    • Recommendations table now groups variants by physical cell with collapsible summary rows
    • Added "Expand all / Collapse all" toggle to manage multi-variant cell visibility
    • Displays "Recommended range" banner for pages with multiple variant options

@coderabbitai

coderabbitai Bot commented May 4, 2026 •

Copy link
Copy Markdown
Contributor

@cristim cristim added 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 triaged Item has been triaged labels May 4, 2026
@cristim

cristim commented May 4, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 4, 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: 2

🤖 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/recommendations.ts`:
- Around line 1510-1522: The default "visible" purchase target must be computed
at cell (group) granularity rather than per-recommendation element: update the
logic that builds the default visible selection (currently using
recommendations/selectedRecs and lastVisibleGroupKeys) to treat each group key
in sortedKeys as one visible target when groups.get(key)?.length > 1 (i.e. use
groupRecsByCell(groups) and lastVisibleGroupKeys as the single selectable unit),
so the action text and default submit operate on one summary row per cell
instead of on every sibling variant; adjust any code that derives the count or
list of visible targets (where selectedRecs or the expand-all behavior is
computed) to use the group key list (lastVisibleGroupKeys or sortedKeys filtered
for multi-variant groups) rather than the raw recommendations array.
- Around line 1467-1493: The code injects rec.provider raw into class names and
text in buildVariantRowMarkup (and the similar block at 1559-1563), so
sanitize/whitelist it before interpolation: validate rec.provider against an
allowed provider list or normalize it to a safe token (e.g., map to lowercase
alphanumerics/hyphens, fallback 'unknown-provider') and use that sanitized token
for the CSS class while still escaping the displayed text with
escapeHtml(rec.provider) or a mapped display name; replace direct uses of
rec.provider and rec.provider.toUpperCase() in the template with the sanitized
class token and escaped display string respectively, and apply the same change
to the other analogous function/markup block.
🪄 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: c4724c87-8107-4905-b6d3-4f0113a5fd9a

📥 Commits

Reviewing files that changed from the base of the PR and between bc25a4d and 9a693d8.

📒 Files selected for processing (4)
  • frontend/src/__tests__/recommendations.test.ts
  • frontend/src/css-modules.d.ts
  • frontend/src/recommendations.ts
  • frontend/src/styles/components.css

Comment thread frontend/src/recommendations.ts
Comment thread frontend/src/recommendations.ts
cristim added a commit that referenced this pull request May 4, 2026
…ell purchase target

- Add providerBadgeClass() helper that whitelists provider to aws|azure|gcp
  before injecting into CSS class attributes; use escapeHtml(providerDisplayName())
  for the badge display text. Applied in both the summary row and variant row
  markup paths.

- resolvePurchaseTarget() now uses pickBestVariantPerCell(visible) for the
  default (no-selection) path so the purchase target is exactly one rec per
  physical resource, matching the collapsed grouped table view. Submitting all
  visible variants of a multi-variant cell would create conflicting reservations.

- updateBottomActionBox() derives visibleCellCount from pickBestVariantPerCell
  for the default path and shows "N cells visible" rather than "N visible" to
  match the cell-grouped table count.

- Update affected tests: #111 (a)/(b)/(c)/(d) fixtures changed to use distinct
  resource_type values per rec so each rec occupies its own cell and all appear
  in the default purchase target (triggering fan-out). Bundle B summary text
  assertions updated to match "cells visible" format.
@cristim

cristim commented May 4, 2026

Copy link
Copy Markdown
Member Author

CodeRabbit review — round 2 response

Addressed both actionable findings from the round 1 review:

Finding 1 — provider injection into CSS class (security)

  • Added providerBadgeClass() helper that whitelists rec.provider against aws|azure|gcp before injecting into class attributes. Non-whitelisted values fall back to '' (no badge class).
  • Badge display text is now escapeHtml(providerDisplayName(rec.provider)) in both the summary row and variant row markup paths.

Finding 2 — cell purchase target regression

  • resolvePurchaseTarget() now uses pickBestVariantPerCell(visible) for the default (no-selection) path. This ensures the purchase target is exactly one rec per physical resource — matching what the user sees in the collapsed grouped table. Submitting all visible variants of a multi-variant cell would create conflicting reservations for the same resource, violating the one-variant-per-cell contract from fix(frontend/recs): enforce one-variant-per-cell radio-button selection (select-all currently 6x over-commits) #224.
  • updateBottomActionBox() derives visibleCellCount from pickBestVariantPerCell for the default path and shows "N cells visible" to match the cell-grouped table count.

Test updates

  • verify recommendations engine actually consumes per-account service overrides #111 (a)/(b)/(c)/(d) fixtures: changed to use distinct resource_type values per rec so each rec occupies its own cell. With pickBestVariantPerCell for the default target, same-cell recs (same account + same resource type) now collapse to a single target — tests that expected fan-out needed different cells.
  • Bundle B summary text assertions updated to match the new "cells visible" format.

All 1452 tests pass, TypeScript clean, build clean.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@cristim Thanks for the detailed round 2 summary. Both fixes look well-reasoned:

  • The providerBadgeClass() whitelist approach is the right pattern for safe CSS-class injection — falling back to '' for unknown providers avoids any class-name pollution or injection risk.
  • Using pickBestVariantPerCell(visible) for the default purchase-target path correctly enforces the one-variant-per-cell contract established in #224, and the test fixture updates (distinct resource_type per rec) ensure those tests remain meaningful.

Triggering a fresh review now.

[review]

cristim added 2 commits May 4, 2026 11:38
…/expand (closes #225, closes #226)

- Group recommendation rows by physical-resource cell (provider|account|service|
  region|resource_type|engine) using the same cellKey as #224's radio enforcement.
- Multi-variant cells render as a collapsed summary row by default (chevron to
  expand); single-variant cells render flat with no group overhead.
- Per-cell summary row shows savings range ($X – $Y/mo), upfront range, and term
  range; collapses to a single value when all variants are uniform.
- When a variant is selected (radio from #224), the summary row shows that
  variant's values with a "+N variants" affordance instead of the range.
- Page-level range banner at the top of the table: "Recommended range: $X –
  $Y/mo across N cells" (sum of per-cell min/max across visible cells).
- Expand-All/Collapse-All toggle added to the filter-status bar; only shown when
  at least one multi-variant cell is present.
- Sort operates at cell level: groups ordered by selected-variant value if one is
  selected in the cell, else by max value across variants. String columns use the
  shared cell-identity value. Within a cell, variants sort by (term ASC, payment
  fixed-order: no-upfront < partial < all-upfront < monthly).
- Filter operates at cell level: any variant matching a filter keeps the entire
  cell visible.
- Adds css-modules.d.ts to fix a pre-existing TS 5.9 strict-mode error on the
  `import './styles.css'` side-effect import in index.ts.
- Removes now-unused sortedRecommendations flat-list sort helper (superseded by
  groupsInSortOrder which reuses the same SORTABLE_* column maps).
- Exports resetExpandedCells() for test isolation of module-level expand state.
- Adds 16 new tests (6 pure-helper, 6 DOM integration, 4 expand/collapse) and
  updates the existing #224 radio-enforcement test to expand the cell first.
…ell purchase target

- Add providerBadgeClass() helper that whitelists provider to aws|azure|gcp
  before injecting into CSS class attributes; use escapeHtml(providerDisplayName())
  for the badge display text. Applied in both the summary row and variant row
  markup paths.

- resolvePurchaseTarget() now uses pickBestVariantPerCell(visible) for the
  default (no-selection) path so the purchase target is exactly one rec per
  physical resource, matching the collapsed grouped table view. Submitting all
  visible variants of a multi-variant cell would create conflicting reservations.

- updateBottomActionBox() derives visibleCellCount from pickBestVariantPerCell
  for the default path and shows "N cells visible" rather than "N visible" to
  match the cell-grouped table count.

- Update affected tests: #111 (a)/(b)/(c)/(d) fixtures changed to use distinct
  resource_type values per rec so each rec occupies its own cell and all appear
  in the default purchase target (triggering fan-out). Bundle B summary text
  assertions updated to match "cells visible" format.
@cristim
cristim force-pushed the feat/issues-225-226-cell-grouping branch from e22afd6 to 5201c69 Compare May 4, 2026 09:39
@cristim

cristim commented May 4, 2026

Copy link
Copy Markdown
Member Author

Rebased onto current feat/multicloud-web-frontend (which now includes PR #254's monthly_cost fix — null-aware rendering, NaN for numeric filters, POSITIVE_INFINITY for sort). Resolved conflicts in frontend/src/recommendations.ts: kept #253's cell-grouping structure (rows.join(''), buildVariantRowMarkup, expand/collapse, groupRecsByCell, cellSummary, pageLevelRange, groupsInSortOrder) + adopted #254's corrected null-aware monthly_cost data path (monthly_cost != null ? formatCurrency(...) : '—' in buildVariantRowMarkup; ?? Number.POSITIVE_INFINITY in sort; ?? Number.NaN in filter). TypeScript clean, 140 tests pass, webpack build clean. @coderabbitai review

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

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