Przeglądaj źródła

HUE-4485 [editor] Scroll to assist entries from search result

Johan Ahlen 9 lat temu
rodzic
commit
fc380a3e25

+ 39 - 1
desktop/core/src/desktop/static/desktop/js/assist/assistDbEntry.js

@@ -55,6 +55,8 @@
     self.editingSearch = ko.observable(false);
     self.editingSearch = ko.observable(false);
     self.sourceType = self.assistDbSource.sourceType;
     self.sourceType = self.assistDbSource.sourceType;
     self.invalidateOnRefresh =  self.assistDbSource.invalidateOnRefresh;
     self.invalidateOnRefresh =  self.assistDbSource.invalidateOnRefresh;
+    self.highlight = ko.observable(false);
+    self.highlightParent = ko.observable(false);
 
 
     self.expandable = typeof definition.type === "undefined" || /table|view|struct|array|map/i.test(definition.type);
     self.expandable = typeof definition.type === "undefined" || /table|view|struct|array|map/i.test(definition.type);
 
 
@@ -147,7 +149,40 @@
     self.assistDbSource.triggerRefresh();
     self.assistDbSource.triggerRefresh();
   };
   };
 
 
-  AssistDbEntry.prototype.loadEntries = function() {
+  AssistDbEntry.prototype.highlightInside = function (path) {
+    var self = this;
+
+    var searchEntry = function () {
+      var foundEntry;
+      $.each(self.entries(), function (idx, entry) {
+        if (entry.definition.name === path[0]) {
+          foundEntry = entry;
+          entry.open(true);
+          return false;
+        }
+      });
+      if (foundEntry) {
+        if (path.length > 1) {
+          foundEntry.highlightParent(true);
+          huePubSub.publish('assist.db.scrollToHighlight');
+          window.setTimeout(function () {
+            foundEntry.highlightInside(path.slice(1));
+          }, 0);
+        } else {
+          foundEntry.highlight(true);
+          huePubSub.publish('assist.db.scrollToHighlight');
+        }
+      }
+    };
+
+    if (self.entries().length == 0) {
+      self.loadEntries(searchEntry);
+    } else {
+      searchEntry();
+    }
+  };
+
+  AssistDbEntry.prototype.loadEntries = function(callback) {
     var self = this;
     var self = this;
     if (!self.expandable || self.loading()) {
     if (!self.expandable || self.loading()) {
       return;
       return;
@@ -254,6 +289,9 @@
       });
       });
 
 
       self.entries(newEntries);
       self.entries(newEntries);
+      if (typeof callback === 'function') {
+        callback();
+      }
     };
     };
 
 
     var errorCallback = function () {
     var errorCallback = function () {

+ 56 - 12
desktop/core/src/desktop/static/desktop/js/assist/assistDbSource.js

@@ -45,6 +45,8 @@
     self.isSearchVisible = ko.observable(false);
     self.isSearchVisible = ko.observable(false);
     self.editingSearch = ko.observable(false);
     self.editingSearch = ko.observable(false);
 
 
+    self.highlight = ko.observable(false);
+
     self.invalidateOnRefresh = ko.observable('cache');
     self.invalidateOnRefresh = ko.observable('cache');
 
 
     self.filter = {
     self.filter = {
@@ -92,21 +94,18 @@
     self.loadingSamples = ko.observable(true);
     self.loadingSamples = ko.observable(true);
     self.samples = ko.observable();
     self.samples = ko.observable();
 
 
-    self.selectedDatabase.subscribe(function (newValue) {
-      if (newValue) {
-        if (self.selectedDatabase() && self.selectedDatabase().definition.name === newValue) {
-          return;
-        }
-        if (!newValue.hasEntries() && !newValue.loading()) {
-          newValue.loadEntries()
+    self.selectedDatabaseChanged = function () {
+      if (self.selectedDatabase()) {
+        if (!self.selectedDatabase().hasEntries() && !self.selectedDatabase().loading()) {
+          self.selectedDatabase().loadEntries()
         }
         }
-        self.apiHelper.setInTotalStorage('assist_' + self.sourceType, 'lastSelectedDb', newValue.definition.name)
+        self.apiHelper.setInTotalStorage('assist_' + self.sourceType, 'lastSelectedDb', self.selectedDatabase().definition.name)
         huePubSub.publish("assist.database.set", {
         huePubSub.publish("assist.database.set", {
           source: self.sourceType,
           source: self.sourceType,
-          name: newValue.definition.name
+          name: self.selectedDatabase().definition.name
         })
         })
       }
       }
-    });
+    };
 
 
     self.loaded = ko.observable(false);
     self.loaded = ko.observable(false);
     self.loading = ko.observable(false);
     self.loading = ko.observable(false);
@@ -128,6 +127,7 @@
         dbIndex[name] = database;
         dbIndex[name] = database;
         if (name === lastSelectedDb) {
         if (name === lastSelectedDb) {
           self.selectedDatabase(database);
           self.selectedDatabase(database);
+          self.selectedDatabaseChanged();
         }
         }
         return database;
         return database;
       }));
       }));
