Ver Fonte

HUE-4035 [editor] Add column search to context popover

This also contains some layout improvements here and there and foreachVisible now supports tables.
Johan Ahlen há 9 anos atrás
pai
commit
b0fec96

+ 28 - 5
desktop/core/src/desktop/static/desktop/js/ko.hue-bindings.js

@@ -3408,6 +3408,17 @@
    *  </ul>
    * </div>
    *
+   * For tables the binding has to be attached to the tbody element:
+   *
+   * <div class=".container" style="overflow-y: scroll; height: 100px">
+   *  <table>
+   *    <thead>...</thead>
+   *    <tbody data-bind="foreachVisible: { data: items, minHeight: 20, container: '.container' }>
+   *      <tr>...</tr>
+   *    </tbody>
+   *  </ul>
+   * </div>
+   *
    * Currently the binding only supports one element inside the bound element otherwise the height
    * calculations will be off. In other words this will make it go bonkers:
    *
@@ -3432,6 +3443,13 @@
     update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
       var options = valueAccessor();
       var $element = $(element);
+      var isTable = false;
+
+      if ($element.parent().is('table')) {
+        $element = $element.parent();
+        isTable = true;
+      }
+
       var $container = $element.closest(options.container);
 
       var id = Math.random();
@@ -3457,7 +3475,7 @@
             return false;
           }
         });
-      };
+      }
 
       if ($parentFVOwnerElement.data('disposalFunction')) {
         $parentFVOwnerElement.data('disposalFunction')();
@@ -3486,6 +3504,8 @@
           huePubSub.publish('foreach.visible.update', id);
         }
       };
+
+      // TODO: Move intervals to webworker
       var updateCountInterval = setInterval(updateVisibleEntryCount, 300);
       updateVisibleEntryCount();
 
@@ -3569,8 +3589,7 @@
           cursorminheight: options.cursorminheight || 20,
           horizrailenabled: options.horizrailenabled || false
         });
-      }
-      else {
+      } else {
         window.setTimeout(function(){
           $container.getNiceScroll().resize();
         }, 200);
@@ -3653,7 +3672,7 @@
       };
 
       var afterRender = function () {
-        renderedElements = $element.children();
+        renderedElements = isTable ? $element.children('tbody').children() : $element.children();
         $container.data('busyRendering', false);
         huePubSub.publish('foreach.visible.update.heights', id);
       };
@@ -3778,6 +3797,10 @@
         $parentFVOwnerElement.data('disposalFunction', null);
       });
 
+      if (typeof options.pubSubDispose !== 'undefined') {
+        huePubSubs.push(huePubSub.subscribe(options.pubSubDispose, $parentFVOwnerElement.data('disposalFunction')));
+      }
+
       ko.utils.domNodeDisposal.addDisposeCallback($wrapper[0], $parentFVOwnerElement.data('disposalFunction'));
 
       setStartAndEndFromScrollTop();
@@ -3793,7 +3816,7 @@
         return {
           data: ko.observableArray([])
         }
-      }
+      };
       ko.bindingHandlers.foreach.init(element, valueAccessorBuilder, allBindings);
     },
     update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {

+ 204 - 53
desktop/core/src/desktop/templates/sql_context_popover.mako

@@ -133,7 +133,7 @@ from metadata.conf import has_navigator
     }
 
     .sql-context-popover-content {
-      padding: 0 9px 14px 9px;
+      padding: 0;
       overflow: hidden;
     }
 
@@ -148,8 +148,8 @@ from metadata.conf import has_navigator
 
     .sql-context-tabs {
       border-bottom: 1px solid #ebebeb;
-      margin-left: -10px;
-      margin-right: -10px;
+      margin-left: -1px;
+      margin-right: -1px;
       padding-left: 15px;
     }
 
@@ -167,63 +167,124 @@ from metadata.conf import has_navigator
       content: "";
       border-width: 5px;
     }
+
+    .sql-context-flex {
+      position: relative;
+      display: flex;
+      flex-flow: column nowrap;
+      height: 100%;
+    }
+
+    .sql-context-flex-header {
+      flex: 0 0 35px;
+    }
+
+    .sql-context-flex-fill {
+      position: relative;
+      flex: 1 1 100%;
+      overflow: auto;
+    }
+
+    .sql-context-flex-bottom-links {
+      flex: 0 0 35px;
+      border-top: 1px solid #ebebeb;
+      z-index: 100;
+      background-color: #FFF;
+    }
+
+    .sql-context-link-row {
+      float: right;
+      margin: 10px 15px 0 10px;
+    }
+
+    .sql-context-link-row a {
+      margin-left: 10px;
+    }
+
+    .sql-context-inline-search {
+      border-radius: 8px !important;
+      min-height: 18px !important;
+      height: 18px !important;
+      margin: 0 5px 0 5px !important;
+      padding-right: 18px !important;
+    }
+
+    .sql-context-empty-columns {
+      letter-spacing: 0.035em;
+      margin-top: 50px;
+      font-size: 14px;
+      color: #737373;
+      text-align: center;
+    }
   </style>
 
 
   <script type="text/html" id="sql-context-table-details">
-    <!-- ko with: fetchedData -->
-      <div style="margin: 15px;" data-bind="foreach: extended_columns">
-        <div>
-          <a class="pointer" data-bind="text: name, attr: { title: comment }, click: function() { huePubSub.publish('sql.context.popover.scroll.to.column', name); }"></a> (<span data-bind="text: type.indexOf('<') !== -1 ? type.substring(0, type.indexOf('<')) : type, attr: { title: type }"></span>)
-        </div>
+    <div class="sql-context-flex">
+      <div class="sql-context-flex-fill" data-bind="with: fetchedData">
+        <!-- ko component: { name: 'sql-columns-table', params: { columns: extended_columns } } --><!-- /ko -->
       </div>
-      <div style="clear: both; margin-top: 20px;">
-        ${ _("Show in ") } <a class="pointer"  data-bind="click: function() { huePubSub.publish('sql.context.popover.show.in.assist') }">${ _("Assist") }</a><br />
-        ${ _("Open in ") } <a class="pointer"  data-bind="click: function() { huePubSub.publish('sql.context.popover.open.in.metastore') }">${ _("Metastore") }</a>, <a class="pointer" data-bind="attr: { href: hdfs_link }" target="_blank">${ _("File Browser") }</a>
+      <div class="sql-context-flex-bottom-links">
+        <div class="sql-context-link-row">
+          <a class="inactive-action pointer" data-bind="click: function() { huePubSub.publish('sql.context.popover.show.in.assist') }"><i style="font-size: 11px;" title="Show in Assist..." class="fa fa-search"></i> ${ _("Assist") }</a>
+          <a class="inactive-action pointer" data-bind="click: function() { huePubSub.publish('sql.context.popover.open.in.metastore') }"><i style="font-size: 11px;" title="Open in Metastore..." class="fa fa-external-link"></i> ${ _("Metastore") }</a>
+        </div>
       </div>
-    <!-- /ko -->
+    </div>
   </script>
 
   <script type="text/html" id="sql-context-column-details">
-    <!-- ko with: fetchedData -->
-    <div style="margin: 15px;">
-      <div><a class="pointer" data-bind="text: name, attr: { title: comment }, click: function() { huePubSub.publish('sql.context.popover.scroll.to.column', name); }"></a> (<span data-bind="text: type.indexOf('<') !== -1 ? type.substring(0, type.indexOf('<')) : type, attr: { title: type }"></span>)</div>
-      <div style="margin-top: 10px; font-weight: bold;">${ _("Comment") }</div>
-      <div data-bind="text: comment"></div>
-    </div>
-    <div style="clear: both; margin-top: 20px;">
-      ${ _("Show in ") } <a class="pointer"  data-bind="click: function() { huePubSub.publish('sql.context.popover.show.in.assist') }">${ _("Assist") }</a><br />
+    <div class="sql-context-flex">
+      <div class="sql-context-flex-fill">
+        <div style="margin: 15px;" data-bind="with: fetchedData">
+          <a class="pointer" data-bind="text: name, attr: { title: comment }, click: function() { huePubSub.publish('sql.context.popover.scroll.to.column', name); }"></a> (<span data-bind="text: type.indexOf('<') !== -1 ? type.substring(0, type.indexOf('<')) : type, attr: { title: type }"></span>)
+          <div style="margin-top: 10px; font-weight: bold;">${ _("Comment") }</div>
+          <div data-bind="text: comment"></div>
+        </div>
+      </div>
+      <div class="sql-context-flex-bottom-links">
+        <div class="sql-context-link-row">
+          <a class="inactive-action pointer" data-bind="click: function() { huePubSub.publish('sql.context.popover.show.in.assist') }"><i style="font-size: 11px;" title="Show in Assist..." class="fa fa-search"></i> ${ _("Assist") }</a>
+        </div>
+      </div>
     </div>
