Skip to content

Chore/sp 4217 enhance component entity output#8

Merged
agustingroh merged 2 commits intomainfrom
chore/SP-4217-enhance-component-entity-output
Apr 1, 2026
Merged

Chore/sp 4217 enhance component entity output#8
agustingroh merged 2 commits intomainfrom
chore/SP-4217-enhance-component-entity-output

Conversation

@agustingroh
Copy link
Copy Markdown
Contributor

@agustingroh agustingroh commented Mar 31, 2026

Summary by CodeRabbit

  • New Features

    • Added a nearest-version selection utility (operator stripping, weighted semver distance, higher-version tie-break).
    • Components now retain original input values and expose a versions list for each component.
  • Documentation

    • README updated with usage guide and examples for the version-selection utility.
  • Tests

    • New unit tests cover nearest-version logic and preservation of original component fields.
  • Chores

    • Updated dependencies.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 31, 2026

Warning

Rate limit exceeded

@agustingroh has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 49 seconds before requesting another review.

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 9 minutes and 49 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eeb121f4-4615-4b84-a371-747ad5765ef5

📥 Commits

Reviewing files that changed from the base of the PR and between 46ee7c1 and 9fedcaf.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (8)
  • CHANGELOG.md
  • README.md
  • componenthelper/component_entity.go
  • componenthelper/component_worker.go
  • componenthelper/component_worker_test.go
  • componenthelper/utils/semver.go
  • componenthelper/utils/semver_test.go
  • go.mod
📝 Walkthrough

Walkthrough

Adds a semver resolution utility FindNearestVersion, extends Component with Versions, OriginalPurl, and OriginalRequirement, preserves originals during sanitisation, extends the resolver interface with GetComponentVersions, updates workers/tests/docs, and bumps deps (Masterminds semver and scanoss/go-models).

Changes

Cohort / File(s) Summary
Documentation & Changelog
CHANGELOG.md, README.md
Added v0.6.0 changelog entry and README section demonstrating FindNearestVersion behavior and usage examples.
Component Struct Definition
componenthelper/component_entity.go
Added exported fields to Component: OriginalPurl string (original_purl), OriginalRequirement string (original_requirement), and Versions []string (versions,omitempty).
Component Sanitisation & Tests
componenthelper/component_sanitise.go, componenthelper/component_sanitise_test.go
sanitiseComponents now records original input values into the new fields across all code paths and populates full parsed PURL metadata. Added TestSanitiseComponentsOriginalFields covering varied PURL/requirement scenarios.
Component Worker & Mock
componenthelper/component_worker.go, componenthelper/component_worker_test.go
Extended componentResolver with GetComponentVersions(ctx, purl). Worker now initializes Versions and calls the new resolver method; mock resolver gained GetComponentVersions support. Error handling reorganized (switch-based) and processed component assignment simplified.
Semver Utilities & Tests
componenthelper/utils/semver.go, componenthelper/utils/semver_test.go
Added FindNearestVersion(requirement, candidates) string, stripOperators, and semverDistance implementing operator stripping, semver validation, weighted distance (major>minor>patch), and higher-version tie-breaking. Comprehensive table-driven tests added.
Dependencies
go.mod
Added github.com/Masterminds/semver/v3 v3.4.0 as a direct dependency and bumped github.com/scanoss/go-models to v0.8.0.

Sequence Diagram(s)

sequenceDiagram
  participant Worker as componentVersionWorker
  participant Resolver as componentResolver
  participant Service as ComponentService

  Worker->>Resolver: GetComponent(ctx, purl)
  alt component found
    Resolver->>Worker: Component (metadata, maybe Version)
    Worker->>Resolver: GetComponentVersions(ctx, purl)
    Resolver->>Service: fetch versions for purl
    Service-->>Resolver: versions list
    Resolver-->>Worker: ComponentVersionsResponse (versions)
    Worker->>Worker: populate processedComponent.Versions
  else not found / version missing
    Resolver-->>Worker: ErrComponentNotFound / ErrVersionNotFound
    Worker->>Worker: set processedComponent.Status / StatusCode
  end
  Worker->>Worker: enqueue / persist processedComponent
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • isasmendiagus
  • scanoss-qg
  • eeisegn

Poem

