Skip to content

Commit 6eb6783

Browse files
HesterGwxiaoguangsilverwind
authored
Refactor authors dropdown (send get request from frontend to avoid long wait time) (#23890)
Right now the authors search dropdown might take a long time to load if amount of authors is huge. Example: (In the video below, there are about 10000 authors, and it takes about 10 seconds to open the author dropdown) https://user-images.githubusercontent.com/17645053/229422229-98aa9656-3439-4f8c-9f4e-83bd8e2a2557.mov Possible improvements can be made, which will take 2 steps (Thanks to @wolfogre for advice): Step 1: Backend: Add a new api, which returns a limit of 30 posters with matched prefix. Frontend: Change the search behavior from frontend search(fomantic search) to backend search(when input is changed, send a request to get authors matching the current search prefix) Step 2: Backend: Optimize the api in step 1 using indexer to support fuzzy search. This PR is implements the first step. The main changes: 1. Added api: `GET /{type:issues|pulls}/posters` , which return a limit of 30 users with matched prefix (prefix sent as query). If `DEFAULT_SHOW_FULL_NAME` in `custom/conf/app.ini` is set to true, will also include fullnames fuzzy search. 2. Added a tooltip saying "Shows a maximum of 30 users" to the author search dropdown 3. Change the search behavior from frontend search to backend search After: https://user-images.githubusercontent.com/17645053/229430960-f88fafd8-fd5d-4f84-9df2-2677539d5d08.mov Fixes: #22586 --------- Co-authored-by: wxiaoguang <[email protected]> Co-authored-by: silverwind <[email protected]>
1 parent 08f4a9c commit 6eb6783

File tree

18 files changed

+327
-112
lines changed

18 files changed

+327
-112
lines changed

models/repo/user_repo.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,14 +155,25 @@ func GetReviewers(ctx context.Context, repo *Repository, doerID, posterID int64)
155155
return users, db.GetEngine(ctx).Where(cond).OrderBy(user_model.GetOrderByName()).Find(&users)
156156
}
157157

158-
// GetIssuePosters returns all users that have authored an issue/pull request for the given repository
159-
func GetIssuePosters(ctx context.Context, repo *Repository, isPull bool) ([]*user_model.User, error) {
160-
users := make([]*user_model.User, 0, 8)
158+
// GetIssuePostersWithSearch returns users with limit of 30 whose username started with prefix that have authored an issue/pull request for the given repository
159+
// If isShowFullName is set to true, also include full name prefix search
160+
func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string, isShowFullName bool) ([]*user_model.User, error) {
161+
users := make([]*user_model.User, 0, 30)
162+
var prefixCond builder.Cond = builder.Like{"name", search + "%"}
163+
if isShowFullName {
164+
prefixCond = prefixCond.Or(builder.Like{"full_name", "%" + search + "%"})
165+
}
166+
161167
cond := builder.In("`user`.id",
162168
builder.Select("poster_id").From("issue").Where(
163169
builder.Eq{"repo_id": repo.ID}.
164170
And(builder.Eq{"is_pull": isPull}),
165-
).GroupBy("poster_id"),
166-
)
167-
return users, db.GetEngine(ctx).Where(cond).OrderBy(user_model.GetOrderByName()).Find(&users)
171+
).GroupBy("poster_id")).And(prefixCond)
172+
173+
return users, db.GetEngine(ctx).
174+
Where(cond).
175+
Cols("id", "name", "full_name", "avatar", "avatar_email", "use_custom_avatar").
176+
OrderBy("name").
177+
Limit(30).
178+
Find(&users)
168179
}

options/locale/locale_en-US.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,7 @@ delete_preexisting = Delete pre-existing files
933933
delete_preexisting_content = Delete files in %s
934934
delete_preexisting_success = Deleted unadopted files in %s
935935
blame_prior = View blame prior to this change
936+
author_search_tooltip = Shows a maximum of 30 users
936937
937938
transfer.accept = Accept Transfer
938939
transfer.accept_desc = Transfer to "%s"

routers/web/repo/helper.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package repo
5+
6+
import (
7+
"sort"
8+
9+
"code.gitea.io/gitea/models/user"
10+
"code.gitea.io/gitea/modules/context"
11+
)
12+
13+
func makeSelfOnTop(ctx *context.Context, users []*user.User) []*user.User {
14+
if ctx.Doer != nil {
15+
sort.Slice(users, func(i, j int) bool {
16+
if users[i].ID == users[j].ID {
17+
return false
18+
}
19+
return users[i].ID == ctx.Doer.ID // if users[i] is self, put it before others, so less=true
20+
})
21+
}
22+
return users
23+
}