-    <!-- /ko -->
   </script>
 
   <script type="text/html" id="sql-context-table-and-column-sample">
-    <!-- ko with: fetchedData -->
-      <div class="sample-scroll" style="text-align: left; padding: 3px; overflow: hidden">
-        <!-- ko if: rows.length == 0 -->
-        <div class="alert">${ _('The selected table has no data.') }</div>
-        <!-- /ko -->
-        <!-- ko if: rows.length > 0 -->
-        <table id="samples-table" class="samples-table table table-striped table-condensed">
-          <thead>
-          <tr>
-            <th style="width: 10px">&nbsp;</th>
-            <!-- ko foreach: headers -->
-            <th data-bind="text: $data"></th>
-            <!-- /ko -->
-          </tr>
-          </thead>
-          <tbody>
-          </tbody>
-        </table>
-        <!-- /ko -->
+    <div class="sql-context-flex">
+      <div style="overflow: hidden;" class="sql-context-flex-fill" data-bind="with: fetchedData">
+        <div class="sample-scroll" style="text-align: left; padding: 3px; overflow: hidden">
+          <!-- ko if: rows.length == 0 -->
+          <div class="alert">${ _('The selected table has no data.') }</div>
+          <!-- /ko -->
+          <!-- ko if: rows.length > 0 -->
+          <table id="samples-table" class="samples-table table table-striped table-condensed">
+            <thead>
+            <tr>
+              <th style="width: 10px">&nbsp;</th>
+              <!-- ko foreach: headers -->
+              <th data-bind="text: $data"></th>
+              <!-- /ko -->
+            </tr>
+            </thead>
+            <tbody>
+            </tbody>
+          </table>
+          <!-- /ko -->
+        </div>
       </div>
-    <!-- /ko -->
+      <div class="sql-context-flex-bottom-links">
+        <div class="sql-context-link-row">
+          <a class="inactive-action pointer" data-bind="click: function() { huePubSub.publish('sql.context.popover.show.in.assist') }"><i style="font-size: 11px;" title="Show in Assist..." class="fa fa-search"></i> ${ _("Assist") }</a>
+          <a class="inactive-action pointer" data-bind="visible: ! $parent.isColumn, click: function() { huePubSub.publish('sql.context.popover.open.in.metastore') }"><i style="font-size: 11px;" title="Open in Metastore..." class="fa fa-external-link"></i> ${ _("Metastore") }</a>
+        </div>
+      </div>
+    </div>
   </script>
 
   <script type="text/html" id="sql-context-table-analysis">
-    <!-- ko with: fetchedData -->
-      <div class="content" data-bind="niceScroll">
+    <div class="sql-context-flex">
+      <div style="overflow: hidden;" class="sql-context-flex-fill" data-bind="with: fetchedData, niceScroll">
         <!-- ko if: stats.length > 0 -->
           <table class="table table-striped">
             <tbody data-bind="foreach: stats">
@@ -235,12 +296,18 @@ from metadata.conf import has_navigator
           </table>
         <!-- /ko -->
       </div>
-    <!-- /ko -->
+      <div class="sql-context-flex-bottom-links">
+        <div class="sql-context-link-row">
+          <a class="inactive-action pointer" data-bind="click: function() { huePubSub.publish('sql.context.popover.show.in.assist') }"><i style="font-size: 11px;" title="Show in Assist..." class="fa fa-search"></i> ${ _("Assist") }</a>
+          <a class="inactive-action pointer" data-bind="visible: ! $parent.isColumn, click: function() { huePubSub.publish('sql.context.popover.open.in.metastore') }"><i style="font-size: 11px;" title="Open in Metastore..." class="fa fa-external-link"></i> ${ _("Metastore") }</a>
+        </div>
+      </div>
+    </div>
   </script>
 
   <script type="text/html" id="sql-context-column-analysis">
-    <!-- ko with: fetchedData -->
-      <div class="content" data-bind="niceScroll">
+    <div class="sql-context-flex">
+      <div style="overflow: hidden;" class="sql-context-flex-fill" data-bind="with: fetchedData, niceScroll">
         <table class="table table-condensed">
           <tbody data-bind="foreach: stats">
             <tr>
@@ -250,7 +317,13 @@ from metadata.conf import has_navigator
           </tbody>
         </table>
       </div>
-    <!-- /ko -->
+      <div class="sql-context-flex-bottom-links">
+        <div class="sql-context-link-row">
+          <a class="inactive-action pointer" data-bind="click: function() { huePubSub.publish('sql.context.popover.show.in.assist') }"><i style="font-size: 11px;" title="Show in Assist..." class="fa fa-search"></i> ${ _("Assist") }</a>
+          <a class="inactive-action pointer" data-bind="visible: ! $parent.isColumn, click: function() { huePubSub.publish('sql.context.popover.open.in.metastore') }"><i style="font-size: 11px;" title="Open in Metastore..." class="fa fa-external-link"></i> ${ _("Metastore") }</a>
+        </div>
+      </div>
+    </div>
   </script>
 
   <script type="text/html" id="sql-context-function-details">
@@ -266,7 +339,6 @@ from metadata.conf import has_navigator
       <div class="sql-context-popover-title">
         <i class="pull-left fa muted" data-bind="css: iconClass" style="margin-top: 3px"></i> <span data-bind="text: title"></span>
         <a class="pull-right pointer inactive-action" data-bind="click: close"><i class="fa fa-fw fa-times"></i></a>
-        <a class="pull-right pointer inactive-action" data-bind="visible: isTable, click: function() { huePubSub.publish('sql.context.popover.open.in.metastore') }"><i style="margin-top: 3px;" title="${ _('Open in Metastore...') }" class="fa fa-fw fa-external-link"></i></a>
       </div>
       <div class="sql-context-popover-content">
         <!-- ko with: contents -->
@@ -276,7 +348,7 @@ from metadata.conf import has_navigator
           </li>
         </ul>
         <div class="tab-content" style="border: none; overflow: auto; height: 365px;" data-bind="foreach: tabs">
-          <div class="tab-pane" id="sampleTab" data-bind="attr: { id: id }, css: { 'active' : $parent.activeTab() === id }" style="overflow: hidden">
+          <div class="tab-pane" id="sampleTab" data-bind="attr: { id: id }, css: { 'active' : $parent.activeTab() === id }" style="height: 100%; overflow: hidden;">
             <!-- ko with: templateData -->
               <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
               <!-- ko if: ! loading() && hasErrors() -->
@@ -293,6 +365,41 @@ from metadata.conf import has_navigator
     </div>
   </script>
 
+  <script type="text/html" id="sql-columns-table-template">
+    <div class="sql-context-flex">
+      <div class="sql-context-flex-header">
+        <div style="margin: 10px 5px 0 10px;">
+          <span style="font-size: 15px; font-weight: 300;">${_('Columns')}</span>
+          <a href="#" data-bind="toggle: searchVisible"><i class="snippet-icon fa fa-search inactive-action" data-bind="css: { 'blue': searchVisible }"></i></a>
+          <input class="input-large sql-context-inline-search" type="text" data-bind="visible: searchVisible, hasFocus: searchFocus, clearable: searchInput, valueUpdate:'afterkeydown'" placeholder="${ _('Search...') }">
+        </div>
+      </div>
+      <div class="sql-context-flex-fill sql-columns-table" style="position:relative; height: 100%; overflow-y: auto;">
+        <table style="width: 100%" class="table table-striped table-condensed table-nowrap">
+          <thead>
+            <tr data-bind="visible: filteredColumns().length !== 0">
+              <th width="6%">&nbsp;</th>
+              <th width="60%">${_('Name')}</th>
+              <th width="34%">${_('Type')}</th>
+              <th width="6%">&nbsp;</th>
+            </tr>
+          </thead>
+          <tbody data-bind="foreachVisible: { data: filteredColumns, minHeight: 29, container: '.sql-columns-table', pubSubDispose: 'sql.context.popover.dispose' }">
+            <tr>
+              <td data-bind="text: $index()+$indexOffset()+1"></td>
+              <td style="overflow: hidden;">
+                <a href="javascript:void(0)" class="column-selector" data-bind="text: name, click: function() { huePubSub.publish('sql.context.popover.scroll.to.column', name); }" title="${ _("Show sample") }"></a>
+              </td>
+              <td><span data-bind="text: type, attr: { 'title': extendedType }, tooltip: { placement: 'bottom' }"></span></td>
+              <td><i class="snippet-icon fa fa-question-circle" data-bind="visible: comment, attr: { 'title': comment }, tooltip: { placement: 'bottom' }"></i></td>
+            </tr>
+          </tbody>
+        </table>
+        <div class="sql-context-empty-columns" data-bind="visible: filteredColumns().length === 0">${_('No columns found')}</div>
+      </div>
+    </div>
+  </script>
+
   <script type="text/javascript" charset="utf-8">
     require([
       'knockout',
@@ -350,6 +457,14 @@ from metadata.conf import has_navigator
           defaultDatabase: self.snippet.database(),
           silenceErrors: true,
           successCallback: function (data) {
+            if (typeof data.extended_columns !== 'undefined') {
+              data.extended_columns.forEach(function (column) {
+                column.extendedType = column.type.replace(/</g, '&lt;').replace(/>/g, '&lt;');
+                if (column.type.indexOf('<') !== -1) {
+                  column.type = column.type.substring(0, column.type.indexOf('<'));
+                }
+              });
+            }
             self.fetchedData(data);
             self.loading(false);
             if (typeof callback === 'function') {
@@ -379,7 +494,8 @@ from metadata.conf import has_navigator
           label: '${ _("Details") }',
           template: isColumn ? 'sql-context-column-details' : 'sql-context-table-details',
           templateData: self.details,
-          errorText: '${ _("There was a problem loading the details.") }'
+          errorText: '${ _("There was a problem loading the details.") }',
+          isColumn: isColumn
         });
 
         self.tabs.push({
@@ -387,7 +503,8 @@ from metadata.conf import has_navigator
           label: '${ _("Sample") }',
           template: 'sql-context-table-and-column-sample',
           templateData: self.sample,
-          errorText: '${ _("There was a problem loading the samples.") }'
+          errorText: '${ _("There was a problem loading the samples.") }',
+          isColumn: isColumn
         });
 
         self.details.fetch(function (data) {
@@ -397,7 +514,8 @@ from metadata.conf import has_navigator
               label: '${ _("Analysis") }',
               template: isColumn ? 'sql-context-column-analysis' : 'sql-context-table-analysis',
               templateData: self.analysis,
-              errorText: '${ _("There was a problem loading the analysis.") }'
+              errorText: '${ _("There was a problem loading the analysis.") }',
+              isColumn: isColumn
             });
           }
         });
@@ -513,6 +631,7 @@ from metadata.conf import has_navigator
           });
 
           $t.parents('.dataTables_wrapper').height(350);
+
           $t.jHueTableExtender({
             fixedHeader: true,
             fixedFirstColumn: true,
@@ -630,7 +749,39 @@ from metadata.conf import has_navigator
         window.setTimeout(function() {
           $(document).on('click', hideOnClickOutside);
         }, 0);
-      })
+      });
+
+
+      function SqlColumnsTable(params) {
+        var self = this;
+
+        var columns = params.columns;
+
+        self.searchInput = ko.observable('');
+        self.searchVisible = ko.observable(false);
+        self.searchFocus = ko.observable(false);
+
+        self.searchVisible.subscribe(function (newValue) {
+          if (newValue) {
+            self.searchFocus(true);
+          }
+        });
+
+        self.filteredColumns = ko.pureComputed(function () {
+          if (self.searchInput() === '') {
+            return columns;
+          }
+          var query = self.searchInput();
+          return columns.filter(function (column) {
+            return column.name.indexOf(query) !== -1 || column.comment.indexOf(query) !== -1;
+          })
+        });
+      }
+
+      ko.components.register('sql-columns-table', {
+        viewModel: SqlColumnsTable,
+        template: { element: 'sql-columns-table-template' }
+      });
     });
   </script>
 </%def>