Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## Unreleased

- add `Last Reviewer` PR field, who left the most recent submitted review; refreshed on open PRs too

## v0.4.0 (2026-09-23)

- add `Reviewed At` PR field, the date of the most recent submitted review; refreshed on open PRs too
Expand Down
2 changes: 1 addition & 1 deletion cli/cmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ to override the default field list, and --dry-run to preview.`,
PreRunE: ValidateParams([]string{"token", "project-owner", "project-number"}),
RunE: CmdPRsRefresh,
}
refreshCmd.Flags().Bool("include-open", false, "also refresh open prs, limited to fields safe without full pr data (PR#, User, Created At, Open Days, Reviewed By, Approved By, Changes Requested By, Reviewed At)")
refreshCmd.Flags().Bool("include-open", false, "also refresh open prs, limited to fields safe without full pr data (PR#, User, Created At, Open Days, Reviewed By, Approved By, Changes Requested By, Reviewed At, Last Reviewer)")
if err := viper.BindPFlag("include-open", refreshCmd.Flags().Lookup("include-open")); err != nil {
return nil, fmt.Errorf("error binding 'include-open' flag: %w", err)
}
Expand Down
9 changes: 9 additions & 0 deletions cli/cmds_prs_fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,15 @@ var PRFields = map[string]PRFieldDef{
return ctx.PR.ReviewedAt.Format(time.RFC3339)
},
},
"Last Reviewer": {
Type: gh.ItemValueTypeText,
ComputeFn: func(ctx PRFieldContext) any {
if ctx.PR.LastReviewer == "" {
return nil // never reviewed
}
return ctx.PR.LastReviewer
},
},
"Filtered Review Count": {
Type: gh.ItemValueTypeNumber,
ComputeFn: func(ctx PRFieldContext) any {
Expand Down
7 changes: 4 additions & 3 deletions cli/cmds_prs_refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import (
// prRefreshDefaultFields are the fields refreshed by default for closed/merged PRs. Fields needing
// data we don't fetch here (review counts, waiting days) are excluded so we don't overwrite real
// values with zeros; use --pr-populate-fields to override.
var prRefreshDefaultFields = []string{"Status", "PR#", "User", "Open Days", "Created At", "Closed At", "Merged At", "Merged By", "Reviewed By", "Approved By", "Changes Requested By", "Reviewed At", "CI", "Mergeable"}
var prRefreshDefaultFields = []string{"Status", "PR#", "User", "Open Days", "Created At", "Closed At", "Merged At", "Merged By", "Reviewed By", "Approved By", "Changes Requested By", "Reviewed At", "Last Reviewer", "CI", "Mergeable"}

// prRefreshOpenFields are the fields safe to refresh on open PRs with --include-open: the REST
// lookup can't see the review decision, so Status would incorrectly knock "Approved" PRs back
// to "Waiting", and waiting/count data isn't available at all. CI/Mergeable are fetched
// separately via GraphQL when needed.
var prRefreshOpenFields = map[string]bool{"PR#": true, "User": true, "Created At": true, "Open Days": true, "Reviewed By": true, "Approved By": true, "Changes Requested By": true, "Reviewed At": true, "CI": true, "Mergeable": true}
var prRefreshOpenFields = map[string]bool{"PR#": true, "User": true, "Created At": true, "Open Days": true, "Reviewed By": true, "Approved By": true, "Changes Requested By": true, "Reviewed At": true, "Last Reviewer": true, "CI": true, "Mergeable": true}

// CmdPRsRefresh walks the project board itself and refreshes fields on PR items that are now
// closed or merged, rather than syncing PRs from a repo. This catches PRs that were added while
Expand Down Expand Up @@ -67,7 +67,7 @@ func CmdPRsRefresh(_ *cobra.Command, _ []string) error {

needReviews, needChangesRequested, needMergeable := false, false, false
for _, fieldName := range prFields {
if fieldName == "Reviewed By" || fieldName == "Approved By" || fieldName == "Changes Requested By" || fieldName == "Reviewed At" {
if fieldName == "Reviewed By" || fieldName == "Approved By" || fieldName == "Changes Requested By" || fieldName == "Reviewed At" || fieldName == "Last Reviewer" {
needReviews = true
}
if fieldName == "Changes Requested By" {
Expand Down Expand Up @@ -198,6 +198,7 @@ func CmdPRsRefresh(_ *cobra.Command, _ []string) error {
login := review.GetUser().GetLogin()
if review.GetState() != "PENDING" && review.GetSubmittedAt().After(pr.ReviewedAt) {
pr.ReviewedAt = review.GetSubmittedAt().Time
pr.LastReviewer = login
}
switch review.GetState() {
case "APPROVED":
Expand Down
2 changes: 2 additions & 0 deletions lib/gh/pr_gql.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type PullRequest struct {
MergedAt time.Time
MergedBy string
ReviewedAt time.Time // when the most recent submitted review (any state except pending) was left, zero when unreviewed
LastReviewer string // who left that review
Draft bool
Milestone string
Mergeable string // MERGEABLE, CONFLICTING, or UNKNOWN (github may still be computing)
Expand Down Expand Up @@ -271,6 +272,7 @@ func (q pullRequestsQuery) flatten(reviewers map[string]struct{}) []PullRequest
for _, review := range pullRequest.Reviews.Nodes {
if review.State != string(githubv4.PullRequestReviewStatePending) && review.SubmittedAt.After(pr.ReviewedAt) {
pr.ReviewedAt = review.SubmittedAt
pr.LastReviewer = review.Author.Login
}

// requesters ordered by their first change request, comment counts summed across all of them
Expand Down
Loading