diff --git a/models/issues/issue.go b/models/issues/issue.go index e0dcf3d269ee0..9a5f000a00aec 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -1119,6 +1119,7 @@ type IssuesOptions struct { //nolint PosterID int64 MentionedID int64 ReviewRequestedID int64 + ReviewedID int64 SubscriberID int64 MilestoneIDs []int64 ProjectID int64 @@ -1233,6 +1234,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) } @@ -1403,6 +1408,33 @@ 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}), + ), + ), + )), + ) + comment := builder.In("issue.id", builder. + Select("issue_id"). + From("comment"). + Where(builder.Eq{"poster_id": reviewedID}), + ) + return sess.And(notPoster, reviewed, comment) +} + func applySubscribedCondition(sess *xorm.Session, subscriberID int64) *xorm.Session { return sess.And( builder. @@ -1557,6 +1589,7 @@ type IssueStats struct { CreateCount int64 MentionCount int64 ReviewRequestedCount int64 + ReviewedCount int64 } // Filter modes. @@ -1566,6 +1599,7 @@ const ( FilterModeCreate FilterModeMention FilterModeReviewRequested + FilterModeReviewed FilterModeYourRepositories ) @@ -1579,6 +1613,7 @@ type IssueStatsOptions struct { MentionedID int64 PosterID int64 ReviewRequestedID int64 + ReviewedID int64 IsPull util.OptionalBool IssueIDs []int64 } @@ -1617,6 +1652,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 @@ -1674,6 +1710,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) @@ -1814,6 +1854,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}) @@ -1842,6 +1895,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/models/migrations/migrations.go b/models/migrations/migrations.go index 73c44f008a6cc..c7497becd1130 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -459,6 +459,8 @@ var migrations = []Migration{ NewMigration("Add card_type column to project table", v1_19.AddCardTypeToProjectTable), // v242 -> v243 NewMigration("Alter gpg_key_import content TEXT field to MEDIUMTEXT", v1_19.AlterPublicGPGKeyImportContentFieldToMediumText), + // v243 -> v244 + NewMigration("Add exclusive label", v1_19.AddExclusiveLabel), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_19/v244.go b/models/migrations/v1_19/v244.go new file mode 100644 index 0000000000000..55bbfafb2fa4a --- /dev/null +++ b/models/migrations/v1_19/v244.go @@ -0,0 +1,16 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_19 //nolint + +import ( + "xorm.io/xorm" +) + +func AddExclusiveLabel(x *xorm.Engine) error { + type Label struct { + Exclusive bool + } + + return x.Sync(new(Label)) +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 8410fef81e26c..3e5b84df1bf8f 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1322,6 +1322,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 458838b935624..06bf06b4e8472 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 42dcfb382d8de..105db3dcbce1e 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), @@ -2409,6 +2414,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 4f45c1d5c3149..8292b8246b79e 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -366,6 +366,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: @@ -434,6 +436,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 4b55e7bec84e6..47e7999d67392 100644 --- a/templates/repo/issue/list.tmpl +++ b/templates/repo/issue/list.tmpl @@ -165,10 +165,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 8d6a97a713a8a..11c61e8b53a0b 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 00fc3b60c4548..e9ae96f4e2617 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 049b6a1681456..29023d921a086 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