Only show relevant repositories on explore page (#19361)

Adds a new option to only show relevant repo's on the explore page, for bigger Gitea instances like Codeberg this is a nice option to enable to make the explore page more populated with unique and "high" quality repo's. A note is shown that the results are filtered and have the possibility to see the unfiltered results.

Co-authored-by: vednoc <vednoc@protonmail.com>
Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
Gusted 2022-08-25 20:38:41 +02:00 committed by GitHub
parent dc0253b063
commit 27ac65a124
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 65 additions and 8 deletions

View File

@ -1164,6 +1164,10 @@ ROUTER = console
;; ;;
;; Whether to enable a Service Worker to cache frontend assets ;; Whether to enable a Service Worker to cache frontend assets
;USE_SERVICE_WORKER = false ;USE_SERVICE_WORKER = false
;;
;; Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used.
;; A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic).
;ONLY_SHOW_RELEVANT_REPOS = false
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -194,6 +194,8 @@ The following configuration set `Content-Type: application/vnd.android.package-a
- `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. - `SEARCH_REPO_DESCRIPTION`: **true**: Whether to search within description at repository search on explore page.
- `USE_SERVICE_WORKER`: **false**: Whether to enable a Service Worker to cache frontend assets. - `USE_SERVICE_WORKER`: **false**: Whether to enable a Service Worker to cache frontend assets.
- `ONLY_SHOW_RELEVANT_REPOS`: **false** Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used.
A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic).
### UI - Admin (`ui.admin`) ### UI - Admin (`ui.admin`)

View File

@ -163,6 +163,10 @@ type SearchRepoOptions struct {
HasMilestones util.OptionalBool HasMilestones util.OptionalBool
// LowerNames represents valid lower names to restrict to // LowerNames represents valid lower names to restrict to
LowerNames []string LowerNames []string
// When specified true, apply some filters over the conditions:
// - Don't show forks, when opts.Fork is OptionalBoolNone.
// - Do not display repositories that don't have a description, an icon and topics.
OnlyShowRelevant bool
} }
// SearchOrderBy is used to sort the result // SearchOrderBy is used to sort the result
@ -463,8 +467,12 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
Where(builder.Eq{"language": opts.Language}).And(builder.Eq{"is_primary": true}))) Where(builder.Eq{"language": opts.Language}).And(builder.Eq{"is_primary": true})))
} }
if opts.Fork != util.OptionalBoolNone { if opts.Fork != util.OptionalBoolNone || opts.OnlyShowRelevant {
cond = cond.And(builder.Eq{"is_fork": opts.Fork == util.OptionalBoolTrue}) if opts.OnlyShowRelevant && opts.Fork == util.OptionalBoolNone {
cond = cond.And(builder.Eq{"is_fork": false})
} else {
cond = cond.And(builder.Eq{"is_fork": opts.Fork == util.OptionalBoolTrue})
}
} }
if opts.Mirror != util.OptionalBoolNone { if opts.Mirror != util.OptionalBoolNone {
@ -486,6 +494,25 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
cond = cond.And(builder.Eq{"num_milestones": 0}.Or(builder.IsNull{"num_milestones"})) cond = cond.And(builder.Eq{"num_milestones": 0}.Or(builder.IsNull{"num_milestones"}))
} }
if opts.OnlyShowRelevant {
// Only show a repo that either has a topic or description.
subQueryCond := builder.NewCond()
// Topic checking. Topics is non-null.
subQueryCond = subQueryCond.Or(builder.And(builder.Neq{"topics": "null"}, builder.Neq{"topics": "[]"}))
// Description checking. Description not empty.
subQueryCond = subQueryCond.Or(builder.Neq{"description": ""})
// Repo has a avatar.
subQueryCond = subQueryCond.Or(builder.Neq{"avatar": ""})
// Always hide repo's that are empty.
subQueryCond = subQueryCond.And(builder.Eq{"is_empty": false})
cond = cond.And(subQueryCond)
}
return cond return cond
} }

View File

@ -240,6 +240,7 @@ var (
CustomEmojisMap map[string]string `ini:"-"` CustomEmojisMap map[string]string `ini:"-"`
SearchRepoDescription bool SearchRepoDescription bool
UseServiceWorker bool UseServiceWorker bool
OnlyShowRelevantRepos bool
Notification struct { Notification struct {
MinTimeout time.Duration MinTimeout time.Duration
@ -1087,6 +1088,7 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
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) UI.SearchRepoDescription = Cfg.Section("ui").Key("SEARCH_REPO_DESCRIPTION").MustBool(true)
UI.UseServiceWorker = Cfg.Section("ui").Key("USE_SERVICE_WORKER").MustBool(false) UI.UseServiceWorker = Cfg.Section("ui").Key("USE_SERVICE_WORKER").MustBool(false)
UI.OnlyShowRelevantRepos = Cfg.Section("ui").Key("ONLY_SHOW_RELEVANT_REPOS").MustBool(false)
HasRobotsTxt, err = util.IsFile(path.Join(CustomPath, "robots.txt")) HasRobotsTxt, err = util.IsFile(path.Join(CustomPath, "robots.txt"))
if err != nil { if err != nil {

View File

@ -277,6 +277,9 @@ org_no_results = No matching organizations found.
code_no_results = No source code matching your search term found. code_no_results = No source code matching your search term found.
code_search_results = Search results for '%s' code_search_results = Search results for '%s'
code_last_indexed_at = Last indexed %s code_last_indexed_at = Last indexed %s
relevant_repositories_tooltip = Repositories that are forks or that have no topic, no icon, and no description are hidden.
relevant_repositories = Only relevant repositories are being shown, <a href="%s">show unfiltered results</a>.
[auth] [auth]
create_new_account = Register Account create_new_account = Register Account

View File

@ -48,10 +48,11 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
} }
var ( var (
repos []*repo_model.Repository repos []*repo_model.Repository
count int64 count int64
err error err error
orderBy db.SearchOrderBy orderBy db.SearchOrderBy
onlyShowRelevant bool
) )
ctx.Data["SortType"] = ctx.FormString("sort") ctx.Data["SortType"] = ctx.FormString("sort")
@ -60,8 +61,6 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
orderBy = db.SearchOrderByNewest orderBy = db.SearchOrderByNewest
case "oldest": case "oldest":
orderBy = db.SearchOrderByOldest orderBy = db.SearchOrderByOldest
case "recentupdate":
orderBy = db.SearchOrderByRecentUpdated
case "leastupdate": case "leastupdate":
orderBy = db.SearchOrderByLeastUpdated orderBy = db.SearchOrderByLeastUpdated
case "reversealphabetically": case "reversealphabetically":
@ -83,9 +82,16 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
default: default:
ctx.Data["SortType"] = "recentupdate" ctx.Data["SortType"] = "recentupdate"
orderBy = db.SearchOrderByRecentUpdated orderBy = db.SearchOrderByRecentUpdated
onlyShowRelevant = setting.UI.OnlyShowRelevantRepos && !ctx.FormBool("no_filter")
} }
keyword := ctx.FormTrim("q") keyword := ctx.FormTrim("q")
if keyword != "" {
onlyShowRelevant = false
}
ctx.Data["OnlyShowRelevant"] = onlyShowRelevant
topicOnly := ctx.FormBool("topic") topicOnly := ctx.FormBool("topic")
ctx.Data["TopicOnly"] = topicOnly ctx.Data["TopicOnly"] = topicOnly
@ -107,6 +113,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
TopicOnly: topicOnly, TopicOnly: topicOnly,
Language: language, Language: language,
IncludeDescription: setting.UI.SearchRepoDescription, IncludeDescription: setting.UI.SearchRepoDescription,
OnlyShowRelevant: onlyShowRelevant,
}) })
if err != nil { if err != nil {
ctx.ServerError("SearchRepository", err) ctx.ServerError("SearchRepository", err)
@ -133,6 +140,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
pager.SetDefaultParams(ctx) pager.SetDefaultParams(ctx)
pager.AddParam(ctx, "topic", "TopicOnly") pager.AddParam(ctx, "topic", "TopicOnly")
pager.AddParam(ctx, "language", "Language") pager.AddParam(ctx, "language", "Language")
pager.AddParamString("no_filter", ctx.FormString("no_filter"))
ctx.Data["Page"] = pager ctx.Data["Page"] = pager
ctx.HTML(http.StatusOK, opts.TplName) ctx.HTML(http.StatusOK, opts.TplName)

View File

@ -29,4 +29,9 @@
<button class="ui primary button">{{.locale.Tr "explore.search"}}</button> <button class="ui primary button">{{.locale.Tr "explore.search"}}</button>
</div> </div>
</form> </form>
{{if .OnlyShowRelevant}}
<div class="ui blue attached message explore-relevancy-note">
<span class="ui tooltip" data-content="{{.locale.Tr "explore.relevant_repositories_tooltip"}}">{{.locale.Tr "explore.relevant_repositories" ((printf "%s%s" $.Link "?no_filter=1")|Escape) | Safe}}</span>
</div>
{{end}}
<div class="ui divider"></div> <div class="ui divider"></div>

View File

@ -89,3 +89,9 @@
} }
} }
} }
.ui.explore-relevancy-note {
border-top: 0;
margin-top: 0;
max-width: 90%;
}