فهرست منبع

[search] Support collections part 1

Romain Rigaux 12 سال پیش
والد
کامیت
1e395fa

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

@@ -60,7 +60,7 @@ class SolrApi(object):
       print solr_query
       print params
 
-      response = self._root.get('%(core)s/select' % solr_query, params)
+      response = self._root.get('%(collection)s/select' % solr_query, params)
       return json.loads(response)
     except RestException, e:
       raise PopupException('Error while accessing Solr: %s' % e)
@@ -76,6 +76,19 @@ class SolrApi(object):
     except RestException, e:
       raise PopupException('Error while accessing Solr: %s' % e)
 
+  def collections(self):
+    try:
+      response = self._root.get('zookeeper', params={'detail': 'true', 'path': '/clusterstate.json'})
+      return json.loads(response['znode']['data'])
+    except RestException, e:
+      raise PopupException('Error while accessing Solr: %s' % e)
+
+  def collection(self, core):
+    try:
+      return self._root.get('admin/cores', params={'wt': 'json', 'core': core})
+    except RestException, e:
+      raise PopupException('Error while accessing Solr: %s' % e)
+
   def cores(self):
     try:
       return self._root.get('admin/cores', params={'wt': 'json'})
@@ -93,4 +106,3 @@ class SolrApi(object):
       return self._root.get('%(core)s/admin/file' % {'core': core}, params={'wt': 'json', 'file': 'schema.xml'})
     except RestException, e:
       raise PopupException('Error while accessing Solr: %s' % e)
-

+ 15 - 0
apps/search/src/search/conf.py

@@ -25,3 +25,18 @@ SOLR_URL = Config(
   help=_("URL of the Solr Server."),
   private=False,
   default="http://localhost:1978/solr/")
+
+#SOLR_URLS = UnspecifiedConfigSection(
+#  "solr_urls",
+#  help="One entry for each Solr server",
+#  each=ConfigSection(
+#    help="Information about a single HDFS cluster",
+#    members=dict(
+#      # Deprecated
+#      NN_HOST=Config("namenode_host", help="Host/IP for name node"),
+#    )
+#  )
+#)
+                                     
+# ZOOKEEPER URL
+# for COLLECTIONS                                     

+ 4 - 4
apps/search/src/search/forms.py

@@ -17,7 +17,7 @@
 
 
 from django import forms
-from search.models import Core
+from search.models import Collection
 
 
 class QueryForm(forms.Form):
@@ -33,7 +33,7 @@ class QueryForm(forms.Form):
 
   def __init__(self, *args, **kwargs):
     super(QueryForm, self).__init__(*args, **kwargs)
-    choices = [(core.name, core.label) for core in Core.objects.filter(enabled=True)]
+    choices = [(core.name, core.label) for core in Collection.objects.filter(enabled=True)]
     initial_choice = self._initial_core(choices)
     self.fields['collection'] = forms.ChoiceField(choices=choices, initial=initial_choice, required=False, label='', widget=forms.Select(attrs={'class':'hide'}))
 
@@ -59,7 +59,7 @@ class HighlightingForm(forms.Form):
 
 
 
-class CoreForm(forms.ModelForm):
+class CollectionForm(forms.ModelForm):
   class Meta:
-    model = Core
+    model = Collection
     exclude = ('facets', 'result', 'sorting', 'properties')

+ 81 - 0
apps/search/src/search/migrations/0002_auto__del_core__add_collection.py

@@ -0,0 +1,81 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+
+        # Deleting model 'Core'
+        db.delete_table('search_core')
+
+        # Adding model 'Collection'
+        db.create_table('search_collection', (
+            ('properties', self.gf('django.db.models.fields.TextField')(default='{}')),
+            ('sorting', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['search.Sorting'])),
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=40)),
+            ('facets', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['search.Facet'])),
+            ('enabled', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)),
+            ('label', self.gf('django.db.models.fields.CharField')(max_length=100)),
+            ('is_core_only', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),
+            ('result', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['search.Result'])),
+            ('cores', self.gf('django.db.models.fields.TextField')(default='{}')),
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+        ))
+        db.send_create_signal('search', ['Collection'])
+
+
+    def backwards(self, orm):
+
+        # Adding model 'Core'
+        db.create_table('search_core', (
+            ('sorting', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['search.Sorting'])),
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=40, unique=True)),
+            ('facets', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['search.Facet'])),
+            ('enabled', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)),
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('result', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['search.Result'])),
+            ('label', self.gf('django.db.models.fields.CharField')(max_length=100)),
+            ('properties', self.gf('django.db.models.fields.TextField')(default='[]')),
+        ))
+        db.send_create_signal('search', ['Core'])
+
+        # Deleting model 'Collection'
+        db.delete_table('search_collection')
+
+
+    models = {
+        'search.collection': {
+            'Meta': {'object_name': 'Collection'},
+            'cores': ('django.db.models.fields.TextField', [], {'default': "'{}'"}),
+            'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'facets': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['search.Facet']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_core_only': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'properties': ('django.db.models.fields.TextField', [], {'default': "'{}'"}),
+            'result': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['search.Result']"}),
+            'sorting': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['search.Sorting']"})
+        },
+        'search.facet': {
+            'Meta': {'object_name': 'Facet'},
+            'data': ('django.db.models.fields.TextField', [], {}),
+            'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'search.result': {
+            'Meta': {'object_name': 'Result'},
+            'data': ('django.db.models.fields.TextField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'search.sorting': {
+            'Meta': {'object_name': 'Sorting'},
+            'data': ('django.db.models.fields.TextField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        }
+    }
+
+    complete_apps = ['search']

+ 14 - 10
apps/search/src/search/models.py

@@ -185,11 +185,11 @@ class Sorting(models.Model):
     ('sort', solr_query['sort']),
 
 
-class CoreManager(models.Manager):
+class CollectionManager(models.Manager):
   def get_or_create(self, name):
     try:
       return self.get(name=name)
-    except Core.DoesNotExist:
+    except Collection.DoesNotExist:
       facets = Facet.objects.create(data=json.dumps({
                    'properties': {'isEnabled': False, 'limit': 10, 'mincount': 1, 'sort': 'count'},
                    'ranges': [],
@@ -353,27 +353,31 @@ margin-top: 2px;
               }))
       sorting = Sorting.objects.create(data=json.dumps({'properties': {'is_enabled': False}, 'fields': []}))
 
-      return Core.objects.create(name=name, label=name, facets=facets, result=result, sorting=sorting)
+      return Collection.objects.create(name=name, label=name, facets=facets, result=result, sorting=sorting)
 
 
-class Core(models.Model):
+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, unique=True, verbose_name=_t('Solr collection'))
+  name = models.CharField(max_length=40, verbose_name=_t('Solr name'))
   label = models.CharField(max_length=100)
-  # solr_address?
-  # results by pages number, autocomplete off...
-  properties = models.TextField(default='[]', verbose_name=_t('Core properties'), help_text=_t('Properties (e.g. facets off, results by pages number)'))
+  is_core_only = models.BooleanField(default=False)
+  cores = models.TextField(default=json.dumps({}), verbose_name=_t('Core data'), help_text=_t('Cores or shards data'))
+  properties = models.TextField(
+      default=json.dumps({}), verbose_name=_t('Properties'),
+      help_text=_t('Properties (e.g. results by pages number)'))
+  
   facets = models.ForeignKey(Facet)
   result = models.ForeignKey(Result)
   sorting = models.ForeignKey(Sorting)
 
-  objects = CoreManager()
+  objects = CollectionManager()
 
   def get_query(self):
     return self.facets.get_query_params() + self.result.get_query_params() + self.sorting.get_query_params()
 
   def get_absolute_url(self):
-    return reverse('search:admin_core', kwargs={'core': self.name})
+    return reverse('search:admin_collection', kwargs={'collection': self.name})
 
   @property
   def fields(self):

+ 75 - 0
apps/search/src/search/search_controler.py

@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+# -- coding: utf-8 --
+# 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.
+
+try:
+  import json
+except ImportError:
+  import simplejson as json
+
+import logging
+
+from desktop.lib.exceptions_renderable import PopupException
+from desktop.lib.rest.http_client import HttpClient, RestException
+from desktop.lib.rest.resource import Resource
+
+from search.api import SolrApi
+from search.conf import SOLR_URL
+from search.decorators import allow_admin_only
+from search.forms import QueryForm, CollectionForm, HighlightingForm
+from search.models import Collection, augment_solr_response
+
+
+LOG = logging.getLogger(__name__)
+
+
+class SearchController(object):
+  """
+  Glue the models to the views.
+  """
+  
+  def __init__(self):
+    pass
+
+  def get_new_collections(self):
+    solr_collections = SolrApi(SOLR_URL.get()).collections()
+    for name in Collection.objects.values_list('name', flat=True):
+      solr_collections.pop(name)
+    
+    return solr_collections
+
+  def get_new_cores(self):    
+    # TODO
+    solr_cores = []    
+    
+    return solr_cores
+
+  def add_new_collection(self, attrs):    
+    if attrs['type'] == 'collection':
+      collections = self.get_new_collections()
+      collection = collections[attrs['name']]
+      hue_collection, created = Collection.objects.get_or_create(
+          name=attrs['name'],
+#          label=attrs['name'],
+#          cores=json.dumps(collection)
+      )
+      print hue_collection
+      print 'aa'
+      hue_collection.label = attrs['name']
+      hue_collection.cores = json.dumps(collection)
+      hue_collection.save()
+      return hue_collection

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

@@ -14,7 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 DJANGO_APPS = [ "search" ]
-NICE_NAME = "Search"
+NICE_NAME = "Solr Search"
 MENU_INDEX = 42
 ICON = "/search/static/art/icon_search_24.png"
 

+ 18 - 16
apps/search/src/search/templates/admin.mako

@@ -27,19 +27,21 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
 <link rel="stylesheet" href="/search/static/css/admin.css">
 
 <div class="container-fluid">
+
   <h1>${_('Search Admin - Cores')}</h1>
   <%actionbar:render>
     <%def name="search()">
-      <input type="text" placeholder="${_('Filter cores by name...')}" class="input-xxlarge search-query" id="filterInput">
+      <input type="text" placeholder="${_('Filter collections by name...')}" class="input-xxlarge search-query" id="filterInput">
     </%def>
+    
   </%actionbar:render>
   <div class="row-fluid">
     <div class="span12">
-      <ul id="cores">
-      % for core in hue_cores:
-        <li style="cursor: move" data-core="${ core.name }">
-          <a href="${ core.get_absolute_url() }" class="pull-right" style="margin-top: 10px;margin-right: 10px"><i class="icon-edit"></i> ${_('Edit')}</a>
-          <h4><i class="icon-list"></i> ${ core.name }</h4>
+      <ul id="collections">
+      % for collection in hue_collections:
+        <li style="cursor: move" data-collection="${ collection.name }">
+          <a href="${ collection.get_absolute_url() }" class="pull-right" style="margin-top: 10px;margin-right: 10px"><i class="icon-edit"></i> ${_('Edit')}</a>
+          <h4><i class="icon-list"></i> ${ collection.name }</h4>
         </li>
       % endfor
       </ul>
@@ -48,14 +50,14 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
 </div>
 
 <style type="text/css">
-  #cores {
+  #collections {
     list-style-type: none;
     margin: 0;
     padding: 0;
     width: 100%;
   }
 
-  #cores li {
+  #collections li {
     margin-bottom: 10px;
     padding: 10px;
     border: 1px solid #E3E3E3;
@@ -75,21 +77,21 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
   $(document).ready(function () {
     var orderedCores;
     serializeList();
-    $("#cores").sortable({
+    $("#collections").sortable({
       placeholder: "placeholder",
       update: function (event, ui) {
         serializeList();
-        ##TODO: serialize via ajax the order of cores
+        ##TODO: serialize via ajax the order of collections
         ## the array is: orderedCores
         ## console.log(orderedCores)
       }
     });
-    $("#cores").disableSelection();
+    $("#collections").disableSelection();
 
     function serializeList() {
       orderedCores = [];
-      $("#cores li").each(function () {
-        orderedCores.push($(this).data("core"));
+      $("#collections li").each(function () {
+        orderedCores.push($(this).data("collection"));
       });
     }
 
@@ -97,9 +99,9 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
     $("#filterInput").on("keyup", function () {
       clearTimeout(filter);
       filter = window.setTimeout(function () {
-        $("#cores li").removeClass("hide");
-        $("#cores li").each(function () {
-          if ($(this).data("core").toLowerCase().indexOf($("#filterInput").val().toLowerCase()) == -1) {
+        $("#collections li").removeClass("hide");
+        $("#collections li").each(function () {
+          if ($(this).data("collection").toLowerCase().indexOf($("#filterInput").val().toLowerCase()) == -1) {
             $(this).addClass("hide");
           }
         });

+ 11 - 11
apps/search/src/search/templates/admin_core_facets.mako → apps/search/src/search/templates/admin_collection_facets.mako

@@ -26,10 +26,10 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
 
 <%layout:skeleton>
   <%def name="title()">
-    <h1>${_('Search Admin - ')}${hue_core.label}</h1>
+    <h1>${_('Search Admin - ')}${hue_collection.label}</h1>
   </%def>
   <%def name="navigation()">
-    ${ layout.sidebar(hue_core.name, 'facets') }
+    ${ layout.sidebar(hue_collection.name, 'facets') }
   </%def>
   <%def name="content()">
     <form method="POST" class="form-horizontal" data-bind="submit: submit">
@@ -287,26 +287,26 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
 
     self.isSaveBtnVisible = ko.observable(false);
 
-    self.fields = ko.observableArray(${ hue_core.fields | n,unicode });
+    self.fields = ko.observableArray(${ hue_collection.fields | n,unicode });
 
     self.fullFields = {}
-    $.each(${ hue_core.fields_data | n,unicode }, function(index, field) {
+    $.each(${ hue_collection.fields_data | n,unicode }, function(index, field) {
       self.fullFields[field.name] = field;
     });
 
-    self.properties = ko.observable(new Properties(${ hue_core.facets.data | n,unicode }.properties));
+    self.properties = ko.observable(new Properties(${ hue_collection.facets.data | n,unicode }.properties));
 
-    self.fieldFacets = ko.observableArray(ko.utils.arrayMap(${ hue_core.facets.data | n,unicode }.fields, function (obj) {
+    self.fieldFacets = ko.observableArray(ko.utils.arrayMap(${ hue_collection.facets.data | n,unicode }.fields, function (obj) {
       return new FieldFacet(obj);
     }));
 
     // Remove already selected fields
-    self.fieldFacetsList = ko.observableArray(${ hue_core.fields | n,unicode });
+    self.fieldFacetsList = ko.observableArray(${ hue_collection.fields | n,unicode });
     $.each(self.fieldFacets(), function(index, field) {
       self.fieldFacetsList.remove(field.field);
     });
 
-    self.rangeFacets = ko.observableArray(ko.utils.arrayMap(${ hue_core.facets.data | n,unicode }.ranges, function (obj) {
+    self.rangeFacets = ko.observableArray(ko.utils.arrayMap(${ hue_collection.facets.data | n,unicode }.ranges, function (obj) {
       return new RangeFacet(obj);
     }));
 
@@ -318,7 +318,7 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
       }
     });
 
-    self.dateFacets = ko.observableArray(ko.utils.arrayMap(${ hue_core.facets.data | n,unicode }.dates, function (obj) {
+    self.dateFacets = ko.observableArray(ko.utils.arrayMap(${ hue_collection.facets.data | n,unicode }.dates, function (obj) {
       return new DateFacet({
           field: obj.field,
           label: obj.label,
@@ -339,7 +339,7 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
     // List of all facets sorted by UUID
     self.sortableFacets = ko.observableArray(self.fieldFacets().concat(self.rangeFacets()).concat(self.dateFacets()));
     self.sortableFacets.sort(function(left, right) {
-      var sorted_ids = ${ hue_core.facets.data | n,unicode }.order;
+      var sorted_ids = ${ hue_collection.facets.data | n,unicode }.order;
       return sorted_ids.indexOf(left.uuid) > sorted_ids.indexOf(right.uuid);
     })
 
@@ -461,7 +461,7 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
     };
 
     self.submit = function () {
-      $.ajax("${ url('search:admin_core_facets', core=hue_core.name) }", {
+      $.ajax("${ url('search:admin_collection_facets', collection=hue_collection.name) }", {
         data: {
           'properties': ko.toJSON(self.properties),
           'fields': ko.utils.stringifyJson(self.fieldFacets),

+ 6 - 6
apps/search/src/search/templates/admin_core_highlighting.mako → apps/search/src/search/templates/admin_collection_highlighting.mako

@@ -26,11 +26,11 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
 
 <%layout:skeleton>
   <%def name="title()">
-    <h1>${_('Search Admin - ')}${hue_core.label}</h1>
+    <h1>${_('Search Admin - ')}${hue_collection.label}</h1>
   </%def>
 
   <%def name="navigation()">
-    ${ layout.sidebar(hue_core.name, 'highlighting') }
+    ${ layout.sidebar(hue_collection.name, 'highlighting') }
   </%def>
 
   <%def name="content()">
@@ -91,16 +91,16 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
 <script type="text/javascript">
   function ViewModel() {
     var self = this;
-    self.fields = ko.observableArray(${ hue_core.fields | n,unicode });
+    self.fields = ko.observableArray(${ hue_collection.fields | n,unicode });
 
-    var highlighting = ${ hue_core.result.get_highlighting() | n,unicode };
-    var properties = ${ hue_core.result.get_properties() | n,unicode };
+    var highlighting = ${ hue_collection.result.get_highlighting() | n,unicode };
+    var properties = ${ hue_collection.result.get_properties() | n,unicode };
 
     self.highlightedFields = ko.observableArray(highlighting != null ? highlighting : []);
     self.isEnabled = ko.observable(properties.highlighting_enabled);
 
     self.submit = function () {
-      $.ajax("${ url('search:admin_core_highlighting', core=hue_core.name) }", {
+      $.ajax("${ url('search:admin_collection_highlighting', collection=hue_collection.name) }", {
         data: {
           'properties': ko.utils.stringifyJson({'highlighting_enabled': self.isEnabled()}),
           'highlighting': ko.utils.stringifyJson(self.highlightedFields)

+ 13 - 13
apps/search/src/search/templates/admin_core_properties.mako → apps/search/src/search/templates/admin_collection_properties.mako

@@ -27,31 +27,31 @@
 ${ commonheader(_('Search'), "search", user) | n,unicode }
 
 <%def name="indexProperty(key)">
-  %if key in solr_core["status"][hue_core.name]["index"]:
-      ${ solr_core["status"][hue_core.name]["index"][key] }
+  %if key in solr_collection["status"][hue_collection.name]["index"]:
+      ${ solr_collection["status"][hue_collection.name]["index"][key] }
     %endif
 </%def>
 
-<%def name="coreProperty(key)">
-  %if key in solr_core["status"][hue_core.name]:
-      ${ solr_core["status"][hue_core.name][key] }
+<%def name="collectionProperty(key)">
+  %if key in solr_collection["status"][hue_collection.name]:
+      ${ solr_collection["status"][hue_collection.name][key] }
     %endif
 </%def>
 
 <%layout:skeleton>
   <%def name="title()">
-    <h1>${_('Search Admin - ')}${hue_core.label}</h1>
+    <h1>${_('Search Admin - ')} ${ hue_collection.label }</h1>
   </%def>
 
   <%def name="navigation()">
-    ${ layout.sidebar(hue_core.name, 'properties') }
+    ${ layout.sidebar(hue_collection.name, 'properties') }
   </%def>
 
   <%def name="content()">
   <form method="POST">
     <ul class="nav nav-tabs">
       <li class="active">
-        <a href="#index" data-toggle="tab">${_('Core')}</a>
+        <a href="#index" data-toggle="tab">${_('Collection')}</a>
       </li>
       <li>
         <a href="#schema" data-toggle="tab">${_('Schema')}</a>
@@ -63,9 +63,9 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
     <div class="tab-content">
       <div class="tab-pane active" id="index">
         <div class="fieldWrapper">
-          ${ utils.render_field(core_form['enabled']) }
-          ${ utils.render_field(core_form['name']) }
-          ${ utils.render_field(core_form['label']) }
+          ${ utils.render_field(collection_form['enabled']) }
+          ${ utils.render_field(collection_form['name']) }
+          ${ utils.render_field(collection_form['label']) }
         </div>
 
 	    <div class="form-actions">
@@ -88,10 +88,10 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
 
 <script type="text/javascript" charset="utf-8">
   $(document).ready(function(){
-    $.get("${ url('search:admin_core_schema', core=hue_core.name) }", function(data) {
+    $.get("${ url('search:admin_collection_schema', collection=hue_collection.name) }", function(data) {
         $("#schema").html(data.content); // Need to scroll to refresh
     });
-    $.get("${ url('search:admin_core_solr_properties', core=hue_core.name) }", function(data) {
+    $.get("${ url('search:admin_collection_solr_properties', collection=hue_collection.name) }", function(data) {
         $("#properties").html(data.content);
     });
  });

+ 15 - 15
apps/search/src/search/templates/admin_core_properties_solr_properties.mako → apps/search/src/search/templates/admin_collection_properties_solr_properties.mako

@@ -23,14 +23,14 @@
 
 
 <%def name="indexProperty(key)">
-  %if key in solr_core["status"][hue_core.name]["index"]:
-      ${ solr_core["status"][hue_core.name]["index"][key] }
+  %if key in solr_collection["status"][hue_collection.name]["index"]:
+      ${ solr_collection["status"][hue_collection.name]["index"][key] }
     %endif
 </%def>
 
-<%def name="coreProperty(key)">
-  %if key in solr_core["status"][hue_core.name]:
-      ${ solr_core["status"][hue_core.name][key] }
+<%def name="collectionProperty(key)">
+  %if key in solr_collection["status"][hue_collection.name]:
+      ${ solr_collection["status"][hue_collection.name][key] }
     %endif
 </%def>
 
@@ -41,7 +41,7 @@
   <%def name="content()">
     <ul class="nav nav-tabs">
       <li class="active"><a href="#index_properties" data-toggle="tab">${_('Index properties')}</a></li>
-      <li><a href="#core_properties" data-toggle="tab">${_('Core properties')}</a></li>
+      <li><a href="#collection_properties" data-toggle="tab">${_('Core properties')}</a></li>
     </ul>
 
     <div class="tab-content">
@@ -97,7 +97,7 @@
           </tbody>
         </table>
       </div>
-      <div class="tab-pane" id="core_properties">
+      <div class="tab-pane" id="collection_properties">
         <table class="table">
           <thead>
           <tr>
@@ -108,35 +108,35 @@
           <tbody>
           <tr>
             <td>uptime</td>
-            <td>${ coreProperty('uptime') }</td>
+            <td>${ collectionProperty('uptime') }</td>
           </tr>
           <tr>
             <td>name</td>
-            <td>${ coreProperty('name') }</td>
+            <td>${ collectionProperty('name') }</td>
           </tr>
           <tr>
             <td>isDefaultCore</td>
-            <td>${ coreProperty('isDefaultCore') }</td>
+            <td>${ collectionProperty('isDefaultCore') }</td>
           </tr>
           <tr>
             <td>dataDir</td>
-            <td>${ coreProperty('dataDir') }</td>
+            <td>${ collectionProperty('dataDir') }</td>
           </tr>
           <tr>
             <td>instanceDir</td>
-            <td>${ coreProperty('instanceDir') }</td>
+            <td>${ collectionProperty('instanceDir') }</td>
           </tr>
           <tr>
             <td>startTime</td>
-            <td>${ coreProperty('startTime') }</td>
+            <td>${ collectionProperty('startTime') }</td>
           </tr>
           <tr>
             <td>config</td>
-            <td>${ coreProperty('config') }</td>
+            <td>${ collectionProperty('config') }</td>
           </tr>
           <tr>
             <td>schema</td>
-            <td>${ coreProperty('schema') }</td>
+            <td>${ collectionProperty('schema') }</td>
           </tr>
           </tbody>
         </table>

+ 0 - 0
apps/search/src/search/templates/admin_core_properties_solr_schema.mako → apps/search/src/search/templates/admin_collection_properties_solr_schema.mako


+ 7 - 7
apps/search/src/search/templates/admin_core_sorting.mako → apps/search/src/search/templates/admin_collection_sorting.mako

@@ -26,11 +26,11 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
 
 <%layout:skeleton>
   <%def name="title()">
-    <h1>${_('Search Admin - ')}${hue_core.label}</h1>
+    <h1>${_('Search Admin - ')}${hue_collection.label}</h1>
   </%def>
 
   <%def name="navigation()">
-    ${ layout.sidebar(hue_core.name, 'sorting') }
+    ${ layout.sidebar(hue_collection.name, 'sorting') }
   </%def>
 
   <%def name="content()">
@@ -121,16 +121,16 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
 
   function ViewModel() {
     var self = this;
-    self.fields = ko.observableArray(${ hue_core.fields | n,unicode });
+    self.fields = ko.observableArray(${ hue_collection.fields | n,unicode });
 
-    self.isEnabled = ko.observable(${ hue_core.sorting.data | n,unicode }.properties.is_enabled);
+    self.isEnabled = ko.observable(${ hue_collection.sorting.data | n,unicode }.properties.is_enabled);
 
-    self.sortingFields = ko.observableArray(ko.utils.arrayMap(${ hue_core.sorting.data | n,unicode }.fields, function (obj) {
+    self.sortingFields = ko.observableArray(ko.utils.arrayMap(${ hue_collection.sorting.data | n,unicode }.fields, function (obj) {
       return new SortingField(obj.field, obj.label, obj.asc);
     }));
 
     // Remove already selected fields
-    self.sortingFieldsList = ko.observableArray(${ hue_core.fields | n,unicode });
+    self.sortingFieldsList = ko.observableArray(${ hue_collection.fields | n,unicode });
     $.each(self.sortingFields(), function(index, field) {
       self.sortingFieldsList.remove(field.field);
     });
@@ -160,7 +160,7 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
     };
 
     self.submit = function () {
-      $.ajax("${ url('search:admin_core_sorting', core=hue_core.name) }", {
+      $.ajax("${ url('search:admin_collection_sorting', collection=hue_collection.name) }", {
         data: {
           'properties': ko.utils.stringifyJson({'is_enabled': self.isEnabled()}),
           'fields': ko.utils.stringifyJson(self.sortingFields)

+ 7 - 7
apps/search/src/search/templates/admin_core_template.mako → apps/search/src/search/templates/admin_collection_template.mako

@@ -124,11 +124,11 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
 
 <%layout:skeleton>
   <%def name="title()">
-    <h1>${ _('Template Editor ') } : ${ hue_core.name }</h1>
+    <h1>${ _('Template Editor ') } : ${ hue_collection.name }</h1>
   </%def>
 
   <%def name="navigation()">
-    ${ layout.sidebar(hue_core.name, 'template') }
+    ${ layout.sidebar(hue_collection.name, 'template') }
   </%def>
 
   <%def name="content()">
@@ -144,7 +144,7 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
         <div class="row-fluid">
           <div class="span9">
             <div id="toolbar"></div>
-            <div id="content-editor" class="clear">${ hue_core.result.get_template() | n,unicode }</div>
+            <div id="content-editor" class="clear">${ hue_collection.result.get_template() | n,unicode }</div>
             <div id="load-template" class="btn-group">
               <a title="Layout" class="btn toolbar-btn toolbar-cmd">
                 <i class="icon-th-large" style="margin-top:2px;"></i>
@@ -261,7 +261,7 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
         <div class="row-fluid">
           <div class="span12">
             <span class="muted"> ${ _('Here you can define custom CSS classes or Javascript functions that you can use in your template.') }</span><br/><br/>
-            <textarea id="template-extra">${ hue_core.result.get_extracode() | n,unicode }</textarea>
+            <textarea id="template-extra">${ hue_collection.result.get_extracode() | n,unicode }</textarea>
           </div>
         </div>
 
@@ -393,7 +393,7 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
 </%layout:skeleton>
 
 <span id="extraCode">
-  ${ hue_core.result.get_extracode() | n,unicode }
+  ${ hue_collection.result.get_extracode() | n,unicode }
 </span>
 
 <link rel="stylesheet" href="/static/ext/farbtastic/farbtastic.css">
@@ -484,7 +484,7 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
 
     function ViewModel() {
       var self = this;
-      self.availableFields = ko.observableArray(${ hue_core.fields | n,unicode });
+      self.availableFields = ko.observableArray(${ hue_collection.fields | n,unicode });
       self.selectedVisualField = ko.observable();
       self.selectedVisualFunction = ko.observable();
       self.selectedVisualFunction.subscribe(function (newValue) {
@@ -618,7 +618,7 @@ ${ commonheader(_('Search'), "search", user) | n,unicode }
     });
 
     $("#save-template").click(function () {
-      $.ajax("${ url('search:admin_core_template', core=hue_core.name) }", {
+      $.ajax("${ url('search:admin_collection_template', collection=hue_collection.name) }", {
         data: {
           'template': ko.utils.stringifyJson($("#content-editor").html()),
           'extracode': ko.utils.stringifyJson(templateExtraMirror.getValue())

+ 111 - 0
apps/search/src/search/templates/admin_collections.mako

@@ -0,0 +1,111 @@
+## 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.
+
+<%!
+  from desktop.views import commonheader, commonfooter
+  from django.utils.translation import ugettext as _
+%>
+
+<%namespace name="macros" file="macros.mako" />
+<%namespace name="actionbar" file="actionbar.mako" />
+
+${ commonheader(_('Search'), "search", user) | n,unicode }
+
+<link rel="stylesheet" href="/search/static/css/admin.css">
+
+<div class="container-fluid">
+  <h1>${_('Search Admin - Cores')}</h1>
+  <%actionbar:render>
+    <%def name="search()">
+      <input type="text" placeholder="${_('Filter collections by name...')}" class="input-xxlarge search-query" id="filterInput">
+    </%def>
+  </%actionbar:render>
+  <div class="row-fluid">
+    <div class="span12">
+      <ul id="collections">
+      % for collection in hue_collections:
+        <li style="cursor: move" data-collection="${ collection.name }">
+          <a href="${ collection.get_absolute_url() }" class="pull-right" style="margin-top: 10px;margin-right: 10px"><i class="icon-edit"></i> ${_('Edit')}</a>
+          <h4><i class="icon-list"></i> ${ collection.name }</h4>
+        </li>
+      % endfor
+      </ul>
+    </div>
+  </div>
+</div>
+
+<style type="text/css">
+  #collections {
+    list-style-type: none;
+    margin: 0;
+    padding: 0;
+    width: 100%;
+  }
+
+  #collections li {
+    margin-bottom: 10px;
+    padding: 10px;
+    border: 1px solid #E3E3E3;
+    height: 40px;
+  }
+
+  .placeholder {
+    height: 40px;
+    background-color: #F5F5F5;
+    border: 1px solid #E3E3E3;
+  }
+</style>
+
+<script src="/static/ext/js/jquery/plugins/jquery-ui-draggable-droppable-sortable-1.8.23.min.js"></script>
+
+<script type="text/javascript">
+  $(document).ready(function () {
+    var orderedCores;
+    serializeList();
+    $("#collections").sortable({
+      placeholder: "placeholder",
+      update: function (event, ui) {
+        serializeList();
+        ##TODO: serialize via ajax the order of collections
+        ## the array is: orderedCores
+        ## console.log(orderedCores)
+      }
+    });
+    $("#collections").disableSelection();
+
+    function serializeList() {
+      orderedCores = [];
+      $("#collections li").each(function () {
+        orderedCores.push($(this).data("collection"));
+      });
+    }
+
+    var filter = -1;
+    $("#filterInput").on("keyup", function () {
+      clearTimeout(filter);
+      filter = window.setTimeout(function () {
+        $("#collections li").removeClass("hide");
+        $("#collections li").each(function () {
+          if ($(this).data("collection").toLowerCase().indexOf($("#filterInput").val().toLowerCase()) == -1) {
+            $(this).addClass("hide");
+          }
+        });
+      }, 300);
+    });
+  });
+</script>
+
+${ commonfooter(messages) | n,unicode }

+ 112 - 0
apps/search/src/search/templates/admin_collections_wizard.mako

@@ -0,0 +1,112 @@
+## 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.
+
+<%!
+  from desktop.views import commonheader, commonfooter
+  from django.utils.translation import ugettext as _
+%>
+
+<%namespace name="macros" file="macros.mako" />
+<%namespace name="actionbar" file="actionbar.mako" />
+
+${ commonheader(_('Search'), "search", user) | n,unicode }
+
+<link rel="stylesheet" href="/search/static/css/admin.css">
+
+<div class="container-fluid">
+  % if collections:
+  <h1>${_('Import a new collection')}</h1>
+
+  <div class="row-fluid">
+    <div class="span12">
+      <ul id="collections">
+      % for collection in collections:
+        <li>
+          <a class="addCollection" data-name="${ collection }">
+            <h4><i class="icon-list"></i> ${ collection }</h4>
+          </a>
+        </li>
+      % endfor
+      </ul>
+    </div>
+  </div>
+  % endif
+  
+  % if cores:
+  <h1>${_('Import a new core')}</h1>
+
+  <div class="row-fluid">
+    <div class="span12">
+      <ul id="collections">
+      % for core in cores:
+        <li>
+          <h4><i class="icon-list"></i> ${ core }</h4>
+        </li>
+      % endfor
+      </ul>
+    </div>
+  </div>  
+  % endif
+  
+  % if not collections and not cores:
+  <h1>${_('No available indexes')}</h1>
+
+  <div class="row-fluid">
+    ${ _('Already installed all the collections. You can change the indexes URL in hue.ini.') }
+  </div>      
+  % endif
+  
+</div>
+
+<style type="text/css">
+  #collections {
+    list-style-type: none;
+    margin: 0;
+    padding: 0;
+    width: 100%;
+  }
+
+  #collections li {
+    margin-bottom: 10px;
+    padding: 10px;
+    border: 1px solid #E3E3E3;
+    height: 40px;
+  }
+
+  .placeholder {
+    height: 40px;
+    background-color: #F5F5F5;
+    border: 1px solid #E3E3E3;
+  }
+</style>
+
+<script type="text/javascript">
+  $(document).ready(function () {
+    $(".addCollection").click(function() {
+      var collectionName = $(this).data('name');
+      $.post('${ url("search:admin_collections_wizard") }', {type: 'collection', name: collectionName},
+        function(response) {
+          if (response['status'] != 0) {
+            $.jHueNotify.error("${ _('Problem: ') }" + response['message']);
+          } else {
+            window.location = "/search/admin/collection/" + collectionName;
+          }
+	  });
+    });
+  });
+</script>
+
+${ commonfooter(messages) | n,unicode }

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

@@ -54,39 +54,39 @@
   </div>
   <script type="text/javascript">
     $(document).ready(function () {
-      $("#change-core").change(function(){
-        location.href = $("#change-core").val();
+      $("#change-collection").change(function(){
+        location.href = $("#change-collection").val();
       });
     });
   </script>
 </%def>
 
-<%def name="sidebar(core, section='')">
+<%def name="sidebar(collection, section='')">
   <div class="well sidebar-nav" style="min-height: 250px">
     <ul class="nav nav-list">
 
     <li class="nav-header">${_('Core')}</li>
       <li class="${ utils.is_selected(section, 'properties') }">
-        <a href="${ url('search:admin_core_properties', core=core) }"><i class="icon-reorder"></i> ${_('Properties')}</a>
+        <a href="${ url('search:admin_collection_properties', collection=collection) }"><i class="icon-reorder"></i> ${_('Properties')}</a>
       </li>
 
       <li class="nav-header">${_('Template')}</li>
       <li class="${ utils.is_selected(section, 'template') }">
-        <a href="${ url('search:admin_core_template', core=core) }">${_('1. Snippet')}</a>
+        <a href="${ url('search:admin_collection_template', collection=collection) }">${_('1. Snippet')}</a>
       </li>
       <li class="${ utils.is_selected(section, 'facets') }">
-        <a href="${ url('search:admin_core_facets', core=core) }">${_('2. Facets')}</a>
+        <a href="${ url('search:admin_collection_facets', collection=collection) }">${_('2. Facets')}</a>
       </li>
       <li class="${ utils.is_selected(section, 'sorting') }">
-        <a href="${ url('search:admin_core_sorting', core=core) }">${_('3. Sorting')}</a>
+        <a href="${ url('search:admin_collection_sorting', collection=collection) }">${_('3. Sorting')}</a>
       </li>
       <li class="${ utils.is_selected(section, 'highlighting') }">
-        <a href="${ url('search:admin_core_highlighting', core=core) }">${_('4. Highlighting')}</a>
+        <a href="${ url('search:admin_collection_highlighting', collection=collection) }">${_('4. Highlighting')}</a>
       </li>
 
       <li class="nav-header">${_('Search')}</li>
       <li>
-        <a href="${ url('search:index') }?collection=${ core }"><i class="icon-share-alt"></i> ${ _('Query') }</a>
+        <a href="${ url('search:index') }?collection=${ collection }"><i class="icon-share-alt"></i> ${ _('Query') }</a>
       </li>
     </ul>
   </div>

+ 32 - 20
apps/search/src/search/templates/search.mako

@@ -31,20 +31,22 @@ ${ commonheader(_('Search'), "search", user, "40px") | n,unicode }
 <div class="search-bar">
   % if user.is_superuser:
     <div class="pull-right" style="margin-top: 4px">
+      <a href="${ url('search:admin_collections') }"><i class="icon-edit"></i> ${ _('Collection manager') }</a>
+      <a href="${ url('search:admin_collections_wizard') }"><i class="icon-edit"></i> ${ _('Add collection') }</a>
       <a class="change-settings" href="#"><i class="icon-edit"></i> ${ _('Customize result display') }</a>
     </div>
   % endif
   <form class="form-search" style="margin: 0">
     <div class="dropdown" style="display: inline">
-      Search in <a href="#" data-toggle="dropdown"><strong class="current-core"></strong> <i class="icon-caret-down"></i></a>
+      Search in <a href="#" data-toggle="dropdown"><strong class="current-collection"></strong> <i class="icon-caret-down"></i></a>
       <ul class="dropdown-menu">
         % if user.is_superuser:
-          % for core in hue_cores:
-            <li><a class="dropdown-core" href="#" data-value="${ core.name }" data-settings-url="${ core.get_absolute_url() }">${ core.label }</a></li>
+          % for collection in hue_collections:
+            <li><a class="dropdown-collection" href="#" data-value="${ collection.name }" data-settings-url="${ collection.get_absolute_url() }">${ collection.label }</a></li>
           % endfor
         % else:
-          % for core in hue_cores:
-            <li><a class="dropdown-core" href="#" data-value="${ core.name }">${ core.label }</a></li>
+          % for collection in hue_collections:
+            <li><a class="dropdown-collection" href="#" data-value="${ collection.name }">${ collection.label }</a></li>
           % endfor
         % endif
       </ul>
@@ -90,24 +92,24 @@ ${ commonheader(_('Search'), "search", user, "40px") | n,unicode }
           % for group, count in macros.pairwise(fld['counts']):
             % if count > 0 and group != "" and found_value == "":
               % if fld['type'] == 'field':
-                <li><a href='?collection=${ current_core }&query=${ solr_query['q'] }&fq=${ solr_query['fq'] }|${ fld['field'] }:"${ urllib.quote_plus(group.encode('ascii', 'xmlcharrefreplace')) }"${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}'>${group}</a> <span class="counter">(${ count })</span></li>
+                <li><a href='?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${ solr_query['fq'] }|${ fld['field'] }:"${ urllib.quote_plus(group.encode('ascii', 'xmlcharrefreplace')) }"${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}'>${group}</a> <span class="counter">(${ count })</span></li>
               % endif
               % if fld['type'] == 'range':
-                <li><a href='?collection=${ current_core }&query=${ solr_query['q'] }&fq=${ solr_query['fq'] }|${ fld['field'] }:["${ group }" TO "${ str(int(group) + int(fld['gap']) - 1) }"]${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}'>${ group } (${ count })</a></li>
+                <li><a href='?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${ solr_query['fq'] }|${ fld['field'] }:["${ group }" TO "${ str(int(group) + int(fld['gap']) - 1) }"]${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}'>${ group } (${ count })</a></li>
               % endif
               % if fld['type'] == 'date':
-                <li class="dateFacetItem"><a href='?collection=${ current_core }&query=${ solr_query['q'] }&fq=${ solr_query['fq'] }|${ fld['field'] }:"${ group }"${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}'><span class="dateFacet">${ group }</span> (${ count })</a></li>
+                <li class="dateFacetItem"><a href='?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${ solr_query['fq'] }|${ fld['field'] }:"${ group }"${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}'><span class="dateFacet">${ group }</span> (${ count })</a></li>
               % endif
             % endif
             % if found_value != "":
               % if fld['type'] == 'field' and '"' + group + '"' == found_value:
-                <li><strong>${ group }</strong> <a href="?collection=${ current_core }&query=${ solr_query['q'] }&fq=${'|'.join(remove_list)}${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}"><i class="icon-remove"></i></a></li>
+                <li><strong>${ group }</strong> <a href="?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${'|'.join(remove_list)}${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}"><i class="icon-remove"></i></a></li>
               % endif
               % if fld['type'] == 'range' and '["' + group + '" TO "' + str(int(group) + int(fld['gap']) - 1) + '"]' == found_value:
-                <li><strong>${ group }</strong> <a href="?collection=${ current_core }&query=${ solr_query['q'] }&fq=${'|'.join(remove_list)}${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}"><i class="icon-remove"></i></a></li>
+                <li><strong>${ group }</strong> <a href="?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${'|'.join(remove_list)}${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}"><i class="icon-remove"></i></a></li>
               % endif
               % if fld['type'] == 'date' and '"' + group + '"' == found_value:
-                <li><strong><span class="dateFacet">${group}</span></strong> <a href="?collection=${ current_core }&query=${ solr_query['q'] }&fq=${'|'.join(remove_list)}${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}"><i class="icon-remove"></i></a></li>
+                <li><strong><span class="dateFacet">${group}</span></strong> <a href="?collection=${ current_collection }&query=${ solr_query['q'] }&fq=${'|'.join(remove_list)}${solr_query.get("sort") and '&sort=' + solr_query.get("sort") or ''}"><i class="icon-remove"></i></a></li>
               % endif
             % endif
           % endfor
@@ -142,7 +144,7 @@ ${ commonheader(_('Search'), "search", user, "40px") | n,unicode }
 
       <div id="result-container"></div>
 
-      <textarea id="mustacheTmpl" class="hide">${ hue_core.result.get_template(with_highlighting=True) | n,unicode }</textarea>
+      <textarea id="mustacheTmpl" class="hide">${ hue_collection.result.get_template(with_highlighting=True) | n,unicode }</textarea>
       <script>
 
       <%
@@ -152,6 +154,16 @@ ${ commonheader(_('Search'), "search", user, "40px") | n,unicode }
             doc.update(response['highlighting'][doc['id']])
         %>
 
+        function genericFormatDate(val, item, format){
+          var d = moment(Mustache.render(val, item));
+          if (d.isValid()) {
+            return d.format(format);
+          }
+          else {
+            return Mustache.render(val, item);
+          }
+        }
+
         var _mustacheTmpl = fixTemplateDots($("#mustacheTmpl").text());
         $.each(${ json.dumps([result for result in docs]) | n,unicode }, function (index, item) {
           addTemplateFunctions(item);
@@ -196,7 +208,7 @@ ${ commonheader(_('Search'), "search", user, "40px") | n,unicode }
 </div>
 
 
-${ hue_core.result.get_extracode() | n,unicode }
+${ hue_collection.result.get_extracode() | n,unicode }
 
 <script>
   $(document).ready(function () {
@@ -228,23 +240,23 @@ ${ hue_core.result.get_extracode() | n,unicode }
     });
     $(".dateFacetHeader").after(orderedDateFacets);
 
-    $(".current-core").text("${ current_core }");
+    $(".current-collection").text("${ current_collection }");
     % if user.is_superuser:
-        $(".dropdown-core").each(function () {
+        $(".dropdown-collection").each(function () {
           if ($(this).data("value") == $("select[name='collection']").val()) {
             $(".change-settings").attr("href", $(this).data("settings-url"));
           }
         });
     % endif
 
-    $(".dropdown-core").click(function (e) {
+    $(".dropdown-collection").click(function (e) {
       e.preventDefault();
-      $(".current-core").text($(this).text());
+      $(".current-collection").text($(this).text());
       $("select[name='collection']").val($(this).data("value"));
       % if user.is_superuser:
           $(".change-settings").attr("href", $(this).data("settings-url"));
       % endif
-      $.cookie("hueSearchLastCore", $(this).text(), {expires: 90});
+      $.cookie("hueSearchLastCollection", $(this).text(), {expires: 90});
       $("form").submit();
     });
 
@@ -254,7 +266,7 @@ ${ hue_core.result.get_extracode() | n,unicode }
     });
     $("#recordsPerPage").val($("input[name='rows']").val());
 
-    var sortingData = ${ hue_core.sorting.data | n,unicode };
+    var sortingData = ${ hue_collection.sorting.data | n,unicode };
     if (sortingData && sortingData.fields && sortingData.fields.length > 0) {
       $.each(sortingData.fields, function (index, item) {
         $("<option>").attr("value", item.label).text(item.label).data("field", item.field).data("asc", item.asc).appendTo($(".sort-by"));
@@ -313,7 +325,7 @@ ${ hue_core.result.get_extracode() | n,unicode }
       var query = $("#id_query").val();
       if ($.trim(query) != "") {
         $("#id_query").addClass("deletable");
-        $.ajax("${ url('search:query_suggest', core=hue_core.name) }" + query, {
+        $.ajax("${ url('search:query_suggest', collection=hue_collection.name) }" + query, {
           type: 'GET',
           success: function (data) {
             if (data.message.spellcheck && ! jQuery.isEmptyObject(data.message.spellcheck.suggestions)) {

+ 13 - 10
apps/search/src/search/urls.py

@@ -22,17 +22,20 @@ urlpatterns = patterns('search.views',
   url(r'^query$', 'index', name='query'),
   url(r'^admin$', 'admin', name='admin'),
 
-  url(r'^admin/cores$', 'admin', name='admin_cores'),
+  url(r'^admin/collections_$', 'admin_collections', name='admin_collections'),
+  url(r'^admin/collections_wizard$', 'admin_collections_wizard', name='admin_collections_wizard'),
 
-  url(r'^admin/core/(?P<core>\w+)$', 'admin_core_template', name='admin_core'),
-  url(r'^admin/core/(?P<core>\w+)/properties$', 'admin_core_properties', name='admin_core_properties'),
-  url(r'^admin/core/(?P<core>\w+)/template$', 'admin_core_template', name='admin_core_template'),
-  url(r'^admin/core/(?P<core>\w+)/facets$', 'admin_core_facets', name='admin_core_facets'),
-  url(r'^admin/core/(?P<core>\w+)/highlighting$', 'admin_core_highlighting', name='admin_core_highlighting'),
-  url(r'^admin/core/(?P<core>\w+)/sorting$', 'admin_core_sorting', name='admin_core_sorting'),
+  #url(r'^admin/collections$', 'admin', name='admin_collections'),
+
+  url(r'^admin/collection/(?P<collection>\w+)$', 'admin_collection_template', name='admin_collection'),
+  url(r'^admin/collection/(?P<collection>\w+)/properties$', 'admin_collection_properties', name='admin_collection_properties'),
+  url(r'^admin/collection/(?P<collection>\w+)/template$', 'admin_collection_template', name='admin_collection_template'),
+  url(r'^admin/collection/(?P<collection>\w+)/facets$', 'admin_collection_facets', name='admin_collection_facets'),
+  url(r'^admin/collection/(?P<collection>\w+)/highlighting$', 'admin_collection_highlighting', name='admin_collection_highlighting'),
+  url(r'^admin/collection/(?P<collection>\w+)/sorting$', 'admin_collection_sorting', name='admin_collection_sorting'),
 
   # Ajax
-  url(r'^suggest/(?P<core>\w+)/(?P<query>\w+)?$', 'query_suggest', name='query_suggest'),
-  url(r'^admin/core/(?P<core>\w+)/schema$', 'admin_core_schema', name='admin_core_schema'),
-  url(r'^admin/core/(?P<core>\w+)/solr_properties$', 'admin_core_solr_properties', name='admin_core_solr_properties'),
+  url(r'^suggest/(?P<collection>\w+)/(?P<query>\w+)?$', 'query_suggest', name='query_suggest'),
+  url(r'^admin/collection/(?P<collection>\w+)/schema$', 'admin_collection_schema', name='admin_collection_schema'),
+  url(r'^admin/collection/(?P<collection>\w+)/solr_properties$', 'admin_collection_solr_properties', name='admin_collection_solr_properties'),
 )

+ 144 - 102
apps/search/src/search/views.py

@@ -28,23 +28,29 @@ from django.utils.translation import ugettext as _
 from django.shortcuts import redirect
 
 from desktop.lib.django_util import render
+from desktop.lib.exceptions_renderable import PopupException
 
 from search.api import SolrApi
 from search.conf import SOLR_URL
 from search.decorators import allow_admin_only
-from search.forms import QueryForm, CoreForm, HighlightingForm
-from search.models import Core, augment_solr_response
-
+from search.forms import QueryForm, CollectionForm, HighlightingForm
+from search.models import Collection, augment_solr_response
+from search.search_controler import SearchController
 
 LOG = logging.getLogger(__name__)
 
 
-def index(request):
-  cores = SolrApi(SOLR_URL.get()).cores()
-  hue_cores = Core.objects.all()
+def index(request):  
+  hue_collections = Collection.objects.all()
 
-  for core in cores['status']:
-    Core.objects.get_or_create(name=core)
+  if not hue_collections:
+    if request.user.is_superuser:
+      return admin_collections_wizard(request)
+    else:
+      raise PopupException(_('No collections! If user message, if admin send to wizard.'))
+#    collections = SolrApi(SOLR_URL.get()).collections()
+#    for collection in collections['status']:
+#      Collection.objects.get_or_create(name=collection)
 
   search_form = QueryForm(request.GET)
   response = {}
@@ -52,10 +58,10 @@ def index(request):
   solr_query = {}
 
   if search_form.is_valid():
-    core = search_form.cleaned_data['collection']
+    collection = search_form.cleaned_data['collection']
     if request.GET.get('collection') is None:
-      core = request.COOKIES.get('hueSearchLastCore', cores['status'].keys()[0])
-    solr_query['core'] = core
+      collection = request.COOKIES.get('hueSearchLastCollection', hue_collections[0].name)
+    solr_query['collection'] = collection
     solr_query['q'] = search_form.cleaned_data['query']
     solr_query['fq'] = search_form.cleaned_data['fq']
     if search_form.cleaned_data['sort']:
@@ -65,194 +71,230 @@ def index(request):
     solr_query['facets'] = search_form.cleaned_data['facets'] or 1
 
     try:
-      hue_core = Core.objects.get_or_create(name=core)
-      response = SolrApi(SOLR_URL.get()).query(solr_query, hue_core)
+      hue_collection = Collection.objects.get_or_create(name=collection)
+      response = SolrApi(SOLR_URL.get()).query(solr_query, hue_collection)
     except Exception, e:
       error['message'] = unicode(str(e), "utf8")
+  elif hue_collections:    
+    hue_collection = hue_collections[0]
+    collection = hue_collections.name
   else:
-    core = request.COOKIES.get('hueSearchLastCore', cores['status'].keys()[0])
-    hue_core = Core.objects.get_or_create(name=core)
+    #collection = request.COOKIES.get('hueSearchLastCollection', collections['status'].keys()[0])
+    #hue_collection = Collection.objects.get_or_create(name=collection)
+    raise PopupException(_('Please configure hue.ini to point to a Solr URL.'))
 
   if request.GET.get('format') == 'json':
-    return HttpResponse(json.dumps(augment_solr_response(response, hue_core.facets.get_data())), mimetype="application/json")
+    return HttpResponse(json.dumps(augment_solr_response(response, hue_collection.facets.get_data())), mimetype="application/json")
 
   return render('search.mako', request, {
     'search_form': search_form,
-    'response': augment_solr_response(response, hue_core.facets.get_data()),
+    'response': augment_solr_response(response, hue_collection.facets.get_data()),
     'error': error,
     'solr_query': solr_query,
-    'hue_core': hue_core,
-    'hue_cores': hue_cores,
-    'current_core': core,
+    'hue_collection': hue_collection,
+    'hue_collections': hue_collections,
+    'current_collection': collection,
     'json': json,
   })
 
 
+@allow_admin_only
+def admin_collections(request):
+  hue_collections = Collection.objects.all()
+
+  return render('admin_collections.mako', request, {
+    'hue_collections': hue_collections,
+  })
+  
+  
+@allow_admin_only
+def admin_collections_wizard(request):
+  searcher = SearchController()
+    
+  if request.method == 'POST':  
+    result = {'status': -1, 'message': 'Error'}
+    try:      
+      searcher.add_new_collection(request.POST.copy())
+      result['status'] = 0
+      request.info(_('Collection added!'))
+    except Exception, e:
+      result['message'] = unicode(str(e), "utf8")
+    return HttpResponse(json.dumps(result), mimetype="application/json")    
+  else:
+    collections = searcher.get_new_collections()
+    cores = searcher.get_new_cores()
+    return render('admin_collections_wizard.mako', request, {
+      'collections': collections,
+      'cores': cores, 
+    })  
+  
+
 @allow_admin_only
 def admin(request):
   # To cross check both
-  cores = SolrApi(SOLR_URL.get()).cores()
-  hue_cores = Core.objects.all()
+  collections = SolrApi(SOLR_URL.get()).collections()
+  hue_collections = Collection.objects.all()
 
   return render('admin.mako', request, {
-    'cores': cores,
-    'hue_cores': hue_cores,
+    'collections': collections,
+    'hue_collections': hue_collections,
   })
 
 
 @allow_admin_only
-def admin_core_properties(request, core):
-  solr_core = SolrApi(SOLR_URL.get()).core(core)
-  hue_core = Core.objects.get(name=core)
-  hue_cores = Core.objects.all()
+def admin_collection_properties(request, collection):
+  # TODO HACK !!
+  collection = 'collection3_shard2_replica1'
+  
+  solr_collection = SolrApi(SOLR_URL.get()).collection(collection)
+  hue_collection = Collection.objects.get(name=collection)
 
   if request.method == 'POST':
-    core_form = CoreForm(request.POST, instance=hue_core)
-    if core_form.is_valid():
-      hue_core = core_form.save()
-      return redirect(reverse('search:admin_core_properties', kwargs={'core': hue_core.name}))
+    collection_form = CollectionForm(request.POST, instance=hue_collection)
+    if collection_form.is_valid():
+      hue_collection = collection_form.save()
+      return redirect(reverse('search:admin_collection_properties', kwargs={'collection': hue_collection.name}))
     else:
-      request.error(_('Errors on the form: %s') % core_form.errors)
+      request.error(_('Errors on the form: %s') % collection_form.errors)
   else:
-    core_form = CoreForm(instance=hue_core)
+    collection_form = CollectionForm(instance=hue_collection)
 
-  return render('admin_core_properties.mako', request, {
-    'solr_core': solr_core,
-    'hue_core': hue_core,
-    'hue_cores': hue_cores,
-    'core_form': core_form,
+  return render('admin_collection_properties.mako', request, {
+    'solr_collection': solr_collection,
+    'hue_collection': hue_collection,
+    'collection_form': collection_form,
   })
 
 
 @allow_admin_only
-def admin_core_template(request, core):
-  solr_core = SolrApi(SOLR_URL.get()).core(core)
-  hue_core = Core.objects.get(name=core)
-  hue_cores = Core.objects.all()
+def admin_collection_template(request, collection):
+  solr_collection = SolrApi(SOLR_URL.get()).collection(collection)
+  hue_collection = Collection.objects.get(name=collection)
+  hue_collections = Collection.objects.all()
 
   if request.method == 'POST':
-    hue_core.result.update_from_post(request.POST)
-    hue_core.result.save()
+    hue_collection.result.update_from_post(request.POST)
+    hue_collection.result.save()
     return HttpResponse(json.dumps({}), mimetype="application/json")
 
   solr_query = {}
-  solr_query['core'] = core
+  solr_query['collection'] = collection
   solr_query['q'] = ''
   solr_query['fq'] = ''
   solr_query['rows'] = 5
   solr_query['start'] = 0
   solr_query['facets'] = 0
 
-  response = SolrApi(SOLR_URL.get()).query(solr_query, hue_core)
+  response = SolrApi(SOLR_URL.get()).query(solr_query, hue_collection)
 
-  return render('admin_core_template.mako', request, {
-    'solr_core': solr_core,
-    'hue_core': hue_core,
-    'hue_cores': hue_cores,
+  return render('admin_collection_template.mako', request, {
+    'solr_collection': solr_collection,
+    'hue_collection': hue_collection,
+    'hue_collections': hue_collections,
     'sample_data': json.dumps(response["response"]["docs"]),
   })
 
 
 @allow_admin_only
-def admin_core_facets(request, core):
-  solr_core = SolrApi(SOLR_URL.get()).core(core)
-  hue_core = Core.objects.get(name=core)
-  hue_cores = Core.objects.all()
+def admin_collection_facets(request, collection):
+  solr_collection = SolrApi(SOLR_URL.get()).collection(collection)
+  hue_collection = Collection.objects.get(name=collection)
+  hue_collections = Collection.objects.all()
 
   if request.method == 'POST':
-    hue_core.facets.update_from_post(request.POST)
-    hue_core.facets.save()
+    hue_collection.facets.update_from_post(request.POST)
+    hue_collection.facets.save()
     return HttpResponse(json.dumps({}), mimetype="application/json")
 
-  return render('admin_core_facets.mako', request, {
-    'solr_core': solr_core,
-    'hue_core': hue_core,
-    'hue_cores': hue_cores,
+  return render('admin_collection_facets.mako', request, {
+    'solr_collection': solr_collection,
+    'hue_collection': hue_collection,
+    'hue_collections': hue_collections,
   })
 
 
 @allow_admin_only
-def admin_core_sorting(request, core):
-  solr_core = SolrApi(SOLR_URL.get()).core(core)
-  hue_core = Core.objects.get(name=core)
-  hue_cores = Core.objects.all()
+def admin_collection_sorting(request, collection):
+  solr_collection = SolrApi(SOLR_URL.get()).collection(collection)
+  hue_collection = Collection.objects.get(name=collection)
+  hue_collections = Collection.objects.all()
 
   if request.method == 'POST':
-    hue_core.sorting.update_from_post(request.POST)
-    hue_core.sorting.save()
+    hue_collection.sorting.update_from_post(request.POST)
+    hue_collection.sorting.save()
     return HttpResponse(json.dumps({}), mimetype="application/json")
 
-  return render('admin_core_sorting.mako', request, {
-    'solr_core': solr_core,
-    'hue_core': hue_core,
-    'hue_cores': hue_cores,
+  return render('admin_collection_sorting.mako', request, {
+    'solr_collection': solr_collection,
+    'hue_collection': hue_collection,
+    'hue_collections': hue_collections,
   })
 
 
 @allow_admin_only
-def admin_core_highlighting(request, core):
-  solr_core = SolrApi(SOLR_URL.get()).core(core)
-  hue_core = Core.objects.get(name=core)
-  hue_cores = Core.objects.all()
+def admin_collection_highlighting(request, collection):
+  solr_collection = SolrApi(SOLR_URL.get()).collection(collection)
+  hue_collection = Collection.objects.get(name=collection)
+  hue_collections = Collection.objects.all()
 
   if request.method == 'POST':
-    hue_core.result.update_from_post(request.POST)
-    hue_core.result.save()
+    hue_collection.result.update_from_post(request.POST)
+    hue_collection.result.save()
     return HttpResponse(json.dumps({}), mimetype="application/json")
 
-  return render('admin_core_highlighting.mako', request, {
-    'solr_core': solr_core,
-    'hue_core': hue_core,
-    'hue_cores': hue_cores,
+  return render('admin_collection_highlighting.mako', request, {
+    'solr_collection': solr_collection,
+    'hue_collection': hue_collection,
+    'hue_collections': hue_collections,
   })
 
 
 # Ajax below
 
 @allow_admin_only
-def admin_core_solr_properties(request, core):
-  solr_core = SolrApi(SOLR_URL.get()).core(core)
-  hue_core = Core.objects.get(name=core)
-  hue_cores = Core.objects.all()
-
-  content = render('admin_core_properties_solr_properties.mako', request, {
-    'solr_core': solr_core,
-    'hue_core': hue_core,
-    'hue_cores': hue_cores,
+def admin_collection_solr_properties(request, collection):
+  solr_collection = SolrApi(SOLR_URL.get()).collection(collection)
+  hue_collection = Collection.objects.get(name=collection)
+  hue_collections = Collection.objects.all()
+
+  content = render('admin_collection_properties_solr_properties.mako', request, {
+    'solr_collection': solr_collection,
+    'hue_collection': hue_collection,
+    'hue_collections': hue_collections,
   }, force_template=True).content
 
   return HttpResponse(json.dumps({'content': content}), mimetype="application/json")
 
 
 @allow_admin_only
-def admin_core_schema(request, core):
-  solr_schema = SolrApi(SOLR_URL.get()).schema(core)
-  hue_core = Core.objects.get(name=core)
-  hue_cores = Core.objects.all()
+def admin_collection_schema(request, collection):
+  solr_schema = SolrApi(SOLR_URL.get()).schema(collection)
+  hue_collection = Collection.objects.get(name=collection)
+  hue_collections = Collection.objects.all()
 
-  content = render('admin_core_properties_solr_schema.mako', request, {
+  content = render('admin_collection_properties_solr_schema.mako', request, {
     'solr_schema': solr_schema,
-    'hue_core': hue_core,
-    'hue_cores': hue_cores,
+    'hue_collection': hue_collection,
+    'hue_collections': hue_collections,
   }, force_template=True).content
 
   return HttpResponse(json.dumps({'content': content}), mimetype="application/json")
 
 
 # TODO security
-def query_suggest(request, core, query=""):
-  hue_core = Core.objects.get(name=core)
+def query_suggest(request, collection, query=""):
+  hue_collection = Collection.objects.get(name=collection)
   result = {'status': -1, 'message': 'Error'}
 
   solr_query = {}
-  solr_query['core'] = core
+  solr_query['collection'] = collection
   solr_query['q'] = query
 
   try:
-    response = SolrApi(SOLR_URL.get()).suggest(solr_query, hue_core)
+    response = SolrApi(SOLR_URL.get()).suggest(solr_query, hue_collection)
     result['message'] = response
     result['status'] = 0
   except Exception, e:
-    error['message'] = unicode(str(e), "utf8")
+    result['message'] = unicode(str(e), "utf8")
 
   return HttpResponse(json.dumps(result), mimetype="application/json")