diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go index 0db668259c..3c0048b29b 100644 --- a/routers/web/org/projects.go +++ b/routers/web/org/projects.go @@ -450,7 +450,7 @@ func ViewProject(ctx *context.Context) { ctx.Data["MilestoneID"] = milestoneID // Get assignees. - assigneeUsers, err := project_service.LoadIssuesAssigneesForProject(ctx, issuesMap) + assigneeUsers, err := project_service.LoadIssuesAssigneesForProject(ctx, project.ID) if err != nil { ctx.ServerError("LoadIssuesAssigneesForProject", err) return diff --git a/routers/web/repo/issue_poster.go b/routers/web/repo/issue_poster.go index 09c941509f..683db527b6 100644 --- a/routers/web/repo/issue_poster.go +++ b/routers/web/repo/issue_poster.go @@ -14,17 +14,6 @@ import ( "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) { isPullList := ctx.PathParam("type") == "pulls" issuePosters(ctx, isPullList) @@ -46,14 +35,5 @@ func issuePosters(ctx *context.Context, isPullList bool) { posters = append(posters, ctx.Doer) } } - - 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) + ctx.JSON(http.StatusOK, shared_user.ToSearchUserResponse(ctx, ctx.Doer, posters)) } diff --git a/routers/web/shared/user/helper.go b/routers/web/shared/user/helper.go index 129b500376..0b3db67091 100644 --- a/routers/web/shared/user/helper.go +++ b/routers/web/shared/user/helper.go @@ -11,22 +11,44 @@ import ( "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 { - if doer != nil { - idx := slices.IndexFunc(users, func(u *user.User) bool { - return u.ID == doer.ID - }) - if idx > 0 { - newUsers := make([]*user.User, len(users)) - newUsers[0] = users[idx] - copy(newUsers[1:], users[:idx]) - copy(newUsers[idx+1:], users[idx+1:]) - return newUsers - } + if doer == nil { + return users + } + idx := slices.IndexFunc(users, func(u *user.User) bool { + return u.ID == doer.ID + }) + if idx > 0 { + newUsers := make([]*user.User, len(users)) + newUsers[0] = users[idx] + copy(newUsers[1:], users[:idx]) + copy(newUsers[idx+1:], users[idx+1:]) + return newUsers } 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. // 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. diff --git a/services/projects/issue.go b/services/projects/issue.go index 01b263760b..ad9abe9d4a 100644 --- a/services/projects/issue.go +++ b/services/projects/issue.go @@ -13,8 +13,9 @@ import ( issues_model "gitea.dev/models/issues" project_model "gitea.dev/models/project" user_model "gitea.dev/models/user" - "gitea.dev/modules/container" "gitea.dev/modules/optional" + + "xorm.io/builder" ) // 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) { - var issueList issues_model.IssueList - for _, colIssues := range issuesMap { - issueList = append(issueList, colIssues...) - } - err := issueList.LoadAssignees(ctx) +func LoadIssuesAssigneesForProject(ctx context.Context, projectID int64) (users []*user_model.User, _ error) { + sub := builder.Select("distinct issue_assignees.assignee_id"). + From("project_issue").Join("INNER", "issue_assignees", "project_issue.issue_id=issue_assignees.issue_id"). + Where(builder.Eq{"project_issue.project_id": projectID}) + err := db.GetEngine(ctx).Table("`user`").Where(builder.In("id", sub)).Find(&users) if err != nil { return nil, err } - users := make([]*user_model.User, 0, len(issueList)) - 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) - }) + slices.SortFunc(users, func(a, b *user_model.User) int { return strings.Compare(a.Name, b.Name) }) return users, nil } diff --git a/services/projects/issue_test.go b/services/projects/issue_test.go index ac0ed2002d..ccd3823f79 100644 --- a/services/projects/issue_test.go +++ b/services/projects/issue_test.go @@ -15,7 +15,6 @@ import ( user_model "gitea.dev/models/user" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func Test_Projects(t *testing.T) { @@ -198,18 +197,4 @@ func Test_Projects(t *testing.T) { 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) - }) } diff --git a/tests/integration/project_test.go b/tests/integration/project_test.go index 5a0c1ef545..3515c57e5e 100644 --- a/tests/integration/project_test.go +++ b/tests/integration/project_test.go @@ -10,12 +10,14 @@ import ( "strings" "testing" + "gitea.dev/models/db" issues_model "gitea.dev/models/issues" project_model "gitea.dev/models/project" repo_model "gitea.dev/models/repo" "gitea.dev/models/unit" "gitea.dev/models/unittest" user_model "gitea.dev/models/user" + project "gitea.dev/services/projects" "gitea.dev/tests" "github.com/PuerkitoBio/goquery" @@ -365,3 +367,28 @@ func TestOrgProjectFilterByMilestone(t *testing.T) { 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) + }) +}