routers/web/repo/helper_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package repo
5+
6+
import (
7+
"testing"
8+
9+
"code.gitea.io/gitea/models/user"
10+
"code.gitea.io/gitea/modules/context"
11+
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestMakeSelfOnTop(t *testing.T) {
16+
users := makeSelfOnTop(&context.Context{}, []*user.User{{ID: 2}, {ID: 1}})
17+
assert.Len(t, users, 2)
18+
assert.EqualValues(t, 2, users[0].ID)
19+
20+
users = makeSelfOnTop(&context.Context{Doer: &user.User{ID: 1}}, []*user.User{{ID: 2}, {ID: 1}})
21+
assert.Len(t, users, 2)
22+
assert.EqualValues(t, 1, users[0].ID)
23+
24+
users = makeSelfOnTop(&context.Context{Doer: &user.User{ID: 2}}, []*user.User{{ID: 2}, {ID: 1}})
25+
assert.Len(t, users, 2)
26+
assert.EqualValues(t, 2, users[0].ID)
27+
}

routers/web/repo/issue.go

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -303,17 +303,12 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
303303
ctx.Data["CommitStatuses"] = commitStatuses
304304

305305
// Get assignees.
306-
ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, repo)
306+
assigneeUsers, err := repo_model.GetRepoAssignees(ctx, repo)
307307
if err != nil {
308-
ctx.ServerError("GetAssignees", err)
309-
return
310-
}
311-
312-
ctx.Data["Posters"], err = repo_model.GetIssuePosters(ctx, repo, isPullOption.IsTrue())
313-
if err != nil {
314-
ctx.ServerError("GetIssuePosters", err)
308+
ctx.ServerError("GetRepoAssignees", err)
315309
return
316310
}
311+
ctx.Data["Assignees"] = makeSelfOnTop(ctx, assigneeUsers)
317312

318313
handleTeamMentions(ctx)
319314
if ctx.Written() {
@@ -479,11 +474,12 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.R
479474
return
480475
}
481476

482-
ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, repo)
477+
assigneeUsers, err := repo_model.GetRepoAssignees(ctx, repo)
483478
if err != nil {
484-
ctx.ServerError("GetAssignees", err)
479+
ctx.ServerError("GetRepoAssignees", err)
485480
return
486481
}
482+
ctx.Data["Assignees"] = makeSelfOnTop(ctx, assigneeUsers)
487483

488484
handleTeamMentions(ctx)
489485
}
@@ -3354,3 +3350,46 @@ func handleTeamMentions(ctx *context.Context) {
33543350
ctx.Data["MentionableTeamsOrg"] = ctx.Repo.Owner.Name
33553351
ctx.Data["MentionableTeamsOrgAvatar"] = ctx.Repo.Owner.AvatarLink(ctx)
33563352
}
3353+
3354+
type userSearchInfo struct {
3355+
UserID int64 `json:"user_id"`
3356+
UserName string `json:"username"`
3357+
AvatarLink string `json:"avatar_link"`
3358+
FullName string `json:"full_name"`
3359+
}
3360+
3361+
type userSearchResponse struct {
3362+
Results []*userSearchInfo `json:"results"`
3363+
}
3364+
3365+
// IssuePosters get posters for current repo's issues/pull requests
3366+
func IssuePosters(ctx *context.Context) {
3367+
repo := ctx.Repo.Repository
3368+
isPullList := ctx.Params(":type") == "pulls"
3369+
search := strings.TrimSpace(ctx.FormString("q"))
3370+
posters, err := repo_model.GetIssuePostersWithSearch(ctx, repo, isPullList, search, setting.UI.DefaultShowFullName)
3371+
if err != nil {
3372+
ctx.JSON(http.StatusInternalServerError, err)
3373+
return
3374+
}
3375+
3376+
if search == "" && ctx.Doer != nil {
3377+
// the returned posters slice only contains limited number of users,
3378+
// to make the current user (doer) can quickly filter their own issues, always add doer to the posters slice
3379+
if !util.SliceContainsFunc(posters, func(user *user_model.User) bool { return user.ID == ctx.Doer.ID }) {
3380+
posters = append(posters, ctx.Doer)
3381+
}
3382+
}
3383+
3384+
posters = makeSelfOnTop(ctx, posters)
3385+
3386+
resp := &userSearchResponse{}
3387+
resp.Results = make([]*userSearchInfo, len(posters))
3388+
for i, user := range posters {
3389+
resp.Results[i] = &userSearchInfo{UserID: user.ID, UserName: user.Name, AvatarLink: user.AvatarLink(ctx)}
3390+
if setting.UI.DefaultShowFullName {
3391+
resp.Results[i].FullName = user.FullName
3392+
}
3393+
}
3394+
ctx.JSON(http.StatusOK, resp)
3395+
}

