Explorar o código

[impala] Dashboard skeleton

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

+ 134 - 0
apps/impala/src/impala/templates/dashboard.mako

@@ -0,0 +1,134 @@
+## 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="dashboard" file="common_dashboard.mako" />
+
+${ commonheader(None, "impala", user) | n,unicode }
+
+<%dashboard:layout_toolbar>
+  <%def name="widgets()">
+    <div data-bind="css: { 'draggable-widget': true},
+          draggable: {data: draggableResultset(), isEnabled: true,
+              options: {'start': function(event, ui){lastWindowScrollPosition = $(window).scrollTop();$('.card-body').slideUp('fast');},
+                        'stop': function(event, ui){$('.card-body').slideDown('fast'); }}}"
+         title="${_('Grid Results')}" rel="tooltip" data-placement="top">
+         <a data-bind="style: { cursor: true ? 'move' : 'default' }">
+            <i class="fa fa-table"></i>
+         </a>
+    </div>
+    <div data-bind="css: { 'draggable-widget': true},
+          draggable: {data: draggablePie(), isEnabled: true,
+              options: {'start': function(event, ui){lastWindowScrollPosition = $(window).scrollTop();$('.card-body').slideUp('fast');},
+                        'stop': function(event, ui){$('.card-body').slideDown('fast'); }}}"
+         title="${_('Pie Chart')}" rel="tooltip" data-placement="top">
+         <a data-bind="style: { cursor: true ? 'move' : 'default' }">
+            <i class="hcha hcha-pie-chart"></i>
+         </a>
+    </div>    
+</%def>
+</%dashboard:layout_toolbar>
+
+
+<div class="search-bar">
+  <div class="pull-right" style="padding-right:50px">
+    % if user.is_superuser:
+      <button type="button" title="${ _('Edit') }" rel="tooltip" data-placement="bottom" data-bind="click: toggleEditing, css: {'btn': true, 'btn-inverse': isEditing}"><i class="fa fa-pencil"></i></button>
+    % endif
+  </div>
+
+
+  <form class="form-search" style="margin: 0" data-bind="submit: search">
+    <strong>${_("Search")}</strong>
+    <div class="input-append">
+
+      <span data-bind="foreach: query.qs">
+        <input data-bind="clearable: q" maxlength="4096" type="text" class="search-query input-xlarge">
+  ##      <!-- ko if: $index() >= 1 -->
+  ##      <a class="btn" href="javascript:void(0)" data-bind="click: $root.query.removeQ"><i class="fa fa-minus"></i></a>
+  ##      <!-- /ko -->
+      </span>
+
+  ##    <a class="btn" href="javascript:void(0)" data-bind="click: $root.query.addQ"><i class="fa fa-plus"></i></a>
+
+      <button type="submit" id="search-btn" class="btn btn-inverse" style="margin-left:10px"><i class="fa fa-search"></i></button>
+    </div>
+  </form>
+</div>
+
+
+${ dashboard.layout_skeleton() }
+
+<script type="text/html" id="resultset-widget">
+
+<div id="result-main" style="overflow-x: auto">
+  <table id="result-container" style="margin-top: 0; width: 100%">
+    <thead>
+    </thead>
+    <tbody data-bind="foreach: { data: $root.results, as: 'row'}" class="result-tbody">
+      <tr class="result-row">
+        <td>
+          <a href="javascript:void(0)">
+            <i class="fa" data-bind="css: {'fa-caret-right' : true }"></i>
+          </a>
+        </td>
+        <!-- ko foreach: row -->
+          <td data-bind="html: $data"></td>
+        <!-- /ko -->
+      </tr>
+    </tbody>
+  </table>
+</div>
+</script>
+
+<script type="text/html" id="pie-widget">
+  <!-- ko if: $root.getFacetFromResult(id()) -->
+  <div class="row-fluid" data-bind="with: $root.getFacetFromResult(id())">
+    <span data-bind="foreach: data()">
+      <span data-bind="text: $data"></span>
+    </span>
+  </div>
+  <!-- /ko -->
+</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>
+
+${ dashboard.import_layout() }
+${ dashboard.import_bindings() }
+${ dashboard.import_charts() }
+
+<script src="/impala/static/js/search.ko.js" type="text/javascript" charset="utf-8"></script>
+
+<script type="text/javascript">
+  function columnDropAdditionalHandler(widget){
+    console.log("column drop")
+  }
+
+  function widgetDraggedAdditionalHandler(widget){
+    console.log("boom")
+  }
+
+  var viewModel = new TestViewModel(${ query_json | n,unicode }, ${ dashboard_json | n,unicode  });
+  ko.applyBindings(viewModel);
+</script>
+
+${ commonfooter(messages) | n,unicode }

