mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(gitsync): enrich PR status with author, base branch, review info (#23038)
## Summary Adds 7 new fields to the PR status stored by gitsync, all sourced from the existing GitHub API calls (**zero additional HTTP requests**): | Field | Source | Purpose | |---|---|---| | `author_login` | `pull.user.login` | PR author username | | `author_avatar_url` | `pull.user.avatar_url` | PR author avatar for UI | | `base_branch` | `pull.base.ref` | Target branch (e.g. `main`) | | `pr_number` | `pull.number` | Explicit PR number | | `commits` | `pull.commits` | Number of commits in PR | | `approved` | Derived from reviews | True when ≥1 approved, no outstanding changes requested | | `reviewer_count` | Derived from reviews | Distinct reviewers with a decisive state | ## Changes - **`gitprovider/gitprovider.go`**: Added 7 fields to `PRStatus` struct. - **`gitprovider/github.go`**: Expanded the anonymous struct in `FetchPullRequestStatus` to decode new JSON fields. Replaced `hasOutstandingChangesRequested()` with `summarizeReviews()` returning a `reviewStats` struct with `changesRequested`, `approved`, and `reviewerCount`. - **Migration 000434**: Adds 7 columns to `chat_diff_statuses`. - **`queries/chats.sql`**: Updated `UpsertChatDiffStatus` INSERT/VALUES/ON CONFLICT. - **`gitsync/gitsync.go`**: Maps new `PRStatus` fields into upsert params. - **`gitsync/worker.go`**: Maps new columns in row-to-model converter. - **`codersdk/chats.go`**: Added fields to SDK `ChatDiffStatus` type. - **`coderd/chats.go`**: Maps new DB fields in `convertChatDiffStatus()`. - Auto-generated: `models.go`, `queries.sql.go`, `dump.sql`, `typesGenerated.ts`.
This commit is contained in:
@@ -265,9 +265,18 @@ func (g *githubProvider) FetchPullRequestStatus(
|
||||
Additions int32 `json:"additions"`
|
||||
Deletions int32 `json:"deletions"`
|
||||
ChangedFiles int32 `json:"changed_files"`
|
||||
Number int `json:"number"`
|
||||
Commits int32 `json:"commits"`
|
||||
Head struct {
|
||||
SHA string `json:"sha"`
|
||||
} `json:"head"`
|
||||
User struct {
|
||||
Login string `json:"login"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
} `json:"user"`
|
||||
Base struct {
|
||||
Ref string `json:"ref"`
|
||||
} `json:"base"`
|
||||
}
|
||||
if err := g.decodeJSON(ctx, pullEndpoint, token, &pull); err != nil {
|
||||
return nil, err
|
||||
@@ -298,6 +307,8 @@ func (g *githubProvider) FetchPullRequestStatus(
|
||||
state = PRStateMerged
|
||||
}
|
||||
|
||||
reviewInfo := summarizeReviews(reviews)
|
||||
|
||||
return &PRStatus{
|
||||
Title: pull.Title,
|
||||
State: state,
|
||||
@@ -308,7 +319,14 @@ func (g *githubProvider) FetchPullRequestStatus(
|
||||
Deletions: pull.Deletions,
|
||||
ChangedFiles: pull.ChangedFiles,
|
||||
},
|
||||
ChangesRequested: hasOutstandingChangesRequested(reviews),
|
||||
ChangesRequested: reviewInfo.changesRequested,
|
||||
Approved: reviewInfo.approved,
|
||||
ReviewerCount: reviewInfo.reviewerCount,
|
||||
AuthorLogin: pull.User.Login,
|
||||
AuthorAvatarURL: pull.User.AvatarURL,
|
||||
BaseBranch: pull.Base.Ref,
|
||||
PRNumber: pull.Number,
|
||||
Commits: pull.Commits,
|
||||
FetchedAt: g.clock.Now().UTC(),
|
||||
}, nil
|
||||
}
|
||||
@@ -495,7 +513,18 @@ func ParseRetryAfter(h http.Header, clk quartz.Clock) time.Duration {
|
||||
return 0
|
||||
}
|
||||
|
||||
func hasOutstandingChangesRequested(
|
||||
// reviewStats holds aggregated review statistics for a PR.
|
||||
type reviewStats struct {
|
||||
changesRequested bool
|
||||
approved bool
|
||||
reviewerCount int32
|
||||
}
|
||||
|
||||
// summarizeReviews extracts review statistics from a list of
|
||||
// reviews. For each reviewer, only the latest decisive review
|
||||
// (by ID) is considered. "Decisive" means APPROVED,
|
||||
// CHANGES_REQUESTED, or DISMISSED.
|
||||
func summarizeReviews(
|
||||
reviews []struct {
|
||||
ID int64 `json:"id"`
|
||||
State string `json:"state"`
|
||||
@@ -503,7 +532,7 @@ func hasOutstandingChangesRequested(
|
||||
Login string `json:"login"`
|
||||
} `json:"user"`
|
||||
},
|
||||
) bool {
|
||||
) reviewStats {
|
||||
type reviewerState struct {
|
||||
reviewID int64
|
||||
state string
|
||||
@@ -533,10 +562,21 @@ func hasOutstandingChangesRequested(
|
||||
}
|
||||
}
|
||||
|
||||
var result reviewStats
|
||||
result.reviewerCount = int32(len(statesByReviewer))
|
||||
|
||||
hasApproval := false
|
||||
for _, state := range statesByReviewer {
|
||||
if state.state == "CHANGES_REQUESTED" {
|
||||
return true
|
||||
result.changesRequested = true
|
||||
}
|
||||
if state.state == "APPROVED" {
|
||||
hasApproval = true
|
||||
}
|
||||
}
|
||||
return false
|
||||
// Approved is true only when at least one reviewer approved
|
||||
// and no reviewer has outstanding changes requested.
|
||||
result.approved = hasApproval && !result.changesRequested
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -79,6 +79,23 @@ type PRStatus struct {
|
||||
// ChangesRequested is a convenience boolean: true if any
|
||||
// reviewer's current state is "changes_requested".
|
||||
ChangesRequested bool
|
||||
// AuthorLogin is the login/username of the PR author.
|
||||
AuthorLogin string
|
||||
// AuthorAvatarURL is the avatar URL of the PR author.
|
||||
AuthorAvatarURL string
|
||||
// BaseBranch is the target branch the PR will merge into.
|
||||
BaseBranch string
|
||||
// PRNumber is the PR number (e.g. 1347).
|
||||
PRNumber int
|
||||
// Commits is the number of commits in the PR.
|
||||
Commits int32
|
||||
// Approved is true when at least one reviewer has approved
|
||||
// and no reviewer has outstanding changes requested.
|
||||
Approved bool
|
||||
// ReviewerCount is the number of distinct reviewers who
|
||||
// have left a decisive review (approved, changes_requested,
|
||||
// or dismissed).
|
||||
ReviewerCount int32
|
||||
// FetchedAt is when this status was fetched.
|
||||
FetchedAt time.Time
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user