mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	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:
		
				
					committed by
					
						 Lauris BH
						Lauris BH
					
				
			
			
				
	
			
			
			
						parent
						
							8c24bb9e43
						
					
				
				
					commit
					c9546d4cdd
				
			| @@ -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", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user