Files
kandev/apps/backend/internal/github/service_pr_status.go
T
João Salavisa 651d66a0cf refactor(github): split service.go into domain subfiles (#1053)
Splits the 2,842-line apps/backend/internal/github/service.go monolith into nine focused files, all under the 800-line revive threshold. No public API changes, no behavior changes — Service struct, constructor, and every method signature preserved verbatim.

Files: errors.go (sentinels), service.go (struct + constructor + getters), service_tokens.go, service_pr.go, service_pr_watch.go, service_reviews.go, service_issues.go, service_cleanup.go, service_pr_status.go.

Only semantic-equivalent code rewrites: CheckPRWatch hasNew collapsed to a single || expression; three narrow //nolint directives for pre-existing complexity reflagged by golangci's new: true mode after the move.

Rebased onto current main after PR #1057 (sentinel extraction) landed — took its errors.go (now 4 sentinels incl. ErrInvalidToken) and ported the %w wraps for ConfigureToken and AssociateExistingPRByURL into the split files.
2026-05-28 23:29:27 +01:00

106 lines
2.6 KiB
Go

package github
import "time"
func findLatestCommentTime(comments []PRComment) *time.Time {
var latest *time.Time
for _, c := range comments {
t := c.UpdatedAt
if latest == nil || t.After(*latest) {
latest = &t
}
}
return latest
}
// computeOverallCheckStatus reduces per-check runs to a single PR-level status.
// Mirrors GitHub's own UI: skipped/neutral conclusions are ignored; any failing
// terminal state (failure, timed_out, cancelled, action_required) makes the PR
// failed; non-completed checks keep the PR pending.
func computeOverallCheckStatus(checks []CheckRun) string {
if len(checks) == 0 {
return ""
}
hasPending := false
hasPassing := false
for _, c := range checks {
if c.Status != checkStatusCompleted {
hasPending = true
continue
}
switch c.Conclusion {
case checkConclusionFail, checkConclusionTimedOut,
checkConclusionCancelled, checkConclusionActionRequired:
return checkConclusionFail
case checkConclusionSkipped, checkConclusionNeutral:
// ignore — GitHub's UI does
default:
// Treat success and any future unknown terminal conclusion as passing.
// Being permissive preserves the success signal if GitHub introduces
// a new conclusion we haven't mapped yet.
hasPassing = true
}
}
if hasPending {
return checkStatusPending
}
if hasPassing {
return checkStatusSuccess
}
return ""
}
func computeOverallReviewState(reviews []PRReview) string {
if len(reviews) == 0 {
return ""
}
latest := latestReviewByAuthor(reviews)
changesReq := false
allApproved := true
for _, r := range latest {
if r.State == reviewStateChangesRequested {
changesReq = true
}
if r.State != reviewStateApproved {
allApproved = false
}
}
if changesReq {
return computedReviewStateChangesRequested
}
if allApproved {
return computedReviewStateApproved
}
return computedReviewStatePending
}
func countPendingReviews(reviews []PRReview) int {
latest := latestReviewByAuthor(reviews)
count := 0
for _, r := range latest {
if r.State == reviewStatePending || r.State == reviewStateCommented {
count++
}
}
return count
}
func countPendingRequestedReviewers(pr *PR) int {
if pr == nil {
return 0
}
return len(pr.RequestedReviewers)
}
func deriveReviewSyncState(pr *PR, reviews []PRReview) (string, int) {
pendingReviewCount := countPendingRequestedReviewers(pr)
if pendingReviewCount == 0 {
pendingReviewCount = countPendingReviews(reviews)
}
reviewState := computeOverallReviewState(reviews)
if reviewState == "" && pendingReviewCount > 0 {
reviewState = computedReviewStatePending
}
return reviewState, pendingReviewCount
}