Add review requested filter on pull request overview (#13701)

* Add review requested filter on pull request overview #13682

fix formatting

* add review_requested filter to /repos/issues/search API endpoint

* only Approve and Reject status should supersede Request status

* add support for team reviews

* refactor: remove duplication of issue filtering conditions
This commit is contained in:
Jimmy Praet 2021-01-17 17:34:19 +01:00 committed by GitHub
parent 872d308892
commit acb1ceb1f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 156 additions and 88 deletions

View file

@ -1090,6 +1090,7 @@ type IssuesOptions struct {
AssigneeID int64 AssigneeID int64
PosterID int64 PosterID int64
MentionedID int64 MentionedID int64
ReviewRequestedID int64
MilestoneIDs []int64 MilestoneIDs []int64
ProjectID int64 ProjectID int64
ProjectBoardID int64 ProjectBoardID int64
@ -1151,8 +1152,7 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) {
} }
if len(opts.RepoIDs) > 0 { if len(opts.RepoIDs) > 0 {
// In case repository IDs are provided but actually no repository has issue. applyReposCondition(sess, opts.RepoIDs)
sess.In("issue.repo_id", opts.RepoIDs)
} }
switch opts.IsClosed { switch opts.IsClosed {
@ -1163,18 +1163,19 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) {
} }
if opts.AssigneeID > 0 { if opts.AssigneeID > 0 {
sess.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id"). applyAssigneeCondition(sess, opts.AssigneeID)
And("issue_assignees.assignee_id = ?", opts.AssigneeID)
} }
if opts.PosterID > 0 { if opts.PosterID > 0 {
sess.And("issue.poster_id=?", opts.PosterID) applyPosterCondition(sess, opts.PosterID)
} }
if opts.MentionedID > 0 { if opts.MentionedID > 0 {
sess.Join("INNER", "issue_user", "issue.id = issue_user.issue_id"). applyMentionedCondition(sess, opts.MentionedID)
And("issue_user.is_mentioned = ?", true). }
And("issue_user.uid = ?", opts.MentionedID)
if opts.ReviewRequestedID > 0 {
applyReviewRequestedCondition(sess, opts.ReviewRequestedID)
} }
if len(opts.MilestoneIDs) > 0 { if len(opts.MilestoneIDs) > 0 {
@ -1232,6 +1233,33 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) {
} }
} }
func applyReposCondition(sess *xorm.Session, repoIDs []int64) *xorm.Session {
return sess.In("issue.repo_id", repoIDs)
}
func applyAssigneeCondition(sess *xorm.Session, assigneeID int64) *xorm.Session {
return sess.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
And("issue_assignees.assignee_id = ?", assigneeID)
}
func applyPosterCondition(sess *xorm.Session, posterID int64) *xorm.Session {
return sess.And("issue.poster_id=?", posterID)
}
func applyMentionedCondition(sess *xorm.Session, mentionedID int64) *xorm.Session {
return sess.Join("INNER", "issue_user", "issue.id = issue_user.issue_id").
And("issue_user.is_mentioned = ?", true).
And("issue_user.uid = ?", mentionedID)
}
func applyReviewRequestedCondition(sess *xorm.Session, reviewRequestedID int64) *xorm.Session {
return sess.Join("INNER", []string{"review", "r"}, "issue.id = r.issue_id").
And("r.type = ?", ReviewTypeRequest).
And("r.reviewer_id = ? and r.id in (select max(id) from review where issue_id = r.issue_id and reviewer_id = r.reviewer_id and type in (?, ?, ?))"+
" or r.reviewer_team_id in (select team_id from team_user where uid = ?)",
reviewRequestedID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest, reviewRequestedID)
}
// CountIssuesByRepo map from repoID to number of issues matching the options // CountIssuesByRepo map from repoID to number of issues matching the options
func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) { func CountIssuesByRepo(opts *IssuesOptions) (map[int64]int64, error) {
sess := x.NewSession() sess := x.NewSession()
@ -1364,6 +1392,7 @@ type IssueStats struct {
AssignCount int64 AssignCount int64
CreateCount int64 CreateCount int64
MentionCount int64 MentionCount int64
ReviewRequestedCount int64
} }
// Filter modes. // Filter modes.
@ -1372,6 +1401,7 @@ const (
FilterModeAssign FilterModeAssign
FilterModeCreate FilterModeCreate
FilterModeMention FilterModeMention
FilterModeReviewRequested
) )
func parseCountResult(results []map[string][]byte) int64 { func parseCountResult(results []map[string][]byte) int64 {
@ -1387,14 +1417,15 @@ func parseCountResult(results []map[string][]byte) int64 {
// IssueStatsOptions contains parameters accepted by GetIssueStats. // IssueStatsOptions contains parameters accepted by GetIssueStats.
type IssueStatsOptions struct { type IssueStatsOptions struct {
RepoID int64 RepoID int64
Labels string Labels string
MilestoneID int64 MilestoneID int64
AssigneeID int64 AssigneeID int64
MentionedID int64 MentionedID int64
PosterID int64 PosterID int64
IsPull util.OptionalBool ReviewRequestedID int64
IssueIDs []int64 IsPull util.OptionalBool
IssueIDs []int64
} }
// GetIssueStats returns issue statistic information by given conditions. // GetIssueStats returns issue statistic information by given conditions.
@ -1423,6 +1454,7 @@ func GetIssueStats(opts *IssueStatsOptions) (*IssueStats, error) {
accum.AssignCount += stats.AssignCount accum.AssignCount += stats.AssignCount
accum.CreateCount += stats.CreateCount accum.CreateCount += stats.CreateCount
accum.OpenCount += stats.MentionCount accum.OpenCount += stats.MentionCount
accum.ReviewRequestedCount += stats.ReviewRequestedCount
i = chunk i = chunk
} }
return accum, nil return accum, nil
@ -1460,18 +1492,19 @@ func getIssueStatsChunk(opts *IssueStatsOptions, issueIDs []int64) (*IssueStats,
} }
if opts.AssigneeID > 0 { if opts.AssigneeID > 0 {
sess.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id"). applyAssigneeCondition(sess, opts.AssigneeID)
And("issue_assignees.assignee_id = ?", opts.AssigneeID)
} }
if opts.PosterID > 0 { if opts.PosterID > 0 {
sess.And("issue.poster_id = ?", opts.PosterID) applyPosterCondition(sess, opts.PosterID)
} }
if opts.MentionedID > 0 { if opts.MentionedID > 0 {
sess.Join("INNER", "issue_user", "issue.id = issue_user.issue_id"). applyMentionedCondition(sess, opts.MentionedID)
And("issue_user.uid = ?", opts.MentionedID). }
And("issue_user.is_mentioned = ?", true)
if opts.ReviewRequestedID > 0 {
applyReviewRequestedCondition(sess, opts.ReviewRequestedID)
} }
switch opts.IsPull { switch opts.IsPull {
@ -1539,57 +1572,66 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
switch opts.FilterMode { switch opts.FilterMode {
case FilterModeAll: case FilterModeAll:
stats.OpenCount, err = sess(cond).And("issue.is_closed = ?", false). stats.OpenCount, err = applyReposCondition(sess(cond), opts.UserRepoIDs).
And(builder.In("issue.repo_id", opts.UserRepoIDs)). And("issue.is_closed = ?", false).
Count(new(Issue)) Count(new(Issue))
if err != nil { if err != nil {
return nil, err return nil, err
} }
stats.ClosedCount, err = sess(cond).And("issue.is_closed = ?", true). stats.ClosedCount, err = applyReposCondition(sess(cond), opts.UserRepoIDs).
And(builder.In("issue.repo_id", opts.UserRepoIDs)). And("issue.is_closed = ?", true).
Count(new(Issue)) Count(new(Issue))
if err != nil { if err != nil {
return nil, err return nil, err
} }
case FilterModeAssign: case FilterModeAssign:
stats.OpenCount, err = sess(cond).And("issue.is_closed = ?", false). stats.OpenCount, err = applyAssigneeCondition(sess(cond), opts.UserID).
Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id"). And("issue.is_closed = ?", false).
And("issue_assignees.assignee_id = ?", opts.UserID).
Count(new(Issue)) Count(new(Issue))
if err != nil { if err != nil {
return nil, err return nil, err
} }
stats.ClosedCount, err = sess(cond).And("issue.is_closed = ?", true). stats.ClosedCount, err = applyAssigneeCondition(sess(cond), opts.UserID).
Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id"). And("issue.is_closed = ?", true).
And("issue_assignees.assignee_id = ?", opts.UserID).
Count(new(Issue)) Count(new(Issue))
if err != nil { if err != nil {
return nil, err return nil, err
} }
case FilterModeCreate: case FilterModeCreate:
stats.OpenCount, err = sess(cond).And("issue.is_closed = ?", false). stats.OpenCount, err = applyPosterCondition(sess(cond), opts.UserID).
And("issue.poster_id = ?", opts.UserID). And("issue.is_closed = ?", false).
Count(new(Issue)) Count(new(Issue))
if err != nil { if err != nil {
return nil, err return nil, err
} }
stats.ClosedCount, err = sess(cond).And("issue.is_closed = ?", true). stats.ClosedCount, err = applyPosterCondition(sess(cond), opts.UserID).
And("issue.poster_id = ?", opts.UserID). And("issue.is_closed = ?", true).
Count(new(Issue)) Count(new(Issue))
if err != nil { if err != nil {
return nil, err return nil, err
} }
case FilterModeMention: case FilterModeMention:
stats.OpenCount, err = sess(cond).And("issue.is_closed = ?", false). stats.OpenCount, err = applyMentionedCondition(sess(cond), opts.UserID).
Join("INNER", "issue_user", "issue.id = issue_user.issue_id and issue_user.is_mentioned = ?", true). And("issue.is_closed = ?", false).
And("issue_user.uid = ?", opts.UserID).
Count(new(Issue)) Count(new(Issue))
if err != nil { if err != nil {
return nil, err return nil, err
} }
stats.ClosedCount, err = sess(cond).And("issue.is_closed = ?", true). stats.ClosedCount, err = applyMentionedCondition(sess(cond), opts.UserID).
Join("INNER", "issue_user", "issue.id = issue_user.issue_id and issue_user.is_mentioned = ?", true). And("issue.is_closed = ?", true).
And("issue_user.uid = ?", opts.UserID). Count(new(Issue))
if err != nil {
return nil, err
}
case FilterModeReviewRequested:
stats.OpenCount, err = applyReviewRequestedCondition(sess(cond), opts.UserID).
And("issue.is_closed = ?", false).
Count(new(Issue))
if err != nil {
return nil, err
}
stats.ClosedCount, err = applyReviewRequestedCondition(sess(cond), opts.UserID).
And("issue.is_closed = ?", true).
Count(new(Issue)) Count(new(Issue))
if err != nil { if err != nil {
return nil, err return nil, err
@ -1597,32 +1639,27 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
} }
cond = cond.And(builder.Eq{"issue.is_closed": opts.IsClosed}) cond = cond.And(builder.Eq{"issue.is_closed": opts.IsClosed})
stats.AssignCount, err = sess(cond). stats.AssignCount, err = applyAssigneeCondition(sess(cond), opts.UserID).Count(new(Issue))
Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
And("issue_assignees.assignee_id = ?", opts.UserID).
Count(new(Issue))
if err != nil { if err != nil {
return nil, err return nil, err
} }
stats.CreateCount, err = sess(cond). stats.CreateCount, err = applyPosterCondition(sess(cond), opts.UserID).Count(new(Issue))
And("poster_id = ?", opts.UserID).
Count(new(Issue))
if err != nil { if err != nil {
return nil, err return nil, err
} }
stats.MentionCount, err = sess(cond). stats.MentionCount, err = applyMentionedCondition(sess(cond), opts.UserID).Count(new(Issue))
Join("INNER", "issue_user", "issue.id = issue_user.issue_id and issue_user.is_mentioned = ?", true).
And("issue_user.uid = ?", opts.UserID).
Count(new(Issue))
if err != nil { if err != nil {
return nil, err return nil, err
} }
stats.YourRepositoriesCount, err = sess(cond). stats.YourRepositoriesCount, err = applyReposCondition(sess(cond), opts.UserRepoIDs).Count(new(Issue))
And(builder.In("issue.repo_id", opts.UserRepoIDs)). if err != nil {
Count(new(Issue)) return nil, err
}
stats.ReviewRequestedCount, err = applyReviewRequestedCondition(sess(cond), opts.UserID).Count(new(Issue))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1646,13 +1683,11 @@ func GetRepoIssueStats(repoID, uid int64, filterMode int, isPull bool) (numOpen
switch filterMode { switch filterMode {
case FilterModeAssign: case FilterModeAssign:
openCountSession.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id"). applyAssigneeCondition(openCountSession, uid)
And("issue_assignees.assignee_id = ?", uid) applyAssigneeCondition(closedCountSession, uid)
closedCountSession.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
And("issue_assignees.assignee_id = ?", uid)
case FilterModeCreate: case FilterModeCreate:
openCountSession.And("poster_id = ?", uid) applyPosterCondition(openCountSession, uid)
closedCountSession.And("poster_id = ?", uid) applyPosterCondition(closedCountSession, uid)
} }
openResult, _ := openCountSession.Count(new(Issue)) openResult, _ := openCountSession.Count(new(Issue))

View file

@ -1030,6 +1030,7 @@ issues.filter_type.all_issues = All issues
issues.filter_type.assigned_to_you = Assigned to you issues.filter_type.assigned_to_you = Assigned to you
issues.filter_type.created_by_you = Created by you issues.filter_type.created_by_you = Created by you
issues.filter_type.mentioning_you = Mentioning you issues.filter_type.mentioning_you = Mentioning you
issues.filter_type.review_requested = Review requested
issues.filter_sort = Sort issues.filter_sort = Sort
issues.filter_sort.latest = Newest issues.filter_sort.latest = Newest
issues.filter_sort.oldest = Oldest issues.filter_sort.oldest = Oldest

View file

@ -79,6 +79,10 @@ func SearchIssues(ctx *context.APIContext) {
// in: query // in: query
// description: filter (issues / pulls) mentioning you, default is false // description: filter (issues / pulls) mentioning you, default is false
// type: boolean // type: boolean
// - name: review_requested
// in: query
// description: filter pulls requesting your review, default is false
// type: boolean
// - name: page // - name: page
// in: query // in: query
// description: page number of results to return (1-based) // description: page number of results to return (1-based)
@ -204,7 +208,7 @@ func SearchIssues(ctx *context.APIContext) {
UpdatedAfterUnix: since, UpdatedAfterUnix: since,
} }
// Filter for: Created by User, Assigned to User, Mentioning User // Filter for: Created by User, Assigned to User, Mentioning User, Review of User Requested
if ctx.QueryBool("created") { if ctx.QueryBool("created") {
issuesOpt.PosterID = ctx.User.ID issuesOpt.PosterID = ctx.User.ID
} }
@ -214,6 +218,9 @@ func SearchIssues(ctx *context.APIContext) {
if ctx.QueryBool("mentioned") { if ctx.QueryBool("mentioned") {
issuesOpt.MentionedID = ctx.User.ID issuesOpt.MentionedID = ctx.User.ID
} }
if ctx.QueryBool("review_requested") {
issuesOpt.ReviewRequestedID = ctx.User.ID
}
if issues, err = models.Issues(issuesOpt); err != nil { if issues, err = models.Issues(issuesOpt); err != nil {
ctx.Error(http.StatusInternalServerError, "Issues", err) ctx.Error(http.StatusInternalServerError, "Issues", err)

View file

@ -113,16 +113,17 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
var err error var err error
viewType := ctx.Query("type") viewType := ctx.Query("type")
sortType := ctx.Query("sort") sortType := ctx.Query("sort")
types := []string{"all", "your_repositories", "assigned", "created_by", "mentioned"} types := []string{"all", "your_repositories", "assigned", "created_by", "mentioned", "review_requested"}
if !util.IsStringInSlice(viewType, types, true) { if !util.IsStringInSlice(viewType, types, true) {
viewType = "all" viewType = "all"
} }
var ( var (
assigneeID = ctx.QueryInt64("assignee") assigneeID = ctx.QueryInt64("assignee")
posterID int64 posterID int64
mentionedID int64 mentionedID int64
forceEmpty bool reviewRequestedID int64
forceEmpty bool
) )
if ctx.IsSigned { if ctx.IsSigned {
@ -133,6 +134,8 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
mentionedID = ctx.User.ID mentionedID = ctx.User.ID
case "assigned": case "assigned":
assigneeID = ctx.User.ID assigneeID = ctx.User.ID
case "review_requested":
reviewRequestedID = ctx.User.ID
} }
} }
@ -169,14 +172,15 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
issueStats = &models.IssueStats{} issueStats = &models.IssueStats{}
} else { } else {
issueStats, err = models.GetIssueStats(&models.IssueStatsOptions{ issueStats, err = models.GetIssueStats(&models.IssueStatsOptions{
RepoID: repo.ID, RepoID: repo.ID,
Labels: selectLabels, Labels: selectLabels,
MilestoneID: milestoneID, MilestoneID: milestoneID,
AssigneeID: assigneeID, AssigneeID: assigneeID,
MentionedID: mentionedID, MentionedID: mentionedID,
PosterID: posterID, PosterID: posterID,
IsPull: isPullOption, ReviewRequestedID: reviewRequestedID,
IssueIDs: issueIDs, IsPull: isPullOption,
IssueIDs: issueIDs,
}) })
if err != nil { if err != nil {
ctx.ServerError("GetIssueStats", err) ctx.ServerError("GetIssueStats", err)
@ -217,17 +221,18 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
Page: pager.Paginater.Current(), Page: pager.Paginater.Current(),
PageSize: setting.UI.IssuePagingNum, PageSize: setting.UI.IssuePagingNum,
}, },
RepoIDs: []int64{repo.ID}, RepoIDs: []int64{repo.ID},
AssigneeID: assigneeID, AssigneeID: assigneeID,
PosterID: posterID, PosterID: posterID,
MentionedID: mentionedID, MentionedID: mentionedID,
MilestoneIDs: mileIDs, ReviewRequestedID: reviewRequestedID,
ProjectID: projectID, MilestoneIDs: mileIDs,
IsClosed: util.OptionalBoolOf(isShowClosed), ProjectID: projectID,
IsPull: isPullOption, IsClosed: util.OptionalBoolOf(isShowClosed),
LabelIDs: labelIDs, IsPull: isPullOption,
SortType: sortType, LabelIDs: labelIDs,
IssueIDs: issueIDs, SortType: sortType,
IssueIDs: issueIDs,
}) })
if err != nil { if err != nil {
ctx.ServerError("Issues", err) ctx.ServerError("Issues", err)

View file

@ -392,6 +392,8 @@ func buildIssueOverview(ctx *context.Context, unitType models.UnitType) {
filterMode = models.FilterModeCreate filterMode = models.FilterModeCreate
case "mentioned": case "mentioned":
filterMode = models.FilterModeMention filterMode = models.FilterModeMention
case "review_requested":
filterMode = models.FilterModeReviewRequested
case "your_repositories": // filterMode already set to All case "your_repositories": // filterMode already set to All
default: default:
viewType = "your_repositories" viewType = "your_repositories"
@ -431,7 +433,9 @@ func buildIssueOverview(ctx *context.Context, unitType models.UnitType) {
case models.FilterModeCreate: case models.FilterModeCreate:
opts.PosterID = ctx.User.ID opts.PosterID = ctx.User.ID
case models.FilterModeMention: case models.FilterModeMention:
opts.MentionedID = ctx.User.ID opts.MentionedID = ctxUser.ID
case models.FilterModeReviewRequested:
opts.ReviewRequestedID = ctxUser.ID
} }
if ctxUser.IsOrganization() { if ctxUser.IsOrganization() {

View file

@ -89,6 +89,9 @@
<a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.assigned_to_you"}}</a> <a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.created_by_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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.mentioning_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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.review_requested"}}</a>
{{end}}
</div> </div>
</div> </div>
{{end}} {{end}}

View file

@ -88,6 +88,7 @@
<a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.assigned_to_you"}}</a> <a class="{{if eq .ViewType "assigned"}}active{{end}} item" href="{{$.Link}}?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}">{{.i18n.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}}">{{.i18n.Tr "repo.issues.filter_type.created_by_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}}">{{.i18n.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}}">{{.i18n.Tr "repo.issues.filter_type.mentioning_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}}">{{.i18n.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}}">{{.i18n.Tr "repo.issues.filter_type.review_requested"}}</a>
</div> </div>
</div> </div>
{{end}} {{end}}

View file

@ -1911,6 +1911,12 @@
"name": "mentioned", "name": "mentioned",
"in": "query" "in": "query"
}, },
{
"type": "boolean",
"description": "filter pulls requesting your review, default is false",
"name": "review_requested",
"in": "query"
},
{ {
"type": "integer", "type": "integer",
"description": "page number of results to return (1-based)", "description": "page number of results to return (1-based)",

View file

@ -21,6 +21,12 @@
{{.i18n.Tr "repo.issues.filter_type.mentioning_you"}} {{.i18n.Tr "repo.issues.filter_type.mentioning_you"}}
<strong class="ui right">{{CountFmt .IssueStats.MentionCount}}</strong> <strong class="ui right">{{CountFmt .IssueStats.MentionCount}}</strong>
</a> </a>
{{if .PageIsPulls}}
<a class="{{if eq .ViewType "review_requested"}}ui basic blue button{{end}} item" href="{{.Link}}?type=review_requested&repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state={{.State}}">
{{.i18n.Tr "repo.issues.filter_type.review_requested"}}
<strong class="ui right">{{CountFmt .IssueStats.ReviewRequestedCount}}</strong>
</a>
{{end}}
<div class="ui divider"></div> <div class="ui divider"></div>
<a class="{{if not $.RepoIDs}}ui basic blue button{{end}} repo name item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&q={{$.Keyword}}"> <a class="{{if not $.RepoIDs}}ui basic blue button{{end}} repo name item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&q={{$.Keyword}}">
<span class="text truncate">All</span> <span class="text truncate">All</span>