Explorar o código

[search] Escape HTML cells in gridlayout

Romain Rigaux %!s(int64=11) %!d(string=hai) anos
pai
achega
11ea101a63

+ 75 - 2
apps/search/src/search/api.py

@@ -19,6 +19,10 @@
 import json
 import json
 import logging
 import logging
 import urllib
 import urllib
+import numbers
+
+from datetime import datetime
+from time import mktime
 
 
 from desktop.lib.exceptions_renderable import PopupException
 from desktop.lib.exceptions_renderable import PopupException
 from desktop.lib.rest.http_client import HttpClient, RestException
 from desktop.lib.rest.http_client import HttpClient, RestException
@@ -37,6 +41,75 @@ DEFAULT_USER = 'hue'
 def utf_quoter(what):
 def utf_quoter(what):
   return urllib.quote(unicode(what).encode('utf-8'), safe='~@#$&()*!+=:;,.?/\'')
   return urllib.quote(unicode(what).encode('utf-8'), safe='~@#$&()*!+=:;,.?/\'')
 
 
+def _guess_range_facet(widget_type, solr_api, collection, facet_field, properties, start=None, end=None, gap=None):
+  is_range = False
+  
+  try:
+    if widget_type == 'pie-widget':
+      SLOTS = 5
+    elif widget_type == 'facet-widget':
+      SLOTS = 10
+    else:
+      SLOTS = 100
+      
+    stats_json = solr_api.stats(collection['name'], [facet_field])
+    stat_facet = stats_json['stats']['stats_fields'][facet_field]
+    
+    if isinstance(stat_facet['min'], numbers.Number):
+      stats_min = int(stat_facet['min']) # if field is float, cast as float isinstance(y, float)
+      stats_max = int(stat_facet['max'])
+      if start is not None:
+        stats_min = max(start, stats_min)
+      if end is not None:
+        stats_max = min(end, stats_max)   
+             
+      if gap is None:
+        gap = (stats_max - stats_min) / SLOTS
+      if gap < 1:
+        gap = 1
+      is_range = True
+    elif 'T' in stat_facet['min']:
+      stats_min = stat_facet['min']
+      stats_max = stat_facet['max']
+      difference = (
+          mktime(datetime.strptime(stats_max, '%Y-%m-%dT%H:%M:%SZ').timetuple()) - 
+          mktime(datetime.strptime(stats_min, '%Y-%m-%dT%H:%M:%SZ').timetuple())
+      ) / SLOTS
+
+      if difference < 1:
+        unit = 'SECONDS'
+      elif difference < 60:
+        unit = 'MINUTES'
+      elif difference < 3600:
+        unit = 'HOURS'
+      elif difference < 3600 * 24:
+        unit = 'DAYS'
+      elif difference < 3600 * 24 * 30:
+        unit = 'MONTHS'        
+      else:
+        unit = 'YEARS'
+      gap = '+1' + unit      
+      is_range = True
+  except Exception, e:
+    # stats not supported on all the fields, like text
+    pass
+
+  if is_range:
+    properties.update({
+      'start': stats_min,
+      'end': stats_max,
+      'gap': gap,
+      'canRange': True,
+    }) 
+
+
+def _guess_gap(solr_api, collection, facet_field, start=None, end=None):
+  properties = {}
+  _guess_range_facet('range-widget', solr_api, collection, facet_field, properties, start=start, end=end)
+  return properties.get('gap')
+
+def _zoom_range_facet(facet, direction):
+  pass
 
 
 class SolrApi(object):
 class SolrApi(object):
   """
   """
@@ -109,7 +182,7 @@ class SolrApi(object):
       )
       )
       for facet in collection['facets']:
       for facet in collection['facets']:
         if facet['type'] == 'query':
         if facet['type'] == 'query':