@@ -142,17 +142,20 @@
       }
       }
       if (databaseName && dbIndex[databaseName]) {
       if (databaseName && dbIndex[databaseName]) {
         self.selectedDatabase(dbIndex[databaseName]);
         self.selectedDatabase(dbIndex[databaseName]);
+        self.selectedDatabaseChanged();
         return;
         return;
       }
       }
       var lastSelectedDb = self.apiHelper.getFromTotalStorage('assist_' + self.sourceType, 'lastSelectedDb', 'default');
       var lastSelectedDb = self.apiHelper.getFromTotalStorage('assist_' + self.sourceType, 'lastSelectedDb', 'default');
       if (lastSelectedDb && dbIndex[lastSelectedDb]) {
       if (lastSelectedDb && dbIndex[lastSelectedDb]) {
         self.selectedDatabase(dbIndex[lastSelectedDb]);
         self.selectedDatabase(dbIndex[lastSelectedDb]);
+        self.selectedDatabaseChanged();
       } else if (self.databases().length > 0) {
       } else if (self.databases().length > 0) {
         self.selectedDatabase(self.databases()[0]);
         self.selectedDatabase(self.databases()[0]);
+        self.selectedDatabaseChanged();
       }
       }
     };
     };
 
 
-    self.initDatabases = function () {
+    self.initDatabases = function (callback) {
       if (self.loading()) {
       if (self.loading()) {
         return;
         return;
       }
       }
@@ -164,7 +167,10 @@
         sourceType: self.sourceType,
         sourceType: self.sourceType,
         successCallback: function(data) {
         successCallback: function(data) {
           self.hasErrors(false);
           self.hasErrors(false);
-          updateDatabases(data, lastSelectedDb)
+          updateDatabases(data, lastSelectedDb);
+          if (typeof callback === 'function') {
+            callback();
+          }
         },
         },
         errorCallback: function() {
         errorCallback: function() {
           self.hasErrors(true);
           self.hasErrors(true);
@@ -199,6 +205,44 @@
     });
     });
   }
   }
 
 
+  AssistDbSource.prototype.highlightInside = function (path) {
+    var self = this;
+
+    var foundDb;
+    var index;
+    var findDatabase = function () {
+      $.each(self.databases(), function (idx, db) {
+        if (db.databaseName === path[0]) {
+          foundDb = db;
+          index = idx;
+        }
+      });
+      if (foundDb && path.length > 1) {
+        var whenLoaded = function () {
+          self.selectedDatabase(foundDb);
+          foundDb.highlightInside(path.slice(1), []);
+          foundDb.open(true);
+        };
+        if (foundDb.hasEntries()) {
+          whenLoaded();
+        } else {
+          foundDb.loadEntries(whenLoaded);
+        }
+      } else if (foundDb) {
+        self.selectedDatabase(null);
+        foundDb.highlight(true);
+        window.setTimeout(function() {
+          huePubSub.publish('assist.db.scrollToHighlight');
+        }, 0)
+      }
+    };
+    if (!self.loaded()) {
+      self.initDatabases(findDatabase);
+    } else {
+      findDatabase();
+    }
+  };
+
   AssistDbSource.prototype.toggleSearch = function () {
   AssistDbSource.prototype.toggleSearch = function () {
     var self = this;
     var self = this;
     self.isSearchVisible(!self.isSearchVisible());
     self.isSearchVisible(!self.isSearchVisible());

+ 43 - 6
desktop/core/src/desktop/static/desktop/js/ko.hue-bindings.js

@@ -3435,7 +3435,7 @@
             return false;
             return false;
           }
           }
         });
         });
-      }
+      };
 
 
       if ($parentFVOwnerElement.data('disposalFunction')) {
       if ($parentFVOwnerElement.data('disposalFunction')) {
         $parentFVOwnerElement.data('disposalFunction')();
         $parentFVOwnerElement.data('disposalFunction')();
@@ -3477,6 +3477,40 @@
         endIndex = allEntries.length - 1;
         endIndex = allEntries.length - 1;
       }
       }
 
 
+      var huePubSubs = [];
+
+      var scrollToIndex = function (idx, offset) {
+        var lastKnownHeights = $parentFVOwnerElement.data('lastKnownHeights');
+        if (! lastKnownHeights) {
+          return;
+        }
+        var top = 0;
+        for (var i = 0; i < idx; i++) {
+          top += lastKnownHeights[i];
+        }
+        $container.scrollTop(top + offset);
+      };
+
+      huePubSubs.push(huePubSub.subscribe('assist.db.scrollToHighlight', function () {
+        var foundIndex;
+        $.each(allEntries, function (idx, entry) {
+          if ((typeof entry.highlight !== 'undefined' && entry.highlight() || (typeof entry.highlightParent !== 'undefined' && entry.highlightParent()))) {
+            foundIndex = idx;
+            window.setTimeout(function () {
+              entry.highlight(false);
+              entry.highlightParent(false);
+            }, 500); // 500 for animation effect
+            return false;
+          }
+        });
+        if (foundIndex) {
+          var offset = depth > 0 ? $container.scrollTop() : 0;
+          window.setTimeout(function () {
+            scrollToIndex(foundIndex, offset);
+          }, 0);
+        }
+      }));
+
       var childBindingContext = bindingContext.createChildContext(
       var childBindingContext = bindingContext.createChildContext(
           bindingContext.$rawData,
           bindingContext.$rawData,
           null,
           null,
@@ -3573,14 +3607,14 @@
 
 
       var updateHeightsInterval = window.setInterval(updateLastKnownHeights, 600);
       var updateHeightsInterval = window.setInterval(updateLastKnownHeights, 600);
 
 
-      huePubSub.subscribe('foreach.visible.update.heights', function (targetId) {
+      huePubSubs.push(huePubSub.subscribe('foreach.visible.update.heights', function (targetId) {
         if (targetId === id) {
         if (targetId === id) {
           clearInterval(updateHeightsInterval);
           clearInterval(updateHeightsInterval);
           updateLastKnownHeights();
           updateLastKnownHeights();
           huePubSub.publish('foreach.visible.update.heights', parentId);
           huePubSub.publish('foreach.visible.update.heights', parentId);
           updateHeightsInterval = window.setInterval(updateLastKnownHeights, 600);
           updateHeightsInterval = window.setInterval(updateLastKnownHeights, 600);
         }
         }
-      });
+      }));
 
 
       updateLastKnownHeights();
       updateLastKnownHeights();
 
 
@@ -3689,7 +3723,7 @@
         if (startIndex > incrementLimit && Math.abs(lastScrollTop - $container.scrollTop()) < (incrementLimit * options.minHeight)) {
         if (startIndex > incrementLimit && Math.abs(lastScrollTop - $container.scrollTop()) < (incrementLimit * options.minHeight)) {
           return;
           return;
         }
         }
-        lastScrollTop = $container.scrollTop();;
+        lastScrollTop = $container.scrollTop();
         setStartAndEndFromScrollTop();
         setStartAndEndFromScrollTop();
         clearTimeout(renderThrottle);
         clearTimeout(renderThrottle);
         if (Math.abs($parentFVOwnerElement.data('startIndex') - startIndex) > incrementLimit ||
         if (Math.abs($parentFVOwnerElement.data('startIndex') - startIndex) > incrementLimit ||
@@ -3698,13 +3732,13 @@
         }
         }
       };
       };
 
 
-      huePubSub.subscribe('foreach.visible.update', function (callerId) {
+      huePubSubs.push(huePubSub.subscribe('foreach.visible.update', function (callerId) {
         if (callerId === id && endIndex > 0) {
         if (callerId === id && endIndex > 0) {
           setStartAndEndFromScrollTop();
           setStartAndEndFromScrollTop();
           clearTimeout(renderThrottle);
           clearTimeout(renderThrottle);
           renderThrottle = setTimeout(render, 0);
           renderThrottle = setTimeout(render, 0);
         }
         }
-      });
+      }));
 
 
       $container.bind('scroll', onScroll);
       $container.bind('scroll', onScroll);
 
 
@@ -3712,6 +3746,9 @@
         setTimeout(function () {
         setTimeout(function () {
           huePubSub.publish('foreach.visible.update.heights', parentId);
           huePubSub.publish('foreach.visible.update.heights', parentId);
         }, 0);
         }, 0);
+        huePubSubs.forEach(function (pubSub) {
+          pubSub.remove();
+        });
         $container.unbind('scroll', onScroll);
         $container.unbind('scroll', onScroll);
         clearInterval(updateCountInterval);
         clearInterval(updateCountInterval);
         clearInterval(updateHeightsInterval);
         clearInterval(updateHeightsInterval);

+ 4 - 4
desktop/core/src/desktop/static/desktop/spec/sqlFunctionsSpec.js

