* 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>tags/v1.10.0-rc1
@@ -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 |
@@ -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`) | |||
@@ -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 |
@@ -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} |
@@ -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 |
@@ -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")) | |||
@@ -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, |
@@ -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 |
@@ -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 | |||
} | |||
@@ -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 | |||
} | |||
@@ -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", |