-          params += (('facet.query', '%s' % facet['field']),)
+          params += (('facet.query', '%s' % facet['field']),)          
         elif facet['type'] == 'range':
         elif facet['type'] == 'range':
           params += tuple([
           params += tuple([
              ('facet.range', '{!ex=%s}%s' % (facet['field'], facet['field'])),
              ('facet.range', '{!ex=%s}%s' % (facet['field'], facet['field'])),
@@ -126,7 +199,7 @@ 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']])),)
         params += (('fq', ' '.join([urllib.unquote(utf_quoter('{!tag=%s}{!field f=%s}%s' % (fq['field'], fq['field'], _filter))) for _filter in fq['filter']])),)
       elif model_facet['type'] == 'range':
       elif model_facet['type'] == 'range':
         # Set end range if last range
         # Set end range if last range
-        if not fq['filter'].get('to'):           
+        if fq['filter'].get('to'):           
           fq['filter']['to'] = model_facet['properties']['end'] if model_facet else '*'
           fq['filter']['to'] = model_facet['properties']['end'] if model_facet else '*'
         params += (('fq', urllib.unquote(utf_quoter('{!tag=%s}%s:[%s TO %s}' % (fq['field'],fq['field'], fq['filter']['from'], fq['filter']['to'])))),)
         params += (('fq', urllib.unquote(utf_quoter('{!tag=%s}%s:[%s TO %s}' % (fq['field'],fq['field'], fq['filter']['from'], fq['filter']['to'])))),)
 
 

+ 3 - 9
apps/search/src/search/templates/search2.mako

@@ -88,7 +88,7 @@ ${ commonheader(_('Search'), "search", user, "60px") | n,unicode }
     <div class="draggable-widget" data-bind="draggable: draggablePie" title="${_('Pie Chart')}" rel="tooltip" data-placement="top"><a href="#"><i class="hcha hcha-pie-chart"></i></a></div>
     <div class="draggable-widget" data-bind="draggable: draggablePie" title="${_('Pie Chart')}" rel="tooltip" data-placement="top"><a href="#"><i class="hcha hcha-pie-chart"></i></a></div>
     <!-- <div class="draggable-widget" data-bind="draggable: draggableHit" title="${_('Hit Count')}" rel="tooltip" data-placement="top"><a href="#"><i class="fa fa-tachometer"></i></a></div> -->
     <!-- <div class="draggable-widget" data-bind="draggable: draggableHit" title="${_('Hit Count')}" rel="tooltip" data-placement="top"><a href="#"><i class="fa fa-tachometer"></i></a></div> -->
     <div class="draggable-widget" data-bind="draggable: draggableBar" title="${_('Bar Chart')}" rel="tooltip" data-placement="top"><a href="#"><i class="hcha hcha-bar-chart"></i></a></div>
     <div class="draggable-widget" data-bind="draggable: draggableBar" title="${_('Bar Chart')}" rel="tooltip" data-placement="top"><a href="#"><i class="hcha hcha-bar-chart"></i></a></div>
-    <div class="draggable-widget" data-bind="draggable: draggableHistogram" title="${_('Timeline')}" rel="tooltip" data-placement="top"><a href="#"><i class="fa fa-long-arrow-right"></i></a></div>
+    <div class="draggable-widget" data-bind="draggable: draggableHistogram" title="${_('Histogram')}" rel="tooltip" data-placement="top"><a href="#"><i class="fa fa-long-arrow-right"></i></a></div> <!-- hcha hcha-line-chart -->
     <div class="draggable-widget" data-bind="draggable: draggableFilter" title="${_('Filter Bar')}" rel="tooltip" data-placement="top"><a href="#"><i class="fa fa-filter"></i></a></div>
     <div class="draggable-widget" data-bind="draggable: draggableFilter" title="${_('Filter Bar')}" rel="tooltip" data-placement="top"><a href="#"><i class="fa fa-filter"></i></a></div>
     <div class="draggable-widget" data-bind="draggable: draggableMap" title="${_('Map')}" rel="tooltip" data-placement="top"><a href="#"><i class="hcha hcha-map-chart"></i></a></div>    
     <div class="draggable-widget" data-bind="draggable: draggableMap" title="${_('Map')}" rel="tooltip" data-placement="top"><a href="#"><i class="hcha hcha-map-chart"></i></a></div>    
   </div>
   </div>
@@ -206,8 +206,6 @@ ${ commonheader(_('Search'), "search", user, "60px") | n,unicode }
   <div class="row-fluid" data-bind="with: $root.getFacetFromQuery(id)">
   <div class="row-fluid" data-bind="with: $root.getFacetFromQuery(id)">
     <div data-bind="visible: $root.isEditing, with: $root.collection.getFacetById($parent.id())" style="margin-bottom: 20px">      
     <div data-bind="visible: $root.isEditing, with: $root.collection.getFacetById($parent.id())" style="margin-bottom: 20px">      
       ${ _('Label') }: <input type="text" data-bind="value: label" />
       ${ _('Label') }: <input type="text" data-bind="value: label" />
-      <br/>      
-      ${ _('Field') }: <input type="text" data-bind="value: field" />
     </div>  
     </div>  
 
 
     <span data-bind="text: query" />: <span data-bind="text: count" />
     <span data-bind="text: query" />: <span data-bind="text: count" />
@@ -224,7 +222,6 @@ ${ commonheader(_('Search'), "search", user, "60px") | n,unicode }
   <div class="row-fluid" data-bind="with: $root.getFacetFromQuery(id)">
   <div class="row-fluid" data-bind="with: $root.getFacetFromQuery(id)">
     <div data-bind="visible: $root.isEditing, with: $root.collection.getFacetById($parent.id())" style="margin-bottom: 20px">      
     <div data-bind="visible: $root.isEditing, with: $root.collection.getFacetById($parent.id())" style="margin-bottom: 20px">      
       ${ _('Label') }: <input type="text" data-bind="value: label" />
       ${ _('Label') }: <input type="text" data-bind="value: label" />
-      ${ _('Field') }: <input type="text" data-bind="value: field" />
       <!-- ko if: type() == 'range' -->
       <!-- ko if: type() == 'range' -->
         <br/>
         <br/>
         ${ _('Start') }: <input type="text" data-bind="value: properties.start" />
         ${ _('Start') }: <input type="text" data-bind="value: properties.start" />
@@ -387,8 +384,6 @@ ${ commonheader(_('Search'), "search", user, "60px") | n,unicode }
     <div data-bind="visible: $root.isEditing, with: $root.collection.getFacetById($parent.id())" style="margin-bottom: 20px">      
     <div data-bind="visible: $root.isEditing, with: $root.collection.getFacetById($parent.id())" style="margin-bottom: 20px">      
       ${ _('Label') }: <input type="text" data-bind="value: label" />
       ${ _('Label') }: <input type="text" data-bind="value: label" />
       <br/>      
       <br/>      
-      ${ _('Field') }: <input type="text" data-bind="value: field" />
-      <br/>
       ${ _('Start') }: <input type="text" data-bind="value: properties.start" />
       ${ _('Start') }: <input type="text" data-bind="value: properties.start" />
       ${ _('End') }: <input type="text" data-bind="value: properties.end" />
       ${ _('End') }: <input type="text" data-bind="value: properties.end" />
       ${ _('Gap') }: <input type="text" data-bind="value: properties.gap" />
       ${ _('Gap') }: <input type="text" data-bind="value: properties.gap" />
@@ -398,7 +393,8 @@ ${ commonheader(_('Search'), "search", user, "60px") | n,unicode }
     <a href="javascript:void(0)"><i class="fa fa-minus"></i></a>
     <a href="javascript:void(0)"><i class="fa fa-minus"></i></a>
 
 
     <div data-bind="timelineChart: {datum: {counts: counts, widget_id: $parent.id(), label: label}, field: field, label: label, transformer: timelineChartDataTransformer,
     <div data-bind="timelineChart: {datum: {counts: counts, widget_id: $parent.id(), label: label}, field: field, label: label, transformer: timelineChartDataTransformer,
-      onSelectRange: function(from, to){viewModel.query.selectRangeFacet({from: from, to: to, cat: field})}, onComplete: function(){viewModel.getWidgetById(id).isLoading(false)}}" />
+      onSelectRange: function(from, to){viewModel.collection.selectTimelineFacet({from: from, to: to, cat: field, widget_id: $parent.id()})},
+      onComplete: function(){viewModel.getWidgetById(id).isLoading(false)}}" />
   </div>
   </div>
   <!-- /ko -->
   <!-- /ko -->
 </script>
 </script>
@@ -417,7 +413,6 @@ ${ commonheader(_('Search'), "search", user, "60px") | n,unicode }
   <div class="row-fluid" data-bind="with: $root.getFacetFromQuery(id)">
   <div class="row-fluid" data-bind="with: $root.getFacetFromQuery(id)">
     <div data-bind="visible: $root.isEditing, with: $root.collection.getFacetById($parent.id())" style="margin-bottom: 20px">      
     <div data-bind="visible: $root.isEditing, with: $root.collection.getFacetById($parent.id())" style="margin-bottom: 20px">      
       ${ _('Label') }: <input type="text" data-bind="value: label" />
       ${ _('Label') }: <input type="text" data-bind="value: label" />
-      ${ _('Field') }: <input type="text" data-bind="value: field" />
       <br/>
       <br/>
       ${ _('Start') }: <input type="text" data-bind="value: properties.start" />
       ${ _('Start') }: <input type="text" data-bind="value: properties.start" />
       ${ _('End') }: <input type="text" data-bind="value: properties.end" />
       ${ _('End') }: <input type="text" data-bind="value: properties.end" />
@@ -442,7 +437,6 @@ ${ commonheader(_('Search'), "search", user, "60px") | n,unicode }
   <div class="row-fluid" data-bind="with: $root.getFacetFromQuery(id)">
   <div class="row-fluid" data-bind="with: $root.getFacetFromQuery(id)">
     <div data-bind="visible: $root.isEditing, with: $root.collection.getFacetById($parent.id())" style="margin-bottom: 20px">
     <div data-bind="visible: $root.isEditing, with: $root.collection.getFacetById($parent.id())" style="margin-bottom: 20px">
       ${ _('Label') }: <input type="text" data-bind="value: label" />
       ${ _('Label') }: <input type="text" data-bind="value: label" />
-      ${ _('Field') }: <input type="text" data-bind="value: field" />
     </div>
     </div>
     <div data-bind="with: $root.collection.getFacetById($parent.id())">
     <div data-bind="with: $root.collection.getFacetById($parent.id())">
       <!-- ko if: type() == 'range' -->
       <!-- ko if: type() == 'range' -->

+ 1 - 0
apps/search/src/search/urls.py

@@ -42,6 +42,7 @@ urlpatterns = patterns('search.views',
   url(r'^index/(?P<collection_id>\w+)/fields/dynamic$', 'index_fields_dynamic', name='index_fields_dynamic'),
   url(r'^index/(?P<collection_id>\w+)/fields/dynamic$', 'index_fields_dynamic', name='index_fields_dynamic'),
   url(r'^template/new_facet$', 'new_facet', name='new_facet'),
   url(r'^template/new_facet$', 'new_facet', name='new_facet'),
   url(r'^get_document$', 'get_document', name='get_document'),
   url(r'^get_document$', 'get_document', name='get_document'),
+  url(r'^get_range_facet$', 'get_range_facet', name='get_range_facet'),
   
   
   # old
   # old
   url(r'^admin/collection/(?P<collection_id>\w+)/schema$', 'admin_collection_schema', name='admin_collection_schema'),
   url(r'^admin/collection/(?P<collection_id>\w+)/schema$', 'admin_collection_schema', name='admin_collection_schema'),

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

@@ -31,7 +31,7 @@ from django.shortcuts import redirect
 from desktop.lib.django_util import render
 from desktop.lib.django_util import render
 from desktop.lib.exceptions_renderable import PopupException
 from desktop.lib.exceptions_renderable import PopupException
 
 
-from search.api import SolrApi
+from search.api import SolrApi, _guess_gap
 from search.conf import SOLR_URL
 from search.conf import SOLR_URL
 from search.data_export import download as export_download
 from search.data_export import download as export_download
 from search.decorators import allow_admin_only
 from search.decorators import allow_admin_only
@@ -578,6 +578,9 @@ def new_facet(request):
   return HttpResponse(json.dumps(result), mimetype="application/json")
   return HttpResponse(json.dumps(result), mimetype="application/json")
 
 
 
 
+def get_range_facet(request):
+  _guess_gap(self, collection, facet['field'], facet['properties']['start'], facet['properties']['end'])
+
 def install_examples(request):
 def install_examples(request):
   result = {'status': -1, 'message': ''}
   result = {'status': -1, 'message': ''}
 
 

+ 13 - 3
apps/search/static/js/search.ko.js

@@ -198,7 +198,7 @@ var Query = function (vm, query) {
   self.q = ko.observable(query.q);
   self.q = ko.observable(query.q);
   self.fqs = ko.mapping.fromJS(query.fqs);
   self.fqs = ko.mapping.fromJS(query.fqs);
 
 
-  self.toggleFacet = function (data) {//alert(ko.mapping.toJSON(data)); 
+  self.toggleFacet = function (data) {
 	var fq = self.getFacetFilter(data.widget_id);
 	var fq = self.getFacetFilter(data.widget_id);
 
 
 	if (fq == null) {
 	if (fq == null) {
@@ -226,11 +226,11 @@ var Query = function (vm, query) {
     vm.search();
     vm.search();
   }  
   }  
   
   
-  self.selectRangeFacet = function (data) {
+  self.selectRangeFacet = function (data) { 
 	var fq = self.getFacetFilter(data.widget_id);
 	var fq = self.getFacetFilter(data.widget_id);
 	var unselect = fq != null && fq.id() == data.widget_id;
 	var unselect = fq != null && fq.id() == data.widget_id;
 	
 	
-	self.removeFilter(ko.mapping.fromJS({'id': data.widget_id}));
+	self.removeFilter(ko.mapping.fromJS({'id': data.widget_id})); // could combine ranges
     
     
 	if (! unselect) {
 	if (! unselect) {
       self.fqs.push(ko.mapping.fromJS({
       self.fqs.push(ko.mapping.fromJS({
@@ -420,6 +420,16 @@ var Collection = function (vm, collection) {
     vm.search();
     vm.search();
   };
   };
   
   
+  self.selectTimelineFacet = function (data) { // alert(ko.mapping.toJSON(facet)); 
+	var facet = self.getFacetById(data.widget_id);
+	
+	facet.properties.start(data.from);
+	facet.properties.end(data.to);
+	//facet.properties.gap(null);
+
+    vm.search();
+  }    
+  
   self.translateSelectedField = function (index, direction) {
   self.translateSelectedField = function (index, direction) {
 	var array = self.template.fieldsSelected();
 	var array = self.template.fieldsSelected();
     if (direction == 'left') {
     if (direction == 'left') {