🐰 I hopped through semver fields and strings,
I stripped the ops and chased the nearest things.
I kept the old PURL safe and sound,
Collected versions all around—
A tidy hop, a version spring! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Chore/sp 4217 enhance component entity output' directly relates to the main changes: adding new fields to the Component struct (OriginalPurl, OriginalRequirement, Versions) and supporting utilities.

✏️ 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 chore/SP-4217-enhance-component-entity-output

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.

@agustingroh agustingroh force-pushed the chore/SP-4217-enhance-component-entity-output branch from ee9e219 to a263b96 Compare March 31, 2026 17:37
Copy link
Copy Markdown

@scanoss-qg scanoss-qg left a comment

Choose a reason for hiding this comment

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

LGTM considering new changes on model helper

@scanoss-qg scanoss-qg marked this pull request as ready for review March 31, 2026 17:45
@agustingroh agustingroh force-pushed the chore/SP-4217-enhance-component-entity-output branch from a263b96 to 46ee7c1 Compare March 31, 2026 17:50
@agustingroh agustingroh requested a review from scanoss-qg March 31, 2026 17:51
Copy link
Copy Markdown

@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

Caution

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

⚠️ Outside diff range comments (2)
componenthelper/component_worker.go (2)

94-97: ⚠️ Potential issue | 🟠 Major

Successful resolutions still leave Versions empty.

