Bläddra i källkod

[search] Integrate Solr suggest in the UI

Enrico Berti 10 år sedan
förälder
incheckning
d93903f

+ 16 - 0
apps/search/src/search/static/search/js/search.ko.js

@@ -1493,6 +1493,22 @@ var SearchViewModel = function (collection_json, query_json, initial_json) {
     });
   };
 
+  self.suggest = function (query, callback) {
+    $.post("/search/suggest/", {
+      collection: ko.mapping.toJSON(self.collection),
+      query: query
+    }, function (data) {
+      if (data.status == 0) {
+        callback(data);
+      }
+      else {
+        callback();
+      }
+    }).fail(function (xhr, textStatus, errorThrown) {
+      $(document).trigger("error", xhr.responseText);
+    });
+  };
+
   self.removeWidget = function (widget_json) {
     self.collection.removeFacet(widget_json.id);
     self.collection.removeLeaflet(widget_json);

+ 30 - 1
apps/search/src/search/templates/search.mako

@@ -96,7 +96,7 @@ ${ commonheader(_('Search'), "search", user, "80px") | n,unicode }
       </div>
 
       <span data-bind="foreach: query.qs">
-        <input data-bind="clearable: q, typeahead: { target: q, source: $root.collection.template.fieldsNames, multipleValues: true, multipleValuesSeparator: ':', extraKeywords: 'AND OR TO', completeSolrRanges: true }, css:{'input-xlarge': $root.query.qs().length == 1, 'input-medium': $root.query.qs().length < 4, 'input-small': $root.query.qs().length >= 4}" maxlength="4096" type="text" class="search-query">
+        <input data-bind="clearable: q, valueUpdate:'afterkeydown', typeahead: { target: q, nonBindableSource: queryTypeahead, multipleValues: true, multipleValuesSeparator: ':', extraKeywords: 'AND OR TO', completeSolrRanges: true }, css:{'input-xlarge': $root.query.qs().length == 1, 'input-medium': $root.query.qs().length < 4, 'input-small': $root.query.qs().length >= 4}" maxlength="4096" type="text" class="search-query">
         <!-- ko if: $index() >= 1 -->
         <a class="btn" href="javascript:void(0)" data-bind="click: $root.query.removeQ"><i class="fa fa-minus"></i></a>
         <!-- /ko -->
@@ -1727,6 +1727,14 @@ ${ dashboard.layout_skeleton() }
                 <input id="settingsdescription" type="text" class="input-xlarge" data-bind="value: $root.collection.description" style="margin-bottom: 0" />
               </div>
             </div>
+            <div class="control-group">
+              <label class="control-label">${ _('Autocomplete') }</label>
+              <div class="controls">
+                <label class="checkbox" style="padding-top:0">
+                  <input type="checkbox" style="margin-right: 4px; margin-top: 9px" data-bind="checked: $root.collection.suggest.enabled"> ${ _('Dictionary') } <input type="text" class="input-xlarge" style="margin-bottom: 0; margin-left: 6px;" data-bind="value: $root.collection.suggest.dictionary" placeholder="${ _('Dictionary name or blank for default') }">
+                </label>
+              </div>
+            </div>
           </fieldset>
         </form>
       </div>
@@ -2370,6 +2378,27 @@ function resizeFieldsList() {
   }, 100);
 }
 