routers/web/repo/pull.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -792,10 +792,13 @@ func ViewPullFiles(ctx *context.Context) {
792792

793793
setCompareContext(ctx, baseCommit, commit, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
794794

795-
if ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository); err != nil {
796-
ctx.ServerError("GetAssignees", err)
795+
assigneeUsers, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository)
796+
if err != nil {
797+
ctx.ServerError("GetRepoAssignees", err)
797798
return
798799
}
800+
ctx.Data["Assignees"] = makeSelfOnTop(ctx, assigneeUsers)
801+
799802
handleTeamMentions(ctx)
800803
if ctx.Written() {
801804
return

routers/web/repo/release.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -333,13 +333,12 @@ func NewRelease(ctx *context.Context) {
333333
}
334334
}
335335
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
336-
var err error
337-
// Get assignees.
338-
ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository)
336+
assigneeUsers, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository)
339337
if err != nil {
340-
ctx.ServerError("GetAssignees", err)
338+
ctx.ServerError("GetRepoAssignees", err)
341339
return
342340
}
341+
ctx.Data["Assignees"] = makeSelfOnTop(ctx, assigneeUsers)
343342

344343
upload.AddUploadContext(ctx, "release")
345344
ctx.HTML(http.StatusOK, tplReleaseNew)
@@ -496,11 +495,12 @@ func EditRelease(ctx *context.Context) {
496495
ctx.Data["attachments"] = rel.Attachments
497496

498497
// Get assignees.
499-
ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, rel.Repo)
498+
assigneeUsers, err := repo_model.GetRepoAssignees(ctx, rel.Repo)
500499
if err != nil {
501-
ctx.ServerError("GetAssignees", err)
500+
ctx.ServerError("GetRepoAssignees", err)
502501
return
503502
}
503+
ctx.Data["Assignees"] = makeSelfOnTop(ctx, assigneeUsers)
504504

505505
ctx.HTML(http.StatusOK, tplReleaseNew)
506506
}

