Merge pull request #2614 from coreos-inc/search-improvements

Improvements to new Quay search
This commit is contained in:
josephschorr 2017-05-03 17:06:58 -04:00 committed by GitHub
commit 43e032299c
5 changed files with 43 additions and 12 deletions

View file

@ -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

View file

@ -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">

View file

@ -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);

View file

@ -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) {

View file

@ -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>