From 10cdcb9ea8077098921d72720f9f36fcfd950452 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Sat, 25 Feb 2023 03:55:50 +0100 Subject: [PATCH] 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. --- models/issues/issue.go | 61 ++++++++++++++++++++++ options/locale/locale_en-US.ini | 1 + routers/api/v1/repo/issue.go | 7 +++ routers/web/repo/issue.go | 10 +++- routers/web/user/home.go | 4 ++ templates/repo/issue/list.tmpl | 3 +- templates/repo/issue/milestone_issues.tmpl | 3 +- templates/swagger/v1_json.tmpl | 6 +++ templates/user/dashboard/issues.tmpl | 12 +++-- 9 files changed, 100 insertions(+), 7 deletions(-) diff --git a/models/issues/issue.go b/models/issues/issue.go index c59e9d14e5..edd74261ec 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -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 } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index c818b0dcc5..2109950ca8 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -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 diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 458838b935..06bf06b4e8 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -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) diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 05ba26a70c..745d6e70a0 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -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()) diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 2593ab148c..a0a5dc3c4b 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -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. diff --git a/templates/repo/issue/list.tmpl b/templates/repo/issue/list.tmpl index 2b1bea822f..23a8a1d0e1 100644 --- a/templates/repo/issue/list.tmpl +++ b/templates/repo/issue/list.tmpl @@ -171,10 +171,11 @@ {{.locale.Tr "repo.issues.filter_type.all_issues"}} {{.locale.Tr "repo.issues.filter_type.assigned_to_you"}} {{.locale.Tr "repo.issues.filter_type.created_by_you"}} - {{.locale.Tr "repo.issues.filter_type.mentioning_you"}} {{if .PageIsPullList}} {{.locale.Tr "repo.issues.filter_type.review_requested"}} + {{.locale.Tr "repo.issues.filter_type.reviewed_by_you"}} {{end}} + {{.locale.Tr "repo.issues.filter_type.mentioning_you"}} {{end}} diff --git a/templates/repo/issue/milestone_issues.tmpl b/templates/repo/issue/milestone_issues.tmpl index fca9597446..d73fb56fbc 100644 --- a/templates/repo/issue/milestone_issues.tmpl +++ b/templates/repo/issue/milestone_issues.tmpl @@ -111,8 +111,9 @@ {{.locale.Tr "repo.issues.filter_type.all_issues"}} {{.locale.Tr "repo.issues.filter_type.assigned_to_you"}} {{.locale.Tr "repo.issues.filter_type.created_by_you"}} - {{.locale.Tr "repo.issues.filter_type.mentioning_you"}} {{.locale.Tr "repo.issues.filter_type.review_requested"}} + {{.locale.Tr "repo.issues.filter_type.reviewed_by_you"}} + {{.locale.Tr "repo.issues.filter_type.mentioning_you"}} {{end}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index de774deaed..0605937599 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -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", diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl index 049b6a1681..29023d921a 100644 --- a/templates/user/dashboard/issues.tmpl +++ b/templates/user/dashboard/issues.tmpl @@ -17,16 +17,20 @@ {{.locale.Tr "repo.issues.filter_type.created_by_you"}} {{CountFmt .IssueStats.CreateCount}} - - {{.locale.Tr "repo.issues.filter_type.mentioning_you"}} - {{CountFmt .IssueStats.MentionCount}} - {{if .PageIsPulls}} {{.locale.Tr "repo.issues.filter_type.review_requested"}} {{CountFmt .IssueStats.ReviewRequestedCount}} + + {{.locale.Tr "repo.issues.filter_type.reviewed_by_you"}} + {{CountFmt .IssueStats.ReviewedCount}} + {{end}} + + {{.locale.Tr "repo.issues.filter_type.mentioning_you"}} + {{CountFmt .IssueStats.MentionCount}} +
All