From c9546d4cdd5f7de8f56b7d4a9806d1aca784cf3f Mon Sep 17 00:00:00 2001 From: David Svantesson Date: Sun, 25 Aug 2019 19:06:36 +0200 Subject: [PATCH] Include description in repository search. (#7942) * Add description in repository search. Signed-off-by: David Svantesson * Refactor SearchRepositoryByName with a general function SearchRepository Signed-off-by: David Svantesson * Allow to specify if description shall be included in API repo search. Signed-off-by: David Svantesson * Add new app.ini setting for whether to search within repo description. Signed-off-by: David Svantesson * Search keyword in description (if setting enabled) on: - Explore page - Organization profile page - User profile page - Admin repo page Do not search keyword in description on: - Any non-keyword search (not relevant) - Incremental search (uses API) Signed-off-by: David Svantesson * Put parameters related to keyword directly after it Signed-off-by: David Svantesson * Add test cases for including (and not including) repository description in search. Signed-off-by: David Svantesson * Rename test function from TestSearchRepositoryByName to TestSearchRepository. Signed-off-by: David Svantesson * Make setting SEARCH_REPO_DESCRIPTION default to true Signed-off-by: David Svantesson --- custom/conf/app.ini.sample | 2 + .../doc/advanced/config-cheat-sheet.en-us.md | 1 + models/fixtures/repository.yml | 1 + models/repo_list.go | 16 +++++- models/repo_list_test.go | 30 ++++++++++- modules/setting/setting.go | 28 +++++----- routers/api/v1/repo/repo.go | 27 ++++++---- routers/home.go | 21 ++++---- routers/user/home.go | 23 ++++---- routers/user/profile.go | 52 ++++++++++--------- templates/swagger/v1_json.tmpl | 6 +++ 11 files changed, 134 insertions(+), 73 deletions(-) diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index a2842c56b..82ffd2d78 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -116,6 +116,8 @@ DEFAULT_THEME = gitea THEMES = gitea,arc-green ; Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used. DEFAULT_SHOW_FULL_NAME = false +; Whether to search within description at repository search on explore page. +SEARCH_REPO_DESCRIPTION = true [ui.admin] ; Number of users that are displayed on one page diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 996b0aa65..252ceee73 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -96,6 +96,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `THEMES`: **gitea,arc-green**: All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`. - `DEFAULT_SHOW_FULL_NAME`: false: Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used. +- `SEARCH_REPO_DESCRIPTION`: true: Whether to search within description at repository search on explore page. ### UI - Admin (`ui.admin`) diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index f43fae3d6..16b0045b2 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -165,6 +165,7 @@ owner_id: 14 lower_name: test_repo_14 name: test_repo_14 + description: test_description_14 is_private: false num_issues: 0 num_closed_issues: 0 diff --git a/models/repo_list.go b/models/repo_list.go index ee4266d4e..692d4d002 100644 --- a/models/repo_list.go +++ b/models/repo_list.go @@ -136,6 +136,8 @@ type SearchRepoOptions struct { Mirror util.OptionalBool // only search topic name TopicOnly bool + // include description in keyword search + IncludeDescription bool } //SearchOrderBy is used to sort the result @@ -163,9 +165,9 @@ const ( SearchOrderByForksReverse SearchOrderBy = "num_forks DESC" ) -// SearchRepositoryByName takes keyword and part of repository name to search, +// SearchRepository returns repositories based on search options, // it returns results in given range and number of total results. -func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, error) { +func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) { if opts.Page <= 0 { opts.Page = 1 } @@ -264,6 +266,9 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err var likes = builder.NewCond() for _, v := range strings.Split(opts.Keyword, ",") { likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)}) + if opts.IncludeDescription { + likes = likes.Or(builder.Like{"LOWER(description)", strings.ToLower(v)}) + } } keywordCond = keywordCond.Or(likes) } @@ -311,6 +316,13 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err return repos, count, nil } +// SearchRepositoryByName takes keyword and part of repository name to search, +// it returns results in given range and number of total results. +func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, error) { + opts.IncludeDescription = false + return SearchRepository(opts) +} + // FindUserAccessibleRepoIDs find all accessible repositories' ID by user's id func FindUserAccessibleRepoIDs(userID int64) ([]int64, error) { var accessCond builder.Cond = builder.Eq{"is_private": false} diff --git a/models/repo_list_test.go b/models/repo_list_test.go index 645de2a59..76ae9ab3d 100644 --- a/models/repo_list_test.go +++ b/models/repo_list_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestSearchRepositoryByName(t *testing.T) { +func TestSearchRepository(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) // test search public repository on explore page @@ -74,6 +74,34 @@ func TestSearchRepositoryByName(t *testing.T) { assert.Empty(t, repos) assert.Equal(t, int64(0), count) + // Test search within description + repos, count, err = SearchRepository(&SearchRepoOptions{ + Keyword: "description_14", + Page: 1, + PageSize: 10, + Collaborate: util.OptionalBoolFalse, + IncludeDescription: true, + }) + + assert.NoError(t, err) + if assert.Len(t, repos, 1) { + assert.Equal(t, "test_repo_14", repos[0].Name) + } + assert.Equal(t, int64(1), count) + + // Test NOT search within description + repos, count, err = SearchRepository(&SearchRepoOptions{ + Keyword: "description_14", + Page: 1, + PageSize: 10, + Collaborate: util.OptionalBoolFalse, + IncludeDescription: false, + }) + + assert.NoError(t, err) + assert.Empty(t, repos) + assert.Equal(t, int64(0), count) + testCases := []struct { name string opts *SearchRepoOptions diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 77b8f2064..3ff9f89ad 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -150,19 +150,20 @@ var ( // UI settings UI = struct { - ExplorePagingNum int - IssuePagingNum int - RepoSearchPagingNum int - FeedMaxCommitNum int - GraphMaxCommitNum int - CodeCommentLines int - ReactionMaxUserNum int - ThemeColorMetaTag string - MaxDisplayFileSize int64 - ShowUserEmail bool - DefaultShowFullName bool - DefaultTheme string - Themes []string + ExplorePagingNum int + IssuePagingNum int + RepoSearchPagingNum int + FeedMaxCommitNum int + GraphMaxCommitNum int + CodeCommentLines int + ReactionMaxUserNum int + ThemeColorMetaTag string + MaxDisplayFileSize int64 + ShowUserEmail bool + DefaultShowFullName bool + DefaultTheme string + Themes []string + SearchRepoDescription bool Admin struct { UserPagingNum int @@ -942,6 +943,7 @@ func NewContext() { UI.ShowUserEmail = Cfg.Section("ui").Key("SHOW_USER_EMAIL").MustBool(true) UI.DefaultShowFullName = Cfg.Section("ui").Key("DEFAULT_SHOW_FULL_NAME").MustBool(false) + UI.SearchRepoDescription = Cfg.Section("ui").Key("SEARCH_REPO_DESCRIPTION").MustBool(true) HasRobotsTxt = com.IsFile(path.Join(CustomPath, "robots.txt")) diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 4afe1661d..eccff8c38 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -55,6 +55,10 @@ func Search(ctx *context.APIContext) { // in: query // description: Limit search to repositories with keyword as topic // type: boolean + // - name: includeDesc + // in: query + // description: include search of keyword within repository description + // type: boolean // - name: uid // in: query // description: search only for repos that the user with the given id owns or contributes to @@ -103,16 +107,17 @@ func Search(ctx *context.APIContext) { // "422": // "$ref": "#/responses/validationError" opts := &models.SearchRepoOptions{ - Keyword: strings.Trim(ctx.Query("q"), " "), - OwnerID: ctx.QueryInt64("uid"), - Page: ctx.QueryInt("page"), - PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")), - TopicOnly: ctx.QueryBool("topic"), - Collaborate: util.OptionalBoolNone, - Private: ctx.IsSigned && (ctx.Query("private") == "" || ctx.QueryBool("private")), - UserIsAdmin: ctx.IsUserSiteAdmin(), - UserID: ctx.Data["SignedUserID"].(int64), - StarredByID: ctx.QueryInt64("starredBy"), + Keyword: strings.Trim(ctx.Query("q"), " "), + OwnerID: ctx.QueryInt64("uid"), + Page: ctx.QueryInt("page"), + PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")), + TopicOnly: ctx.QueryBool("topic"), + Collaborate: util.OptionalBoolNone, + Private: ctx.IsSigned && (ctx.Query("private") == "" || ctx.QueryBool("private")), + UserIsAdmin: ctx.IsUserSiteAdmin(), + UserID: ctx.Data["SignedUserID"].(int64), + StarredByID: ctx.QueryInt64("starredBy"), + IncludeDescription: ctx.QueryBool("includeDesc"), } if ctx.QueryBool("exclusive") { @@ -157,7 +162,7 @@ func Search(ctx *context.APIContext) { } var err error - repos, count, err := models.SearchRepositoryByName(opts) + repos, count, err := models.SearchRepository(opts) if err != nil { ctx.JSON(500, api.SearchError{ OK: false, diff --git a/routers/home.go b/routers/home.go index dbe27bd42..f8fb849c8 100644 --- a/routers/home.go +++ b/routers/home.go @@ -133,18 +133,19 @@ 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, - PageSize: opts.PageSize, - OrderBy: orderBy, - Private: opts.Private, - Keyword: keyword, - OwnerID: opts.OwnerID, - AllPublic: true, - TopicOnly: topicOnly, + repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ + Page: page, + PageSize: opts.PageSize, + OrderBy: orderBy, + Private: opts.Private, + Keyword: keyword, + OwnerID: opts.OwnerID, + AllPublic: true, + TopicOnly: topicOnly, + IncludeDescription: setting.UI.SearchRepoDescription, }) if err != nil { - ctx.ServerError("SearchRepositoryByName", err) + ctx.ServerError("SearchRepository", err) return } ctx.Data["Keyword"] = keyword diff --git a/routers/user/home.go b/routers/user/home.go index adf47d289..0c7dfec7f 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -499,19 +499,20 @@ func showOrgProfile(ctx *context.Context) { count int64 err error ) - repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{ - Keyword: keyword, - OwnerID: org.ID, - OrderBy: orderBy, - Private: ctx.IsSigned, - UserIsAdmin: ctx.IsUserSiteAdmin(), - UserID: ctx.Data["SignedUserID"].(int64), - Page: page, - IsProfile: true, - PageSize: setting.UI.User.RepoPagingNum, + repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ + Keyword: keyword, + OwnerID: org.ID, + OrderBy: orderBy, + Private: ctx.IsSigned, + UserIsAdmin: ctx.IsUserSiteAdmin(), + UserID: ctx.Data["SignedUserID"].(int64), + Page: page, + IsProfile: true, + PageSize: setting.UI.User.RepoPagingNum, + IncludeDescription: setting.UI.SearchRepoDescription, }) if err != nil { - ctx.ServerError("SearchRepositoryByName", err) + ctx.ServerError("SearchRepository", err) return } diff --git a/routers/user/profile.go b/routers/user/profile.go index 7df92d44f..8a62ddeac 100644 --- a/routers/user/profile.go +++ b/routers/user/profile.go @@ -169,40 +169,42 @@ func Profile(ctx *context.Context) { } case "stars": ctx.Data["PageIsProfileStarList"] = true - repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{ - Keyword: keyword, - OrderBy: orderBy, - Private: ctx.IsSigned, - UserIsAdmin: ctx.IsUserSiteAdmin(), - UserID: ctx.Data["SignedUserID"].(int64), - Page: page, - PageSize: setting.UI.User.RepoPagingNum, - StarredByID: ctxUser.ID, - Collaborate: util.OptionalBoolFalse, - TopicOnly: topicOnly, + repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ + Keyword: keyword, + OrderBy: orderBy, + Private: ctx.IsSigned, + UserIsAdmin: ctx.IsUserSiteAdmin(), + UserID: ctx.Data["SignedUserID"].(int64), + Page: page, + PageSize: setting.UI.User.RepoPagingNum, + StarredByID: ctxUser.ID, + Collaborate: util.OptionalBoolFalse, + TopicOnly: topicOnly, + IncludeDescription: setting.UI.SearchRepoDescription, }) if err != nil { - ctx.ServerError("SearchRepositoryByName", err) + ctx.ServerError("SearchRepository", err) return } total = int(count) default: - repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{ - Keyword: keyword, - OwnerID: ctxUser.ID, - OrderBy: orderBy, - Private: ctx.IsSigned, - UserIsAdmin: ctx.IsUserSiteAdmin(), - UserID: ctx.Data["SignedUserID"].(int64), - Page: page, - IsProfile: true, - PageSize: setting.UI.User.RepoPagingNum, - Collaborate: util.OptionalBoolFalse, - TopicOnly: topicOnly, + repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ + Keyword: keyword, + OwnerID: ctxUser.ID, + OrderBy: orderBy, + Private: ctx.IsSigned, + UserIsAdmin: ctx.IsUserSiteAdmin(), + UserID: ctx.Data["SignedUserID"].(int64), + Page: page, + IsProfile: true, + PageSize: setting.UI.User.RepoPagingNum, + Collaborate: util.OptionalBoolFalse, + TopicOnly: topicOnly, + IncludeDescription: setting.UI.SearchRepoDescription, }) if err != nil { - ctx.ServerError("SearchRepositoryByName", err) + ctx.ServerError("SearchRepository", err) return } diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index ee0534569..64fce4a9f 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -1099,6 +1099,12 @@ "name": "topic", "in": "query" }, + { + "type": "boolean", + "description": "include search of keyword within repository description", + "name": "includeDesc", + "in": "query" + }, { "type": "integer", "format": "int64",