Skip to content

feat(stats): filter unsupported release branches#98

Merged
brianmcarey merged 1 commit intokubevirt:mainfrom
dhiller:filter-unsupported-release-branches
Oct 7, 2025
Merged

feat(stats): filter unsupported release branches#98
brianmcarey merged 1 commit intokubevirt:mainfrom
dhiller:filter-unsupported-release-branches

Conversation

@dhiller
Copy link
Contributor

@dhiller dhiller commented Sep 26, 2025

What this PR does / why we need it:

Platform Support

Every KubeVirt release is

  1. targeted towards and life-time coupled with the most recent Kubernetes release
  2. throughout it's life-time the release is validated against the Kubernetes release
    in the support period at the time of GA of the KubeVirt release
    (source)

Also the k8s-support matrix shows the EOL'd versions: https://github.com/kubevirt/sig-release/blob/main/releases/k8s-support-matrix.md

This effectively means that the KubeVirt community will only actively support the three latest release branches, since only these target supported Kubernetes versions - all other branches are supported at best-effort.

Therefore we should calculate the metrics only considering the supported branches.

This change filters unsupported release branches from the metric calculation process.

Which issue(s) this PR fixes (optional, in fixes #<issue number>(, fixes #<issue_number>, ...) format, will close the issue(s) when PR gets merged):
Fixes #

Special notes for your reviewer:

/cc @brianmcarey @cuiyingd

@kubevirt-bot kubevirt-bot added the dco-signoff: yes Indicates the PR's author has DCO signed all their commits. label Sep 26, 2025
@kubevirt-bot
Copy link
Collaborator

@dhiller: GitHub didn't allow me to request PR reviews from the following users: cuiyingd.

Note that only kubevirt members and repo collaborators can review this PR, and authors cannot review their own PRs.

Details

In response to this:

What this PR does / why we need it:

Platform Support

Every KubeVirt release is

  1. targeted towards and life-time coupled with the most recent Kubernetes release
  2. throughout it's life-time the release is validated against the Kubernetes release
    in the support period at the time of GA of the KubeVirt release
    (source)

Also the k8s-support matrix shows the EOL'd versions: https://github.com/kubevirt/sig-release/blob/main/releases/k8s-support-matrix.md

This effectively means that the KubeVirt community will only actively support the three latest release branches, since only these target supported Kubernetes versions - all other branches are supported at best-effort.

Therefore we should calculate the metrics only considering the supported branches.

This change filters unsupported release branches from the metric calculation process.

Which issue(s) this PR fixes (optional, in fixes #<issue number>(, fixes #<issue_number>, ...) format, will close the issue(s) when PR gets merged):
Fixes #

Special notes for your reviewer:

/cc @brianmcarey @cuiyingd

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@kubevirt-bot kubevirt-bot added needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. size/XL labels Sep 26, 2025
Co-authored-by: Gemini <gemini@google.com>
Signed-off-by: Daniel Hiller <dhiller@redhat.com>
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • In GetSupportedBranches, consider using a proper semver parser (e.g. github.com/blang/semver) instead of naive string comparison to reliably pick the top 3 release branches.
  • The getJobTargetBranch logic is brittle—using regex to extract the “release-X.Y” prefix (or a semantic version parser) will be more robust than splitting on dashes and float-parsing the last segment.
  • FilterJobsPerSigs is now doing both SIG metrics counting and branch filtering; consider renaming it (e.g. filterJobsByBranch) or splitting that responsibility for clearer intent.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In GetSupportedBranches, consider using a proper semver parser (e.g. github.com/blang/semver) instead of naive string comparison to reliably pick the top 3 release branches.
- The getJobTargetBranch logic is brittle—using regex to extract the “release-X.Y” prefix (or a semantic version parser) will be more robust than splitting on dashes and float-parsing the last segment.
- FilterJobsPerSigs is now doing both SIG metrics counting and branch filtering; consider renaming it (e.g. filterJobsByBranch) or splitting that responsibility for clearer intent.

## Individual Comments

### Comment 1
<location> `pkg/sigretests/main.go:286` </location>
<code_context>
 	for _, job := range jobs {
+		targetBranch := getJobTargetBranch(job.jobName)
+		isSupported := false
+		for _, supportedBranch := range supportedBranches {
+			if targetBranch == supportedBranch {
+				isSupported = true
</code_context>

<issue_to_address>
**issue (complexity):** Consider using a pre-built set for supportedBranches and a regex in getJobTargetBranch to improve lookup efficiency and code clarity.

Consider building a set for supportedBranches (up front, once) for O(1) lookups and simplifying getJobTargetBranch with a small regex. For example:

```go
// at package scope – compile once
var releaseSuffix = regexp.MustCompile(`-(\d+(\.\d+)?)$`)

func getJobTargetBranch(jobName string) string {
    if m := releaseSuffix.FindStringSubmatch(jobName); m != nil {
        return "release-" + m[1]
    }
    return "main"
}
```

And in FilterJobsPerSigs, build your set before the loop:

```go
func FilterJobsPerSigs(jobs []job, supportedBranches []string) SigRetests {
    branchSet := make(map[string]struct{}, len(supportedBranches))
    for _, b := range supportedBranches {
        branchSet[b] = struct{}{}
    }

    var prSigRetests SigRetests
    for _, job := range jobs {
        target := getJobTargetBranch(job.jobName)
        if _, ok := branchSet[target]; !ok {
            continue
        }

        switch {
        case checkSIGCIFailure(job):
            prSigRetests.SigCIFailure++
        // ... rest of your cases
        }
        prSigRetests = sortJobNamesOnResult(job, prSigRetests)
    }
    return prSigRetests
}
```

This removes the inner slice loop and avoids `strconv.ParseFloat` on each job.
</issue_to_address>

### Comment 2
<location> `pkg/gh/main.go:141` </location>
<code_context>
 	return mergedQuery.Search.Nodes, err
 }
+
+func (c *Client) GetSupportedBranches(ctx context.Context) ([]string, error) {
+	var query struct {
+		Repository struct {
</code_context>

<issue_to_address>
**issue (complexity):** Consider moving filtering, sorting, and truncation logic out of the main function into GraphQL or a helper to reduce cognitive overhead.

```suggestion
The manual fetch–filter–sort–truncate in one function is adding cognitive overhead. You can push most of that logic into GraphQL (filter + ordering + limit) or extract a small helper that uses a semver library. For example, with GraphQL ordering + limit you could simplify to:

```go
func (c *Client) GetSupportedBranches(ctx context.Context) ([]string, error) {
    var q struct {
        Repository struct {
            Refs struct {
                Nodes []struct{ Name string }
            } `graphql:"refs(
                refPrefix: \"refs/heads/release-\",
                first: 3,
                orderBy: {field: ALPHABETICAL, direction: DESC}
            )"`
        } `graphql:"repository(owner: \"kubevirt\", name: \"kubevirt\")"`
    }
    if err := c.inner.Query(ctx, &q, nil); err != nil {
        return nil, err
    }
    // Prepend "main" and return exactly the 3 newest release-* branches
    out := []string{"main"}
    for _, node := range q.Repository.Refs.Nodes {
        out = append(out, node.Name)
    }
    return out, nil
}
```

Or, if you need true semver sort, extract into a helper with golang.org/x/mod/semver:

```go
// helper.go
func latestReleases(names []string, n int) []string {
    vers := make([]string, 0, len(names))
    for _, b := range names {
        vs := strings.TrimPrefix(b, "release-")
        vers = append(vers, semver.Canonical("v"+vs))
    }
    sort.Slice(vers, func(i, j int) bool {
        return semver.Compare(vers[i], vers[j]) > 0
    })
    out := make([]string, 0, min(n, len(vers)))
    for i := 0; i < n && i < len(vers); i++ {
        out = append(out, "release-"+vers[i][1:]) // drop leading "v"
    }
    return out
}

// main function
func (c *Client) GetSupportedBranches(ctx context.Context) ([]string, error) {
    // fetch all refs/heads/…
    // filter for release-*, then:
    releases := latestReleases(allBranches, 3)
    return append([]string{"main"}, releases...), nil
}
```

Either approach keeps the core function small and moves the sorting/truncation into its own, well-scoped piece.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

return mergedQuery.Search.Nodes, err
}

func (c *Client) GetSupportedBranches(ctx context.Context) ([]string, error) {
Copy link

Choose a reason for hiding this comment

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

issue (complexity): Consider moving filtering, sorting, and truncation logic out of the main function into GraphQL or a helper to reduce cognitive overhead.

Suggested change
func (c *Client) GetSupportedBranches(ctx context.Context) ([]string, error) {
The manual fetchfiltersorttruncate in one function is adding cognitive overhead. You can push most of that logic into GraphQL (filter + ordering + limit) or extract a small helper that uses a semver library. For example, with GraphQL ordering + limit you could simplify to:
```go
func (c *Client) GetSupportedBranches(ctx context.Context) ([]string, error) {
var q struct {
Repository struct {
Refs struct {
Nodes []struct{ Name string }
} `graphql:"refs(
refPrefix: \"refs/heads/release-\",
first: 3,
orderBy: {field: ALPHABETICAL, direction: DESC}
)"`
} `graphql:"repository(owner: \"kubevirt\", name: \"kubevirt\")"`
}
if err := c.inner.Query(ctx, &q, nil); err != nil {
return nil, err
}
// Prepend "main" and return exactly the 3 newest release-* branches
out := []string{"main"}
for _, node := range q.Repository.Refs.Nodes {
out = append(out, node.Name)
}
return out, nil
}

Or, if you need true semver sort, extract into a helper with golang.org/x/mod/semver:

// helper.go
func latestReleases(names []string, n int) []string {
    vers := make([]string, 0, len(names))
    for _, b := range names {
        vs := strings.TrimPrefix(b, "release-")
        vers = append(vers, semver.Canonical("v"+vs))
    }
    sort.Slice(vers, func(i, j int) bool {
        return semver.Compare(vers[i], vers[j]) > 0
    })
    out := make([]string, 0, min(n, len(vers)))
    for i := 0; i < n && i < len(vers); i++ {
        out = append(out, "release-"+vers[i][1:]) // drop leading "v"
    }
    return out
}

// main function
func (c *Client) GetSupportedBranches(ctx context.Context) ([]string, error) {
    // fetch all refs/heads/…
    // filter for release-*, then:
    releases := latestReleases(allBranches, 3)
    return append([]string{"main"}, releases...), nil
}

Either approach keeps the core function small and moves the sorting/truncation into its own, well-scoped piece.

@dhiller dhiller force-pushed the filter-unsupported-release-branches branch from 2172df8 to 83c8953 Compare September 26, 2025 16:39
@kubevirt-bot kubevirt-bot added size/L and removed needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. size/XL labels Sep 26, 2025
@dhiller
Copy link
Contributor Author

dhiller commented Oct 6, 2025

@brianmcarey

e2e tests do succeed locally:

• [SLOW TEST:59.313 seconds]
ci-health stats
/home/dhiller/Projects/github.com/kubevirt.io/ci-health/e2e/tests_suite_test.go:67
  Retrieves data from github and writes badges
  /home/dhiller/Projects/github.com/kubevirt.io/ci-health/e2e/tests_suite_test.go:68
------------------------------

Ran 1 of 1 Specs in 59.313 seconds
SUCCESS! -- 1 Passed | 0 Failed | 0 Pending | 0 Skipped
--- PASS: TestCiHealth (59.31s)
PASS
ok      github.com/kubevirt/ci-health/e2e       59.318s

Copy link
Member

@brianmcarey brianmcarey left a comment

Choose a reason for hiding this comment

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

/approve

thanks @dhiller !

@brianmcarey brianmcarey merged commit 858a7d8 into kubevirt:main Oct 7, 2025
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dco-signoff: yes Indicates the PR's author has DCO signed all their commits. size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants