Merge pull request #2614 from coreos-inc/search-improvements
Improvements to new Quay search
This commit is contained in:
commit
43e032299c
5 changed files with 43 additions and 12 deletions
|
@ -4,6 +4,7 @@ import random
|
|||
from enum import Enum
|
||||
from datetime import timedelta, datetime
|
||||
from peewee import JOIN_LEFT_OUTER, fn, SQL, IntegrityError
|
||||
from playhouse.shortcuts import case
|
||||
from cachetools import ttl_cache
|
||||
|
||||
from data.model import (config, DataModelException, tag, db_transaction, storage, permission,
|
||||
|
@ -485,14 +486,21 @@ def _get_sorted_matching_repositories(lookup_value, repo_kind='image', include_p
|
|||
|
||||
# Always search at least on name (init clause)
|
||||
clause = Repository.name.match(lookup_value)
|
||||
computed_score = RepositorySearchScore.score.alias('score')
|
||||
|
||||
# If the description field is in the search fields, then we need to compute a synthetic score
|
||||
# to discount the weight of the description more than the name.
|
||||
if SEARCH_FIELDS.description.name in search_fields:
|
||||
clause = Repository.description.match(lookup_value) | clause
|
||||
|
||||
last_week = datetime.now() - timedelta(weeks=1)
|
||||
cases = [
|
||||
(Repository.name.match(lookup_value), 100 * RepositorySearchScore.score),
|
||||
]
|
||||
|
||||
computed_score = case(None, cases, RepositorySearchScore.score).alias('score')
|
||||
|
||||
query = (Repository
|
||||
.select(Repository, Namespace)
|
||||
.select(Repository, Namespace, computed_score)
|
||||
.join(Namespace, on=(Namespace.id == Repository.namespace_user))
|
||||
.where(clause)
|
||||
.group_by(Repository.id, Namespace.id))
|
||||
|
@ -507,7 +515,7 @@ def _get_sorted_matching_repositories(lookup_value, repo_kind='image', include_p
|
|||
.switch(Repository)
|
||||
.join(RepositorySearchScore)
|
||||
.group_by(Repository, Namespace, RepositorySearchScore)
|
||||
.order_by(RepositorySearchScore.score.desc()))
|
||||
.order_by(SQL('score').desc()))
|
||||
|
||||
return query
|
||||
|
||||
|
|
|
@ -35,16 +35,24 @@
|
|||
first-line-only="true" placeholder-needed="false"></div>
|
||||
</div>
|
||||
</span>
|
||||
<span ng-switch-when="application">
|
||||
<span class="avatar" data="result.namespace.avatar" size="16"></span>
|
||||
<span class="result-name">{{ result.namespace.name }}/{{ result.name }}</span>
|
||||
<div class="result-description" ng-if="result.description">
|
||||
<div class="description markdown-view" content="result.description"
|
||||
first-line-only="true" placeholder-needed="false"></div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<input class="form-control" type="text" placeholder="search"
|
||||
ng-model="$ctrl.enteredQuery"
|
||||
typeahead="$ctrl.onTypeahead($event)"
|
||||
ta-debounce="250"
|
||||
ta-display-key="name"
|
||||
ta-suggestion-tmpl="search-result-template"
|
||||
ta-clear-on-select="true"
|
||||
ta-clear-on-select="$ctrl.clearOnSearch"
|
||||
(ta-selected)="$ctrl.onSelected($event)"
|
||||
(ta-entered)="$ctrl.onEntered($event)">
|
||||
<span class="search-icon">
|
||||
|
|
|
@ -10,6 +10,7 @@ import { Input, Component, Inject } from 'ng-metadata/core';
|
|||
})
|
||||
export class SearchBoxComponent {
|
||||
@Input('<query') public enteredQuery: string = '';
|
||||
@Input('@clearOnSearch') public clearOnSearch: string = 'true';
|
||||
|
||||
private isSearching: boolean = false;
|
||||
private currentQuery: string = '';
|
||||
|
@ -48,7 +49,7 @@ export class SearchBoxComponent {
|
|||
|
||||
private onEntered($event): void {
|
||||
this.$timeout(() => {
|
||||
$event['callback'](true); // Clear the value.
|
||||
$event['callback'](this.clearOnSearch == 'true'); // Clear the value.
|
||||
this.$location.url('/search');
|
||||
this.$location.search('q', $event['value']);
|
||||
}, 10);
|
||||
|
|
|
@ -13,16 +13,19 @@ export class TypeaheadDirective implements AfterContentInit {
|
|||
@Input('taDisplayKey') displayKey: string = '';
|
||||
@Input('taSuggestionTmpl') suggestionTemplate: string = '';
|
||||
@Input('taClearOnSelect') clearOnSelect: boolean = false;
|
||||
@Input('taDebounce') debounce: number = 250;
|
||||
|
||||
@Output('taSelected') selected = new EventEmitter<any>();
|
||||
@Output('taEntered') entered = new EventEmitter<any>();
|
||||
|
||||
private itemSelected: boolean = false;
|
||||
private existingTimer: Promise = null;
|
||||
|
||||
constructor(@Inject('$element') private $element: ng.IAugmentedJQuery,
|
||||
@Inject('$compile') private $compile: ng.ICompileService,
|
||||
@Inject('$scope') private $scope: ng.IScope,
|
||||
@Inject('$templateRequest') private $templateRequest: ng.ITemplateRequestService) {
|
||||
@Inject('$templateRequest') private $templateRequest: ng.ITemplateRequestService,
|
||||
@Inject('$timeout') private $timeout: ng.ITimeoutService) {
|
||||
}
|
||||
|
||||
public ngAfterContentInit(): void {
|
||||
|
@ -52,12 +55,23 @@ export class TypeaheadDirective implements AfterContentInit {
|
|||
templates: templates,
|
||||
display: this.displayKey,
|
||||
source: (query, results, asyncResults) => {
|
||||
this.typeahead.emit({'query': query, 'callback': asyncResults});
|
||||
this.itemSelected = false;
|
||||
this.debounceQuery(query, asyncResults);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private debounceQuery(query: string, asyncResults: Function): void {
|
||||
if (this.existingTimer) {
|
||||
this.$timeout.cancel(this.existingTimer);
|
||||
this.existingTimer = null;
|
||||
}
|
||||
|
||||
this.existingTimer = this.$timeout(() => {
|
||||
this.typeahead.emit({'query': query, 'callback': asyncResults});
|
||||
this.itemSelected = false;
|
||||
}, this.debounce);
|
||||
}
|
||||
|
||||
@HostListener('keyup', ['$event'])
|
||||
public onKeyup(event: JQueryKeyEventObject): void {
|
||||
if (!this.itemSelected && event.keyCode == 13) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="cor-container search">
|
||||
<div class="search-top-bar">
|
||||
<search-box query="currentQuery"></search-box>
|
||||
<search-box query="currentQuery" clear-on-search="false"></search-box>
|
||||
</div>
|
||||
|
||||
<div class="search-results-section">
|
||||
|
@ -21,11 +21,11 @@
|
|||
</span>
|
||||
<h4>
|
||||
<i class="fa fa-hdd-o" ng-if="result.kind == 'repository'"></i>
|
||||
<i class="fa ci-app-cube" ng-if="result.kind == 'application'"></i>
|
||||
<i class="fa ci-appcube" ng-if="result.kind == 'application'"></i>
|
||||
<a href="{{ result.href }}">{{ result.namespace.name }}/{{ result.name }}</a>
|
||||
</h4>
|
||||
<p class="description">
|
||||
<span class="markdown-view" content="result.description"></span>
|
||||
<span class="markdown-view" content="result.description" first-line-only="true"></span>
|
||||
</p>
|
||||
<p class="result-info-bar">
|
||||
Last Modified: <span am-time-ago="result.last_modified * 1000"></span>
|
||||
|
|
Reference in a new issue