The new public field is only populated in the error branch, so the common success path never returns the available version list even after adding Component.Versions. If this field is part of the enhanced component output, populate it here as well or document it as error-only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@componenthelper/component_worker.go` around lines 94 - 97, The success path
never populates the new public Versions field, so set
processedComponent.Versions = component.Versions before sending the result;
specifically, in the block around component.Version != "" and just before
results <- processedComponent, assign the component's Versions slice/map into
processedComponent.Versions so successful responses include the available
version list (mirroring what the error branch does).

72-88: ⚠️ Potential issue | 🟠 Major

Only call GetComponentVersions for ErrVersionNotFound.

This block currently runs for every non-ErrComponentNotFound failure, so transient/backend errors now trigger a second lookup and still get flattened to ComponentNotFound. It also logs err instead of errCompVersion, which hides the actual versions-lookup failure.

Suggested change
-		// Add list of component versions
-		processedComponent.Versions = []string{}
-		componentVersions, errCompVersion := resolver.GetComponentVersions(ctx, j.Purl)
-		if errCompVersion != nil {
-			s.Errorf("Failed to get component versions: %s, %v", j.Purl, err)
-		} else {
-			processedComponent.Versions = componentVersions.Versions
-		}
-
 		if errors.Is(err, services.ErrVersionNotFound) {
+			processedComponent.Versions = []string{}
+			componentVersions, errCompVersion := resolver.GetComponentVersions(ctx, j.Purl)
+			if errCompVersion != nil {
+				s.Errorf("Failed to get component versions: %s, %v", j.Purl, errCompVersion)
+			} else {
+				processedComponent.Versions = componentVersions.Versions
+			}
 			s.Warnf("Failed to get component version: %s, %s", j.Purl, j.Requirement)
 			processedComponent.Status.StatusCode = domain.VersionNotFound
 			processedComponent.Status.Message = "Component version not found"
 			results <- processedComponent
 			continue
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@componenthelper/component_worker.go` around lines 72 - 88, The code currently
calls resolver.GetComponentVersions() for every non-ErrComponentNotFound path
and logs the wrong error variable; instead, only invoke
resolver.GetComponentVersions(ctx, j.Purl) when errors.Is(err,
services.ErrVersionNotFound) and use errCompVersion in any log messages; change
the control flow in the block around processedComponent.Versions so that: 1) if
errors.Is(err, services.ErrVersionNotFound) then call
resolver.GetComponentVersions and on errCompVersion log s.Errorf with
errCompVersion (not err), set processedComponent.Status.StatusCode =
domain.VersionNotFound and Message accordingly and send results <-
processedComponent; 2) for other errors (including transient/backend) do not
call GetComponentVersions and keep the existing s.Errorf("Failed to get
component: ...", err) handling. Ensure you reference
resolver.GetComponentVersions, errCompVersion, errors.Is(err,
services.ErrVersionNotFound), and processedComponent when making the change.
🧹 Nitpick comments (1)
componenthelper/component_worker_test.go (1)

48-53: Make unexpected GetComponentVersions calls fail fast in the mock.

Returning types.ComponentVersionsResponse{}, nil when the hook is unset lets worker changes call the new resolver method without any test noticing. Returning an error here keeps the mock honest and makes those regressions visible.

Suggested change
 func (m *mockComponentResolver) GetComponentVersions(ctx context.Context, purl string) (types.ComponentVersionsResponse, error) {
 	if m.getComponentVersionsFn != nil {
 		return m.getComponentVersionsFn(ctx, purl)
 	}
-	return types.ComponentVersionsResponse{}, nil
+	return types.ComponentVersionsResponse{}, errors.New("unexpected GetComponentVersions call")
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@componenthelper/component_worker_test.go` around lines 48 - 53, The mock's
GetComponentVersions currently returns an empty success response when
getComponentVersionsFn is nil, hiding unexpected calls; update
mockComponentResolver.GetComponentVersions to return a non-nil error (e.g.,
fmt.Errorf("unexpected GetComponentVersions call")) when getComponentVersionsFn
== nil so tests fail fast and reveal unintended usage instead of silently
succeeding; reference the getComponentVersionsFn hook in your change and ensure
callers in tests that expect this behavior are updated to set the hook or assert
the error.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CHANGELOG.md`:
- Around line 10-19: The changelog incorrectly marks "## [0.6.0] - 2026-04-01"
as released; keep the 0.6.0 notes under the "## [Unreleased]" section until the
tag is actually created: move the entire 0.6.0 block (the header and its
"Added"/"Changed" bullet points, including the compare link if present) back
under "## [Unreleased]" and remove or defer any release date/compare link for
"0.6.0" so the file only shows the Unreleased entry until the release/tag is
cut.

In `@componenthelper/utils/semver_test.go`:
- Around line 54-63: The test incorrectly expects "~2.0.0" to match 2.1.0;
update the semver handling to stop stripping constraint operators and instead
evaluate candidates against the actual constraint (use Masterminds/semver
Constraint parsing and .Check/version satisfies logic) and only fall back to
nearest-version heuristics if no candidate satisfies the parsed constraint;
update the test case named "with ~ operator" so its expected result reflects
that no candidate satisfies "~2.0.0" (or adjust to a valid satisfying
candidate), and ensure the resolver function that previously removed operators
now uses constraint parsing to filter candidates first.

In `@componenthelper/utils/semver.go`:
- Line 35: The operator stripping misses ^ and = and can leave a leading space
(e.g., ">= 1.2.3"), causing semver.Parse to fail; update the operatorRegex used
by stripOperators to include ^ and = and optionally consume any following space
(so it matches operators like "^", "=", ">=", "<=", "~", ">", "<" with optional
whitespace), and ensure stripOperators trims remaining whitespace (use
strings.TrimSpace before/after replacement) so the returned version string is
normalized before calling semver.Parse; references: operatorRegex and
stripOperators (the semver.Parse caller should receive the trimmed,
operator-stripped string).
- Around line 60-71: The loop that parses candidates treats invalid semver
strings as zeroVersion (v0.0.0) and still sets nearestOriginal to the raw
(invalid) string; change the logic in the candidates loop (the block using
semver.NewVersion, zeroVersion, semverDistance, minDistance, nearest, and
nearestOriginal) so that invalid parses are skipped (do not consider
v==zeroVersion from a failed parse) or only update nearestOriginal when
semver.NewVersion succeeded; ensure only successfully parsed semver values can
win nearest/minDistance comparisons and that nearestOriginal stores the original
string only for valid parsed versions.

---

Outside diff comments:
In `@componenthelper/component_worker.go`:
- Around line 94-97: The success path never populates the new public Versions
field, so set processedComponent.Versions = component.Versions before sending
the result; specifically, in the block around component.Version != "" and just
before results <- processedComponent, assign the component's Versions slice/map
into processedComponent.Versions so successful responses include the available
version list (mirroring what the error branch does).
- Around line 72-88: The code currently calls resolver.GetComponentVersions()
for every non-ErrComponentNotFound path and logs the wrong error variable;
instead, only invoke resolver.GetComponentVersions(ctx, j.Purl) when
errors.Is(err, services.ErrVersionNotFound) and use errCompVersion in any log
messages; change the control flow in the block around
processedComponent.Versions so that: 1) if errors.Is(err,
services.ErrVersionNotFound) then call resolver.GetComponentVersions and on
errCompVersion log s.Errorf with errCompVersion (not err), set
processedComponent.Status.StatusCode = domain.VersionNotFound and Message
accordingly and send results <- processedComponent; 2) for other errors
(including transient/backend) do not call GetComponentVersions and keep the
existing s.Errorf("Failed to get component: ...", err) handling. Ensure you
reference resolver.GetComponentVersions, errCompVersion, errors.Is(err,
services.ErrVersionNotFound), and processedComponent when making the change.

