Include description in repository search. (#7942)

* Add description in repository search.

Signed-off-by: David Svantesson <davidsvantesson@gmail.com>

* Refactor SearchRepositoryByName with a general function SearchRepository

Signed-off-by: David Svantesson <davidsvantesson@gmail.com>

* Allow to specify if description shall be included in API repo search.

Signed-off-by: David Svantesson <davidsvantesson@gmail.com>

* Add new app.ini setting for whether to search within repo description.

Signed-off-by: David Svantesson <davidsvantesson@gmail.com>

* 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 <davidsvantesson@gmail.com>

* Put parameters related to keyword directly after it

Signed-off-by: David Svantesson <davidsvantesson@gmail.com>

* Add test cases for including (and not including) repository description in search.

Signed-off-by: David Svantesson <davidsvantesson@gmail.com>

* Rename test function from TestSearchRepositoryByName to TestSearchRepository.

Signed-off-by: David Svantesson <davidsvantesson@gmail.com>

* Make setting SEARCH_REPO_DESCRIPTION default to true

Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
This commit is contained in:
David Svantesson 2019-08-25 19:06:36 +02:00 committed by Lauris BH
parent 8c24bb9e43
commit c9546d4cdd
11 changed files with 134 additions and 73 deletions

View file

@ -116,6 +116,8 @@ DEFAULT_THEME = gitea
THEMES = gitea,arc-green 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. ; 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 DEFAULT_SHOW_FULL_NAME = false
; Whether to search within description at repository search on explore page.
SEARCH_REPO_DESCRIPTION = true
[ui.admin] [ui.admin]
; Number of users that are displayed on one page ; Number of users that are displayed on one page

View file

@ -96,6 +96,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `THEMES`: **gitea,arc-green**: All available themes. Allow users select personalized themes - `THEMES`: **gitea,arc-green**: All available themes. Allow users select personalized themes
regardless of the value of `DEFAULT_THEME`. 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. - `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`) ### UI - Admin (`ui.admin`)

View file

@ -165,6 +165,7 @@
owner_id: 14 owner_id: 14
lower_name: test_repo_14 lower_name: test_repo_14
name: test_repo_14 name: test_repo_14
description: test_description_14
is_private: false is_private: false
num_issues: 0 num_issues: 0
num_closed_issues: 0 num_closed_issues: 0

View file

