fix(settings): clamp per-service payment options to supported combinations#30
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThe PR introduces per-service AWS payment/term validity constraints in the settings UI. It adds payment option visibility rules (e.g., RDS hides Changes
Sequence DiagramsequenceDiagram
actor User
participant UI as Settings UI
participant Handler as Term/Payment Handler
participant Validator as Constraint Validator
participant DOM as Form DOM
User->>UI: Change service term dropdown
UI->>Handler: Trigger term change event
Handler->>Validator: Call syncPaymentConstraintsForService(service)
Validator->>Validator: Check isValidCombination for each payment option
Validator->>DOM: Hide/disable invalid payment options
Validator->>DOM: If current payment invalid, clamp to first valid option
DOM->>User: Display updated payment options
User->>UI: Propagate payment to all services
UI->>Handler: Call propagatePaymentToServices(payment)
Handler->>DOM: Update payment value for all service dropdowns
Handler->>Validator: Sync constraints for each service
Validator->>DOM: Hide/disable + clamp per-service invalid combinations
DOM->>User: Display final valid state across all services
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/settings.ts (1)
33-33:⚠️ Potential issue | 🟡 MinorService key mismatch:
savingsplansvssavings-plans.
SERVICE_FIELDSusesservice: 'savingsplans'here, butcommitmentOptions.tsregisters the config under the key'savings-plans'(with hyphen). WhenisValidCombination('aws', 'savingsplans', …)is called, the lookup fails and silently falls back to_defaultinstead of the savings-plans-specific config.Today both are identical (same terms and payments, no
invalidCombinations), so constraint checking still works. However, if someone adds restrictions to the'savings-plans'config incommitmentOptions.ts, this file will silently skip them. Fix by aligning the key:Change SERVICE_FIELDS to use hyphenated form
- { provider: 'aws', service: 'savingsplans', termId: 'aws-savingsplans-term', paymentId: 'aws-savingsplans-payment' }, + { provider: 'aws', service: 'savings-plans', termId: 'aws-savingsplans-term', paymentId: 'aws-savingsplans-payment' },The DOM ids (
aws-savingsplans-*) can remain unchanged; only theservicekey needs aligning withcommitmentOptions.ts.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/settings.ts` at line 33, SERVICE_FIELDS currently uses service: 'savingsplans' which does not match the key 'savings-plans' registered in commitmentOptions.ts, causing isValidCombination('aws','savingsplans',...) to miss the specific config; update the SERVICE_FIELDS entry for AWS to use the hyphenated service key 'savings-plans' (leave DOM ids like 'aws-savingsplans-term' unchanged) so lookups in commitmentOptions.ts and calls to isValidCombination resolve to the correct config.
🧹 Nitpick comments (1)
frontend/src/__tests__/settings.test.ts (1)
735-862: Solid coverage for the new constraint wiring.The suite exercises the three behavioral axes the implementation cares about:
- Visibility differences by term (RDS 3yr vs 1yr).
- Event-driven clamp on term change.
- Load-time clamp for legacy-persisted invalid combos.
- Negative case (EC2 is unrestricted).
.eachfor the other restricted services (elasticache/opensearch/redshift) avoids repetition.- Propagation path end-to-end (global default → per-service clamp).
Two minor optional refinements:
- The
await Promise.resolve(); await Promise.resolve();pump at lines 853–854 is brittle — ifconfirmAndPropagatePaymentever gains an extraawaitin its chain the test will silently pass before the assertion is meaningful. Prefer an explicit settle helper such asawait new Promise(r => setTimeout(r, 0));(matching whatsetupSettingsHandlerstests already use at lines 192/208/230).memorydbhas the same{ term: 3, payment: 'no-upfront' }restriction incommitmentOptions.tsbut no entry inSERVICE_FIELDS, so it's correctly omitted from the.each. If a MemoryDB card is ever added to the Settings UI, extend the.eachto cover it.💚 Proposed test-settling tweak
- // Allow the async confirmDialog promise to resolve. - await Promise.resolve(); - await Promise.resolve(); + // Allow the async confirmDialog → propagate chain to settle. + await new Promise((r) => setTimeout(r, 0));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/__tests__/settings.test.ts` around lines 735 - 862, Replace the brittle double Promise.resolve pump after dispatching the defaultPayment change with an explicit microtask tick (e.g. await new Promise(r => setTimeout(r, 0))) so the test reliably waits for confirmAndPropagatePayment/confirmDialog promise chains to settle; also, note that memorydb currently has the same restriction in commitmentOptions.ts but is absent from SERVICE_FIELDS, so if a MemoryDB settings card is later added extend the test.each([...]) that enumerates 'elasticache','opensearch','redshift' to include 'memorydb' to keep coverage consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@frontend/src/settings.ts`:
- Line 33: SERVICE_FIELDS currently uses service: 'savingsplans' which does not
match the key 'savings-plans' registered in commitmentOptions.ts, causing
isValidCombination('aws','savingsplans',...) to miss the specific config; update
the SERVICE_FIELDS entry for AWS to use the hyphenated service key
'savings-plans' (leave DOM ids like 'aws-savingsplans-term' unchanged) so
lookups in commitmentOptions.ts and calls to isValidCombination resolve to the
correct config.
---
Nitpick comments:
In `@frontend/src/__tests__/settings.test.ts`:
- Around line 735-862: Replace the brittle double Promise.resolve pump after
dispatching the defaultPayment change with an explicit microtask tick (e.g.
await new Promise(r => setTimeout(r, 0))) so the test reliably waits for
confirmAndPropagatePayment/confirmDialog promise chains to settle; also, note
that memorydb currently has the same restriction in commitmentOptions.ts but is
absent from SERVICE_FIELDS, so if a MemoryDB settings card is later added extend
the test.each([...]) that enumerates 'elasticache','opensearch','redshift' to
include 'memorydb' to keep coverage consistent.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5dc4bde3-5823-4b60-ab57-9e0290757e42
📒 Files selected for processing (2)
frontend/src/__tests__/settings.test.tsfrontend/src/settings.ts
Two related fixes in the Settings → Purchasing form. 1. The per-service dropdowns let users pick AWS RDS 3-year + No Upfront, which the provider refuses (see `cmd/validators.go:warnRDS3YearNoUpfront`). The plan-creation modal already guarded against this through `commitmentOptions.isValidCombination`; the Settings form was the last place an invalid default could be persisted. 2. `commitmentOptions.ts` was marking ElastiCache / OpenSearch / Redshift / MemoryDB as also rejecting 3yr no-upfront, but that was over-cautious copy-paste. The backend validator only warns about RDS, and the newer `lib/purchase-compatibility.ts` calls RDS out as "the one hard rule". AWS does offer 3yr no-upfront on those other services, so the rules have been narrowed to RDS only. Wiring: - `settings.ts` gains `syncPaymentConstraintsForService(field)` and `syncAllServiceCommitmentConstraints()`. For each AWS service with an `invalidCombinations` entry (just RDS after this fix), payment `<option>` rows that pair invalidly with the currently-selected term are hidden + disabled. If the currently-selected payment becomes invalid after a term change, it auto-clamps to the first valid option so the form never holds an unsavable value. - A `change` listener on each AWS service term `<select>` re-runs the sync on the matching payment select. - `loadGlobalSettings` calls the sync at the end, so legacy-persisted invalid combos (RDS 3yr + no-upfront, possible on settings saved before this fix) get clamped on load. - `propagateTermToServices` / `propagatePaymentToServices` both re-sync after the bulk write so global-default propagation never silently writes an invalid per-service combo. Tests: New `per-service term/payment combination constraints` describe block: - RDS 3yr hides "No Upfront" and keeps Partial / All Upfront. - RDS 1yr keeps all three payment options visible. - Switching RDS term 1→3 with "No Upfront" selected auto-clamps. - Legacy-persisted invalid combo (RDS 3yr + no-upfront) is clamped on load. - EC2 3yr keeps all three options visible. - ElastiCache / OpenSearch / Redshift 3yr all KEEP "No Upfront" visible and round-trip the persisted value cleanly (via test.each). - Propagating global "no-upfront" to all services with term=3 clamps RDS specifically, leaves EC2 and the others unchanged. Updated commitmentOptions.test.ts to assert ElastiCache / OpenSearch / Redshift / MemoryDB have no `invalidCombinations` and accept 3yr no-upfront through `isValidCombination`, `getValidPaymentOptions`, and `getValidTermOptions`. The existing "sets up default payment to propagate to AWS services" test in settings.test.ts was updated to reflect the clamping behaviour — previously it asserted RDS would accept propagated no-upfront, which was the bug. Test fixture updated so AWS payment selects carry all three options like production — the previous two-option fixture couldn't meaningfully exercise the constraint filtering. Verified: `npm test` (1235/1235 pass across 35 suites), `npm run typecheck` clean, `npm run build` emits a successful bundle. Refs: #12
a62260c to
423b09b
Compare
…opdown/DB (#63) CodeRabbit flagged a key mismatch on PR #30: `SERVICE_FIELDS` in `settings.ts` uses `service: 'savingsplans'`, while `commitmentConfigs` in `commitmentOptions.ts` registered the AWS entry under `'savings-plans'` (hyphenated). Any lookup via `isValidCombination`/`getCommitmentConfig` with the non-hyphenated identifier silently fell through to `_default` instead of hitting the Savings-Plans-specific config. Today both configs are identical so behaviour is unchanged, but any future restriction added to `'savings-plans'` would have been silently skipped. CodeRabbit's suggestion was to rewrite `SERVICE_FIELDS` to the hyphenated form, but `field.service` in `SERVICE_FIELDS` flows directly to `api.updateServiceConfig(provider, service, cfg)` (settings.ts:1443) which persists `service` as the primary key in the `service_configs` table — existing production rows are `('aws', 'savingsplans')`, and renaming SERVICE_FIELDS without a DB migration would have created duplicate rows and orphaned the old config. The dropdown at `index.html:112,703` (`<option value="savingsplans">`) and the backend CLI flag (`cmd/main.go:92`) also use the non-hyphenated form. Fix by renaming the `commitmentConfigs.aws` key from `'savings-plans'` to `savingsplans` so every frontend call site — the SERVICE_FIELDS path, the plan-form dropdown path, and any direct lookups — resolves to the Savings-Plans-specific config. Update the two test cases in `commitmentOptions.test.ts` to match. This addresses the only live CodeRabbit finding across all open/closed PRs (24-39, 52-54); PR #52's nitpicks were already resolved in db429fe, and the remaining PR #30 nitpicks (double `Promise.resolve()` in settings.test.ts:854, memorydb absent from SERVICE_FIELDS) apply only to code that lives on PR #30's branch and not the base.
Summary
Follow-up to #12 (Azure payment defaults, fixed in #27). The Settings → Purchasing form still let users set RDS 3-year + No Upfront (and the same for ElastiCache / OpenSearch / Redshift), which the AWS provider refuses at plan-execution time — see
cmd/validators.go:warnRDS3YearNoUpfront. The plan-creation modal already guards against this viacommitmentOptions.isValidCombination; this PR wires the same rules into the Settings form so a default that the backend can't honour can't even be typed in.What changes
frontend/src/settings.tssyncPaymentConstraintsForService(field)andsyncAllServiceCommitmentConstraints()— for each AWS service with a restricted pairing, hide + disable the invalid<option>rows in the payment dropdown. If the currently-selected payment becomes invalid after a term change, it auto-clamps to the first valid option so the form never holds an unsavable value.changelistener on each AWS service term dropdown → re-applies the constraint on that service's payment dropdown.syncAllServiceCommitmentConstraints()at the end ofloadGlobalSettingsso legacy-persisted invalid combos (RDS 3yr + no-upfront, possible on settings saved before this fix) get clamped on load.propagateTermToServicesandpropagatePaymentToServicesso global-default propagation never silently writes invalid per-service combos.Azure and GCP are intentionally out of scope — Azure has only two payment options (neither restricted by term) and GCP has no payment selector at all, so there are no same-shape bugs to fix there.
Test changes
New describe block
per-service term/payment combination constraints:test.each)The existing "sets up default payment to propagate to AWS services" test was updated to reflect the new clamping behaviour — previously it asserted that RDS would accept the propagated
no-upfront, which was exactly the bug this PR fixes.Test fixture updated so the AWS payment selects carry all three options (
no-upfront,partial-upfront,all-upfront) like production — the previous fixture only had two and couldn't meaningfully exercise the constraint filtering.Test plan
npm test— 1243/1243 pass across 35 suitesnpm run typecheck— cleannpm run build— production bundle buildsRefs: #12
@coderabbitai review
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes