Skip to content

Commit ea20ada

Browse files
authored
feat(repo): support search repository by topic name (#4505)
* feat(repo): support search repository by topic name
1 parent 7dd93b2 commit ea20ada

17 files changed

+134
-26
lines changed

integrations/api_admin_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import (
99
"net/http"
1010
"testing"
1111

12-
"github.com/stretchr/testify/assert"
13-
1412
"code.gitea.io/gitea/models"
1513
api "code.gitea.io/sdk/gitea"
14+
15+
"github.com/stretchr/testify/assert"
1616
)
1717

1818
func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) {

integrations/api_issue_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
package integrations
66

77
import (
8+
"fmt"
89
"net/http"
910
"testing"
1011

1112
"code.gitea.io/gitea/models"
1213
api "code.gitea.io/sdk/gitea"
1314

14-
"fmt"
1515
"github.com/stretchr/testify/assert"
1616
)
1717

integrations/api_repo_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ func TestAPISearchRepo(t *testing.T) {
6767
expectedResults
6868
}{
6969
{name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50", expectedResults: expectedResults{
70-
nil: {count: 17},
71-
user: {count: 17},
72-
user2: {count: 17}},
70+
nil: {count: 19},
71+
user: {count: 19},
72+
user2: {count: 19}},
7373
},
7474
{name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10", expectedResults: expectedResults{
7575
nil: {count: 10},

models/fixtures/repo_topic.yml

+8
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,11 @@
99
-
1010
repo_id: 1
1111
topic_id: 3
12+
13+
-
14+
repo_id: 33
15+
topic_id: 1
16+
17+
-
18+
repo_id: 33
19+
topic_id: 4

models/fixtures/repository.yml

+22
Original file line numberDiff line numberDiff line change
@@ -407,3 +407,25 @@
407407
lower_name: utf8
408408
name: utf8
409409
is_private: false
410+
411+
-
412+
id: 34
413+
owner_id: 21
414+
lower_name: golang
415+
name: golang
416+
is_private: false
417+
num_stars: 0
418+
num_forks: 0
419+
num_issues: 0
420+
is_mirror: false
421+
422+
-
423+
id: 35
424+
owner_id: 21
425+
lower_name: graphql
426+
name: graphql
427+
is_private: false
428+
num_stars: 0
429+
num_forks: 0
430+
num_issues: 0
431+
is_mirror: false

models/fixtures/topic.yml

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
-
22
id: 1
33
name: golang
4-
repo_count: 1
4+
repo_count: 2
55

66
-
77
id: 2
@@ -11,3 +11,7 @@
1111
- id: 3
1212
name: SQL
1313
repo_count: 1
14+
15+
- id: 4
16+
name: graphql
17+
repo_count: 1

models/fixtures/user.yml

+15
Original file line numberDiff line numberDiff line change
@@ -314,3 +314,18 @@
314314
avatar_email: [email protected]
315315
num_repos: 4
316316
is_active: true
317+
318+
-
319+
id: 21
320+
lower_name: user21
321+
name: user21
322+
full_name: User 21
323+
324+
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
325+
type: 0 # individual
326+
salt: ZogKvWdyEx
327+
is_admin: false
328+
avatar: avatar21
329+
avatar_email: [email protected]
330+
num_repos: 2
331+
is_active: true

models/repo_list.go

+30-3
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ type SearchRepoOptions struct {
131131
// True -> include just mirrors
132132
// False -> include just non-mirrors
133133
Mirror util.OptionalBool
134+
// only search topic name
135+
TopicOnly bool
134136
}
135137

136138
//SearchOrderBy is used to sort the result
@@ -184,7 +186,7 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
184186

185187
if opts.Collaborate != util.OptionalBoolFalse {
186188
collaborateCond := builder.And(
187-
builder.Expr("id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)", opts.OwnerID),
189+
builder.Expr("repository.id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)", opts.OwnerID),
188190
builder.Neq{"owner_id": opts.OwnerID})
189191
if !opts.Private {
190192
collaborateCond = collaborateCond.And(builder.Expr("owner_id NOT IN (SELECT org_id FROM org_user WHERE org_user.uid = ? AND org_user.is_public = ?)", opts.OwnerID, false))
@@ -202,7 +204,14 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
202204
}
203205

204206
if opts.Keyword != "" {
205-
cond = cond.And(builder.Like{"lower_name", strings.ToLower(opts.Keyword)})
207+
var keywordCond = builder.NewCond()
208+
if opts.TopicOnly {
209+
keywordCond = keywordCond.Or(builder.Like{"topic.name", strings.ToLower(opts.Keyword)})
210+
} else {
211+
keywordCond = keywordCond.Or(builder.Like{"lower_name", strings.ToLower(opts.Keyword)})
212+
keywordCond = keywordCond.Or(builder.Like{"topic.name", strings.ToLower(opts.Keyword)})
213+
}
214+
cond = cond.And(keywordCond)
206215
}
207216

208217
if opts.Fork != util.OptionalBoolNone {
@@ -224,9 +233,15 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
224233
sess.Join("INNER", "star", "star.repo_id = repository.id")
225234
}
226235

236+
if opts.Keyword != "" {
237+
sess.Join("LEFT", "repo_topic", "repo_topic.repo_id = repository.id")
238+
sess.Join("LEFT", "topic", "repo_topic.topic_id = topic.id")
239+
}
240+
227241
count, err := sess.
228242
Where(cond).
229243
Count(new(Repository))
244+
230245
if err != nil {
231246
return nil, 0, fmt.Errorf("Count: %v", err)
232247
}
@@ -236,11 +251,23 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
236251
sess.Join("INNER", "star", "star.repo_id = repository.id")
237252
}
238253

254+
if opts.Keyword != "" {
255+
sess.Join("LEFT", "repo_topic", "repo_topic.repo_id = repository.id")
256+
sess.Join("LEFT", "topic", "repo_topic.topic_id = topic.id")
257+
}
258+
259+
if opts.Keyword != "" {
260+
sess.Select("repository.*")
261+
sess.GroupBy("repository.id")
262+
sess.OrderBy("repository." + opts.OrderBy.String())
263+
} else {
264+
sess.OrderBy(opts.OrderBy.String())
265+
}
266+
239267
repos := make(RepositoryList, 0, opts.PageSize)
240268
if err = sess.
241269
Where(cond).
242270
Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
243-
OrderBy(opts.OrderBy.String()).
244271
Find(&repos); err != nil {
245272
return nil, 0, fmt.Errorf("Repo: %v", err)
246273
}

models/repo_list_test.go

+28-3
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,10 @@ func TestSearchRepositoryByName(t *testing.T) {
147147
count: 14},
148148
{name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
149149
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true},
150-
count: 17},
150+
count: 19},
151151
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
152152
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
153-
count: 21},
153+
count: 23},
154154
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
155155
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
156156
count: 13},
@@ -159,7 +159,7 @@ func TestSearchRepositoryByName(t *testing.T) {
159159
count: 11},
160160
{name: "AllPublic/PublicRepositoriesOfOrganization",
161161
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse},
162-
count: 17},
162+
count: 19},
163163
}
164164

165165
for _, testCase := range testCases {
@@ -222,3 +222,28 @@ func TestSearchRepositoryByName(t *testing.T) {
222222
})
223223
}
224224
}
225+
226+
func TestSearchRepositoryByTopicName(t *testing.T) {
227+
assert.NoError(t, PrepareTestDatabase())
228+
229+
testCases := []struct {
230+
name string
231+
opts *SearchRepoOptions
232+
count int
233+
}{
234+
{name: "AllPublic/SearchPublicRepositoriesFromTopicAndName",
235+
opts: &SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql"},
236+
count: 2},
237+
{name: "AllPublic/OnlySearchPublicRepositoriesFromTopic",
238+
opts: &SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql", TopicOnly: true},
239+
count: 1},
240+
}
241+
242+
for _, testCase := range testCases {
243+
t.Run(testCase.name, func(t *testing.T) {
244+
_, count, err := SearchRepositoryByName(testCase.opts)
245+
assert.NoError(t, err)
246+
assert.Equal(t, int64(testCase.count), count)
247+
})
248+
}
249+
}

models/ssh_key.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ import (
1818
"sync"
1919
"time"
2020

21-
"github.com/Unknwon/com"
22-
"github.com/go-xorm/xorm"
23-
"golang.org/x/crypto/ssh"
24-
2521
"code.gitea.io/gitea/modules/log"
2622
"code.gitea.io/gitea/modules/process"
2723
"code.gitea.io/gitea/modules/setting"
2824
"code.gitea.io/gitea/modules/util"
25+
26+
"github.com/Unknwon/com"
27+
"github.com/go-xorm/xorm"
28+
"golang.org/x/crypto/ssh"
2929
)
3030

