Skip to content

Commit

Permalink
Add "Reviewed by you" filter for pull requests (#22927)
Browse files Browse the repository at this point in the history
This includes pull requests that you approved, requested changes or
commented on. Currently such pull requests are not visible in any of the
filters on /pulls, while they may need further action like merging, or
prodding the author or reviewers.

Especially when working with a large team on a repository it's helpful
to get a full overview of pull requests that may need your attention,
without having to sift through the complete list.
  • Loading branch information
brechtvl committed Feb 25, 2023
1 parent 843f811 commit 10cdcb9
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 7 deletions.
61 changes: 61 additions & 0 deletions models/issues/issue.go
Expand Up @@ -1148,6 +1148,7 @@ type IssuesOptions struct { //nolint
PosterID int64
MentionedID int64
ReviewRequestedID int64
ReviewedID int64
SubscriberID int64
MilestoneIDs []int64
ProjectID int64
Expand Down Expand Up @@ -1262,6 +1263,10 @@ func (opts *IssuesOptions) setupSessionNoLimit(sess *xorm.Session) {
applyReviewRequestedCondition(sess, opts.ReviewRequestedID)
}

if opts.ReviewedID > 0 {
applyReviewedCondition(sess, opts.ReviewedID)
}

if opts.SubscriberID > 0 {
applySubscribedCondition(sess, opts.SubscriberID)
}
Expand Down Expand Up @@ -1432,6 +1437,36 @@ func applyReviewRequestedCondition(sess *xorm.Session, reviewRequestedID int64)
reviewRequestedID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest, reviewRequestedID)
}

func applyReviewedCondition(sess *xorm.Session, reviewedID int64) *xorm.Session {
// Query for pull requests where you are a reviewer or commenter, excluding
// any pull requests already returned by the the review requested filter.
notPoster := builder.Neq{"issue.poster_id": reviewedID}
reviewed := builder.In("issue.id", builder.
Select("issue_id").
From("review").
Where(builder.And(
builder.Neq{"type": ReviewTypeRequest},
builder.Or(
builder.Eq{"reviewer_id": reviewedID},
builder.In("reviewer_team_id", builder.
Select("team_id").
From("team_user").
Where(builder.Eq{"uid": reviewedID}),
),
),
)),
)
commented := builder.In("issue.id", builder.
Select("issue_id").
From("comment").
Where(builder.And(
builder.Eq{"poster_id": reviewedID},
builder.In("type", CommentTypeComment, CommentTypeCode, CommentTypeReview),
)),
)
return sess.And(notPoster, builder.Or(reviewed, commented))
}

func applySubscribedCondition(sess *xorm.Session, subscriberID int64) *xorm.Session {
return sess.And(
builder.
Expand Down Expand Up @@ -1586,6 +1621,7 @@ type IssueStats struct {
CreateCount int64
MentionCount int64
ReviewRequestedCount int64
ReviewedCount int64
}

// Filter modes.
Expand All @@ -1595,6 +1631,7 @@ const (
FilterModeCreate
FilterModeMention
FilterModeReviewRequested
FilterModeReviewed
FilterModeYourRepositories
)

Expand All @@ -1608,6 +1645,7 @@ type IssueStatsOptions struct {
MentionedID int64
PosterID int64
ReviewRequestedID int64
ReviewedID int64
IsPull util.OptionalBool
IssueIDs []int64
}
Expand Down Expand Up @@ -1646,6 +1684,7 @@ func GetIssueStats(opts *IssueStatsOptions) (*IssueStats, error) {
accum.CreateCount += stats.CreateCount
accum.OpenCount += stats.MentionCount
accum.ReviewRequestedCount += stats.ReviewRequestedCount
accum.ReviewedCount += stats.ReviewedCount
i = chunk
}
return accum, nil
Expand Down Expand Up @@ -1703,6 +1742,10 @@ func getIssueStatsChunk(opts *IssueStatsOptions, issueIDs []int64) (*IssueStats,
applyReviewRequestedCondition(sess, opts.ReviewRequestedID)
}

if opts.ReviewedID > 0 {
applyReviewedCondition(sess, opts.ReviewedID)
}

switch opts.IsPull {
case util.OptionalBoolTrue:
sess.And("issue.is_pull=?", true)
Expand Down Expand Up @@ -1843,6 +1886,19 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
if err != nil {
return nil, err
}
case FilterModeReviewed:
stats.OpenCount, err = applyReviewedCondition(sess(cond), opts.UserID).
And("issue.is_closed = ?", false).
Count(new(Issue))
if err != nil {
return nil, err
}
stats.ClosedCount, err = applyReviewedCondition(sess(cond), opts.UserID).
And("issue.is_closed = ?", true).
Count(new(Issue))
if err != nil {
return nil, err
}
}

cond = cond.And(builder.Eq{"issue.is_closed": opts.IsClosed})
Expand Down Expand Up @@ -1871,6 +1927,11 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
return nil, err
}

stats.ReviewedCount, err = applyReviewedCondition(sess(cond), opts.UserID).Count(new(Issue))
if err != nil {
return nil, err
}

return stats, nil
}

Expand Down
1 change: 1 addition & 0 deletions options/locale/locale_en-US.ini
Expand Up @@ -1323,6 +1323,7 @@ issues.filter_type.assigned_to_you = Assigned to you
issues.filter_type.created_by_you = Created by you
issues.filter_type.mentioning_you = Mentioning you
issues.filter_type.review_requested = Review requested
issues.filter_type.reviewed_by_you = Reviewed by you
issues.filter_sort = Sort
issues.filter_sort.latest = Newest
issues.filter_sort.oldest = Oldest
Expand Down
7 changes: 7 additions & 0 deletions routers/api/v1/repo/issue.go
Expand Up @@ -92,6 +92,10 @@ func SearchIssues(ctx *context.APIContext) {
// in: query
// description: filter pulls requesting your review, default is false
// type: boolean
// - name: reviewed
// in: query
// description: filter pulls reviewed by you, default is false
// type: boolean
// - name: owner
// in: query
// description: filter by owner
Expand Down Expand Up @@ -266,6 +270,9 @@ func SearchIssues(ctx *context.APIContext) {
if ctx.FormBool("review_requested") {
issuesOpt.ReviewRequestedID = ctxUserID
}
if ctx.FormBool("reviewed") {
issuesOpt.ReviewedID = ctxUserID
}

if issues, err = issues_model.Issues(ctx, issuesOpt); err != nil {
ctx.Error(http.StatusInternalServerError, "Issues", err)
Expand Down
10 changes: 9 additions & 1 deletion routers/web/repo/issue.go
Expand Up @@ -138,7 +138,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
var err error
viewType := ctx.FormString("type")
sortType := ctx.FormString("sort")
types := []string{"all", "your_repositories", "assigned", "created_by", "mentioned", "review_requested"}
types := []string{"all", "your_repositories", "assigned", "created_by", "mentioned", "review_requested", "reviewed_by"}
if !util.SliceContainsString(types, viewType, true) {
viewType = "all"
}
Expand All @@ -148,6 +148,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
posterID = ctx.FormInt64("poster")
mentionedID int64
reviewRequestedID int64
reviewedID int64
forceEmpty bool
)

Expand All @@ -161,6 +162,8 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
assigneeID = ctx.Doer.ID
case "review_requested":
reviewRequestedID = ctx.Doer.ID
case "reviewed_by":
reviewedID = ctx.Doer.ID
}
}

Expand Down Expand Up @@ -208,6 +211,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
MentionedID: mentionedID,
PosterID: posterID,
ReviewRequestedID: reviewRequestedID,
ReviewedID: reviewedID,
IsPull: isPullOption,
IssueIDs: issueIDs,
})
Expand Down Expand Up @@ -255,6 +259,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
PosterID: posterID,
MentionedID: mentionedID,
ReviewRequestedID: reviewRequestedID,
ReviewedID: reviewedID,
MilestoneIDs: mileIDs,
ProjectID: projectID,
IsClosed: util.OptionalBoolOf(isShowClosed),
Expand Down Expand Up @@ -2425,6 +2430,9 @@ func SearchIssues(ctx *context.Context) {
if ctx.FormBool("review_requested") {
issuesOpt.ReviewRequestedID = ctxUserID
}
if ctx.FormBool("reviewed") {
issuesOpt.ReviewedID = ctxUserID
}

if issues, err = issues_model.Issues(ctx, issuesOpt); err != nil {
ctx.Error(http.StatusInternalServerError, "Issues", err.Error())
Expand Down
4 changes: 4 additions & 0 deletions routers/web/user/home.go
Expand Up @@ -385,6 +385,8 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
filterMode = issues_model.FilterModeMention
case "review_requested":
filterMode = issues_model.FilterModeReviewRequested
case "reviewed_by":
filterMode = issues_model.FilterModeReviewed
case "your_repositories":
fallthrough
default:
Expand Down Expand Up @@ -453,6 +455,8 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
opts.MentionedID = ctx.Doer.ID
case issues_model.FilterModeReviewRequested:
opts.ReviewRequestedID = ctx.Doer.ID
case issues_model.FilterModeReviewed:
opts.ReviewedID = ctx.Doer.ID
}

// keyword holds the search term entered into the search field.
Expand Down
3 changes: 2 additions & 1 deletion templates/repo/issue/list.tmpl
Expand Up @@ -171,10 +171,11 @@
<a class="{{if eq .ViewType "all"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=all&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.all_issues"}}</a>
<a class="{{if eq .ViewType "assigned"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.assigned_to_you"}}</a>
<a class="{{if eq .ViewType "created_by"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=created_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.created_by_you"}}</a>
<a class="{{if eq .ViewType "mentioned"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=mentioned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.mentioning_you"}}</a>
{{if .PageIsPullList}}
<a class="{{if eq .ViewType "review_requested"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=review_requested&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.review_requested"}}</a>
<a class="{{if eq .ViewType "reviewed_by"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=reviewed_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.reviewed_by_you"}}</a>
{{end}}
<a class="{{if eq .ViewType "mentioned"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=mentioned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.mentioning_you"}}</a>
</div>
</div>
{{end}}
Expand Down
3 changes: 2 additions & 1 deletion templates/repo/issue/milestone_issues.tmpl
Expand Up @@ -111,8 +111,9 @@
<a class="{{if eq .ViewType "all"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=all&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.all_issues"}}</a>
<a class="{{if eq .ViewType "assigned"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.assigned_to_you"}}</a>
<a class="{{if eq .ViewType "created_by"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=created_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.created_by_you"}}</a>
<a class="{{if eq .ViewType "mentioned"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=mentioned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.mentioning_you"}}</a>
<a class="{{if eq .ViewType "review_requested"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=review_requested&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.review_requested"}}</a>
<a class="{{if eq .ViewType "reviewed_by"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=reviewed_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.reviewed_by_you"}}</a>
<a class="{{if eq .ViewType "mentioned"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=mentioned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.mentioning_you"}}</a>
</div>
</div>
{{end}}
Expand Down
6 changes: 6 additions & 0 deletions templates/swagger/v1_json.tmpl
Expand Up @@ -2366,6 +2366,12 @@
"name": "review_requested",
"in": "query"
},
{
"type": "boolean",
"description": "filter pulls reviewed by you, default is false",
"name": "reviewed",
"in": "query"
},
{
"type": "string",
"description": "filter by owner",
Expand Down
12 changes: 8 additions & 4 deletions templates/user/dashboard/issues.tmpl
Expand Up @@ -17,16 +17,20 @@
{{.locale.Tr "repo.issues.filter_type.created_by_you"}}
<strong class="ui right">{{CountFmt .IssueStats.CreateCount}}</strong>
</a>
<a class="{{if eq .ViewType "mentioned"}}ui basic primary button{{end}} item" href="{{.Link}}?type=mentioned&repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state={{.State}}">
{{.locale.Tr "repo.issues.filter_type.mentioning_you"}}
<strong class="ui right">{{CountFmt .IssueStats.MentionCount}}</strong>
</a>
{{if .PageIsPulls}}
<a class="{{if eq .ViewType "review_requested"}}ui basic primary button{{end}} item" href="{{.Link}}?type=review_requested&repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state={{.State}}">
{{.locale.Tr "repo.issues.filter_type.review_requested"}}
<strong class="ui right">{{CountFmt .IssueStats.ReviewRequestedCount}}</strong>
</a>
<a class="{{if eq .ViewType "reviewed_by"}}ui basic primary button{{end}} item" href="{{.Link}}?type=reviewed_by&repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state={{.State}}">
{{.locale.Tr "repo.issues.filter_type.reviewed_by_you"}}
<strong class="ui right">{{CountFmt .IssueStats.ReviewedCount}}</strong>
</a>
{{end}}
<a class="{{if eq .ViewType "mentioned"}}ui basic primary button{{end}} item" href="{{.Link}}?type=mentioned&repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state={{.State}}">
{{.locale.Tr "repo.issues.filter_type.mentioning_you"}}
<strong class="ui right">{{CountFmt .IssueStats.MentionCount}}</strong>
</a>
<div class="ui divider"></div>
<a class="{{if not $.RepoIDs}}ui basic primary button{{end}} repo name item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&q={{$.Keyword}}">
<span class="text truncate">All</span>
Expand Down

0 comments on commit 10cdcb9

Please sign in to comment.