mirror of
https://github.com/go-gitea/gitea
synced 2025-07-03 09:07:19 +00:00
Add label/author/assignee filters to the user/org home issue list (#32779)
Replace #26661, fix #25979 Not perfect, but usable and much better than before. Since it is quite complex, I am not quite sure whether there would be any regression, if any, I will fix in first time. I have tested the related pages many times: issue list, milestone issue list, project view, user issue list, org issue list.
This commit is contained in:
@ -21,6 +21,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/web/shared/issue"
|
||||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
@ -334,23 +335,15 @@ func ViewProject(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var labelIDs []int64
|
||||
// 1,-2 means including label 1 and excluding label 2
|
||||
// 0 means issues with no label
|
||||
// blank means labels will not be filtered for issues
|
||||
selectLabels := ctx.FormString("labels")
|
||||
if selectLabels != "" {
|
||||
labelIDs, err = base.StringsToInt64s(strings.Split(selectLabels, ","))
|
||||
if err != nil {
|
||||
ctx.Flash.Error(ctx.Tr("invalid_data", selectLabels), true)
|
||||
}
|
||||
labelIDs := issue.PrepareFilterIssueLabels(ctx, project.RepoID, project.Owner)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
assigneeID := ctx.FormInt64("assignee")
|
||||
assigneeID := ctx.FormInt64("assignee") // TODO: use "optional" but not 0 in the future
|
||||
|
||||
issuesMap, err := issues_model.LoadIssuesFromColumnList(ctx, columns, &issues_model.IssuesOptions{
|
||||
LabelIDs: labelIDs,
|
||||
AssigneeID: assigneeID,
|
||||
AssigneeID: optional.Some(assigneeID),
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadIssuesOfColumns", err)
|
||||
@ -426,8 +419,6 @@ func ViewProject(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
ctx.Data["Assignees"] = shared_user.MakeSelfOnTop(ctx.Doer, assigneeUsers)
|
||||
|
||||
ctx.Data["SelectLabels"] = selectLabels
|
||||
ctx.Data["AssigneeID"] = assigneeID
|
||||
|
||||
project.RenderedContent = templates.NewRenderUtils(ctx).MarkdownToHtml(project.Description)
|
||||
|
@ -17,12 +17,12 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/routers/web/shared/issue"
|
||||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
@ -263,8 +263,10 @@ func getUserIDForFilter(ctx *context.Context, queryName string) int64 {
|
||||
return user.ID
|
||||
}
|
||||
|
||||
// ListIssues list the issues of a repository
|
||||
func ListIssues(ctx *context.Context) {
|
||||
// SearchRepoIssuesJSON lists the issues of a repository
|
||||
// This function was copied from API (decouple the web and API routes),
|
||||
// it is only used by frontend to search some dependency or related issues
|
||||
func SearchRepoIssuesJSON(ctx *context.Context) {
|
||||
before, since, err := context.GetQueryBeforeSince(ctx.Base)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusUnprocessableEntity, err.Error())
|
||||
@ -286,20 +288,11 @@ func ListIssues(ctx *context.Context) {
|
||||
keyword = ""
|
||||
}
|
||||
|
||||
var labelIDs []int64
|
||||
if splitted := strings.Split(ctx.FormString("labels"), ","); len(splitted) > 0 {
|
||||
labelIDs, err = issues_model.GetLabelIDsInRepoByNames(ctx, ctx.Repo.Repository.ID, splitted)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var mileIDs []int64
|
||||
if part := strings.Split(ctx.FormString("milestones"), ","); len(part) > 0 {
|
||||
for i := range part {
|
||||
// uses names and fall back to ids
|
||||
// non existent milestones are discarded
|
||||
// non-existent milestones are discarded
|
||||
mile, err := issues_model.GetMilestoneByRepoIDANDName(ctx, ctx.Repo.Repository.ID, part[i])
|
||||
if err == nil {
|
||||
mileIDs = append(mileIDs, mile.ID)
|
||||
@ -370,17 +363,8 @@ func ListIssues(ctx *context.Context) {
|
||||
if before != 0 {
|
||||
searchOpt.UpdatedBeforeUnix = optional.Some(before)
|
||||
}
|
||||
if len(labelIDs) == 1 && labelIDs[0] == 0 {
|
||||
searchOpt.NoLabelOnly = true
|
||||
} else {
|
||||
for _, labelID := range labelIDs {
|
||||
if labelID > 0 {
|
||||
searchOpt.IncludedLabelIDs = append(searchOpt.IncludedLabelIDs, labelID)
|
||||
} else {
|
||||
searchOpt.ExcludedLabelIDs = append(searchOpt.ExcludedLabelIDs, -labelID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: the "labels" query parameter is never used, so no need to handle it
|
||||
|
||||
if len(mileIDs) == 1 && mileIDs[0] == db.NoConditionID {
|
||||
searchOpt.MilestoneIDs = []int64{0}
|
||||
@ -503,8 +487,8 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
||||
if !util.SliceContainsString(types, viewType, true) {
|
||||
viewType = "all"
|
||||
}
|
||||
// TODO: "assignee" should also use GetFilterUserIDByName in the future to support usernames directly
|
||||
assigneeID := ctx.FormInt64("assignee")
|
||||
|
||||
assigneeID := ctx.FormInt64("assignee") // TODO: use "optional" but not 0 in the future
|
||||
posterUsername := ctx.FormString("poster")
|
||||
posterUserID := shared_user.GetFilterUserIDByName(ctx, posterUsername)
|
||||
var mentionedID, reviewRequestedID, reviewedID int64
|
||||
@ -512,7 +496,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
||||
if ctx.IsSigned {
|
||||
switch viewType {
|
||||
case "created_by":
|
||||
posterUserID = ctx.Doer.ID
|
||||
posterUserID = optional.Some(ctx.Doer.ID)
|
||||
case "mentioned":
|
||||
mentionedID = ctx.Doer.ID
|
||||
case "assigned":
|
||||
@ -525,18 +509,6 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
||||
}
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
var labelIDs []int64
|
||||
// 1,-2 means including label 1 and excluding label 2
|
||||
// 0 means issues with no label
|
||||
// blank means labels will not be filtered for issues
|
||||
selectLabels := ctx.FormString("labels")
|
||||
if selectLabels != "" {
|
||||
labelIDs, err = base.StringsToInt64s(strings.Split(selectLabels, ","))
|
||||
if err != nil {
|
||||
ctx.Flash.Error(ctx.Tr("invalid_data", selectLabels), true)
|
||||
}
|
||||
}
|
||||
|
||||
keyword := strings.Trim(ctx.FormString("q"), " ")
|
||||
if bytes.Contains([]byte(keyword), []byte{0x00}) {
|
||||
keyword = ""
|
||||
@ -547,13 +519,18 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
||||
mileIDs = []int64{milestoneID}
|
||||
}
|
||||
|
||||
labelIDs := issue.PrepareFilterIssueLabels(ctx, repo.ID, ctx.Repo.Owner)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
var issueStats *issues_model.IssueStats
|
||||
statsOpts := &issues_model.IssuesOptions{
|
||||
RepoIDs: []int64{repo.ID},
|
||||
LabelIDs: labelIDs,
|
||||
MilestoneIDs: mileIDs,
|
||||
ProjectID: projectID,
|
||||
AssigneeID: assigneeID,
|
||||
AssigneeID: optional.Some(assigneeID),
|
||||
MentionedID: mentionedID,
|
||||
PosterID: posterUserID,
|
||||
ReviewRequestedID: reviewRequestedID,
|
||||
@ -634,7 +611,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
||||
PageSize: setting.UI.IssuePagingNum,
|
||||
},
|
||||
RepoIDs: []int64{repo.ID},
|
||||
AssigneeID: assigneeID,
|
||||
AssigneeID: optional.Some(assigneeID),
|
||||
PosterID: posterUserID,
|
||||
MentionedID: mentionedID,
|
||||
ReviewRequestedID: reviewRequestedID,
|
||||
@ -709,49 +686,6 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
||||
return
|
||||
}
|
||||
|
||||
labels, err := issues_model.GetLabelsByRepoID(ctx, repo.ID, "", db.ListOptions{})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetLabelsByRepoID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if repo.Owner.IsOrganization() {
|
||||
orgLabels, err := issues_model.GetLabelsByOrgID(ctx, repo.Owner.ID, ctx.FormString("sort"), db.ListOptions{})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetLabelsByOrgID", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["OrgLabels"] = orgLabels
|
||||
labels = append(labels, orgLabels...)
|
||||
}
|
||||
|
||||
// Get the exclusive scope for every label ID
|
||||
labelExclusiveScopes := make([]string, 0, len(labelIDs))
|
||||
for _, labelID := range labelIDs {
|
||||
foundExclusiveScope := false
|
||||
for _, label := range labels {
|
||||
if label.ID == labelID || label.ID == -labelID {
|
||||
labelExclusiveScopes = append(labelExclusiveScopes, label.ExclusiveScope())
|
||||
foundExclusiveScope = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundExclusiveScope {
|
||||
labelExclusiveScopes = append(labelExclusiveScopes, "")
|
||||
}
|
||||
}
|
||||
|
||||
for _, l := range labels {
|
||||
l.LoadSelectedLabelsAfterClick(labelIDs, labelExclusiveScopes)
|
||||
}
|
||||
ctx.Data["Labels"] = labels
|
||||
ctx.Data["NumLabels"] = len(labels)
|
||||
|
||||
if ctx.FormInt64("assignee") == 0 {
|
||||
assigneeID = 0 // Reset ID to prevent unexpected selection of assignee.
|
||||
}
|
||||
|
||||
ctx.Data["IssueRefEndNames"], ctx.Data["IssueRefURLs"] = issue_service.GetRefEndNamesAndURLs(issues, ctx.Repo.RepoLink)
|
||||
|
||||
ctx.Data["ApprovalCounts"] = func(issueID int64, typ string) int64 {
|
||||
@ -792,13 +726,11 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
||||
ctx.Data["OpenCount"] = issueStats.OpenCount
|
||||
ctx.Data["ClosedCount"] = issueStats.ClosedCount
|
||||
ctx.Data["SelLabelIDs"] = labelIDs
|
||||
ctx.Data["SelectLabels"] = selectLabels
|
||||
ctx.Data["ViewType"] = viewType
|
||||
ctx.Data["SortType"] = sortType
|
||||
ctx.Data["MilestoneID"] = milestoneID
|
||||
ctx.Data["ProjectID"] = projectID
|
||||
ctx.Data["AssigneeID"] = assigneeID
|
||||
ctx.Data["PosterUserID"] = posterUserID
|
||||
ctx.Data["PosterUsername"] = posterUsername
|
||||
ctx.Data["Keyword"] = keyword
|
||||
ctx.Data["IsShowClosed"] = isShowClosed
|
||||
@ -810,19 +742,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
||||
default:
|
||||
ctx.Data["State"] = "open"
|
||||
}
|
||||
|
||||
pager.AddParamString("q", keyword)
|
||||
pager.AddParamString("type", viewType)
|
||||
pager.AddParamString("sort", sortType)
|
||||
pager.AddParamString("state", fmt.Sprint(ctx.Data["State"]))
|
||||
pager.AddParamString("labels", fmt.Sprint(selectLabels))
|
||||
pager.AddParamString("milestone", fmt.Sprint(milestoneID))
|
||||
pager.AddParamString("project", fmt.Sprint(projectID))
|
||||
pager.AddParamString("assignee", fmt.Sprint(assigneeID))
|
||||
pager.AddParamString("poster", posterUsername)
|
||||
if showArchivedLabels {
|
||||
pager.AddParamString("archived_labels", "true")
|
||||
}
|
||||
pager.AddParamFromRequest(ctx.Req)
|
||||
ctx.Data["Page"] = pager
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/web/shared/issue"
|
||||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
@ -307,23 +308,13 @@ func ViewProject(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var labelIDs []int64
|
||||
// 1,-2 means including label 1 and excluding label 2
|
||||
// 0 means issues with no label
|
||||
// blank means labels will not be filtered for issues
|
||||
selectLabels := ctx.FormString("labels")
|
||||
if selectLabels != "" {
|
||||
labelIDs, err = base.StringsToInt64s(strings.Split(selectLabels, ","))
|
||||
if err != nil {
|
||||
ctx.Flash.Error(ctx.Tr("invalid_data", selectLabels), true)
|
||||
}
|
||||
}
|
||||
labelIDs := issue.PrepareFilterIssueLabels(ctx, ctx.Repo.Repository.ID, ctx.Repo.Owner)
|
||||
|
||||
assigneeID := ctx.FormInt64("assignee")
|
||||
assigneeID := ctx.FormInt64("assignee") // TODO: use "optional" but not 0 in the future
|
||||
|
||||
issuesMap, err := issues_model.LoadIssuesFromColumnList(ctx, columns, &issues_model.IssuesOptions{
|
||||
LabelIDs: labelIDs,
|
||||
AssigneeID: assigneeID,
|
||||
AssigneeID: optional.Some(assigneeID),
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadIssuesOfColumns", err)
|
||||
@ -409,8 +400,6 @@ func ViewProject(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
ctx.Data["Assignees"] = shared_user.MakeSelfOnTop(ctx.Doer, assigneeUsers)
|
||||
|
||||
ctx.Data["SelectLabels"] = selectLabels
|
||||
ctx.Data["AssigneeID"] = assigneeID
|
||||
|
||||
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository)
|
||||
|
71
routers/web/shared/issue/issue_label.go
Normal file
71
routers/web/shared/issue/issue_label.go
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package issue
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
// PrepareFilterIssueLabels reads the "labels" query parameter, sets `ctx.Data["Labels"]` and `ctx.Data["SelectLabels"]`
|
||||
func PrepareFilterIssueLabels(ctx *context.Context, repoID int64, owner *user_model.User) (labelIDs []int64) {
|
||||
// 1,-2 means including label 1 and excluding label 2
|
||||
// 0 means issues with no label
|
||||
// blank means labels will not be filtered for issues
|
||||
selectLabels := ctx.FormString("labels")
|
||||
if selectLabels != "" {
|
||||
var err error
|
||||
labelIDs, err = base.StringsToInt64s(strings.Split(selectLabels, ","))
|
||||
if err != nil {
|
||||
ctx.Flash.Error(ctx.Tr("invalid_data", selectLabels), true)
|
||||
}
|
||||
}
|
||||
|
||||
var allLabels []*issues_model.Label
|
||||
if repoID != 0 {
|
||||
repoLabels, err := issues_model.GetLabelsByRepoID(ctx, repoID, "", db.ListOptions{})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetLabelsByRepoID", err)
|
||||
return nil
|
||||
}
|
||||
allLabels = append(allLabels, repoLabels...)
|
||||
}
|
||||
|
||||
if owner != nil && owner.IsOrganization() {
|
||||
orgLabels, err := issues_model.GetLabelsByOrgID(ctx, owner.ID, "", db.ListOptions{})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetLabelsByOrgID", err)
|
||||
return nil
|
||||
}
|
||||
allLabels = append(allLabels, orgLabels...)
|
||||
}
|
||||
|
||||
// Get the exclusive scope for every label ID
|
||||
labelExclusiveScopes := make([]string, 0, len(labelIDs))
|
||||
for _, labelID := range labelIDs {
|
||||
foundExclusiveScope := false
|
||||
for _, label := range allLabels {
|
||||
if label.ID == labelID || label.ID == -labelID {
|
||||
labelExclusiveScopes = append(labelExclusiveScopes, label.ExclusiveScope())
|
||||
foundExclusiveScope = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundExclusiveScope {
|
||||
labelExclusiveScopes = append(labelExclusiveScopes, "")
|
||||
}
|
||||
}
|
||||
|
||||
for _, l := range allLabels {
|
||||
l.LoadSelectedLabelsAfterClick(labelIDs, labelExclusiveScopes)
|
||||
}
|
||||
ctx.Data["Labels"] = allLabels
|
||||
ctx.Data["SelectLabels"] = selectLabels
|
||||
return labelIDs
|
||||
}
|
@ -8,7 +8,9 @@ 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 {
|
||||
@ -31,17 +33,20 @@ func MakeSelfOnTop(doer *user.User, users []*user.User) []*user.User {
|
||||
// Before, the "issue filter" passes user ID to query the list, but in many cases, it's impossible to pre-fetch the full user list.
|
||||
// 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.
|
||||
// Old usage: poster=123, new usage: poster=the-username (at the moment, non-existing username is treated as poster=0, not ideal but acceptable)
|
||||
func GetFilterUserIDByName(ctx context.Context, name string) int64 {
|
||||
// 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] {
|
||||
if name == "" {
|
||||
return 0
|
||||
return optional.None[int64]()
|
||||
}
|
||||
u, err := user.GetUserByName(ctx, name)
|
||||
if err != nil {
|
||||
if id, err := strconv.ParseInt(name, 10, 64); err == nil {
|
||||
return id
|
||||
return optional.Some(id)
|
||||
}
|
||||
return 0
|
||||
return optional.Some(db.NonExistingID)
|
||||
}
|
||||
return u.ID
|
||||
return optional.Some(u.ID)
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/routers/web/feed"
|
||||
"code.gitea.io/gitea/routers/web/shared/issue"
|
||||
"code.gitea.io/gitea/routers/web/shared/user"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
feed_service "code.gitea.io/gitea/services/feed"
|
||||
@ -413,6 +414,13 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
viewType = "your_repositories"
|
||||
}
|
||||
|
||||
isPullList := unitType == unit.TypePullRequests
|
||||
opts := &issues_model.IssuesOptions{
|
||||
IsPull: optional.Some(isPullList),
|
||||
SortType: sortType,
|
||||
IsArchived: optional.Some(false),
|
||||
User: ctx.Doer,
|
||||
}
|
||||
// --------------------------------------------------------------------------
|
||||
// Build opts (IssuesOptions), which contains filter information.
|
||||
// Will eventually be used to retrieve issues relevant for the overview page.
|
||||
@ -422,30 +430,24 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
// Get repository IDs where User/Org/Team has access.
|
||||
var team *organization.Team
|
||||
var org *organization.Organization
|
||||
if ctx.Org != nil {
|
||||
org = ctx.Org.Organization
|
||||
team = ctx.Org.Team
|
||||
}
|
||||
if ctx.Org != nil && ctx.Org.Organization != nil {
|
||||
opts.Org = ctx.Org.Organization
|
||||
opts.Team = ctx.Org.Team
|
||||
|
||||
isPullList := unitType == unit.TypePullRequests
|
||||
opts := &issues_model.IssuesOptions{
|
||||
IsPull: optional.Some(isPullList),
|
||||
SortType: sortType,
|
||||
IsArchived: optional.Some(false),
|
||||
Org: org,
|
||||
Team: team,
|
||||
User: ctx.Doer,
|
||||
issue.PrepareFilterIssueLabels(ctx, 0, ctx.Org.Organization.AsUser())
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Get filter by author id & assignee id
|
||||
// FIXME: this feature doesn't work at the moment, because frontend can't use a "user-remote-search" dropdown directly
|
||||
// the existing "/posters" handlers doesn't work for this case, it is unable to list the related users correctly.
|
||||
// In the future, we need something like github: "author:user1" to accept usernames directly.
|
||||
posterUsername := ctx.FormString("poster")
|
||||
ctx.Data["FilterPosterUsername"] = posterUsername
|
||||
opts.PosterID = user.GetFilterUserIDByName(ctx, posterUsername)
|
||||
// TODO: "assignee" should also use GetFilterUserIDByName in the future to support usernames directly
|
||||
opts.AssigneeID, _ = strconv.ParseInt(ctx.FormString("assignee"), 10, 64)
|
||||
assigneeUsername := ctx.FormString("assignee")
|
||||
ctx.Data["FilterAssigneeUsername"] = assigneeUsername
|
||||
opts.AssigneeID = user.GetFilterUserIDByName(ctx, assigneeUsername)
|
||||
|
||||
isFuzzy := ctx.FormBool("fuzzy")
|
||||
|
||||
@ -471,8 +473,8 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
UnitType: unitType,
|
||||
Archived: optional.Some(false),
|
||||
}
|
||||
if team != nil {
|
||||
repoOpts.TeamID = team.ID
|
||||
if opts.Team != nil {
|
||||
repoOpts.TeamID = opts.Team.ID
|
||||
}
|
||||
accessibleRepos := container.Set[int64]{}
|
||||
{
|
||||
@ -500,9 +502,9 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
case issues_model.FilterModeAll:
|
||||
case issues_model.FilterModeYourRepositories:
|
||||
case issues_model.FilterModeAssign:
|
||||
opts.AssigneeID = ctx.Doer.ID
|
||||
opts.AssigneeID = optional.Some(ctx.Doer.ID)
|
||||
case issues_model.FilterModeCreate:
|
||||
opts.PosterID = ctx.Doer.ID
|
||||
opts.PosterID = optional.Some(ctx.Doer.ID)
|
||||
case issues_model.FilterModeMention:
|
||||
opts.MentionedID = ctx.Doer.ID
|
||||
case issues_model.FilterModeReviewRequested:
|
||||
@ -584,10 +586,6 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
// because the doer may create issues or be mentioned in any public repo.
|
||||
// So we need search issues in all public repos.
|
||||
o.AllPublic = ctx.Doer.ID == ctxUser.ID
|
||||
// TODO: to make it work with poster/assignee filter, then these IDs should be kept
|
||||
o.AssigneeID = nil
|
||||
o.PosterID = nil
|
||||
|
||||
o.MentionID = nil
|
||||
o.ReviewRequestedID = nil
|
||||
o.ReviewedID = nil
|
||||
@ -645,10 +643,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
ctx.Data["ViewType"] = viewType
|
||||
ctx.Data["SortType"] = sortType
|
||||
ctx.Data["IsShowClosed"] = isShowClosed
|
||||
ctx.Data["SelectLabels"] = selectedLabels
|
||||
ctx.Data["IsFuzzy"] = isFuzzy
|
||||
ctx.Data["SearchFilterPosterID"] = util.Iif[any](opts.PosterID != 0, opts.PosterID, nil)
|
||||
ctx.Data["SearchFilterAssigneeID"] = util.Iif[any](opts.AssigneeID != 0, opts.AssigneeID, nil)
|
||||
|
||||
if isShowClosed {
|
||||
ctx.Data["State"] = "closed"
|
||||
@ -657,16 +652,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
}
|
||||
|
||||
pager := context.NewPagination(shownIssues, setting.UI.IssuePagingNum, page, 5)
|
||||
pager.AddParamString("q", keyword)
|
||||
pager.AddParamString("type", viewType)
|
||||
pager.AddParamString("sort", sortType)
|
||||
pager.AddParamString("state", fmt.Sprint(ctx.Data["State"]))
|
||||
pager.AddParamString("labels", selectedLabels)
|
||||
pager.AddParamString("fuzzy", fmt.Sprint(isFuzzy))
|
||||
pager.AddParamString("poster", posterUsername)
|
||||
if opts.AssigneeID != 0 {
|
||||
pager.AddParamString("assignee", fmt.Sprint(opts.AssigneeID))
|
||||
}
|
||||
pager.AddParamFromRequest(ctx.Req)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.HTML(http.StatusOK, tplIssues)
|
||||
|
@ -1208,7 +1208,7 @@ func registerRoutes(m *web.Router) {
|
||||
Post(web.Bind(forms.CreateIssueForm{}), repo.NewIssuePost)
|
||||
m.Get("/choose", context.RepoRef(), repo.NewIssueChooseTemplate)
|
||||
})
|
||||
m.Get("/search", repo.ListIssues)
|
||||
m.Get("/search", repo.SearchRepoIssuesJSON)
|
||||
}, context.RepoMustNotBeArchived(), reqRepoIssueReader)
|
||||
|
||||
// FIXME: should use different URLs but mostly same logic for comments of issue and pull request.
|
||||
|
Reference in New Issue
Block a user