+ 2 - 0
apps/impala/src/impala/urls.py

@@ -21,6 +21,8 @@ from beeswax.urls import urlpatterns as beeswax_urls
 
 
 urlpatterns = patterns('impala.views',
+  url(r'^dashboard$', 'dashboard', name='dashboard'),
+  url(r'^query', 'query', name='query'),
 )
 
 urlpatterns += beeswax_urls

+ 65 - 1
apps/impala/src/impala/views.py

@@ -15,4 +15,68 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-## Views are inherited from Beeswax.
+
+## Main views are inherited from Beeswax.
+
+
+import logging
+import json
+
+from django.http import HttpResponse
+
+from desktop.lib.django_util import render
+from beeswax.design import hql_query
+from beeswax.server import dbms
+from beeswax.server.dbms import get_query_server_config
+
+
+LOG = logging.getLogger(__name__)
+
+
+def dashboard(request):
+  return render('dashboard.mako', request, {
+    'query_json': json.dumps({}),
+    'dashboard_json': json.dumps({'layout': [
+              {"size":2,"rows":[{"widgets":[{"size":12,"name":"Pie Results","id":"52f07188-f30f-1296-2450-f77e02e1a5c1","widgetType":"pie-widget",
+                   "properties":{},"offset":0,"isLoading":True,"klass":"card card-widget span12"}]}],"drops":["temp"],"klass":"card card-home card-column span2"},
+              {"size":10,"rows":[{"widgets":[
+                  {"size":12,"name":"Grid Results","id":"52f07188-f30f-1296-2450-f77e02e1a5c0","widgetType":"resultset-widget",
+                   "properties":{},"offset":0,"isLoading":True,"klass":"card card-widget span12"}]}],
+              "drops":["temp"],"klass":"card card-home card-column span10"}
+         ],
+        'facets': [{'id': '52f07188-f30f-1296-2450-f77e02e1a5c1', 'label': 'aa', 'field': 'salary', 'widget_type': 'pie'}],
+        'properties': [{'database': 'default', 'table': 'sample_07'}]
+        }), 
+                                            # type: MAX, / ORDER BY, LIMIT 100
+  })
+
+
+def query(request):  
+  result = {
+    'status': -1,
+    'data': {}
+  }
+    
+  if 'facet' in request.POST: 
+    database = 'default'
+    table = 'sample_07'    
+    hql = "SELECT salary FROM %s.%s WHERE salary IS NOT NULL ORDER BY salary DESC LIMIT 10" % (database, table,)
+    result['id'] = json.loads(request.POST['facet'])['id']
+  else:
+    database = 'default'
+    table = 'sample_07'    
+    hql = "SELECT * FROM %s.%s" % (database, table,)
+
+  query_server = get_query_server_config(name='impala')
+  query = hql_query(hql)
+  db = dbms.get(request.user, query_server=query_server)
+  handle = db.execute_and_wait(query, timeout_sec=5.0)
+
+  if handle:
+    result['data'] = list(db.fetch(handle, rows=100).rows())
+    result['status'] = 0
+    db.close(handle)
+    
+  return HttpResponse(json.dumps(result), mimetype="application/json")
+
+  

+ 157 - 0
apps/impala/static/js/search.ko.js

