mirror of
https://github.com/go-gitea/gitea
synced 2025-07-03 09:07:19 +00:00
Refactor issue list (#32755)
1. add backend support for filtering "poster" and "assignee" * due to the limits, there is no frontend support at the moment 2. rewrite TS code without jquery, now there are 14 jQuery files left:
This commit is contained in:
@ -504,19 +504,16 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
||||
if !util.SliceContainsString(types, viewType, true) {
|
||||
viewType = "all"
|
||||
}
|
||||
|
||||
var (
|
||||
assigneeID = ctx.FormInt64("assignee")
|
||||
posterID = ctx.FormInt64("poster")
|
||||
mentionedID int64
|
||||
reviewRequestedID int64
|
||||
reviewedID int64
|
||||
)
|
||||
// TODO: "assignee" should also use GetFilterUserIDByName in the future to support usernames directly
|
||||
assigneeID := ctx.FormInt64("assignee")
|
||||
posterUsername := ctx.FormString("poster")
|
||||
posterUserID := shared_user.GetFilterUserIDByName(ctx, posterUsername)
|
||||
var mentionedID, reviewRequestedID, reviewedID int64
|
||||
|
||||
if ctx.IsSigned {
|
||||
switch viewType {
|
||||
case "created_by":
|
||||
posterID = ctx.Doer.ID
|
||||
posterUserID = ctx.Doer.ID
|
||||
case "mentioned":
|
||||
mentionedID = ctx.Doer.ID
|
||||
case "assigned":
|
||||
@ -564,7 +561,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
||||
ProjectID: projectID,
|
||||
AssigneeID: assigneeID,
|
||||
MentionedID: mentionedID,
|
||||
PosterID: posterID,
|
||||
PosterID: posterUserID,
|
||||
ReviewRequestedID: reviewRequestedID,
|
||||
ReviewedID: reviewedID,
|
||||
IsPull: isPullOption,
|
||||
@ -646,7 +643,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
||||
},
|
||||
RepoIDs: []int64{repo.ID},
|
||||
AssigneeID: assigneeID,
|
||||
PosterID: posterID,
|
||||
PosterID: posterUserID,
|
||||
MentionedID: mentionedID,
|
||||
ReviewRequestedID: reviewRequestedID,
|
||||
ReviewedID: reviewedID,
|
||||
@ -800,16 +797,16 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
||||
ctx.Data["IssueStats"] = issueStats
|
||||
ctx.Data["OpenCount"] = issueStats.OpenCount
|
||||
ctx.Data["ClosedCount"] = issueStats.ClosedCount
|
||||
linkStr := "%s?q=%s&type=%s&sort=%s&state=%s&labels=%s&milestone=%d&project=%d&assignee=%d&poster=%d&archived=%t"
|
||||
linkStr := "%s?q=%s&type=%s&sort=%s&state=%s&labels=%s&milestone=%d&project=%d&assignee=%d&poster=%v&archived=%t"
|
||||
ctx.Data["AllStatesLink"] = fmt.Sprintf(linkStr, ctx.Link,
|
||||
url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "all", url.QueryEscape(selectLabels),
|
||||
milestoneID, projectID, assigneeID, posterID, archived)
|
||||
milestoneID, projectID, assigneeID, url.QueryEscape(posterUsername), archived)
|
||||
ctx.Data["OpenLink"] = fmt.Sprintf(linkStr, ctx.Link,
|
||||
url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "open", url.QueryEscape(selectLabels),
|
||||
milestoneID, projectID, assigneeID, posterID, archived)
|
||||
milestoneID, projectID, assigneeID, url.QueryEscape(posterUsername), archived)
|
||||
ctx.Data["ClosedLink"] = fmt.Sprintf(linkStr, ctx.Link,
|
||||
url.QueryEscape(keyword), url.QueryEscape(viewType), url.QueryEscape(sortType), "closed", url.QueryEscape(selectLabels),
|
||||
milestoneID, projectID, assigneeID, posterID, archived)
|
||||
milestoneID, projectID, assigneeID, url.QueryEscape(posterUsername), archived)
|
||||
ctx.Data["SelLabelIDs"] = labelIDs
|
||||
ctx.Data["SelectLabels"] = selectLabels
|
||||
ctx.Data["ViewType"] = viewType
|
||||
@ -817,7 +814,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
||||
ctx.Data["MilestoneID"] = milestoneID
|
||||
ctx.Data["ProjectID"] = projectID
|
||||
ctx.Data["AssigneeID"] = assigneeID
|
||||
ctx.Data["PosterID"] = posterID
|
||||
ctx.Data["PosterUsername"] = posterUsername
|
||||
ctx.Data["Keyword"] = keyword
|
||||
ctx.Data["IsShowClosed"] = isShowClosed
|
||||
switch {
|
||||
@ -838,7 +835,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt
|
||||
pager.AddParamString("milestone", fmt.Sprint(milestoneID))
|
||||
pager.AddParamString("project", fmt.Sprint(projectID))
|
||||
pager.AddParamString("assignee", fmt.Sprint(assigneeID))
|
||||
pager.AddParamString("poster", fmt.Sprint(posterID))
|
||||
pager.AddParamString("poster", posterUsername)
|
||||
pager.AddParamString("archived", fmt.Sprint(archived))
|
||||
|
||||
ctx.Data["Page"] = pager
|
||||
|
@ -4,7 +4,9 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/models/user"
|
||||
)
|
||||
@ -24,3 +26,22 @@ func MakeSelfOnTop(doer *user.User, users []*user.User) []*user.User {
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
// GetFilterUserIDByName tries to get the user ID from the given username.
|
||||
// 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 {
|
||||
if name == "" {
|
||||
return 0
|
||||
}
|
||||
u, err := user.GetUserByName(ctx, name)
|
||||
if err != nil {
|
||||
if id, err := strconv.ParseInt(name, 10, 64); err == nil {
|
||||
return id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return u.ID
|
||||
}
|
||||
|
@ -31,7 +31,9 @@ import (
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"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/user"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
feed_service "code.gitea.io/gitea/services/feed"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
@ -375,16 +377,8 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
viewType string
|
||||
sortType = ctx.FormString("sort")
|
||||
filterMode int
|
||||
)
|
||||
|
||||
// Default to recently updated, unlike repository issues list
|
||||
if sortType == "" {
|
||||
sortType = "recentupdate"
|
||||
}
|
||||
sortType := util.IfZero(ctx.FormString("sort"), "recentupdate")
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// Distinguish User from Organization.
|
||||
@ -399,7 +393,8 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
|
||||
// TODO: distinguish during routing
|
||||
|
||||
viewType = ctx.FormString("type")
|
||||
viewType := ctx.FormString("type")
|
||||
var filterMode int
|
||||
switch viewType {
|
||||
case "assigned":
|
||||
filterMode = issues_model.FilterModeAssign
|
||||
@ -443,6 +438,14 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
Team: team,
|
||||
User: ctx.Doer,
|
||||
}
|
||||
// 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")
|
||||
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)
|
||||
|
||||
isFuzzy := ctx.FormBool("fuzzy")
|
||||
|
||||
@ -573,8 +576,22 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
// -------------------------------
|
||||
// Fill stats to post to ctx.Data.
|
||||
// -------------------------------
|
||||
issueStats, err := getUserIssueStats(ctx, ctxUser, filterMode, issue_indexer.ToSearchOptions(keyword, opts).Copy(
|
||||
func(o *issue_indexer.SearchOptions) { o.IsFuzzyKeyword = isFuzzy },
|
||||
issueStats, err := getUserIssueStats(ctx, filterMode, issue_indexer.ToSearchOptions(keyword, opts).Copy(
|
||||
func(o *issue_indexer.SearchOptions) {
|
||||
o.IsFuzzyKeyword = isFuzzy
|
||||
// If the doer is the same as the context user, which means the doer is viewing his own dashboard,
|
||||
// it's not enough to show the repos that the doer owns or has been explicitly granted access to,
|
||||
// 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
|
||||
},
|
||||
))
|
||||
if err != nil {
|
||||
ctx.ServerError("getUserIssueStats", err)
|
||||
@ -630,6 +647,8 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
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"
|
||||
@ -643,7 +662,11 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
|
||||
pager.AddParamString("sort", sortType)
|
||||
pager.AddParamString("state", fmt.Sprint(ctx.Data["State"]))
|
||||
pager.AddParamString("labels", selectedLabels)
|
||||
pager.AddParamString("fuzzy", fmt.Sprintf("%v", isFuzzy))
|
||||
pager.AddParamString("fuzzy", fmt.Sprint(isFuzzy))
|
||||
pager.AddParamString("poster", posterUsername)
|
||||
if opts.AssigneeID != 0 {
|
||||
pager.AddParamString("assignee", fmt.Sprint(opts.AssigneeID))
|
||||
}
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.HTML(http.StatusOK, tplIssues)
|
||||
@ -768,27 +791,10 @@ func UsernameSubRoute(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func getUserIssueStats(ctx *context.Context, ctxUser *user_model.User, filterMode int, opts *issue_indexer.SearchOptions) (*issues_model.IssueStats, error) {
|
||||
func getUserIssueStats(ctx *context.Context, filterMode int, opts *issue_indexer.SearchOptions) (ret *issues_model.IssueStats, err error) {
|
||||
ret = &issues_model.IssueStats{}
|
||||
doerID := ctx.Doer.ID
|
||||
|
||||
opts = opts.Copy(func(o *issue_indexer.SearchOptions) {
|
||||
// If the doer is the same as the context user, which means the doer is viewing his own dashboard,
|
||||
// it's not enough to show the repos that the doer owns or has been explicitly granted access to,
|
||||
// because the doer may create issues or be mentioned in any public repo.
|
||||
// So we need search issues in all public repos.
|
||||
o.AllPublic = doerID == ctxUser.ID
|
||||
o.AssigneeID = nil
|
||||
o.PosterID = nil
|
||||
o.MentionID = nil
|
||||
o.ReviewRequestedID = nil
|
||||
o.ReviewedID = nil
|
||||
})
|
||||
|
||||
var (
|
||||
err error
|
||||
ret = &issues_model.IssueStats{}
|
||||
)
|
||||
|
||||
{
|
||||
openClosedOpts := opts.Copy()
|
||||
switch filterMode {
|
||||
|
Reference in New Issue
Block a user