Skip to content

feat(gcp): populate RecurringMonthlyCost in GCP rec parsers (closes #264) - #811

Merged
cristim merged 7 commits into
mainfrom
fix/264-wave5
Jul 19, 2026
Merged

feat(gcp): populate RecurringMonthlyCost in GCP rec parsers (closes #264)#811
cristim merged 7 commits into
mainfrom
fix/264-wave5

Conversation

@cristim

@cristim cristim commented May 28, 2026

Copy link
Copy Markdown
Member

Summary

  • Populates RecurringMonthlyCost (*float64) on all three GCP recommendation parsers, making GCP rows consistent with AWS and Azure in the frontend's monthly-cost column (closes feat(gcp): populate RecurringMonthlyCost in GCP recommendation parsers #264).
  • computeengine: PaymentOption is "upfront" (all-upfront commitment), so sets RecurringMonthlyCost = &0 -- no per-month charge.
  • cloudsql and memorystore: PaymentOption is "monthly", so calls the existing Billing SKU API (getSQLPricing / getRedisPricing) to derive CommitmentPrice / termMonths. Billing lookup failure is non-fatal: logs a warning and leaves the field nil so the frontend renders "—" rather than a stale value.

Test plan

  • go test github.com/LeanerCloud/CUDly/providers/gcp/services/computeengine/... -- TestComputeEngineClient_ConvertGCPRecommendation asserts RecurringMonthlyCost is a non-nil pointer to 0
  • go test github.com/LeanerCloud/CUDly/providers/gcp/services/cloudsql/... -- two new tests: success path (positive value matching CommitmentPrice/12) and billing-failure path (nil)
  • go test github.com/LeanerCloud/CUDly/providers/gcp/services/memorystore/... -- same two new tests for Memorystore
  • go build ./... clean
  • Pre-existing TestRecommendationsClientAdapter_GetRecommendations_PropagatesContextCancellation failure is unrelated to this PR (was failing on base branch before these changes)

Summary by CodeRabbit

Release Notes

  • New Features

    • GCP service recommendations now include estimated monthly recurring cost calculations for Cloud SQL, Cloud Storage, Compute Engine, and Memorystore.
    • Enhanced cost and resource type extraction from recommendation payloads.
  • Bug Fixes

    • Corrected recurring monthly cost to display $0 for upfront payment options instead of showing no value.

@cristim cristim added triaged Item has been triaged priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/this-quarter Within the quarter impact/few Limited audience effort/s Hours type/feat New capability labels May 28, 2026
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 20 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a2059c52-b76e-42ea-a350-4de523ea4cb2

📥 Commits

Reviewing files that changed from the base of the PR and between c04a1a8 and d994635.

📒 Files selected for processing (8)
  • providers/gcp/services/cloudsql/client.go
  • providers/gcp/services/cloudsql/client_test.go
  • providers/gcp/services/cloudstorage/client.go
  • providers/gcp/services/cloudstorage/client_test.go
  • providers/gcp/services/computeengine/client.go
  • providers/gcp/services/computeengine/client_test.go
  • providers/gcp/services/memorystore/client.go
  • providers/gcp/services/memorystore/client_test.go
📝 Walkthrough

Walkthrough

This PR extends GCP recommendation converters for Cloud SQL, Cloud Storage, Memorystore, and Compute Engine to populate RecurringMonthlyCost by extracting commitment pricing and term information, introducing shared helpers for resource type and savings extraction, and computing monthly costs divided by term months with graceful billing lookup failure handling.

Changes

GCP Recommendation RecurringMonthlyCost Population

Layer / File(s) Summary
Cloud SQL recommendation conversion refactor
providers/gcp/services/cloudsql/client.go, providers/gcp/services/cloudsql/client_test.go
Introduces extractGCPResourceType, extractGCPSavings, and termYearsFromLabel helpers for resource/savings/term extraction. Refactors convertGCPRecommendation to use these helpers, threads term-aware pricing via fillSQLPricing, and computes RecurringMonthlyCost from CommitmentPrice / termMonths with logging on billing lookup failures. Tests validate both successful computation and nil retention on billing errors.
Cloud Storage recommendation conversion refactor
providers/gcp/services/cloudstorage/client.go, providers/gcp/services/cloudstorage/client_test.go
Introduces same three helpers (extractGCPResourceType, extractGCPSavings, termYearsFromLabel) for storage-specific extraction. Refactors convertGCPRecommendation to derive resource type and savings, guard pricing lookup with ResourceType presence, and compute RecurringMonthlyCost from term-normalized commitment price with resilient billing failures. Tests validate computation success and nil behavior on errors.
Memorystore recommendation conversion refactor
providers/gcp/services/memorystore/client.go, providers/gcp/services/memorystore/client_test.go
Adds the same three helpers for Memorystore; refactors convertGCPRecommendation to extract resource type and savings, thread termYearsFromLabel through fillRedisPricing, and compute RecurringMonthlyCost via getRedisPricing with logging and nil-on-failure semantics. Tests verify commitment-based monthly cost computation with 30% 1yr discount factor and nil retention on billing errors.
Compute Engine upfront payment recurring cost
providers/gcp/services/computeengine/client.go, providers/gcp/services/computeengine/client_test.go
Sets rec.RecurringMonthlyCost to pointer-to-zero (&0.0) when params.PaymentOption is "upfront", ensuring explicit zero rendering instead of nil em-dash. Test assertion validates non-nil zero value for upfront recommendations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • LeanerCloud/CUDly#264: This PR directly implements the objective of populating RecurringMonthlyCost in GCP service parsers (Cloud SQL, Cloud Storage, Compute Engine, and also extends to Memorystore) by computing commitment-based monthly costs and handling pricing lookup resilience as described in the issue scope.

Possibly related PRs

  • LeanerCloud/CUDly#1047: This PR's refactor of convertGCPRecommendation and new pricing-computation logic across Cloud SQL, Cloud Storage, Memorystore, and Compute Engine builds directly on earlier wiring of billing-backed pricing introduced in PR #1047.
  • LeanerCloud/CUDly#254: This PR's population of rec.RecurringMonthlyCost across GCP converters (including nil behavior on pricing failures and zero for upfront options) aligns with PR #254's addition of the field and subsequent rendering logic for monthly_cost in the frontend and scheduler.

Suggested labels

priority/p2, severity/medium, urgency/this-sprint, impact/many, effort/m

Poem

🐰 Commitment costs now clearly shown,
No dashes hiding what's been known;
SQL, Storage, Cache aligned,
With upfront zeros finely signed.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.18% 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 PR title clearly and concisely summarizes the main change: populating RecurringMonthlyCost in GCP recommendation parsers across multiple services.
Linked Issues check ✅ Passed The PR successfully implements all core requirements from issue #264: populates RecurringMonthlyCost for GCP services (computeengine, cloudsql, memorystore, cloudstorage), handles term-based commitments and upfront payments correctly, treats billing failures as non-fatal, and includes comprehensive unit tests.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #264 scope: updates to GCP service parsers (computeengine, cloudsql, memorystore, cloudstorage), helper functions for term parsing and extraction, and associated unit tests. No unrelated changes detected.

✏️ 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/264-wave5

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

@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
✅ 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 May 30, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@cristim

cristim commented Jun 1, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 1, 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.

@cristim

cristim commented Jun 4, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Rate Limit Exceeded

@cristim have exceeded the limit for the number of chat messages per hour. Please wait 38 minutes and 55 seconds before sending another message.

@cristim

cristim commented Jun 5, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

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 8, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@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: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
providers/gcp/services/cloudsql/client.go (1)

4-19: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Run gofmt on changed Go files before merging.

CI is currently failing on the go-fmt pre-commit hook.

🤖 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 `@providers/gcp/services/cloudsql/client.go` around lines 4 - 19, CI is failing
due to unformatted Go code in the changed file (the import block in
providers/gcp/services/cloudsql/client.go); run gofmt (or goimports) on that
file to fix formatting and import grouping, save the changes, and commit the
formatted file so the go-fmt pre-commit hook and CI pass.

Source: Pipeline failures

🤖 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 `@providers/gcp/services/cloudsql/client_test.go`:
- Around line 849-915: The test fails because getSQLPricing now requires a
commitment-price SKU but the MockBillingService fixture only provides an
on-demand tier; update the mock (MockBillingService.skus / the Sku in the test)
to include a commitment SKU entry (with appropriate
PricingInfo/PricingExpression/TieredRates representing the 1yr commitment hourly
or total price) so convertGCPRecommendation can find commitment pricing, and
change the expected value computation (used in the assertion comparing against
rec.RecurringMonthlyCost) to derive the monthly commitment from that commitment
SKU rate (or reuse estimateSQLCommitmentPrice logic) instead of relying on the
stale hardcoded 0.85 multiplier.

In `@providers/gcp/services/cloudsql/client.go`:
- Around line 553-568: convertGCPRecommendation is calling getSQLPricing twice:
once indirectly via fillSQLPricing(rec, termYearsFromLabel(rec.Term)) and again
directly when computing RecurringMonthlyCost, causing duplicate external SKU
calls and warnings for empty rec.ResourceType. Fix by removing the second
getSQLPricing call in convertGCPRecommendation and instead have fillSQLPricing
either return the pricing or populate RecurringMonthlyCost (using
termYearsFromLabel(rec.Term) and pricing.CommitmentPrice/float64(termYears*12));
then in convertGCPRecommendation rely on the value set by fillSQLPricing (or use
its returned pricing) to set rec.RecurringMonthlyCost and avoid any extra
getSQLPricing invocation for empty rec.ResourceType.

In `@providers/gcp/services/cloudstorage/client_test.go`:
- Around line 868-926: The test currently only provides an on-demand SKU so
convertGCPRecommendation/getStoragePricing falls into the non-fatal path and
leaves RecurringMonthlyCost nil; update the mock billing response (the
MockBillingService instance used by client.SetBillingService, e.g., mockBilling)
to include an additional commitment SKU with a PricingInfo that contains a
PricingExpression -> TieredRates -> UnitPrice (Money) and appropriate
ServiceRegions so that getStoragePricing can find commitment pricing; re-run the
test to assert RecurringMonthlyCost is non-nil and > 0 for the successful path.
- Around line 922-923: The tests call convertGCPRecommendation with only (ctx,
gcpRec) but the function signature requires a third parameter of type
common.RecommendationParams; update the test invocations (the calls to
convertGCPRecommendation in providers/gcp/services/cloudstorage/client_test.go)
to pass an appropriate common.RecommendationParams value (e.g., a
default/constructed params struct or the existing test params variable) so the
call matches convertGCPRecommendation(ctx, gcpRec, params) and recompile.

In `@providers/gcp/services/cloudstorage/client.go`:
- Around line 545-560: The code is making a redundant billing lookup:
c.fillStoragePricing already performs the pricing lookup, so remove the second
call to c.getStoragePricing and instead use the pricing data populated by
fillStoragePricing to set rec.RecurringMonthlyCost (and ensure you only attempt
to compute monthly when rec.ResourceType is non-empty and the pricing data
populated by fillStoragePricing exists); update the block around
c.fillStoragePricing / RecurringMonthlyCost to rely on the filled pricing rather
than calling getStoragePricing again (references: fillStoragePricing,
getStoragePricing, rec.RecurringMonthlyCost, rec.ResourceType).

In `@providers/gcp/services/computeengine/client_test.go`:
- Around line 903-906: The test is asserting RecurringMonthlyCost is non-nil and
zero for an "upfront" case but the call uses default (monthly) params; either
make the test actually request an upfront recommendation or change assertions to
match monthly-default semantics. Update the test in
providers/gcp/services/computeengine/client_test.go so that the request params
explicitly set PaymentOption = "upfront" when asserting rec.RecurringMonthlyCost
is non-nil and exactly 0, or alternatively (preferred if testing monthly
default) change the assertions to allow RecurringMonthlyCost to be nil (meaning
unknown) and not assert zero; locate references to rec.RecurringMonthlyCost and
the test helper that constructs the request parameters and modify accordingly.

In `@providers/gcp/services/computeengine/client.go`:
- Around line 948-953: The code unconditionally sets rec.RecurringMonthlyCost to
a pointer-to-zero which misrepresents monthly costs; change this so you only set
RecurringMonthlyCost = &zero when the recommendation truly represents an upfront
payment option (i.e., when rec.PaymentOption == "upfront" or the original
source/payment flag indicates upfront), otherwise leave RecurringMonthlyCost
nil; update the logic around RecurringMonthlyCost (and the related PaymentOption
handling) so the nil-vs-zero contract is preserved and only upfront CUDs get the
explicit $0 pointer.

In `@providers/gcp/services/memorystore/client_test.go`:
- Around line 827-828: Tests call convertGCPRecommendation with only (ctx,
gcpRec) but the function signature requires a third common.RecommendationParams
parameter; update the test calls to pass an appropriate RecommendationParams
(e.g., zero-value or constructed params relevant to the test) into
client.convertGCPRecommendation so calls become (ctx, gcpRec, params) where
params is a common.RecommendationParams instance set for the test; update both
occurrences (the call assigning rec and the other call at line ~854) to supply
this third argument.

In `@providers/gcp/services/memorystore/client.go`:
- Around line 536-551: fillRedisPricing is already performing the billing lookup
so avoid calling c.getRedisPricing again; change fillRedisPricing to return the
pricing (or a pointer/struct containing CommitmentPrice) and an error (e.g.,
func (c *Client) fillRedisPricing(ctx context.Context, rec *Record, termYears
int) (*Pricing, error)), update call sites to capture the returned pricing and
error, and use that returned pricing to compute RecurringMonthlyCost (using
termYearsFromLabel(rec.Term)) instead of calling c.getRedisPricing here; also
ensure you skip all pricing work when rec.ResourceType == "" so no duplicate
lookup or warnings occur.

---

Outside diff comments:
In `@providers/gcp/services/cloudsql/client.go`:
- Around line 4-19: CI is failing due to unformatted Go code in the changed file
(the import block in providers/gcp/services/cloudsql/client.go); run gofmt (or
goimports) on that file to fix formatting and import grouping, save the changes,
and commit the formatted file so the go-fmt pre-commit hook and CI pass.
🪄 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: c77e36a8-5091-484d-8d4a-b97586e84bed

📥 Commits

Reviewing files that changed from the base of the PR and between e31ed68 and c04a1a8.

📒 Files selected for processing (8)
  • providers/gcp/services/cloudsql/client.go
  • providers/gcp/services/cloudsql/client_test.go
  • providers/gcp/services/cloudstorage/client.go
  • providers/gcp/services/cloudstorage/client_test.go
  • providers/gcp/services/computeengine/client.go
  • providers/gcp/services/computeengine/client_test.go
  • providers/gcp/services/memorystore/client.go
  • providers/gcp/services/memorystore/client_test.go

Comment thread providers/gcp/services/cloudsql/client_test.go Outdated
Comment thread providers/gcp/services/cloudsql/client.go
Comment thread providers/gcp/services/cloudstorage/client_test.go
Comment thread providers/gcp/services/cloudstorage/client_test.go Outdated
Comment thread providers/gcp/services/cloudstorage/client.go
Comment thread providers/gcp/services/computeengine/client_test.go Outdated
Comment thread providers/gcp/services/computeengine/client.go Outdated
Comment thread providers/gcp/services/memorystore/client_test.go Outdated
Comment thread providers/gcp/services/memorystore/client.go
cristim added a commit that referenced this pull request Jun 8, 2026
…undant lookups

Address CodeRabbit findings on PR #811:

- cloudsql/cloudstorage/memorystore converters issued a second
  getSQLPricing/getStoragePricing/getRedisPricing call after fill*Pricing
  already performed the lookup, doubling SKU API calls and emitting warnings
  even for empty ResourceType. Reuse the CommitmentCost the fill* helpers
  populate and gate the whole block on a non-empty ResourceType.

- computeengine unconditionally set RecurringMonthlyCost to a pointer-to-zero,
  mislabeling monthly-billed CUDs as a known-zero recurring fee and breaking
  the nil-vs-zero contract. Derive the monthly charge from CommitmentCost and
  leave the field nil when pricing is unavailable. Add termYearsFromTerm and
  reuse it in enrichRecWithPricing, which also makes 36mo terms map to 3 years.

- Fix the success-path test fixtures: getSQLPricing/getRedisPricing now require
  a commitment SKU, so the on-demand-only fixtures drove the failure branch and
  left RecurringMonthlyCost nil. Add commitment SKUs and replace the stale
  hardcoded discount multipliers with the commitment-rate-derived expectation.

- Fix compile errors in cloudstorage and memorystore tests that still called
  convertGCPRecommendation with two arguments; pass common.RecommendationParams.

- Align the compute test assertion with the monthly-default path: empty params
  yield no computed monthly pricing, so RecurringMonthlyCost stays nil (unknown),
  not an explicit zero.
@cristim

cristim commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

Addressed all 9 review threads in b84c7ce. Per-finding summary:

  1. cloudsql/client_test.go (missing commitment SKU + stale 0.85 multiplier) — FIXED. getSQLPricing now requires a commitment SKU, so the on-demand-only fixture drove the failure branch. Added a commitment SKU and replaced the stale 0.85 expectation with commitmentHourly * 8760 / 12.
  2. cloudsql/client.go (duplicate getSQLPricing call) — FIXED. Reuse rec.CommitmentCost (already set by fillSQLPricing) instead of a second lookup; gated on non-empty ResourceType.
  3. cloudstorage/client_test.go (missing commitment SKU) — FIXED. Added a commitment SKU so the success path actually populates RecurringMonthlyCost.
  4. cloudstorage/client_test.go (compile error: 2-arg call) — FIXED. Pass common.RecommendationParams{} at both call sites.
  5. cloudstorage/client.go (redundant billing call) — FIXED. Same reuse-CommitmentCost pattern as Setup CI/CD Process #2.
  6. computeengine/client_test.go (assertion contradicts monthly-default path) — FIXED. Changed to assert.Nil: empty params default to monthly, no billing mock is injected, so pricing is unavailable and the field stays nil (unknown), not explicit zero.
  7. computeengine/client.go (unconditional $0) — FIXED. Derive monthly from CommitmentCost; leave nil when pricing is unavailable, preserving the nil-vs-zero contract. Added termYearsFromTerm (also reused in enrichRecWithPricing, which incidentally makes 36mo map to 3 years).
  8. memorystore/client_test.go (compile error: 2-arg call) — FIXED. Also added a commitment SKU (getRedisPricing requires one) and replaced the stale 0.70 multiplier with the commitment-rate-derived expectation.
  9. memorystore/client.go (duplicate Redis lookup) — FIXED. Same reuse-CommitmentCost pattern as Setup CI/CD Process #2.

Build green (go build ./...); GCP tests green (go test ./..., 265 passed). gofmt + go vet clean.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

@cristim: Thanks for the thorough per-finding summary — the fixes look well-reasoned across the board. The reuse-CommitmentCost pattern to eliminate redundant billing lookups (#2, #5, #9) and the nil-vs-zero contract clarification for computeengine (#6, #7) are particularly clean resolutions. Triggering a review of commit b84c7cebf now.

(ノ◕ヮ◕)ノ*:・゚✧

✅ Action performed

Review finished.

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 changed the base branch from feat/multicloud-web-frontend to main June 9, 2026 15:45
cristim added a commit that referenced this pull request Jun 19, 2026
…undant lookups

Address CodeRabbit findings on PR #811:

- cloudsql/cloudstorage/memorystore converters issued a second
  getSQLPricing/getStoragePricing/getRedisPricing call after fill*Pricing
  already performed the lookup, doubling SKU API calls and emitting warnings
  even for empty ResourceType. Reuse the CommitmentCost the fill* helpers
  populate and gate the whole block on a non-empty ResourceType.

- computeengine unconditionally set RecurringMonthlyCost to a pointer-to-zero,
  mislabeling monthly-billed CUDs as a known-zero recurring fee and breaking
  the nil-vs-zero contract. Derive the monthly charge from CommitmentCost and
  leave the field nil when pricing is unavailable. Add termYearsFromTerm and
  reuse it in enrichRecWithPricing, which also makes 36mo terms map to 3 years.

- Fix the success-path test fixtures: getSQLPricing/getRedisPricing now require
  a commitment SKU, so the on-demand-only fixtures drove the failure branch and
  left RecurringMonthlyCost nil. Add commitment SKUs and replace the stale
  hardcoded discount multipliers with the commitment-rate-derived expectation.

- Fix compile errors in cloudstorage and memorystore tests that still called
  convertGCPRecommendation with two arguments; pass common.RecommendationParams.

- Align the compute test assertion with the monthly-default path: empty params
  yield no computed monthly pricing, so RecurringMonthlyCost stays nil (unknown),
  not an explicit zero.
@cristim

cristim commented Jun 19, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

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 Jul 9, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

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 added a commit that referenced this pull request Jul 10, 2026
…undant lookups

Address CodeRabbit findings on PR #811:

- cloudsql/cloudstorage/memorystore converters issued a second
  getSQLPricing/getStoragePricing/getRedisPricing call after fill*Pricing
  already performed the lookup, doubling SKU API calls and emitting warnings
  even for empty ResourceType. Reuse the CommitmentCost the fill* helpers
  populate and gate the whole block on a non-empty ResourceType.

- computeengine unconditionally set RecurringMonthlyCost to a pointer-to-zero,
  mislabeling monthly-billed CUDs as a known-zero recurring fee and breaking
  the nil-vs-zero contract. Derive the monthly charge from CommitmentCost and
  leave the field nil when pricing is unavailable. Add termYearsFromTerm and
  reuse it in enrichRecWithPricing, which also makes 36mo terms map to 3 years.

- Fix the success-path test fixtures: getSQLPricing/getRedisPricing now require
  a commitment SKU, so the on-demand-only fixtures drove the failure branch and
  left RecurringMonthlyCost nil. Add commitment SKUs and replace the stale
  hardcoded discount multipliers with the commitment-rate-derived expectation.

- Fix compile errors in cloudstorage and memorystore tests that still called
  convertGCPRecommendation with two arguments; pass common.RecommendationParams.

- Align the compute test assertion with the monthly-default path: empty params
  yield no computed monthly pricing, so RecurringMonthlyCost stays nil (unknown),
  not an explicit zero.
@cristim

cristim commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

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 added a commit that referenced this pull request Jul 17, 2026
…undant lookups

Address CodeRabbit findings on PR #811:

- cloudsql/cloudstorage/memorystore converters issued a second
  getSQLPricing/getStoragePricing/getRedisPricing call after fill*Pricing
  already performed the lookup, doubling SKU API calls and emitting warnings
  even for empty ResourceType. Reuse the CommitmentCost the fill* helpers
  populate and gate the whole block on a non-empty ResourceType.

- computeengine unconditionally set RecurringMonthlyCost to a pointer-to-zero,
  mislabeling monthly-billed CUDs as a known-zero recurring fee and breaking
  the nil-vs-zero contract. Derive the monthly charge from CommitmentCost and
  leave the field nil when pricing is unavailable. Add termYearsFromTerm and
  reuse it in enrichRecWithPricing, which also makes 36mo terms map to 3 years.

- Fix the success-path test fixtures: getSQLPricing/getRedisPricing now require
  a commitment SKU, so the on-demand-only fixtures drove the failure branch and
  left RecurringMonthlyCost nil. Add commitment SKUs and replace the stale
  hardcoded discount multipliers with the commitment-rate-derived expectation.

- Fix compile errors in cloudstorage and memorystore tests that still called
  convertGCPRecommendation with two arguments; pass common.RecommendationParams.

- Align the compute test assertion with the monthly-default path: empty params
  yield no computed monthly pricing, so RecurringMonthlyCost stays nil (unknown),
  not an explicit zero.
cristim added a commit that referenced this pull request Jul 19, 2026
…undant lookups

Address CodeRabbit findings on PR #811:

- cloudsql/cloudstorage/memorystore converters issued a second
  getSQLPricing/getStoragePricing/getRedisPricing call after fill*Pricing
  already performed the lookup, doubling SKU API calls and emitting warnings
  even for empty ResourceType. Reuse the CommitmentCost the fill* helpers
  populate and gate the whole block on a non-empty ResourceType.

- computeengine unconditionally set RecurringMonthlyCost to a pointer-to-zero,
  mislabeling monthly-billed CUDs as a known-zero recurring fee and breaking
  the nil-vs-zero contract. Derive the monthly charge from CommitmentCost and
  leave the field nil when pricing is unavailable. Add termYearsFromTerm and
  reuse it in enrichRecWithPricing, which also makes 36mo terms map to 3 years.

- Fix the success-path test fixtures: getSQLPricing/getRedisPricing now require
  a commitment SKU, so the on-demand-only fixtures drove the failure branch and
  left RecurringMonthlyCost nil. Add commitment SKUs and replace the stale
  hardcoded discount multipliers with the commitment-rate-derived expectation.

- Fix compile errors in cloudstorage and memorystore tests that still called
  convertGCPRecommendation with two arguments; pass common.RecommendationParams.

- Align the compute test assertion with the monthly-default path: empty params
  yield no computed monthly pricing, so RecurringMonthlyCost stays nil (unknown),
  not an explicit zero.
@cristim

cristim commented Jul 19, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

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 added 7 commits July 19, 2026 20:19
)

Set RecurringMonthlyCost on all three GCP service recommendation parsers:
- computeengine: PaymentOption "upfront" -> &0 (no monthly charge)
- cloudsql: monthly -> CommitmentPrice/termMonths via billing SKU lookup
- memorystore: monthly -> CommitmentPrice/termMonths via billing SKU lookup

Billing lookup failure is non-fatal: log a warning and leave the field nil
so the frontend renders "—" rather than a stale value.

Adds 5 new tests (2 per monthly service + 1 on computeengine) asserting
the field is non-nil and matches the expected pricing formula.
Extract extractGCPResourceType, extractGCPSavings, and termYearsFromLabel
helpers from convertGCPRecommendation in both the memorystore and cloudsql
packages. Complexity drops from 13 to 5, clearing the gocyclo pre-commit
hook failure on CI.
… (refs #264)

The three other GCP service clients (cloudsql, memorystore, computeengine)
already populate RecurringMonthlyCost in convertGCPRecommendation via their
respective pricing helpers. cloudstorage was the only remaining client that
left the field nil, causing the frontend to render "--" for all Cloud Storage
rows.

The fix calls getStoragePricing (which already existed for GetOfferingDetails)
and sets RecurringMonthlyCost = CommitmentPrice / (termYears * 12). A pricing
lookup failure is non-fatal: log a warning and leave the field nil so the
frontend renders "--" rather than a stale value -- matching the sibling pattern.

Tests added:
- success case: non-nil RecurringMonthlyCost > 0 when billing returns a SKU
- billing-failure case: field stays nil when billing call errors out (must not
  coerce to 0)
…mendation

Extract extractGCPResourceType, extractGCPSavings, and termYearsFromLabel
helpers from convertGCPRecommendation in the cloudstorage package, mirroring
the existing refactor in the cloudsql and memorystore packages. Complexity
drops from 13 to under the limit, clearing the gocyclo pre-commit hook
failure on CI. Behavior is unchanged.
…undant lookups

Address CodeRabbit findings on PR #811:

- cloudsql/cloudstorage/memorystore converters issued a second
  getSQLPricing/getStoragePricing/getRedisPricing call after fill*Pricing
  already performed the lookup, doubling SKU API calls and emitting warnings
  even for empty ResourceType. Reuse the CommitmentCost the fill* helpers
  populate and gate the whole block on a non-empty ResourceType.

- computeengine unconditionally set RecurringMonthlyCost to a pointer-to-zero,
  mislabeling monthly-billed CUDs as a known-zero recurring fee and breaking
  the nil-vs-zero contract. Derive the monthly charge from CommitmentCost and
  leave the field nil when pricing is unavailable. Add termYearsFromTerm and
  reuse it in enrichRecWithPricing, which also makes 36mo terms map to 3 years.

- Fix the success-path test fixtures: getSQLPricing/getRedisPricing now require
  a commitment SKU, so the on-demand-only fixtures drove the failure branch and
  left RecurringMonthlyCost nil. Add commitment SKUs and replace the stale
  hardcoded discount multipliers with the commitment-rate-derived expectation.

- Fix compile errors in cloudstorage and memorystore tests that still called
  convertGCPRecommendation with two arguments; pass common.RecommendationParams.

- Align the compute test assertion with the monthly-default path: empty params
  yield no computed monthly pricing, so RecurringMonthlyCost stays nil (unknown),
  not an explicit zero.
golangci-lint misspell (locale US) flagged "unrecognised" in the new
termYearsFromLabel/termYearsFromTerm doc comments added by this branch.
Use "unrecognized" so the PR introduces no new lint findings.
Use a raw string literal (backtick) for the assert message that
contains square-bracket quotes around "gcp" to fix the
"missing ',' in argument list" compile error in the pre-commit
gofmt/go-vet pass.
@cristim

cristim commented Jul 19, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

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 dc1e2fa into main Jul 19, 2026
19 checks passed
@cristim
cristim deleted the fix/264-wave5 branch July 27, 2026 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/few Limited audience priority/p3 Polish / idea / may never ship severity/low Minor 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.

feat(gcp): populate RecurringMonthlyCost in GCP recommendation parsers

1 participant