1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-22 18:28:37 +00:00

Allow filtering issues by any assignee (#33343)

This is the opposite of the "No assignee" filter, it will match all
issues that have at least one assignee.

Before
![Before
change](https://github.com/user-attachments/assets/4aea194b-9add-4a84-8d6b-61bfd8d9e58e)

After
![After change with any
filter](https://github.com/user-attachments/assets/99f1205d-ba9f-4a0a-a60b-cc1a0c0823fe)

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Andreas Svanberg
2025-03-21 05:25:36 +01:00
committed by GitHub
parent a4df01b580
commit 0da7318cf3
22 changed files with 167 additions and 101 deletions

View File

@@ -347,11 +347,11 @@ func ViewProject(ctx *context.Context) {
if ctx.Written() {
return
}
assigneeID := ctx.FormInt64("assignee") // TODO: use "optional" but not 0 in the future
assigneeID := ctx.FormString("assignee")
opts := issues_model.IssuesOptions{
LabelIDs: labelIDs,
AssigneeID: optional.Some(assigneeID),
AssigneeID: assigneeID,
Owner: project.Owner,
Doer: ctx.Doer,
}

View File

@@ -208,10 +208,10 @@ func SearchIssues(ctx *context.Context) {
if ctx.IsSigned {
ctxUserID := ctx.Doer.ID
if ctx.FormBool("created") {
searchOpt.PosterID = optional.Some(ctxUserID)
searchOpt.PosterID = strconv.FormatInt(ctxUserID, 10)
}
if ctx.FormBool("assigned") {
searchOpt.AssigneeID = optional.Some(ctxUserID)
searchOpt.AssigneeID = strconv.FormatInt(ctxUserID, 10)
}
if ctx.FormBool("mentioned") {
searchOpt.MentionID = optional.Some(ctxUserID)
@@ -373,10 +373,10 @@ func SearchRepoIssuesJSON(ctx *context.Context) {
}
if createdByID > 0 {
searchOpt.PosterID = optional.Some(createdByID)
searchOpt.PosterID = strconv.FormatInt(createdByID, 10)
}
if assignedByID > 0 {
searchOpt.AssigneeID = optional.Some(assignedByID)
searchOpt.AssigneeID = strconv.FormatInt(assignedByID, 10)
}
if mentionedByID > 0 {
searchOpt.MentionID = optional.Some(mentionedByID)
@@ -490,7 +490,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
viewType = "all"
}
assigneeID := ctx.FormInt64("assignee") // TODO: use "optional" but not 0 in the future
assigneeID := ctx.FormString("assignee")
posterUsername := ctx.FormString("poster")
posterUserID := shared_user.GetFilterUserIDByName(ctx, posterUsername)
var mentionedID, reviewRequestedID, reviewedID int64
@@ -498,11 +498,11 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
if ctx.IsSigned {
switch viewType {
case "created_by":
posterUserID = optional.Some(ctx.Doer.ID)
posterUserID = strconv.FormatInt(ctx.Doer.ID, 10)
case "mentioned":
mentionedID = ctx.Doer.ID
case "assigned":
assigneeID = ctx.Doer.ID
assigneeID = fmt.Sprint(ctx.Doer.ID)
case "review_requested":
reviewRequestedID = ctx.Doer.ID
case "reviewed_by":
@@ -532,7 +532,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
LabelIDs: labelIDs,
MilestoneIDs: mileIDs,
ProjectID: projectID,
AssigneeID: optional.Some(assigneeID),
AssigneeID: assigneeID,
MentionedID: mentionedID,
PosterID: posterUserID,
ReviewRequestedID: reviewRequestedID,
@@ -613,7 +613,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
PageSize: setting.UI.IssuePagingNum,
},
RepoIDs: []int64{repo.ID},
AssigneeID: optional.Some(assigneeID),
AssigneeID: assigneeID,
PosterID: posterUserID,
MentionedID: mentionedID,
ReviewRequestedID: reviewRequestedID,

View File

@@ -315,12 +315,12 @@ func ViewProject(ctx *context.Context) {
labelIDs := issue.PrepareFilterIssueLabels(ctx, ctx.Repo.Repository.ID, ctx.Repo.Owner)
assigneeID := ctx.FormInt64("assignee") // TODO: use "optional" but not 0 in the future
assigneeID := ctx.FormString("assignee")
issuesMap, err := project_service.LoadIssuesFromProject(ctx, project, &issues_model.IssuesOptions{
RepoIDs: []int64{ctx.Repo.Repository.ID},
LabelIDs: labelIDs,
AssigneeID: optional.Some(assigneeID),
AssigneeID: assigneeID,
})
if err != nil {
ctx.ServerError("LoadIssuesOfColumns", err)

View File

@@ -8,9 +8,7 @@ import (
"slices"
"strconv"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/optional"
)
func MakeSelfOnTop(doer *user.User, users []*user.User) []*user.User {
@@ -34,19 +32,20 @@ func MakeSelfOnTop(doer *user.User, users []*user.User) []*user.User {
// So it's better to make it work like GitHub: users could input username directly.
// Since it only converts the username to ID directly and is only used internally (to search issues), so no permission check is needed.
// Return values:
// * nil: no filter
// * some(id): match the id, the id could be -1 to match the issues without assignee
// * some(NonExistingID): match no issue (due to the user doesn't exist)
func GetFilterUserIDByName(ctx context.Context, name string) optional.Option[int64] {
// * "": no filter
// * "{the-id}": match the id
// * "(none)": match no issue (due to the user doesn't exist)
func GetFilterUserIDByName(ctx context.Context, name string) string {
if name == "" {
return optional.None[int64]()
return ""
}
u, err := user.GetUserByName(ctx, name)
if err != nil {
if id, err := strconv.ParseInt(name, 10, 64); err == nil {
return optional.Some(id)
return strconv.FormatInt(id, 10)
}
return optional.Some(db.NonExistingID)
// The "(none)" is for internal usage only: when doer tries to search non-existing user, use "(none)" to return empty result.
return "(none)"
}
return optional.Some(u.ID)
return strconv.FormatInt(u.ID, 10)
}

View File

@@ -501,9 +501,9 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
case issues_model.FilterModeAll:
case issues_model.FilterModeYourRepositories:
case issues_model.FilterModeAssign:
opts.AssigneeID = optional.Some(ctx.Doer.ID)
opts.AssigneeID = strconv.FormatInt(ctx.Doer.ID, 10)
case issues_model.FilterModeCreate:
opts.PosterID = optional.Some(ctx.Doer.ID)
opts.PosterID = strconv.FormatInt(ctx.Doer.ID, 10)
case issues_model.FilterModeMention:
opts.MentionedID = ctx.Doer.ID
case issues_model.FilterModeReviewRequested:
@@ -792,9 +792,9 @@ func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMod
case issues_model.FilterModeYourRepositories:
openClosedOpts.AllPublic = false
case issues_model.FilterModeAssign:
openClosedOpts.AssigneeID = optional.Some(doerID)
openClosedOpts.AssigneeID = strconv.FormatInt(doerID, 10)
case issues_model.FilterModeCreate:
openClosedOpts.PosterID = optional.Some(doerID)
openClosedOpts.PosterID = strconv.FormatInt(doerID, 10)
case issues_model.FilterModeMention:
openClosedOpts.MentionID = optional.Some(doerID)
case issues_model.FilterModeReviewRequested:
@@ -816,8 +816,8 @@ func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMod
// Below stats are for the left sidebar
opts = opts.Copy(func(o *issue_indexer.SearchOptions) {
o.AssigneeID = nil
o.PosterID = nil
o.AssigneeID = ""
o.PosterID = ""
o.MentionID = nil
o.ReviewRequestedID = nil
o.ReviewedID = nil
@@ -827,11 +827,11 @@ func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMod
if err != nil {
return nil, err
}
ret.AssignCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.AssigneeID = optional.Some(doerID) }))
ret.AssignCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.AssigneeID = strconv.FormatInt(doerID, 10) }))
if err != nil {
return nil, err
}
ret.CreateCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.PosterID = optional.Some(doerID) }))
ret.CreateCount, err = issue_indexer.CountIssues(ctx, opts.Copy(func(o *issue_indexer.SearchOptions) { o.PosterID = strconv.FormatInt(doerID, 10) }))
if err != nil {
return nil, err
}