---

Nitpick comments:
In `@componenthelper/component_worker_test.go`:
- Around line 48-53: The mock's GetComponentVersions currently returns an empty
success response when getComponentVersionsFn is nil, hiding unexpected calls;
update mockComponentResolver.GetComponentVersions to return a non-nil error
(e.g., fmt.Errorf("unexpected GetComponentVersions call")) when
getComponentVersionsFn == nil so tests fail fast and reveal unintended usage
instead of silently succeeding; reference the getComponentVersionsFn hook in
your change and ensure callers in tests that expect this behavior are updated to
set the hook or assert the error.
🪄 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: e33cc7f5-52b7-4e84-ac6d-b76aeffccf57

📥 Commits

Reviewing files that changed from the base of the PR and between f4f8334 and a263b96.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (10)
  • CHANGELOG.md
  • README.md
  • componenthelper/component_entity.go
  • componenthelper/component_sanitise.go
  • componenthelper/component_sanitise_test.go
  • componenthelper/component_worker.go
  • componenthelper/component_worker_test.go
  • componenthelper/utils/semver.go
  • componenthelper/utils/semver_test.go
  • go.mod

Comment thread CHANGELOG.md
Comment thread componenthelper/utils/semver_test.go
Comment thread componenthelper/utils/semver.go Outdated
Comment thread componenthelper/utils/semver.go
Copy link
Copy Markdown

@scanoss-qg scanoss-qg left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
componenthelper/component_worker.go (1)

84-91: Normalize resolver version-list output to avoid nil slice regressions.

Line 90 can replace the pre-initialized empty slice with nil when the resolver returns no versions. Keeping [] consistently can make downstream JSON/output handling more predictable.

Proposed adjustment
-			processedComponent.Versions = componentVersions.Versions
+			if componentVersions.Versions == nil {
+				processedComponent.Versions = []string{}
+			} else {
+				processedComponent.Versions = componentVersions.Versions
+			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@componenthelper/component_worker.go` around lines 84 - 91, The resolver may
return an empty version list which should be normalized to nil to avoid
downstream JSON/slice regressions: after calling
resolver.GetComponentVersions(ctx, j.Purl) in the block that assigns
processedComponent.Versions, check componentVersions.Versions and if its length
is zero set processedComponent.Versions = nil (otherwise assign the returned
slice); update the assignment around the resolver.GetComponentVersions /
processedComponent.Versions logic to ensure empty lists become nil.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@componenthelper/component_worker.go`:
- Around line 84-91: The resolver may return an empty version list which should
be normalized to nil to avoid downstream JSON/slice regressions: after calling
resolver.GetComponentVersions(ctx, j.Purl) in the block that assigns
processedComponent.Versions, check componentVersions.Versions and if its length
is zero set processedComponent.Versions = nil (otherwise assign the returned
slice); update the assignment around the resolver.GetComponentVersions /
processedComponent.Versions logic to ensure empty lists become nil.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a03d38f2-4b31-4fd8-ac59-666bc77fcd76

📥 Commits

Reviewing files that changed from the base of the PR and between a263b96 and 46ee7c1.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (8)
  • CHANGELOG.md
  • README.md
  • componenthelper/component_entity.go
  • componenthelper/component_worker.go
  • componenthelper/component_worker_test.go
  • componenthelper/utils/semver.go
  • componenthelper/utils/semver_test.go
  • go.mod
✅ Files skipped from review due to trivial changes (3)
  • go.mod
  • componenthelper/utils/semver_test.go
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • README.md

@agustingroh agustingroh force-pushed the chore/SP-4217-enhance-component-entity-output branch from 46ee7c1 to 9fedcaf Compare March 31, 2026 18:09
@agustingroh agustingroh merged commit 67e5c1c into main Apr 1, 2026
3 checks passed
@agustingroh agustingroh deleted the chore/SP-4217-enhance-component-entity-output branch April 1, 2026 11:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants