Selaa lähdekoodia

HUE-5304 [indexer] Add index page with properties, sample data and field list

Romain Rigaux 8 vuotta sitten
vanhempi
commit
6cf01ac313

+ 15 - 0
desktop/libs/indexer/src/indexer/solr_api.py

@@ -161,6 +161,21 @@ def create_alias(request):
   return JsonResponse(response)
   return JsonResponse(response)
 
 
 
 
+@require_POST
+@api_error_handler
+def sample_index(request):
+  response = {'status': -1}
+
+  name = request.POST.get('name')
+
+  client = SolrClient(user=request.user)
+
+  response['sample'] = client.sample_index(name)['response']['docs']
+  response['status'] = 0
+
+  return JsonResponse(response)
+
+
 @require_POST
 @require_POST
 @api_error_handler
 @api_error_handler
 def list_configs(request):
 def list_configs(request):

+ 4 - 15
desktop/libs/indexer/src/indexer/solr_client.py

@@ -45,26 +45,11 @@ ZK_SOLR_CONFIG_NAMESPACE = 'configs'
 IS_SOLR_CLOUD = None
 IS_SOLR_CLOUD = None
 
 
 
 
-DEFAULT_FIELD = {
-  'name': None,
-  'type': 'text',
-  'indexed': True,
-  'stored': True,
-  'required': False,
-  'multiValued': False
-}
-
 
 
 class SolrClientException(Exception):
 class SolrClientException(Exception):
   pass
   pass
 
 
 
 
-def _get_fields(name='id', type='text'):
-  default_field = DEFAULT_FIELD.copy()
-  default_field.update({'name': name, 'type': type})
-  return [default_field]
-
-
 class SolrClient(object):
 class SolrClient(object):
 
 
   def __init__(self, user):
   def __init__(self, user):
@@ -213,6 +198,10 @@ class SolrClient(object):
         raise PopupException(_('Could not remove collection: %(message)s') % result)
         raise PopupException(_('Could not remove collection: %(message)s') % result)
 
 
 
 
+  def sample_index(self, collection, rows=100):
+    return self.api.select(collection, rows=min(rows, 1000))
+
+
   def list_configs(self):
   def list_configs(self):
     return self.api.configs()
     return self.api.configs()
 
 

+ 147 - 31
desktop/libs/indexer/src/indexer/templates/indexes.mako

@@ -15,10 +15,12 @@
 ## limitations under the License.
 ## limitations under the License.
 
 
 <%!
 <%!
-  from desktop.views import commonheader, commonfooter, commonshare, commonimportexport
   from django.utils.translation import ugettext as _
   from django.utils.translation import ugettext as _
+
+  from desktop.views import commonheader, commonfooter, commonshare, commonimportexport
 %>
 %>
 
 
+
 <%namespace name="actionbar" file="actionbar.mako" />
 <%namespace name="actionbar" file="actionbar.mako" />
 
 
 ${ commonheader(_("Index Browser"), "search", user, request, "60px") | n,unicode }
 ${ commonheader(_("Index Browser"), "search", user, request, "60px") | n,unicode }
@@ -67,35 +69,18 @@ ${ commonheader(_("Index Browser"), "search", user, request, "60px") | n,unicode
         <a href="javascript:void(0)" class="btn" data-bind="click: function() { alias.showCreateModal(true); }">
         <a href="javascript:void(0)" class="btn" data-bind="click: function() { alias.showCreateModal(true); }">
           <i class="fa fa-plus-circle"></i> ${ _('Create alias') }
           <i class="fa fa-plus-circle"></i> ${ _('Create alias') }
         </a>
         </a>
-
-        <a class="inactive-action" data-bind="tooltip: { placement: 'bottom', delay: 750 }" title="${_('Query the table')}" href="javascript:void(0)">
-          <i class="fa fa-play fa-fw"></i>
-        </a>
-
-        <a class="inactive-action" data-bind="tooltip: { placement: 'bottom', delay: 750 }" title="${_('Refresh')}" href="javascript:void(0)">
-          <i class="pointer fa fa-refresh fa-fw"></i>
-        </a>
-
-        <a class="inactive-action" href="javascript:void(0)" data-bind="tooltip: { placement: 'bottom', delay: 750 }" title="${_('Import Data')}">
-          <i class="fa fa-upload fa-fw"></i>
-        </a>
-
-        <a class="inactive-action" href="javascript:void(0)" data-toggle="modal" data-bind="tooltip: { placement: 'bottom', delay: 750 }">
-          <i class="fa fa-times fa-fw"></i>
-        </a>
       </%def>
       </%def>
     </%actionbar:render>
     </%actionbar:render>
 
 
     <!-- ko template: { if: alias.showCreateModal(), name: 'create-alias' }--><!-- /ko -->
     <!-- ko template: { if: alias.showCreateModal(), name: 'create-alias' }--><!-- /ko -->
     <!-- ko template: { if: section() == 'list-indexes', name: 'list-indexes' }--><!-- /ko -->
     <!-- ko template: { if: section() == 'list-indexes', name: 'list-indexes' }--><!-- /ko -->
     <!-- ko template: { if: section() == 'list-index', name: 'list-index', data: index() }--><!-- /ko -->
     <!-- ko template: { if: section() == 'list-index', name: 'list-index', data: index() }--><!-- /ko -->
-
   </div>
   </div>
 </div>
 </div>
 
 
 
 
 <script type="text/html" id="list-indexes">
 <script type="text/html" id="list-indexes">
-  <table id="indexTable" class="table datatables">
+  <table class="table datatables">
     <thead>
     <thead>
       <tr>
       <tr>
         <th width="1%"><div data-bind="click: selectAll, css: {hueCheckbox: true, 'fa': true, 'fa-check': allSelected}" class="select-all"></div></th>
         <th width="1%"><div data-bind="click: selectAll, css: {hueCheckbox: true, 'fa': true, 'fa-check': allSelected}" class="select-all"></div></th>
@@ -124,50 +109,158 @@ ${ commonheader(_("Index Browser"), "search", user, request, "60px") | n,unicode
 
 
 
 
 <script type="text/html" id="list-index">
 <script type="text/html" id="list-index">
+  <div class="pull-right">
+    <a class="inactive-action" data-bind="hueLink: '/search/browse/' + name(), tooltip: { placement: 'bottom', delay: 750 }" title="${_('Search the index')}" href="javascript:void(0)">
+      <i class="fa fa-search fa-fw"></i>
+    </a>
+
+    <a class="inactive-action" data-bind="tooltip: { placement: 'bottom', delay: 750 }" title="${_('Refresh')}" href="javascript:void(0)">
+      <i class="pointer fa fa-refresh fa-fw"></i>
+    </a>
+
+    <a class="inactive-action" href="javascript:void(0)" data-bind="tooltip: { placement: 'bottom', delay: 750 }" title="${_('Index Data')}">
+      <i class="fa fa-upload fa-fw"></i>
+    </a>
+
+    <a class="inactive-action" href="javascript:void(0)" data-toggle="modal" data-bind="tooltip: { placement: 'bottom', delay: 750 }">
+      <i class="fa fa-times fa-fw"></i>
+    </a>
+  </div>
+
+
   <div class="clearfix"></div>
   <div class="clearfix"></div>
 
 
   <ul class="nav nav-pills margin-top-30">
   <ul class="nav nav-pills margin-top-30">
-    <li class="active"><a href="#overview" data-toggle="tab" data-bind="click: function(){ $root.tab('index-overview'); }">${_('Overview')}</a></li>
-    <li><a href="#columns" data-toggle="tab" data-bind="click: function(){ $root.tab('index-columns'); }">${_('Fields')} (<span data-bind="text: fields().length"></span>)</a></li>
-    <li><a href="#sample" data-toggle="tab" data-bind="click: function(){ $root.tab('index-sample'); }">${_('Sample')}</a></li>
+    <li class="active"><a href="#index-overview" data-toggle="tab" data-bind="click: function(){ $root.tab('index-overview'); }">${_('Overview')}</a></li>
+    <li><a href="#index-columns" data-toggle="tab" data-bind="click: function(){ $root.tab('index-columns'); }">${_('Fields')} (<span data-bind="text: fields().length"></span>)</a></li>
+    <li><a href="#index-sample" data-toggle="tab" data-bind="click: function(){ $root.tab('index-sample'); }">${_('Sample')}</a></li>
   </ul>
   </ul>
 
 
   <div class="tab-content margin-top-10" style="border: none; overflow: hidden">
   <div class="tab-content margin-top-10" style="border: none; overflow: hidden">
-    <div class="tab-pane" id="overview">
-      <!-- ko template: { if: $root.tab() == 'index-overview', name: 'index-overview-tab' }--><!-- /ko -->
+    <div class="tab-pane active" id="index-overview">
+      <!-- ko template: { if: $root.tab() == 'index-overview', name: 'indexes-index-overview' }--><!-- /ko -->
     </div>
     </div>
 
 
-    <div class="tab-pane" id="columns">
+    <div class="tab-pane" id="index-columns">
       <!-- ko if: $root.tab() == 'index-columns' -->
       <!-- ko if: $root.tab() == 'index-columns' -->
         <input class="input-xlarge search-query margin-left-10" type="text" placeholder="${ _('Search for a column...') }" data-bind="clearable: $root.columnFilter, value: $root.columnFilter, valueUpdate: 'afterkeydown'"/>
         <input class="input-xlarge search-query margin-left-10" type="text" placeholder="${ _('Search for a column...') }" data-bind="clearable: $root.columnFilter, value: $root.columnFilter, valueUpdate: 'afterkeydown'"/>
-        <!-- ko template: 'index-columns-tab' --><!-- /ko -->
+        <!-- ko template: 'indexes-index-fields' --><!-- /ko -->
       <!-- /ko -->
       <!-- /ko -->
     </div>
     </div>
 
 
-    <div class="tab-pane" id="sample">
-      <!-- ko template: { if: $root.tab() == 'index-sample', name: 'index-sample-tab' }--><!-- /ko -->
+    <div class="tab-pane" id="index-sample">
+      <!-- ko template: { if: $root.tab() == 'index-sample', name: 'indexes-index-sample', data: sample() }--><!-- /ko -->
     </div>
     </div>
   </div>
   </div>
 </script>
 </script>
 
 
 
 
-<script type="text/html" id="index-overview-tab">
+<script type="text/html" id="indexes-index-overview">
   <div>
   <div>
     Overview
     Overview
+    
+    <!-- ko template: 'indexes-index-properties' --><!-- /ko -->
+
+    <!-- ko template: { name: 'indexes-index-fields-fields', data: fieldsPreview }--><!-- /ko -->
+    <a class="pointer" data-bind="visible: fields().length > fieldsPreview().length, click: function() { $('li a[href=\'#index-columns\']').click(); }">
+      ${_('View more...')}
+    </a>
+
+    <!-- ko template: { name: 'indexes-index-sample', data: samplePreview }--><!-- /ko -->
+    <a class="pointer" data-bind="visible: sample().length > samplePreview().length, click: function() { $('li a[href=\'#index-sample\']').click(); }">
+      ${_('View more...')}
+    </a>
+  </div>
+</script>
+
+
+<script type="text/html" id="indexes-index-properties">
+  <h4>${ _('Properties') }</h4>
+  <div class="row-fluid">
+    <div title="${ _('Type') }">
+      <i class="fa fa-fw fa-eye muted"></i> ${ _('Collection') }
+    </div>
+    <div title="${ _('Unique Key') }">
+      <i class="fa fa-fw fa-key muted"></i> <span data-bind="text: uniqueKey"></span>
+    </div>    
   </div>
   </div>
 </script>
 </script>
 
 
 
 
-<script type="text/html" id="index-columns-tab">
+<script type="text/html" id="indexes-index-fields-fields">
   <div>
   <div>
     Fields
     Fields
+
+    <table id="indexTable" class="table datatables">
+      <thead>
+        <tr>
+          <th width="1%"></th>
+          <th>${ _('Name') }</th>
+          <th>${ _('Type') }</th>
+          <th>${ _('Required') }</th>
+          <th>${ _('Stored') }</th>
+          <th>${ _('Indexed') }</th>
+          <th>${ _('Multivalued') }</th>
+        </tr>
+      </thead>
+      <tbody data-bind="foreach: $data">
+        <tr>
+          <td>
+            <div></div>
+          </td>
+          <td data-bind="text: name"></td>
+          <td data-bind="text: type"></td>
+          <td><i data-bind="visible: $data.required" class="fa fa-check muted"></i></td>
+          <td><i data-bind="visible: $data.stored" class="fa fa-check muted"></i></td>
+          <td><i data-bind="visible: indexed" class="fa fa-check muted"></i></td>
+          <td><i data-bind="visible: $data.multiValued" class="fa fa-check muted"></i></td>
+        </tr>
+      </tbody>
+    </table>
   </div>
   </div>
 </script>
 </script>
 
 
 
 
-<script type="text/html" id="index-sample-tab">
+<script type="text/html" id="indexes-index-fields">
+  <div>
+    <!-- ko template: { name: 'indexes-index-fields-fields', data: fields }--><!-- /ko -->
+    
+    Copy Fields
+    <span data-bind="text: ko.mapping.toJSON(copyFields)"></span>
+  
+  
+    Dynamic Fields
+    <span data-bind="text: ko.mapping.toJSON(dynamicFields)"></span> 
+  </div>
+</script>
+
+
+<script type="text/html" id="indexes-index-sample">
   <div>
   <div>
     Sample
     Sample
+    
+    <a data-bind="click: $root.index().getSample">Load</a>
+
+    <table class="table table-condensed table-nowrap sample-table">
+      <thead>
+        <tr>
+          <th style="width: 1%">&nbsp;</th>
+          <!-- ko foreach: $root.index().fields() -->
+          <th data-bind="text: name"></th>
+          <!-- /ko -->
+        </tr>
+      </thead>
+      <tbody>
+        <!-- ko foreach: $data -->
+          <tr>
+            <td data-bind="text: $index() + 1"></td>
+            <!-- ko foreach: $root.index().fields() -->
+              <td data-bind="text: $parent[name()]"></td>
+            <!-- /ko -->
+          </tr>
+        <!-- /ko -->
+      </tbody>
+    </table>
   </div>
   </div>
 </script>
 </script>
 
 
@@ -248,8 +341,31 @@ ${ commonheader(_("Index Browser"), "search", user, request, "60px") | n,unicode
     self.name = ko.observable(data.name);
     self.name = ko.observable(data.name);
     self.uniqueKey = ko.observable(data.schema.uniqueKey);
     self.uniqueKey = ko.observable(data.schema.uniqueKey);
     self.fields = ko.mapping.fromJS(data.schema.fields);
     self.fields = ko.mapping.fromJS(data.schema.fields);
+    self.fieldsPreview = ko.pureComputed(function() {
+      return self.fields().splice(0, 5)
+    });
     self.dynamicFields = ko.mapping.fromJS(data.schema.dynamicFields);
     self.dynamicFields = ko.mapping.fromJS(data.schema.dynamicFields);
     self.copyFields = ko.mapping.fromJS(data.schema.copyFields);
     self.copyFields = ko.mapping.fromJS(data.schema.copyFields);
+    
+    self.sample = ko.observableArray();
+    self.samplePreview = ko.pureComputed(function() {
+      return self.sample().splice(0, 5)
+    });
+    
+    self.getSample = function() {
+      $.post("${ url('indexer:sample_index') }", {
+        name: self.name(),
+        rows: 100
+      }, function(data) {
+        if (data.status == 0) {
+          self.sample(data.sample)
+        } else {
+          $(document).trigger("error", data.message);
+        }
+      }).fail(function (xhr, textStatus, errorThrown) {
+        $(document).trigger("error", xhr.responseText);
+      });
+    };
   };
   };
 
 
 
 

+ 1 - 0
desktop/libs/indexer/src/indexer/urls.py

@@ -51,6 +51,7 @@ urlpatterns += patterns('indexer.solr_api',
   url(r'^api/indexe/list/$', 'list_index', name='list_index'),
   url(r'^api/indexe/list/$', 'list_index', name='list_index'),
   url(r'^api/indexes/list/$', 'list_indexes', name='list_indexes'),
   url(r'^api/indexes/list/$', 'list_indexes', name='list_indexes'),
   url(r'^api/indexes/create/$', 'create_index', name='create_index'),
   url(r'^api/indexes/create/$', 'create_index', name='create_index'),
+  url(r'^api/indexes/sample/$', 'sample_index', name='sample_index'),
   url(r'^api/indexes/delete/$', 'delete_indexes', name='delete_indexes'),
   url(r'^api/indexes/delete/$', 'delete_indexes', name='delete_indexes'),
   url(r'^api/indexes/(?P<index>\w+)/schema/$', 'design_schema', name='design_schema')
   url(r'^api/indexes/(?P<index>\w+)/schema/$', 'design_schema', name='design_schema')
 )
 )

+ 15 - 0
desktop/libs/libsolr/src/libsolr/api.py

@@ -287,6 +287,21 @@ class SolrApi(object):
           break
           break
 
 
 
 
+  def select(self, collection, query=None, rows=100, start=0):
+    if query is None:
+      query = EMPTY_QUERY.get()
+
+    params = self._get_params() + (
+        ('q', query),
+        ('wt', 'json'),
+        ('rows', rows),
+        ('start', start),
+    )
+
+    response = self._root.get('%s/select' % collection, params)
+    return self._get_json(response)
+
+
   def suggest(self, collection, query):
   def suggest(self, collection, query):
     try:
     try:
       params = self._get_params() + (
       params = self._get_params() + (