fix: org project view assignee list (#38357)

fix #38129
This commit is contained in:
wxiaoguang
2026-07-07 14:40:12 +08:00
committed by GitHub
parent 582217a0da
commit 26bff7f47e
6 changed files with 70 additions and 68 deletions
+1 -1
View File
@@ -450,7 +450,7 @@ func ViewProject(ctx *context.Context) {
ctx.Data["MilestoneID"] = milestoneID ctx.Data["MilestoneID"] = milestoneID
// Get assignees. // Get assignees.
assigneeUsers, err := project_service.LoadIssuesAssigneesForProject(ctx, issuesMap) assigneeUsers, err := project_service.LoadIssuesAssigneesForProject(ctx, project.ID)
if err != nil { if err != nil {
ctx.ServerError("LoadIssuesAssigneesForProject", err) ctx.ServerError("LoadIssuesAssigneesForProject", err)
return return
+1 -21
View File
@@ -14,17 +14,6 @@ import (
"gitea.dev/services/context" "gitea.dev/services/context"
) )
type userSearchInfo struct {
UserID int64 `json:"user_id"`
UserName string `json:"username"`
AvatarLink string `json:"avatar_link"`
FullName string `json:"full_name"`
}
type userSearchResponse struct {
Results []*userSearchInfo `json:"results"`
}
func IssuePullPosters(ctx *context.Context) { func IssuePullPosters(ctx *context.Context) {
isPullList := ctx.PathParam("type") == "pulls" isPullList := ctx.PathParam("type") == "pulls"
issuePosters(ctx, isPullList) issuePosters(ctx, isPullList)
@@ -46,14 +35,5 @@ func issuePosters(ctx *context.Context, isPullList bool) {
posters = append(posters, ctx.Doer) posters = append(posters, ctx.Doer)
} }
} }
ctx.JSON(http.StatusOK, shared_user.ToSearchUserResponse(ctx, ctx.Doer, posters))
posters = shared_user.MakeSelfOnTop(ctx.Doer, posters)
resp := &userSearchResponse{}
resp.Results = make([]*userSearchInfo, len(posters))
for i, user := range posters {
resp.Results[i] = &userSearchInfo{UserID: user.ID, UserName: user.Name, AvatarLink: user.AvatarLink(ctx)}
resp.Results[i].FullName = user.FullName
}
ctx.JSON(http.StatusOK, resp)
} }
+33 -11
View File
@@ -11,22 +11,44 @@ import (
"gitea.dev/models/user" "gitea.dev/models/user"
) )
type SearchUserInfo struct {
UserID int64 `json:"user_id"`
UserName string `json:"username"`
AvatarLink string `json:"avatar_link"`
FullName string `json:"full_name"`
}
type SearchUserResponse struct {
Results []*SearchUserInfo `json:"results"`
}
func MakeSelfOnTop(doer *user.User, users []*user.User) []*user.User { func MakeSelfOnTop(doer *user.User, users []*user.User) []*user.User {
if doer != nil { if doer == nil {
idx := slices.IndexFunc(users, func(u *user.User) bool { return users
return u.ID == doer.ID }
}) idx := slices.IndexFunc(users, func(u *user.User) bool {
if idx > 0 { return u.ID == doer.ID
newUsers := make([]*user.User, len(users)) })
newUsers[0] = users[idx] if idx > 0 {
copy(newUsers[1:], users[:idx]) newUsers := make([]*user.User, len(users))
copy(newUsers[idx+1:], users[idx+1:]) newUsers[0] = users[idx]
return newUsers copy(newUsers[1:], users[:idx])
} copy(newUsers[idx+1:], users[idx+1:])
return newUsers
} }
return users return users
} }
func ToSearchUserResponse(ctx context.Context, self *user.User, users []*user.User) (ret *SearchUserResponse) {
infos := MakeSelfOnTop(self, users)
resp := &SearchUserResponse{Results: make([]*SearchUserInfo, len(infos))}
for i, u := range infos {
resp.Results[i] = &SearchUserInfo{UserID: u.ID, UserName: u.Name, AvatarLink: u.AvatarLink(ctx)}
resp.Results[i].FullName = u.FullName
}
return resp
}
// GetFilterUserIDByName tries to get the user ID from the given username. // 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. // 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. // So it's better to make it work like GitHub: users could input username directly.
+8 -20
View File
@@ -13,8 +13,9 @@ import (
issues_model "gitea.dev/models/issues" issues_model "gitea.dev/models/issues"
project_model "gitea.dev/models/project" project_model "gitea.dev/models/project"
user_model "gitea.dev/models/user" user_model "gitea.dev/models/user"
"gitea.dev/modules/container"
"gitea.dev/modules/optional" "gitea.dev/modules/optional"
"xorm.io/builder"
) )
// MoveIssuesOnProjectColumn moves or keeps issues in a column and sorts them inside that column // MoveIssuesOnProjectColumn moves or keeps issues in a column and sorts them inside that column
@@ -100,28 +101,15 @@ func MoveIssuesOnProjectColumn(ctx context.Context, doer *user_model.User, colum
}) })
} }
func LoadIssuesAssigneesForProject(ctx context.Context, issuesMap map[int64]issues_model.IssueList) ([]*user_model.User, error) { func LoadIssuesAssigneesForProject(ctx context.Context, projectID int64) (users []*user_model.User, _ error) {
var issueList issues_model.IssueList sub := builder.Select("distinct issue_assignees.assignee_id").
for _, colIssues := range issuesMap { From("project_issue").Join("INNER", "issue_assignees", "project_issue.issue_id=issue_assignees.issue_id").
issueList = append(issueList, colIssues...) Where(builder.Eq{"project_issue.project_id": projectID})
} err := db.GetEngine(ctx).Table("`user`").Where(builder.In("id", sub)).Find(&users)
err := issueList.LoadAssignees(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
users := make([]*user_model.User, 0, len(issueList)) slices.SortFunc(users, func(a, b *user_model.User) int { return strings.Compare(a.Name, b.Name) })
usersAdded := container.Set[int64]{}
for _, issue := range issueList {
for _, assignee := range issue.Assignees {
if !usersAdded.Contains(assignee.ID) {
usersAdded.Add(assignee.ID)
users = append(users, assignee)
}
}
}
slices.SortFunc(users, func(a, b *user_model.User) int {
return strings.Compare(a.Name, b.Name)
})
return users, nil return users, nil
} }
-15
View File
@@ -15,7 +15,6 @@ import (
user_model "gitea.dev/models/user" user_model "gitea.dev/models/user"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func Test_Projects(t *testing.T) { func Test_Projects(t *testing.T) {
@@ -198,18 +197,4 @@ func Test_Projects(t *testing.T) {
assert.Len(t, columnIssues[3], 1) assert.Len(t, columnIssues[3], 1)
}) })
}) })
t.Run("LoadIssuesAssigneesForProject", func(t *testing.T) {
issuesMap := map[int64]issues_model.IssueList{}
issue1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
issue6 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 6})
issuesMap[1] = issues_model.IssueList{issue1}
issuesMap[2] = issues_model.IssueList{issue6}
assignees, err := LoadIssuesAssigneesForProject(t.Context(), issuesMap)
require.NoError(t, err)
require.Len(t, assignees, 3)
require.Equal(t, "user1", assignees[0].Name)
require.Equal(t, "user10", assignees[1].Name)
require.Equal(t, "user2", assignees[2].Name)
})
} }
+27
View File
@@ -10,12 +10,14 @@ import (
"strings" "strings"
"testing" "testing"
"gitea.dev/models/db"
issues_model "gitea.dev/models/issues" issues_model "gitea.dev/models/issues"
project_model "gitea.dev/models/project" project_model "gitea.dev/models/project"
repo_model "gitea.dev/models/repo" repo_model "gitea.dev/models/repo"
"gitea.dev/models/unit" "gitea.dev/models/unit"
"gitea.dev/models/unittest" "gitea.dev/models/unittest"
user_model "gitea.dev/models/user" user_model "gitea.dev/models/user"
project "gitea.dev/services/projects"
"gitea.dev/tests" "gitea.dev/tests"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
@@ -365,3 +367,28 @@ func TestOrgProjectFilterByMilestone(t *testing.T) {
assert.NotContains(t, issueIDs, issue17.ID) assert.NotContains(t, issueIDs, issue17.ID)
}) })
} }
func TestProjects(t *testing.T) {
defer tests.PrepareTestEnv(t)()
t.Run("LoadIssuesAssigneesForProject", func(t *testing.T) {
_ = db.TruncateBeans(t.Context(), "project_issue", "issue_assignees")
_ = db.Insert(t.Context(),
&project_model.ProjectIssue{ProjectID: 1, IssueID: 1},
&project_model.ProjectIssue{ProjectID: 1, IssueID: 6},
)
_ = db.Insert(t.Context(),
&issues_model.IssueAssignees{IssueID: 1, AssigneeID: 1},
&issues_model.IssueAssignees{IssueID: 1, AssigneeID: 10},
&issues_model.IssueAssignees{IssueID: 1, AssigneeID: 2},
&issues_model.IssueAssignees{IssueID: 6, AssigneeID: 2},
&issues_model.IssueAssignees{IssueID: 6, AssigneeID: 4},
)
assignees, err := project.LoadIssuesAssigneesForProject(t.Context(), 1)
require.NoError(t, err)
require.Len(t, assignees, 4)
require.Equal(t, "user1", assignees[0].Name)
require.Equal(t, "user10", assignees[1].Name)
require.Equal(t, "user2", assignees[2].Name)
require.Equal(t, "user4", assignees[3].Name)
})
}