@@ -0,0 +1,157 @@
+// 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 magicLayout(vm) {
+  loadLayout(vm, vm.initial.layout);
+  $(document).trigger("magicLayout");
+}
+
+function loadLayout(viewModel, json_layout) {
+  var _columns = [];
+
+  $(json_layout).each(function (cnt, json_col) {
+    var _rows = [];
+    $(json_col.rows).each(function (rcnt, json_row) {
+      var row = new Row([], viewModel);
+      $(json_row.widgets).each(function (wcnt, widget) {
+        row.addWidget(new Widget({
+          size:widget.size,
+          id: widget.id,
+          name: widget.name,
+          widgetType: widget.widgetType,
+          properties: widget.properties,
+          offset: widget.offset,
+          loading: true,
+          vm: viewModel
+        }));
+      });
+      _rows.push(row);
+    });
+    var column = new Column(json_col.size, _rows);
+    _columns = _columns.concat(column);
+  });
+
+  viewModel.columns(_columns);
+}
+
+// End dashboard lib
+
+var Query = function (vm, query) {
+  var self = this;
+  // current select
+  self.qs = ko.mapping.fromJS([{'q': ''}]);
+  self.fqs = ko.mapping.fromJS([{'q': ''}]);
+}
+
+var Dashboard = function (vm, dashboard) { 
+  // id from DB
+  var self = this;
+  
+  self.facets = ko.observable(dashboard.facets);
+  
+  self.addFacet = function(data) {
+    
+  }
+}
+
+var TestViewModel = function (query_json, dashboard_json) {
+    var self = this;
+    self.isEditing = ko.observable(false);
+    self.toggleEditing = function () {
+      self.isEditing(! self.isEditing());
+    };
+    self.previewColumns = ko.observable("");
+    self.columns = ko.observable([]);
+
+    loadLayout(self, dashboard_json.layout);
+
+    self.query = new Query(self, query_json);
+    self.dashboard = new Dashboard(self, dashboard_json);
+    self.results = ko.observableArray([]);
+    self.results_facet = ko.observableArray([]);
+
+    self.search = function (callback) {
+    	self.results.removeAll();
+    	
+    	var multiQs = $.map(self.dashboard.facets(), function(facet) {
+            return $.post("/impala/query", {
+                "query": ko.mapping.toJSON(self.query),
+                "dashboard": ko.mapping.toJSON(self.dashboard),
+                "layout": ko.mapping.toJSON(self.columns),
+                "facet": ko.mapping.toJSON(facet),
+              }, function (data) {return data});
+          });    	
+    	
+    	$.when.apply($, [
+          $.post("/impala/query", {
+            "query": ko.mapping.toJSON(self.query),
+            "dashboard": ko.mapping.toJSON(self.dashboard),
+            "layout": ko.mapping.toJSON(self.columns),
+            }, function (data) {
+              if (data.status == 0) {
+            	$.each(data.data, function (index, row) {
+            	  self.results.push(row);
+            	});
+              } else {
+                $(document).trigger("error", data.message);
+              }
+            })
+          ].concat(multiQs)
+        )
+        .done(function() {
+          if (arguments.length > 1) { // If multi queries
+            for (var i = 1; i < arguments.length; i++) {
+              var facet = arguments[i][0];
+              removeFrom(self.results_facet, facet.id);
+              self.results_facet.push(ko.mapping.fromJS(facet));
+            }
+          }
+        })
+        .fail(function (xhr, textStatus, errorThrown) {});
+    };
+
+    function removeFrom(collection, item_id) {
+      $.each(collection(), function (index, item) {
+        if (item.id() == item_id) {
+          self.collection.remove(item);
+          return false;
+        }
+      });
+    }
+    
+    self.getFacetFromResult = function (facet_id) {
+      var _facet = null;
+      $.each(self.results_facet(), function (index, facet) {
+        if (facet.id() == facet_id) {
+          _facet = facet;
+          return false;
+        }
+      });
+      return _facet;
+    }   
+    
+    function bareWidgetBuilder(name, type){
+      return new Widget({
+        size: 12,
+        id: UUID(),
+        name: name,
+        widgetType: type
+      });
+    }
+
+    self.draggableResultset = ko.observable(bareWidgetBuilder("Grid Results", "resultset-widget"));
+    self.draggablePie = ko.observable(bareWidgetBuilder("Pie Chart", "pie-widget"));
+};