routers/web/web.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1228,7 +1228,10 @@ func RegisterRoutes(m *web.Route) {
12281228

12291229
m.Group("/{username}/{reponame}", func() {
12301230
m.Group("", func() {
1231-
m.Get("/{type:issues|pulls}", repo.Issues)
1231+
m.Group("/{type:issues|pulls}", func() {
1232+
m.Get("", repo.Issues)
1233+
m.Get("/posters", repo.IssuePosters)
1234+
})
12321235
m.Get("/{type:issues|pulls}/{index}", repo.ViewIssue)
12331236
m.Group("/{type:issues|pulls}/{index}/content-history", func() {
12341237
m.Get("/overview", repo.GetContentHistoryOverview)

templates/repo/issue/list.tmpl

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{{template "base/head" .}}
2-
<div role="main" aria-label="{{.Title}}" class="page-content repository">
2+
<div role="main" aria-label="{{.Title}}" class="page-content repository issue-list">
33
{{template "repo/header" .}}
44
<div class="ui container">
55
<div class="ui three column grid issue-list-headers">
@@ -117,7 +117,11 @@
117117
</div>
118118

119119
<!-- Author -->
120-
<div class="ui {{if not .Posters}}disabled{{end}} dropdown jump item">
120+
<div class="ui dropdown jump item user-remote-search" data-tooltip-content="{{.locale.Tr "repo.author_search_tooltip"}}"
121+
data-search-url="{{$.Link}}/posters"
122+
data-selected-user-id="{{$.PosterID}}"
123+
data-action-jump-url="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={user_id}"
124+
>
121125
<span class="text">
122126
{{.locale.Tr "repo.issues.filter_poster"}}
123127
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
@@ -127,12 +131,7 @@
127131
<i class="icon gt-df gt-ac gt-jc">{{svg "octicon-search" 16}}</i>
128132
<input type="text" placeholder="{{.locale.Tr "repo.issues.filter_poster"}}">
129133
</div>
130-
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}">{{.locale.Tr "repo.issues.filter_poster_no_select"}}</a>
131-
{{range .Posters}}
132-
<a class="{{if eq $.PosterID .ID}}active selected{{end}} item gt-df" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{.ID}}">
133-
{{avatar $.Context .}}{{template "repo/search_name" .}}
134-
</a>
135-
{{end}}
134+
<a class="item" data-value="0">{{.locale.Tr "repo.issues.filter_poster_no_select"}}</a>
136135
</div>
137136
</div>
138137

@@ -150,7 +149,7 @@
150149
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_assginee_no_select"}}</a>
151150
{{range .Assignees}}
152151
<a class="{{if eq $.AssigneeID .ID}}active selected{{end}} item gt-df" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{.ID}}&poster={{$.PosterID}}">
153-
{{avatar $.Context .}}{{template "repo/search_name" .}}
152+
{{avatar $.Context . 20}}{{template "repo/search_name" .}}
154153
</a>
155154
{{end}}
156155
</div>
@@ -299,7 +298,7 @@
299298
</div>
300299
{{range .Assignees}}
301300
<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/assignee">
302-
{{avatar $.Context .}} {{.GetDisplayName}}
301+
{{avatar $.Context . 20}} {{.GetDisplayName}}
303302
</div>
304303
{{end}}
305304
</div>

templates/repo/issue/milestone_issues.tmpl

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{{template "base/head" .}}
2-
<div role="main" aria-label="{{.Title}}" class="page-content repository">
2+
<div role="main" aria-label="{{.Title}}" class="page-content repository milestone-issue-list">
33
{{template "repo/header" .}}
44
<div class="ui container">
55
<div class="ui two column stackable grid">
@@ -71,7 +71,11 @@
7171
</div>
7272

7373
<!-- Author -->
74-
<div class="ui {{if not .Posters}}disabled{{end}} dropdown jump item">
74+
<div class="ui dropdown jump item user-remote-search" data-tooltip-content="{{.locale.Tr "repo.author_search_tooltip"}}"
75+
data-search-url="{{$.RepoLink}}/issues/posters"
76+
data-selected-user-id="{{$.PosterID}}"
77+
data-action-jump-url="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&assignee={{$.AssigneeID}}&poster={user_id}"
78+
>
7579
<span class="text">
7680
{{.locale.Tr "repo.issues.filter_poster"}}
7781
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
@@ -81,12 +85,7 @@
8185
<i class="icon gt-df gt-ac gt-jc">{{svg "octicon-search" 16}}</i>
8286
<input type="text" placeholder="{{.locale.Tr "repo.issues.filter_poster"}}">
8387
</div>
84-
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}">{{.locale.Tr "repo.issues.filter_poster_no_select"}}</a>
85-
{{range .Posters}}
86-
<a class="{{if eq $.PosterID .ID}}active selected{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&assignee={{$.AssigneeID}}&poster={{.ID}}">
87-
{{avatar $.Context .}}{{template "repo/search_name" .}}
88-
</a>
89-
{{end}}
88+
<a class="item" data-value="0">{{.locale.Tr "repo.issues.filter_poster_no_select"}}</a>
9089
</div>
9190
</div>
9291

@@ -104,7 +103,7 @@
104103
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_assginee_no_select"}}</a>
105104
{{range .Assignees}}
106105
<a class="{{if eq $.AssigneeID .ID}}active selected{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&assignee={{.ID}}&poster={{$.PosterID}}">
107-
{{avatar $.Context . 28 "gt-mr-2"}}{{template "repo/search_name" .}}
106+
{{avatar $.Context . 20}}{{template "repo/search_name" .}}
108107
</a>
109108
{{end}}
110109
</div>
@@ -190,7 +189,7 @@
190189
</div>
191190
{{range .Assignees}}
192191
<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/assignee">
193-
{{avatar $.Context . 28 "gt-mr-2"}}
192+
{{avatar $.Context . 20}}
194193
{{.GetDisplayName}}
195194
</div>
196195
{{end}}

templates/repo/issue/view_content/sidebar.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@
254254
{{end}}
255255
<span class="octicon-check {{if not $checked}}invisible{{end}}">{{svg "octicon-check"}}</span>
256256
<span class="text">
257-
{{avatar $.Context . 28 "gt-mr-3"}}{{template "repo/search_name" .}}
257+
{{avatar $.Context . 20 "gt-mr-3"}}{{template "repo/search_name" .}}
258258
</span>
259259
</a>
260260
{{end}}

web_src/css/repository.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@
7272
max-height: 500px;
7373
}
7474

75-
.repository .metas .ui.list.assignees .icon {
76-
line-height: 2em;
75+
.repository .metas .ui.list.assignees .item {
76+
line-height: 2.5em;
7777
}
7878

7979
.repository .metas .ui.list.assignees .teamavatar {

0 commit comments

Comments
 (0)