@@ -19,7 +19,7 @@ define([
   describe('sqlFunctions.js', function () {
   describe('sqlFunctions.js', function () {
     it('should suggest only BOOLEAN functions when return type is set to BOOLEAN for Hive', function () {
     it('should suggest only BOOLEAN functions when return type is set to BOOLEAN for Hive', function () {
       var completions = [];
       var completions = [];
-      sqlFunctions.suggestFunctions('hive', ['BOOLEAN'], undefined, completions);
+      sqlFunctions.suggestFunctions('hive', ['BOOLEAN'], undefined, undefined, completions);
 
 
       expect(completions.length).not.toEqual(0);
       expect(completions.length).not.toEqual(0);
 
 
@@ -32,7 +32,7 @@ define([
 
 
     it('should suggest only STRING functions when return type is set to STRING for Hive', function () {
     it('should suggest only STRING functions when return type is set to STRING for Hive', function () {
       var completions = [];
       var completions = [];
-      sqlFunctions.suggestFunctions('hive', ['STRING'], undefined, completions);
+      sqlFunctions.suggestFunctions('hive', ['STRING'], undefined, undefined, completions);
 
 
       expect(completions.length).not.toEqual(0);
       expect(completions.length).not.toEqual(0);
 
 
@@ -45,7 +45,7 @@ define([
 
 
     it('should suggest only NUMBER functions when return type is set to NUMBER for Hive', function () {
     it('should suggest only NUMBER functions when return type is set to NUMBER for Hive', function () {
       var completions = [];
       var completions = [];
-      sqlFunctions.suggestFunctions('hive', ['NUMBER'], undefined, completions);
+      sqlFunctions.suggestFunctions('hive', ['NUMBER'], undefined, undefined, completions);
 
 
       expect(completions.length).not.toEqual(0);
       expect(completions.length).not.toEqual(0);
 
 
@@ -64,7 +64,7 @@ define([
 
 
     it('should suggest only NUMBER functions when return type is set to NUMBER for Impala', function () {
     it('should suggest only NUMBER functions when return type is set to NUMBER for Impala', function () {
       var completions = [];
       var completions = [];
-      sqlFunctions.suggestFunctions('impala', ['NUMBER'], undefined, completions);
+      sqlFunctions.suggestFunctions('impala', ['NUMBER'], undefined, undefined, completions);
 
 
       expect(completions.length).not.toEqual(0);
       expect(completions.length).not.toEqual(0);
 
 

+ 143 - 91
desktop/core/src/desktop/templates/assist.mako

@@ -262,6 +262,18 @@ from metadata.conf import has_navigator
       display: table-row;
       display: table-row;
     }
     }
 
 
+    .highlightable {
+      -webkit-transition: all .5s linear;
+      -moz-transition: all .5s linear;
+      -o-transition: all .5s linear;
+      transition: all .5s linear;
+    }
+
+    .highlight {
+      color: #338BB8 !important;
+      padding-left: 8px;
+    }
+
     .assist-details-header {
     .assist-details-header {
       display: table-cell;
       display: table-cell;
       width: 95px;
       width: 95px;
@@ -426,7 +438,7 @@ from metadata.conf import has_navigator
         <span data-bind="visible: navigationSettings.showStats, component: { name: 'table-stats', params: { statsVisible: statsVisible, sourceType: sourceType, snippet: assistDbSource.snippet, databaseName: databaseName, tableName: tableName, columnName: columnName, fieldType: definition.type, apiHelper: assistDbSource.apiHelper }}"></span>
         <span data-bind="visible: navigationSettings.showStats, component: { name: 'table-stats', params: { statsVisible: statsVisible, sourceType: sourceType, snippet: assistDbSource.snippet, databaseName: databaseName, tableName: tableName, columnName: columnName, fieldType: definition.type, apiHelper: assistDbSource.apiHelper }}"></span>
         <a class="inactive-action" href="javascript:void(0)" data-bind="visible: navigationSettings.openItem, click: openItem"><i class="fa fa-long-arrow-right" title="${_('Open')}"></i></a>
         <a class="inactive-action" href="javascript:void(0)" data-bind="visible: navigationSettings.openItem, click: openItem"><i class="fa fa-long-arrow-right" title="${_('Open')}"></i></a>
       </div>
       </div>
-      <a class="assist-entry assist-table-link" href="javascript:void(0)" data-bind="multiClick: { click: toggleOpen, dblClick: dblClick }, attr: {'title': definition.title }, draggableText: { text: editorText,  meta: {'table': tableName, 'database': databaseName} }"><i class="fa fa-fw fa-table muted valign-middle"></i><span data-bind="text: definition.displayName"></span></a>
+      <a class="assist-entry assist-table-link" href="javascript:void(0)" data-bind="multiClick: { click: toggleOpen, dblClick: dblClick }, attr: {'title': definition.title }, draggableText: { text: editorText,  meta: {'table': tableName, 'database': databaseName} }"><i class="fa fa-fw fa-table muted valign-middle"></i><span data-bind="text: definition.displayName, css: { 'highlight': highlight }"></span></a>
       <div class="center" data-bind="visible: loading" style="display:none;"><i class="fa fa-spinner fa-spin assist-spinner"></i></div>
       <div class="center" data-bind="visible: loading" style="display:none;"><i class="fa fa-spinner fa-spin assist-spinner"></i></div>
       <!-- ko template: { if: open, name: 'assist-db-entries'  } --><!-- /ko -->
       <!-- ko template: { if: open, name: 'assist-db-entries'  } --><!-- /ko -->
     </li>
     </li>
@@ -448,7 +460,7 @@ from metadata.conf import has_navigator
           <!-- ko if: definition.isView -->
           <!-- ko if: definition.isView -->
             <i class="fa fa-fw fa-eye muted valign-middle"></i>
             <i class="fa fa-fw fa-eye muted valign-middle"></i>
           <!-- /ko -->
           <!-- /ko -->
-          <span data-bind="css: {'query-builder-menu': definition.isColumn}, attr: {'column': columnName, 'table': tableName, 'database': databaseName}, text: definition.displayName, draggableText: { text: editorText, meta: {'column': columnName, 'table': tableName, 'database': databaseName} }"></span>
+          <span class="highlightable" data-bind="css: {'query-builder-menu': definition.isColumn, 'highlight': highlight}, attr: {'column': columnName, 'table': tableName, 'database': databaseName}, text: definition.displayName, draggableText: { text: editorText, meta: {'column': columnName, 'table': tableName, 'database': databaseName} }"></span>
         </a>
         </a>
         <!-- /ko -->
         <!-- /ko -->
         <!-- ko ifnot: expandable -->
         <!-- ko ifnot: expandable -->
@@ -456,7 +468,7 @@ from metadata.conf import has_navigator
           <!-- ko if: definition.isView -->
           <!-- ko if: definition.isView -->
           <i class="fa fa-fw fa-eye muted valign-middle"></i>
           <i class="fa fa-fw fa-eye muted valign-middle"></i>
           <!-- /ko -->
           <!-- /ko -->
-          <span data-bind="css: {'query-builder-menu': definition.isColumn}, attr: {'column': columnName, 'table': tableName, 'database': databaseName}, text: definition.displayName, draggableText: { text: editorText, meta: {'column': columnName, 'table': tableName, 'database': databaseName} }"></span>
+          <span class="highlightable" data-bind="css: {'query-builder-menu': definition.isColumn, 'highlight': highlight}, attr: {'column': columnName, 'table': tableName, 'database': databaseName}, text: definition.displayName, draggableText: { text: editorText, meta: {'column': columnName, 'table': tableName, 'database': databaseName} }"></span>
         </span>
         </span>
         <!-- /ko -->
         <!-- /ko -->
         <div class="center" data-bind="visible: loading" style="display:none;"><i class="fa fa-spinner fa-spin assist-spinner"></i></div>
         <div class="center" data-bind="visible: loading" style="display:none;"><i class="fa fa-spinner fa-spin assist-spinner"></i></div>
@@ -499,15 +511,18 @@ from metadata.conf import has_navigator
   <script type="text/html" id="assist-db-inner-panel">
   <script type="text/html" id="assist-db-inner-panel">
     <div class="assist-inner-panel">
     <div class="assist-inner-panel">
       <div class="assist-flex-panel">
       <div class="assist-flex-panel">
-      <!-- ko template: { if: breadcrumb() !== null, name: 'assist-db-breadcrumb' } --><!-- /ko -->
-       <!-- ko template: { ifnot: selectedSource, name: 'assist-sources-template' } --><!-- /ko -->
-       <!-- ko with: selectedSource -->
-       <!-- ko template: { ifnot: selectedDatabase, name: 'assist-databases-template' }--><!-- /ko -->
-       <!-- ko with: selectedDatabase -->
-       <!-- ko template: { name: "assist-tables-template" } --><!-- /ko -->
-       <!-- /ko -->
-       <!-- /ko -->
+        <!-- ko template: { if: breadcrumb() !== null, name: 'assist-db-breadcrumb' } --><!-- /ko -->
+        <!-- ko template: { ifnot: selectedSource, name: 'assist-sources-template' } --><!-- /ko -->
+        <!-- ko with: selectedSource -->
+        <!-- ko template: { ifnot: selectedDatabase, name: 'assist-databases-template' }--><!-- /ko -->
+        <!-- ko with: selectedDatabase -->
+        <!-- ko template: { name: "assist-tables-template" } --><!-- /ko -->
+        <!-- /ko -->
+        <!-- /ko -->
       </div>
       </div>
+      <!-- ko with: $parents[1] -->
+      <!-- ko template: { if: (searchInput() !== '' || searchHasFocus()) && navigatorEnabled(), name: 'nav-search-result' } --><!-- /ko -->
+      <!-- /ko -->
     </div>
     </div>
   </script>
   </script>
 
 
@@ -744,7 +759,7 @@ from metadata.conf import has_navigator
       <ul class="assist-tables" data-bind="foreachVisible: {data: filteredEntries, minHeight: 20, container: '.assist-db-scrollable' }">
       <ul class="assist-tables" data-bind="foreachVisible: {data: filteredEntries, minHeight: 20, container: '.assist-db-scrollable' }">
         <li class="assist-table" data-bind="visibleOnHover: { selector: '.database-actions' }">
         <li class="assist-table" data-bind="visibleOnHover: { selector: '.database-actions' }">
           <!-- ko template: { name: 'assist-entry-actions' } --><!-- /ko -->
           <!-- ko template: { name: 'assist-entry-actions' } --><!-- /ko -->
-          <a class="assist-table-link" href="javascript: void(0);" data-bind="click: function () { $parent.selectedDatabase($data) }"><i class="fa fa-fw fa-database muted valign-middle"></i> <span data-bind="text: definition.name"></span></a>
+          <a class="assist-table-link" href="javascript: void(0);" data-bind="click: function () { $parent.selectedDatabase($data) }"><i class="fa fa-fw fa-database muted valign-middle"></i> <span class="highlightable" data-bind="text: definition.name, css: { 'highlight': highlight() }"></span></a>
         </li>
         </li>
       </ul>
       </ul>
     </div>
     </div>
@@ -809,7 +824,6 @@ from metadata.conf import has_navigator
   </script>
   </script>
 
 
   <script type="text/html" id="assist-panel-template">
   <script type="text/html" id="assist-panel-template">
-    <!-- ko if: (searchInput() === '' && !searchHasFocus()) || ! navigatorEnabled() -->
     <div style="position:relative; height: 100%; overflow: hidden" data-bind="assistVerticalResizer: { panels: visiblePanels, apiHelper: apiHelper, noFixedHeights: onlySql }">
     <div style="position:relative; height: 100%; overflow: hidden" data-bind="assistVerticalResizer: { panels: visiblePanels, apiHelper: apiHelper, noFixedHeights: onlySql }">
       <!-- ko template: { if: navigatorEnabled, name: 'assist-panel-navigator-search' }--><!-- /ko -->
       <!-- ko template: { if: navigatorEnabled, name: 'assist-panel-navigator-search' }--><!-- /ko -->
       <!-- ko template: { if: availablePanels.length > 1, name: 'assist-panel-switches' }--><!-- /ko -->
       <!-- ko template: { if: availablePanels.length > 1, name: 'assist-panel-switches' }--><!-- /ko -->
@@ -819,83 +833,82 @@ from metadata.conf import has_navigator
       <!-- ko template: { name: templateName, data: panelData } --><!-- /ko -->
       <!-- ko template: { name: templateName, data: panelData } --><!-- /ko -->
       <!-- /ko -->
       <!-- /ko -->
     </div>
     </div>
-    <!-- /ko -->
-    <!-- ko if: (searchInput() !== '' || searchHasFocus()) && navigatorEnabled()-->
-    <div style="position:relative; height: 100%; overflow: hidden">
-      <div class="assist-flex-panel">
-        <div style="flex: 1"></div>
-        <!-- ko template: { if: navigatorEnabled, name: 'assist-panel-navigator-search' }--><!-- /ko -->
-        <div class="assist-flex-fill" style="overflow-x: none; overflow-y: scroll; outline: none;">
-          <!-- ko hueSpinner: { spin: searching, center: true, size: 'large' } --><!-- /ko -->
-          <!-- ko if: !searching() -->
-          <!-- ko foreach: searchResult -->
-          <div class="result-entry">
-            <div class="icon-col">
-              <!-- ko if: type === 'FILE' -->
-              <i class="fa fa-fw fa-file-o valign-middle"></i>
-              <!-- /ko -->
-              <!-- ko if: type === 'DIRECTORY' -->
-              <i class="fa fa-fw fa-folder-o valign-middle"></i>
-              <!-- /ko -->
-              <!-- ko if: type === 'TABLE' -->
-              <i class="fa fa-fw fa-table valign-middle"></i>
-              <!-- /ko -->              
-              <!-- ko if: type === 'DATABASE' -->
-              <i class="fa fa-fw fa-database valign-middle"></i>
-              <!-- /ko -->
-              <!-- ko if: type === 'SOURCE' -->
-              <i class="fa fa-fw fa-server valign-middle"></i>
-              <!-- /ko -->
-              <!-- ko if: type === 'SUB_OPERATION' -->
-              <i class="fa fa-fw fa-code-fork valign-middle"></i>
-              <!-- /ko -->
-              <!-- ko if: type === 'FIELD' -->
-              <i class="fa fa-fw fa-columns valign-middle"></i>
-              <!-- /ko -->
-              <!-- ko if: type === 'OPERATION_EXECUTION' -->
-              <i class="fa fa-fw fa-cog valign-middle"></i>
-              <!-- /ko -->
-              <!-- ko if: type === 'OPERATION' -->
-              <i class="fa fa-fw fa-cogs valign-middle"></i>
-              <!-- /ko -->
-              <!-- ko if: type === 'PARTITION' -->
-              <i class="fa fa-fw fa-th valign-middle"></i>
-              <!-- /ko -->
-            </div>
-            <div class="doc-col">
-              <a data-bind="attr: { 'href': link }, text: originalName" target="_blank" ></a>
-              <!-- ko if: type === 'DATABASE' -->
-              <div class="doc-desc" data-bind="text: originalDescription"></div>
-              <!-- /ko -->
-              <!-- ko if: type === 'TABLE' -->
-              <div class="doc-desc" data-bind="text: originalDescription"></div>
-              <div class="doc-desc" data-bind="text: parentPath"></div>
-              <!-- /ko -->
-              <!-- ko if: type === 'FIELD' -->
-              <div class="doc-desc" data-bind="text: originalDescription"></div>
-              <div class="doc-desc" data-bind="text: parentPath"></div>
-              <!-- /ko -->
-              <!-- ko if: type === 'PARTITION' -->
-              <div class="doc-desc" data-bind="text: originalDescription"></div>
-              <div class="doc-desc" data-bind="text: parentPath"></div>
-              <!-- /ko -->
-              <!-- ko if: type === 'SUB_OPERATION' -->
-              <div class="doc-desc" data-bind="text: metaClassName"></div>
-              <!-- /ko -->
-              <!-- ko if: type === 'SOURCE' -->
-              <div class="doc-desc" data-bind="text: 'Cluster: ' + clusterName"></div>
-              <!-- /ko -->
-              <!-- ko if: type === 'FILE' || type === 'DIRECTORY' -->
-              <div class="doc-desc" data-bind="text: parentPath"></div>
-              <!-- /ko -->
-            </div>
-          </div>
+  </script>
+
+  <script type="text/html" id="nav-search-result">
+    <div style="position:absolute; left:0; right: 0; top: 0; bottom: 0; overflow: hidden; background-color: #FFF;">
+      <!-- ko hueSpinner: { spin: searching, center: true, size: 'large' } --><!-- /ko -->
+      <!-- ko if: !searching() -->
+      <!-- ko foreach: searchResult -->
+      <div class="result-entry">
+        <div class="icon-col">
+          <!-- ko if: type === 'FILE' -->
+          <i class="fa fa-fw fa-file-o valign-middle"></i>
+          <!-- /ko -->
+          <!-- ko if: type === 'DIRECTORY' -->
+          <i class="fa fa-fw fa-folder-o valign-middle"></i>
+          <!-- /ko -->
+          <!-- ko if: type === 'TABLE' -->
+          <i class="fa fa-fw fa-table valign-middle"></i>
+          <!-- /ko -->
+          <!-- ko if: type === 'DATABASE' -->
+          <i class="fa fa-fw fa-database valign-middle"></i>
+          <!-- /ko -->
+          <!-- ko if: type === 'SOURCE' -->
+          <i class="fa fa-fw fa-server valign-middle"></i>
+          <!-- /ko -->
+          <!-- ko if: type === 'SUB_OPERATION' -->
+          <i class="fa fa-fw fa-code-fork valign-middle"></i>
+          <!-- /ko -->
+          <!-- ko if: type === 'FIELD' -->
+          <i class="fa fa-fw fa-columns valign-middle"></i>
+          <!-- /ko -->
+          <!-- ko if: type === 'OPERATION_EXECUTION' -->
+          <i class="fa fa-fw fa-cog valign-middle"></i>
+          <!-- /ko -->
+          <!-- ko if: type === 'OPERATION' -->
+          <i class="fa fa-fw fa-cogs valign-middle"></i>
+          <!-- /ko -->
+          <!-- ko if: type === 'PARTITION' -->
+          <i class="fa fa-fw fa-th valign-middle"></i>
+          <!-- /ko -->
+        </div>
+        <div class="doc-col">
+          <!-- ko if: typeof click !== 'undefined' -->
+          <a class="pointer" data-bind="click: click, text: originalName" target="_blank" ></a>
           <!-- /ko -->
           <!-- /ko -->
+          <!-- ko if: typeof click === 'undefined' -->
+          <a class="pointer" data-bind="attr: { 'href': link }, text: originalName" target="_blank" ></a>
+          <!-- /ko -->
+          <!-- ko if: type === 'DATABASE' -->
+          <div class="doc-desc" data-bind="text: originalDescription"></div>
+          <!-- /ko -->
+          <!-- ko if: type === 'TABLE' -->
+          <div class="doc-desc" data-bind="text: originalDescription"></div>
+          <div class="doc-desc" data-bind="text: parentPath"></div>
+          <!-- /ko -->
+          <!-- ko if: type === 'FIELD' -->
+          <div class="doc-desc" data-bind="text: originalDescription"></div>
+          <div class="doc-desc" data-bind="text: parentPath"></div>
+          <!-- /ko -->
+          <!-- ko if: type === 'PARTITION' -->
+          <div class="doc-desc" data-bind="text: originalDescription"></div>
+          <div class="doc-desc" data-bind="text: parentPath"></div>
+          <!-- /ko -->
+          <!-- ko if: type === 'SUB_OPERATION' -->
+          <div class="doc-desc" data-bind="text: metaClassName"></div>
+          <!-- /ko -->
+          <!-- ko if: type === 'SOURCE' -->
+          <div class="doc-desc" data-bind="text: 'Cluster: ' + clusterName"></div>
+          <!-- /ko -->
+          <!-- ko if: type === 'FILE' || type === 'DIRECTORY' -->
+          <div class="doc-desc" data-bind="text: parentPath"></div>
           <!-- /ko -->
           <!-- /ko -->
         </div>
         </div>
       </div>
       </div>
+      <!-- /ko -->
+      <!-- /ko -->
     </div>
     </div>
-    <!-- /ko -->
   </script>
   </script>
 
 
   <script type="text/javascript" charset="utf-8">
   <script type="text/javascript" charset="utf-8">
@@ -988,7 +1001,6 @@ from metadata.conf import has_navigator
        * @param {Object} options.navigationSettings - enable/disable the links
        * @param {Object} options.navigationSettings - enable/disable the links
        * @param {boolean} options.navigationSettings.openItem
        * @param {boolean} options.navigationSettings.openItem
        * @param {boolean} options.navigationSettings.showStats
        * @param {boolean} options.navigationSettings.showStats
-
        * @constructor
        * @constructor
        **/
        **/
       function AssistDbPanel (options) {
       function AssistDbPanel (options) {
@@ -1009,6 +1021,27 @@ from metadata.conf import has_navigator
           self.sources.push(sourceIndex[sourceType.type]);
           self.sources.push(sourceIndex[sourceType.type]);
         });
         });
 
 
+        huePubSub.subscribe('assist.db.highlight', function (location) {
+          var foundSource;
+          $.each(self.sources(), function (idx, source) {
+            if (source.sourceType === location.sourceType) {
+              foundSource = source;
+              return false;
+            }
+          });
+          if (foundSource) {
+            if (foundSource.hasEntries()) {
+              self.selectedSource(foundSource);
+              foundSource.highlightInside(location.path);
+            } else {
+              foundSource.initDatabases(function () {
+                self.selectedSource(foundSource);
+                foundSource.highlightInside(location.path);
+              });
+            }
+          }
+        });
+
         self.selectedSource = ko.observable(null);
         self.selectedSource = ko.observable(null);
 
 
         var setDatabaseWhenLoaded = function (databaseName) {
         var setDatabaseWhenLoaded = function (databaseName) {
@@ -1052,7 +1085,7 @@ from metadata.conf import has_navigator
         huePubSub.publish("assist.db.panel.ready");
         huePubSub.publish("assist.db.panel.ready");
 
 
         self.selectedSource.subscribe(function (newSource) {
         self.selectedSource.subscribe(function (newSource) {
-          if (newSource) {
+          if (newSource && newSource.databases().length === 0) {
             newSource.initDatabases();
             newSource.initDatabases();
             self.apiHelper.setInTotalStorage('assist', 'lastSelectedSource', newSource.sourceType);
             self.apiHelper.setInTotalStorage('assist', 'lastSelectedSource', newSource.sourceType);
           } else {
           } else {
@@ -1276,16 +1309,34 @@ from metadata.conf import has_navigator
           }
           }
           lastQuery = self.searchInput();
           lastQuery = self.searchInput();
           self.searching(true);
           self.searching(true);
+
+          var showInAssist = function (entry) {
+            self.searchInput('');
+            self.searchHasFocus(false);
+            var path = entry.parentPath.split('/').concat([entry.originalName]).splice(1);
+            window.setTimeout(function () {
+              huePubSub.publish('assist.db.highlight', { sourceType: entry.sourceType.toLowerCase(), path: path });
+            }, 200); // For animation effect
+          };
+
           $.post('/metadata/api/navigator/search_entities', {
           $.post('/metadata/api/navigator/search_entities', {
-              query_s: self.searchInput(),
-              sources: ko.mapping.toJSON($.map(self.availablePanels[0].panelData.sources(), function(source) { return source.sourceType; })) // type empty for some reason, using name
+            query_s: self.searchInput(),
+            sources: ko.mapping.toJSON($.map(self.availablePanels[0].panelData.sources(), function(source) { return source.sourceType; })) // type empty for some reason, using name
           })
           })
           .done(function (data) {
           .done(function (data) {
             data.entities.forEach(function (entity) {
             data.entities.forEach(function (entity) {
               if (entity.type === 'DATABASE') {
               if (entity.type === 'DATABASE') {
-                entity.link = '/metastore/tables/' + entity.originalName;
+                entity.click = function () {
+                  showInAssist(entity);
+                }
               } else if (entity.type === 'TABLE') {
               } else if (entity.type === 'TABLE') {
-                entity.link = '/metastore/table' + entity.parentPath + '/' + entity.originalName;
+                entity.click = function () {
+                  showInAssist(entity);
+                }
+              } else if (entity.type === 'FIELD') {
+                entity.click = function () {
+                  showInAssist(entity);
+                }
               } else if (entity.type === 'SOURCE') {
               } else if (entity.type === 'SOURCE') {
                 entity.link = entity.sourceUrl;
                 entity.link = entity.sourceUrl;
               } else if (entity.type === 'OPERATION_EXECUTION') {
               } else if (entity.type === 'OPERATION_EXECUTION') {
@@ -1301,6 +1352,7 @@ from metadata.conf import has_navigator
           })
           })
         };
         };
 
 
+
         self.visiblePanels = ko.pureComputed(function () {
         self.visiblePanels = ko.pureComputed(function () {
           var result = $.grep(self.availablePanels, function (panel) {
           var result = $.grep(self.availablePanels, function (panel) {
             return panel.visible();
             return panel.visible();