Skip to content

fix: resolving feature#4338

Merged
chrisgacsal merged 3 commits into
mainfrom
fix/feature-key
May 11, 2026
Merged

fix: resolving feature#4338
chrisgacsal merged 3 commits into
mainfrom
fix/feature-key

Conversation

@chrisgacsal
Copy link
Copy Markdown
Collaborator

@chrisgacsal chrisgacsal commented May 11, 2026

Overview

Fix resolving features for ratecards both in product catalog and subscription services.
Prior to this fix ratecards with features referenced by their ID instead of key were not properly resolved, handled.

Notes for reviewer

Some end-to-end tests covering product catalog functionalities are temporarily disabled and are going to be fixed in a followup PR.

Summary by CodeRabbit

  • Bug Fixes

    • Improved feature resolution for rate cards to ensure correct feature associations
    • Enhanced subscription item feature mapping to handle both feature keys and IDs
    • Made data loading more reliable to avoid missing related references
    • Ensured subscription views reflect chosen item feature IDs in rate card metadata
  • Tests

    • Skipped certain e2e product catalog subtests related to specific rate-card update/validation flows
    • Updated unit tests to align with adjusted rate-card metadata and added stronger assertions for subscription updates

Review Change Stack

@chrisgacsal chrisgacsal self-assigned this May 11, 2026
@chrisgacsal chrisgacsal requested a review from a team as a code owner May 11, 2026 14:51
@chrisgacsal chrisgacsal added the release-note/bug-fix Release note: Bug Fixes label May 11, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 11, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 77f8fdca-6459-4454-8090-296e77948b03

📥 Commits

Reviewing files that changed from the base of the PR and between 22f782c and e22850b.

📒 Files selected for processing (1)
  • e2e/productcatalog_smoke_v3_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • e2e/productcatalog_smoke_v3_test.go

📝 Walkthrough

Walkthrough

Feature resolution and rate card metadata handling across product catalog and subscription layers is refined: adapter mappings backfill missing feature keys when IDs exist, plan service phase iteration is fixed for in-place mutations, subscription views propagate resolved feature IDs, and subscription service feature resolution expands to handle both identifiers. Some E2E tests are skipped pending validation.

Changes

Feature Resolution and Rate Card Metadata Backfill

Layer / File(s) Summary
Test Data Adjustment
openmeter/productcatalog/plan/service/service_test.go
Plan service test input is adjusted so rate card metadata no longer pre-populates FeatureKey/FeatureID from test features; trial phase FlatFeeRateCard FeatureKey and pro phase UsageBasedRateCard FeatureID are now nil.
Adapter Mapping Feature Key Backfill
openmeter/productcatalog/addon/adapter/mapping.go, openmeter/productcatalog/plan/adapter/mapping.go
FromAddonRateCardRow and fromPlanRateCardRow now backfill FeatureKey from the associated feature when FeatureID is set but FeatureKey is nil; edge loading failures return errors.
Phase Edge Loading Requirement
openmeter/productcatalog/plan/adapter/mapping.go
fromPlanPhaseRow now requires ratecards edge to be eagerly loaded via OrErr; missing edge returns an error instead of conditionally proceeding.
Phase Creation Reload Fix
openmeter/productcatalog/plan/adapter/phase.go
Phase creation now unconditionally re-queries the created PlanPhase with eager-loaded rate cards and tax codes after the conditional rate-card bulk creation block.
Plan Service Feature Resolution Fix
openmeter/productcatalog/plan/service/plan.go
CreatePlan and UpdatePlan iterate phases by index instead of value range, passing pointers to in-slice RateCards so resolveFeatures mutations persist back into params.Phases.
Subscription Mapping Cleanup
openmeter/subscription/repo/mapping.go
MapDBSubscriptionItem moves nil-guard check to function start; adds NOTE clarifying that feature resolution happens at service layer.
Subscription View Feature ID Propagation
openmeter/subscription/subscriptionview.go
NewSubscriptionView updates rate card metadata via ChangeMeta to set FeatureID to resolved itemFeat.ID when a feature choice is selected.
Subscription Service Feature Resolution Enhancement
openmeter/subscription/service/service.go
ExpandViews now collects both FeatureKey and FeatureID and fetches features using the combined set; feature-to-item mapping matches items by either key or ID.
Integration Test Assertions
openmeter/subscription/service/sync_test.go
Subscription service sync_test now captures GetView and Update return values and asserts subscription IDs are non-empty before validation.
E2E Test Skips
e2e/productcatalog_smoke_v3_test.go
E2E smoke test skips three subtests: updating a plan with mixed rate card types, adding a defective cadence-misaligned rate card, and removing it to verify validation errors clear.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

area/product-catalog, area/subscriptions

Suggested reviewers

  • tothandras
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'fix: resolving feature' is vague and generic—it lacks specificity about which aspect of feature resolution is being fixed or what the main change accomplishes. Consider a more descriptive title like 'fix: resolve features by ID in ratecards and subscriptions' to better convey what problem is being addressed.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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/feature-key

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.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
openmeter/productcatalog/plan/adapter/mapping.go (1)

302-310: 💤 Low value

Feature key backfill workaround is consistent with addon mapping.

This mirrors the same pattern in addon/adapter/mapping.go. Same suggestion applies here about making the error message more descriptive with namespace/ID context.

🤖 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 `@openmeter/productcatalog/plan/adapter/mapping.go` around lines 302 - 310, The
error returned when failing to load the feature for a ratecard is too generic;
update the block that checks r.FeatureID/r.FeatureKey (the code that calls
r.Edges.FeaturesOrErr() and sets meta.FeatureKey from ratecardFeature.Key) to
return a descriptive error using fmt.Errorf that includes identifying context
such as the ratecard/record ID and the FeatureID (or namespace) so the message
reads something like "feature not loaded for ratecard <ID> featureID
<FeatureID>: %w" and include the original err as the wrapped error.
openmeter/productcatalog/addon/adapter/mapping.go (1)

101-109: 💤 Low value

Workaround for missing feature key linkage works correctly.

The logic properly handles the case where FeatureID is set but FeatureKey is missing by backfilling from the loaded Features edge. The error message could be slightly more helpful by including context:

💬 Optional: More descriptive error message
 if r.FeatureID != nil && r.FeatureKey == nil {
   ratecardFeature, err := r.Edges.FeaturesOrErr()
   if err != nil {
-    return nil, errors.New("feature is not loaded for ratecard")
+    return nil, fmt.Errorf("feature edge not loaded for ratecard %s (namespace=%s, feature_id=%s)", r.ID, r.Namespace, *r.FeatureID)
   }
🤖 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 `@openmeter/productcatalog/addon/adapter/mapping.go` around lines 101 - 109,
The error returned when Features edge is not loaded in the block that backfills
FeatureKey (within mapping.go where r.FeatureID != nil && r.FeatureKey == nil
uses r.Edges.FeaturesOrErr()) should include more context; update the
errors.New(...) to return a descriptive error that includes identifying
information such as the RateCard or FeatureID (use r.FeatureID or another
available identifier) and mention that the Features edge was not loaded so
callers can quickly identify the problematic entity.
openmeter/subscription/service/service.go (1)

554-572: 💤 Low value

Consider extracting the unique key/ID builder for clarity.

The inline function that builds uniqFeatureKeyOrIDs is readable but adds nesting. Extracting it to a helper method would improve flow and make it easier to unit test the deduplication logic separately.

♻️ Optional: Extract helper
+func extractUniqueFeatureIdentifiers(items []subscription.SubscriptionItem) []string {
+	keyOrIDs := make(map[string]struct{}, len(items))
+	for _, item := range items {
+		fKey, fID := lo.FromPtr(item.RateCard.AsMeta().FeatureKey), lo.FromPtr(item.RateCard.AsMeta().FeatureID)
+		if fKey != "" {
+			keyOrIDs[fKey] = struct{}{}
+		}
+		if fID != "" {
+			keyOrIDs[fID] = struct{}{}
+		}
+	}
+	return lo.MapToSlice(keyOrIDs, func(key string, _ struct{}) string {
+		return key
+	})
+}
+
 func (s *service) ExpandViews(ctx context.Context, subs []subscription.Subscription) ([]subscription.SubscriptionView, error) {
 	...
 	var featsOfItems pagination.Result[feature.Feature]
 
 	{
-		uniqFeatureKeyOrIDs := func() []string {
-			keyOrIDS := make(map[string]struct{}, len(items))
-
-			for _, item := range items {
-				fKey, fID := lo.FromPtr(item.RateCard.AsMeta().FeatureKey), lo.FromPtr(item.RateCard.AsMeta().FeatureID)
-
-				if fKey != "" {
-					keyOrIDS[fKey] = struct{}{}
-				}
-
-				if fID != "" {
-					keyOrIDS[fID] = struct{}{}
-				}
-			}
-
-			return lo.MapToSlice(keyOrIDS, func(key string, _ struct{}) string {
-				return key
-			})
-		}()
+		uniqFeatureKeyOrIDs := extractUniqueFeatureIdentifiers(items)
🤖 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 `@openmeter/subscription/service/service.go` around lines 554 - 572, Extract
the inline builder for uniqFeatureKeyOrIDs into a new helper function (e.g.,
buildUniqueFeatureKeysOrIDs(items []*SubscriptionItem) []string) that
encapsulates the loop, the lo.FromPtr calls on item.RateCard.AsMeta().FeatureKey
and FeatureID, the map-based deduplication and the final lo.MapToSlice
conversion; replace the inline anonymous function with a call to that helper so
the deduplication logic is isolated and unit-testable while preserving the exact
returned []string behavior.
🤖 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 `@e2e/productcatalog_smoke_v3_test.go`:
- Around line 140-141: Remove or update the unconditional t.Skip("Skip this
test...") in e2e/productcatalog_smoke_v3_test.go so the defective rate card
validation subtests are not silently disabled; either revert the skip to
re-enable the tests (so the plan/ratecard persistence and validation modified by
the PR are exercised) or replace the skip message with a precise explanation why
the test must remain skipped (for example "validation now requires explicit
feature IDs"), and apply the same change to the other skipped call around the
related subtests (the t.Skip at the other location referenced in the comment).
- Around line 92-93: The e2e test skip in e2e/productcatalog_smoke_v3_test.go
removes validation of the multi-rate-card scenario that exercises feature
resolution changed in openmeter/subscription/service/service.go; update the test
(remove the t.Skip) to use the new dual key/ID resolution by referencing some
features by ID and others by key in the rate cards so the flat + usage-based +
graduated scenario runs, or if you prefer add a small dedicated e2e that
specifically asserts feature-by-ID resolution, and if you must keep it skipped
include a tracking issue/PR number in the skip message for accountability.

In `@openmeter/subscription/service/service.go`:
- Around line 658-670: The feature-matching lambda passed to lo.Find uses
sequential assignments to featFound which lets the second check overwrite the
first; change the predicate in that anonymous function (the one iterating
featsOfItems.Items with feature.Feature f and using fKey/fID/featFound) to use
OR logic instead of sequential assignment—e.g., compute featFound as (fKey != ""
&& f.Key == fKey) || (fID != "" && f.ID == fID) or use short-circuit if/else
checks so a key match isn’t lost when an ID check follows.

In `@openmeter/subscription/subscriptionview.go`:
- Around line 411-417: The call to item.RateCard.ChangeMeta is ignoring its
returned error; update the code around itemFeat handling so you capture the
error from item.RateCard.ChangeMeta (the lambda that sets m.FeatureID =
lo.ToPtr(itemFeat.ID) can still lead ChangeMeta to fail) and handle it
appropriately—e.g., return the error up the call stack or log it and continue
depending on surrounding semantics; ensure you reference
item.RateCard.ChangeMeta, productcatalog.RateCardMeta, and itemFeat when adding
the error check so failures to mutate the rate card are not silently dropped.

---

Nitpick comments:
In `@openmeter/productcatalog/addon/adapter/mapping.go`:
- Around line 101-109: The error returned when Features edge is not loaded in
the block that backfills FeatureKey (within mapping.go where r.FeatureID != nil
&& r.FeatureKey == nil uses r.Edges.FeaturesOrErr()) should include more
context; update the errors.New(...) to return a descriptive error that includes
identifying information such as the RateCard or FeatureID (use r.FeatureID or
another available identifier) and mention that the Features edge was not loaded
so callers can quickly identify the problematic entity.

In `@openmeter/productcatalog/plan/adapter/mapping.go`:
- Around line 302-310: The error returned when failing to load the feature for a
ratecard is too generic; update the block that checks r.FeatureID/r.FeatureKey
(the code that calls r.Edges.FeaturesOrErr() and sets meta.FeatureKey from
ratecardFeature.Key) to return a descriptive error using fmt.Errorf that
includes identifying context such as the ratecard/record ID and the FeatureID
(or namespace) so the message reads something like "feature not loaded for
ratecard <ID> featureID <FeatureID>: %w" and include the original err as the
wrapped error.

In `@openmeter/subscription/service/service.go`:
- Around line 554-572: Extract the inline builder for uniqFeatureKeyOrIDs into a
new helper function (e.g., buildUniqueFeatureKeysOrIDs(items
[]*SubscriptionItem) []string) that encapsulates the loop, the lo.FromPtr calls
on item.RateCard.AsMeta().FeatureKey and FeatureID, the map-based deduplication
and the final lo.MapToSlice conversion; replace the inline anonymous function
with a call to that helper so the deduplication logic is isolated and
unit-testable while preserving the exact returned []string behavior.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 47e373ad-873f-405c-97c7-255e5cc8b311

📥 Commits

Reviewing files that changed from the base of the PR and between 23d9f70 and 22f782c.

📒 Files selected for processing (10)
  • e2e/productcatalog_smoke_v3_test.go
  • openmeter/productcatalog/addon/adapter/mapping.go
  • openmeter/productcatalog/plan/adapter/mapping.go
  • openmeter/productcatalog/plan/adapter/phase.go
  • openmeter/productcatalog/plan/service/plan.go
  • openmeter/productcatalog/plan/service/service_test.go
  • openmeter/subscription/repo/mapping.go
  • openmeter/subscription/service/service.go
  • openmeter/subscription/service/sync_test.go
  • openmeter/subscription/subscriptionview.go

Comment thread e2e/productcatalog_smoke_v3_test.go
Comment thread e2e/productcatalog_smoke_v3_test.go
Comment thread openmeter/subscription/service/service.go
Comment thread openmeter/subscription/subscriptionview.go
@chrisgacsal chrisgacsal merged commit f3ae09c into main May 11, 2026
28 checks passed
@chrisgacsal chrisgacsal deleted the fix/feature-key branch May 11, 2026 15:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/bug-fix Release note: Bug Fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants