diff --git a/data/model/repository.py b/data/model/repository.py
index c2f65bdfe..1b113fe42 100644
--- a/data/model/repository.py
+++ b/data/model/repository.py
@@ -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
diff --git a/static/js/directives/ui/search-box/search-box.component.html b/static/js/directives/ui/search-box/search-box.component.html
index d62b9c2f5..162529c52 100644
--- a/static/js/directives/ui/search-box/search-box.component.html
+++ b/static/js/directives/ui/search-box/search-box.component.html
@@ -35,16 +35,24 @@
first-line-only="true" placeholder-needed="false">
+
+
+ {{ result.namespace.name }}/{{ result.name }}
+
+
-
diff --git a/static/js/directives/ui/search-box/search-box.component.ts b/static/js/directives/ui/search-box/search-box.component.ts
index cdb30ffff..597019d76 100644
--- a/static/js/directives/ui/search-box/search-box.component.ts
+++ b/static/js/directives/ui/search-box/search-box.component.ts
@@ -10,6 +10,7 @@ import { Input, Component, Inject } from 'ng-metadata/core';
})
export class SearchBoxComponent {
@Input(' {
- $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);
diff --git a/static/js/directives/ui/typeahead/typeahead.directive.ts b/static/js/directives/ui/typeahead/typeahead.directive.ts
index d733d88b2..d49b6de9c 100644
--- a/static/js/directives/ui/typeahead/typeahead.directive.ts
+++ b/static/js/directives/ui/typeahead/typeahead.directive.ts
@@ -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();
@Output('taEntered') entered = new EventEmitter();
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) {
diff --git a/static/partials/search.html b/static/partials/search.html
index baca42247..df484a81f 100644
--- a/static/partials/search.html
+++ b/static/partials/search.html
@@ -1,6 +1,6 @@
-
+
@@ -21,11 +21,11 @@
-
+
Last Modified: