Browse Source

[core] Add table and column stats to assist component

Johan Ahlen 10 years ago
parent
commit
eeab27b
1 changed files with 184 additions and 5 deletions
  1. 184 5
      desktop/core/src/desktop/templates/ko_components.mako

+ 184 - 5
desktop/core/src/desktop/templates/ko_components.mako

@@ -115,6 +115,50 @@ from django.utils.translation import ugettext as _
     }
     }
   </style>
   </style>
 
 
+  <script type="text/html" id="assist-panel-table-stats">
+    <div class="content">
+      <!-- ko if: statRows().length -->
+      <table class="table table-striped">
+        <tbody data-bind="foreach: statRows">
+          <tr><th data-bind="text: data_type"></th><td data-bind="text: comment"></td></tr>
+        </tbody>
+      </table>
+      <!-- /ko -->
+    </div>
+  </script>
+
+  <script type="text/html" id="assist-panel-column-stats">
+    <div class="pull-right hide filter">
+      <input id="columnAnalysisTermsFilter" type="text" placeholder="${ _('Prefix filter...') }"/>
+    </div>
+    <ul class="nav nav-tabs" role="tablist">
+      <li class="active"><a href="#columnAnalysisStats" role="tab" data-toggle="tab">${ _('Stats') }</a></li>
+      <li><a href="#columnAnalysisTerms" role="tab" data-toggle="tab">${ _('Terms') }</a></li>
+    </ul>
+    <div class="tab-content">
+      <div class="tab-pane active" id="columnAnalysisStats" style="text-align: left">
+        <div class="content">
+          <table class="table table-striped">
+            <tbody data-bind="foreach: statRows">
+              <tr><th data-bind="text: Object.keys($data)[0]"></th><td data-bind="text: $data[Object.keys($data)[0]]"></td></tr>
+            </tbody>
+          </table>
+        </div>
+      </div>
+      <div class="tab-pane" id="columnAnalysisTerms" style="text-align: left">
+        <i style="margin: 5px;" data-bind="visible: loadingTerms" class='fa fa-spinner fa-spin'></i>
+        <div class="alert" data-bind="visible: !loadingTerms() && terms().length == 0">${ _('There are no terms to be shown') }</div>
+        <div class="content">
+          <table class="table table-striped">
+            <tbody data-bind="foreach: terms">
+              <tr><td data-bind="text: name"></td><td style="width: 40px"><div class="progress"><div class="bar-label" data-bind="text: count"></div><div class="bar bar-info" style="margin-top: -20px;" data-bind="style: { 'width' : percent + '%' }"></div></div></td></tr>
+            </tbody>
+          </table>
+        </div>
+      </div>
+    </div>
+  </script>
+
   <script type="text/html" id="assist-panel-template">
   <script type="text/html" id="assist-panel-template">
     <div style="position: relative;">
     <div style="position: relative;">
       <ul class="nav nav-list" style="float:left; border: none; padding: 0; background-color: #FFF; margin-bottom: 1px; width: 100%;">
       <ul class="nav nav-list" style="float:left; border: none; padding: 0; background-color: #FFF; margin-bottom: 1px; width: 100%;">
@@ -132,14 +176,20 @@ from django.utils.translation import ugettext as _
         <li>
         <li>
           <div data-bind="slideVisible: options.isSearchVisible"><input type="text" placeholder="${ _('Table name...') }" style="width:90%;" data-bind="value: assist.filter, valueUpdate: 'afterkeydown'"/></div>
           <div data-bind="slideVisible: options.isSearchVisible"><input type="text" placeholder="${ _('Table name...') }" style="width:90%;" data-bind="value: assist.filter, valueUpdate: 'afterkeydown'"/></div>
           <ul class="assist-tables" data-bind="visible: Object.keys(assist.firstLevelObjects()).length > 0, foreach: assist.filteredFirstLevelObjects()">
           <ul class="assist-tables" data-bind="visible: Object.keys(assist.firstLevelObjects()).length > 0, foreach: assist.filteredFirstLevelObjects()">
-            <li data-bind="event: { mouseover: function(){ $('#assistHover_' + $data).show(); }, mouseout: function(){ $('#assistHover_' + $data).hide(); } }" style="position:relative;">
-              <div class="table-actions" data-bind="attr: {'id': 'assistHover_' + $data}" style="position:absolute; right: 0; display: none; padding-left:3px; background-color: #FFF">
-                <a href="javascript:void(0)" data-bind="click: $parent.showTablePreview"  class="preview-sample"><i class="fa fa-list" title="${'Preview Sample data'}"></i></a>
+            <li class="assist-table" style="position:relative;">
+              <div class="table-actions">
+                <a href="javascript:void(0)" class="preview-sample" data-bind="click: $parent.showTablePreview"><i class="fa fa-list" title="${_('Preview Sample data')}"></i></a>
+                <a href="javascript:void(0)" class="table-stats" data-bind="click: function(data, event) { $parent.showStats(data, null, event) }"><i class='fa fa-bar-chart' title="${_('View statistics') }"></i></a>
               </div>
               </div>
               <a class="assist-table-link" href="javascript:void(0)" data-bind="click: $parent.loadAssistSecondLevel, event: { 'dblclick': function(){ huePubSub.publish('assist.dblClickItem', $data); }, text: $data }"><span data-bind="text: $data"></span></a>
               <a class="assist-table-link" href="javascript:void(0)" data-bind="click: $parent.loadAssistSecondLevel, event: { 'dblclick': function(){ huePubSub.publish('assist.dblClickItem', $data); }, text: $data }"><span data-bind="text: $data"></span></a>
               <div data-bind="visible: $parent.assist.firstLevelObjects()[$data].loaded() && $parent.assist.firstLevelObjects()[$data].open()">
               <div data-bind="visible: $parent.assist.firstLevelObjects()[$data].loaded() && $parent.assist.firstLevelObjects()[$data].open()">
                 <ul class="assist-columns" data-bind="visible: $parent.assist.firstLevelObjects()[$data].items().length > 0, foreach: $parent.assist.firstLevelObjects()[$data].items()">
                 <ul class="assist-columns" data-bind="visible: $parent.assist.firstLevelObjects()[$data].items().length > 0, foreach: $parent.assist.firstLevelObjects()[$data].items()">
-                  <li><a class="assist-column-link" data-bind="attr: {'title': $parents[1].secondLevelTitle($data)}" style="padding-left:10px" href="javascript:void(0)"><span data-bind="html: $parents[1].truncateSecondLevel($data), event: { 'dblclick': function() { huePubSub.publish('assist.dblClickItem', $data.name +', '); } }"></span></a></li>
+                  <li class="assist-column">
+                    <div class="column-actions">
+                      <a href="javascript:void(0)" class="table-stats" data-bind="click: function(data, event) { $parents[1].showStats($parent, data.name, event) }"><i class='fa fa-bar-chart' title="${_('View statistics') }"></i></a>
+                    </div>
+                    <a class="assist-column-link" data-bind="attr: {'title': $parents[1].secondLevelTitle($data)}" style="padding-left:10px" href="javascript:void(0)"><span data-bind="html: $parents[1].truncateSecondLevel($data), event: { 'dblclick': function() { huePubSub.publish('assist.dblClickItem', $data.name +', '); } }"></span></a>
+                  </li>
                 </ul>
                 </ul>
               </div>
               </div>
             </li>
             </li>
@@ -176,6 +226,25 @@ from django.utils.translation import ugettext as _
         <button class="btn btn-primary disable-feedback" data-dismiss="modal">${_('Ok')}</button>
         <button class="btn btn-primary disable-feedback" data-dismiss="modal">${_('Ok')}</button>
       </div>
       </div>
     </div>
     </div>
+
+    <div id="tableAnalysis" style="position: fixed; display: none;" class="popover show mega-popover right" data-bind="visible: analysisStats() != null, with: analysisStats">
+      <div class="arrow"></div>
+      <h3 class="popover-title" style="text-align: left">
+        <a class="pull-right pointer close-popover" style="margin-left: 8px" data-bind="click: function() { $parent.analysisStats(null) }"><i class="fa fa-times"></i></a>
+        <span class="pull-right stats-warning muted" data-bind="visible: inaccurate" rel="tooltip" data-placement="top" title="${ _('The column stats for this table are not accurate') }" style="margin-left: 8px"><i class="fa fa-exclamation-triangle"></i></span>
+        <i data-bind="visible: loading" class='fa fa-spinner fa-spin'></i>
+        <!-- ko if: column == null -->
+        <strong class="table-name" data-bind="text: table"></strong> ${ _(' table analysis') }
+        <!-- /ko -->
+        <!-- ko ifnot: column == null -->
+        <strong class="table-name" data-bind="text: column"></strong> ${ _(' column analysis') }
+        <!-- /ko -->
+      </h3>
+      <div class="popover-content">
+        <!-- ko template: {if: column == null, name: 'assist-panel-table-stats' } --><!-- /ko -->
+        <!-- ko template: {ifnot: column == null, name: 'assist-panel-column-stats' } --><!-- /ko -->
+      </div>
+    </div>
   </script>
   </script>
 
 
   <script type="text/javascript" charset="utf-8">
   <script type="text/javascript" charset="utf-8">
@@ -183,6 +252,7 @@ from django.utils.translation import ugettext as _
       function AssistPanel(params) {
       function AssistPanel(params) {
         var self = this;
         var self = this;
 
 
+        self.assistAppName = params.assistAppName || "beeswax";
         self.options = ko.mapping.fromJS($.extend({
         self.options = ko.mapping.fromJS($.extend({
           isSearchVisible: false,
           isSearchVisible: false,
           lastSelectedDb: null
           lastSelectedDb: null
@@ -203,6 +273,7 @@ from django.utils.translation import ugettext as _
         };
         };
 
 
         self.modalItem = ko.observable();
         self.modalItem = ko.observable();
+        self.analysisStats = ko.observable();
 
 
         self.secondLevelTitle = function(level) {
         self.secondLevelTitle = function(level) {
           var _title = "";
           var _title = "";
@@ -303,7 +374,7 @@ from django.utils.translation import ugettext as _
         };
         };
 
 
         self.showTablePreview = function(table) {
         self.showTablePreview = function(table) {
-          var tableUrl = "/beeswax/api/table/" + self.assist.selectedMainObject() + "/" + table;
+          var tableUrl = "/" + self.assistAppName + "/api/table/" + self.assist.selectedMainObject() + "/" + table;
           $("#assistQuickLook").find(".tableName").text(table);
           $("#assistQuickLook").find(".tableName").text(table);
           $("#assistQuickLook").find(".tableLink").attr("href", "/metastore/table/" + self.assist.selectedMainObject() + "/" + table);
           $("#assistQuickLook").find(".tableLink").attr("href", "/metastore/table/" + self.assist.selectedMainObject() + "/" + table);
           $("#assistQuickLook").find(".sample").empty("");
           $("#assistQuickLook").find(".sample").empty("");
@@ -329,6 +400,114 @@ from django.utils.translation import ugettext as _
           $("#assistQuickLook").modal("show");
           $("#assistQuickLook").modal("show");
         };
         };
 
 
+        function TableStats (assistAppName, database, table, column) {
+          var self = this;
+
+          self.table = table;
+          self.column = column;
+          self.loading = ko.observable(false);
+          self.loadingTerms = ko.observable(false);
+          self.inaccurate = ko.observable(false);
+          self.statRows = ko.observableArray();
+          self.terms = ko.observableArray();
+
+          self.fetchTerms = function () {
+            self.loadingTerms(true);
+            $.ajax({
+              url: "/" + assistAppName + "/api/table/" + database + "/" + table + "/terms/" + column + "/",
+              data: {},
+              beforeSend: function (xhr) {
+                xhr.setRequestHeader("X-Requested-With", "Hue");
+              },
+              dataType: "json",
+              success: function (data) {
+                if (data && data.status == 0) {
+                  self.terms($.map(data.terms, function (term) {
+                    return {
+                      name: term[0],
+                      count: term[1],
+                      percent: (parseFloat(term[1]) / parseFloat(data.terms[0][1])) * 100
+                    }
+                  }));
+                } else {
+                  $("#tableAnalysis").hide();
+                  $(document).trigger("error", options.errorLabel);
+                }
+              },
+              error: function (e) {
+                if (e.status == 500) {
+                  $("#tableAnalysis").hide();
+                  $(document).trigger("error", options.errorLabel);
+                }
+              },
+              complete: function () {
+                self.loadingTerms(false);
+              }
+            });
+          };
+
+          self.fetchData = function() {
+            self.loading(true);
+            $.ajax({
+              url: "/" + assistAppName + "/api/table/" + database + "/" + table + "/stats/" + (column || ""),
+              data: {},
+              beforeSend: function (xhr) {
+                xhr.setRequestHeader("X-Requested-With", "Hue");
+              },
+              dataType: "json",
+              success: function (data) {
+                if (data && data.status == 0) {
+                  self.statRows(data.stats);
+                  for(var i = 0; i < data.stats.length; i++) {
+                    if (data.stats[i].data_type == "COLUMN_STATS_ACCURATE" && data.stats[i].comment == "false") {
+                      self.inaccurate(true);
+                      break;
+                    }
+                  }
+                } else {
+                  $("#tableAnalysis").hide();
+                  $(document).trigger("error", options.errorLabel);
+                }
+              },
+              error: function (e) {
+                if (e.status == 500) {
+                  $("#tableAnalysis").hide();
+                  $(document).trigger("error", options.errorLabel);
+                }
+              },
+              complete: function () {
+                self.loading(false);
+              }
+            });
+          };
+
+          self.fetchData();
+          if (this.column != null) {
+            self.fetchTerms();
+          }
+        }
+
+        var lastOffset = { top: -1, left: -1 };
+        var $tableAnalysis = $("#tableAnalysis");
+        var refreshPosition = function () {
+          var targetElement = $tableAnalysis.data("targetElement");
+          if (targetElement != null && targetElement.is(":visible")) {
+            if (targetElement != null && (lastOffset.left != targetElement.offset().left || lastOffset.top != targetElement.offset().top)) {
+              lastOffset = targetElement.offset();
+              $tableAnalysis.css("top", lastOffset.top - $tableAnalysis.outerHeight() / 2 + targetElement.outerHeight() / 2).css("left", lastOffset.left + targetElement.outerWidth());
+            }
+          } else {
+            $tableAnalysis.hide();
+          }
+        };
+        window.setInterval(refreshPosition, 200);
+
+        self.showStats = function (table, column, event) {
+          self.analysisStats(new TableStats(self.assistAppName, self.assist.selectedMainObject(), table, column));
+          $("#tableAnalysis").data("targetElement", $(event.target));
+          window.setTimeout(refreshPosition, 20);
+        };
+
         if (self.assist.options.baseURL != ""){
         if (self.assist.options.baseURL != ""){
           self.loadAssistMain();
           self.loadAssistMain();
         }
         }