chore(common): normalize Savings Plans identifier to "savingsplans" (frontend canonical)#94
Conversation
…frontend canonical)
Aligns common.ServiceSavingsPlans with the value the frontend already
persists ("savingsplans") and removes the foot-gun where any new code
comparing rec.Service == common.ServiceSavingsPlans would silently miss
Savings Plans rows. Picks the frontend canonical (issue #85 Option B) to
avoid a SQL data migration on service_configs.service / purchase_history.
Pre-flight findings (grep over the whole tree):
- frontend already uses "savingsplans" everywhere — no frontend changes.
- the only Go string literals using the hyphenated form were the
constant declaration, its constant-value test, the dual-case branch in
the purchase-execution mapper, and the matching test case for that
mapper.
- service_configs.service is VARCHAR(64) with no CHECK constraint and no
seed data; the frontend has always written "savingsplans".
- purchase_executions.recommendations is JSONB and rec.Service is
serialised as `string(rec.Service)` (scheduler.go, audit.go), so any
Lambda-scheduled SP execution persisted before this change carries
rec.Service == "savings-plans" in the JSONB blob and is re-fed through
mapServiceType on retry / approval. The dual-case in execution.go is
therefore retained as a backwards-compat alias (with a TODO and a
follow-up issue) instead of being removed; the persistent rows are the
reason the issue's hard "remove the dual-case branch" couldn't be
satisfied in a single PR.
Changes:
- pkg/common/types.go: ServiceSavingsPlans value flipped from
"savings-plans" to "savingsplans"; expanded godoc explains the
rationale and points back at the issue.
- pkg/common/types_test.go: regression guard now asserts the constant
value is "savingsplans" so any future flip back is a deliberate change
that requires a SQL migration.
- internal/purchase/execution.go: mapServiceType keeps both
"savingsplans" and "savings-plans" as legacy alias, with a comment
block + TODO(#85) explaining when it can be removed.
- internal/purchase/coverage_extra_test.go: legacy "savings-plans" input
case retained and re-documented.
Refs #85
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 31 minutes and 41 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
✨ 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.
|
|
Merge conflict detected ( |
Summary
Normalises
common.ServiceSavingsPlansfrom the hyphenated"savings-plans"to the frontend canonical"savingsplans"so that direct comparisons (rec.Service == common.ServiceSavingsPlans) work without the historical normaliser shim. Picks issue #85 Option B (frontend wins) to avoid a SQL data migration onservice_configs.service/purchase_history.service.Refs #85(the issue's hard removal of the dual-case branch is intentionally deferred — see "WhyRefsnotCloses" below).Pre-flight findings
Tree-wide grep for
savings-plans/savingsplans/ServiceSavingsPlans:frontend/src/**)"savingsplans"everywhere"savingsplans"pkg/common/types.go:49"savings-plans""savingsplans"pkg/common/types_test.go:42"savings-plans"internal/purchase/execution.go:384dual-case"savings-plans", "savingsplans"internal/purchase/coverage_extra_test.gocommon.ServiceSavingsPlanssymbolicallycmd/main.go::parseServicesmap"savingsplans"service VARCHAR(64), no CHECK, no seed data with either formpkg/common/audit.go)Why the dual-case in
execution.gois retained (deferred from #85's acceptance)scheduler.go:834andaudit.go:50serialiserec.Serviceasstring(rec.Service)into thepurchase_executions.recommendationsJSONB column. Any Lambda-scheduled Savings Plans execution persisted before this PR carries"service": "savings-plans"in that JSONB blob. On retry / approval (internal/purchase/execution.go:231iteratesexec.Recommendations), each rec is re-fed throughmapServiceType(). Removing the legacy alias arm now would silently break those rows by mapping them toServiceType("savings-plans")instead ofServiceSavingsPlans, after whichcloudProvider.GetServiceClient(ctx, serviceType, …)would fail.Trade-off:
TODO(#85)marker, and a follow-up issue tracking eventual removal once historical rows have aged out (~6 months retention). The constant is canonical; the alias is purely a read-side shim at the API boundary.purchase_executions.recommendationsJSONB to flip"savings-plans"→"savingsplans". Out of scope for this PR (matches issue Normalize 'savings-plans' vs 'savingsplans' identifier across frontend + backend #85's explicit "Option B" preference of avoiding migrations).The regression test in
pkg/common/types_test.gowill fail if anyone flips the constant back, so the canonical never silently drifts again.Changes
pkg/common/types.go—ServiceSavingsPlans = "savingsplans"; expanded godoc explains rationale + points back at Normalize 'savings-plans' vs 'savingsplans' identifier across frontend + backend #85.pkg/common/types_test.go— regression guard asserts"savingsplans".internal/purchase/execution.go—mapServiceTypekeeps both"savingsplans"and"savings-plans"with a documented backwards-compat block +TODO(#85)for removal.internal/purchase/coverage_extra_test.go— legacy"savings-plans"input case retained and re-documented.Net diff: 30 insertions, 5 deletions across 4 files.
Verification
go build ./...clean from the root and from each submodule (pkg/,providers/aws,providers/azure,providers/gcp).go test ./...from root +pkg/submodule: all tests pass that were passing before.go test ./providers/aws/...: the only AWS-side failures areTestAWSProvider_GetDefaultRegion/*, which are pre-existing environment-leak bugs unrelated to this PR (the test mutatesaws.Config.RegionbutIsConfigured()clobbers it from the SDK default-credentials chain reading~/.aws/config).TestSavingsPlans*andTestAWSProvider_GetSupportedServices(which touchesServiceSavingsPlansdirectly) all pass.go test ./providers/gcp/...: pre-existing failure inTestMemorystoreClient_GetExistingCommitments_WithMockServiceis unrelated to Savings Plans.go vet ./...clean.Why
Refs #85notCloses #85Issue #85's acceptance includes "The dual-case branch in
execution.go:384removed." This PR retains the dual-case as a backwards-compat alias for persisted Lambda-scheduled rows (rationale above). The follow-up issue tracking the eventual removal will be filed once this lands and the post-merge verification completes.Test plan
service_configs(it should — frontend was already writing"savingsplans"); the round-trip read should now compare equal tocommon.ServiceSavingsPlanswithout the normaliser