@ -136,6 +136,8 @@ type SearchRepoOptions struct {
Mirror util.OptionalBool Mirror util.OptionalBool
// only search topic name // only search topic name
TopicOnly bool TopicOnly bool
// include description in keyword search
IncludeDescription bool
} }
//SearchOrderBy is used to sort the result //SearchOrderBy is used to sort the result
@ -163,9 +165,9 @@ const (
SearchOrderByForksReverse SearchOrderBy = "num_forks DESC" 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. // 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 { if opts.Page <= 0 {
opts.Page = 1 opts.Page = 1
} }
@ -264,6 +266,9 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
var likes = builder.NewCond() var likes = builder.NewCond()
for _, v := range strings.Split(opts.Keyword, ",") { for _, v := range strings.Split(opts.Keyword, ",") {
likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)}) 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) keywordCond = keywordCond.Or(likes)
} }
@ -311,6 +316,13 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
return repos, count, nil 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 // FindUserAccessibleRepoIDs find all accessible repositories' ID by user's id
func FindUserAccessibleRepoIDs(userID int64) ([]int64, error) { func FindUserAccessibleRepoIDs(userID int64) ([]int64, error) {
var accessCond builder.Cond = builder.Eq{"is_private": false} var accessCond builder.Cond = builder.Eq{"is_private": false}

View file

@ -12,7 +12,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestSearchRepositoryByName(t *testing.T) { func TestSearchRepository(t *testing.T) {
assert.NoError(t, PrepareTestDatabase()) assert.NoError(t, PrepareTestDatabase())
// test search public repository on explore page // test search public repository on explore page
@ -74,6 +74,34 @@ func TestSearchRepositoryByName(t *testing.T) {
assert.Empty(t, repos) assert.Empty(t, repos)
assert.Equal(t, int64(0), count) 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 { testCases := []struct {
name string name string
opts *SearchRepoOptions opts *SearchRepoOptions

View file

@ -150,19 +150,20 @@ var (
// UI settings // UI settings
UI = struct { UI = struct {
ExplorePagingNum int ExplorePagingNum int
IssuePagingNum int IssuePagingNum int
RepoSearchPagingNum int RepoSearchPagingNum int
FeedMaxCommitNum int FeedMaxCommitNum int
GraphMaxCommitNum int GraphMaxCommitNum int
CodeCommentLines int CodeCommentLines int
ReactionMaxUserNum int ReactionMaxUserNum int
ThemeColorMetaTag string ThemeColorMetaTag string
MaxDisplayFileSize int64 MaxDisplayFileSize int64
ShowUserEmail bool ShowUserEmail bool
DefaultShowFullName bool DefaultShowFullName bool
DefaultTheme string DefaultTheme string
Themes []string Themes []string
SearchRepoDescription bool
Admin struct { Admin struct {
UserPagingNum int UserPagingNum int
@ -942,6 +943,7 @@ func NewContext() {
UI.ShowUserEmail = Cfg.Section("ui").Key("SHOW_USER_EMAIL").MustBool(true) UI.ShowUserEmail = Cfg.Section("ui").Key("SHOW_USER_EMAIL").MustBool(true)
UI.DefaultShowFullName = Cfg.Section("ui").Key("DEFAULT_SHOW_FULL_NAME").MustBool(false) 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")) HasRobotsTxt = com.IsFile(path.Join(CustomPath, "robots.txt"))

View file

@ -55,6 +55,10 @@ func Search(ctx *context.APIContext) {
// in: query // in: query
// description: Limit search to repositories with keyword as topic // description: Limit search to repositories with keyword as topic
// type: boolean // type: boolean
// - name: includeDesc
// in: query
// description: include search of keyword within repository description
// type: boolean
// - name: uid // - name: uid
// in: query // in: query
// description: search only for repos that the user with the given id owns or contributes to // 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": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
opts := &models.SearchRepoOptions{ opts := &models.SearchRepoOptions{
Keyword: strings.Trim(ctx.Query("q"), " "), Keyword: strings.Trim(ctx.Query("q"), " "),
OwnerID: ctx.QueryInt64("uid"), OwnerID: ctx.QueryInt64("uid"),
Page: ctx.QueryInt("page"), Page: ctx.QueryInt("page"),
PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")), PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")),
TopicOnly: ctx.QueryBool("topic"), TopicOnly: ctx.QueryBool("topic"),
Collaborate: util.OptionalBoolNone, Collaborate: util.OptionalBoolNone,
Private: ctx.IsSigned && (ctx.Query("private") == "" || ctx.QueryBool("private")), Private: ctx.IsSigned && (ctx.Query("private") == "" || ctx.QueryBool("private")),
UserIsAdmin: ctx.IsUserSiteAdmin(), UserIsAdmin: ctx.IsUserSiteAdmin(),
UserID: ctx.Data["SignedUserID"].(int64), UserID: ctx.Data["SignedUserID"].(int64),
StarredByID: ctx.QueryInt64("starredBy"), StarredByID: ctx.QueryInt64("starredBy"),
IncludeDescription: ctx.QueryBool("includeDesc"),
} }
if ctx.QueryBool("exclusive") { if ctx.QueryBool("exclusive") {
@ -157,7 +162,7 @@ func Search(ctx *context.APIContext) {
} }
var err error var err error
repos, count, err := models.SearchRepositoryByName(opts) repos, count, err := models.SearchRepository(opts)
if err != nil { if err != nil {
ctx.JSON(500, api.SearchError{ ctx.JSON(500, api.SearchError{
OK: false, OK: false,

View file

@ -133,18 +133,19 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
keyword := strings.Trim(ctx.Query("q"), " ") keyword := strings.Trim(ctx.Query("q"), " ")
topicOnly := ctx.QueryBool("topic") topicOnly := ctx.QueryBool("topic")
repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{ repos, count, err = models.SearchRepository(&models.SearchRepoOptions{
Page: page, Page: page,
PageSize: opts.PageSize, PageSize: opts.PageSize,
OrderBy: orderBy, OrderBy: orderBy,
Private: opts.Private, Private: opts.Private,
Keyword: keyword, Keyword: keyword,
OwnerID: opts.OwnerID, OwnerID: opts.OwnerID,
AllPublic: true, AllPublic: true,
TopicOnly: topicOnly, TopicOnly: topicOnly,
IncludeDescription: setting.UI.SearchRepoDescription,
}) })
if err != nil { if err != nil {
ctx.ServerError("SearchRepositoryByName", err) ctx.ServerError("SearchRepository", err)
return return
} }
ctx.Data["Keyword"] = keyword ctx.Data["Keyword"] = keyword

View file

@ -499,19 +499,20 @@ func showOrgProfile(ctx *context.Context) {
count int64 count int64
err error err error
) )
repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{ repos, count, err = models.SearchRepository(&models.SearchRepoOptions{
Keyword: keyword, Keyword: keyword,
OwnerID: org.ID, OwnerID: org.ID,
OrderBy: orderBy, OrderBy: orderBy,
Private: ctx.IsSigned, Private: ctx.IsSigned,
UserIsAdmin: ctx.IsUserSiteAdmin(), UserIsAdmin: ctx.IsUserSiteAdmin(),
UserID: ctx.Data["SignedUserID"].(int64), UserID: ctx.Data["SignedUserID"].(int64),
Page: page, Page: page,
IsProfile: true, IsProfile: true,
PageSize: setting.UI.User.RepoPagingNum, PageSize: setting.UI.User.RepoPagingNum,
IncludeDescription: setting.UI.SearchRepoDescription,
}) })
if err != nil { if err != nil {
ctx.ServerError("SearchRepositoryByName", err) ctx.ServerError("SearchRepository", err)
return return
} }

View file

@ -169,40 +169,42 @@ func Profile(ctx *context.Context) {
} }
case "stars": case "stars":
ctx.Data["PageIsProfileStarList"] = true ctx.Data["PageIsProfileStarList"] = true
repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{ repos, count, err = models.SearchRepository(&models.SearchRepoOptions{
Keyword: keyword, Keyword: keyword,
OrderBy: orderBy, OrderBy: orderBy,
Private: ctx.IsSigned, Private: ctx.IsSigned,
UserIsAdmin: ctx.IsUserSiteAdmin(), UserIsAdmin: ctx.IsUserSiteAdmin(),
UserID: ctx.Data["SignedUserID"].(int64), UserID: ctx.Data["SignedUserID"].(int64),
Page: page, Page: page,
PageSize: setting.UI.User.RepoPagingNum, PageSize: setting.UI.User.RepoPagingNum,
StarredByID: ctxUser.ID, StarredByID: ctxUser.ID,
Collaborate: util.OptionalBoolFalse, Collaborate: util.OptionalBoolFalse,
TopicOnly: topicOnly, TopicOnly: topicOnly,
IncludeDescription: setting.UI.SearchRepoDescription,
}) })
if err != nil { if err != nil {
ctx.ServerError("SearchRepositoryByName", err) ctx.ServerError("SearchRepository", err)
return return
} }
total = int(count) total = int(count)
default: default:
repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{ repos, count, err = models.SearchRepository(&models.SearchRepoOptions{
Keyword: keyword, Keyword: keyword,
OwnerID: ctxUser.ID, OwnerID: ctxUser.ID,
OrderBy: orderBy, OrderBy: orderBy,
Private: ctx.IsSigned, Private: ctx.IsSigned,
UserIsAdmin: ctx.IsUserSiteAdmin(), UserIsAdmin: ctx.IsUserSiteAdmin(),
UserID: ctx.Data["SignedUserID"].(int64), UserID: ctx.Data["SignedUserID"].(int64),
Page: page, Page: page,
IsProfile: true, IsProfile: true,
PageSize: setting.UI.User.RepoPagingNum, PageSize: setting.UI.User.RepoPagingNum,
Collaborate: util.OptionalBoolFalse, Collaborate: util.OptionalBoolFalse,
TopicOnly: topicOnly, TopicOnly: topicOnly,
IncludeDescription: setting.UI.SearchRepoDescription,
}) })
if err != nil { if err != nil {
ctx.ServerError("SearchRepositoryByName", err) ctx.ServerError("SearchRepository", err)
return return
} }

View file

@ -1099,6 +1099,12 @@
"name": "topic", "name": "topic",
"in": "query" "in": "query"
}, },
{
"type": "boolean",
"description": "include search of keyword within repository description",
"name": "includeDesc",
"in": "query"
},
{ {
"type": "integer", "type": "integer",
"format": "int64", "format": "int64",