+function queryTypeahead(query, process) {
+  var _source = viewModel.collection.template.fieldsNames();
+  _source = _source.concat("AND OR TO".split(" "))
+
+  if (viewModel.collection.suggest.enabled()) {
+    viewModel.suggest(query, function (data) {
+      var _tmp = [];
+      if (typeof data != "undefined" && data.response && data.response.suggest && data.response.suggest[viewModel.collection.suggest.dictionary()]) {
+        var suggestions = data.response.suggest[viewModel.collection.suggest.dictionary()][query].suggestions;
+        suggestions.forEach(function (sugg) {
+          _tmp.push(sugg.term + "<i class='muted fa fa-search'></i>");
+        });
+      }
+      process(_tmp.concat(_source));
+    });
+  }
+  else {
+    return _source;
+  }
+}
+
 $(document).ready(function () {
 
   var _resizeTimeout = -1;

+ 9 - 4
apps/search/src/search/views.py

@@ -263,13 +263,18 @@ def admin_collection_copy(request):
   return JsonResponse(response)
 
 
-def query_suggest(request, collection_id, query=""):
-  hue_collection = Collection.objects.get(id=collection_id)
-  result = {'status': -1, 'message': 'Error'}
+def query_suggest(request):
+  if request.method != 'POST':
+    raise PopupException(_('POST request required.'))
+
+  collection = json.loads(request.POST.get('collection', '{}'))
+  query = request.POST.get('query', '')
+
+  result = {'status': -1, 'message': ''}
 
   solr_query = {}
-  solr_query['collection'] = hue_collection.name
   solr_query['q'] = query
+  solr_query['dictionary'] = collection['suggest']['dictionary']
 
   try:
     response = SolrApi(SOLR_URL.get(), request.user).suggest(solr_query, hue_collection)

+ 33 - 22
desktop/core/src/desktop/static/desktop/js/ko.hue-bindings.js

@@ -1061,35 +1061,37 @@ ko.bindingHandlers.typeahead = {
     var elem = $(element);
     var valueAccessor = valueAccessor();
 
-    var _options = {
-      source: function () {
-        var _source = ko.utils.unwrapObservable(valueAccessor.source);
-        if (valueAccessor.extraKeywords) {
-          _source = _source.concat(valueAccessor.extraKeywords.split(" "))
-        }
+    var source = valueAccessor.nonBindableSource ? valueAccessor.nonBindableSource : function () {
+      var _source = ko.utils.unwrapObservable(valueAccessor.source);
+      if (valueAccessor.extraKeywords) {
+        _source = _source.concat(valueAccessor.extraKeywords.split(" "))
+      }
 
-        if (valueAccessor.sourceSuffix && _source) {
-          var _tmp = [];
-          _source.forEach(function(item){
-            _tmp.push(item + valueAccessor.sourceSuffix);
-          });
-          _source = _tmp;
-        }
-        return _source;
-      },
+      if (valueAccessor.sourceSuffix && _source) {
+        var _tmp = [];
+        _source.forEach(function (item) {
+          _tmp.push(item + valueAccessor.sourceSuffix);
+        });
+        _source = _tmp;
+      }
+      return _source;
+    }
+
+    var _options = {
+      source: source,
       onselect: function (val) {
         if (typeof valueAccessor.target == "function") {
           valueAccessor.target(val);
         }
         else {
-         valueAccessor.target = val;
+          valueAccessor.target = val;
         }
       }
     }
 
     function extractor(query, extractorSeparator) {
       var result = /([^ ]+)$/.exec(query);
-      if (extractorSeparator){
+      if (extractorSeparator) {
         result = new RegExp("([^\\" + extractorSeparator + "]+)$").exec(query);
       }
       if (result && result[1])
@@ -1121,12 +1123,17 @@ ko.bindingHandlers.typeahead = {
         if (valueAccessor.extraKeywords && valueAccessor.extraKeywords.split(" ").indexOf(item) > -1) {
           _separator = "";
         }
+        var isSpecialResult = false;
+        if (item.indexOf("<i ") > -1) {
+          _separator = "";
+          isSpecialResult = true;
+        }
         updateExtractors();
         if (_extractorFound != null) {
-          return _val.substring(0, _val.lastIndexOf(_extractorFound)) + _extractorFound + item + _separator;
+          return (isSpecialResult ? '"' : '') + _val.substring(0, _val.lastIndexOf(_extractorFound)) + _extractorFound + $.trim(item.replace(/<[^>]*>/gi, "")) + (isSpecialResult ? '"' : '') + _separator;
         }
         else {
-          return item + _separator;
+          return (isSpecialResult ? '"' : '') + $.trim(item.replace(/<[^>]*>/gi, "")) + (isSpecialResult ? '"' : '') + _separator;
         }
       }
       _options.matcher = function (item) {
@@ -1138,9 +1145,13 @@ ko.bindingHandlers.typeahead = {
           _options.highlighter = function (item) {
             updateExtractors();
             var _query = extractor(this.query, _extractorFound).replace(/[\-\[\]{}()*+?.:\\\^$|#\s]/g, '\\$&');
-            return item.replace(new RegExp('(' + _query + ')', 'ig'), function ($1, match) {
+            var _result = $.trim(item.replace(/<[^>]*>/gi, "")).replace(new RegExp('(' + _query + ')', 'ig'), function ($1, match) {
               return '<strong>' + match + '</strong>'
             });
+            if (item.indexOf("<i ") > -1) {
+              _result += " " + item.substr(item.indexOf("<i "));
+            }
+            return _result;
           }
     }
 
@@ -1180,7 +1191,7 @@ ko.bindingHandlers.typeahead = {
         valueAccessor.target(elem.val());
       }
       else {
-       valueAccessor.target = elem.val();
+        valueAccessor.target = elem.val();
       }
     });
   },
@@ -1202,7 +1213,7 @@ ko.bindingHandlers.typeahead = {
 
         if (valueAccessor.sourceSuffix && _source) {
           var _tmp = [];
-          _source.forEach(function(item){
+          _source.forEach(function (item) {
             _tmp.push(item + valueAccessor.sourceSuffix);
           });
           _source = _tmp;