From ea20adaa84cbff762fcaa7f2d5fa6b7bf56706ec Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 13 Sep 2018 10:33:48 +0800 Subject: [PATCH] feat(repo): support search repository by topic name (#4505) * feat(repo): support search repository by topic name --- integrations/api_admin_test.go | 4 ++-- integrations/api_issue_test.go | 2 +- integrations/api_repo_test.go | 6 +++--- models/fixtures/repo_topic.yml | 8 ++++++++ models/fixtures/repository.yml | 22 +++++++++++++++++++++ models/fixtures/topic.yml | 6 +++++- models/fixtures/user.yml | 15 +++++++++++++++ models/repo_list.go | 33 +++++++++++++++++++++++++++++--- models/repo_list_test.go | 31 +++++++++++++++++++++++++++--- models/ssh_key.go | 8 ++++---- models/topic_test.go | 6 +++--- models/user_test.go | 4 ++-- routers/api/v1/repo/repo.go | 1 + routers/home.go | 2 ++ routers/user/profile.go | 4 ++++ templates/explore/repo_list.tmpl | 2 +- templates/repo/home.tmpl | 6 +++--- 17 files changed, 134 insertions(+), 26 deletions(-) diff --git a/integrations/api_admin_test.go b/integrations/api_admin_test.go index f801b08d39..690edad757 100644 --- a/integrations/api_admin_test.go +++ b/integrations/api_admin_test.go @@ -9,10 +9,10 @@ import ( "net/http" "testing" - "github.com/stretchr/testify/assert" - "code.gitea.io/gitea/models" api "code.gitea.io/sdk/gitea" + + "github.com/stretchr/testify/assert" ) func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) { diff --git a/integrations/api_issue_test.go b/integrations/api_issue_test.go index 97207f3368..0ac2f3b67e 100644 --- a/integrations/api_issue_test.go +++ b/integrations/api_issue_test.go @@ -5,13 +5,13 @@ package integrations import ( + "fmt" "net/http" "testing" "code.gitea.io/gitea/models" api "code.gitea.io/sdk/gitea" - "fmt" "github.com/stretchr/testify/assert" ) diff --git a/integrations/api_repo_test.go b/integrations/api_repo_test.go index 3fa2ae21dc..62237e2be3 100644 --- a/integrations/api_repo_test.go +++ b/integrations/api_repo_test.go @@ -67,9 +67,9 @@ func TestAPISearchRepo(t *testing.T) { expectedResults }{ {name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50", expectedResults: expectedResults{ - nil: {count: 17}, - user: {count: 17}, - user2: {count: 17}}, + nil: {count: 19}, + user: {count: 19}, + user2: {count: 19}}, }, {name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10", expectedResults: expectedResults{ nil: {count: 10}, diff --git a/models/fixtures/repo_topic.yml b/models/fixtures/repo_topic.yml index 58937031cd..7041ccfd09 100644 --- a/models/fixtures/repo_topic.yml +++ b/models/fixtures/repo_topic.yml @@ -9,3 +9,11 @@ - repo_id: 1 topic_id: 3 + +- + repo_id: 33 + topic_id: 1 + +- + repo_id: 33 + topic_id: 4 diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index 54f3ed7b09..c2987b9658 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -407,3 +407,25 @@ lower_name: utf8 name: utf8 is_private: false + +- + id: 34 + owner_id: 21 + lower_name: golang + name: golang + is_private: false + num_stars: 0 + num_forks: 0 + num_issues: 0 + is_mirror: false + +- + id: 35 + owner_id: 21 + lower_name: graphql + name: graphql + is_private: false + num_stars: 0 + num_forks: 0 + num_issues: 0 + is_mirror: false diff --git a/models/fixtures/topic.yml b/models/fixtures/topic.yml index b6b94ff4d9..c868b207cb 100644 --- a/models/fixtures/topic.yml +++ b/models/fixtures/topic.yml @@ -1,7 +1,7 @@ - id: 1 name: golang - repo_count: 1 + repo_count: 2 - id: 2 @@ -11,3 +11,7 @@ - id: 3 name: SQL repo_count: 1 + +- id: 4 + name: graphql + repo_count: 1 diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index 55606e42b9..b3850e3599 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -314,3 +314,18 @@ avatar_email: user20@example.com num_repos: 4 is_active: true + +- + id: 21 + lower_name: user21 + name: user21 + full_name: User 21 + email: user21@example.com + passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password + type: 0 # individual + salt: ZogKvWdyEx + is_admin: false + avatar: avatar21 + avatar_email: user21@example.com + num_repos: 2 + is_active: true diff --git a/models/repo_list.go b/models/repo_list.go index b1527b73c9..c7e292d8a9 100644 --- a/models/repo_list.go +++ b/models/repo_list.go @@ -131,6 +131,8 @@ type SearchRepoOptions struct { // True -> include just mirrors // False -> include just non-mirrors Mirror util.OptionalBool + // only search topic name + TopicOnly bool } //SearchOrderBy is used to sort the result @@ -184,7 +186,7 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err if opts.Collaborate != util.OptionalBoolFalse { collaborateCond := builder.And( - builder.Expr("id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)", opts.OwnerID), + builder.Expr("repository.id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)", opts.OwnerID), builder.Neq{"owner_id": opts.OwnerID}) if !opts.Private { 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 } if opts.Keyword != "" { - cond = cond.And(builder.Like{"lower_name", strings.ToLower(opts.Keyword)}) + var keywordCond = builder.NewCond() + if opts.TopicOnly { + keywordCond = keywordCond.Or(builder.Like{"topic.name", strings.ToLower(opts.Keyword)}) + } else { + keywordCond = keywordCond.Or(builder.Like{"lower_name", strings.ToLower(opts.Keyword)}) + keywordCond = keywordCond.Or(builder.Like{"topic.name", strings.ToLower(opts.Keyword)}) + } + cond = cond.And(keywordCond) } if opts.Fork != util.OptionalBoolNone { @@ -224,9 +233,15 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err sess.Join("INNER", "star", "star.repo_id = repository.id") } + if opts.Keyword != "" { + sess.Join("LEFT", "repo_topic", "repo_topic.repo_id = repository.id") + sess.Join("LEFT", "topic", "repo_topic.topic_id = topic.id") + } + count, err := sess. Where(cond). Count(new(Repository)) + if err != nil { return nil, 0, fmt.Errorf("Count: %v", err) } @@ -236,11 +251,23 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err sess.Join("INNER", "star", "star.repo_id = repository.id") } + if opts.Keyword != "" { + sess.Join("LEFT", "repo_topic", "repo_topic.repo_id = repository.id") + sess.Join("LEFT", "topic", "repo_topic.topic_id = topic.id") + } + + if opts.Keyword != "" { + sess.Select("repository.*") + sess.GroupBy("repository.id") + sess.OrderBy("repository." + opts.OrderBy.String()) + } else { + sess.OrderBy(opts.OrderBy.String()) + } + repos := make(RepositoryList, 0, opts.PageSize) if err = sess. Where(cond). Limit(opts.PageSize, (opts.Page-1)*opts.PageSize). - OrderBy(opts.OrderBy.String()). Find(&repos); err != nil { return nil, 0, fmt.Errorf("Repo: %v", err) } diff --git a/models/repo_list_test.go b/models/repo_list_test.go index 4b5d659ce2..8f4947dbb2 100644 --- a/models/repo_list_test.go +++ b/models/repo_list_test.go @@ -147,10 +147,10 @@ func TestSearchRepositoryByName(t *testing.T) { count: 14}, {name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative", opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true}, - count: 17}, + count: 19}, {name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative", opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true}, - count: 21}, + count: 23}, {name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName", opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true}, count: 13}, @@ -159,7 +159,7 @@ func TestSearchRepositoryByName(t *testing.T) { count: 11}, {name: "AllPublic/PublicRepositoriesOfOrganization", opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse}, - count: 17}, + count: 19}, } for _, testCase := range testCases { @@ -222,3 +222,28 @@ func TestSearchRepositoryByName(t *testing.T) { }) } } + +func TestSearchRepositoryByTopicName(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + + testCases := []struct { + name string + opts *SearchRepoOptions + count int + }{ + {name: "AllPublic/SearchPublicRepositoriesFromTopicAndName", + opts: &SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql"}, + count: 2}, + {name: "AllPublic/OnlySearchPublicRepositoriesFromTopic", + opts: &SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql", TopicOnly: true}, + count: 1}, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + _, count, err := SearchRepositoryByName(testCase.opts) + assert.NoError(t, err) + assert.Equal(t, int64(testCase.count), count) + }) + } +} diff --git a/models/ssh_key.go b/models/ssh_key.go index 997e8ee997..9c839755c8 100644 --- a/models/ssh_key.go +++ b/models/ssh_key.go @@ -18,14 +18,14 @@ import ( "sync" "time" - "github.com/Unknwon/com" - "github.com/go-xorm/xorm" - "golang.org/x/crypto/ssh" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" + + "github.com/Unknwon/com" + "github.com/go-xorm/xorm" + "golang.org/x/crypto/ssh" ) const ( diff --git a/models/topic_test.go b/models/topic_test.go index ef374e557b..65e52afb12 100644 --- a/models/topic_test.go +++ b/models/topic_test.go @@ -15,7 +15,7 @@ func TestAddTopic(t *testing.T) { topics, err := FindTopics(&FindTopicOptions{}) assert.NoError(t, err) - assert.EqualValues(t, 3, len(topics)) + assert.EqualValues(t, 4, len(topics)) topics, err = FindTopics(&FindTopicOptions{ Limit: 2, @@ -32,7 +32,7 @@ func TestAddTopic(t *testing.T) { assert.NoError(t, SaveTopics(2, "golang")) topics, err = FindTopics(&FindTopicOptions{}) assert.NoError(t, err) - assert.EqualValues(t, 3, len(topics)) + assert.EqualValues(t, 4, len(topics)) topics, err = FindTopics(&FindTopicOptions{ RepoID: 2, @@ -47,7 +47,7 @@ func TestAddTopic(t *testing.T) { topics, err = FindTopics(&FindTopicOptions{}) assert.NoError(t, err) - assert.EqualValues(t, 4, len(topics)) + assert.EqualValues(t, 5, len(topics)) topics, err = FindTopics(&FindTopicOptions{ RepoID: 2, diff --git a/models/user_test.go b/models/user_test.go index 20de1a64be..4713b6864c 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -77,13 +77,13 @@ func TestSearchUsers(t *testing.T) { } testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1}, - []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20}) + []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21}) testUserSuccess(&SearchUserOptions{Page: 1, IsActive: util.OptionalBoolFalse}, []int64{9}) testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue}, - []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20}) + []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21}) testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue}, []int64{1, 10, 11, 12, 13, 14, 15, 16, 18}) diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 90acde2eea..e48b100af6 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -91,6 +91,7 @@ func Search(ctx *context.APIContext) { OwnerID: ctx.QueryInt64("uid"), Page: ctx.QueryInt("page"), PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")), + TopicOnly: ctx.QueryBool("topic"), Collaborate: util.OptionalBoolNone, } diff --git a/routers/home.go b/routers/home.go index 0aa907658c..7a23e8765e 100644 --- a/routers/home.go +++ b/routers/home.go @@ -122,6 +122,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { } keyword := strings.Trim(ctx.Query("q"), " ") + topicOnly := ctx.QueryBool("topic") repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{ Page: page, @@ -131,6 +132,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { Keyword: keyword, OwnerID: opts.OwnerID, AllPublic: true, + TopicOnly: topicOnly, }) if err != nil { ctx.ServerError("SearchRepositoryByName", err) diff --git a/routers/user/profile.go b/routers/user/profile.go index fb731e715c..827226d3e1 100644 --- a/routers/user/profile.go +++ b/routers/user/profile.go @@ -105,6 +105,8 @@ func Profile(ctx *context.Context) { page = 1 } + topicOnly := ctx.QueryBool("topic") + var ( repos []*models.Repository count int64 @@ -174,6 +176,7 @@ func Profile(ctx *context.Context) { PageSize: setting.UI.User.RepoPagingNum, Starred: true, Collaborate: util.OptionalBoolFalse, + TopicOnly: topicOnly, }) if err != nil { ctx.ServerError("SearchRepositoryByName", err) @@ -217,6 +220,7 @@ func Profile(ctx *context.Context) { IsProfile: true, PageSize: setting.UI.User.RepoPagingNum, Collaborate: util.OptionalBoolFalse, + TopicOnly: topicOnly, }) if err != nil { ctx.ServerError("SearchRepositoryByName", err) diff --git a/templates/explore/repo_list.tmpl b/templates/explore/repo_list.tmpl index b8f4490c11..e769e4b7fd 100644 --- a/templates/explore/repo_list.tmpl +++ b/templates/explore/repo_list.tmpl @@ -20,7 +20,7 @@ {{if .Topics }}
{{range .Topics}} - {{if ne . "" }}
{{.}}
{{end}} + {{if ne . "" }}
{{.}}
{{end}} {{end}}
{{end}} diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index f14f2b9a28..d5a1416cf6 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -24,8 +24,8 @@ {{end}}
- {{range .Topics}}
{{.Name}}
{{end}} - {{if .IsRepositoryAdmin}}{{.i18n.Tr "repo.topic.manage_topics"}}{{end}} + {{range .Topics}}{{.Name}}{{end}} + {{if .IsRepositoryAdmin}}{{.i18n.Tr "repo.topic.manage_topics"}}{{end}}
{{if .IsRepositoryAdmin}}
@@ -34,7 +34,7 @@