3131
const (

models/topic_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func TestAddTopic(t *testing.T) {
1515

1616
topics, err := FindTopics(&FindTopicOptions{})
1717
assert.NoError(t, err)
18-
assert.EqualValues(t, 3, len(topics))
18+
assert.EqualValues(t, 4, len(topics))
1919

2020
topics, err = FindTopics(&FindTopicOptions{
2121
Limit: 2,
@@ -32,7 +32,7 @@ func TestAddTopic(t *testing.T) {
3232
assert.NoError(t, SaveTopics(2, "golang"))
3333
topics, err = FindTopics(&FindTopicOptions{})
3434
assert.NoError(t, err)
35-
assert.EqualValues(t, 3, len(topics))
35+
assert.EqualValues(t, 4, len(topics))
3636

3737
topics, err = FindTopics(&FindTopicOptions{
3838
RepoID: 2,
@@ -47,7 +47,7 @@ func TestAddTopic(t *testing.T) {
4747

4848
topics, err = FindTopics(&FindTopicOptions{})
4949
assert.NoError(t, err)
50-
assert.EqualValues(t, 4, len(topics))
50+
assert.EqualValues(t, 5, len(topics))
5151

5252
topics, err = FindTopics(&FindTopicOptions{
5353
RepoID: 2,

models/user_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,13 @@ func TestSearchUsers(t *testing.T) {
7777
}
7878

7979
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1},
80-
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20})
80+
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21})
8181

8282
testUserSuccess(&SearchUserOptions{Page: 1, IsActive: util.OptionalBoolFalse},
8383
[]int64{9})
8484

8585
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
86-
[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20})
86+
[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21})
8787

8888
testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
8989
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})

routers/api/v1/repo/repo.go

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ func Search(ctx *context.APIContext) {
9191
OwnerID: ctx.QueryInt64("uid"),
9292
Page: ctx.QueryInt("page"),
9393
PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")),
94+
TopicOnly: ctx.QueryBool("topic"),
9495
Collaborate: util.OptionalBoolNone,
9596
}
9697

routers/home.go

+2
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
122122
}
123123

124124
keyword := strings.Trim(ctx.Query("q"), " ")
125+
topicOnly := ctx.QueryBool("topic")
125126

126127
repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{
127128
Page: page,
@@ -131,6 +132,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
131132
Keyword: keyword,
132133
OwnerID: opts.OwnerID,
133134
AllPublic: true,
135+
TopicOnly: topicOnly,
134136
})
135137
if err != nil {
136138
ctx.ServerError("SearchRepositoryByName", err)

routers/user/profile.go

+4
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ func Profile(ctx *context.Context) {
105105
page = 1
106106
}
107107

108+
topicOnly := ctx.QueryBool("topic")
109+
108110
var (
109111
repos []*models.Repository
110112
count int64
@@ -174,6 +176,7 @@ func Profile(ctx *context.Context) {
174176
PageSize: setting.UI.User.RepoPagingNum,
175177
Starred: true,
176178
Collaborate: util.OptionalBoolFalse,
179+
TopicOnly: topicOnly,
177180
})
178181
if err != nil {
179182
ctx.ServerError("SearchRepositoryByName", err)
@@ -217,6 +220,7 @@ func Profile(ctx *context.Context) {
217220
IsProfile: true,
218221
PageSize: setting.UI.User.RepoPagingNum,
219222
Collaborate: util.OptionalBoolFalse,
223+
TopicOnly: topicOnly,
220224
})
221225
if err != nil {
222226
ctx.ServerError("SearchRepositoryByName", err)

templates/explore/repo_list.tmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
{{if .Topics }}
2121
<div>
2222
{{range .Topics}}
23-
{{if ne . "" }}<div class="ui green basic label topic">{{.}}</div>{{end}}
23+
{{if ne . "" }}<a href="/explore/repos?q={{.}}&topic=1"><div class="ui green basic label topic">{{.}}</div></a>{{end}}
2424
{{end}}
2525
</div>
2626
{{end}}

templates/repo/home.tmpl

+3-3
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
{{end}}
2525
</div>
2626
<div class="ui repo-topic" id="repo-topic">
27-
{{range .Topics}}<div class="ui green basic label topic" style="cursor:pointer;">{{.Name}}</div>{{end}}
28-
{{if .IsRepositoryAdmin}}<a id="manage_topic" style="cursor:pointer;margin-left:10px;">{{.i18n.Tr "repo.topic.manage_topics"}}</a>{{end}}
27+
{{range .Topics}}<a class="ui green basic label topic" style="cursor:pointer;" href="/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}
28+
{{if .IsRepositoryAdmin}}<a id="manage_topic" style="cursor:pointer;margin-left:10px;" href="/explore/repos?q={{.Name}}&topic=1">{{.i18n.Tr "repo.topic.manage_topics"}}</a>{{end}}
2929
</div>
3030
{{if .IsRepositoryAdmin}}
3131
<div class="ui repo-topic-edit grid form segment error" id="topic_edit" >
@@ -34,7 +34,7 @@
3434
<div class="ui fluid multiple search selection dropdown">
3535
<input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if lt (Add $i 1) (len $.Topics)}},{{end}}{{end}}">
3636
{{range .Topics}}
37-
<a class="ui green basic label topic transition visible" data-value="{{.Name}}" style="display: inline-block !important;">{{.Name}}<i class="delete icon"></i></a>
37+
<a class="ui green basic label topic transition visible" data-value="{{.Name}}" style="display: inline-block !important;" href="/explore/repos?q={{.Name}}&topic=1">{{.Name}}<i class="delete icon"></i></a>
3838
{{end}}
3939
<div class="text"></div>
4040
</div>

0 commit comments

Comments
 (0)