Ver Fonte

HUE-2332 [search] Exclusion facet

field facet
range facet
HUE-2331 [search] Single term filter query
Romain Rigaux há 11 anos atrás
pai
commit
811a2a3

+ 1 - 1
apps/search/src/search/models.py

@@ -440,7 +440,7 @@ def augment_solr_response(response, collection, query):
 
   normalized_facets = []
 
-  selected_values = dict([((fq['id'], fq['field'], fq['type']), fq['filter']) for fq in query['fqs']])
+  selected_values = dict([((fq['id'], fq['field'], fq['type']), [f['value'] for f in fq['filter']]) for fq in query['fqs']])
 
   if response and response.get('facet_counts'):
     # e.g. [{u'field': u'sun', u'type': u'query', u'id': u'67b43a63-ed22-747b-47e8-b31aad1431ea', u'label': u'sun'}

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

@@ -28,7 +28,7 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
 
 <div class="search-bar" style="height: 30px">
   <div class="pull-right">
-    % if 'indexer' in app:
+    % if user.has_hue_permission(action="access", app='indexer'):
     <a class="btn importBtn" href="${ url('indexer:collections') }">
       <i class="fa fa-database"></i> ${ _('Indexes') }
     </a>

+ 15 - 9
apps/search/src/search/templates/search.mako

@@ -174,8 +174,8 @@ ${ commonheader(_('Search'), "search", user, "80px") | n,unicode }
                        <i class="hcha hcha-timeline-chart"></i>
          </a>
     </div>
