forked from NYANDEV/forgejo
Add "Reviewed by you" filter for pull requests (#22927)
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.
This commit is contained in:
parent
843f81113e
commit
10cdcb9ea8
9 changed files with 100 additions and 7 deletions
|
@ -1148,6 +1148,7 @@ type IssuesOptions struct { //nolint
|
|||
PosterID int64
|
||||
MentionedID int64
|
||||
ReviewRequestedID int64
|
||||
ReviewedID int64
|
||||
SubscriberID int64
|
||||
MilestoneIDs []int64
|
||||
ProjectID int64
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -1586,6 +1621,7 @@ type IssueStats struct {
|
|||
CreateCount int64
|
||||
MentionCount int64
|
||||
ReviewRequestedCount int64
|
||||
ReviewedCount int64
|
||||
}
|
||||
|
||||
// Filter modes.
|
||||
|
@ -1595,6 +1631,7 @@ const (
|
|||
FilterModeCreate
|
||||
FilterModeMention
|
||||
FilterModeReviewRequested
|
||||
FilterModeReviewed
|
||||
FilterModeYourRepositories
|
||||
)
|
||||
|
||||
|
@ -1608,6 +1645,7 @@ type IssueStatsOptions struct {
|
|||
MentionedID int64
|
||||
PosterID int64
|
||||
ReviewRequestedID int64
|
||||
ReviewedID int64
|
||||
IsPull util.OptionalBool
|
||||
IssueIDs []int64
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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})
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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),
|
||||
|
@ -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())
|
||||
|
|
|
@ -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:
|
||||
|
@ -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.
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue