Files
kandev/apps/backend/internal/github/client_helpers.go
T
2026-02-24 00:48:55 +00:00

152 lines
3.9 KiB
Go

package github
import (
"context"
"time"
)
// buildReviewSearchQuery assembles the full GitHub search query.
// When customQuery is non-empty, it is used verbatim as the entire query.
// Otherwise a query is built from scope + optional filter qualifier.
func buildReviewSearchQuery(scope, filter, customQuery string) string {
if customQuery != "" {
return customQuery
}
var base string
if scope == ReviewScopeUser {
base = "type:pr state:open user-review-requested:@me"
} else {
base = "type:pr state:open review-requested:@me"
}
if filter != "" {
base += " " + filter
}
return base
}
// getPRFeedback fetches aggregated feedback for a PR using any Client implementation.
// This shared function eliminates duplication between GHClient and PATClient.
func getPRFeedback(ctx context.Context, c Client, owner, repo string, number int) (*PRFeedback, error) {
pr, err := c.GetPR(ctx, owner, repo, number)
if err != nil {
return nil, err
}
reviews, err := c.ListPRReviews(ctx, owner, repo, number)
if err != nil {
return nil, err
}
comments, err := c.ListPRComments(ctx, owner, repo, number, nil)
if err != nil {
return nil, err
}
checks, err := c.ListCheckRuns(ctx, owner, repo, pr.HeadSHA)
if err != nil {
return nil, err
}
hasIssues := hasFailingChecks(checks) || hasChangesRequested(reviews)
return &PRFeedback{
PR: pr,
Reviews: reviews,
Comments: comments,
Checks: checks,
HasIssues: hasIssues,
}, nil
}
// convertRawCheckRuns converts raw ghCheckRun structs into the domain CheckRun type.
func convertRawCheckRuns(raw []ghCheckRun) []CheckRun {
checks := make([]CheckRun, len(raw))
for i, cr := range raw {
conclusion := ""
if cr.Conclusion != nil {
conclusion = *cr.Conclusion
}
output := ""
if cr.Output.Summary != nil {
output = *cr.Output.Summary
}
checks[i] = CheckRun{
Name: cr.Name,
Status: cr.Status,
Conclusion: conclusion,
HTMLURL: cr.HTMLURL,
Output: output,
StartedAt: parseTimePtr(cr.StartedAt),
CompletedAt: parseTimePtr(cr.CompletedAt),
}
}
return checks
}
// convertRawComments converts raw ghComment structs into the domain PRComment type.
func convertRawComments(raw []ghComment) []PRComment {
comments := make([]PRComment, len(raw))
for i, c := range raw {
comments[i] = PRComment{
ID: c.ID,
Author: c.User.Login,
AuthorAvatar: c.User.AvatarURL,
Body: c.Body,
Path: c.Path,
Line: c.Line,
Side: c.Side,
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
InReplyTo: c.InReplyTo,
}
}
return comments
}
// convertSearchItemToPR converts common search result fields into a PR struct.
func convertSearchItemToPR(
number int, title, htmlURL, state, authorLogin, repositoryURL string,
draft bool, createdAt, updatedAt time.Time,
) *PR {
owner, repo := parseRepoURL(repositoryURL)
return &PR{
Number: number,
Title: title,
HTMLURL: htmlURL,
State: state,
AuthorLogin: authorLogin,
Draft: draft,
RepoOwner: owner,
RepoName: repo,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
}
// latestReviewByAuthor returns a map of the most recent review per author.
func latestReviewByAuthor(reviews []PRReview) map[string]PRReview {
latest := make(map[string]PRReview)
for _, r := range reviews {
existing, ok := latest[r.Author]
if !ok || r.CreatedAt.After(existing.CreatedAt) {
latest[r.Author] = r
}
}
return latest
}
// hasFailingChecks returns true if any completed check run has failed.
func hasFailingChecks(checks []CheckRun) bool {
for _, c := range checks {
if c.Status == checkStatusCompleted && c.Conclusion == checkConclusionFail {
return true
}
}
return false
}
// hasChangesRequested returns true if any review has requested changes.
func hasChangesRequested(reviews []PRReview) bool {
for _, r := range reviews {
if r.State == reviewStateChangesRequested {
return true
}
}
return false
}