Преглед изворни кода

[search] Refactoring API and KO models

Romain Rigaux пре 11 година
родитељ
комит
b75ef6d16f

+ 1 - 0
apps/search/examples/collections/solr_configs_twitter_demo/conf/schema.xml

@@ -123,6 +123,7 @@
    <field name="in_reply_to_status_id" type="long" indexed="true" stored="true" multiValued="true"/>
    <field name="media_url_https" type="string" indexed="false" stored="true" />
    <field name="expanded_url" type="string" indexed="false" stored="true" />
+   <dynamicField name="*_i" type="int" indexed="true" stored="true"/>
 
    <!-- file metadata -->   
 <!--

+ 52 - 29
apps/search/src/search/api.py

@@ -35,7 +35,7 @@ DEFAULT_USER = 'hue'
 
 
 def utf_quoter(what):
-  return urllib.quote(unicode(what).encode('utf-8'), safe='~@#$&()*!+=;,.?/\'')
+  return urllib.quote(unicode(what).encode('utf-8'), safe='~@#$&()*!+=:;,.?/\'')
 
 
 class SolrApi(object):
@@ -92,39 +92,41 @@ class SolrApi(object):
       raise PopupException(e, title=_('Error while accessing Solr'))
 
   #@demo_handler
-  def query2(self, solr_query, dd):
-
-      params = self._get_params() + (
-          ('q', solr_query['q'] or EMPTY_QUERY.get()),
-          ('wt', 'json'),
-          ('rows', solr_query['rows']),
-          ('start', solr_query['start']),
+  def query2(self, solr_query, collection):
+    params = self._get_params() + (
+        ('q', solr_query['q'] or EMPTY_QUERY.get()),
+        ('wt', 'json'),
+        ('rows', solr_query['rows']),
+        ('start', solr_query['start']),
+    )
+
+    if collection['facets']:
+      params += (
+        ('facet', 'true'),
+        ('facet.mincount', 0),
+        ('facet.limit', 10),
+        ##('facet.sort', properties.get('sort')),
       )
+      params += tuple([('facet.field', '{!ex=%s}%s' % (facet['field'], facet['field'])) for facet in collection['facets']])
 
-      #params += hue_core.get_query(solr_query)
-      if dd:
-        params += (
-          ('facet', 'true'),
-          ('facet.mincount', 0),
-          ('facet.limit', 10),
-          ##('facet.sort', properties.get('sort')),
-        )
-        # {!ex=dt}
-        params += tuple([('facet.field', '{!ex=%s}%s' % (d['field'], d['field'])) for d in dd])
+    fqs = solr_query['fq']
+    for fq, val in fqs.iteritems():
+      params += (('fq', urllib.unquote(utf_quoter('{!tag=%s}{!field f=%s}%s' % (fq, fq, val)))),)
 
+#    if collection['fields']:
+      # If we do this, need to parse the template and fill up the fields list
+      #params += (('fl', urllib.unquote(utf_quoter(','.join(collection['fields'])))),)
+    params += (('fl', '*'),)
+    # To parameterize
+    params += (
+      ('hl', 'true'),
+      ('hl.fl', '*'),
+      ('hl.snippets', 3)
+    )
 
-      fqs = solr_query['fq'] #.split('|')
-      print 'fq'
-      print fqs
-      for fq, val in fqs.iteritems():
-        params += (('fq', urllib.unquote(utf_quoter('{!tag=%s}%s:%s' % (fq, fq, val)))),)
+    response = self._root.get('%(collection)s/select' % solr_query, params)
 
-      if solr_query.get('fl'):
-        params += (('fl', urllib.unquote(utf_quoter(','.join(solr_query['fl'])))),)
-
-      response = self._root.get('%(collection)s/select' % solr_query, params)
-
-      return self._get_json(response)
+    return self._get_json(response)
 
 
   def suggest(self, solr_query, hue_core):
@@ -205,3 +207,24 @@ class SolrApi(object):
       return self._get_json(response)
     except RestException, e:
       raise PopupException(e, title=_('Error while accessing Solr'))
+
+  def luke(self, core):
+    try:
+      params = self._get_params() + (
+          ('wt', 'json'),
+      )
+      response = self._root.get('%(core)s/admin/luke' % {'core': core}, params=params)
+      return self._get_json(response)    
+    except RestException, e:
+      raise PopupException(e, title=_('Error while accessing Solr'))
+
+  def schema_fields(self, core):
+    try:
+      params = self._get_params() + (
+          ('wt', 'json'),
+      )
+      response = self._root.get('%(core)s/schema/fields' % {'core': core}, params=params)
+      return self._get_json(response)    
+    except RestException, e:
+      raise PopupException(e, title=_('Error while accessing Solr'))
+    

+ 75 - 72
apps/search/src/search/models.py

@@ -18,6 +18,7 @@
 import itertools
 import json
 import logging
+import math
 import re
 
 from django.db import models
@@ -205,6 +206,12 @@ class CollectionManager(models.Manager):
     try:
       return self.get(name=name), False
     except Collection.DoesNotExist:
+#      id_field = ''
+#      schema_fields = SolrApi(SOLR_URL.get(), user).fields(self.name)      
+#      for name, props in schema_fields['schema']['fields'].iteritems():
+#        if props['stored'] and props['required'] and props['multiValued']:
+#          id_field = name
+
       facets = Facet.objects.create(data=json.dumps({
                    'properties': {'isEnabled': False, 'limit': 10, 'mincount': 1, 'sort': 'count'},
                    'ranges': [],
@@ -256,7 +263,6 @@ em {
 
 
 class Collection(models.Model):
-  # Perms coming with https://issues.cloudera.org/browse/HUE-950
   enabled = models.BooleanField(default=True)
   name = models.CharField(max_length=40, verbose_name=_t('Solr index name pointing to'))
   label = models.CharField(max_length=100, verbose_name=_t('Friendlier name in UI'))
@@ -273,6 +279,28 @@ class Collection(models.Model):
 
   objects = CollectionManager()
 
+  def get_c(self, user):
+    TEMPLATE = {
+      "extracode": "<style type=\"text/css\">\nem {\n  font-weight: bold;\n  background-color: yellow;\n}</style>", "highlighting": ["body"],
+      "properties": {"highlighting_enabled": True},
+      "template": "{{user_screen_name}} {{user_name}} {{text}}", "isGridLayout": True,
+      "fields": ["user_screen_name", "user_name", "text"]
+    };
+    FACETS = {"dates": [], "fields": [{
+         "uuid": "f6618a5c-bbba-2886-1886-bbcaf01409ca", "verbatim": "", "isVerbatim": False, "label": "Location", 
+         "field": "to", "type": "field"
+       }
+       ],
+       "charts": [], "properties": {"sort": "count", "mincount": 1, "isEnabled": True, "limit": 10, 'schemd_id_field': 'id'}, "ranges": [], "order": []
+    };  
+  
+    m = {
+      'id': self.id, 'name': self.name, 'template': TEMPLATE, 'facets': FACETS['fields'], 
+      'fields': self.fields(user)
+    };
+    
+    return json.dumps(m)
+
   def get_query(self, client_query=None):
     return self.facets.get_query_params() + self.result.get_query_params() + self.sorting.get_query_params(client_query)
 
@@ -286,12 +314,6 @@ class Collection(models.Model):
     schema_fields = SolrApi(SOLR_URL.get(), user).fields(self.name)
     schema_fields = schema_fields['schema']['fields']
 
-    dynamic_fields = []
-#    dynamic_fields = SolrApi(SOLR_URL.get(), user).fields(self.name, dynamic=True)
-#    dynamic_fields = dynamic_fields['fields']
-
-    schema_fields.update(dynamic_fields)
-
     return sorted([{'name': str(field), 'type': str(attributes.get('type', ''))}
                   for field, attributes in schema_fields.iteritems()])
 
@@ -394,20 +416,11 @@ def is_chart_field(field, charts):
   return found
 
 
-def augment_solr_response2(response, facets, solr_query):
+def augment_solr_response2(response, collection, solr_query):
   augmented = response
   augmented['normalized_facets'] = []
 
-  normalized_facets = {}
-  default_facets = []
-
-#  chart_facets = facets.get('charts', [])
-
-  def pairwise(iterable):
-      "s -> (s0,s1), (s1,s2), (s2, s3), ..."
-      a, b = itertools.tee(iterable)
-      next(b, None)
-      return list(itertools.izip(a, b))
+  normalized_facets = []
 
   def pairwise2(cat, fq, iterable):
       pairs = []
@@ -425,67 +438,57 @@ def augment_solr_response2(response, facets, solr_query):
         facet = {
           'field': cat,
           'type': 'field',
-          'label': get_facet_field_label(cat, facets),
+          'label': get_facet_field_label(cat, collection['facets']),
           'counts': pairwise2(cat, selected_field, response['facet_counts']['facet_fields'][cat]),
         }
-        uuid = '' #get_facet_field_uuid(cat, 'field', facets)
-        if uuid == '':
-          default_facets.append(facet)
-        else:
-          normalized_facets[uuid] = facet
+        normalized_facets.append(facet)
 
-#    if response['facet_counts']['facet_ranges']:
-#      for cat in response['facet_counts']['facet_ranges']:
-#        facet = {
-#          'field': cat,
-#          'type': 'chart' if is_chart_field(cat, chart_facets) else 'range',
-#          'label': get_facet_field_label(cat, 'range', facets),
-#          'counts': response['facet_counts']['facet_ranges'][cat]['counts'],
-#          'start': response['facet_counts']['facet_ranges'][cat]['start'],
-#          'end': response['facet_counts']['facet_ranges'][cat]['end'],
-#          'gap': response['facet_counts']['facet_ranges'][cat]['gap'],
-#        }
-#        uuid = get_facet_field_uuid(cat, 'range', facets)
-#        if uuid == '':
-#          default_facets.append(facet)
-#        else:
-#          normalized_facets[uuid] = facet
-#
-#    if response['facet_counts']['facet_dates']:
-#      for cat in response['facet_counts']['facet_dates']:
-#        facet = {
-#          'field': cat,
-#          'type': 'date',
-#          'label': get_facet_field_label(cat, 'date', facets),
-#          'format': get_facet_field_format(cat, 'date', facets),
-#          'start': response['facet_counts']['facet_dates'][cat]['start'],
-#          'end': response['facet_counts']['facet_dates'][cat]['end'],
-#          'gap': response['facet_counts']['facet_dates'][cat]['gap'],
-#        }
-#        counts = []
-#        for date, count in response['facet_counts']['facet_dates'][cat].iteritems():
-#          if date not in ('start', 'end', 'gap'):
-#            counts.append(date)
-#            counts.append(count)
-#        facet['counts'] = counts
-#
-#        uuid = get_facet_field_uuid(cat, 'date', facets)
-#        if uuid == '':
-#          default_facets.append(facet)
-#        else:
-#          normalized_facets[uuid] = facet
-
-#  for ordered_uuid in facets.get('order', []):
-#    try:
-#      augmented['normalized_facets'].append(normalized_facets[ordered_uuid])
-#    except:
-#      pass
+  # TODO HTML escape docs!
 
-  if default_facets:
-    augmented['normalized_facets'].extend(default_facets)
+  highlighted_fields = response.get('highlighting', [])
+  if highlighted_fields:
+    for doc in response['response']['docs']:
+      # TODO: Beware, schema requires an 'id' field, silently do nothing
+      if 'message_id' in doc and doc['message_id'] in highlighted_fields:
+        doc.update(response['highlighting'][doc['message_id']])
+        
+  response['total_pages'] = int(math.ceil((float(response['response']['numFound']) / float(solr_query['rows']))))
+  response['search_time'] = response['responseHeader']['QTime']
+
+  if normalized_facets:
+    augmented['normalized_facets'].extend(normalized_facets)
 
   return augmented
 
+def augment_solr_exception(response, collection, solr_query):
+  response.update(
+  {
+    "facet_counts": {   
+    },
+    "highlighting": {
+    },
+    "normalized_facets": [
+      {
+        "field": facet['field'],
+        "counts": [],
+        "type": facet['type'],
+        "label": facet['label']
+      }
+      for facet in collection['facets']
+    ],
+    "responseHeader": {
+      "status": -1,
+      "QTime": 0,
+      "params": {
+      }
+    },
+    "response": {
+      "start": 0,
+      "numFound": 0,
+      "docs": [
+      ]
+    }
+  }) 
 
 def augment_solr_response(response, facets, solr_query):
   augmented = response

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

@@ -142,7 +142,7 @@ ${ commonheader(_('Search'), "search", user, "29px") | n,unicode }
 </style>
 
 <script src="/static/ext/js/knockout-min.js" type="text/javascript" charset="utf-8"></script>
-<script src="/search/static/js/search.ko.js" type="text/javascript" charset="utf-8"></script>
+<script src="/search/static/js/collections.ko.js" type="text/javascript" charset="utf-8"></script>
 
 <script type="text/javascript">
 

+ 26 - 35
apps/search/src/search/templates/search2.mako

@@ -17,23 +17,14 @@
 <%!
 from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
-from django.utils.dateparse import parse_datetime
-from search.api import utf_quoter
-import urllib
-import math
-import time
 %>
 
-<%namespace name="macros" file="macros.mako" />
-
 ${ commonheader(_('Search'), "search", user, "90px") | n,unicode }
 
 <link rel="stylesheet" href="/search/static/css/search.css">
 <link href="/static/ext/css/hue-filetypes.css" rel="stylesheet">
 <script src="/static/ext/js/moment.min.js" type="text/javascript" charset="utf-8"></script>
 <script src="/search/static/js/search.utils.js" type="text/javascript" charset="utf-8"></script>
-<script src="/search/static/js/template.ko.js" type="text/javascript" charset="utf-8"></script>
-<script src="/search/static/js/query.ko.js" type="text/javascript" charset="utf-8"></script>
 
 
 <div class="search-bar">
@@ -50,17 +41,16 @@ ${ commonheader(_('Search'), "search", user, "90px") | n,unicode }
       <div class="selectMask">
         <span class="current-collection"></span>
         <ul class="unstyled">
-          % if user.is_superuser:
-            <li><a class="dropdown-collection" href="#" data-value="${ hue_collection.id }" data-settings-url="${ hue_collection.get_absolute_url() }">${ hue_collection.label }</a></li>
-          % else:
-            <li><a class="dropdown-hue_collection" href="#" data-value="${ hue_collection.id }">${ hue_collection.label }</a></li>
-          % endif
+          <li><a class="dropdown-collection" href="#" data-value="${ hue_collection.id }" data-settings-url="${ hue_collection.get_absolute_url() }">${ hue_collection.label }</a></li>
         </ul>
       </div>
 
-      ${ search_form | n,unicode }
+      <input data-bind="value: query.q" name="query" maxlength="256" type="text" class="search-query input-xxlarge" id="id_query" style="cursor: auto;">
       
       <button type="submit" id="search-btn" class="btn btn-inverse"><i class="fa fa-search"></i></button>
+      <span style="padding-left:15px">
+        <button type="button" id="download-btn" class="btn btn-inverse dropdown-toggle" data-toggle="dropdown"><i class="fa fa-download"></i></button>
+      </span>
     </div>
   </form>
 </div>
@@ -89,18 +79,17 @@ ${ commonheader(_('Search'), "search", user, "90px") | n,unicode }
         <span class="pull-right">
           <a href="javascript:void(0)" data-bind="click: editFacet"><i class="fa fa-pencil"></i></a>
           <a href="javascript:void(0)" data-bind="click: $root.removeFacet"><i class="fa fa-times"></i></a>
-          <i class="fa fa-arrows" id="move-facet"></i>
         </span>
         <div data-bind="text: label"></div>
         <div data-bind="foreach: counts">
           <div>
             <a href="script:void(0)">
             <!-- ko if: selected -->
-              <span data-bind="text: value, click: $root.unselectFacet"></span>
-              <i data-bind="click: $root.unselectFacet" class="fa fa-times"></i>            
+              <span data-bind="text: value, click: $root.query.unselectFacet"></span>
+              <i data-bind="click: $root.query.unselectFacet" class="fa fa-times"></i>            
             <!-- /ko -->
             <!-- ko if: !selected -->           
-              <span data-bind="text: value, click: $root.selectFacet"></span> (<span data-bind="text: count, click: $root.selectFacet"></span>)            
+              <span data-bind="text: value, click: $root.query.selectFacet"></span> (<span data-bind="text: count, click: $root.query.selectFacet"></span>)            
             <!-- /ko -->
             </a>
           </div>
@@ -122,21 +111,21 @@ ${ commonheader(_('Search'), "search", user, "90px") | n,unicode }
         </span>
       </div>
       
-      <!-- ko if: $root.template.isGridLayout() -->
+      <!-- ko if: $root.collection.template.isGridLayout() -->
       <table id="result-container">        
         <thead>
-          <tr data-bind="foreach: $root.template.fields">
+          <tr data-bind="foreach: $root.collection.template.fields">
             <th data-bind="text: $data"></th>
           </tr>
         </thead>
         <tbody data-bind="foreach: results">
           <tr class="result-row" data-bind="foreach: $data">
-            <td data-bind="text: $data"></td>
+            <td data-bind="html: $data"></td>
           </tr>
         </tbody>
       </table>
       <!-- /ko -->
-      <!-- ko if: !$root.template.isGridLayout() -->
+      <!-- ko if: ! $root.collection.template.isGridLayout() -->
       <div id="result-container" data-bind="foreach: results">
         <div class="result-row" data-bind="html: $data"></div>
       </div>
@@ -166,6 +155,12 @@ ${ commonheader(_('Search'), "search", user, "90px") | n,unicode }
 </div>
 </script>
 
+<style type="text/css">
+  em {
+    font-weight: bold; 
+    background-color: yellow;
+  }
+</style>
 
 <div id="addFacetModal" class="modal hide fade">
   <div class="modal-header">
@@ -222,18 +217,18 @@ ${ commonheader(_('Search'), "search", user, "90px") | n,unicode }
       <div class="clearfix"></div>
       <div style="margin-top: 20px">        
         <p>
-          ${ _('Grid result') }: <input type="checkbox" data-bind="checked: template.isGridLayout" />
+          ${ _('Grid result') }: <input type="checkbox" data-bind="checked: collection.template.isGridLayout" />
         </p>
         
-        <!-- ko if: template.isGridLayout() -->
+        <!-- ko if: $root.collection.template.isGridLayout() -->
         <p>
           ${ _('Fields') }
-          <select data-bind="options: fields, selectedOptions: template.fields" size="5" multiple="true"></select>
+          <select data-bind="options: collection.fields, selectedOptions: collection.template.fields" size="5" multiple="true"></select>
         </p>  
         <!-- /ko -->
         
-        <!-- ko if: !template.isGridLayout() -->
-        <textarea data-bind="value: template.template, valueUpdate:'afterkeydown'"></textarea>
+        <!-- ko if: ! $root.collection.template.isGridLayout() -->
+        <textarea data-bind="value: collection.template.template, valueUpdate:'afterkeydown'"></textarea>
         <!-- /ko -->
       </div>
     </p>
@@ -243,18 +238,16 @@ ${ commonheader(_('Search'), "search", user, "90px") | n,unicode }
   </div>
 </div>
 
-##<script src="/search/static/js/query.ko.js" type="text/javascript" charset="utf-8"></script>
-
 <script src="/static/ext/js/knockout-min.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/knockout.mapping-2.3.2.js" type="text/javascript" charset="utf-8"></script>
 <script src="/static/ext/js/mustache.js"></script>
-<script src="/search/static/js/template.ko.js" type="text/javascript" charset="utf-8"></script>
+<script src="/search/static/js/search.ko.js" type="text/javascript" charset="utf-8"></script>
 
 <script type="text/javascript" charset="utf-8">
 var viewModel;
 
 $(document).ready(function () {
-  viewModel = new SearchViewModel(${ hue_collection.result.data | n,unicode }, ${ hue_collection.facets.data | n,unicode });
+  viewModel = new SearchViewModel(${ hue_collection.get_c(user) | n,unicode }, ${ hue_query | n,unicode });
   ko.applyBindings(viewModel);
   
   viewModel.search();
@@ -264,7 +257,7 @@ $(document).ready(function () {
   });
 
   $("#submitAddFacetModal").click(function() {
-    viewModel.addFacet({'name': $("#facetName").val()});
+    viewModel.collection.addFacet({'name': $("#facetName").val()});
     $('#addFacetModal').modal("hide");
     viewModel.search();
   });
@@ -272,8 +265,6 @@ $(document).ready(function () {
   $("#edit-template").click(function() {
     $("#editTemplateModal").modal("show");
   });
-  
-
 });
 
   function editFacet(facet) {

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

@@ -21,6 +21,7 @@ urlpatterns = patterns('search.views',
   url(r'^$', 'index', name='index'),
   url(r'^index2$', 'index2', name='index2'),
   url(r'^query2$', 'index2', name='query2'),
+  url(r'^search$', 'search', name='search'),
   url(r'^query$', 'index', name='query'),
   url(r'^download/(?P<format>(csv|xls))$', 'download', name='download'),
 
@@ -38,10 +39,13 @@ urlpatterns = patterns('search.views',
 
   # Ajax
   url(r'^suggest/(?P<collection_id>\w+)/(?P<query>\w+)?$', 'query_suggest', name='query_suggest'),
+  url(r'^index/(?P<collection_id>\w+)/fields/dynamic$', 'index_fields_dynamic', name='index_fields_dynamic'),
   url(r'^admin/collection/(?P<collection_id>\w+)/schema$', 'admin_collection_schema', name='admin_collection_schema'),
   url(r'^admin/collection/(?P<collection_id>\w+)/solr_properties$', 'admin_collection_solr_properties', name='admin_collection_solr_properties'),
   url(r'^admin/collection_delete$', 'admin_collection_delete', name='admin_collection_delete'),
   url(r'^admin/collection_copy$', 'admin_collection_copy', name='admin_collection_copy'),
   
+  
+  
   url(r'^install_examples$', 'install_examples', name='install_examples'),
 )

+ 55 - 1211
apps/search/src/search/views.py

@@ -34,7 +34,8 @@ from search.data_export import download as export_download
 from search.decorators import allow_admin_only
 from search.forms import QueryForm, CollectionForm
 from search.management.commands import search_setup
-from search.models import Collection, augment_solr_response, augment_solr_response2
+from search.models import Collection, augment_solr_response, augment_solr_response2,\
+  augment_solr_exception
 from search.search_controller import SearchController
 
 from django.utils.encoding import force_unicode
@@ -109,33 +110,40 @@ def index2(request):
     else:
       return no_collections(request)
 
-  init_collection = initial_collection(request, hue_collections)
+  collection_id = request.GET.get('collection')
+  hue_collection = Collection.objects.get(id=collection_id) # TODO perms HUE-1987
+  hue_query = {'q': '', 'fq': {}}
 
-  search_form = QueryForm(request.POST, initial_collection=init_collection)
-  response = {}
-  solr_query = {}
+  return render('search2.mako', request, {
+    'hue_collection': hue_collection,
+    'hue_query': hue_query,
+  })
 
-  if search_form.is_valid():    
-    try:
-      collection_id = search_form.cleaned_data['collection']
-      hue_collection = Collection.objects.get(id=collection_id)
 
-      solr_query = search_form.solr_query_dict
-      print request.POST
-      # if selected facets --> add fq fields + exlcude tag y/n
-      fcets = json.loads(request.POST.get('facets', '[]'))
-      solr_query['fq'] = json.loads(request.POST.get('fq', '{}'))
-      solr_query['q'] = json.loads(request.POST.get('q', '""'))
-      template = json.loads(request.POST.get('template', '{}'))
-      #solr_query['fl'] = template.get('fields', []) # if we do this, need to parse the template and fill up the fields list
-      print solr_query
-      print fcets, '===='
-      response = SolrApi(SOLR_URL.get(), request.user).query2(solr_query, fcets)
-
-      if hue_collection is not None:
-        response = augment_solr_response2(response, fcets, solr_query)
-      solr_query['total_pages'] = int(math.ceil((float(response['response']['numFound']) / float(solr_query['rows']))))
-      solr_query['search_time'] = response['responseHeader']['QTime']
+def search(request):
+  response = {}  
+  
+  collection = json.loads(request.POST.get('collection', '{}')) # TODO perms
+  query = json.loads(request.POST.get('query', '{}'))
+  
+  print request.POST
+  print collection
+    
+ 
+  if collection:
+    solr_query = {}    
+    try:
+      hue_collection = Collection.objects.get(id=collection['id']) # TODO perms
+      solr_query = {}      
+      
+      solr_query['collection'] = collection['name'] # TODO perms
+      solr_query['rows'] = 10
+      solr_query['start'] = 0
+      solr_query['fq'] = query['fq']
+      solr_query['q'] = query['q']
+      
+      response = SolrApi(SOLR_URL.get(), request.user).query2(solr_query, collection)
+      response = augment_solr_response2(response, collection, solr_query)
     except RestException, e:
       try:
         response['error'] = json.loads(e.message)['error']['msg']
@@ -149,50 +157,10 @@ def index2(request):
     response['error'] = _('There is no collection to search.')
 
   if 'error' in response:
-    response.update(
-    {
-      "facet_counts": {   
-      },
-      "highlighting": {
-      },
-      "normalized_facets": [
-        {
-          "field": "user_location",
-          "counts": [],
-          "type": "field",
-          "label": "Location"
-        },
-        {
-          "field": "not_there",
-          "counts": [],
-          "type": "field",
-          "label": "Bad facet"
-        },
-      ],
-      "responseHeader": {
-        "status": -1,
-        "QTime": 0,
-        "params": {
-        }
-      },
-      "response": {
-        "start": 0,
-        "numFound": 0,
-        "docs": [
-        ]
-      }
-    }) 
+    augment_solr_exception(response, collection, solr_query)
 
-  if request.GET.get('format') == 'json':
-    return HttpResponse(json.dumps(response), mimetype="application/json")
+  return HttpResponse(json.dumps(response), mimetype="application/json")
 
-  return render('search2.mako', request, {
-    'search_form': search_form,
-    'response': json.dumps(response),
-    'solr_query': solr_query,
-    'hue_collection': hue_collection,
-    'current_collection': collection_id,
-  })
 
 def download(request, format):
   hue_collections = SearchController(request.user).get_search_collections()
@@ -493,6 +461,26 @@ def query_suggest(request, collection_id, query=""):
   return HttpResponse(json.dumps(result), mimetype="application/json")
 
 
+# TODO security
+def index_fields_dynamic(request, collection_id):
+  hue_collection = Collection.objects.get(id=collection_id)
+  result = {'status': -1, 'message': 'Error'}
+
+  solr_query = {}
+  solr_query['collection'] = hue_collection.name
+
+  try:
+    dynamic_fields = SolrApi(SOLR_URL.get(), request.user).luke(hue_collection.name)
+    result['message'] = ''
+    result['dynamic_fields'] = [name for name, properties in dynamic_fields['fields'].iteritems() if 'dynamicBase' in properties]
+    result['status'] = 0
+  except Exception, e:
+    result['message'] = unicode(str(e), "utf8")
+
+  return HttpResponse(json.dumps(result), mimetype="application/json")
+
+
+
 def install_examples(request):
   result = {'status': -1, 'message': ''}
 
@@ -507,1147 +495,3 @@ def install_examples(request):
       result['message'] = str(e)
 
   return HttpResponse(json.dumps(result), mimetype="application/json")
-
-MOCK = """{
-  "facet_counts": {
-    "facet_ranges": {
-      "created_at": {
-        "start": "2014-02-18T12:00:00Z",
-        "counts": [
-          "2014-02-25T16:05:00Z",
-          97,
-          "2014-02-25T16:10:00Z",
-          92,
-          "2014-02-25T16:15:00Z",
-          115,
-          "2014-02-25T16:20:00Z",
-          83,
-          "2014-02-25T16:25:00Z",
-          108,
-          "2014-02-25T16:30:00Z",
-          120,
-          "2014-02-25T16:35:00Z",
-          98,
-          "2014-02-25T16:40:00Z",
-          101,
-          "2014-02-25T16:45:00Z",
-          110,
-          "2014-02-25T16:50:00Z",
-          100,
-          "2014-02-25T16:55:00Z",
-          96,
-          "2014-02-25T17:00:00Z",
-          119,
-          "2014-02-25T17:05:00Z",
-          125,
-          "2014-02-25T17:10:00Z",
-          115,
-          "2014-02-25T17:15:00Z",
-          116,
-          "2014-02-25T17:20:00Z",
-          107,
-          "2014-02-25T17:25:00Z",
-          139,
-          "2014-02-25T17:30:00Z",
-          131,
-          "2014-02-25T17:35:00Z",
-          123,
-          "2014-02-25T17:40:00Z",
-          125,
-          "2014-02-25T17:45:00Z",
-          125,
-          "2014-02-25T17:50:00Z",
-          102,
-          "2014-02-25T17:55:00Z",
-          149,
-          "2014-02-25T18:00:00Z",
-          130,
-          "2014-02-25T18:05:00Z",
-          123,
-          "2014-02-25T18:10:00Z",
-          145,
-          "2014-02-25T18:15:00Z",
-          108,
-          "2014-02-25T18:20:00Z",
-          133,
-          "2014-02-25T18:25:00Z",
-          132,
-          "2014-02-25T18:30:00Z",
-          162,
-          "2014-02-25T18:35:00Z",
-          142,
-          "2014-02-25T18:40:00Z",
-          139,
-          "2014-02-25T18:45:00Z",
-          156,
-          "2014-02-25T18:50:00Z",
-          132,
-          "2014-02-25T18:55:00Z",
-          159,
-          "2014-02-25T19:00:00Z",
-          157,
-          "2014-02-25T19:05:00Z",
-          136,
-          "2014-02-25T19:10:00Z",
-          137,
-          "2014-02-25T19:15:00Z",
-          164,
-          "2014-02-25T19:20:00Z",
-          132,
-          "2014-02-25T19:25:00Z",
-          154,
-          "2014-02-25T19:30:00Z",
-          187,
-          "2014-02-25T19:35:00Z",
-          161,
-          "2014-02-25T19:40:00Z",
-          159,
-          "2014-02-25T19:45:00Z",
-          144,
-          "2014-02-25T19:50:00Z",
-          157,
-          "2014-02-25T19:55:00Z",
-          146,
-          "2014-02-25T20:00:00Z",
-          193,
-          "2014-02-25T20:05:00Z",
-          175,
-          "2014-02-25T20:10:00Z",
-          189,
-          "2014-02-25T20:15:00Z",
-          182,
-          "2014-02-25T20:20:00Z",
-          168,
-          "2014-02-25T20:25:00Z",
-          171,
-          "2014-02-25T20:30:00Z",
-          175,
-          "2014-02-25T20:35:00Z",
-          169,
-          "2014-02-25T20:40:00Z",
-          182,
-          "2014-02-25T20:45:00Z",
-          163,
-          "2014-02-25T20:50:00Z",
-          186,
-          "2014-02-25T20:55:00Z",
-          184,
-          "2014-02-25T21:00:00Z",
-          216,
-          "2014-02-25T21:05:00Z",
-          202,
-          "2014-02-25T21:10:00Z",
-          176,
-          "2014-02-25T21:15:00Z",
-          196,
-          "2014-02-25T21:20:00Z",
-          192,
-          "2014-02-25T21:25:00Z",
-          189,
-          "2014-02-25T21:30:00Z",
-          190,
-          "2014-02-25T21:35:00Z",
-          187,
-          "2014-02-25T21:40:00Z",
-          215,
-          "2014-02-25T21:45:00Z",
-          197,
-          "2014-02-25T21:50:00Z",
-          174,
-          "2014-02-25T21:55:00Z",
-          179,
-          "2014-02-25T22:00:00Z",
-          216,
-          "2014-02-25T22:05:00Z",
-          185,
-          "2014-02-25T22:10:00Z",
-          178,
-          "2014-02-25T22:15:00Z",
-          219,
-          "2014-02-25T22:20:00Z",
-          190,
-          "2014-02-25T22:25:00Z",
-          190,
-          "2014-02-25T22:30:00Z",
-          196,
-          "2014-02-25T22:35:00Z",
-          176,
-          "2014-02-25T22:40:00Z",
-          215,
-          "2014-02-25T22:45:00Z",
-          212,
-          "2014-02-25T22:50:00Z",
-          200,
-          "2014-02-25T22:55:00Z",
-          216,
-          "2014-02-25T23:00:00Z",
-          225,
-          "2014-02-25T23:05:00Z",
-          186,
-          "2014-02-25T23:10:00Z",
-          181,
-          "2014-02-25T23:15:00Z",
-          213,
-          "2014-02-25T23:20:00Z",
-          214,
-          "2014-02-25T23:25:00Z",
-          189,
-          "2014-02-25T23:30:00Z",
-          188,
-          "2014-02-25T23:35:00Z",
-          174,
-          "2014-02-25T23:40:00Z",
-          199,
-          "2014-02-25T23:45:00Z",
-          165,
-          "2014-02-25T23:50:00Z",
-          198,
-          "2014-02-25T23:55:00Z",
-          158,
-          "2014-02-26T00:00:00Z",
-          193,
-          "2014-02-26T00:05:00Z",
-          168,
-          "2014-02-26T00:10:00Z",
-          186,
-          "2014-02-26T00:15:00Z",
-          182,
-          "2014-02-26T00:20:00Z",
-          174,
-          "2014-02-26T00:25:00Z",
-          185,
-          "2014-02-26T00:30:00Z",
-          184,
-          "2014-02-26T00:35:00Z",
-          157,
-          "2014-02-26T00:40:00Z",
-          161,
-          "2014-02-26T00:45:00Z",
-          158,
-          "2014-02-26T00:50:00Z",
-          175,
-          "2014-02-26T00:55:00Z",
-          151,
-          "2014-02-26T01:00:00Z",
-          203,
-          "2014-02-26T01:05:00Z",
-          154,
-          "2014-02-26T01:10:00Z",
-          158,
-          "2014-02-26T01:15:00Z",
-          153,
-          "2014-02-26T01:20:00Z",
-          141,
-          "2014-02-26T01:25:00Z",
-          150,
-          "2014-02-26T01:30:00Z",
-          165,
-          "2014-02-26T01:35:00Z",
-          152,
-          "2014-02-26T01:40:00Z",
-          161,
-          "2014-02-26T01:45:00Z",
-          178,
-          "2014-02-26T01:50:00Z",
-          145,
-          "2014-02-26T01:55:00Z",
-          161,
-          "2014-02-26T02:00:00Z",
-          171,
-          "2014-02-26T02:05:00Z",
-          151,
-          "2014-02-26T02:10:00Z",
-          141,
-          "2014-02-26T02:15:00Z",
-          145,
-          "2014-02-26T02:20:00Z",
-          149,
-          "2014-02-26T02:25:00Z",
-          131,
-          "2014-02-26T02:30:00Z",
-          134,
-          "2014-02-26T02:35:00Z",
-          142,
-          "2014-02-26T02:40:00Z",
-          133,
-          "2014-02-26T02:45:00Z",
-          157,
-          "2014-02-26T02:50:00Z",
-          154,
-          "2014-02-26T02:55:00Z",
-          146,
-          "2014-02-26T03:00:00Z",
-          124,
-          "2014-02-26T03:05:00Z",
-          147,
-          "2014-02-26T03:10:00Z",
-          142,
-          "2014-02-26T03:15:00Z",
-          137,
-          "2014-02-26T03:20:00Z",
-          139,
-          "2014-02-26T03:25:00Z",
-          156,
-          "2014-02-26T03:30:00Z",
-          18,
-          "2014-02-26T04:10:00Z",
-          15,
-          "2014-02-26T04:15:00Z",
-          65,
-          "2014-02-26T04:20:00Z",
-          53,
-          "2014-02-26T04:25:00Z",
-          66,
-          "2014-02-26T04:30:00Z",
-          65,
-          "2014-02-26T04:35:00Z",
-          57,
-          "2014-02-26T04:40:00Z",
-          61
-        ],
-        "end": "2014-02-28T12:00:00Z",
-        "gap": "+5MINUTES"
-      },
-      "user_followers_count": {
-        "start": 0,
-        "counts": [
-          "0",
-          4585,
-          "100",
-          3725,
-          "200",
-          2719,
-          "300",
-          1881,
-          "400",
-          1346,
-          "500",
-          966,
-          "600",
-          709,
-          "700",
-          668,
-          "800",
-          448,
-          "900",
-          383
-        ],
-        "end": 1000,
-        "gap": 100
-      },
-      "user_statuses_count": {
-        "start": 0,
-        "counts": [
-          "0",
-          3981,
-          "1000",
-          2223,
-          "2000",
-          1701,
-          "3000",
-          1270,
-          "4000",
-          1051,
-          "5000",
-          922,
-          "6000",
-          784,
-          "7000",
-          770,
-          "8000",
-          603,
-          "9000",
-          587
-        ],
-        "end": 10000,
-        "gap": 1000
-      }
-    },
-    "facet_fields": {
-      "user_location": [
-        "indonesia",
-        2897,
-        "venezuela",
-        1798,
-        "london",
-        1783,
-        "istanbul",
-        1674,
-        "philippines",
-        1284,
-        "argentina",
-        1199,
-        "brasil",
-        1017,
-        "thailand",
-        1009,
-        "jakarta",
-        912,
-        "paris",
-        911,
-        "uk",
-        902,
-        "france",
-        862,
-        "\u6771\u4eac",
-        809,
-        "malaysia",
-        758,
-        "japan",
-        719,
-        "usa",
-        696,
-        "madrid",
-        680,
-        "espa\u00f1a",
-        659,
-        "\u5927\u962a",
-        591,
-        "new york",
-        581,
-        "t\u00fcrkiye",
-        477
-      ]
-    },
-    "facet_dates": {},
-    "facet_queries": {}
-  },
-  "highlighting": {
-    "438585496994725888": {},
-    "438585509556658176": {},
-    "438585555710791680": {},
-    "438585614410063872": {},
-    "438585664741703681": {},
-    "438585618617352192": {},
-    "438585568302489600": {},
-    "438585593438560256": {},
-    "438585606034046976": {},
-    "438585664745906176": {},
-    "438585526355238912": {},
-    "438585601831763968": {},
-    "438585618612756480": {},
-    "438585501180653568": {},
-    "438585580856045568": {}
-  },
-  "normalized_facets": [
-    {
-      "field": "user_location",
-      "counts": [
-        "indonesia",
-        2897,
-        "venezuela",
-        1798,
-        "london",
-        1783,
-        "istanbul",
-        1674,
-        "philippines",
-        1284,
-        "argentina",
-        1199,
-        "brasil",
-        1017,
-        "thailand",
-        1009,
-        "jakarta",
-        912,
-        "paris",
-        911,
-        "uk",
-        902,
-        "france",
-        862,
-        "\u6771\u4eac",
-        809,
-        "malaysia",
-        758,
-        "japan",
-        719,
-        "usa",
-        696,
-        "madrid",
-        680,
-        "espa\u00f1a",
-        659,
-        "\u5927\u962a",
-        591,
-        "new york",
-        581,
-        "t\u00fcrkiye",
-        477
-      ],
-      "type": "field",
-      "label": "Location"
-    },
-    {
-      "end": 1000,
-      "start": 0,
-      "label": "Followers count",
-      "field": "user_followers_count",
-      "counts": [
-        "0",
-        4585,
-        "100",
-        3725,
-        "200",
-        2719,
-        "300",
-        1881,
-        "400",
-        1346,
-        "500",
-        966,
-        "600",
-        709,
-        "700",
-        668,
-        "800",
-        448,
-        "900",
-        383
-      ],
-      "gap": 100,
-      "type": "range"
-    },
-    {
-      "end": 10000,
-      "start": 0,
-      "label": "Tweet count",
-      "field": "user_statuses_count",
-      "counts": [
-        "0",
-        3981,
-        "1000",
-        2223,
-        "2000",
-        1701,
-        "3000",
-        1270,
-        "4000",
-        1051,
-        "5000",
-        922,
-        "6000",
-        784,
-        "7000",
-        770,
-        "8000",
-        603,
-        "9000",
-        587
-      ],
-      "gap": 1000,
-      "type": "range"
-    },
-    {
-      "end": "2014-02-28T12:00:00Z",
-      "start": "2014-02-18T12:00:00Z",
-      "label": "created_at",
-      "field": "created_at",
-      "counts": [
-        "2014-02-25T16:05:00Z",
-        97,
-        "2014-02-25T16:10:00Z",
-        92,
-        "2014-02-25T16:15:00Z",
-        115,
-        "2014-02-25T16:20:00Z",
-        83,
-        "2014-02-25T16:25:00Z",
-        108,
-        "2014-02-25T16:30:00Z",
-        120,
-        "2014-02-25T16:35:00Z",
-        98,
-        "2014-02-25T16:40:00Z",
-        101,
-        "2014-02-25T16:45:00Z",
-        110,
-        "2014-02-25T16:50:00Z",
-        100,
-        "2014-02-25T16:55:00Z",
-        96,
-        "2014-02-25T17:00:00Z",
-        119,
-        "2014-02-25T17:05:00Z",
-        125,
-        "2014-02-25T17:10:00Z",
-        115,
-        "2014-02-25T17:15:00Z",
-        116,
-        "2014-02-25T17:20:00Z",
-        107,
-        "2014-02-25T17:25:00Z",
-        139,
-        "2014-02-25T17:30:00Z",
-        131,
-        "2014-02-25T17:35:00Z",
-        123,
-        "2014-02-25T17:40:00Z",
-        125,
-        "2014-02-25T17:45:00Z",
-        125,
-        "2014-02-25T17:50:00Z",
-        102,
-        "2014-02-25T17:55:00Z",
-        149,
-        "2014-02-25T18:00:00Z",
-        130,
-        "2014-02-25T18:05:00Z",
-        123,
-        "2014-02-25T18:10:00Z",
-        145,
-        "2014-02-25T18:15:00Z",
-        108,
-        "2014-02-25T18:20:00Z",
-        133,
-        "2014-02-25T18:25:00Z",
-        132,
-        "2014-02-25T18:30:00Z",
-        162,
-        "2014-02-25T18:35:00Z",
-        142,
-        "2014-02-25T18:40:00Z",
-        139,
-        "2014-02-25T18:45:00Z",
-        156,
-        "2014-02-25T18:50:00Z",
-        132,
-        "2014-02-25T18:55:00Z",
-        159,
-        "2014-02-25T19:00:00Z",
-        157,
-        "2014-02-25T19:05:00Z",
-        136,
-        "2014-02-25T19:10:00Z",
-        137,
-        "2014-02-25T19:15:00Z",
-        164,
-        "2014-02-25T19:20:00Z",
-        132,
-        "2014-02-25T19:25:00Z",
-        154,
-        "2014-02-25T19:30:00Z",
-        187,
-        "2014-02-25T19:35:00Z",
-        161,
-        "2014-02-25T19:40:00Z",
-        159,
-        "2014-02-25T19:45:00Z",
-        144,
-        "2014-02-25T19:50:00Z",
-        157,
-        "2014-02-25T19:55:00Z",
-        146,
-        "2014-02-25T20:00:00Z",
-        193,
-        "2014-02-25T20:05:00Z",
-        175,
-        "2014-02-25T20:10:00Z",
-        189,
-        "2014-02-25T20:15:00Z",
-        182,
-        "2014-02-25T20:20:00Z",
-        168,
-        "2014-02-25T20:25:00Z",
-        171,
-        "2014-02-25T20:30:00Z",
-        175,
-        "2014-02-25T20:35:00Z",
-        169,
-        "2014-02-25T20:40:00Z",
-        182,
-        "2014-02-25T20:45:00Z",
-        163,
-        "2014-02-25T20:50:00Z",
-        186,
-        "2014-02-25T20:55:00Z",
-        184,
-        "2014-02-25T21:00:00Z",
-        216,
-        "2014-02-25T21:05:00Z",
-        202,
-        "2014-02-25T21:10:00Z",
-        176,
-        "2014-02-25T21:15:00Z",
-        196,
-        "2014-02-25T21:20:00Z",
-        192,
-        "2014-02-25T21:25:00Z",
-        189,
-        "2014-02-25T21:30:00Z",
-        190,
-        "2014-02-25T21:35:00Z",
-        187,
-        "2014-02-25T21:40:00Z",
-        215,
-        "2014-02-25T21:45:00Z",
-        197,
-        "2014-02-25T21:50:00Z",
-        174,
-        "2014-02-25T21:55:00Z",
-        179,
-        "2014-02-25T22:00:00Z",
-        216,
-        "2014-02-25T22:05:00Z",
-        185,
-        "2014-02-25T22:10:00Z",
-        178,
-        "2014-02-25T22:15:00Z",
-        219,
-        "2014-02-25T22:20:00Z",
-        190,
-        "2014-02-25T22:25:00Z",
-        190,
-        "2014-02-25T22:30:00Z",
-        196,
-        "2014-02-25T22:35:00Z",
-        176,
-        "2014-02-25T22:40:00Z",
-        215,
-        "2014-02-25T22:45:00Z",
-        212,
-        "2014-02-25T22:50:00Z",
-        200,
-        "2014-02-25T22:55:00Z",
-        216,
-        "2014-02-25T23:00:00Z",
-        225,
-        "2014-02-25T23:05:00Z",
-        186,
-        "2014-02-25T23:10:00Z",
-        181,
-        "2014-02-25T23:15:00Z",
-        213,
-        "2014-02-25T23:20:00Z",
-        214,
-        "2014-02-25T23:25:00Z",
-        189,
-        "2014-02-25T23:30:00Z",
-        188,
-        "2014-02-25T23:35:00Z",
-        174,
-        "2014-02-25T23:40:00Z",
-        199,
-        "2014-02-25T23:45:00Z",
-        165,
-        "2014-02-25T23:50:00Z",
-        198,
-        "2014-02-25T23:55:00Z",
-        158,
-        "2014-02-26T00:00:00Z",
-        193,
-        "2014-02-26T00:05:00Z",
-        168,
-        "2014-02-26T00:10:00Z",
-        186,
-        "2014-02-26T00:15:00Z",
-        182,
-        "2014-02-26T00:20:00Z",
-        174,
-        "2014-02-26T00:25:00Z",
-        185,
-        "2014-02-26T00:30:00Z",
-        184,
-        "2014-02-26T00:35:00Z",
-        157,
-        "2014-02-26T00:40:00Z",
-        161,
-        "2014-02-26T00:45:00Z",
-        158,
-        "2014-02-26T00:50:00Z",
-        175,
-        "2014-02-26T00:55:00Z",
-        151,
-        "2014-02-26T01:00:00Z",
-        203,
-        "2014-02-26T01:05:00Z",
-        154,
-        "2014-02-26T01:10:00Z",
-        158,
-        "2014-02-26T01:15:00Z",
-        153,
-        "2014-02-26T01:20:00Z",
-        141,
-        "2014-02-26T01:25:00Z",
-        150,
-        "2014-02-26T01:30:00Z",
-        165,
-        "2014-02-26T01:35:00Z",
-        152,
-        "2014-02-26T01:40:00Z",
-        161,
-        "2014-02-26T01:45:00Z",
-        178,
-        "2014-02-26T01:50:00Z",
-        145,
-        "2014-02-26T01:55:00Z",
-        161,
-        "2014-02-26T02:00:00Z",
-        171,
-        "2014-02-26T02:05:00Z",
-        151,
-        "2014-02-26T02:10:00Z",
-        141,
-        "2014-02-26T02:15:00Z",
-        145,
-        "2014-02-26T02:20:00Z",
-        149,
-        "2014-02-26T02:25:00Z",
-        131,
-        "2014-02-26T02:30:00Z",
-        134,
-        "2014-02-26T02:35:00Z",
-        142,
-        "2014-02-26T02:40:00Z",
-        133,
-        "2014-02-26T02:45:00Z",
-        157,
-        "2014-02-26T02:50:00Z",
-        154,
-        "2014-02-26T02:55:00Z",
-        146,
-        "2014-02-26T03:00:00Z",
-        124,
-        "2014-02-26T03:05:00Z",
-        147,
-        "2014-02-26T03:10:00Z",
-        142,
-        "2014-02-26T03:15:00Z",
-        137,
-        "2014-02-26T03:20:00Z",
-        139,
-        "2014-02-26T03:25:00Z",
-        156,
-        "2014-02-26T03:30:00Z",
-        18,
-        "2014-02-26T04:10:00Z",
-        15,
-        "2014-02-26T04:15:00Z",
-        65,
-        "2014-02-26T04:20:00Z",
-        53,
-        "2014-02-26T04:25:00Z",
-        66,
-        "2014-02-26T04:30:00Z",
-        65,
-        "2014-02-26T04:35:00Z",
-        57,
-        "2014-02-26T04:40:00Z",
-        61
-      ],
-      "gap": "+5MINUTES",
-      "type": "chart"
-    }
-  ],
-  "responseHeader": {
-    "status": 0,
-    "QTime": 248,
-    "params": {
-      "f.created_at.facet.range.start": "2014-02-28T12:00:00Z-10DAYS",
-      "f.created_at.facet.range.gap": "+5MINUTES",
-      "f.user_followers_count.facet.range.start": "0",
-      "facet": "true",
-      "facet.mincount": "1",
-      "rows": "15",
-      "f.user_statuses_count.facet.range.gap": "1000",
-      "doAs": "romain",
-      "start": "0",
-      "user.name": "hue",
-      "f.created_at.facet.range.end": "2014-02-28T12:00:00Z",
-      "f.user_statuses_count.facet.range.start": "0",
-      "facet.field": "user_location",
-      "wt": "json",
-      "hl": "true",
-      "hl.fl": "text",
-      "f.user_followers_count.facet.range.gap": "100",
-      "facet.sort": "count",
-      "f.user_statuses_count.facet.range.end": "10000",
-      "f.user_followers_count.facet.range.end": "1000",
-      "facet.limit": "100",
-      "facet.range": [
-        "created_at",
-        "user_followers_count",
-        "user_statuses_count"
-      ],
-      "f.created_at.facet.limit": "-1",
-      "q": "*:*"
-    }
-  },
-  "response": {
-    "start": 0,
-    "numFound": 22218,
-    "docs": [
-      {
-        "created_at": "2014-02-25T16:05:05Z",
-        "user_followers_count": 1897,
-        "text": "RT @Fact: People who are strongly connected with their friends have stronger immune system than those who keep themselves isolated.",
-        "user_screen_name": "Jade___Richards",
-        "user_location": "UK",
-        "user_statuses_count": 75045,
-        "source": "<a href=\"http://dlvr.it\" rel=\"nofollow\">dlvr.it</a>",
-        "in_reply_to_status_id": [
-          -1
-        ],
-        "in_reply_to_user_id": -1,
-        "_version_": 1461767321259343872,
-        "retweet_count": 0,
-        "user_name": "Jade Richards News",
-        "id": "438585496994725888",
-        "user_friends_count": 2617
-      },
-      {
-        "created_at": "2014-02-25T16:05:08Z",
-        "user_followers_count": 39,
-        "text": "Udh dimanee@nimnimc",
-        "user_screen_name": "vergiawanlista2",
-        "user_location": "JAKARTA",
-        "user_statuses_count": 136,
-        "source": "<a href=\"http://blackberry.com/twitter\" rel=\"nofollow\">Twitter for BlackBerry\u00ae</a>",
-        "in_reply_to_status_id": [
-          -1
-        ],
-        "in_reply_to_user_id": -1,
-        "_version_": 1461767321313869824,
-        "retweet_count": 0,
-        "user_name": "VergiawanLisTanto",
-        "id": "438585509556658176",
-        "user_friends_count": 121
-      },
-      {
-        "created_at": "2014-02-25T16:05:06Z",
-        "user_followers_count": 140,
-        "text": "Emo. huwaaaaa... T.T",
-        "user_screen_name": "nickdayah",
-        "user_location": "Malaysia",
-        "user_statuses_count": 8777,
-        "source": "<a href=\"https://twitter.com/download/android\" rel=\"nofollow\">Twitter for  Android</a>",
-        "in_reply_to_status_id": [
-          -1
-        ],
-        "in_reply_to_user_id": -1,
-        "_version_": 1461767321315966976,
-        "retweet_count": 0,
-        "user_name": "Dayah Badri",
-        "id": "438585501180653568",
-        "user_friends_count": 144
-      },
-      {
-        "created_at": "2014-02-25T16:05:19Z",
-        "user_followers_count": 124,
-        "text": "RT @2TheHacker_: hati hati dengan saya saya memauntau anda",
-        "user_screen_name": "dayantiday",
-        "user_location": "indonesia",
-        "user_statuses_count": 7355,
-        "source": "<a href=\"http://www.twitter.com\" rel=\"nofollow\">Sistem Autentikasi</a>",
-        "in_reply_to_status_id": [
-          -1
-        ],
-        "in_reply_to_user_id": -1,
-        "_version_": 1461767321318064128,
-        "retweet_count": 0,
-        "user_name": "dayanti sukmawati",
-        "id": "438585555710791680",
-        "user_friends_count": 2002
-      },
-      {
-        "created_at": "2014-02-25T16:05:12Z",
-        "user_followers_count": 14,
-        "text": "RT @cosythirlwall: The girls have to win this, rt rt rt #VoteLittleMixUK #KCA",
-        "user_screen_name": "glitterstars98",
-        "user_location": "London",
-        "user_statuses_count": 468,
-        "source": "<a href=\"http://twitter.com/download/android\" rel=\"nofollow\">Twitter for Android</a>",
-        "in_reply_to_status_id": [
-          -1
-        ],
-        "in_reply_to_user_id": -1,
-        "_version_": 1461767321320161280,
-        "retweet_count": 0,
-        "user_name": "LM are my idols",
-        "id": "438585526355238912",
-        "user_friends_count": 28
-      },
-      {
-        "created_at": "2014-02-25T16:05:30Z",
-        "user_followers_count": 2666,
-        "text": "Toriii (Red) Gates number 995 - 1000 @reginachristian #prettygirl #livebold #liveglorious\u2026 http://t.co/1LXuzzvmmk",
-        "user_screen_name": "evantjandra",
-        "user_location": "Jakarta",
-        "user_statuses_count": 4439,
-        "source": "<a href=\"http://instagram.com\" rel=\"nofollow\">Instagram</a>",
-        "in_reply_to_status_id": [
-          -1
-        ],
-        "in_reply_to_user_id": -1,
-        "_version_": 1461767321322258432,
-        "retweet_count": 0,
-        "user_name": "Evan Tjandra",
-        "id": "438585601831763968",
-        "user_friends_count": 587
-      },
-      {
-        "created_at": "2014-02-25T16:05:31Z",
-        "user_followers_count": 4258,
-        "text": "\u5f15\u3063\u8d8a\u3057\u306e\u4e00\u62ec\u898b\u7a4d\u3082\u308a\u3057\u3066\u2026\u55b6\u696d\u306e\u96fb\u8a71\u304c\u5acc\u3067\u3059\u3088\u306d\uff61\u696d\u754c\u521d\uff01\u500b\u4eba\u60c5\u5831\u3092\u4f0f\u305b\u305f\u4e0a\u3067\u3001\u5f15\u8d8a\u696d\u8005\u62c5\u5f53\u8005\u3068\u30c1\u30e3\u30c3\u30c8\u3067\u3084\u308a\u53d6\u308a\u304c\u51fa\u6765\u308b\u30b5\u30a4\u30c8\u306f\u3053\u3053\u3060\u3051\uff01 http://t.co/OsRUDcfQTM",
-        "user_screen_name": "otoku_matome",
-        "user_location": "japan",
-        "user_statuses_count": 15614,
-        "source": "<a href=\"http://twittbot.net/\" rel=\"nofollow\">twittbot.net</a>",
-        "in_reply_to_status_id": [
-          -1
-        ],
-        "in_reply_to_user_id": -1,
-        "_version_": 1461767321324355584,
-        "retweet_count": 0,
-        "user_name": "\u304a\u5f97\u60c5\u5831\u307e\u3068\u3081",
-        "id": "438585606034046976",
-        "user_friends_count": 4016
-      },
-      {
-        "created_at": "2014-02-25T16:05:28Z",
-        "user_followers_count": 248,
-        "text": "@TeladanRasul: Jgn kalian saling membenci,jangan saling hasad,jangan saling membelakangi,jangan saling memutuskan silaturrahim (HR Muslim)",
-        "user_screen_name": "SATGASiti",
-        "user_location": "Indonesia",
-        "user_statuses_count": 1776,
-        "source": "<a href=\"https://twitter.com/download/android\" rel=\"nofollow\">Twitter for  Android</a>",
-        "in_reply_to_status_id": [
-          -1
-        ],
-        "in_reply_to_user_id": 213140358,
-        "_version_": 1461767321324355585,
-        "retweet_count": 0,
-        "user_name": "Nana\u2665",
-        "id": "438585593438560256",
-        "user_friends_count": 299
-      },
-      {
-        "created_at": "2014-02-25T16:05:33Z",
-        "user_followers_count": 103,
-        "text": "It hurts, because I'm so lonely so I say I'm missing you. 2NE1's slow songs.. *cry*",
-        "user_screen_name": "ahyu_savitri",
-        "user_location": "Indonesia",
-        "user_statuses_count": 268,
-        "source": "<a href=\"https://twitter.com/download/android\" rel=\"nofollow\">Twitter for  Android</a>",
-        "in_reply_to_status_id": [
-          -1
-        ],
-        "in_reply_to_user_id": -1,
-        "_version_": 1461767321325404160,
-        "retweet_count": 0,
-        "user_name": "Savitri",
-        "id": "438585614410063872",
-        "user_friends_count": 347
-      },
-      {
-        "created_at": "2014-02-25T16:05:25Z",
-        "user_followers_count": 140,
-        "text": "Dustin O'Halloran - An Ending, A Beginning http://t.co/jafiD40DFr",
-        "user_screen_name": "adadidem",
-        "user_location": "\u0130stanbul",
-        "user_statuses_count": 11670,
-        "source": "<a href=\"http://www.apple.com\" rel=\"nofollow\">iOS</a>",
-        "in_reply_to_status_id": [
-          -1
-        ],
-        "in_reply_to_user_id": -1,
-        "_version_": 1461767321325404161,
-        "retweet_count": 0,
-        "user_name": "Didem Saritas",
-        "id": "438585580856045568",
-        "user_friends_count": 287
-      },
-      {
-        "created_at": "2014-02-25T16:05:22Z",
-        "user_followers_count": 554,
-        "text": "Me tiene que sonar la alarma a las 11:30 para tomar la pasti, espero despertarme U.U",
-        "user_screen_name": "Badgalcamm",
-        "user_location": "Argentina",
-        "user_statuses_count": 19242,
-        "source": "<a href=\"http://twitter.com/download/android\" rel=\"nofollow\">Twitter for Android</a>",
-        "in_reply_to_status_id": [
-          -1
-        ],
-        "in_reply_to_user_id": -1,
-        "_version_": 1461767321326452736,
-        "retweet_count": 0,
-        "user_name": "Camila\u2661",
-        "id": "438585568302489600",
-        "user_friends_count": 667
-      },
-      {
-        "created_at": "2014-02-25T16:05:34Z",
-        "user_followers_count": 33,
-        "text": "RT @2TheHacker_: hati hati dengan saya saya memauntau anda",
-        "user_screen_name": "uzie_oz",
-        "user_location": "indonesia",
-        "user_statuses_count": 5419,
-        "source": "<a href=\"http://www.twitter.com\" rel=\"nofollow\">Sistem Autentikasi</a>",
-        "in_reply_to_status_id": [
-          -1
-        ],
-        "in_reply_to_user_id": -1,
-        "_version_": 1461767321326452737,
-        "retweet_count": 0,
-        "user_name": "ahmad fauji",
-        "id": "438585618612756480",
-        "user_friends_count": 2002
-      },
-      {
-        "created_at": "2014-02-25T16:05:34Z",
-        "user_followers_count": 86,
-        "text": "En Espa\u00f1a existen tres tipos diferentes de legislaci\u00f3n sobre el r\u00e9gimen econ\u00f3mico matrimonial http://t.co/SVxwfCaYDS",
-        "user_screen_name": "DGAabogados",
-        "user_location": "Madrid",
-        "user_statuses_count": 535,
-        "source": "<a href=\"http://twitter.com/download/android\" rel=\"nofollow\">Twitter for Android</a>",
-        "in_reply_to_status_id": [
-          -1
-        ],
-        "in_reply_to_user_id": -1,
-        "_version_": 1461767321327501312,
-        "retweet_count": 0,
-        "user_name": "DGA Abogados",
-        "id": "438585618617352192",
-        "user_friends_count": 201
-      },
-      {
-        "created_at": "2014-02-25T16:05:45Z",
-        "user_followers_count": 547,
-        "text": "Dan ga seharusnya mslh beginian gw yg ngadepin!!! Yg gw tau,gw cm ngurusin mslh DUIT dan kelancaran operasional divisi kitchen !!!!",
-        "user_screen_name": "nona_etty",
-        "user_location": "jakarta",
-        "user_statuses_count": 1018,
-        "source": "<a href=\"http://ubersocial.com\" rel=\"nofollow\">UberSocial for Android</a>",
-        "in_reply_to_status_id": [
-          -1
-        ],
-        "in_reply_to_user_id": -1,
-        "_version_": 1461767321329598464,
-        "retweet_count": 0,
-        "user_name": "Etty_Sundari",
-        "id": "438585664741703681",
-        "user_friends_count": 95
-      },
-      {
-        "created_at": "2014-02-25T16:05:45Z",
-        "user_followers_count": 267,
-        "text": "This is real",
-        "user_screen_name": "dinimslm",
-        "user_location": "Malaysia",
-        "user_statuses_count": 14817,
-        "source": "<a href=\"http://twitter.com/download/android\" rel=\"nofollow\">Twitter for Android</a>",
-        "in_reply_to_status_id": [
-          -1
-        ],
-        "in_reply_to_user_id": -1,
-        "_version_": 1461767321330647040,
-        "retweet_count": 0,
-        "user_name": "aurora ",
-        "id": "438585664745906176",
-        "user_friends_count": 649
-      }
-    ]
-  }
-}"""

+ 188 - 0
apps/search/static/js/collections.ko.js

@@ -0,0 +1,188 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+var Importable = function (importable) {
+  var self = this;
+  self.type = ko.observable(importable.type);
+  self.name = ko.observable(importable.name);
+  self.selected = ko.observable(false);
+  self.handleSelect = function (row, e) {
+    this.selected(!this.selected());
+  };
+};
+
+var Collection = function (coll) {
+  var self = this;
+
+  self.id = ko.observable(coll.id);
+  self.name = ko.observable(coll.name);
+  self.label = ko.observable(coll.label);
+  self.isCoreOnly = ko.observable(coll.isCoreOnly);
+  self.absoluteUrl = ko.observable(coll.absoluteUrl);
+  self.selected = ko.observable(false);
+  self.hovered = ko.observable(false);
+
+  self.handleSelect = function (row, e) {
+    this.selected(!this.selected());
+  };
+  self.toggleHover = function (row, e) {
+    this.hovered(!this.hovered());
+  };
+}
+
+var SearchCollectionsModel = function (props) {
+  var self = this;
+
+  self.LABELS = props.labels;
+
+  self.LIST_COLLECTIONS_URL = props.listCollectionsUrl;
+  self.LIST_IMPORTABLES_URL = props.listImportablesUrl;
+  self.IMPORT_URL = props.importUrl;
+  self.DELETE_URL = props.deleteUrl;
+  self.COPY_URL = props.copyUrl;
+
+  self.isLoading = ko.observable(true);
+  self.isLoadingImportables = ko.observable(false);
+  self.allSelected = ko.observable(false);
+
+  self.collections = ko.observableArray([]);
+  self.filteredCollections = ko.observableArray(self.collections());
+
+  self.importableCollections = ko.observableArray([]);
+  self.importableCores = ko.observableArray([]);
+
+  self.collectionToDelete = null;
+
+  self.selectedCollections = ko.computed(function () {
+    return ko.utils.arrayFilter(self.collections(), function (coll) {
+      return coll.selected();
+    });
+  }, self);
+
+  self.selectedImportableCollections = ko.computed(function () {
+    return ko.utils.arrayFilter(self.importableCollections(), function (imp) {
+      return imp.selected();
+    });
+  }, self);
+
+  self.selectedImportableCores = ko.computed(function () {
+    return ko.utils.arrayFilter(self.importableCores(), function (imp) {
+      return imp.selected();
+    });
+  }, self);
+
+  self.selectAll = function () {
+    self.allSelected(!self.allSelected());
+    ko.utils.arrayForEach(self.collections(), function (coll) {
+      coll.selected(self.allSelected());
+    });
+    return true;
+  };
+
+  self.filterCollections = function (filter) {
+    self.filteredCollections(ko.utils.arrayFilter(self.collections(), function (coll) {
+      return coll.name().toLowerCase().indexOf(filter.toLowerCase()) > -1
+    }));
+  };
+
+  self.editCollection = function (collection) {
+    self.isLoading(true);
+    self.collections.removeAll();
+    self.filteredCollections.removeAll();
+    location.href = collection.absoluteUrl();
+  };
+
+  self.markForDeletion = function (collection) {
+    self.collectionToDelete = collection;
+    $(document).trigger("confirmDelete");
+  };
+
+  self.deleteCollection = function () {
+    $(document).trigger("deleting");
+    $.post(self.DELETE_URL,
+      {
+        id: self.collectionToDelete.id()
+      },
+      function (data) {
+        self.updateCollections();
+        $(document).trigger("collectionDeleted");
+      }, "json");
+  };
+
+  self.copyCollection = function (collection) {
+    $(document).trigger("copying");
+    $.post(self.COPY_URL,
+      {
+        id: collection.id(),
+        type: collection.isCoreOnly()?"core":"collection"
+      },
+      function (data) {
+        self.updateCollections();
+        $(document).trigger("collectionCopied");
+      }, "json");
+  };
+
+  self.updateCollections = function () {
+    self.isLoading(true);
+    $.getJSON(self.LIST_COLLECTIONS_URL, function (data) {
+      self.collections(ko.utils.arrayMap(data, function (coll) {
+        return new Collection(coll);
+      }));
+      self.filteredCollections(self.collections());
+      $(document).trigger("collectionsRefreshed");
+      self.isLoading(false);
+    });
+  };
+
+  self.updateImportables = function () {
+    self.isLoadingImportables(true);
+    $.getJSON(self.LIST_IMPORTABLES_URL, function (data) {
+      self.importableCollections(ko.utils.arrayMap(data.newSolrCollections, function (coll) {
+        return new Importable(coll);
+      }));
+      self.importableCores(ko.utils.arrayMap(data.newSolrCores, function (core) {
+        return new Importable(core);
+      }));
+      self.isLoadingImportables(false);
+    });
+  };
+
+  self.importCollectionsAndCores = function () {
+    $(document).trigger("importing");
+    var selected = [];
+    ko.utils.arrayForEach(self.selectedImportableCollections(), function (imp) {
+      selected.push({
+        type: imp.type(),
+        name: imp.name()
+      });
+    });
+    ko.utils.arrayForEach(self.selectedImportableCores(), function (imp) {
+      selected.push({
+        type: imp.type(),
+        name: imp.name()
+      });
+    });
+    $.post(self.IMPORT_URL,
+      {
+        selected: ko.toJSON(selected)
+      },
+      function (data) {
+        $(document).trigger("imported", data);
+        self.updateCollections();
+      }, "json");
+  };
+
+};

+ 0 - 55
apps/search/static/js/query.ko.js

@@ -1,55 +0,0 @@
-// Licensed to Cloudera, Inc. under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  Cloudera, Inc. licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-
-function QueryViewModel(json_tags, json_docs) {
-  var self = this;
-
-  var MOCK_TAGS = {
-    'history': {'name': 'History', 'id': 1, 'docs': [1], 'type': 'history'},
-    'trash': {'name': 'Trash', 'id': 3, 'docs': [2]},
-    'mine': [
-      {'name': 'default', 'id': 2, 'docs': [3]},
-      {'name': 'web', 'id': 3, 'docs': [3]}
-    ],
-    'notmine': [
-      {'name': 'romain', 'projects': [
-        {'name': 'example', 'id': 20, 'docs': [10]},
-        {'name': 'ex2', 'id': 30, 'docs': [10, 11]}
-      ]},
-      {'name': 'pai', 'projects': [
-        {'name': 'example2', 'id': 20, 'docs': [10]}
-      ]}
-    ]
-  };
-
-  var ALL_DOCUMENTS = json_docs;
-  self.tags = ko.mapping.fromJS(json_tags);
-  self.documents = ko.observableArray([]);
-
-  self.editTagsToCreate = ko.observableArray([]);
-  self.editTagsToDelete = ko.observableArray([]);
-
-  self.selectedTag = ko.observable("");
-
-  self.trash = ko.computed(function () {
-    return self.tags.trash;
-  });
-
-  self.history = ko.computed(function () {
-    return self.tags.history;
-  });
-}

+ 122 - 158
apps/search/static/js/search.ko.js

@@ -14,175 +14,139 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-var Importable = function (importable) {
-  var self = this;
-  self.type = ko.observable(importable.type);
-  self.name = ko.observable(importable.name);
-  self.selected = ko.observable(false);
-  self.handleSelect = function (row, e) {
-    this.selected(!this.selected());
-  };
-};
-
-var Collection = function (coll) {
+var Query = function (vm, query) {
   var self = this;
 
-  self.id = ko.observable(coll.id);
-  self.name = ko.observable(coll.name);
-  self.label = ko.observable(coll.label);
-  self.isCoreOnly = ko.observable(coll.isCoreOnly);
-  self.absoluteUrl = ko.observable(coll.absoluteUrl);
-  self.selected = ko.observable(false);
-  self.hovered = ko.observable(false);
+  self.q = ko.observable(query.q);
+  self.fq = query.fq
+  
+  self.selectFacet = function(facet_json) {
+	self.fq[facet_json.cat] = facet_json.value;
+	vm.search();
+  }
+
+  self.unselectFacet = function(facet_json) {
+	delete self.fq[facet_json.cat];
+    vm.search();
+  }
+};
 
-  self.handleSelect = function (row, e) {
-    this.selected(!this.selected());
-  };
-  self.toggleHover = function (row, e) {
-    this.hovered(!this.hovered());
-  };
+var FieldFacet = function(vm, props) {
+  self.id = props.id;  
+  self.label = props.name;
+  self.field = props.name;
+  self.type = "field";
 }
 
-var SearchCollectionsModel = function (props) {
-  var self = this;
-
-  self.LABELS = props.labels;
-
-  self.LIST_COLLECTIONS_URL = props.listCollectionsUrl;
-  self.LIST_IMPORTABLES_URL = props.listImportablesUrl;
-  self.IMPORT_URL = props.importUrl;
-  self.DELETE_URL = props.deleteUrl;
-  self.COPY_URL = props.copyUrl;
-
-  self.isLoading = ko.observable(true);
-  self.isLoadingImportables = ko.observable(false);
-  self.allSelected = ko.observable(false);
-
-  self.collections = ko.observableArray([]);
-  self.filteredCollections = ko.observableArray(self.collections());
-
-  self.importableCollections = ko.observableArray([]);
-  self.importableCores = ko.observableArray([]);
-
-  self.collectionToDelete = null;
-
-  self.selectedCollections = ko.computed(function () {
-    return ko.utils.arrayFilter(self.collections(), function (coll) {
-      return coll.selected();
-    });
-  }, self);
-
-  self.selectedImportableCollections = ko.computed(function () {
-    return ko.utils.arrayFilter(self.importableCollections(), function (imp) {
-      return imp.selected();
-    });
-  }, self);
+// FieldListFacet
+// RangeFacet
 
-  self.selectedImportableCores = ko.computed(function () {
-    return ko.utils.arrayFilter(self.importableCores(), function (imp) {
-      return imp.selected();
-    });
-  }, self);
 
-  self.selectAll = function () {
-    self.allSelected(!self.allSelected());
-    ko.utils.arrayForEach(self.collections(), function (coll) {
-      coll.selected(self.allSelected());
-    });
-    return true;
-  };
+var Collection = function (vm, collection) {
+  var self = this;
 
-  self.filterCollections = function (filter) {
-    self.filteredCollections(ko.utils.arrayFilter(self.collections(), function (coll) {
-      return coll.name().toLowerCase().indexOf(filter.toLowerCase()) > -1
+  self.id = collection.id;
+  self.name = collection.name;
+  self.template = ko.mapping.fromJS(collection.template);
+  self.template.fields.subscribe(function() {
+	vm.search();
+  });
+  self.template.template.subscribe(function() {
+    vm.search();
+  });
+  self.facets = ko.mapping.fromJS(collection.facets);
+
+  self.fields = ko.observableArray(collection.fields);
+
+  self.addFacet = function(facet_json) {
+    self.facets.push(ko.mapping.fromJS({
+	   "uuid": "f6618a5c-bbba-2886-1886-bbcaf01409ca",
+        "verbatim": "", "isVerbatim": false, "label": facet_json.name, 
+	    "field": facet_json.name, "type": "field"
     }));
-  };
-
-  self.editCollection = function (collection) {
-    self.isLoading(true);
-    self.collections.removeAll();
-    self.filteredCollections.removeAll();
-    location.href = collection.absoluteUrl();
-  };
-
-  self.markForDeletion = function (collection) {
-    self.collectionToDelete = collection;
-    $(document).trigger("confirmDelete");
-  };
-
-  self.deleteCollection = function () {
-    $(document).trigger("deleting");
-    $.post(self.DELETE_URL,
-      {
-        id: self.collectionToDelete.id()
-      },
-      function (data) {
-        self.updateCollections();
-        $(document).trigger("collectionDeleted");
-      }, "json");
-  };
-
-  self.copyCollection = function (collection) {
-    $(document).trigger("copying");
-    $.post(self.COPY_URL,
-      {
-        id: collection.id(),
-        type: collection.isCoreOnly()?"core":"collection"
-      },
-      function (data) {
-        self.updateCollections();
-        $(document).trigger("collectionCopied");
-      }, "json");
-  };
+  }  
+  
+  self.addDynamicFields = function() {
+	$.post("/search/index/" + self.id + "/fields/dynamic", {		
+	  }, function (data){
+		if (data.status == 0) {
+		  $.each(data.dynamic_fields, function(index, field) {
+            self.fields.push(field);
+		  });
+		}
+	  }).fail(function(xhr, textStatus, errorThrown) {}
+	);
+  }
+    
+  // Init
+  self.addDynamicFields();
+};
 
-  self.updateCollections = function () {
-    self.isLoading(true);
-    $.getJSON(self.LIST_COLLECTIONS_URL, function (data) {
-      self.collections(ko.utils.arrayMap(data, function (coll) {
-        return new Collection(coll);
-      }));
-      self.filteredCollections(self.collections());
-      $(document).trigger("collectionsRefreshed");
-      self.isLoading(false);
-    });
-  };
 
-  self.updateImportables = function () {
-    self.isLoadingImportables(true);
-    $.getJSON(self.LIST_IMPORTABLES_URL, function (data) {
-      self.importableCollections(ko.utils.arrayMap(data.newSolrCollections, function (coll) {
-        return new Importable(coll);
-      }));
-      self.importableCores(ko.utils.arrayMap(data.newSolrCores, function (core) {
-        return new Importable(core);
-      }));
-      self.isLoadingImportables(false);
-    });
-  };
+var SearchViewModel = function (collection_json, query_json) {
+  var self = this;
 
-  self.importCollectionsAndCores = function () {
-    $(document).trigger("importing");
-    var selected = [];
-    ko.utils.arrayForEach(self.selectedImportableCollections(), function (imp) {
-      selected.push({
-        type: imp.type(),
-        name: imp.name()
-      });
-    });
-    ko.utils.arrayForEach(self.selectedImportableCores(), function (imp) {
-      selected.push({
-        type: imp.type(),
-        name: imp.name()
-      });
-    });
-    $.post(self.IMPORT_URL,
-      {
-        selected: ko.toJSON(selected)
-      },
-      function (data) {
-        $(document).trigger("imported", data);
-        self.updateCollections();
-      }, "json");
+  // Models
+  self.collection = new Collection(self, collection_json);
+  self.query = new Query(self, query_json);
+  
+  // UI
+  self.response = ko.observable({});
+  self.results = ko.observableArray([]);
+  self.norm_facets = ko.computed(function () {
+    return self.response().normalized_facets;
+  });
+  
+  self.selectedFacet = ko.observable();
+
+  self.search = function () {
+	$(".jHueNotify").hide();
+    $.post("/search/search", {
+        collection: ko.mapping.toJSON(self.collection),
+        query: ko.mapping.toJSON(self.query),
+      }, function (data) {
+       self.response(data); // If error we should probably update only the facets
+   	   self.results.removeAll(); 
+   	   if (data.error) {
+   		 $(document).trigger("error", data.error);
+   	   } else {
+   	     if (self.collection.template.isGridLayout()) {
+ 	       // Table view
+ 	       $.each(data.response.docs, function (index, item) {
+ 	    	 var row = [];
+ 	    	 $.each(self.collection.template.fields(), function (index, column) {
+ 	    	   row.push(item[column]); // TODO: if null + some escaping
+ 	    	 });
+ 	    	 self.results.push(row);
+ 	       });
+   	     } else {
+   	   	   // Template view
+   	       var _mustacheTmpl = fixTemplateDotsAndFunctionNames(self.collection.template.template());
+           $.each(data.response.docs, function (index, item) {
+             addTemplateFunctions(item);
+             self.results.push(Mustache.render(_mustacheTmpl, item));
+           });
+         }
+   	   }
+     }).fail(function(xhr, textStatus, errorThrown) {    	
+       $(document).trigger("error", xhr.responseText);
+     });
   };
-
+    
+  self.selectSingleFacet = function(normalized_facet_json) {
+	$.each(self.collection.facets(), function(index, facet) {
+      if (facet.field() == normalized_facet_json.field) {
+        self.selectedFacet(facet);
+      }
+	});	  
+  }
+  
+  self.removeFacet = function(facet_json) {
+	$.each(self.collection.facets(), function(index, item) {
+	  if (item.field() == facet_json.field) {
+		self.collection.facets.remove(item);
+	   }
+	});
+	self.search();
+  }
 };

+ 0 - 177
apps/search/static/js/template.ko.js

@@ -1,177 +0,0 @@
-// Licensed to Cloudera, Inc. under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  Cloudera, Inc. licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-
-var Query = function (importable) {
-  var self = this;
-  self.type = ko.observable(importable.type);
-  self.name = ko.observable(importable.name);
-  self.selected = ko.observable(false);
-  self.handleSelect = function (row, e) {
-    this.selected(!this.selected());
-  };
-};
-
-var Collection = function (coll) {
-  var self = this;
-
-  self.id = ko.observable(coll.id);
-  self.name = ko.observable(coll.name);
-  self.label = ko.observable(coll.label);
-  self.isCoreOnly = ko.observable(coll.isCoreOnly);
-  self.absoluteUrl = ko.observable(coll.absoluteUrl);
-  self.selected = ko.observable(false);
-  self.hovered = ko.observable(false);
-
-  self.handleSelect = function (row, e) {
-    this.selected(!this.selected());
-  };
-  self.toggleHover = function (row, e) {
-    this.hovered(!this.hovered());
-  };
-};
-
-// sorting
-// highlithing
-
-//
-// Facets (text or chart)
-//   field 
-//   range
-
-// spacial search
-// query facet
-// pivot facet
-
-
-
-var SearchViewModel = function (result, facets) {
-  var self = this;
-
-  // Mock testing
-  //var TEMPLATE = {"extracode": "      \n<style type=\"text/css\">\nem {\n  font-weight: bold;\n  background-color: yellow;\n}\n.avatar {\n  margin: 10px;\n}\n.created {\n  margin-top: 10px;\n  color: #CCC;\n}\n.openTweet {\n  float: right;\n  margin: 10px;\n}\n</style>\n      \n    ", "highlighting": ["text"], "properties": {"highlighting_enabled": true}, "template": "\n\n<div class=\"row-fluid\">\n  <div class=\"row-fluid\">\n    <div class=\"row-fluid\">\n      <div class=\"span1\">\n        <img src=\"http://twitter.com/api/users/profile_image/{{user_screen_name}}\" class=\"avatar\">\n        </div>\n        <div class=\"span11\">\n          <b>{{user_name}}</b>\n          <br>\n            <a href=\"https://twitter.com/{{user_screen_name}}/status/{{id}}\" target=\"_blank\">\n              {{text}}\n            </a>\n            <br>\n              <div class=\"created\">{{#fromnow}}{{created_at}}{{/fromnow}}</div>\n            </div>\n          </div>\n          <br>\n          </div>\n        </div>\n        \n        "}
-  //var FACETS = {"dates": [], "fields": [{"uuid": "f6618a5c-bbba-2886-1886-bbcaf01409ca", "verbatim": "", "isVerbatim": false, "label": "Location", "field": "user_location", "type": "field"}], "charts": [{"end": "2014-02-28T12:00:00Z", "uuid": "4883871c-0cea-8547-de60-7166d498098a", "verbatim": "", "start": "2014-02-28T12:00:00Z-10DAYS", "isVerbatim": false, "label": "Posted", "field": "created_at", "gap": "+5MINUTES", "type": "chart"}], "properties": {"sort": "count", "mincount": 1, "isEnabled": true, "limit": 10}, "ranges": [{"end": "1000", "uuid": "5533165a-0b1c-21b6-4ede-9d2fc301ed6b", "verbatim": "", "start": 0, "isVerbatim": false, "label": "Followers count", "field": "user_followers_count", "gap": "100", "type": "range"}, {"end": "10000", "uuid": "d5e66f3d-ca7d-67d7-05c7-33ec499cc106", "verbatim": "", "start": 0, "isVerbatim": false, "label": "Tweet count", "field": "user_statuses_count", "gap": "1000", "type": "range"}], "order": ["f6618a5c-bbba-2886-1886-bbcaf01409ca", "5533165a-0b1c-21b6-4ede-9d2fc301ed6b", "d5e66f3d-ca7d-67d7-05c7-33ec499cc106"]}
-  
-  var TEMPLATE = {"extracode": "", "highlighting": ["text"], "properties": {"highlighting_enabled": true},
-		          "template": "{{user_screen_name}} {{user_name}} {{text}}", "isGridLayout": true, "fields": ["user_screen_name", "user_name", "text"]
-  };
-  var FACETS = {"dates": [], "fields": [
-                   {
-                	   "uuid": "f6618a5c-bbba-2886-1886-bbcaf01409ca", "verbatim": "", "isVerbatim": false, "label": "Location", 
-                	    "field": "user_location", "type": "field"
-                   }
-                 ],
-                 "charts": [], "properties": {"sort": "count", "mincount": 1, "isEnabled": true, "limit": 10}, "ranges": [], "order": []
-  };  
-
-  
-  // Collection customization
-  var collection = 10000004;
-  self.template = ko.mapping.fromJS(TEMPLATE); //result.template;
-  self.template.fields.subscribe(function() {
-	self.search();
-  });
-  self.template.template.subscribe(function() {
-    self.search();
-  });
-  self.facets = ko.mapping.fromJS(FACETS.fields); //facets.fields
-
-  self.fields = ko.observableArray(["user_screen_name", "user_name", "text", "created_at", "user_statuses_count", "id"]); // ad dynamic ajaxifoed
-  
-  // Query URL  
-  self.q = ko.observable('');
-  self.qFacets = {}
-  
-  // Query results
-  self.response = ko.observable({});
-  self.results = ko.observableArray([]);
-  self.norm_facets = ko.computed(function () {
-    return self.response().normalized_facets;
-  });
-  
-  // Forms
-  self.selectedFacet = ko.observable();
-  
-  self.search = function () {
-    $.post("/search/query2?format=json", {
-        collection: collection,
-        q: ko.toJSON(self.q),
-        facets: ko.toJSON(self.facets),
-        fq: ko.utils.stringifyJson(self.qFacets),
-        template:  ko.mapping.toJSON(self.template),
-      }, function (data) {
-       self.response(data);
-   	   self.results.removeAll(); 
-   	   if (data.error) {
-   		 $(document).trigger("error", data.error);   
-   	   } else {
-   	     if (self.template.isGridLayout()) {
- 	       // Table view
- 	       $.each(data.response.docs, function (index, item) {
- 	    	 var row = [];
- 	    	 $.each(self.template.fields(), function (index, column) {
- 	    	   row.push(item[column]); // todo if is null
- 	    	 });
- 	    	 self.results.push(row);
- 	       });
-   	     } else {
-   	   	   // Template view
-   	       var _mustacheTmpl = fixTemplateDotsAndFunctionNames(self.template.template());
-           $.each(data.response.docs, function (index, item) {
-             addTemplateFunctions(item);
-             self.results.push(Mustache.render(_mustacheTmpl, item));
-           });
-         }
-   	   }
-     }).fail(function(xhr, textStatus, errorThrown) {    	
-       $(document).trigger("error", xhr.responseText); // cleanup all "alert jHueNotify alert-error" before
-     });
-  };
-  
-  self.addFacet = function(facet_json) {
-    self.facets.push(ko.mapping.fromJS({
-	   "uuid": "f6618a5c-bbba-2886-1886-bbcaf01409ca", "verbatim": "", "isVerbatim": false, "label": "Location", 
-	    "field": facet_json.name, "type": "field"
-    }));
-  }
-  
-  self.selectSingleFacet = function(normalized_facet_json) {
-	$.each(self.facets(), function(index, facet) {
-      if (facet.field() == normalized_facet_json.field) {
-        self.selectedFacet(facet);
-      }
-	});	  
-  }
-  
-  self.removeFacet = function(facet_json) {
-	 $.each(self.facets(), function(index, item) {
-		if (item.field() == facet_json.field) {
-		  self.facets.remove(item);
-		}
-	 });
-	 self.search();
-  }
-  
-  self.selectFacet = function(facet_json) {
-	self.qFacets[facet_json.cat] = facet_json.value;
-	self.search();
-  }
-
-  self.unselectFacet = function(facet_json) {
-	delete self.qFacets[facet_json.cat];
-    self.search();
-  }
-};