-    <div data-bind="css: { 'draggable-widget': true, 'disabled': !availableDraggableChart() },
-                    draggable: {data: draggableTree(), isEnabled: availableDraggableChart,
+    <div data-bind="css: { 'draggable-widget': true, 'disabled': false },
+                    draggable: {data: draggableTree(), isEnabled: true,
                     options: {'start': function(event, ui){lastWindowScrollPosition = $(window).scrollTop();$('.card-body').slideUp('fast');},
                               'stop': function(event, ui){$('.card-body').slideDown('fast', function(){$(window).scrollTop(lastWindowScrollPosition)});}}}"
          title="${_('Tree')}" rel="tooltip" data-placement="top">
@@ -241,8 +241,7 @@ ${ dashboard.layout_skeleton() }
       ${ _('Plot') }
       <select data-bind="selectedOptions: properties.scope" class="input-small">
         <option value="tree">${ _("Tree") }</option>
-        <option value="stack">${ _("Stacked Bars") }</option>
-        <option value="heat">${ _("Heat Map") }</option>
+        <option value="stack">${ _("Bars") }</option>
       </select>
 
       </br>
@@ -301,6 +300,7 @@ ${ dashboard.layout_skeleton() }
                 <!-- ko if: ! $data.selected -->
                   <span data-bind="text: $data.value, click: function(){ $root.query.toggleFacet({facet: $data, widget_id: $parent.id()}) }"></span>
                   <span class="counter" data-bind="text: ' (' + $data.count + ')', click: function(){ $root.query.toggleFacet({facet: $data, widget_id: $parent.id()}) }"></span>
+                  <i class="fa fa-minus" data-bind="click: function(){ $root.query.toggleFacet({facet: $data, widget_id: $parent.id(), 'exclude': true}) }"></i>
                 <!-- /ko -->
                 <!-- ko if: $data.selected -->
                   <span data-bind="click: function(){ $root.query.toggleFacet({facet: $data, widget_id: $parent.id()}) }">
@@ -335,10 +335,9 @@ ${ dashboard.layout_skeleton() }
           <div>
             <a href="javascript: void(0)">
               <!-- ko if: ! selected -->
-                <span data-bind="click: function(){ $root.query.selectRangeFacet({count: $data.value, widget_id: $parent.id(), from: $data.from, to: $data.to, cat: $data.field}) }">
-                  <span data-bind="text: $data.from + ' - ' + $data.to"></span>
-                  <span class="counter" data-bind="text: ' (' + $data.value + ')'"></span>
-                </span>
+                <span data-bind="text: $data.from + ' - ' + $data.to, click: function(){ $root.query.selectRangeFacet({count: $data.value, widget_id: $parent.id(), from: $data.from, to: $data.to, cat: $data.field}) }"></span>
+                <span class="counter" data-bind="text: ' (' + $data.value + ')', click: function(){ $root.query.selectRangeFacet({count: $data.value, widget_id: $parent.id(), from: $data.from, to: $data.to, cat: $data.field}) }"></span>
+                <i class="fa fa-minus" data-bind="click: function(){ $root.query.selectRangeFacet({count: $data.value, widget_id: $parent.id(), from: $data.from, to: $data.to, cat: $data.field, 'exclude': true}) }"></i>
               <!-- /ko -->
               <!-- ko if: selected -->
                 <span data-bind="click: function(){ $root.query.selectRangeFacet({count: $data.value, widget_id: $parent.id(), from: $data.from, to: $data.to, cat: $data.field}) }">
@@ -856,7 +855,7 @@ ${ dashboard.layout_skeleton() }
       <span data-bind="text: $data.field"></span>
       <br/>
       <strong>${_('value')}</strong>:
-      <span data-bind="text: $data.filter"></span>
+      <span data-bind="text: ko.mapping.toJSON($data.filter)"></span>
     </div>
     <!-- /ko -->
     <!-- ko if: $data.type() == 'range' -->
@@ -865,6 +864,7 @@ ${ dashboard.layout_skeleton() }
       <strong>${_('field')}</strong>:
       <span data-bind="text: $data.field"></span>
       <br/>
+      <span data-bind="text: ko.mapping.toJSON($data.filter)"></span>
       <span data-bind="foreach: $data.properties" style="font-weight: normal">
         <strong>${_('from')}</strong>: <span data-bind="text: $data.from"></span>
         <br/>
@@ -957,6 +957,12 @@ ${ dashboard.layout_skeleton() }
           <tbody data-bind="foreach: $data.data">
           <tr>
             <td data-bind="text: val.value"></td>
+            <td data-bind="click: $root.query.addSingleTermFacet">
+              <a href="javascript: void(0)"><i class="fa fa-plus" title="${ _('Select this value') }"></i></a>
+            </td>
+            <td data-bind="click: $root.query.removeSingleTermFacet">
+              <a href="javascript: void(0)"><i class="fa fa-minus" title="${ _('Exclude this value') }"></i></a>
+            </td>
             <td style="width: 40px">
               <div class="progress">
                 <div class="bar-label" data-bind="text:val.count"></div>

+ 46 - 8
apps/search/static/js/search.ko.js

@@ -114,19 +114,20 @@ var Query = function (vm, query) {
       self.fqs.push(ko.mapping.fromJS({
         'id': data.widget_id,
         'field': data.facet.cat,
-        'filter': [data.facet.value],
+        'filter': [{'exclude': data.exclude ? true : false, 'value': data.facet.value}],
         'type': 'field'
       }));
     } else {
       $.each(self.fqs(), function (index, fq) {
         if (fq.id() == data.widget_id) {
-          if (fq.filter.indexOf(data.facet.value) > -1) {
-            fq.filter.remove(data.facet.value);
+          var f = $.grep(fq.filter(), function(f) { return f.value() == data.facet.value; }); 
+          if (f.length > 0) {
+            fq.filter.remove(f[0]);
             if (fq.filter().length == 0) {
               self.fqs.remove(fq);
             }
           } else {
-            fq.filter.push(data.facet.value);
+            fq.filter.push(ko.mapping.fromJS({'exclude': data.exclude ? true : false, 'value': data.facet.value}));
           }
         }
       });
@@ -135,6 +136,24 @@ var Query = function (vm, query) {
     vm.search();
   }
 
+  function _toggleSingleTermFacet(data, exclude) {
+    var fq = getFilterByField(data.val.cat);
+    var id;
+    if (fq) {
+      id = fq.id();
+    } else {
+      id = '***single' + UUID(); // Tag single terms like this
+    }
+    self.toggleFacet({'widget_id': id, 'facet': {'cat': data.val.cat, 'value': data.val.value}, 'exclude': exclude});
+    vm.search();
+  }  
+  self.addSingleTermFacet = function(data) {
+    _toggleSingleTermFacet(data, false);
+  }
+  self.removeSingleTermFacet = function(data) {
+	  _toggleSingleTermFacet(data, true);
+  }
+
   self.selectRangeFacet = function (data) {
     if (data.force != undefined) {
       self.removeFilter(ko.mapping.fromJS({'id': data.widget_id}));
@@ -146,13 +165,14 @@ var Query = function (vm, query) {
       self.fqs.push(ko.mapping.fromJS({
           'id': data.widget_id,
           'field': data.cat,
-          'filter': [data.from],
+          'filter': [{'exclude': data.exclude ? true : false, 'value': data.from}],
           'properties': [{'from': data.from, 'to': data.to}],
           'type': 'range'
       }));
     } else {
-      if (fq.filter().indexOf(data.from) > -1) { // Unselect
-        fq.filter.remove(data.from);
+      var f = $.grep(fq.filter(), function(f) { return f.value() == data.from; });
+      if (f.length > 0) { // Unselect
+        fq.filter.remove(f[0]);
         $.each(fq.properties(), function (index, prop) {
           if (prop && prop.from() == data.from) {
             fq.properties.remove(prop);
@@ -162,7 +182,7 @@ var Query = function (vm, query) {
           self.removeFilter(ko.mapping.fromJS({'id': data.widget_id}));
         }
       } else {
-       fq.filter.push(data.from);
+       fq.filter.push(ko.mapping.fromJS({'exclude': data.exclude ? true : false, 'value': data.from}));
        fq.properties.push(ko.mapping.fromJS({'from': data.from, 'to': data.to}));
       }
     }
@@ -171,6 +191,17 @@ var Query = function (vm, query) {
       vm.search();
     }
   };
+  
+  function getFilterByField(field) {
+    var _fq = null;
+    $.each(self.fqs(), function (index, fq) {
+      if (fq.field() == field && fq.id().indexOf('***') == 0) {
+        _fq= fq;
+        return false;
+      }
+    });
+    return _fq;
+  };
 
   self.removeFilter = function (data) {
     var found = false;
@@ -549,6 +580,9 @@ var Collection = function (vm, collection) {
     else if (self.template.fieldsModalType() == 'line-widget') {
       return vm.availableNumberFields();
     }
+    else if (self.template.fieldsModalType() == 'tree-widget') {
+      return vm.availablePivotFields();
+    }
     else {
       return self.availableFacetFields();
     }
@@ -918,6 +952,9 @@ var SearchViewModel = function (collection_json, query_json, initial_json) {
   self.availableNumberFields = ko.computed(function() {
     return $.grep(self.collection.availableFacetFields(), function(field) { return NUMBER_TYPES.indexOf(field.type()) != -1; });
   });
+  self.availablePivotFields = ko.computed(function() {
+    return self.collection.fields();
+  });
 
   function getWidgets(equalsTo) {
     return $.map(self.columns(), function (col){return $.map(col.rows(), function(row){ return $.grep(row.widgets(), function(widget){ return equalsTo(widget); });}) ;})
@@ -942,6 +979,7 @@ var SearchViewModel = function (collection_json, query_json, initial_json) {
     return self.collection.availableFacetFields().length > 0;
   });
 
+
   self.init = function (callback) {
     self.initial.init();
     self.collection.syncFields();

+ 2 - 2
desktop/core/static/js/ko.charts.js

@@ -65,7 +65,7 @@ ko.bindingHandlers.pieChart = {
         if (_options.fqs) {
           $.each(_options.fqs(), function (cnt, item) {
             if (item.field() == _options.field()) {
-              _chart.selectSlices(item.filter());
+              _chart.selectSlices($.map(item.filter(), function(it) {return it.value();}));
             }
           });
         }
@@ -540,7 +540,7 @@ function barChartBuilder(element, options, isTimeline) {
 
       $.each(options.fqs(), function (cnt, item) {
         if (item.field() == options.field) {
-          _chart.selectBars(item.filter());
+          _chart.selectBars($.map(item.filter(), function(it) {return it.value();}));
         }
       });
 

+ 12 - 5
desktop/libs/libsolr/src/libsolr/api.py

@@ -72,13 +72,20 @@ class SolrApi(object):
         # params += (('fq', ' '.join([urllib.unquote(utf_quoter('{!tag=%s}{!field f=%s}%s' % (fq['field'], fq['field'], _filter))) for _filter in fq['filter']])),)
         f = []
         for _filter in fq['filter']:
-          if _filter is not None and ' ' in _filter:
-            f.append('%s:"%s"' % (fq['field'], _filter))
+          value = _filter['value']
+          exclude = '-' if _filter['exclude'] else ''
+          if value is not None and ' ' in value:
+            f.append('%s%s:"%s"' % (exclude, fq['field'], value))
           else:
-            f.append('{!field f=%s}%s' % (fq['field'], _filter))
-        params += (('fq', urllib.unquote(utf_quoter('{!tag=%s}' % fq['field'] + ' '.join(f)))),)
+            f.append('%s{!field f=%s}%s' % (exclude, fq['field'], value))
+        if fq['id'].startswith('***single'): # Do not tag Single term fq
+          _params = ' '.join(f)
+        else:
+          _params ='{!tag=%s}' % fq['field'] + ' '.join(f)
+        params += (('fq', urllib.unquote(utf_quoter(_params))),)
       elif fq['type'] == 'range':
-        params += (('fq', '{!tag=%s}' % fq['field'] + ' '.join([urllib.unquote(utf_quoter('%s:[%s TO %s}' % (fq['field'], f['from'], f['to']))) for f in fq['properties']])),)
+        params += (('fq', '{!tag=%s}' % fq['field'] + ' '.join([urllib.unquote(
+                    utf_quoter('%s%s:[%s TO %s}' % ('-' if field['exclude'] else '', fq['field'], f['from'], f['to']))) for field, f in zip(fq['filter'], fq['properties'])])),)
 
     return params