Răsfoiți Sursa

HUE-8893 [assist] Extract all the left assist panels to ko components

This also gets rid of all the mako inline python from these assist panels.
Johan Ahlen 6 ani în urmă
părinte
comite
21c6e8aa1a
34 a modificat fișierele cu 2627 adăugiri și 2558 ștergeri
  1. 0 81
      desktop/core/src/desktop/js/assist/assistAdlsPanel.js
  2. 0 352
      desktop/core/src/desktop/js/assist/assistDbPanel.js
  3. 0 209
      desktop/core/src/desktop/js/assist/assistDocumentsPanel.js
  4. 0 71
      desktop/core/src/desktop/js/assist/assistGitPanel.js
  5. 0 131
      desktop/core/src/desktop/js/assist/assistHBasePanel.js
  6. 0 85
      desktop/core/src/desktop/js/assist/assistHdfsPanel.js
  7. 0 81
      desktop/core/src/desktop/js/assist/assistPanel.js
  8. 0 78
      desktop/core/src/desktop/js/assist/assistS3Panel.js
  9. 0 150
      desktop/core/src/desktop/js/assist/ko/ko.assistPanel.js
  10. 1 22
      desktop/core/src/desktop/js/hue.js
  11. 6 0
      desktop/core/src/desktop/js/ko/bindings/ko.assistFileDraggable.js
  12. 0 0
      desktop/core/src/desktop/js/ko/bindings/ko.assistFileDroppable.js
  13. 1 1
      desktop/core/src/desktop/js/ko/bindings/ko.storageContextPopover.js
  14. 0 0
      desktop/core/src/desktop/js/ko/components/assist/assistDbEntry.js
  15. 1 1
      desktop/core/src/desktop/js/ko/components/assist/assistDbNamespace.js
  16. 1 1
      desktop/core/src/desktop/js/ko/components/assist/assistDbSource.js
  17. 0 0
      desktop/core/src/desktop/js/ko/components/assist/assistGitEntry.js
  18. 0 0
      desktop/core/src/desktop/js/ko/components/assist/assistHBaseEntry.js
  19. 1 1
      desktop/core/src/desktop/js/ko/components/assist/assistInnerPanel.js
  20. 0 0
      desktop/core/src/desktop/js/ko/components/assist/assistStorageEntry.js
  21. 2 8
      desktop/core/src/desktop/js/ko/components/assist/assistViewModel.js
  22. 171 0
      desktop/core/src/desktop/js/ko/components/assist/ko.assistAdlsPanel.js
  23. 894 0
      desktop/core/src/desktop/js/ko/components/assist/ko.assistDbPanel.js
  24. 400 0
      desktop/core/src/desktop/js/ko/components/assist/ko.assistDocumentsPanel.js
  25. 136 0
      desktop/core/src/desktop/js/ko/components/assist/ko.assistGitPanel.js
  26. 207 0
      desktop/core/src/desktop/js/ko/components/assist/ko.assistHBasePanel.js
  27. 197 0
      desktop/core/src/desktop/js/ko/components/assist/ko.assistHdfsPanel.js
  28. 351 0
      desktop/core/src/desktop/js/ko/components/assist/ko.assistPanel.js
  29. 153 0
      desktop/core/src/desktop/js/ko/components/assist/ko.assistS3Panel.js
  30. 1 1
      desktop/core/src/desktop/js/ko/components/contextPopover/storageContext.js
  31. 10 0
      desktop/core/src/desktop/js/ko/ko.all.js
  32. 1 1
      desktop/core/src/desktop/js/sql/aceLocationHandler.js
  33. 0 1279
      desktop/core/src/desktop/templates/assist.mako
  34. 93 5
      desktop/core/src/desktop/templates/global_js_constants.mako

+ 0 - 81
desktop/core/src/desktop/js/assist/assistAdlsPanel.js

@@ -1,81 +0,0 @@
-// 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.
-
-import ko from 'knockout';
-
-import apiHelper from 'api/apiHelper';
-import AssistStorageEntry from 'assist/assistStorageEntry';
-import huePubSub from 'utils/huePubSub';
-
-class AssistAdlsPanel {
-  constructor(options) {
-    const self = this;
-    self.selectedAdlsEntry = ko.observable();
-    self.loading = ko.observable();
-    self.initialized = false;
-
-    const loadPath = path => {
-      self.loading(true);
-      const parts = path.split('/');
-      parts.shift();
-
-      const currentEntry = new AssistStorageEntry({
-        type: 'adls',
-        definition: {
-          name: '/',
-          type: 'dir'
-        },
-        parent: null
-      });
-
-      currentEntry.loadDeep(parts, entry => {
-        self.selectedAdlsEntry(entry);
-        entry.open(true);
-        self.loading(false);
-      });
-    };
-
-    self.reload = () => {
-      loadPath(apiHelper.getFromTotalStorage('assist', 'currentAdlsPath', '/'));
-    };
-
-    huePubSub.subscribe('assist.adls.go.home', () => {
-      loadPath(window.USER_HOME_DIR);
-      apiHelper.setInTotalStorage('assist', 'currentAdlsPath', window.USER_HOME_DIR);
-    });
-
-    huePubSub.subscribe('assist.selectAdlsEntry', entry => {
-      self.selectedAdlsEntry(entry);
-      apiHelper.setInTotalStorage('assist', 'currentAdlsPath', entry.path);
-    });
-
-    huePubSub.subscribe('assist.adls.refresh', () => {
-      huePubSub.publish('assist.clear.adls.cache');
-      self.reload();
-    });
-  }
-
-  init() {
-    const self = this;
-    if (self.initialized) {
-      return;
-    }
-    self.reload();
-    self.initialized = true;
-  }
-}
-
-export default AssistAdlsPanel;

+ 0 - 352
desktop/core/src/desktop/js/assist/assistDbPanel.js

@@ -1,352 +0,0 @@
-// 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.
-
-import $ from 'jquery';
-import ko from 'knockout';
-
-import apiHelper from 'api/apiHelper';
-import AssistDbSource from 'assist/assistDbSource';
-import dataCatalog from 'catalog/dataCatalog';
-import huePubSub from 'utils/huePubSub';
-
-class AssistDbPanel {
-  /**
-   * @param {Object} options
-   * @param {Object} options.i18n
-   * @param {Object[]} options.sourceTypes - All the available SQL source types
-   * @param {string} options.sourceTypes[].name - Example: Hive SQL
-   * @param {string} options.sourceTypes[].type - Example: hive
-   * @param {string} [options.activeSourceType] - Example: hive
-   * @param {Object} options.navigationSettings - enable/disable the links
-   * @param {boolean} options.navigationSettings.openItem
-   * @param {boolean} options.navigationSettings.showStats
-   * @param {boolean} options.navigationSettings.pinEnabled
-   * @param {boolean} [options.isSolr] - Default false;
-   * @param {boolean} [options.isStreams] - Default false;
-   * @constructor
-   **/
-  constructor(options) {
-    const self = this;
-    self.options = options;
-    self.i18n = options.i18n;
-    self.initialized = false;
-    self.initalizing = false;
-
-    self.isStreams = options.isStreams;
-    self.isSolr = options.isSolr;
-    self.nonSqlType = self.isSolr || self.isStreams;
-
-    if (typeof options.sourceTypes === 'undefined') {
-      options.sourceTypes = [];
-
-      if (self.isSolr) {
-        options.sourceTypes = [{ type: 'solr', name: 'solr' }];
-      } else if (self.isStreams) {
-        options.sourceTypes = [{ type: 'kafka', name: 'kafka' }];
-      } else {
-        window.ASSIST_SQL_INTERPRETERS.forEach(interpreter => {
-          options.sourceTypes.push(interpreter);
-        });
-      }
-    }
-
-    self.sources = ko.observableArray();
-    self.sourceIndex = {};
-    self.selectedSource = ko.observable(null);
-
-    options.sourceTypes.forEach(sourceType => {
-      self.sourceIndex[sourceType.type] = new AssistDbSource({
-        i18n: self.i18n,
-        type: sourceType.type,
-        name: sourceType.name,
-        nonSqlType: sourceType.type === 'solr' || sourceType.type === 'kafka',
-        navigationSettings: options.navigationSettings
-      });
-      self.sources.push(self.sourceIndex[sourceType.type]);
-    });
-
-    if (self.sources().length === 1) {
-      self.selectedSource(self.sources()[0]);
-    }
-
-    if (self.sourceIndex['solr']) {
-      huePubSub.subscribe('assist.collections.refresh', () => {
-        const solrSource = self.sourceIndex['solr'];
-        const doRefresh = () => {
-          if (solrSource.selectedNamespace()) {
-            const assistDbNamespace = solrSource.selectedNamespace();
-            dataCatalog
-              .getEntry({
-                sourceType: 'solr',
-                namespace: assistDbNamespace.namespace,
-                compute: assistDbNamespace.compute(),
-                path: []
-              })
-              .done(entry => {
-                entry.clearCache({ cascade: true });
-              });
-          }
-        };
-        if (!solrSource.hasNamespaces()) {
-          solrSource.loadNamespaces(true).done(doRefresh);
-        } else {
-          doRefresh();
-        }
-      });
-    }
-
-    huePubSub.subscribe('assist.db.highlight', catalogEntry => {
-      huePubSub.publish('left.assist.show');
-      if (catalogEntry.getSourceType() === 'solr') {
-        huePubSub.publish('assist.show.solr');
-      } else {
-        huePubSub.publish('assist.show.sql');
-      }
-      huePubSub.publish('context.popover.hide');
-      window.setTimeout(() => {
-        let foundSource;
-        self.sources().some(source => {
-          if (source.sourceType === catalogEntry.getSourceType()) {
-            foundSource = source;
-            return true;
-          }
-        });
-        if (foundSource) {
-          if (self.selectedSource() !== foundSource) {
-            self.selectedSource(foundSource);
-          }
-          foundSource.highlightInside(catalogEntry);
-        }
-      }, 0);
-    });
-
-    if (!self.isSolr && !self.isStreams) {
-      huePubSub.subscribe('assist.set.database', databaseDef => {
-        if (!databaseDef.source || !self.sourceIndex[databaseDef.source]) {
-          return;
-        }
-        self.selectedSource(self.sourceIndex[databaseDef.source]);
-        self.setDatabaseWhenLoaded(databaseDef.namespace, databaseDef.name);
-      });
-
-      const getSelectedDatabase = source => {
-        const deferred = $.Deferred();
-        const assistDbSource = self.sourceIndex[source];
-        if (assistDbSource) {
-          assistDbSource.loadedDeferred.done(() => {
-            if (assistDbSource.selectedNamespace()) {
-              assistDbSource.selectedNamespace().loadedDeferred.done(() => {
-                if (assistDbSource.selectedNamespace().selectedDatabase()) {
-                  deferred.resolve({
-                    sourceType: source,
-                    namespace: assistDbSource.selectedNamespace().namespace,
-                    name: assistDbSource.selectedNamespace().selectedDatabase().name
-                  });
-                } else {
-                  const lastSelectedDb = apiHelper.getFromTotalStorage(
-                    'assist_' + source + '_' + assistDbSource.selectedNamespace().namespace.id,
-                    'lastSelectedDb',
-                    'default'
-                  );
-                  deferred.resolve({
-                    sourceType: source,
-                    namespace: assistDbSource.selectedNamespace().namespace,
-                    name: lastSelectedDb
-                  });
-                }
-              });
-            } else {
-              deferred.resolve({
-                sourceType: source,
-                namespace: { id: 'default' },
-                name: 'default'
-              });
-            }
-          });
-        } else {
-          deferred.reject();
-        }
-
-        return deferred;
-      };
-
-      huePubSub.subscribe('assist.get.database', source => {
-        getSelectedDatabase(source).done(databaseDef => {
-          huePubSub.publish('assist.database.set', databaseDef);
-        });
-      });
-
-      huePubSub.subscribe('assist.get.database.callback', options => {
-        getSelectedDatabase(options.source).done(options.callback);
-      });
-
-      huePubSub.subscribe('assist.get.source', () => {
-        huePubSub.publish(
-          'assist.source.set',
-          self.selectedSource() ? self.selectedSource().sourceType : undefined
-        );
-      });
-
-      huePubSub.subscribe('assist.set.source', source => {
-        if (self.sourceIndex[source]) {
-          self.selectedSource(self.sourceIndex[source]);
-        }
-      });
-
-      huePubSub.publish('assist.db.panel.ready');
-
-      huePubSub.subscribe('assist.is.db.panel.ready', () => {
-        huePubSub.publish('assist.db.panel.ready');
-      });
-
-      self.selectedSource.subscribe(newSource => {
-        if (newSource) {
-          if (newSource.namespaces().length === 0) {
-            newSource.loadNamespaces();
-          }
-          apiHelper.setInTotalStorage('assist', 'lastSelectedSource', newSource.sourceType);
-        } else {
-          apiHelper.setInTotalStorage('assist', 'lastSelectedSource');
-        }
-      });
-    }
-
-    if (self.isSolr || self.isStreams) {
-      if (self.sources().length === 1) {
-        self.selectedSource(self.sources()[0]);
-        self
-          .selectedSource()
-          .loadNamespaces()
-          .done(() => {
-            const solrNamespace = self.selectedSource().selectedNamespace();
-            if (solrNamespace) {
-              solrNamespace.initDatabases();
-              solrNamespace.whenLoaded(() => {
-                solrNamespace.setDatabase('default');
-              });
-            }
-          });
-      }
-    }
-
-    self.breadcrumb = ko.pureComputed(() => {
-      if (self.isStreams && self.selectedSource()) {
-        return self.selectedSource().name;
-      }
-      if (!self.isSolr && self.selectedSource()) {
-        if (self.selectedSource().selectedNamespace()) {
-          if (
-            self
-              .selectedSource()
-              .selectedNamespace()
-              .selectedDatabase()
-          ) {
-            return self
-              .selectedSource()
-              .selectedNamespace()
-              .selectedDatabase().catalogEntry.name;
-          }
-          if (window.HAS_MULTI_CLUSTER) {
-            return self.selectedSource().selectedNamespace().name;
-          }
-        }
-        return self.selectedSource().name;
-      }
-      return null;
-    });
-  }
-
-  setDatabaseWhenLoaded(namespace, databaseName) {
-    const self = this;
-    self.selectedSource().whenLoaded(() => {
-      if (
-        self.selectedSource().selectedNamespace() &&
-        self.selectedSource().selectedNamespace().namespace.id !== namespace.id
-      ) {
-        self
-          .selectedSource()
-          .namespaces()
-          .some(otherNamespace => {
-            if (otherNamespace.namespace.id === namespace.id) {
-              self.selectedSource().selectedNamespace(otherNamespace);
-              return true;
-            }
-          });
-      }
-
-      if (self.selectedSource().selectedNamespace()) {
-        self
-          .selectedSource()
-          .selectedNamespace()
-          .whenLoaded(() => {
-            self
-              .selectedSource()
-              .selectedNamespace()
-              .setDatabase(databaseName);
-          });
-      }
-    });
-  }
-
-  back() {
-    if (this.isStreams) {
-      this.selectedSource(null);
-      return;
-    }
-    if (this.selectedSource()) {
-      if (this.selectedSource() && this.selectedSource().selectedNamespace()) {
-        if (
-          this.selectedSource()
-            .selectedNamespace()
-            .selectedDatabase()
-        ) {
-          this.selectedSource()
-            .selectedNamespace()
-            .selectedDatabase(null);
-        } else if (window.HAS_MULTI_CLUSTER) {
-          this.selectedSource().selectedNamespace(null);
-        } else {
-          this.selectedSource(null);
-        }
-      } else {
-        this.selectedSource(null);
-      }
-    }
-  }
-
-  init() {
-    if (this.initialized) {
-      return;
-    }
-    if (this.isSolr) {
-      this.selectedSource(this.sourceIndex['solr']);
-    } else if (this.isStreams) {
-      this.selectedSource(this.sourceIndex['kafka']);
-    } else {
-      const storageSourceType = apiHelper.getFromTotalStorage('assist', 'lastSelectedSource');
-      if (!this.selectedSource()) {
-        if (this.options.activeSourceType) {
-          this.selectedSource(this.sourceIndex[this.options.activeSourceType]);
-        } else if (storageSourceType && this.sourceIndex[storageSourceType]) {
-          this.selectedSource(this.sourceIndex[storageSourceType]);
-        }
-      }
-    }
-    this.initialized = true;
-  }
-}
-
-export default AssistDbPanel;

+ 0 - 209
desktop/core/src/desktop/js/assist/assistDocumentsPanel.js

@@ -1,209 +0,0 @@
-// 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.
-
-import $ from 'jquery';
-import ko from 'knockout';
-
-import apiHelper from 'api/apiHelper';
-import huePubSub from 'utils/huePubSub';
-import HueFileEntry from 'doc/hueFileEntry';
-import { DOCUMENT_TYPES } from 'doc/docSupport';
-
-class AssistDocumentsPanel {
-  /**
-   * @param {Object} options
-   * @param {string} options.user
-   * @constructor
-   **/
-  constructor(options) {
-    const self = this;
-    self.user = options.user;
-
-    self.activeEntry = ko.observable();
-    self.activeSort = ko.observable('defaultAsc');
-    self.typeFilter = ko.observable(DOCUMENT_TYPES[0]); // all is first
-
-    self.highlightTypeFilter = ko.observable(false);
-
-    const lastOpenedUuid = apiHelper.getFromTotalStorage('assist', 'last.opened.assist.doc.uuid');
-
-    if (lastOpenedUuid) {
-      self.activeEntry(
-        new HueFileEntry({
-          activeEntry: self.activeEntry,
-          trashEntry: ko.observable(),
-          app: 'documents',
-          user: self.user,
-          activeSort: self.activeSort,
-          typeFilter: self.typeFilter,
-          definition: {
-            uuid: lastOpenedUuid,
-            type: 'directory'
-          }
-        })
-      );
-    } else {
-      self.fallbackToRoot();
-    }
-
-    self.activeEntry.subscribe(newEntry => {
-      if (!newEntry.loaded()) {
-        const loadedSub = newEntry.loaded.subscribe(loaded => {
-          if (
-            loaded &&
-            !newEntry.hasErrors() &&
-            newEntry.definition() &&
-            newEntry.definition().uuid
-          ) {
-            apiHelper.setInTotalStorage(
-              'assist',
-              'last.opened.assist.doc.uuid',
-              newEntry.definition().uuid
-            );
-          }
-          loadedSub.dispose();
-        });
-      } else if (!newEntry.hasErrors() && newEntry.definition() && newEntry.definition().uuid) {
-        apiHelper.setInTotalStorage(
-          'assist',
-          'last.opened.assist.doc.uuid',
-          newEntry.definition().uuid
-        );
-      }
-    });
-
-    self.reload = () => {
-      self.activeEntry().load(
-        () => {},
-        () => {
-          self.fallbackToRoot();
-        }
-      );
-    };
-
-    huePubSub.subscribe('assist.document.refresh', () => {
-      huePubSub.publish('assist.clear.document.cache');
-      self.reload();
-    });
-
-    huePubSub.subscribe('assist.doc.highlight', details => {
-      huePubSub.publish('assist.show.documents');
-      huePubSub.publish('context.popover.hide');
-      const whenLoaded = $.Deferred().done(() => {
-        self.activeEntry().highlightInside(details.docUuid);
-      });
-      if (
-        self.activeEntry() &&
-        self.activeEntry().definition() &&
-        self.activeEntry().definition().uuid === details.parentUuid
-      ) {
-        if (self.activeEntry().loaded() && !self.activeEntry().hasErrors()) {
-          whenLoaded.resolve();
-        } else {
-          const loadedSub = self.activeEntry().loaded.subscribe(newVal => {
-            if (newVal) {
-              if (!self.activeEntry().hasErrors()) {
-                whenLoaded.resolve();
-              }
-              whenLoaded.reject();
-              loadedSub.remove();
-            }
-          });
-        }
-        self.activeEntry().highlight(details.docUuid);
-      } else {
-        self.activeEntry(
-          new HueFileEntry({
-            activeEntry: self.activeEntry,
-            trashEntry: ko.observable(),
-            app: 'documents',
-            user: self.user,
-            activeSort: self.activeSort,
-            typeFilter: self.typeFilter,
-            definition: {
-              uuid: details.parentUuid,
-              type: 'directory'
-            }
-          })
-        );
-        self.activeEntry().load(
-          () => {
-            whenLoaded.resolve();
-          },
-          () => {
-            whenLoaded.reject();
-            self.fallbackToRoot();
-          }
-        );
-      }
-    });
-  }
-
-  setTypeFilter(newType) {
-    const self = this;
-    DOCUMENT_TYPES.some(docType => {
-      if (docType.type === newType) {
-        self.typeFilter(docType);
-        return true;
-      }
-    });
-    self.highlightTypeFilter(true);
-    window.setTimeout(() => {
-      self.highlightTypeFilter(false);
-    }, 600);
-  }
-
-  fallbackToRoot() {
-    const self = this;
-    if (
-      !self.activeEntry() ||
-      (self.activeEntry().definition() &&
-        (self.activeEntry().definition().path !== '/' || self.activeEntry().definition().uuid))
-    ) {
-      apiHelper.setInTotalStorage('assist', 'last.opened.assist.doc.uuid', null);
-      self.activeEntry(
-        new HueFileEntry({
-          activeEntry: self.activeEntry,
-          trashEntry: ko.observable(),
-          app: 'documents',
-          user: self.user,
-          activeSort: self.activeSort,
-          typeFilter: self.typeFilter,
-          definition: {
-            name: '/',
-            type: 'directory'
-          }
-        })
-      );
-      self.activeEntry().load();
-    }
-  }
-
-  init() {
-    const self = this;
-    if (!self.activeEntry().loaded()) {
-      self.activeEntry().load(
-        () => {},
-        () => {
-          self.fallbackToRoot();
-        },
-        true
-      );
-    }
-  }
-}
-
-export default AssistDocumentsPanel;

+ 0 - 71
desktop/core/src/desktop/js/assist/assistGitPanel.js

@@ -1,71 +0,0 @@
-// 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.
-
-import ko from 'knockout';
-
-import apiHelper from 'api/apiHelper';
-import AssistGitEntry from 'assist/assistGitEntry';
-import huePubSub from 'utils/huePubSub';
-
-class AssistGitPanel {
-  /**
-   * @param {Object} options
-   * @constructor
-   **/
-  constructor(options) {
-    const self = this;
-
-    self.selectedGitEntry = ko.observable();
-    self.reload = function() {
-      const lastKnownPath = apiHelper.getFromTotalStorage(
-        'assist',
-        'currentGitPath',
-        window.USER_HOME_DIR
-      );
-      const parts = lastKnownPath.split('/');
-      parts.shift();
-
-      const currentEntry = new AssistGitEntry({
-        definition: {
-          name: '/',
-          type: 'dir'
-        },
-        parent: null
-      });
-
-      currentEntry.loadDeep(parts, entry => {
-        self.selectedGitEntry(entry);
-        entry.open(true);
-      });
-    };
-
-    huePubSub.subscribe('assist.selectGitEntry', entry => {
-      self.selectedGitEntry(entry);
-      apiHelper.setInTotalStorage('assist', 'currentGitPath', entry.path);
-    });
-
-    huePubSub.subscribe('assist.git.refresh', () => {
-      huePubSub.publish('assist.clear.git.cache');
-      self.reload();
-    });
-  }
-
-  init() {
-    this.reload();
-  }
-}
-
-export default AssistGitPanel;

+ 0 - 131
desktop/core/src/desktop/js/assist/assistHBasePanel.js

@@ -1,131 +0,0 @@
-// 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.
-
-import ko from 'knockout';
-
-import apiHelper from 'api/apiHelper';
-import AssistHBaseEntry from 'assist/assistHBaseEntry';
-import huePubSub from 'utils/huePubSub';
-
-class AssistHBasePanel {
-  /**
-   * @param {Object} options
-   * @constructor
-   **/
-  constructor(options) {
-    const self = this;
-    self.initialized = false;
-
-    const root = new AssistHBaseEntry({
-      definition: {
-        host: '',
-        name: '',
-        port: 0
-      }
-    });
-
-    self.selectedHBaseEntry = ko.observable();
-    self.reload = () => {
-      self.selectedHBaseEntry(root);
-      root.loadEntries(() => {
-        const lastOpenendPath = apiHelper.getFromTotalStorage(
-          'assist',
-          'last.opened.hbase.entry',
-          null
-        );
-        if (lastOpenendPath) {
-          root.entries().every(entry => {
-            if (entry.path === lastOpenendPath) {
-              entry.open();
-              return false;
-            }
-            return true;
-          });
-        }
-      });
-    };
-
-    self.selectedHBaseEntry.subscribe(newEntry => {
-      if (newEntry !== root || (newEntry === root && newEntry.loaded)) {
-        apiHelper.setInTotalStorage('assist', 'last.opened.hbase.entry', newEntry.path);
-      }
-    });
-
-    huePubSub.subscribe('assist.clickHBaseItem', entry => {
-      if (entry.definition.host) {
-        entry.loadEntries();
-        self.selectedHBaseEntry(entry);
-      } else {
-        huePubSub.publish('assist.dblClickHBaseItem', entry);
-      }
-    });
-
-    huePubSub.subscribe('assist.clickHBaseRootItem', () => {
-      self.reload();
-    });
-
-    const delayChangeHash = hash => {
-      window.setTimeout(() => {
-        window.location.hash = hash;
-      }, 0);
-    };
-
-    self.lastClickeHBaseEntry = null;
-    self.HBaseLoaded = false;
-
-    huePubSub.subscribeOnce('hbase.app.loaded', () => {
-      if (self.selectedHBaseEntry() && self.lastClickeHBaseEntry) {
-        delayChangeHash(
-          self.selectedHBaseEntry().definition.name +
-            '/' +
-            self.lastClickeHBaseEntry.definition.name
-        );
-      }
-      self.HBaseLoaded = true;
-    });
-
-    huePubSub.subscribe('assist.dblClickHBaseItem', entry => {
-      const hash = self.selectedHBaseEntry().definition.name + '/' + entry.definition.name;
-      if (window.location.pathname.startsWith('/hue/hbase')) {
-        window.location.hash = hash;
-      } else {
-        self.lastClickeHBaseEntry = entry;
-        huePubSub.subscribeOnce('app.gained.focus', app => {
-          if (app === 'hbase' && self.HBaseLoaded) {
-            delayChangeHash(hash);
-          }
-        });
-        huePubSub.publish('open.link', '/hbase');
-      }
-    });
-
-    huePubSub.subscribe('assist.hbase.refresh', () => {
-      huePubSub.publish('assist.clear.hbase.cache');
-      self.reload();
-    });
-  }
-
-  init() {
-    const self = this;
-    if (self.initialized) {
-      return;
-    }
-    self.reload();
-    self.initialized = true;
-  }
-}
-
-export default AssistHBasePanel;

+ 0 - 85
desktop/core/src/desktop/js/assist/assistHdfsPanel.js

@@ -1,85 +0,0 @@
-// 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.
-
-import ko from 'knockout';
-
-import apiHelper from 'api/apiHelper';
-import AssistStorageEntry from 'assist/assistStorageEntry';
-import huePubSub from 'utils/huePubSub';
-
-class AssistHdfsPanel {
-  /**
-   * @param {Object} options
-   * @constructor
-   **/
-  constructor(options) {
-    const self = this;
-    self.selectedHdfsEntry = ko.observable();
-    self.loading = ko.observable();
-    self.initialized = false;
-
-    const loadPath = path => {
-      self.loading(true);
-      const parts = path.split('/');
-      parts.shift();
-
-      const currentEntry = new AssistStorageEntry({
-        type: 'hdfs',
-        definition: {
-          name: '/',
-          type: 'dir'
-        },
-        parent: null
-      });
-
-      currentEntry.loadDeep(parts, entry => {
-        self.selectedHdfsEntry(entry);
-        entry.open(true);
-        self.loading(false);
-      });
-    };
-
-    self.reload = () => {
-      loadPath(apiHelper.getFromTotalStorage('assist', 'currentHdfsPath', window.USER_HOME_DIR));
-    };
-
-    huePubSub.subscribe('assist.hdfs.go.home', () => {
-      loadPath(window.USER_HOME_DIR);
-      apiHelper.setInTotalStorage('assist', 'currentHdfsPath', window.USER_HOME_DIR);
-    });
-
-    huePubSub.subscribe('assist.selectHdfsEntry', entry => {
-      self.selectedHdfsEntry(entry);
-      apiHelper.setInTotalStorage('assist', 'currentHdfsPath', entry.path);
-    });
-
-    huePubSub.subscribe('assist.hdfs.refresh', () => {
-      huePubSub.publish('assist.clear.hdfs.cache');
-      self.reload();
-    });
-  }
-
-  init() {
-    const self = this;
-    if (self.initialized) {
-      return;
-    }
-    self.reload();
-    self.initialized = true;
-  }
-}
-
-export default AssistHdfsPanel;

+ 0 - 81
desktop/core/src/desktop/js/assist/assistPanel.js

@@ -1,81 +0,0 @@
-// 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.
-
-import ko from 'knockout';
-
-import apiHelper from 'api/apiHelper';
-import AssistStorageEntry from 'assist/assistStorageEntry';
-import huePubSub from 'utils/huePubSub';
-
-class AssistAdlsPanel {
-  constructor(options) {
-    const self = this;
-    self.selectedAdlsEntry = ko.observable();
-    self.loading = ko.observable();
-    self.initialized = false;
-
-    const loadPath = path => {
-      self.loading(true);
-      const parts = path.split('/');
-      parts.shift();
-
-      const currentEntry = new AssistStorageEntry({
-        type: 'adls',
-        definition: {
-          name: '/',
-          type: 'dir'
-        },
-        parent: null
-      });
-
-      currentEntry.loadDeep(parts, entry => {
-        self.selectedAdlsEntry(entry);
-        entry.open(true);
-        self.loading(false);
-      });
-    };
-
-    self.reload = () => {
-      loadPath(apiHelper.getFromTotalStorage('assist', 'currentAdlsPath', '/'));
-    };
-
-    huePubSub.subscribe('assist.adls.go.home', () => {
-      loadPath(window.USER_HOME_DIR);
-      apiHelper.setInTotalStorage('assist', 'currentAdlsPath', window.USER_HOME_DIR);
-    });
-
-    huePubSub.subscribe('assist.selectAdlsEntry', entry => {
-      self.selectedAdlsEntry(entry);
-      apiHelper.setInTotalStorage('assist', 'currentAdlsPath', entry.path);
-    });
-
-    huePubSub.subscribe('assist.adls.refresh', () => {
-      huePubSub.publish('assist.clear.adls.cache');
-      self.reload();
-    });
-  }
-
-  init() {
-    const self = this;
-    if (self.initialized) {
-      return;
-    }
-    self.reload();
-    self.initialized = true;
-  }
-}
-
-export default AssistAdlsPanel;

+ 0 - 78
desktop/core/src/desktop/js/assist/assistS3Panel.js

@@ -1,78 +0,0 @@
-// 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.
-
-import ko from 'knockout';
-
-import apiHelper from 'api/apiHelper';
-import AssistStorageEntry from 'assist/assistStorageEntry';
-import huePubSub from 'utils/huePubSub';
-
-class AssistS3Panel {
-  /**
-   * @param {Object} options
-   * @constructor
-   **/
-  constructor(options) {
-    const self = this;
-
-    self.selectedS3Entry = ko.observable();
-    self.loading = ko.observable();
-    self.initialized = false;
-
-    self.reload = () => {
-      self.loading(true);
-      const lastKnownPath = apiHelper.getFromTotalStorage('assist', 'currentS3Path', '/');
-      const parts = lastKnownPath.split('/');
-      parts.shift();
-
-      const currentEntry = new AssistStorageEntry({
-        type: 's3',
-        definition: {
-          name: '/',
-          type: 'dir'
-        },
-        parent: null
-      });
-
-      currentEntry.loadDeep(parts, entry => {
-        self.selectedS3Entry(entry);
-        entry.open(true);
-        self.loading(false);
-      });
-    };
-
-    huePubSub.subscribe('assist.selectS3Entry', entry => {
-      self.selectedS3Entry(entry);
-      apiHelper.setInTotalStorage('assist', 'currentS3Path', entry.path);
-    });
-
-    huePubSub.subscribe('assist.s3.refresh', () => {
-      huePubSub.publish('assist.clear.s3.cache');
-      self.reload();
-    });
-  }
-
-  init() {
-    const self = this;
-    if (self.initialized) {
-      return;
-    }
-    self.reload();
-    self.initialized = true;
-  }
-}
-
-export default AssistS3Panel;

+ 0 - 150
desktop/core/src/desktop/js/assist/ko/ko.assistPanel.js

@@ -1,150 +0,0 @@
-// 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.
-
-import ko from 'knockout';
-
-import componentUtils from 'ko/components/componentUtils';
-
-const TEMPLATE = `
-  <ul class="cui-app-switcher nav navbar-nav">
-    <li class="dropdown">
-      <button class="btn btn-flat" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true" role="button">
-        <i class="fa fa-th"></i>
-      </button>
-
-      <ul class="dropdown-menu pull-right" role="menu">
-        <!-- ko foreach: links -->
-        <li class="nav-item">
-          <!-- ko if: $data.divider -->
-          <div class="divider"></div>
-          <!-- /ko -->
-          <!-- ko ifnot: $data.divider -->
-          <a role="button" class="nav-link" data-bind="attr: { href: link }">
-            <span class="app-switcher-app-icon">
-              <!-- ko if: $data.icon -->
-              <i data-bind="attr: { class: icon }"></i>
-              <!-- /ko -->
-              <!-- ko if: $data.img -->
-              <!-- ko template: 'app-switcher-icon-template' --><!-- /ko -->
-              <!-- /ko -->
-            </span>
-            <span class="app-switcher-app-name" data-bind="text: label"></span>
-          </a>
-          <!-- /ko -->
-        </li>
-        <!-- /ko -->
-      </ul>
-    </li>
-  </ul>
-`;
-
-const apps = {
-  hue: {
-    label: 'Data Warehouse',
-    icon: 'altus-icon altus-adb-query'
-  },
-  cdsw: {
-    label: 'Data Science',
-    icon: 'altus-icon altus-ds'
-  },
-  dataFlow: {
-    label: 'Data Engineering',
-    icon: 'altus-icon altus-workload'
-  },
-  navigator: {
-    label: 'Data Stewart',
-    icon: 'altus-icon altus-adb'
-  },
-  navopt: {
-    label: 'Admin',
-    icon: 'altus-icon altus-iam'
-  }
-};
-
-const AppSwitcher = function AppSwitcher(params) {
-  const self = this;
-
-  self.links = ko.observableArray([]);
-
-  const altusLinks = [
-    {
-      product: 'hue',
-      link: 'https://sso.staging.aem.cloudera.com'
-    },
-    {
-      product: 'cdsw',
-      link: 'https://sso.staging.aem.cloudera.com'
-    },
-    {
-      product: 'dataFlow',
-      link: 'https://sso.staging.aem.cloudera.com'
-    },
-    {
-      product: 'navigator',
-      link: 'https://sso.staging.aem.cloudera.com'
-    },
-    {
-      product: 'navopt',
-      link: 'https://sso.staging.aem.cloudera.com'
-    }
-  ];
-
-  const onPremLinks = [
-    {
-      product: 'cdsw',
-      link: '/'
-    },
-    {
-      divider: true
-    },
-    {
-      product: 'cm',
-      link: '/'
-    },
-    {
-      product: 'navigator',
-      link: '/'
-    }
-  ];
-
-  const applyLinks = function(links) {
-    const newLinks = [];
-    links.forEach(link => {
-      if (link.product) {
-        const lookup = apps[link.product];
-        if (lookup) {
-          lookup.link = link.link;
-          newLinks.push(lookup);
-        }
-      } else {
-        newLinks.push(link);
-      }
-    });
-    self.links(newLinks);
-  };
-
-  params.onPrem.subscribe(newValue => {
-    if (newValue) {
-      applyLinks(onPremLinks);
-    } else {
-      applyLinks(altusLinks);
-    }
-  });
-
-  applyLinks(altusLinks);
-};
-
-componentUtils.registerComponent('hue-app-switcher', AppSwitcher, TEMPLATE);

+ 1 - 22
desktop/core/src/desktop/js/hue.js

@@ -56,7 +56,7 @@ import sqlUtils from 'sql/sqlUtils';
 import { PigFunctions, SqlSetOptions, SqlFunctions } from 'sql/sqlFunctions';
 import sqlWorkerHandler from 'sql/sqlWorkerHandler';
 
-import 'assist/assistViewModel';
+import 'ko/components/assist/assistViewModel';
 import OnePageViewModel from 'onePageViewModel';
 import SideBarViewModel from 'sideBarViewModel';
 import SidePanelViewModel from 'sidePanelViewModel';
@@ -72,27 +72,6 @@ import sqlStatementsParser from 'parse/sqlStatementsParser'; // In search.ko and
 import HueFileEntry from 'doc/hueFileEntry';
 import HueDocument from 'doc/hueDocument';
 
-// TODO: Temporary
-import 'assist/ko/ko.assistFileDraggable';
-import 'assist/ko/ko.assistFileDroppable';
-import AssistDbPanel from 'assist/assistDbPanel';
-import AssistInnerPanel from 'assist/assistInnerPanel';
-import AssistDocumentsPanel from 'assist/assistDocumentsPanel';
-import AssistHdfsPanel from 'assist/assistHdfsPanel';
-import AssistAdlsPanel from 'assist/assistAdlsPanel';
-import AssistGitPanel from 'assist/assistGitPanel';
-import AssistS3Panel from 'assist/assistS3Panel';
-import AssistHBasePanel from 'assist/assistHBasePanel';
-
-window.AssistDbPanel = AssistDbPanel;
-window.AssistInnerPanel = AssistInnerPanel;
-window.AssistDocumentsPanel = AssistDocumentsPanel;
-window.AssistHdfsPanel = AssistHdfsPanel;
-window.AssistAdlsPanel = AssistAdlsPanel;
-window.AssistGitPanel = AssistGitPanel;
-window.AssistS3Panel = AssistS3Panel;
-window.AssistHBasePanel = AssistHBasePanel;
-
 // TODO: Migrate away
 window._ = _;
 window.apiHelper = apiHelper;

+ 6 - 0
desktop/core/src/desktop/js/assist/ko/ko.assistFileDraggable.js → desktop/core/src/desktop/js/ko/bindings/ko.assistFileDraggable.js

@@ -39,6 +39,12 @@ ko.bindingHandlers.assistFileDraggable = {
         dragStartX = event.clientX;
         dragStartY = event.clientY;
 
+        if ($('.assist-file-entry-drag').length === 0) {
+          $('<div class="assist-file-entry-drag"><span class="drag-text"></span></div>').appendTo(
+            'body'
+          );
+        }
+
         const $helper = $('.assist-file-entry-drag')
           .clone()
           .appendTo($container);

+ 0 - 0
desktop/core/src/desktop/js/assist/ko/ko.assistFileDroppable.js → desktop/core/src/desktop/js/ko/bindings/ko.assistFileDroppable.js


+ 1 - 1
desktop/core/src/desktop/js/ko/bindings/ko.storageContextPopover.js

@@ -17,7 +17,7 @@
 import $ from 'jquery';
 import ko from 'knockout';
 
-import AssistStorageEntry from 'assist/assistStorageEntry';
+import AssistStorageEntry from 'ko/components/assist/assistStorageEntry';
 import huePubSub from 'utils/huePubSub';
 
 /**

+ 0 - 0
desktop/core/src/desktop/js/assist/assistDbEntry.js → desktop/core/src/desktop/js/ko/components/assist/assistDbEntry.js


+ 1 - 1
desktop/core/src/desktop/js/assist/assistDbNamespace.js → desktop/core/src/desktop/js/ko/components/assist/assistDbNamespace.js

@@ -17,7 +17,7 @@
 import $ from 'jquery';
 import ko from 'knockout';
 
-import AssistDbEntry from 'assist/assistDbEntry';
+import AssistDbEntry from 'ko/components/assist/assistDbEntry';
 import dataCatalog from 'catalog/dataCatalog';
 import huePubSub from 'utils/huePubSub';
 

+ 1 - 1
desktop/core/src/desktop/js/assist/assistDbSource.js → desktop/core/src/desktop/js/ko/components/assist/assistDbSource.js

@@ -18,7 +18,7 @@ import $ from 'jquery';
 import ko from 'knockout';
 
 import apiHelper from 'api/apiHelper';
-import AssistDbNamespace from 'assist/assistDbNamespace';
+import AssistDbNamespace from 'ko/components/assist/assistDbNamespace';
 import contextCatalog from 'catalog/contextCatalog';
 import huePubSub from 'utils/huePubSub';
 

+ 0 - 0
desktop/core/src/desktop/js/assist/assistGitEntry.js → desktop/core/src/desktop/js/ko/components/assist/assistGitEntry.js


+ 0 - 0
desktop/core/src/desktop/js/assist/assistHBaseEntry.js → desktop/core/src/desktop/js/ko/components/assist/assistHBaseEntry.js


+ 1 - 1
desktop/core/src/desktop/js/assist/assistInnerPanel.js → desktop/core/src/desktop/js/ko/components/assist/assistInnerPanel.js

@@ -26,7 +26,7 @@ class AssistInnerPanel {
    * @param {string} options.icon
    * @param {boolean} [options.rightAlignIcon] - Default false
    * @param {boolean} options.visible
-   * @param {(AssistDbPanel|AssistHdfsPanel|AssistGitPanel|AssistDocumentsPanel|AssistS3Panel)} panelData
+   * @param {Object} panelData - component data
    * @constructor
    */
   constructor(options) {

+ 0 - 0
desktop/core/src/desktop/js/assist/assistStorageEntry.js → desktop/core/src/desktop/js/ko/components/assist/assistStorageEntry.js


+ 2 - 8
desktop/core/src/desktop/js/assist/assistViewModel.js → desktop/core/src/desktop/js/ko/components/assist/assistViewModel.js

@@ -14,15 +14,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import AssistDbEntry from 'assist/assistDbEntry';
-import AssistDbSource from 'assist/assistDbSource';
-import AssistGitEntry from 'assist/assistGitEntry';
-import AssistHBaseEntry from 'assist/assistHBaseEntry';
-import AssistStorageEntry from 'assist/assistStorageEntry';
+import AssistDbEntry from 'ko/components/assist/assistDbEntry';
+import AssistDbSource from 'ko/components/assist/assistDbSource';
 
 // TODO: Get rid of
 window.AssistDbEntry = AssistDbEntry;
 window.AssistDbSource = AssistDbSource;
-window.AssistGitEntry = AssistGitEntry;
-window.AssistHBaseEntry = AssistHBaseEntry;
-window.AssistStorageEntry = AssistStorageEntry;

+ 171 - 0
desktop/core/src/desktop/js/ko/components/assist/ko.assistAdlsPanel.js

@@ -0,0 +1,171 @@
+// 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.
+
+import ko from 'knockout';
+
+import apiHelper from 'api/apiHelper';
+import AssistStorageEntry from 'ko/components/assist/assistStorageEntry';
+import componentUtils from 'ko/components/componentUtils';
+import huePubSub from 'utils/huePubSub';
+import HDFS_CONTEXT_ITEMS_TEMPLATE from 'ko/components/assist/ko.assistHdfsPanel';
+import I18n from 'utils/i18n';
+
+const TEMPLATE =
+  HDFS_CONTEXT_ITEMS_TEMPLATE +
+  `
+  <script type="text/html" id="assist-adls-header-actions">
+    <div class="assist-db-header-actions">
+      <a class="inactive-action" href="javascript:void(0)" data-bind="click: goHome, attr: { title: I18n('Go to ' + window.USER_HOME_DIR) }"><i class="pointer fa fa-home"></i></a>
+      <!-- ko if: window.SHOW_UPLOAD_BUTTON -->
+      <a class="inactive-action" data-bind="dropzone: {
+            url: '/filebrowser/upload/file?dest=adl:' + path,
+            params: { dest: path },
+            paramName: 'hdfs_file',
+            onError: function(x, e){ $(document).trigger('error', e); },
+            onComplete: function () { huePubSub.publish('assist.adls.refresh'); } }" title="${I18n(
+              'Upload file'
+            )}" href="javascript:void(0)">
+        <div class="dz-message inline" data-dz-message><i class="pointer fa fa-plus" title="${I18n(
+          'Upload file'
+        )}"></i></div>
+      </a>
+      <!-- /ko -->
+      <a class="inactive-action" href="javascript:void(0)" data-bind="click: function () { huePubSub.publish('assist.adls.refresh'); }" title="${I18n(
+        'Manual refresh'
+      )}"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }"></i></a>
+    </div>
+  </script>
+  
+  <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
+  <!-- ko with: selectedAdlsEntry -->
+  <div class="assist-flex-header assist-breadcrumb" >
+    <!-- ko if: parent !== null -->
+    <a href="javascript: void(0);" data-bind="appAwareTemplateContextMenu: { template: 'hdfs-context-items', scrollContainer: '.assist-adls-scrollable' }, click: function () { huePubSub.publish('assist.selectAdlsEntry', parent); }">
+      <i class="fa fa-fw fa-chevron-left"></i>
+      <i class="fa fa-fw fa-folder-o"></i>
+      <span data-bind="text: definition.name, tooltip: {'title': path, 'placement': 'top' }"></span>
+    </a>
+    <!-- /ko -->
+    <!-- ko if: parent === null -->
+    <div>
+      <i class="fa fa-fw fa-folder-o"></i>
+      <span data-bind="text: path"></span>
+    </div>
+    <!-- /ko -->
+    <!-- ko template: 'assist-adls-header-actions' --><!-- /ko -->
+  </div>
+  <div class="assist-flex-search">
+    <div class="assist-filter"><input class="clearable" type="text" placeholder="${I18n(
+      'Filter...'
+    )}" data-bind="clearable: filter, value: filter, valueUpdate: 'afterkeydown'"/></div>
+  </div>
+  <div class="assist-flex-fill assist-adls-scrollable" data-bind="delayedOverflow">
+    <div data-bind="visible: ! loading() && ! hasErrors()" style="position: relative;">
+      <!-- ko hueSpinner: { spin: loadingMore, overlay: true } --><!-- /ko -->
+      <ul class="assist-tables" data-bind="foreachVisible: { data: entries, minHeight: 22, container: '.assist-adls-scrollable', fetchMore: $data.fetchMore.bind($data) }">
+        <li class="assist-entry assist-table-link" style="position: relative;" data-bind="appAwareTemplateContextMenu: { template: 'hdfs-context-items', scrollContainer: '.assist-adls-scrollable' }, visibleOnHover: { override: contextPopoverVisible, 'selector': '.assist-actions' }">
+          <div class="assist-actions table-actions" style="opacity: 0;" >
+            <a style="padding: 0 3px;" class="inactive-action" href="javascript:void(0);" data-bind="click: showContextPopover, css: { 'blue': contextPopoverVisible }">
+              <i class='fa fa-info' title="${I18n('Details')}"></i>
+            </a>
+          </div>
+
+          <a href="javascript:void(0)" class="assist-entry assist-table-link" data-bind="multiClick: { click: toggleOpen, dblClick: dblClick }, attr: {'title': definition.name }">
+            <!-- ko if: definition.type === 'dir' -->
+            <i class="fa fa-fw fa-folder-o muted valign-middle"></i>
+            <!-- /ko -->
+            <!-- ko if: definition.type === 'file' -->
+            <i class="fa fa-fw fa-file-o muted valign-middle"></i>
+            <!-- /ko -->
+            <span draggable="true" data-bind="text: definition.name, draggableText: { text: '\\\\\\\\'' + path + '\\\\\\\\'', meta: {'type': 'adls', 'definition': definition} }"></span>
+          </a>
+        </li>
+      </ul>
+      <!-- ko if: !loading() && entries().length === 0 -->
+      <ul class="assist-tables">
+        <li class="assist-entry"><span class="assist-no-entries"><!-- ko if: filter() -->${I18n(
+          'No results found.'
+        )}<!-- /ko --><!-- ko ifnot: filter() -->${I18n('Empty directory')}<!-- /ko --></span></li>
+      </ul>
+      <!-- /ko -->
+    </div>
+    <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
+    <div class="assist-errors" data-bind="visible: ! loading() && hasErrors()">
+      <span>${I18n('Error loading contents.')}</span>
+    </div>
+  </div>
+  <!-- /ko -->
+`;
+
+class AssistAdlsPanel {
+  constructor() {
+    const self = this;
+    self.selectedAdlsEntry = ko.observable();
+    self.loading = ko.observable();
+    self.initialized = false;
+
+    const loadPath = path => {
+      self.loading(true);
+      const parts = path.split('/');
+      parts.shift();
+
+      const currentEntry = new AssistStorageEntry({
+        type: 'adls',
+        definition: {
+          name: '/',
+          type: 'dir'
+        },
+        parent: null
+      });
+
+      currentEntry.loadDeep(parts, entry => {
+        self.selectedAdlsEntry(entry);
+        entry.open(true);
+        self.loading(false);
+      });
+    };
+
+    self.reload = () => {
+      loadPath(apiHelper.getFromTotalStorage('assist', 'currentAdlsPath', '/'));
+    };
+
+    huePubSub.subscribe('assist.adls.go.home', () => {
+      loadPath(window.USER_HOME_DIR);
+      apiHelper.setInTotalStorage('assist', 'currentAdlsPath', window.USER_HOME_DIR);
+    });
+
+    huePubSub.subscribe('assist.selectAdlsEntry', entry => {
+      self.selectedAdlsEntry(entry);
+      apiHelper.setInTotalStorage('assist', 'currentAdlsPath', entry.path);
+    });
+
+    huePubSub.subscribe('assist.adls.refresh', () => {
+      huePubSub.publish('assist.clear.adls.cache');
+      self.reload();
+    });
+  }
+
+  init() {
+    const self = this;
+    if (self.initialized) {
+      return;
+    }
+    self.reload();
+    self.initialized = true;
+  }
+}
+
+componentUtils.registerComponent('hue-assist-adls-panel', AssistAdlsPanel, TEMPLATE);

+ 894 - 0
desktop/core/src/desktop/js/ko/components/assist/ko.assistDbPanel.js

@@ -0,0 +1,894 @@
+// 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.
+
+import $ from 'jquery';
+import ko from 'knockout';
+
+import apiHelper from 'api/apiHelper';
+import AssistDbSource from 'ko/components/assist/assistDbSource';
+import componentUtils from 'ko/components/componentUtils';
+import dataCatalog from 'catalog/dataCatalog';
+import huePubSub from 'utils/huePubSub';
+import I18n from 'utils/i18n';
+
+const TEMPLATE = `
+  <script type="text/html" id="assist-no-database-entries">
+    <ul class="assist-tables">
+      <li>
+        <span class="assist-no-entries">${I18n('No entries found')}</span>
+      </li>
+    </ul>
+  </script>
+
+  <script type="text/html" id="assist-no-table-entries">
+    <ul>
+      <li>
+        <span class="assist-no-entries">${I18n('The table has no columns')}</span>
+      </li>
+    </ul>
+  </script>
+
+  <script type="text/html" id="assist-database-actions">
+    <div class="assist-actions database-actions" style="opacity: 0">
+      <!-- ko if: sourceType === 'hive' || sourceType === 'impala' -->
+      <a class="inactive-action" href="javascript:void(0)" data-bind="visible: navigationSettings.showStats, click: function (data, event) { showContextPopover(data, event); }, css: { 'blue': statsVisible }"><i class="fa fa-fw fa-info" title="${I18n(
+        'Show details'
+      )}"></i></a>
+      <!-- /ko -->
+      <a class="inactive-action" href="javascript:void(0)" data-bind="visible: navigationSettings.openItem, click: openItem"><i class="fa fa-long-arrow-right" title="${I18n(
+        'Open'
+      )}"></i></a>
+    </div>
+  </script>
+  
+  <script type="text/html" id="collection-title-context-items">
+    <li><a href="javascript:void(0);" data-bind="hueLink: '/indexer'"><i class="fa fa-fw fa-table"></i> ${I18n(
+      'Open in Browser'
+    )}</a></li>
+  </script>
+
+  <script type="text/html" id="sql-context-items">
+    <!-- ko if: typeof catalogEntry !== 'undefined' -->
+      <li><a href="javascript:void(0);" data-bind="click: function (data) { showContextPopover(data, { target: $parentContext.$contextSourceElement }, { left: -15, top: 2 }); }"><i class="fa fa-fw fa-info"></i> ${I18n(
+        'Show details'
+      )}</a></li>
+      <!-- ko switch: sourceType -->
+      <!-- ko case: 'solr' -->
+        <!-- ko if: catalogEntry.isTableOrView() -->
+        <li><a href="javascript:void(0);" data-bind="click: openInIndexer"><i class="fa fa-fw fa-table"></i> ${I18n(
+          'Open in Browser'
+        )}</a></li>
+        <li><a href="javascript: void(0);" data-bind="click: function() { explore(true); }"><!-- ko template: { name: 'app-icon-template', data: { icon: 'dashboard' } } --><!-- /ko --> ${I18n(
+          'Open in Dashboard'
+        )}</a></li>
+        <!-- /ko -->
+      <!-- /ko -->
+      <!-- ko case: $default -->
+        <!-- ko if: !catalogEntry.isDatabase() && $currentApp() === 'editor' -->
+        <li><a href="javascript:void(0);" data-bind="click: dblClick"><i class="fa fa-fw fa-paste"></i> ${I18n(
+          'Insert at cursor'
+        )}</a></li>
+        <!-- /ko -->
+        <!-- ko if: !window.IS_EMBEDDED && catalogEntry.path.length <=2 -->
+        <li><a href="javascript:void(0);" data-bind="click: openInMetastore"><i class="fa fa-fw fa-table"></i> ${I18n(
+          'Open in Browser'
+        )}</a></li>
+        <!-- /ko -->
+        <!-- ko if: catalogEntry.isTableOrView() -->
+        <li><a href="javascript:void(0);" data-bind="click: function() { huePubSub.publish('query.and.watch', {'url': '/notebook/browse/' + databaseName + '/' + tableName + '/', sourceType: sourceType}); }"><i class="fa fa-fw fa-code"></i> ${I18n(
+          'Open in Editor'
+        )}</a></li>
+        <!-- ko if: window.HAS_SQL_DASHBOARD -->
+        <li><a href="javascript: void(0);" data-bind="click: function() { explore(false); }"><!-- ko template: { name: 'app-icon-template', data: { icon: 'dashboard' } } --><!-- /ko --> ${I18n(
+          'Open in Dashboard'
+        )}</a></li>
+        <!-- /ko -->
+        <!-- /ko -->
+        <!-- ko if: window.ENABLE_QUERY_BUILDER && catalogEntry.isColumn() && $currentApp() === 'editor' -->
+        <li class="divider"></li>
+        <!-- ko template: { name: 'query-builder-context-items' } --><!-- /ko -->
+        <!-- /ko -->
+      <!-- /ko -->
+      <!-- /ko -->
+    <!-- /ko -->
+  </script>
+
+  <script type="text/html" id="query-builder-context-items">
+    <li data-bind="contextSubMenu: '.hue-context-sub-menu'">
+      <a href="javascript:void(0);"><i class="fa fa-fw fa-magic"></i> ${I18n(
+        'Project'
+      )}<i class="sub-icon fa fa-fw fa-chevron-right"></i></a>
+      <ul class="hue-context-menu hue-context-sub-menu">
+        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Select', 'Project', '{0}', false, false); }">Select</a></li>
+        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Select distinct', 'Project', '{0}', false, false); }">Select distinct</a></li>
+      </ul>
+    </li>
+
+    <li data-bind="contextSubMenu: '.hue-context-sub-menu'">
+      <a href="javascript:void(0);"><i class="fa fa-fw fa-magic"></i> ${I18n(
+        'Aggregate'
+      )}<i class="sub-icon fa fa-fw fa-chevron-right"></i></a>
+      <ul class="hue-context-menu hue-context-sub-menu">
+        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Count', 'Aggregate', 'COUNT({0}.{1}) as count_{1}', false, false); }">Count</a></li>
+        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Count distinct', 'Aggregate', 'COUNT(DISTINCT {0}.{1}) as distinct_count_{1}', false, false); }">Count distinct</a></li>
+        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Sum', 'Aggregate', 'SUM({0}.{1}) as sum_{1}', false, false); }">Sum</a></li>
+        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Minimum', 'Aggregate', 'MIN({0}.{1}) as min_{1}', false, false); }">Minimum</a></li>
+        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Maximum', 'Aggregate', 'MAX({0}.{1}) as max_{1}', false, false); }">Maximum</a></li>
+        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Average', 'Aggregate', 'AVG({0}.{1}) as avg_{1}', false, false); }">Average</a></li>
+      </ul>
+    </li>
+
+    <li data-bind="contextSubMenu: '.hue-context-sub-menu'">
+      <a href="javascript:void(0);"><i class="fa fa-fw fa-magic"></i> ${I18n(
+        'Filter'
+      )}<i class="sub-icon fa fa-fw fa-chevron-right"></i></a>
+      <ul class="hue-context-menu hue-context-sub-menu">
+        <li><a href="javascript:void(0);" data-bind="click: function () { var isNotNullRule = QueryBuilder.getRule(databaseName, tableName, columnName, 'Is not null'); if (isNotNullRule.length) { isNotNullRule.attr('rule', 'Is null'); isNotNullRule.find('.rule').text('Is null'); } else { QueryBuilder.addRule(databaseName, tableName, columnName, 'Is null', 'Filter', '{0}.{1} = null', false, false); } }">Is null</a></li>
+        <li><a href="javascript:void(0);" data-bind="click: function () { var isNullRule = QueryBuilder.getRule(databaseName, tableName, columnName, 'Is null'); if (isNullRule.length) { isNullRule.attr('rule', 'Is not null'); isNullRule.find('.rule').text('Is not null'); } else { QueryBuilder.addRule(databaseName, tableName, columnName, 'Is not null', 'Filter', '{0}.{1} != null', false, false); } }">Is not null</a></li>
+        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Equal to', 'Filter', '{0}.{1} = {2}', true, true); }">Equal to</a></li>
+        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Not equal to', 'Filter', '{0}.{1} != {2}', true, true); }">Not equal to</a></li>
+        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Greater than', 'Filter', '{0}.{1} > {2}', true, false) }">Greater than</a></li>
+        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Less than', 'Filter', '{0}.{1} < {2}', true, false); }">Less than</a></li>
+      </ul>
+    </li>
+    <li data-bind="contextSubMenu: '.hue-context-sub-menu'">
+      <a href="javascript:void(0);"><i class="fa fa-fw fa-magic"></i> ${I18n(
+        'Order'
+      )}<i class="sub-icon fa fa-fw fa-chevron-right"></i></a>
+      <ul class="hue-context-menu hue-context-sub-menu">
+        <li><a href="javascript:void(0);" data-bind="click: function () { var descendingRule = QueryBuilder.getRule(databaseName, tableName, columnName, 'Descending'); if (descendingRule.length) { descendingRule.attr('rule', 'Ascending'); descendingRule.find('.rule').text('Ascending'); } else { QueryBuilder.addRule(databaseName, tableName, columnName, 'Ascending', 'Order', '{0}.{1} ASC', false, false); } }">Ascending</a></li>
+        <li><a href="javascript:void(0);" data-bind="click: function () { var ascendingRule = QueryBuilder.getRule(databaseName, tableName, columnName, 'Ascending'); if (ascendingRule.length) { ascendingRule.attr('rule', 'Descending'); ascendingRule.find('.rule').text('Descending'); } else { QueryBuilder.addRule(databaseName, tableName, columnName, 'Descending', 'Order', '{0}.{1} DESC', false, false); } }">Descending</a></li>
+      </ul>
+    </li>
+  </script>
+
+  <script type="text/html" id="assist-database-entry">
+    <li class="assist-table" data-bind="appAwareTemplateContextMenu: { template: 'sql-context-items', scrollContainer: '.assist-db-scrollable' }, visibleOnHover: { selector: '.database-actions' }">
+      <!-- ko template: { name: 'assist-database-actions' } --><!-- /ko -->
+      <a class="assist-table-link" href="javascript: void(0);" data-bind="click: function () { $parent.selectedDatabase($data); $parent.selectedDatabaseChanged(); }, attr: {'title': catalogEntry.getTitle(true) }, draggableText: { text: editorText,  meta: {'type': 'sql', 'database': databaseName} }"><i class="fa fa-fw fa-database muted valign-middle"></i> <span class="highlightable" data-bind="text: catalogEntry.name, css: { 'highlight': highlight() }"></span></a>
+    </li>
+  </script>
+
+  <script type="text/html" id="assist-table-entry">
+    <li class="assist-table" data-bind="appAwareTemplateContextMenu: { template: 'sql-context-items', scrollContainer: '.assist-db-scrollable' }, visibleOnHover: { override: statsVisible() || navigationSettings.rightAssist, selector: '.table-actions' }">
+      <div class="assist-actions table-actions" data-bind="css: { 'assist-actions-left': navigationSettings.rightAssist }" style="opacity: 0">
+        <a class="inactive-action" href="javascript:void(0)" data-bind="visible: navigationSettings.showStats, click: showContextPopover, css: { 'blue': statsVisible }"><i class="fa fa-fw fa-info" title="${I18n(
+          'Show details'
+        )}"></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="${I18n(
+          'Open'
+        )}"></i></a>
+      </div>
+      <a class="assist-entry assist-table-link" href="javascript:void(0)" data-bind="click: toggleOpen, attr: {'title': catalogEntry.getTitle(true) }, draggableText: { text: editorText,  meta: {'type': 'sql', 'isView': catalogEntry.isView(), 'table': tableName, 'database': databaseName} }">
+        <i class="fa fa-fw muted valign-middle" data-bind="css: iconClass"></i>
+        <span class="highlightable" data-bind="text: catalogEntry.getDisplayName(navigationSettings.rightAssist), css: { 'highlight': highlight }"></span>
+      </a>
+      <div class="center assist-spinner" data-bind="visible: loading() && open()"><i class="fa fa-spinner fa-spin"></i></div>
+      <!-- ko template: { if: open, name: 'assist-db-entries'  } --><!-- /ko -->
+    </li>
+  </script>
+
+  <script type="text/html" id="assist-column-entry">
+    <li data-bind="appAwareTemplateContextMenu: { template: 'sql-context-items', scrollContainer: '.assist-db-scrollable' }, visible: ! hasErrors(), visibleOnHover: { childrenOnly: true, override: statsVisible, selector: catalogEntry.isView() ? '.table-actions' : '.column-actions' }, css: { 'assist-table': catalogEntry.isView(), 'assist-column': catalogEntry.isField() }">
+      <div class="assist-actions column-actions" data-bind="css: { 'assist-actions-left': navigationSettings.rightAssist }" style="opacity: 0">
+        <a class="inactive-action" href="javascript:void(0)" data-bind="visible: navigationSettings.showStats, click: showContextPopover, css: { 'blue': statsVisible }"><i class="fa fa-fw fa-info" title="${I18n(
+          'Show details'
+        )}"></i></a>
+      </div>
+      <!-- ko if: expandable -->
+      <a class="assist-entry assist-field-link" href="javascript:void(0)" data-bind="click: toggleOpen, attr: {'title': catalogEntry.getTitle(true) }, css: { 'assist-entry-left-action': navigationSettings.rightAssist }">
+        <span class="highlightable" data-bind="css: { 'highlight': highlight}, attr: {'column': columnName, 'table': tableName, 'database': databaseName }, text: catalogEntry.getDisplayName(), draggableText: { text: editorText, meta: {'type': 'sql', 'column': columnName, 'table': tableName, 'database': databaseName } }"></span><!-- ko if: catalogEntry.isPrimaryKey() --> <i class="fa fa-key"></i><!-- /ko -->
+      </a>
+      <!-- /ko -->
+      <!-- ko ifnot: expandable -->
+      <div class="assist-entry assist-field-link default-cursor" href="javascript:void(0)" data-bind="event: { dblclick: dblClick }, attr: {'title': catalogEntry.getTitle(true) }, css: { 'assist-entry-left-action': navigationSettings.rightAssist }">
+        <span class="highlightable" data-bind="css: { 'highlight': highlight}, attr: {'column': columnName, 'table': tableName, 'database': databaseName}, text: catalogEntry.getDisplayName(), draggableText: { text: editorText, meta: {'type': 'sql', 'column': columnName, 'table': tableName, 'database': databaseName} }"></span><!-- ko if: catalogEntry.isPrimaryKey()  --> <i class="fa fa-key"></i><!-- /ko -->
+      </div>
+      <!-- /ko -->
+      <div class="center assist-spinner" data-bind="visible: loading"><i class="fa fa-spinner fa-spin"></i></div>
+      <!-- ko template: { if: open, name: 'assist-db-entries'  } --><!-- /ko -->
+    </li>
+  </script>
+
+  <script type="text/html" id="assist-column-entry-assistant">
+    <li data-bind="appAwareTemplateContextMenu: { template: 'sql-context-items', scrollContainer: '.assist-db-scrollable' }, visible: ! hasErrors(), visibleOnHover: { childrenOnly: true, override: statsVisible, selector: catalogEntry.isView() ? '.table-actions' : '.column-actions' }, css: { 'assist-table': catalogEntry.isView(), 'assist-column': catalogEntry.isField() }">
+      <div class="assist-actions column-actions assist-actions-left" style="opacity: 0">
+        <a class="inactive-action" href="javascript:void(0)" data-bind="visible: navigationSettings.showStats, click: showContextPopover, css: { 'blue': statsVisible }"><i class="fa fa-fw fa-info" title="${I18n(
+          'Show details'
+        )}"></i></a>
+      </div>
+      <!-- ko if: expandable -->
+      <a class="assist-entry assist-field-link assist-field-link-dark assist-entry-left-action assist-ellipsis" href="javascript:void(0)" data-bind="click: toggleOpen, attr: {'title': catalogEntry.getTitle(true) }">
+        <span data-bind="text: catalogEntry.getType()" class="muted pull-right margin-right-20"></span>
+        <span class="highlightable" data-bind="css: { 'highlight': highlight}, attr: {'column': columnName, 'table': tableName, 'database': databaseName }, text: catalogEntry.name, draggableText: { text: editorText, meta: {'type': 'sql', 'column': columnName, 'table': tableName, 'database': databaseName } }"></span><!-- ko if: catalogEntry.isPrimaryKey() --> <i class="fa fa-key"></i><!-- /ko -->
+      </a>
+      <!-- /ko -->
+      <!-- ko ifnot: expandable -->
+      <div class="assist-entry assist-field-link assist-field-link-dark default-cursor assist-ellipsis" href="javascript:void(0)" data-bind="event: { dblclick: dblClick }, attr: {'title': catalogEntry.getTitle(true) }, css: { 'assist-entry-left-action': navigationSettings.rightAssist }">
+        <span data-bind="text: catalogEntry.getType()" class="muted pull-right margin-right-20"></span>
+        <span class="highlightable" data-bind="css: { 'highlight': highlight}, attr: {'column': columnName, 'table': tableName, 'database': databaseName}, text: catalogEntry.name, draggableText: { text: editorText, meta: {'type': 'sql', 'column': columnName, 'table': tableName, 'database': databaseName} }"></span><!-- ko if: catalogEntry.isPrimaryKey() --> <i class="fa fa-key"></i><!-- /ko -->
+      </div>
+      <!-- /ko -->
+      <div class="center assist-spinner" data-bind="visible: loading"><i class="fa fa-spinner fa-spin"></i></div>
+      <!-- ko template: { if: open, name: 'assist-db-entries'  } --><!-- /ko -->
+    </li>
+  </script>
+
+  <script type="text/html" id="assist-db-entries">
+    <!-- ko if: ! hasErrors() && hasEntries() && ! loading() && filteredEntries().length == 0 -->
+    <ul class="assist-tables">
+      <li class="assist-entry assist-no-entries"><!-- ko if: catalogEntry.isTableOrView() -->${I18n(
+        'No columns found'
+      )}<!--/ko--><!-- ko if: catalogEntry.isDatabase() -->${I18n(
+  'No tables found'
+)}<!--/ko--><!-- ko if: catalogEntry.isField() -->${I18n('No results found')}<!--/ko--></li>
+    </ul>
+    <!-- /ko -->
+    <!-- ko if: ! hasErrors() && hasEntries() && ! loading() && filteredEntries().length > 0 -->
+    <ul class="database-tree" data-bind="foreachVisible: { data: filteredEntries, minHeight: navigationSettings.rightAssist ? 22 : 23, container: '.assist-db-scrollable', skipScrollEvent: navigationSettings.rightAssist, usePreloadBackground: true }, css: { 'assist-tables': catalogEntry.isDatabase() }">
+      <!-- ko template: { if: catalogEntry.isTableOrView(), name: 'assist-table-entry' } --><!-- /ko -->
+      <!-- ko if: navigationSettings.rightAssist -->
+        <!-- ko template: { ifnot: catalogEntry.isTableOrView(), name: 'assist-column-entry-assistant' } --><!-- /ko -->
+      <!-- /ko -->
+      <!-- ko ifnot: navigationSettings.rightAssist -->
+        <!-- ko template: { ifnot: catalogEntry.isTableOrView(), name: 'assist-column-entry' } --><!-- /ko -->
+      <!-- /ko -->
+    </ul>
+    <!-- /ko -->
+    <!-- ko template: { if: ! hasErrors() && ! hasEntries() && ! loading() && (catalogEntry.isTableOrView()), name: 'assist-no-table-entries' } --><!-- /ko -->
+    <!-- ko template: { if: ! hasErrors() && ! hasEntries() && ! loading() && catalogEntry.isDatabase(), name: 'assist-no-database-entries' } --><!-- /ko -->
+    <!-- ko if: hasErrors -->
+    <ul class="assist-tables">
+      <!-- ko if: catalogEntry.isTableOrView() -->
+      <li class="assist-errors">${I18n('Error loading columns.')}</li>
+      <!-- /ko -->
+      <!-- ko if: catalogEntry.isField() -->
+      <li class="assist-errors">${I18n('Error loading fields.')}</li>
+      <!-- /ko -->
+    </ul>
+    <!-- /ko -->
+  </script>
+
+  <script type="text/html" id="assist-db-breadcrumb">
+    <div class="assist-flex-header assist-breadcrumb">
+      <!-- ko if: selectedSource() -->
+      <!-- ko if: selectedSource().selectedNamespace() -->
+      <!-- ko if: selectedSource().selectedNamespace().selectedDatabase() -->
+      <a data-bind="click: back, appAwareTemplateContextMenu: { template: 'sql-context-items', viewModel: selectedSource().selectedNamespace().selectedDatabase() }">
+        <i class="fa fa-chevron-left assist-breadcrumb-back" ></i>
+        <i class="fa assist-breadcrumb-text" data-bind="css: { 'fa-server': nonSqlType, 'fa-database': !nonSqlType }"></i>
+        <span class="assist-breadcrumb-text" data-bind="text: breadcrumb, attr: {'title': breadcrumb() +  (nonSqlType ? '' : ' (' + selectedSource().sourceType + ' ' + selectedSource().selectedNamespace().name + ')') }"></span>
+      </a>
+      <!-- /ko -->
+      <!-- ko ifnot: selectedSource().selectedNamespace().selectedDatabase() -->
+      <!-- ko if: window.HAS_MULTI_CLUSTER-->
+      <a data-bind="click: back">
+        <i class="fa fa-chevron-left assist-breadcrumb-back"></i>
+        <i class="fa fa-snowflake-o assist-breadcrumb-text"></i>
+        <span class="assist-breadcrumb-text" data-bind="text: breadcrumb, attr: {'title': breadcrumb() + ' (' + selectedSource().sourceType + ')' }"></span>
+      </a>
+      <!-- /ko -->
+      <!-- ko ifnot: window.HAS_MULTI_CLUSTER -->
+      <a data-bind="click: back">
+        <i class="fa fa-chevron-left assist-breadcrumb-back"></i>
+        <i class="fa fa-server assist-breadcrumb-text"></i>
+        <span class="assist-breadcrumb-text" data-bind="text: breadcrumb"></span>
+      </a>
+      <!-- /ko -->
+      <!-- /ko -->
+      <!-- /ko -->
+      <!-- ko ifnot: selectedSource().selectedNamespace() -->
+      <a data-bind="click: back">
+        <i class="fa fa-chevron-left assist-breadcrumb-back"></i>
+        <i class="fa fa-server assist-breadcrumb-text"></i>
+        <span class="assist-breadcrumb-text" data-bind="text: breadcrumb"></span>
+      </a>
+      <!-- /ko -->
+      <!-- /ko -->
+    </div>
+  </script>
+
+  <script type="text/html" id="assist-sql-inner-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: selectedNamespace, name: 'assist-namespaces-template' } --><!-- /ko -->
+      <!-- ko with: selectedNamespace -->
+        <!-- ko template: { ifnot: selectedDatabase, name: 'assist-databases-template' } --><!-- /ko -->
+        <!-- ko with: selectedDatabase -->
+          <!-- ko template: { name: 'assist-tables-template' } --><!-- /ko -->
+        <!-- /ko -->
+      <!-- /ko -->
+    <!-- /ko -->
+  </script>
+
+  <script type="text/html" id="ask-for-invalidate-title">
+    &nbsp;<a class="pull-right pointer close-popover inactive-action">&times;</a>
+  </script>
+
+  <script type="text/html" id="ask-for-invalidate-content">
+    <label class="radio">
+      <input type="radio" name="refreshImpala" value="cache" data-bind="checked: invalidateOnRefresh" />
+      ${I18n('Clear cache')}
+    </label>
+    <label class="radio">
+      <input type="radio" name="refreshImpala" value="invalidate" data-bind="checked: invalidateOnRefresh" />
+      ${I18n('Perform incremental metadata update.')}
+    </label>
+    <div class="assist-invalidate-description">${I18n('This will sync missing tables.')}</div>
+    <label class="radio">
+      <input type="radio" name="refreshImpala" value="invalidateAndFlush" data-bind="checked: invalidateOnRefresh"  />
+      ${I18n('Invalidate all metadata and rebuild index.')}
+    </label>
+    <div class="assist-invalidate-description">${I18n(
+      'WARNING: This can be both resource and time-intensive.'
+    )}</div>
+    <div style="width: 100%; display: inline-block; margin-top: 5px;"><button class="pull-right btn btn-primary" data-bind="css: { 'btn-primary': invalidateOnRefresh() !== 'invalidateAndFlush', 'btn-danger': invalidateOnRefresh() === 'invalidateAndFlush' }, click: function (data, event) { huePubSub.publish('close.popover'); triggerRefresh(data, event); }, clickBubble: false">${I18n(
+      'Refresh'
+    )}</button></div>
+  </script>
+
+  <script type="text/html" id="assist-namespace-header-actions">
+    <div class="assist-db-header-actions">
+      <!-- ko ifnot: loading -->
+      <span class="assist-tables-counter">(<span data-bind="text: filteredNamespaces().length"></span>)</span>
+      <!-- ko if: window.HAS_MULTI_CLUSTER -->
+      <a class="inactive-action" href="javascript:void(0)" data-bind="click: triggerRefresh"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }" title="${I18n(
+        'Refresh'
+      )}"></i></a>
+      <!-- /ko -->
+      <!-- /ko -->
+      <!-- ko if: loading -->
+      <i class="fa fa-refresh fa-spin blue" title="${I18n('Refresh')}"></i>
+      <!-- /ko -->
+    </div>
+  </script>
+
+  <script type="text/html" id="assist-db-header-actions">
+    <div class="assist-db-header-actions">
+      <!-- ko ifnot: loading -->
+      <span class="assist-tables-counter">(<span data-bind="text: filteredEntries().length"></span>)</span>
+      <!-- ko if: window.ENABLE_NEW_CREATE_TABLE && !window.IS_EMBEDDED && (sourceType === 'hive' || sourceType === 'impala') -->
+      <!-- ko if: typeof databaseName !== 'undefined' -->
+        <a class="inactive-action" data-bind="hueLink: window.HUE_URLS.IMPORTER_CREATE_TABLE + databaseName + '/?sourceType=' + sourceType + '&namespace=' + assistDbNamespace.namespace.id" title="${I18n(
+          'Create table'
+        )}" href="javascript:void(0)">
+          <i class="pointer fa fa-plus" title="${I18n('Create table')}"></i>
+        </a>
+      <!-- /ko -->
+      <!-- ko if: typeof databases !== 'undefined' -->
+        <a class="inactive-action" data-bind="hueLink: window.HUE_URLS.IMPORTER_CREATE_DATABASE + '/?sourceType=' + sourceType + '&namespace=' + namespace.id" href="javascript:void(0)">
+          <i class="pointer fa fa-plus" title="${I18n('Create database')}"></i>
+        </a>
+      <!-- /ko -->
+      <!-- /ko -->
+      <!-- ko if: sourceType === 'solr' -->
+      <a class="inactive-action" data-bind="hueLink: '/indexer/importer/prefill/all/index/'" title="${I18n(
+        'Create index'
+      )}">
+        <i class="pointer fa fa-plus" title="${I18n('Create index')}"></i>
+      </a>
+      <!-- /ko -->
+      <!-- ko if: sourceType === 'impala' -->
+      <a class="inactive-action" href="javascript:void(0)" data-bind="templatePopover : { contentTemplate: 'ask-for-invalidate-content', titleTemplate: 'ask-for-invalidate-title', trigger: 'click', minWidth: '320px' }"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }" title="${I18n(
+        'Refresh'
+      )}"></i></a>
+      <!-- /ko -->
+      <!-- ko if: sourceType !== 'impala' -->
+      <a class="inactive-action" href="javascript:void(0)" data-bind="click: triggerRefresh"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }" title="${I18n(
+        'Refresh'
+      )}"></i></a>
+      <!-- /ko -->
+      <!-- /ko -->
+      <!-- ko if: loading -->
+      <i class="fa fa-refresh fa-spin blue" title="${I18n('Refresh')}"></i>
+      <!-- /ko -->
+    </div>
+  </script>
+
+  <script type="text/html" id="assist-sources-template">
+    <div class="assist-flex-header">
+      <div class="assist-inner-header">
+        ${I18n('Sources')}
+      </div>
+    </div>
+    <div class="assist-flex-fill">
+      <ul class="assist-tables" data-bind="foreach: sources">
+        <li class="assist-table">
+          <a class="assist-table-link" href="javascript: void(0);" data-bind="click: function () { $parent.selectedSource($data); }"><i class="fa fa-fw fa-server muted valign-middle"></i> <span data-bind="text: name"></span></a>
+        </li>
+      </ul>
+    </div>
+  </script>
+
+  <script type="text/html" id="assist-namespaces-template">
+    <div class="assist-flex-header">
+      <div class="assist-inner-header">
+        ${I18n('Namespaces')}
+        <!-- ko template: 'assist-namespace-header-actions' --><!-- /ko -->
+      </div>
+    </div>
+    <div class="assist-flex-search" data-bind="visible: hasNamespaces() && !hasErrors()">
+      <div class="assist-filter">
+        <!-- ko component: {
+            name: 'inline-autocomplete',
+            params: {
+              querySpec: filter.querySpec,
+              facets: [],
+              knownFacetValues: {},
+              autocompleteFromEntries: autocompleteFromNamespaces
+            }
+          } --><!-- /ko -->
+      </div>
+    </div>
+    <div class="assist-flex-fill assist-db-scrollable" data-bind="visible: !hasErrors() && !loading() && hasNamespaces(), delayedOverflow">
+      <!-- ko if: !loading() && filteredNamespaces().length == 0 -->
+      <ul class="assist-tables">
+        <li class="assist-entry no-entries">${I18n('No results found')}</li>
+      </ul>
+      <!-- /ko -->
+      <ul class="assist-tables" data-bind="foreach: filteredNamespaces">
+        <li class="assist-table">
+          <!-- ko if: status() === 'STARTING' -->
+          <span class="assist-table-link" title="${I18n(
+            'Starting'
+          )}" data-bind="tooltip: { placement: 'bottom' }"><i class="fa fa-fw fa-spinner fa-spin muted valign-middle"></i> <span data-bind="text: name"></span></span>
+          <!-- /ko -->
+          <!-- ko if: status() !== 'STARTING' -->
+          <!-- ko if: namespace.computes.length -->
+          <a class="assist-table-link" href="javascript: void(0);" data-bind="click: function () { $parent.selectedNamespace($data); }"><i class="fa fa-fw fa-snowflake-o muted valign-middle"></i> <span data-bind="text: name"></span></a>
+          <!-- /ko -->
+          <!-- ko ifnot: namespace.computes.length -->
+          <span class="assist-table-link" title="${I18n(
+            'No related computes'
+          )}" data-bind="tooltip: { placement: 'bottom' }"><i class="fa fa-fw fa-warning muted valign-middle"></i> <span data-bind="text: name"></span></span>
+          <!-- /ko -->
+          <!-- /ko -->
+        </li>
+      </ul>
+    </div>
+    <div class="assist-flex-fill" data-bind="visible: loading">
+      <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
+    </div>
+    <div class="assist-flex-fill" data-bind="visible: hasErrors() && !loading()">
+      <span class="assist-errors">${I18n('Error loading namespaces.')}</span>
+    </div>
+    <div class="assist-flex-fill" data-bind="visible: !hasErrors() && !loading() && !hasNamespaces()">
+      <span class="assist-errors">${I18n('No namespaces found.')}</span>
+    </div>
+  </script>
+
+  <script type="text/html" id="assist-databases-template">
+    <div class="assist-flex-header" data-bind="visibleOnHover: { selector: '.hover-actions', override: loading() }">
+      <div class="assist-inner-header">
+        <!-- ko ifnot: sourceType === 'solr' || sourceType === 'kafka' -->
+        ${I18n('Databases')}
+        <!-- ko template: 'assist-db-header-actions' --><!-- /ko -->
+        <!-- /ko -->
+        <!-- ko if: sourceType === 'solr' || sourceType === 'kafka'-->
+        ${I18n('Sources')}
+        <!-- /ko -->
+      </div>
+    </div>
+    <div class="assist-flex-search" data-bind="visible: hasEntries() && ! hasErrors()">
+      <div class="assist-filter">
+        <!-- ko component: {
+            name: 'inline-autocomplete',
+            params: {
+              querySpec: filter.querySpec,
+              facets: [],
+              placeHolder: sourceType === 'solr' || sourceType === 'kafka' ? '${I18n(
+                'Filter sources...'
+              )}' : '${I18n('Filter databases...')}',
+              knownFacetValues: {},
+              autocompleteFromEntries: autocompleteFromEntries
+            }
+          } --><!-- /ko -->
+      </div>
+    </div>
+    <div class="assist-flex-fill assist-db-scrollable" data-bind="visible: ! hasErrors() && ! loading() && hasEntries(), delayedOverflow">
+      <!-- ko if: ! loading() && filteredEntries().length == 0 -->
+      <ul class="assist-tables">
+        <li class="assist-entry no-entries">${I18n('No results found')}</li>
+      </ul>
+      <!-- /ko -->
+      <ul class="assist-tables" data-bind="foreachVisible: {data: filteredEntries, minHeight: 23, container: '.assist-db-scrollable', skipScrollEvent: navigationSettings.rightAssist }">
+        <!-- ko template: { name: 'assist-database-entry' } --><!-- /ko -->
+      </ul>
+    </div>
+    <div class="assist-flex-fill" data-bind="visible: loading">
+      <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
+    </div>
+    <div class="assist-flex-fill" data-bind="visible: hasErrors() && ! loading()">
+      <span class="assist-errors">${I18n('Error loading databases.')}</span>
+    </div>
+    <div class="assist-flex-fill" data-bind="visible: ! hasErrors() && ! loading() && ! hasEntries()">
+      <span class="assist-errors">${I18n('No databases found.')}</span>
+    </div>
+  </script>
+
+  <script type="text/html" id="assist-tables-template">
+    <div class="assist-flex-header">
+      <div class="assist-inner-header" data-bind="visible: !$parent.loading() && !$parent.hasErrors()">
+        <!-- ko ifnot: sourceType === 'solr' || sourceType === 'kafka' -->
+          ${I18n('Tables')}
+        <!-- /ko -->
+        <!-- ko if: sourceType === 'solr' -->
+          <div data-bind="appAwareTemplateContextMenu: { template: 'collection-title-context-items', scrollContainer: '.assist-db-scrollable' }">${I18n(
+            'Indexes'
+          )}</div>
+        <!-- /ko -->
+        <!-- ko if: sourceType === 'kafka' -->
+          ${I18n('Topics')}
+        <!-- /ko -->
+        <!-- ko template: 'assist-db-header-actions' --><!-- /ko -->
+      </div>
+    </div>
+    <div class="assist-flex-search" data-bind="visible: hasEntries() && !$parent.loading() && !$parent.hasErrors()">
+      <div class="assist-filter">
+        <!-- ko component: {
+          name: 'inline-autocomplete',
+          params: {
+            querySpec: filter.querySpec,
+            facets: ['type'],
+            knownFacetValues: knownFacetValues.bind($data),
+            autocompleteFromEntries: autocompleteFromEntries
+          }
+        } --><!-- /ko -->
+      </div>
+    </div>
+    <div class="assist-flex-fill assist-db-scrollable" data-bind="visible: ! hasErrors() && ! loading(), delayedOverflow">
+      <!-- ko template: 'assist-db-entries' --><!-- /ko -->
+    </div>
+    <div class="assist-flex-fill" data-bind="visible: loading() || $parent.loading()">
+      <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
+    </div>
+    <div class="assist-flex-fill" data-bind="visible: hasErrors() && ! loading()">
+      <span class="assist-errors">${I18n('Error loading tables.')}</span>
+    </div>
+  </script>
+  
+  <!-- ko template: 'assist-sql-inner-panel' --><!-- /ko -->
+`;
+
+class AssistDbPanel {
+  /**
+   * @param {Object} options
+   * @param {Object} options.i18n
+   * @param {Object[]} options.sourceTypes - All the available SQL source types
+   * @param {string} options.sourceTypes[].name - Example: Hive SQL
+   * @param {string} options.sourceTypes[].type - Example: hive
+   * @param {string} [options.activeSourceType] - Example: hive
+   * @param {Object} options.navigationSettings - enable/disable the links
+   * @param {boolean} options.navigationSettings.openItem
+   * @param {boolean} options.navigationSettings.showStats
+   * @param {boolean} options.navigationSettings.pinEnabled
+   * @param {boolean} [options.isSolr] - Default false;
+   * @param {boolean} [options.isStreams] - Default false;
+   * @constructor
+   **/
+  constructor(options) {
+    const self = this;
+    self.options = options;
+    self.i18n = options.i18n;
+    self.initialized = false;
+    self.initalizing = false;
+
+    self.isStreams = options.isStreams;
+    self.isSolr = options.isSolr;
+    self.nonSqlType = self.isSolr || self.isStreams;
+
+    if (typeof options.sourceTypes === 'undefined') {
+      options.sourceTypes = [];
+
+      if (self.isSolr) {
+        options.sourceTypes = [{ type: 'solr', name: 'solr' }];
+      } else if (self.isStreams) {
+        options.sourceTypes = [{ type: 'kafka', name: 'kafka' }];
+      } else {
+        window.ASSIST_SQL_INTERPRETERS.forEach(interpreter => {
+          options.sourceTypes.push(interpreter);
+        });
+      }
+    }
+
+    self.sources = ko.observableArray();
+    self.sourceIndex = {};
+    self.selectedSource = ko.observable(null);
+
+    options.sourceTypes.forEach(sourceType => {
+      self.sourceIndex[sourceType.type] = new AssistDbSource({
+        i18n: self.i18n,
+        type: sourceType.type,
+        name: sourceType.name,
+        nonSqlType: sourceType.type === 'solr' || sourceType.type === 'kafka',
+        navigationSettings: options.navigationSettings
+      });
+      self.sources.push(self.sourceIndex[sourceType.type]);
+    });
+
+    if (self.sources().length === 1) {
+      self.selectedSource(self.sources()[0]);
+    }
+
+    if (self.sourceIndex['solr']) {
+      huePubSub.subscribe('assist.collections.refresh', () => {
+        const solrSource = self.sourceIndex['solr'];
+        const doRefresh = () => {
+          if (solrSource.selectedNamespace()) {
+            const assistDbNamespace = solrSource.selectedNamespace();
+            dataCatalog
+              .getEntry({
+                sourceType: 'solr',
+                namespace: assistDbNamespace.namespace,
+                compute: assistDbNamespace.compute(),
+                path: []
+              })
+              .done(entry => {
+                entry.clearCache({ cascade: true });
+              });
+          }
+        };
+        if (!solrSource.hasNamespaces()) {
+          solrSource.loadNamespaces(true).done(doRefresh);
+        } else {
+          doRefresh();
+        }
+      });
+    }
+
+    huePubSub.subscribe('assist.db.highlight', catalogEntry => {
+      huePubSub.publish('left.assist.show');
+      if (catalogEntry.getSourceType() === 'solr') {
+        huePubSub.publish('assist.show.solr');
+      } else {
+        huePubSub.publish('assist.show.sql');
+      }
+      huePubSub.publish('context.popover.hide');
+      window.setTimeout(() => {
+        let foundSource;
+        self.sources().some(source => {
+          if (source.sourceType === catalogEntry.getSourceType()) {
+            foundSource = source;
+            return true;
+          }
+        });
+        if (foundSource) {
+          if (self.selectedSource() !== foundSource) {
+            self.selectedSource(foundSource);
+          }
+          foundSource.highlightInside(catalogEntry);
+        }
+      }, 0);
+    });
+
+    if (!self.isSolr && !self.isStreams) {
+      huePubSub.subscribe('assist.set.database', databaseDef => {
+        if (!databaseDef.source || !self.sourceIndex[databaseDef.source]) {
+          return;
+        }
+        self.selectedSource(self.sourceIndex[databaseDef.source]);
+        self.setDatabaseWhenLoaded(databaseDef.namespace, databaseDef.name);
+      });
+
+      const getSelectedDatabase = source => {
+        const deferred = $.Deferred();
+        const assistDbSource = self.sourceIndex[source];
+        if (assistDbSource) {
+          assistDbSource.loadedDeferred.done(() => {
+            if (assistDbSource.selectedNamespace()) {
+              assistDbSource.selectedNamespace().loadedDeferred.done(() => {
+                if (assistDbSource.selectedNamespace().selectedDatabase()) {
+                  deferred.resolve({
+                    sourceType: source,
+                    namespace: assistDbSource.selectedNamespace().namespace,
+                    name: assistDbSource.selectedNamespace().selectedDatabase().name
+                  });
+                } else {
+                  const lastSelectedDb = apiHelper.getFromTotalStorage(
+                    'assist_' + source + '_' + assistDbSource.selectedNamespace().namespace.id,
+                    'lastSelectedDb',
+                    'default'
+                  );
+                  deferred.resolve({
+                    sourceType: source,
+                    namespace: assistDbSource.selectedNamespace().namespace,
+                    name: lastSelectedDb
+                  });
+                }
+              });
+            } else {
+              deferred.resolve({
+                sourceType: source,
+                namespace: { id: 'default' },
+                name: 'default'
+              });
+            }
+          });
+        } else {
+          deferred.reject();
+        }
+
+        return deferred;
+      };
+
+      huePubSub.subscribe('assist.get.database', source => {
+        getSelectedDatabase(source).done(databaseDef => {
+          huePubSub.publish('assist.database.set', databaseDef);
+        });
+      });
+
+      huePubSub.subscribe('assist.get.database.callback', options => {
+        getSelectedDatabase(options.source).done(options.callback);
+      });
+
+      huePubSub.subscribe('assist.get.source', () => {
+        huePubSub.publish(
+          'assist.source.set',
+          self.selectedSource() ? self.selectedSource().sourceType : undefined
+        );
+      });
+
+      huePubSub.subscribe('assist.set.source', source => {
+        if (self.sourceIndex[source]) {
+          self.selectedSource(self.sourceIndex[source]);
+        }
+      });
+
+      huePubSub.publish('assist.db.panel.ready');
+
+      huePubSub.subscribe('assist.is.db.panel.ready', () => {
+        huePubSub.publish('assist.db.panel.ready');
+      });
+
+      self.selectedSource.subscribe(newSource => {
+        if (newSource) {
+          if (newSource.namespaces().length === 0) {
+            newSource.loadNamespaces();
+          }
+          apiHelper.setInTotalStorage('assist', 'lastSelectedSource', newSource.sourceType);
+        } else {
+          apiHelper.setInTotalStorage('assist', 'lastSelectedSource');
+        }
+      });
+    }
+
+    if (self.isSolr || self.isStreams) {
+      if (self.sources().length === 1) {
+        self.selectedSource(self.sources()[0]);
+        self
+          .selectedSource()
+          .loadNamespaces()
+          .done(() => {
+            const solrNamespace = self.selectedSource().selectedNamespace();
+            if (solrNamespace) {
+              solrNamespace.initDatabases();
+              solrNamespace.whenLoaded(() => {
+                solrNamespace.setDatabase('default');
+              });
+            }
+          });
+      }
+    }
+
+    self.breadcrumb = ko.pureComputed(() => {
+      if (self.isStreams && self.selectedSource()) {
+        return self.selectedSource().name;
+      }
+      if (!self.isSolr && self.selectedSource()) {
+        if (self.selectedSource().selectedNamespace()) {
+          if (
+            self
+              .selectedSource()
+              .selectedNamespace()
+              .selectedDatabase()
+          ) {
+            return self
+              .selectedSource()
+              .selectedNamespace()
+              .selectedDatabase().catalogEntry.name;
+          }
+          if (window.HAS_MULTI_CLUSTER) {
+            return self.selectedSource().selectedNamespace().name;
+          }
+        }
+        return self.selectedSource().name;
+      }
+      return null;
+    });
+  }
+
+  setDatabaseWhenLoaded(namespace, databaseName) {
+    const self = this;
+    self.selectedSource().whenLoaded(() => {
+      if (
+        self.selectedSource().selectedNamespace() &&
+        self.selectedSource().selectedNamespace().namespace.id !== namespace.id
+      ) {
+        self
+          .selectedSource()
+          .namespaces()
+          .some(otherNamespace => {
+            if (otherNamespace.namespace.id === namespace.id) {
+              self.selectedSource().selectedNamespace(otherNamespace);
+              return true;
+            }
+          });
+      }
+
+      if (self.selectedSource().selectedNamespace()) {
+        self
+          .selectedSource()
+          .selectedNamespace()
+          .whenLoaded(() => {
+            self
+              .selectedSource()
+              .selectedNamespace()
+              .setDatabase(databaseName);
+          });
+      }
+    });
+  }
+
+  back() {
+    if (this.isStreams) {
+      this.selectedSource(null);
+      return;
+    }
+    if (this.selectedSource()) {
+      if (this.selectedSource() && this.selectedSource().selectedNamespace()) {
+        if (
+          this.selectedSource()
+            .selectedNamespace()
+            .selectedDatabase()
+        ) {
+          this.selectedSource()
+            .selectedNamespace()
+            .selectedDatabase(null);
+        } else if (window.HAS_MULTI_CLUSTER) {
+          this.selectedSource().selectedNamespace(null);
+        } else {
+          this.selectedSource(null);
+        }
+      } else {
+        this.selectedSource(null);
+      }
+    }
+  }
+
+  init() {
+    if (this.initialized) {
+      return;
+    }
+    if (this.isSolr) {
+      this.selectedSource(this.sourceIndex['solr']);
+    } else if (this.isStreams) {
+      this.selectedSource(this.sourceIndex['kafka']);
+    } else {
+      const storageSourceType = apiHelper.getFromTotalStorage('assist', 'lastSelectedSource');
+      if (!this.selectedSource()) {
+        if (this.options.activeSourceType) {
+          this.selectedSource(this.sourceIndex[this.options.activeSourceType]);
+        } else if (storageSourceType && this.sourceIndex[storageSourceType]) {
+          this.selectedSource(this.sourceIndex[storageSourceType]);
+        }
+      }
+    }
+    this.initialized = true;
+  }
+}
+
+componentUtils.registerComponent('hue-assist-db-panel', AssistDbPanel, TEMPLATE);

+ 400 - 0
desktop/core/src/desktop/js/ko/components/assist/ko.assistDocumentsPanel.js

@@ -0,0 +1,400 @@
+// 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.
+
+import $ from 'jquery';
+import ko from 'knockout';
+
+import apiHelper from 'api/apiHelper';
+import componentUtils from 'ko/components/componentUtils';
+import HueFileEntry from 'doc/hueFileEntry';
+import huePubSub from 'utils/huePubSub';
+import I18n from 'utils/i18n';
+import { DOCUMENT_TYPES } from 'doc/docSupport';
+
+const TEMPLATE = `
+  <script type="text/html" id="document-context-items">
+    <!-- ko if: definition().type === 'directory' -->
+    <li><a href="javascript: void(0);" data-bind="click: open"><i class="fa fa-fw fa-folder-open-o"></i> ${I18n(
+      'Open folder'
+    )}</a></li>
+    <!-- /ko -->
+    <!-- ko if: definition().type !== 'directory' -->
+    <li><a href="javascript: void(0);" data-bind="click: function(data) { showContextPopover(data, { target: $parentContext.$contextSourceElement }, { left: -15, top: 2 }); }"><i class="fa fa-fw fa-info"></i> ${I18n(
+      'Show details'
+    )}</a></li>
+    <li><a href="javascript: void(0);" data-bind="click: open"><i class="fa fa-fw fa-edit"></i> ${I18n(
+      'Open document'
+    )}</a></li>
+    <li><a href="javascript: void(0);" data-bind="click: function() { huePubSub.publish('doc.show.delete.modal', $data); activeEntry().getSelectedDocsWithDependents(); activeEntry().showDeleteConfirmation(); }"><i class="fa fa-fw fa-trash-o"></i> ${I18n(
+      'Delete document'
+    )}</a></li>
+    <!-- /ko -->
+  </script>
+  
+  <script type="text/html" id="assist-document-header-actions">
+    <div class="assist-db-header-actions">
+      <!-- ko if: !loading() -->
+      <div class="highlightable" data-bind="css: { 'highlight': $parent.highlightTypeFilter() }, component: { name: 'hue-drop-down', params: { fixedPosition: true, value: typeFilter, searchable: true, entries: DOCUMENT_TYPES, linkTitle: '${I18n(
+        'Document type'
+      )}' } }" style="display: inline-block"></div>
+      <!-- /ko -->
+      <span class="dropdown new-document-drop-down">
+
+        <a class="inactive-action dropdown-toggle" data-toggle="dropdown" data-bind="dropdown" href="javascript:void(0);">
+          <i class="pointer fa fa-plus" title="${I18n('New document')}"></i>
+        </a>
+        <ul class="dropdown-menu less-padding document-types" style="margin-top:3px; margin-left:-140px; width: 175px;position: absolute;" role="menu">
+            <!-- ko if: window.HUE_APPS.indexOf('beeswax') !== -1 -->
+              <li>
+                <a title="${I18n(
+                  'Hive Query'
+                )}" data-bind="click: function() { huePubSub.publish('open.editor.new.query', {type: 'hive', 'directoryUuid': $data.getDirectory()}); }" href="javascript:void(0);">
+                  <!-- ko template: { name: 'app-icon-template', data: { icon: 'hive' } } --><!-- /ko --> ${I18n(
+                    'Hive Query'
+                  )}
+                </a>
+              </li>
+            <!-- /ko -->
+            <!-- ko if: window.HUE_APPS.indexOf('impala') !== -1 -->
+              <li>
+                <a title="${I18n(
+                  'Impala Query'
+                )}" class="dropdown-item" data-bind="click: function() { huePubSub.publish('open.editor.new.query', {type: 'impala', 'directoryUuid': $data.getDirectory()}); }" href="javascript:void(0);">
+                  <!-- ko template: { name: 'app-icon-template', data: { icon: 'impala' } } --><!-- /ko --> ${I18n(
+                    'Impala Query'
+                  )}
+                </a>
+            </li>
+            <!-- /ko -->
+            <!-- ko if: window.SHOW_NOTEBOOKS -->
+              <li>
+                <a title="${I18n(
+                  'Notebook'
+                )}" data-bind="click: function() { $('.new-document-drop-down').removeClass('open');}, hueLink: $data.addDirectoryParamToUrl(window.HUE_URLS.NOTEBOOK_INDEX)">
+                  <!-- ko template: { name: 'app-icon-template', data: { icon: 'notebook' } } --><!-- /ko --> ${I18n(
+                    'Notebook'
+                  )}
+                </a>
+              </li>
+            <!-- /ko -->
+            <!-- ko if: window.HUE_APPS.indexOf('pig') !== -1 -->
+              <li>
+                <a title="${I18n(
+                  'Pig Script'
+                )}" data-bind="click: function() { $('.new-document-drop-down').removeClass('open');}, hueLink: $data.addDirectoryParamToUrl(window.HUE_URLS.PIG_INDEX)">
+                  <!-- ko template: { name: 'app-icon-template', data: { icon: 'pig' } } --><!-- /ko --> ${I18n(
+                    'Pig Script'
+                  )}
+                </a>
+              </li>
+            <!-- /ko -->
+            <!-- ko if: window.HUE_APPS.indexOf('oozie') !== -1 -->
+              <li>
+                <a title="${I18n(
+                  'Oozie Workflow'
+                )}" data-bind="click: function() { $('.new-document-drop-down').removeClass('open');}, hueLink: $data.addDirectoryParamToUrl(window.HUE_URLS.OOZIE_NEW_WORKFLOW)">
+                  <!-- ko template: { name: 'app-icon-template', data: { icon: 'oozie-workflow' } } --><!-- /ko --> ${I18n(
+                    'Workflow'
+                  )}
+                </a>
+              </li>
+              <li>
+                <a title="${I18n(
+                  'Oozie Schedule'
+                )}" data-bind="click: function() { $('.new-document-drop-down').removeClass('open');}, hueLink: $data.addDirectoryParamToUrl(window.HUE_URLS.OOZIE_NEW_COORDINATOR)">
+                  <!-- ko template: { name: 'app-icon-template', data: { icon: 'oozie-coordinator' } } --><!-- /ko --> ${I18n(
+                    'Schedule'
+                  )}
+                </a>
+              </li>
+              <li>
+                <a title="${I18n(
+                  'Oozie Bundle'
+                )}" data-bind="click: function() { $('.new-document-drop-down').removeClass('open');}, hueLink: $data.addDirectoryParamToUrl(window.HUE_URLS.OOZIE_NEW_BUNDLE)">
+                  <!-- ko template: { name: 'app-icon-template', data: { icon: 'oozie-bundle' } } --><!-- /ko --> ${I18n(
+                    'Bundle'
+                  )}
+                </a>
+              </li>
+            <!-- /ko -->
+            <!-- ko if: window.HUE_APPS.indexOf('search') !== -1 -->
+              <li>
+                <a title="${I18n(
+                  'Solr Search'
+                )}" data-bind="click: function() { $('.new-document-drop-down').removeClass('open');}, hueLink: $data.addDirectoryParamToUrl(window.HUE_URLS.SEARCH_NEW_SEARCH)">
+                  <!-- ko template: { name: 'app-icon-template', data: { icon: 'dashboard' } } --><!-- /ko --> ${I18n(
+                    'Dashboard'
+                  )}
+                </a>
+              </li>
+            <!-- /ko -->
+            <li class="divider"></li>
+            <li data-bind="css: { 'disabled': $data.isTrash() || $data.isTrashed() || !$data.canModify() }">
+              <a href="javascript:void(0);" data-bind="click: function () { $('.new-document-drop-down').removeClass('open'); huePubSub.publish('show.create.directory.modal', $data); $data.showNewDirectoryModal()}"><svg class="hi"><use xlink:href="#hi-folder"></use><use xlink:href="#hi-plus-addon"></use></svg> ${I18n(
+                'New folder'
+              )}</a>
+            </li>
+          </ul>
+      </span>
+      <a class="inactive-action" href="javascript:void(0)" data-bind="click: function () { huePubSub.publish('assist.document.refresh'); }"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }" title="${I18n(
+        'Manual refresh'
+      )}"></i></a>
+    </div>
+  </script>
+
+  <!-- ko with: activeEntry -->
+  <div class="assist-flex-header assist-breadcrumb" style="overflow: visible">
+    <!-- ko ifnot: isRoot -->
+    <a href="javascript: void(0);" data-bind="click: function () { if (loaded()) { parent.makeActive(); } }">
+      <i class="fa fa-fw fa-chevron-left"></i>
+      <i class="fa fa-fw fa-folder-o"></i>
+      <span data-bind="text: definition().name, attr: {'title': definition().name }"></span>
+    </a>
+    <!-- /ko -->
+    <!-- ko if: isRoot -->
+    <div>
+      <i class="fa fa-fw fa-folder-o"></i>
+      <span>/</span>
+    </div>
+    <!-- /ko -->
+    <!-- ko template: 'assist-document-header-actions' --><!-- /ko -->
+  </div>
+  <div class="assist-flex-search">
+    <div class="assist-filter"><input class="clearable" type="text" placeholder="${I18n(
+      'Filter...'
+    )}" data-bind="clearable: filter, value: filter, valueUpdate: 'afterkeydown'"/></div>
+  </div>
+  <div class="assist-flex-fill assist-file-scrollable" data-bind="delayedOverflow">
+    <div data-bind="visible: ! loading() && ! hasErrors() && entries().length > 0">
+      <!-- ko if: filteredEntries().length == 0 -->
+      <ul class="assist-tables">
+        <li class="assist-entry"><span class="assist-no-entries">${I18n(
+          'No documents found'
+        )}</span></li>
+      </ul>
+      <!-- /ko -->
+      <ul class="assist-tables" data-bind="foreachVisible: { data: filteredEntries, minHeight: 27, container: '.assist-file-scrollable' }">
+        <li class="assist-entry assist-file-entry" data-bind="appAwareTemplateContextMenu: { template: 'document-context-items', scrollContainer: '.assist-file-scrollable', beforeOpen: beforeContextOpen }, assistFileDroppable, assistFileDraggable, visibleOnHover: { 'selector': '.assist-file-actions' }">
+          <div class="assist-file-actions table-actions">
+            <a class="inactive-action" href="javascript:void(0)" data-bind="click: showContextPopover, css: { 'blue': statsVisible }"><i class="fa fa-fw fa-info" title="${I18n(
+              'Show details'
+            )}"></i></a>
+          </div>
+          <a href="javascript:void(0)" class="assist-entry assist-document-link" data-bind="click: open, attr: {'title': name }">
+            <!-- ko template: { name: 'document-icon-template', data: { document: $data, showShareAddon: false } } --><!-- /ko -->
+            <span class="highlightable" data-bind="css: { 'highlight': highlight }, text: definition().name"></span>
+          </a>
+        </li>
+      </ul>
+    </div>
+    <div data-bind="visible: !loading() && ! hasErrors() && entries().length === 0">
+      <span class="assist-no-entries">${I18n('Empty directory')}</span>
+    </div>
+    <div class="center" data-bind="visible: loading() && ! hasErrors()">
+      <i class="fa fa-spinner fa-spin" style="font-size: 20px; color: #BBB"></i>
+    </div>
+    <div class="assist-errors" data-bind="visible: ! loading() && hasErrors()">
+      <span>${I18n('Error loading contents.')}</span>
+    </div>
+  </div>
+  <!-- /ko -->
+`;
+
+class AssistDocumentsPanel {
+  /**
+   * @param {Object} options
+   * @param {string} options.user
+   * @constructor
+   **/
+  constructor(options) {
+    const self = this;
+    self.user = options.user;
+
+    self.activeEntry = ko.observable();
+    self.activeSort = ko.observable('defaultAsc');
+    self.typeFilter = ko.observable(DOCUMENT_TYPES[0]); // all is first
+
+    self.highlightTypeFilter = ko.observable(false);
+
+    const lastOpenedUuid = apiHelper.getFromTotalStorage('assist', 'last.opened.assist.doc.uuid');
+
+    if (lastOpenedUuid) {
+      self.activeEntry(
+        new HueFileEntry({
+          activeEntry: self.activeEntry,
+          trashEntry: ko.observable(),
+          app: 'documents',
+          user: self.user,
+          activeSort: self.activeSort,
+          typeFilter: self.typeFilter,
+          definition: {
+            uuid: lastOpenedUuid,
+            type: 'directory'
+          }
+        })
+      );
+    } else {
+      self.fallbackToRoot();
+    }
+
+    self.activeEntry.subscribe(newEntry => {
+      if (!newEntry.loaded()) {
+        const loadedSub = newEntry.loaded.subscribe(loaded => {
+          if (
+            loaded &&
+            !newEntry.hasErrors() &&
+            newEntry.definition() &&
+            newEntry.definition().uuid
+          ) {
+            apiHelper.setInTotalStorage(
+              'assist',
+              'last.opened.assist.doc.uuid',
+              newEntry.definition().uuid
+            );
+          }
+          loadedSub.dispose();
+        });
+      } else if (!newEntry.hasErrors() && newEntry.definition() && newEntry.definition().uuid) {
+        apiHelper.setInTotalStorage(
+          'assist',
+          'last.opened.assist.doc.uuid',
+          newEntry.definition().uuid
+        );
+      }
+    });
+
+    self.reload = () => {
+      self.activeEntry().load(
+        () => {},
+        () => {
+          self.fallbackToRoot();
+        }
+      );
+    };
+
+    huePubSub.subscribe('assist.document.refresh', () => {
+      huePubSub.publish('assist.clear.document.cache');
+      self.reload();
+    });
+
+    huePubSub.subscribe('assist.doc.highlight', details => {
+      huePubSub.publish('assist.show.documents');
+      huePubSub.publish('context.popover.hide');
+      const whenLoaded = $.Deferred().done(() => {
+        self.activeEntry().highlightInside(details.docUuid);
+      });
+      if (
+        self.activeEntry() &&
+        self.activeEntry().definition() &&
+        self.activeEntry().definition().uuid === details.parentUuid
+      ) {
+        if (self.activeEntry().loaded() && !self.activeEntry().hasErrors()) {
+          whenLoaded.resolve();
+        } else {
+          const loadedSub = self.activeEntry().loaded.subscribe(newVal => {
+            if (newVal) {
+              if (!self.activeEntry().hasErrors()) {
+                whenLoaded.resolve();
+              }
+              whenLoaded.reject();
+              loadedSub.remove();
+            }
+          });
+        }
+        self.activeEntry().highlight(details.docUuid);
+      } else {
+        self.activeEntry(
+          new HueFileEntry({
+            activeEntry: self.activeEntry,
+            trashEntry: ko.observable(),
+            app: 'documents',
+            user: self.user,
+            activeSort: self.activeSort,
+            typeFilter: self.typeFilter,
+            definition: {
+              uuid: details.parentUuid,
+              type: 'directory'
+            }
+          })
+        );
+        self.activeEntry().load(
+          () => {
+            whenLoaded.resolve();
+          },
+          () => {
+            whenLoaded.reject();
+            self.fallbackToRoot();
+          }
+        );
+      }
+    });
+  }
+
+  setTypeFilter(newType) {
+    const self = this;
+    DOCUMENT_TYPES.some(docType => {
+      if (docType.type === newType) {
+        self.typeFilter(docType);
+        return true;
+      }
+    });
+    self.highlightTypeFilter(true);
+    window.setTimeout(() => {
+      self.highlightTypeFilter(false);
+    }, 600);
+  }
+
+  fallbackToRoot() {
+    const self = this;
+    if (
+      !self.activeEntry() ||
+      (self.activeEntry().definition() &&
+        (self.activeEntry().definition().path !== '/' || self.activeEntry().definition().uuid))
+    ) {
+      apiHelper.setInTotalStorage('assist', 'last.opened.assist.doc.uuid', null);
+      self.activeEntry(
+        new HueFileEntry({
+          activeEntry: self.activeEntry,
+          trashEntry: ko.observable(),
+          app: 'documents',
+          user: self.user,
+          activeSort: self.activeSort,
+          typeFilter: self.typeFilter,
+          definition: {
+            name: '/',
+            type: 'directory'
+          }
+        })
+      );
+      self.activeEntry().load();
+    }
+  }
+
+  init() {
+    const self = this;
+    if (!self.activeEntry().loaded()) {
+      self.activeEntry().load(
+        () => {},
+        () => {
+          self.fallbackToRoot();
+        },
+        true
+      );
+    }
+  }
+}
+
+componentUtils.registerComponent('hue-assist-documents-panel', AssistDocumentsPanel, TEMPLATE);

+ 136 - 0
desktop/core/src/desktop/js/ko/components/assist/ko.assistGitPanel.js

@@ -0,0 +1,136 @@
+// 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.
+
+import ko from 'knockout';
+
+import apiHelper from 'api/apiHelper';
+import AssistGitEntry from 'ko/components/assist/assistGitEntry';
+import componentUtils from 'ko/components/componentUtils';
+import huePubSub from 'utils/huePubSub';
+import I18n from 'utils/i18n';
+
+const TEMPLATE = `
+  <script type="text/html" id="git-details-title">
+    <span data-bind="text: definition.name"></span>
+  </script>
+
+  <script type="text/html" id="assist-git-header-actions">
+    <div class="assist-db-header-actions">
+      <a class="inactive-action" href="javascript:void(0)" data-bind="click: function () { huePubSub.publish('assist.git.refresh'); }"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }" title="${I18n(
+        'Manual refresh'
+      )}"></i></a>
+    </div>
+  </script>
+
+  <!-- ko with: selectedGitEntry -->
+  <div class="assist-flex-header assist-breadcrumb" >
+    <!-- ko if: parent !== null -->
+    <a href="javascript: void(0);" data-bind="click: function () { huePubSub.publish('assist.selectGitEntry', parent); }">
+      <i class="fa fa-fw fa-chevron-left"></i>
+      <i class="fa fa-fw fa-folder-o"></i>
+      <span data-bind="text: path, attr: {'title': path }"></span>
+    </a>
+    <!-- /ko -->
+    <!-- ko if: parent === null -->
+    <div>
+      <i class="fa fa-fw fa-folder-o"></i>
+      <span data-bind="text: path"></span>
+    </div>
+    <!-- /ko -->
+    <!-- ko template: 'assist-git-header-actions' --><!-- /ko -->
+  </div>
+  <div class="assist-flex-fill assist-git-scrollable" data-bind="delayedOverflow">
+    <div data-bind="visible: ! loading() && ! hasErrors()" style="position: relative;">
+      <!-- ko hueSpinner: { spin: loadingMore, overlay: true } --><!-- /ko -->
+      <ul class="assist-tables" data-bind="foreachVisible: { data: entries, minHeight: 22, container: '.assist-git-scrollable' }">
+        <li class="assist-entry assist-table-link" style="position: relative;" data-bind="visibleOnHover: { 'selector': '.assist-actions' }">
+
+          <a href="javascript:void(0)" class="assist-entry assist-table-link" data-bind="multiClick: { click: toggleOpen, dblClick: dblClick }, attr: {'title': definition.name }">
+            <!-- ko if: definition.type === 'dir' -->
+            <i class="fa fa-fw fa-folder-o muted valign-middle"></i>
+            <!-- /ko -->
+            <!-- ko ifnot: definition.type === 'dir' -->
+            <i class="fa fa-fw fa-file-o muted valign-middle"></i>
+            <!-- /ko -->
+            <span draggable="true" data-bind="text: definition.name, draggableText: { text: '\\\\'' + path + '\\\\'', meta: {'type': 'git', 'definition': definition} }"></span>
+          </a>
+        </li>
+      </ul>
+      <!-- ko if: !loading() && entries().length === 0 -->
+      <ul class="assist-tables">
+        <li class="assist-entry"><span class="assist-no-entries">${I18n(
+          'Empty directory'
+        )}</span></li>
+      </ul>
+      <!-- /ko -->
+    </div>
+    <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
+    <div class="assist-errors" data-bind="visible: ! loading() && hasErrors()">
+      <span>${I18n('Error loading contents.')}</span>
+    </div>
+  </div>
+  <!-- /ko -->
+`;
+
+class AssistGitPanel {
+  /**
+   * @param {Object} options
+   * @constructor
+   **/
+  constructor(options) {
+    const self = this;
+
+    self.selectedGitEntry = ko.observable();
+    self.reload = function() {
+      const lastKnownPath = apiHelper.getFromTotalStorage(
+        'assist',
+        'currentGitPath',
+        window.USER_HOME_DIR
+      );
+      const parts = lastKnownPath.split('/');
+      parts.shift();
+
+      const currentEntry = new AssistGitEntry({
+        definition: {
+          name: '/',
+          type: 'dir'
+        },
+        parent: null
+      });
+
+      currentEntry.loadDeep(parts, entry => {
+        self.selectedGitEntry(entry);
+        entry.open(true);
+      });
+    };
+
+    huePubSub.subscribe('assist.selectGitEntry', entry => {
+      self.selectedGitEntry(entry);
+      apiHelper.setInTotalStorage('assist', 'currentGitPath', entry.path);
+    });
+
+    huePubSub.subscribe('assist.git.refresh', () => {
+      huePubSub.publish('assist.clear.git.cache');
+      self.reload();
+    });
+  }
+
+  init() {
+    this.reload();
+  }
+}
+
+componentUtils.registerComponent('hue-assist-git-panel', AssistGitPanel, TEMPLATE);

+ 207 - 0
desktop/core/src/desktop/js/ko/components/assist/ko.assistHBasePanel.js

@@ -0,0 +1,207 @@
+// 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.
+
+import ko from 'knockout';
+
+import apiHelper from 'api/apiHelper';
+import AssistHBaseEntry from 'ko/components/assist/assistHBaseEntry';
+import componentUtils from 'ko/components/componentUtils';
+import huePubSub from 'utils/huePubSub';
+import I18n from 'utils/i18n';
+
+const TEMPLATE = `
+  <script type="text/html" id="hbase-context-items">
+    <!-- ko if: definition.host -->
+    <li><a href="javascript: void(0);" data-bind="click: open"><i class="fa fa-fw fa-folder-open-o"></i> ${I18n(
+      'Open cluster'
+    )}</a></li>
+    <!-- /ko -->
+    <!-- ko ifnot: definition.host -->
+    <li><a href="javascript: void(0);" data-bind="click: open"><!-- ko template: { name: 'app-icon-template', data: { icon: 'hbase' } } --><!-- /ko --> ${I18n(
+      'Open in HBase'
+    )}</a></li>
+    <!-- /ko -->
+  </script>
+  
+  <script type="text/html" id="assist-hbase-header-actions">
+    <div class="assist-db-header-actions">
+      <a class="inactive-action" href="javascript:void(0)" data-bind="click: function () { huePubSub.publish('assist.hbase.refresh'); }"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }" title="${I18n(
+        'Manual refresh'
+      )}"></i></a>
+    </div>
+  </script>
+
+  <!-- ko with: selectedHBaseEntry -->
+  <div class="assist-inner-header assist-breadcrumb" >
+    <!-- ko if: definition.host !== '' -->
+    <a href="javascript: void(0);" data-bind="click: function () { huePubSub.publish('assist.clickHBaseRootItem'); }">
+      <i class="fa fa-fw fa-chevron-left"></i>
+      <i class="fa fa-fw fa-th-large"></i>
+      <span data-bind="text: definition.name"></span>
+    </a>
+    <!-- /ko -->
+    <!-- ko if: definition.host === '' -->
+    ${I18n('Clusters')}
+    <!-- /ko -->
+    <!-- ko template: 'assist-hbase-header-actions' --><!-- /ko -->
+  </div>
+  <div class="assist-flex-fill assist-hbase-scrollable" data-bind="delayedOverflow">
+    <div data-bind="visible: ! loading() && ! hasErrors()" style="position: relative;">
+      <ul class="assist-tables" data-bind="foreachVisible: { data: entries, minHeight: 22, container: '.assist-hbase-scrollable' }">
+        <li class="assist-entry assist-table-link" style="position: relative;" data-bind="appAwareTemplateContextMenu: { template: 'hbase-context-items', scrollContainer: '.assist-hbase-scrollable' }, visibleOnHover: { 'selector': '.assist-actions' }">
+          <a href="javascript:void(0)" class="assist-entry assist-table-link" data-bind="multiClick: { click: click, dblClick: dblClick }, attr: {'title': definition.name }">
+            <!-- ko if: definition.host -->
+            <i class="fa fa-fw fa-th-large muted valign-middle"></i>
+            <!-- /ko -->
+            <!-- ko ifnot: definition.host -->
+            <i class="fa fa-fw fa-th muted valign-middle"></i>
+            <!-- /ko -->
+            <span draggable="true" data-bind="text: definition.name, draggableText: { text: '\\\\'' + definition.name + '\\\\'', meta: {'type': 'collection', 'definition': definition} }"></span>
+          </a>
+        </li>
+      </ul>
+      <!-- ko if: !loading() && entries().length === 0 -->
+      <ul class="assist-tables">
+        <li class="assist-entry">
+          <span class="assist-no-entries">
+          <!-- ko if: definition.host === '' -->
+          ${I18n('No clusters available.')}
+          <!-- /ko -->
+          <!-- ko if: definition.host !== '' -->
+          ${I18n('No tables available.')}
+          <!-- /ko -->
+          </span>
+        </li>
+      </ul>
+      <!-- /ko -->
+    </div>
+    <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
+    <div class="assist-errors" data-bind="visible: ! loading() && hasErrors()">
+      <span>${I18n('Error loading contents.')}</span>
+    </div>
+  </div>
+  <!-- /ko -->
+`;
+
+class AssistHBasePanel {
+  /**
+   * @param {Object} options
+   * @constructor
+   **/
+  constructor(options) {
+    const self = this;
+    self.initialized = false;
+
+    const root = new AssistHBaseEntry({
+      definition: {
+        host: '',
+        name: '',
+        port: 0
+      }
+    });
+
+    self.selectedHBaseEntry = ko.observable();
+    self.reload = () => {
+      self.selectedHBaseEntry(root);
+      root.loadEntries(() => {
+        const lastOpenendPath = apiHelper.getFromTotalStorage(
+          'assist',
+          'last.opened.hbase.entry',
+          null
+        );
+        if (lastOpenendPath) {
+          root.entries().every(entry => {
+            if (entry.path === lastOpenendPath) {
+              entry.open();
+              return false;
+            }
+            return true;
+          });
+        }
+      });
+    };
+
+    self.selectedHBaseEntry.subscribe(newEntry => {
+      if (newEntry !== root || (newEntry === root && newEntry.loaded)) {
+        apiHelper.setInTotalStorage('assist', 'last.opened.hbase.entry', newEntry.path);
+      }
+    });
+
+    huePubSub.subscribe('assist.clickHBaseItem', entry => {
+      if (entry.definition.host) {
+        entry.loadEntries();
+        self.selectedHBaseEntry(entry);
+      } else {
+        huePubSub.publish('assist.dblClickHBaseItem', entry);
+      }
+    });
+
+    huePubSub.subscribe('assist.clickHBaseRootItem', () => {
+      self.reload();
+    });
+
+    const delayChangeHash = hash => {
+      window.setTimeout(() => {
+        window.location.hash = hash;
+      }, 0);
+    };
+
+    self.lastClickeHBaseEntry = null;
+    self.HBaseLoaded = false;
+
+    huePubSub.subscribeOnce('hbase.app.loaded', () => {
+      if (self.selectedHBaseEntry() && self.lastClickeHBaseEntry) {
+        delayChangeHash(
+          self.selectedHBaseEntry().definition.name +
+            '/' +
+            self.lastClickeHBaseEntry.definition.name
+        );
+      }
+      self.HBaseLoaded = true;
+    });
+
+    huePubSub.subscribe('assist.dblClickHBaseItem', entry => {
+      const hash = self.selectedHBaseEntry().definition.name + '/' + entry.definition.name;
+      if (window.location.pathname.startsWith('/hue/hbase')) {
+        window.location.hash = hash;
+      } else {
+        self.lastClickeHBaseEntry = entry;
+        huePubSub.subscribeOnce('app.gained.focus', app => {
+          if (app === 'hbase' && self.HBaseLoaded) {
+            delayChangeHash(hash);
+          }
+        });
+        huePubSub.publish('open.link', '/hbase');
+      }
+    });
+
+    huePubSub.subscribe('assist.hbase.refresh', () => {
+      huePubSub.publish('assist.clear.hbase.cache');
+      self.reload();
+    });
+  }
+
+  init() {
+    const self = this;
+    if (self.initialized) {
+      return;
+    }
+    self.reload();
+    self.initialized = true;
+  }
+}
+
+componentUtils.registerComponent('hue-assist-hbase-panel', AssistHBasePanel, TEMPLATE);

+ 197 - 0
desktop/core/src/desktop/js/ko/components/assist/ko.assistHdfsPanel.js

@@ -0,0 +1,197 @@
+// 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.
+
+import ko from 'knockout';
+
+import apiHelper from 'api/apiHelper';
+import AssistStorageEntry from 'ko/components/assist/assistStorageEntry';
+import componentUtils from 'ko/components/componentUtils';
+import huePubSub from 'utils/huePubSub';
+import I18n from 'utils/i18n';
+
+const HDFS_CONTEXT_ITEMS_TEMPLATE = `
+  <script type="text/html" id="hdfs-context-items">
+    <li><a href="javascript:void(0);" data-bind="click: function (data) { showContextPopover(data, { target: $parentContext.$contextSourceElement }, { left: -15, top: 2 }); }"><i class="fa fa-fw fa-info"></i> ${I18n(
+      'Show details'
+    )}</a></li>
+    <li><a href="javascript:void(0);" data-bind="hueLink: definition.url"><i class="fa fa-fw" data-bind="css: {'fa-folder-open-o': definition.type === 'dir', 'fa-file-text-o': definition.type === 'file'}"></i> ${I18n(
+      'Open in Browser'
+    )}</a></li>
+    <!-- ko if: definition.type === 'file' -->
+    <li><a href="javascript:void(0);" data-bind="click: openInImporter"><!-- ko template: { name: 'app-icon-template', data: { icon: 'importer' } } --><!-- /ko --> ${I18n(
+      'Open in Importer'
+    )}</a></li>
+    <!-- /ko -->
+    <!-- ko if: $currentApp() === 'editor' -->
+    <li><a href="javascript:void(0);" data-bind="click: dblClick"><i class="fa fa-fw fa-paste"></i> ${I18n(
+      'Insert at cursor'
+    )}</a></li>
+    <!-- /ko -->
+  </script>
+`;
+
+const TEMPLATE =
+  HDFS_CONTEXT_ITEMS_TEMPLATE +
+  `
+  <script type="text/html" id="assist-hdfs-header-actions">
+    <div class="assist-db-header-actions">
+      <a class="inactive-action" href="javascript:void(0)" data-bind="click: goHome, attr: { title: I18n('Go to ' + window.USER_HOME_DIR) }"><i class="pointer fa fa-home"></i></a>
+      <!-- ko if: window.SHOW_UPLOAD_BUTTON -->
+      <a class="inactive-action" data-bind="dropzone: {
+            url: '/filebrowser/upload/file?dest=' + path,
+            params: { dest: path },
+            paramName: 'hdfs_file',
+            onError: function(x, e){ $(document).trigger('error', e); },
+            onComplete: function () { huePubSub.publish('assist.hdfs.refresh'); huePubSub.publish('fb.hdfs.refresh', path); } }" title="${I18n(
+              'Upload file'
+            )}" href="javascript:void(0)">
+        <div class="dz-message inline" data-dz-message><i class="pointer fa fa-plus" title="${I18n(
+          'Upload file'
+        )}"></i></div>
+      </a>
+      <!-- /ko -->
+      <a class="inactive-action" href="javascript:void(0)" data-bind="click: function () { huePubSub.publish('assist.hdfs.refresh'); }" title="${I18n(
+        'Manual refresh'
+      )}"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }"></i></a>
+    </div>
+  </script>
+  
+  <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
+  <!-- ko with: selectedHdfsEntry -->
+  <div class="assist-flex-header assist-breadcrumb" >
+    <!-- ko if: parent !== null -->
+    <a href="javascript: void(0);" data-bind="appAwareTemplateContextMenu: { template: 'hdfs-context-items', scrollContainer: '.assist-hdfs-scrollable' }, click: function () { huePubSub.publish('assist.selectHdfsEntry', parent); }">
+      <i class="fa fa-fw fa-chevron-left"></i>
+      <i class="fa fa-fw fa-folder-o"></i>
+      <span data-bind="text: definition.name, tooltip: {'title': path, 'placement': 'top' }"></span>
+    </a>
+    <!-- /ko -->
+    <!-- ko if: parent === null -->
+    <div>
+      <i class="fa fa-fw fa-folder-o"></i>
+      <span data-bind="text: path"></span>
+    </div>
+    <!-- /ko -->
+    <!-- ko template: 'assist-hdfs-header-actions' --><!-- /ko -->
+  </div>
+  <div class="assist-flex-search">
+    <div class="assist-filter"><input class="clearable" type="text" placeholder="${I18n(
+      'Filter...'
+    )}" data-bind="clearable: filter, value: filter, valueUpdate: 'afterkeydown'"/></div>
+  </div>
+  <div class="assist-flex-fill assist-hdfs-scrollable" data-bind="delayedOverflow">
+    <div data-bind="visible: ! loading() && ! hasErrors()" style="position: relative;">
+      <!-- ko hueSpinner: { spin: loadingMore, overlay: true } --><!-- /ko -->
+      <ul class="assist-tables" data-bind="foreachVisible: { data: entries, minHeight: 22, container: '.assist-hdfs-scrollable', fetchMore: $data.fetchMore.bind($data) }">
+        <li class="assist-entry assist-table-link" style="position: relative;" data-bind="appAwareTemplateContextMenu: { template: 'hdfs-context-items', scrollContainer: '.assist-hdfs-scrollable' }, visibleOnHover: { override: contextPopoverVisible, 'selector': '.assist-actions' }">
+          <div class="assist-actions table-actions" style="opacity: 0;" >
+            <a style="padding: 0 3px;" class="inactive-action" href="javascript:void(0);" data-bind="click: showContextPopover, css: { 'blue': contextPopoverVisible }">
+              <i class='fa fa-info' title="${I18n('Details')}"></i>
+            </a>
+          </div>
+
+          <a href="javascript:void(0)" class="assist-entry assist-table-link" data-bind="multiClick: { click: toggleOpen, dblClick: dblClick }, attr: {'title': definition.name }">
+            <!-- ko if: definition.type === 'dir' -->
+            <i class="fa fa-fw fa-folder-o muted valign-middle"></i>
+            <!-- /ko -->
+            <!-- ko if: definition.type === 'file' -->
+            <i class="fa fa-fw fa-file-o muted valign-middle"></i>
+            <!-- /ko -->
+            <span draggable="true" data-bind="text: definition.name, draggableText: { text: '\\\\'' + path + '\\\\'', meta: {'type': 'hdfs', 'definition': definition} }"></span>
+          </a>
+        </li>
+      </ul>
+      <!-- ko if: !loading() && entries().length === 0 -->
+      <ul class="assist-tables">
+        <li class="assist-entry"><span class="assist-no-entries"><!-- ko if: filter() -->${I18n(
+          'No results found'
+        )}<!-- /ko --><!-- ko ifnot: filter() -->${I18n('Empty directory')}<!-- /ko --></span></li>
+      </ul>
+      <!-- /ko -->
+    </div>
+    <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
+    <div class="assist-errors" data-bind="visible: ! loading() && hasErrors()">
+      <span>${I18n('Error loading contents.')}</span>
+    </div>
+  </div>
+  <!-- /ko -->
+`;
+
+class AssistHdfsPanel {
+  /**
+   * @param {Object} options
+   * @constructor
+   **/
+  constructor(options) {
+    const self = this;
+    self.selectedHdfsEntry = ko.observable();
+    self.loading = ko.observable();
+    self.initialized = false;
+
+    const loadPath = path => {
+      self.loading(true);
+      const parts = path.split('/');
+      parts.shift();
+
+      const currentEntry = new AssistStorageEntry({
+        type: 'hdfs',
+        definition: {
+          name: '/',
+          type: 'dir'
+        },
+        parent: null
+      });
+
+      currentEntry.loadDeep(parts, entry => {
+        self.selectedHdfsEntry(entry);
+        entry.open(true);
+        self.loading(false);
+      });
+    };
+
+    self.reload = () => {
+      loadPath(apiHelper.getFromTotalStorage('assist', 'currentHdfsPath', window.USER_HOME_DIR));
+    };
+
+    huePubSub.subscribe('assist.hdfs.go.home', () => {
+      loadPath(window.USER_HOME_DIR);
+      apiHelper.setInTotalStorage('assist', 'currentHdfsPath', window.USER_HOME_DIR);
+    });
+
+    huePubSub.subscribe('assist.selectHdfsEntry', entry => {
+      self.selectedHdfsEntry(entry);
+      apiHelper.setInTotalStorage('assist', 'currentHdfsPath', entry.path);
+    });
+
+    huePubSub.subscribe('assist.hdfs.refresh', () => {
+      huePubSub.publish('assist.clear.hdfs.cache');
+      self.reload();
+    });
+  }
+
+  init() {
+    const self = this;
+    if (self.initialized) {
+      return;
+    }
+    self.reload();
+    self.initialized = true;
+  }
+}
+
+componentUtils.registerComponent('hue-assist-hdfs-panel', AssistHdfsPanel, TEMPLATE);
+
+export default HDFS_CONTEXT_ITEMS_TEMPLATE;

+ 351 - 0
desktop/core/src/desktop/js/ko/components/assist/ko.assistPanel.js

@@ -0,0 +1,351 @@
+// 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.
+
+import $ from 'jquery';
+import ko from 'knockout';
+
+import componentUtils from 'ko/components/componentUtils';
+import huePubSub from 'utils/huePubSub';
+import I18n from 'utils/i18n';
+import AssistInnerPanel from 'ko/components/assist/assistInnerPanel';
+
+const TEMPLATE = `
+  <script type="text/html" id="assist-panel-inner-header">
+    <div class="assist-header assist-fixed-height" data-bind="visibleOnHover: { selector: '.assist-header-actions' }, css: { 'assist-resizer': $index() > 0 }" style="display:none;">
+      <span data-bind="text: $parent.name"></span>
+      <div class="assist-header-actions">
+        <div class="inactive-action" data-bind="click: function () { $parent.visible(false) }"><i class="fa fa-times"></i></div>
+      </div>
+    </div>
+  </script>
+  
+  <div class="assist-panel" data-bind="dropzone: { url: '/filebrowser/upload/file?dest=' + DROPZONE_HOME_DIR, params: {dest: DROPZONE_HOME_DIR}, clickable: false, paramName: 'hdfs_file', onComplete: function(path){ huePubSub.publish('assist.dropzone.complete', path); }, disabled: !window.SHOW_UPLOAD_BUTTON }">
+    <!-- ko if: availablePanels().length > 1 -->
+    <div class="assist-panel-switches">
+      <!-- ko foreach: availablePanels -->
+      <div class="inactive-action assist-type-switch" data-bind="click: function () { $parent.visiblePanel($data); }, css: { 'blue': $parent.visiblePanel() === $data }, style: { 'float': rightAlignIcon ? 'right' : 'left' },  attr: { 'title': name }">
+        <!-- ko if: iconSvg --><span style="font-size:22px;"><svg class="hi"><use data-bind="attr: {'xlink:href': iconSvg }" xlink:href=''></use></svg></span><!-- /ko -->
+        <!-- ko if: !iconSvg --><i class="fa fa-fw valign-middle" data-bind="css: icon"></i><!-- /ko -->
+      </div>
+      <!-- /ko -->
+    </div>
+    <!-- /ko -->
+    <!-- ko with: visiblePanel -->
+    <div class="assist-panel-contents" data-bind="style: { 'padding-top': $parent.availablePanels().length > 1 ? '10px' : '5px' }">
+      <div class="assist-inner-panel">
+        <div class="assist-flex-panel">
+          <!-- ko component: panelData --><!-- /ko -->
+        </div>
+      </div>
+    </div>
+    <!-- /ko -->
+  </div>
+`;
+
+class AssistPanel {
+  /**
+   * @param {Object} params
+   * @param {string} params.user
+   * @param {boolean} params.onlySql - For the old query editors
+   * @param {string[]} params.visibleAssistPanels - Panels that will initially be shown regardless of total storage
+   * @param {Object} params.sql
+   * @param {Object[]} params.sql.sourceTypes - All the available SQL source types
+   * @param {string} params.sql.sourceTypes[].name - Example: Hive SQL
+   * @param {string} params.sql.sourceTypes[].type - Example: hive
+   * @param {string} [params.sql.activeSourceType] - Example: hive
+   * @param {Object} params.sql.navigationSettings - enable/disable the links
+   * @param {boolean} params.sql.navigationSettings.openItem - Example: true
+   * @param {boolean} params.sql.navigationSettings.showStats - Example: true
+   * @constructor
+   */
+  constructor(params) {
+    const self = this;
+    const i18n = {
+      errorLoadingDatabases: I18n('There was a problem loading the databases'),
+      errorLoadingTablePreview: I18n('There was a problem loading the table preview')
+    };
+    const i18nCollections = {
+      errorLoadingDatabases: I18n('There was a problem loading the indexes'),
+      errorLoadingTablePreview: I18n('There was a problem loading the index preview')
+    };
+
+    self.tabsEnabled = window.USE_NEW_SIDE_PANELS;
+
+    self.availablePanels = ko.observableArray();
+    self.visiblePanel = ko.observable();
+
+    self.lastOpenPanelType = ko.observable();
+    window.apiHelper.withTotalStorage('assist', 'last.open.panel', self.lastOpenPanelType);
+
+    huePubSub.subscribeOnce('cluster.config.set.config', clusterConfig => {
+      if (clusterConfig && clusterConfig['app_config']) {
+        const panels = [];
+        const appConfig = clusterConfig['app_config'];
+
+        if (appConfig['editor']) {
+          const sqlPanel = new AssistInnerPanel({
+            panelData: {
+              name: 'hue-assist-db-panel',
+              params: $.extend(
+                {
+                  i18n: i18n
+                },
+                params.sql
+              )
+            },
+            name: I18n('SQL'),
+            type: 'sql',
+            icon: 'fa-database',
+            minHeight: 75
+          });
+          panels.push(sqlPanel);
+
+          huePubSub.subscribe('assist.show.sql', () => {
+            if (self.visiblePanel() !== sqlPanel) {
+              self.visiblePanel(sqlPanel);
+            }
+          });
+        }
+
+        if (self.tabsEnabled) {
+          if (
+            appConfig['browser'] &&
+            appConfig['browser']['interpreter_names'].indexOf('hdfs') !== -1
+          ) {
+            panels.push(
+              new AssistInnerPanel({
+                panelData: {
+                  name: 'hue-assist-hdfs-panel',
+                  params: {}
+                },
+                name: I18n('HDFS'),
+                type: 'hdfs',
+                icon: 'fa-files-o',
+                minHeight: 50
+              })
+            );
+          }
+
+          if (
+            appConfig['browser'] &&
+            appConfig['browser']['interpreter_names'].indexOf('s3') !== -1
+          ) {
+            panels.push(
+              new AssistInnerPanel({
+                panelData: {
+                  name: 'hue-assist-s3-panel',
+                  params: {}
+                },
+                name: I18n('S3'),
+                type: 's3',
+                icon: 'fa-cubes',
+                minHeight: 50
+              })
+            );
+          }
+
+          if (
+            appConfig['browser'] &&
+            appConfig['browser']['interpreter_names'].indexOf('adls') !== -1
+          ) {
+            panels.push(
+              new AssistInnerPanel({
+                panelData: {
+                  name: 'hue-assist-adls-panel',
+                  params: {}
+                },
+                name: I18n('ADLS'),
+                type: 'adls',
+                icon: 'fa-windows',
+                iconSvg: '#hi-adls',
+                minHeight: 50
+              })
+            );
+          }
+
+          if (
+            appConfig['browser'] &&
+            appConfig['browser']['interpreter_names'].indexOf('indexes') !== -1
+          ) {
+            const solrPanel = new AssistInnerPanel({
+              panelData: {
+                name: 'hue-assist-db-panel',
+                params: $.extend(
+                  {
+                    i18n: i18nCollections,
+                    isSolr: true
+                  },
+                  params.sql
+                )
+              },
+              name: I18n('Indexes'),
+              type: 'solr',
+              icon: 'fa-search-plus',
+              minHeight: 75
+            });
+            panels.push(solrPanel);
+            huePubSub.subscribe('assist.show.solr', () => {
+              if (self.visiblePanel() !== solrPanel) {
+                self.visiblePanel(solrPanel);
+              }
+            });
+          }
+
+          if (
+            appConfig['browser'] &&
+            appConfig['browser']['interpreter_names'].indexOf('kafka') !== -1
+          ) {
+            const streamsPanel = new AssistInnerPanel({
+              panelData: $.extend(
+                {
+                  i18n: i18nCollections,
+                  isStreams: true
+                },
+                params.sql
+              ),
+              name: I18n('Streams'),
+              type: 'kafka',
+              icon: 'fa-sitemap',
+              minHeight: 75
+            });
+            panels.push(streamsPanel);
+          }
+
+          if (
+            appConfig['browser'] &&
+            appConfig['browser']['interpreter_names'].indexOf('hbase') !== -1
+          ) {
+            panels.push(
+              new AssistInnerPanel({
+                panelData: {
+                  name: 'hue-assist-hbase-panel',
+                  params: {}
+                },
+                name: I18n('HBase'),
+                type: 'hbase',
+                icon: 'fa-th-large',
+                minHeight: 50
+              })
+            );
+          }
+
+          if (!window.IS_EMBEDDED) {
+            const documentsPanel = new AssistInnerPanel({
+              panelData: {
+                name: 'hue-assist-documents-panel',
+                params: {
+                  user: params.user
+                }
+              },
+              name: I18n('Documents'),
+              type: 'documents',
+              icon: 'fa-files-o',
+              iconSvg: '#hi-documents',
+              minHeight: 50,
+              rightAlignIcon: true,
+              visible:
+                params.visibleAssistPanels && params.visibleAssistPanels.indexOf('documents') !== -1
+            });
+
+            panels.push(documentsPanel);
+
+            huePubSub.subscribe('assist.show.documents', docType => {
+              huePubSub.publish('left.assist.show');
+              if (self.visiblePanel() !== documentsPanel) {
+                self.visiblePanel(documentsPanel);
+              }
+              if (docType) {
+                documentsPanel.panelData.setTypeFilter(docType);
+              }
+            });
+          }
+
+          if (window.HAS_GIT) {
+            panels.push(
+              new AssistInnerPanel({
+                panelData: {
+                  name: 'hue-assist-git-panel',
+                  params: {}
+                },
+                name: I18n('Git'),
+                type: 'git',
+                icon: 'fa-github',
+                minHeight: 50,
+                rightAlignIcon: true
+              })
+            );
+          }
+        }
+
+        self.availablePanels(panels);
+      } else {
+        self.availablePanels([
+          new AssistInnerPanel({
+            panelData: {
+              name: 'hue-assist-db-panel',
+              params: $.extend(
+                {
+                  i18n: i18n
+                },
+                params.sql
+              )
+            },
+            name: I18n('SQL'),
+            type: 'sql',
+            icon: 'fa-database',
+            minHeight: 75
+          })
+        ]);
+      }
+
+      if (!self.lastOpenPanelType()) {
+        self.lastOpenPanelType(self.availablePanels()[0].type);
+      }
+
+      const lastFoundPanel = self.availablePanels().filter(panel => {
+        return panel.type === self.lastOpenPanelType();
+      });
+
+      // always forces the db panel to load
+      const dbPanel = self.availablePanels().filter(panel => {
+        return panel.type === 'sql';
+      });
+      if (dbPanel.length > 0) {
+        dbPanel[0].panelData.init();
+      }
+
+      self.visiblePanel.subscribe(newValue => {
+        self.lastOpenPanelType(newValue.type);
+        if (newValue.type !== 'sql' && !newValue.panelData.initialized) {
+          newValue.panelData.init();
+        }
+      });
+
+      self.visiblePanel(
+        lastFoundPanel.length === 1 ? lastFoundPanel[0] : self.availablePanels()[0]
+      );
+    });
+
+    window.setTimeout(() => {
+      // Main initialization trigger in hue.mako, this is for Hue 3
+      if (self.availablePanels().length === 0) {
+        huePubSub.publish('cluster.config.get.config');
+      }
+    }, 0);
+  }
+}
+
+componentUtils.registerComponent('assist-panel', AssistPanel, TEMPLATE);

+ 153 - 0
desktop/core/src/desktop/js/ko/components/assist/ko.assistS3Panel.js

@@ -0,0 +1,153 @@
+// 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.
+
+import ko from 'knockout';
+
+import apiHelper from 'api/apiHelper';
+import AssistStorageEntry from 'ko/components/assist/assistStorageEntry';
+import componentUtils from 'ko/components/componentUtils';
+import HDFS_CONTEXT_ITEMS_TEMPLATE from 'ko/components/assist/ko.assistHdfsPanel';
+import huePubSub from 'utils/huePubSub';
+import I18n from 'utils/i18n';
+
+const TEMPLATE =
+  HDFS_CONTEXT_ITEMS_TEMPLATE +
+  `
+  <script type="text/html" id="assist-s3-header-actions">
+    <div class="assist-db-header-actions">
+      <a class="inactive-action" href="javascript:void(0)" data-bind="click: function () { huePubSub.publish('assist.s3.refresh'); }"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }" title="${I18n(
+        'Manual refresh'
+      )}"></i></a>
+    </div>
+  </script>
+
+  <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
+  <!-- ko with: selectedS3Entry -->
+  <div class="assist-flex-header assist-breadcrumb" >
+    <!-- ko if: parent !== null -->
+    <a href="javascript: void(0);" data-bind="appAwareTemplateContextMenu: { template: 'hdfs-context-items', scrollContainer: '.assist-s3-scrollable' }, click: function () { huePubSub.publish('assist.selectS3Entry', parent); }">
+      <i class="fa fa-fw fa-chevron-left"></i>
+      <i class="fa fa-fw fa-folder-o"></i>
+      <span data-bind="text: definition.name, tooltip: {'title': path, 'placement': 'top' }"></span>
+    </a>
+    <!-- /ko -->
+    <!-- ko if: parent === null -->
+    <div>
+      <i class="fa fa-fw fa-folder-o"></i>
+      <span data-bind="text: path"></span>
+    </div>
+    <!-- /ko -->
+    <!-- ko template: 'assist-s3-header-actions' --><!-- /ko -->
+  </div>
+  <div class="assist-flex-search">
+    <div class="assist-filter"><input class="clearable" type="text" placeholder="${I18n(
+      'Filter...'
+    )}" data-bind="clearable: filter, value: filter, valueUpdate: 'afterkeydown'"/></div>
+  </div>
+  <div class="assist-flex-fill assist-s3-scrollable" data-bind="delayedOverflow">
+    <div data-bind="visible: ! loading() && ! hasErrors()" style="position: relative;">
+      <!-- ko hueSpinner: { spin: loadingMore, overlay: true } --><!-- /ko -->
+      <ul class="assist-tables" data-bind="foreachVisible: { data: entries, minHeight: 22, container: '.assist-s3-scrollable', fetchMore: $data.fetchMore.bind($data) }">
+        <li class="assist-entry assist-table-link" style="position: relative;" data-bind="appAwareTemplateContextMenu: { template: 'hdfs-context-items', scrollContainer: '.assist-s3-scrollable' }, visibleOnHover: { override: contextPopoverVisible, 'selector': '.assist-actions' }">
+          <div class="assist-actions table-actions" style="opacity: 0;" >
+            <a style="padding: 0 3px;" class="inactive-action" href="javascript:void(0);" data-bind="click: showContextPopover, css: { 'blue': contextPopoverVisible }">
+              <i class='fa fa-info' title="${I18n('Details')}"></i>
+            </a>
+          </div>
+
+          <a href="javascript:void(0)" class="assist-entry assist-table-link" data-bind="multiClick: { click: toggleOpen, dblClick: dblClick }, attr: {'title': definition.name }">
+            <!-- ko if: definition.type === 'dir' -->
+            <i class="fa fa-fw fa-folder-o muted valign-middle"></i>
+            <!-- /ko -->
+            <!-- ko if: definition.type === 'file' -->
+            <i class="fa fa-fw fa-file-o muted valign-middle"></i>
+            <!-- /ko -->
+            <span draggable="true" data-bind="text: definition.name, draggableText: { text: '\\\\'' + path + '\\\\'', meta: {'type': 's3', 'definition': definition} }"></span>
+          </a>
+        </li>
+      </ul>
+      <!-- ko if: !loading() && entries().length === 0 -->
+      <ul class="assist-tables">
+        <li class="assist-entry"><span class="assist-no-entries"><!-- ko if: filter() -->${I18n(
+          'No results found'
+        )}<!-- /ko --><!-- ko ifnot: filter() -->${I18n('Empty directory')}<!-- /ko --></span></li>
+      </ul>
+      <!-- /ko -->
+    </div>
+    <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
+    <div class="assist-errors" data-bind="visible: ! loading() && hasErrors()">
+      <span>${I18n('Error loading contents.')}</span>
+    </div>
+  </div>
+  <!-- /ko -->
+`;
+
+class AssistS3Panel {
+  /**
+   * @param {Object} options
+   * @constructor
+   **/
+  constructor(options) {
+    const self = this;
+
+    self.selectedS3Entry = ko.observable();
+    self.loading = ko.observable();
+    self.initialized = false;
+
+    self.reload = () => {
+      self.loading(true);
+      const lastKnownPath = apiHelper.getFromTotalStorage('assist', 'currentS3Path', '/');
+      const parts = lastKnownPath.split('/');
+      parts.shift();
+
+      const currentEntry = new AssistStorageEntry({
+        type: 's3',
+        definition: {
+          name: '/',
+          type: 'dir'
+        },
+        parent: null
+      });
+
+      currentEntry.loadDeep(parts, entry => {
+        self.selectedS3Entry(entry);
+        entry.open(true);
+        self.loading(false);
+      });
+    };
+
+    huePubSub.subscribe('assist.selectS3Entry', entry => {
+      self.selectedS3Entry(entry);
+      apiHelper.setInTotalStorage('assist', 'currentS3Path', entry.path);
+    });
+
+    huePubSub.subscribe('assist.s3.refresh', () => {
+      huePubSub.publish('assist.clear.s3.cache');
+      self.reload();
+    });
+  }
+
+  init() {
+    const self = this;
+    if (self.initialized) {
+      return;
+    }
+    self.reload();
+    self.initialized = true;
+  }
+}
+
+componentUtils.registerComponent('hue-assist-s3-panel', AssistS3Panel, TEMPLATE);

+ 1 - 1
desktop/core/src/desktop/js/ko/components/contextPopover/storageContext.js

@@ -16,7 +16,7 @@
 
 import ko from 'knockout';
 
-import AssistStorageEntry from 'assist/assistStorageEntry';
+import AssistStorageEntry from 'ko/components/assist/assistStorageEntry';
 import huePubSub from 'utils/huePubSub';
 
 class StorageContext {

+ 10 - 0
desktop/core/src/desktop/js/ko/ko.all.js

@@ -37,6 +37,8 @@ import 'ko/bindings/charts/ko.timelineChart';
 import 'ko/bindings/ko.aceEditor';
 import 'ko/bindings/ko.aceResizer';
 import 'ko/bindings/ko.appAwareTemplateContextMenu';
+import 'ko/bindings/ko.assistFileDraggable';
+import 'ko/bindings/ko.assistFileDroppable';
 import 'ko/bindings/ko.assistVerticalResizer';
 import 'ko/bindings/ko.attachViewModelToElementData';
 import 'ko/bindings/ko.augmentHtml';
@@ -124,6 +126,14 @@ import 'ko/bindings/ko.typeahead';
 import 'ko/bindings/ko.verticalSlide';
 import 'ko/bindings/ko.visibleOnHover';
 
+import 'ko/components/assist/ko.assistAdlsPanel';
+import 'ko/components/assist/ko.assistDbPanel';
+import 'ko/components/assist/ko.assistDocumentsPanel';
+import 'ko/components/assist/ko.assistGitPanel';
+import 'ko/components/assist/ko.assistHBasePanel';
+import 'ko/components/assist/ko.assistHdfsPanel';
+import 'ko/components/assist/ko.assistPanel';
+import 'ko/components/assist/ko.assistS3Panel';
 import 'ko/components/contextPopover/ko.contextPopover';
 import 'ko/components/simpleAceEditor/ko.simpleAceEditor';
 import 'ko/components/ko.appSwitcher';

+ 1 - 1
desktop/core/src/desktop/js/sql/aceLocationHandler.js

@@ -16,7 +16,7 @@
 
 import $ from 'jquery';
 
-import AssistStorageEntry from 'assist/assistStorageEntry';
+import AssistStorageEntry from 'ko/components/assist/assistStorageEntry';
 import dataCatalog from 'catalog/dataCatalog';
 import hueDebug from 'utils/hueDebug';
 import huePubSub from 'utils/huePubSub';

+ 0 - 1279
desktop/core/src/desktop/templates/assist.mako

@@ -33,1285 +33,6 @@ from desktop.views import _ko
 <%namespace name="sqlDocIndex" file="/sql_doc_index.mako" />
 
 <%def name="assistPanel(is_s3_enabled=False)">
-  <script type="text/html" id="assist-no-database-entries">
-    <ul class="assist-tables">
-      <li>
-        <span class="assist-no-entries">${_('No entries found')}</span>
-      </li>
-    </ul>
-  </script>
-
-  <script type="text/html" id="assist-no-table-entries">
-    <ul>
-      <li>
-        <span class="assist-no-entries">${_('The table has no columns')}</span>
-      </li>
-    </ul>
-  </script>
-
-  <script type="text/html" id="assist-database-actions">
-    <div class="assist-actions database-actions" style="opacity: 0">
-      <!-- ko if: sourceType === 'hive' || sourceType === 'impala' -->
-      <a class="inactive-action" href="javascript:void(0)" data-bind="visible: navigationSettings.showStats, click: function (data, event) { showContextPopover(data, event); }, css: { 'blue': statsVisible }"><i class="fa fa-fw fa-info" title="${_('Show details')}"></i></a>
-      <!-- /ko -->
-      <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>
-  </script>
-
-  <script type="text/html" id="collection-title-context-items">
-    <li><a href="javascript:void(0);" data-bind="hueLink: '/indexer'"><i class="fa fa-fw fa-table"></i> ${ _('Open in Browser') }</a></li>
-  </script>
-
-  <script type="text/html" id="sql-context-items">
-    <!-- ko if: typeof catalogEntry !== 'undefined' -->
-      <li><a href="javascript:void(0);" data-bind="click: function (data) { showContextPopover(data, { target: $parentContext.$contextSourceElement }, { left: -15, top: 2 }); }"><i class="fa fa-fw fa-info"></i> ${ _('Show details') }</a></li>
-      <!-- ko switch: sourceType -->
-      <!-- ko case: 'solr' -->
-        <!-- ko if: catalogEntry.isTableOrView() -->
-        <li><a href="javascript:void(0);" data-bind="click: openInIndexer"><i class="fa fa-fw fa-table"></i> ${ _('Open in Browser') }</a></li>
-        <li><a href="javascript: void(0);" data-bind="click: function() { explore(true); }"><!-- ko template: { name: 'app-icon-template', data: { icon: 'dashboard' } } --><!-- /ko --> ${ _('Open in Dashboard') }</a></li>
-        <!-- /ko -->
-      <!-- /ko -->
-      <!-- ko case: $default -->
-        <!-- ko if: !catalogEntry.isDatabase() && $currentApp() === 'editor' -->
-        <li><a href="javascript:void(0);" data-bind="click: dblClick"><i class="fa fa-fw fa-paste"></i> ${ _('Insert at cursor') }</a></li>
-        <!-- /ko -->
-        % if not IS_EMBEDDED.get():
-        <!-- ko if: catalogEntry.path.length <=2 -->
-        <li><a href="javascript:void(0);" data-bind="click: openInMetastore"><i class="fa fa-fw fa-table"></i> ${ _('Open in Browser') }</a></li>
-        <!-- /ko -->
-        % endif
-        <!-- ko if: catalogEntry.isTableOrView() -->
-        <li><a href="javascript:void(0);" data-bind="click: function() { huePubSub.publish('query.and.watch', {'url': '/notebook/browse/' + databaseName + '/' + tableName + '/', sourceType: sourceType}); }"><i class="fa fa-fw fa-code"></i> ${ _('Open in Editor') }</a></li>
-        % if HAS_SQL_ENABLED.get():
-        <li><a href="javascript: void(0);" data-bind="click: function() { explore(false); }"><!-- ko template: { name: 'app-icon-template', data: { icon: 'dashboard' } } --><!-- /ko --> ${ _('Open in Dashboard') }</a></li>
-        % endif
-        <!-- /ko -->
-        %if ENABLE_QUERY_BUILDER.get():
-        <!-- ko if: catalogEntry.isColumn() && $currentApp() === 'editor' -->
-        <li class="divider"></li>
-        <!-- ko template: { name: 'query-builder-context-items' } --><!-- /ko -->
-        <!-- /ko -->
-        %endif
-      <!-- /ko -->
-      <!-- /ko -->
-    <!-- /ko -->
-  </script>
-
-  <script type="text/html" id="query-builder-context-items">
-    <li data-bind="contextSubMenu: '.hue-context-sub-menu'">
-      <a href="javascript:void(0);"><i class="fa fa-fw fa-magic"></i> ${ _('Project') }<i class="sub-icon fa fa-fw fa-chevron-right"></i></a>
-      <ul class="hue-context-menu hue-context-sub-menu">
-        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Select', 'Project', '{0}', false, false); }">${ _('Select') }</a></li>
-        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Select distinct', 'Project', '{0}', false, false); }">${ _('Select distinct') }</a></li>
-      </ul>
-    </li>
-
-    <li data-bind="contextSubMenu: '.hue-context-sub-menu'">
-      <a href="javascript:void(0);"><i class="fa fa-fw fa-magic"></i> ${ _('Aggregate') }<i class="sub-icon fa fa-fw fa-chevron-right"></i></a>
-      <ul class="hue-context-menu hue-context-sub-menu">
-        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Count', 'Aggregate', 'COUNT({0}.{1}) as count_{1}', false, false); }">${ _('Count') }</a></li>
-        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Count distinct', 'Aggregate', 'COUNT(DISTINCT {0}.{1}) as distinct_count_{1}', false, false); }">${ _('Count distinct') }</a></li>
-        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Sum', 'Aggregate', 'SUM({0}.{1}) as sum_{1}', false, false); }">${ _('Sum') }</a></li>
-        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Minimum', 'Aggregate', 'MIN({0}.{1}) as min_{1}', false, false); }">${ _('Minimum') }</a></li>
-        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Maximum', 'Aggregate', 'MAX({0}.{1}) as max_{1}', false, false); }">${ _('Maximum') }</a></li>
-        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Average', 'Aggregate', 'AVG({0}.{1}) as avg_{1}', false, false); }">${ _('Average') }</a></li>
-      </ul>
-    </li>
-
-    <li data-bind="contextSubMenu: '.hue-context-sub-menu'">
-      <a href="javascript:void(0);"><i class="fa fa-fw fa-magic"></i> ${ _('Filter') }<i class="sub-icon fa fa-fw fa-chevron-right"></i></a>
-      <ul class="hue-context-menu hue-context-sub-menu">
-        <li><a href="javascript:void(0);" data-bind="click: function () { var isNotNullRule = QueryBuilder.getRule(databaseName, tableName, columnName, 'Is not null'); if (isNotNullRule.length) { isNotNullRule.attr('rule', 'Is null'); isNotNullRule.find('.rule').text('Is null'); } else { QueryBuilder.addRule(databaseName, tableName, columnName, 'Is null', 'Filter', '{0}.{1} = null', false, false); } }">${ _('Is null') }</a></li>
-        <li><a href="javascript:void(0);" data-bind="click: function () { var isNullRule = QueryBuilder.getRule(databaseName, tableName, columnName, 'Is null'); if (isNullRule.length) { isNullRule.attr('rule', 'Is not null'); isNullRule.find('.rule').text('Is not null'); } else { QueryBuilder.addRule(databaseName, tableName, columnName, 'Is not null', 'Filter', '{0}.{1} != null', false, false); } }">${ _('Is not null') }</a></li>
-        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Equal to', 'Filter', '{0}.{1} = {2}', true, true); }">${ _('Equal to') }</a></li>
-        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Not equal to', 'Filter', '{0}.{1} != {2}', true, true); }">${ _('Not equal to') }</a></li>
-        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Greater than', 'Filter', '{0}.{1} > {2}', true, false) }">${ _('Greater than') }</a></li>
-        <li><a href="javascript:void(0);" data-bind="click: function () { QueryBuilder.addRule(databaseName, tableName, columnName, 'Less than', 'Filter', '{0}.{1} < {2}', true, false); }">${ _('Less than') }</a></li>
-      </ul>
-    </li>
-    <li data-bind="contextSubMenu: '.hue-context-sub-menu'">
-      <a href="javascript:void(0);"><i class="fa fa-fw fa-magic"></i> ${ _('Order') }<i class="sub-icon fa fa-fw fa-chevron-right"></i></a>
-      <ul class="hue-context-menu hue-context-sub-menu">
-        <li><a href="javascript:void(0);" data-bind="click: function () { var descendingRule = QueryBuilder.getRule(databaseName, tableName, columnName, 'Descending'); if (descendingRule.length) { descendingRule.attr('rule', 'Ascending'); descendingRule.find('.rule').text('Ascending'); } else { QueryBuilder.addRule(databaseName, tableName, columnName, 'Ascending', 'Order', '{0}.{1} ASC', false, false); } }">${ _('Ascending') }</a></li>
-        <li><a href="javascript:void(0);" data-bind="click: function () { var ascendingRule = QueryBuilder.getRule(databaseName, tableName, columnName, 'Ascending'); if (ascendingRule.length) { ascendingRule.attr('rule', 'Descending'); ascendingRule.find('.rule').text('Descending'); } else { QueryBuilder.addRule(databaseName, tableName, columnName, 'Descending', 'Order', '{0}.{1} DESC', false, false); } }">${ _('Descending') }</a></li>
-      </ul>
-    </li>
-  </script>
-
-  <script type="text/html" id="hdfs-context-items">
-    <li><a href="javascript:void(0);" data-bind="click: function (data) { showContextPopover(data, { target: $parentContext.$contextSourceElement }, { left: -15, top: 2 }); }"><i class="fa fa-fw fa-info"></i> ${ _('Show details') }</a></li>
-    <li><a href="javascript:void(0);" data-bind="hueLink: definition.url"><i class="fa fa-fw" data-bind="css: {'fa-folder-open-o': definition.type === 'dir', 'fa-file-text-o': definition.type === 'file'}"></i> ${ _('Open in Browser') }</a></li>
-    <!-- ko if: definition.type === 'file' -->
-    <li><a href="javascript:void(0);" data-bind="click: openInImporter"><!-- ko template: { name: 'app-icon-template', data: { icon: 'importer' } } --><!-- /ko --> ${ _('Open in Importer') }</a></li>
-    <!-- /ko -->
-    <!-- ko if: $currentApp() === 'editor' -->
-    <li><a href="javascript:void(0);" data-bind="click: dblClick"><i class="fa fa-fw fa-paste"></i> ${ _('Insert at cursor') }</a></li>
-    <!-- /ko -->
-  </script>
-
-  <script type="text/html" id="document-context-items">
-    <!-- ko if: definition().type === 'directory' -->
-    <li><a href="javascript: void(0);" data-bind="click: open"><i class="fa fa-fw fa-folder-open-o"></i> ${ _('Open folder') }</a></li>
-    <!-- /ko -->
-    <!-- ko if: definition().type !== 'directory' -->
-    <li><a href="javascript: void(0);" data-bind="click: function(data) { showContextPopover(data, { target: $parentContext.$contextSourceElement }, { left: -15, top: 2 }); }"><i class="fa fa-fw fa-info"></i> ${ _('Show details') }</a></li>
-    <li><a href="javascript: void(0);" data-bind="click: open"><i class="fa fa-fw fa-edit"></i> ${ _('Open document') }</a></li>
-    <li><a href="javascript: void(0);" data-bind="click: function() { huePubSub.publish('doc.show.delete.modal', $data); activeEntry().getSelectedDocsWithDependents(); activeEntry().showDeleteConfirmation(); }"><i class="fa fa-fw fa-trash-o"></i> ${ _('Delete document') }</a></li>
-    <!-- /ko -->
-  </script>
-
-  <script type="text/html" id="hbase-context-items">
-    <!-- ko if: definition.host -->
-    <li><a href="javascript: void(0);" data-bind="click: open"><i class="fa fa-fw fa-folder-open-o"></i> ${ _('Open cluster') }</a></li>
-    <!-- /ko -->
-    <!-- ko ifnot: definition.host -->
-    <li><a href="javascript: void(0);" data-bind="click: open"><!-- ko template: { name: 'app-icon-template', data: { icon: 'hbase' } } --><!-- /ko --> ${ _('Open in HBase') }</a></li>
-    <!-- /ko -->
-  </script>
-
-  <script type="text/html" id="assist-database-entry">
-    <li class="assist-table" data-bind="appAwareTemplateContextMenu: { template: 'sql-context-items', scrollContainer: '.assist-db-scrollable' }, visibleOnHover: { selector: '.database-actions' }">
-      <!-- ko template: { name: 'assist-database-actions' } --><!-- /ko -->
-      <a class="assist-table-link" href="javascript: void(0);" data-bind="click: function () { $parent.selectedDatabase($data); $parent.selectedDatabaseChanged(); }, attr: {'title': catalogEntry.getTitle(true) }, draggableText: { text: editorText,  meta: {'type': 'sql', 'database': databaseName} }"><i class="fa fa-fw fa-database muted valign-middle"></i> <span class="highlightable" data-bind="text: catalogEntry.name, css: { 'highlight': highlight() }"></span></a>
-    </li>
-  </script>
-
-  <script type="text/html" id="assist-table-entry">
-    <li class="assist-table" data-bind="appAwareTemplateContextMenu: { template: 'sql-context-items', scrollContainer: '.assist-db-scrollable' }, visibleOnHover: { override: statsVisible() || navigationSettings.rightAssist, selector: '.table-actions' }">
-      <div class="assist-actions table-actions" data-bind="css: { 'assist-actions-left': navigationSettings.rightAssist }" style="opacity: 0">
-        <a class="inactive-action" href="javascript:void(0)" data-bind="visible: navigationSettings.showStats, click: showContextPopover, css: { 'blue': statsVisible }"><i class="fa fa-fw fa-info" title="${_('Show details')}"></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>
-      <a class="assist-entry assist-table-link" href="javascript:void(0)" data-bind="click: toggleOpen, attr: {'title': catalogEntry.getTitle(true) }, draggableText: { text: editorText,  meta: {'type': 'sql', 'isView': catalogEntry.isView(), 'table': tableName, 'database': databaseName} }">
-        <i class="fa fa-fw muted valign-middle" data-bind="css: iconClass"></i>
-        <span class="highlightable" data-bind="text: catalogEntry.getDisplayName(navigationSettings.rightAssist), css: { 'highlight': highlight }"></span>
-      </a>
-      <div class="center assist-spinner" data-bind="visible: loading() && open()"><i class="fa fa-spinner fa-spin"></i></div>
-      <!-- ko template: { if: open, name: 'assist-db-entries'  } --><!-- /ko -->
-    </li>
-  </script>
-
-  <script type="text/html" id="assist-column-entry">
-    <li data-bind="appAwareTemplateContextMenu: { template: 'sql-context-items', scrollContainer: '.assist-db-scrollable' }, visible: ! hasErrors(), visibleOnHover: { childrenOnly: true, override: statsVisible, selector: catalogEntry.isView() ? '.table-actions' : '.column-actions' }, css: { 'assist-table': catalogEntry.isView(), 'assist-column': catalogEntry.isField() }">
-      <div class="assist-actions column-actions" data-bind="css: { 'assist-actions-left': navigationSettings.rightAssist }" style="opacity: 0">
-        <a class="inactive-action" href="javascript:void(0)" data-bind="visible: navigationSettings.showStats, click: showContextPopover, css: { 'blue': statsVisible }"><i class="fa fa-fw fa-info" title="${_('Show details')}"></i></a>
-      </div>
-      <!-- ko if: expandable -->
-      <a class="assist-entry assist-field-link" href="javascript:void(0)" data-bind="click: toggleOpen, attr: {'title': catalogEntry.getTitle(true) }, css: { 'assist-entry-left-action': navigationSettings.rightAssist }">
-        <span class="highlightable" data-bind="css: { 'highlight': highlight}, attr: {'column': columnName, 'table': tableName, 'database': databaseName }, text: catalogEntry.getDisplayName(), draggableText: { text: editorText, meta: {'type': 'sql', 'column': columnName, 'table': tableName, 'database': databaseName } }"></span><!-- ko if: catalogEntry.isPrimaryKey() --> <i class="fa fa-key"></i><!-- /ko -->
-      </a>
-      <!-- /ko -->
-      <!-- ko ifnot: expandable -->
-      <div class="assist-entry assist-field-link default-cursor" href="javascript:void(0)" data-bind="event: { dblclick: dblClick }, attr: {'title': catalogEntry.getTitle(true) }, css: { 'assist-entry-left-action': navigationSettings.rightAssist }">
-        <span class="highlightable" data-bind="css: { 'highlight': highlight}, attr: {'column': columnName, 'table': tableName, 'database': databaseName}, text: catalogEntry.getDisplayName(), draggableText: { text: editorText, meta: {'type': 'sql', 'column': columnName, 'table': tableName, 'database': databaseName} }"></span><!-- ko if: catalogEntry.isPrimaryKey()  --> <i class="fa fa-key"></i><!-- /ko -->
-      </div>
-      <!-- /ko -->
-      <div class="center assist-spinner" data-bind="visible: loading"><i class="fa fa-spinner fa-spin"></i></div>
-      <!-- ko template: { if: open, name: 'assist-db-entries'  } --><!-- /ko -->
-    </li>
-  </script>
-
-  <script type="text/html" id="assist-column-entry-assistant">
-    <li data-bind="appAwareTemplateContextMenu: { template: 'sql-context-items', scrollContainer: '.assist-db-scrollable' }, visible: ! hasErrors(), visibleOnHover: { childrenOnly: true, override: statsVisible, selector: catalogEntry.isView() ? '.table-actions' : '.column-actions' }, css: { 'assist-table': catalogEntry.isView(), 'assist-column': catalogEntry.isField() }">
-      <div class="assist-actions column-actions assist-actions-left" style="opacity: 0">
-        <a class="inactive-action" href="javascript:void(0)" data-bind="visible: navigationSettings.showStats, click: showContextPopover, css: { 'blue': statsVisible }"><i class="fa fa-fw fa-info" title="${_('Show details')}"></i></a>
-      </div>
-      <!-- ko if: expandable -->
-      <a class="assist-entry assist-field-link assist-field-link-dark assist-entry-left-action assist-ellipsis" href="javascript:void(0)" data-bind="click: toggleOpen, attr: {'title': catalogEntry.getTitle(true) }">
-        <span data-bind="text: catalogEntry.getType()" class="muted pull-right margin-right-20"></span>
-        <span class="highlightable" data-bind="css: { 'highlight': highlight}, attr: {'column': columnName, 'table': tableName, 'database': databaseName }, text: catalogEntry.name, draggableText: { text: editorText, meta: {'type': 'sql', 'column': columnName, 'table': tableName, 'database': databaseName } }"></span><!-- ko if: catalogEntry.isPrimaryKey() --> <i class="fa fa-key"></i><!-- /ko -->
-      </a>
-      <!-- /ko -->
-      <!-- ko ifnot: expandable -->
-      <div class="assist-entry assist-field-link assist-field-link-dark default-cursor assist-ellipsis" href="javascript:void(0)" data-bind="event: { dblclick: dblClick }, attr: {'title': catalogEntry.getTitle(true) }, css: { 'assist-entry-left-action': navigationSettings.rightAssist }">
-        <span data-bind="text: catalogEntry.getType()" class="muted pull-right margin-right-20"></span>
-        <span class="highlightable" data-bind="css: { 'highlight': highlight}, attr: {'column': columnName, 'table': tableName, 'database': databaseName}, text: catalogEntry.name, draggableText: { text: editorText, meta: {'type': 'sql', 'column': columnName, 'table': tableName, 'database': databaseName} }"></span><!-- ko if: catalogEntry.isPrimaryKey() --> <i class="fa fa-key"></i><!-- /ko -->
-      </div>
-      <!-- /ko -->
-      <div class="center assist-spinner" data-bind="visible: loading"><i class="fa fa-spinner fa-spin"></i></div>
-      <!-- ko template: { if: open, name: 'assist-db-entries'  } --><!-- /ko -->
-    </li>
-  </script>
-
-  <script type="text/html" id="assist-db-entries">
-    <!-- ko if: ! hasErrors() && hasEntries() && ! loading() && filteredEntries().length == 0 -->
-    <ul class="assist-tables">
-      <li class="assist-entry assist-no-entries"><!-- ko if: catalogEntry.isTableOrView() -->${_('No columns found')}<!--/ko--><!-- ko if: catalogEntry.isDatabase() -->${_('No tables found')}<!--/ko--><!-- ko if: catalogEntry.isField() -->${_('No results found')}<!--/ko--></li>
-    </ul>
-    <!-- /ko -->
-    <!-- ko if: ! hasErrors() && hasEntries() && ! loading() && filteredEntries().length > 0 -->
-    <ul class="database-tree" data-bind="foreachVisible: { data: filteredEntries, minHeight: navigationSettings.rightAssist ? 22 : 23, container: '.assist-db-scrollable', skipScrollEvent: navigationSettings.rightAssist, usePreloadBackground: true }, css: { 'assist-tables': catalogEntry.isDatabase() }">
-      <!-- ko template: { if: catalogEntry.isTableOrView(), name: 'assist-table-entry' } --><!-- /ko -->
-      <!-- ko if: navigationSettings.rightAssist -->
-        <!-- ko template: { ifnot: catalogEntry.isTableOrView(), name: 'assist-column-entry-assistant' } --><!-- /ko -->
-      <!-- /ko -->
-      <!-- ko ifnot: navigationSettings.rightAssist -->
-        <!-- ko template: { ifnot: catalogEntry.isTableOrView(), name: 'assist-column-entry' } --><!-- /ko -->
-      <!-- /ko -->
-    </ul>
-    <!-- /ko -->
-    <!-- ko template: { if: ! hasErrors() && ! hasEntries() && ! loading() && (catalogEntry.isTableOrView()), name: 'assist-no-table-entries' } --><!-- /ko -->
-    <!-- ko template: { if: ! hasErrors() && ! hasEntries() && ! loading() && catalogEntry.isDatabase(), name: 'assist-no-database-entries' } --><!-- /ko -->
-    <!-- ko if: hasErrors -->
-    <ul class="assist-tables">
-      <!-- ko if: catalogEntry.isTableOrView() -->
-      <li class="assist-errors">${ _('Error loading columns.') }</li>
-      <!-- /ko -->
-      <!-- ko if: catalogEntry.isField() -->
-      <li class="assist-errors">${ _('Error loading fields.') }</li>
-      <!-- /ko -->
-    </ul>
-    <!-- /ko -->
-  </script>
-
-  <script type="text/html" id="assist-db-breadcrumb">
-    <div class="assist-flex-header assist-breadcrumb">
-      <!-- ko if: selectedSource() -->
-      <!-- ko if: selectedSource().selectedNamespace() -->
-      <!-- ko if: selectedSource().selectedNamespace().selectedDatabase() -->
-      <a data-bind="click: back, appAwareTemplateContextMenu: { template: 'sql-context-items', viewModel: selectedSource().selectedNamespace().selectedDatabase() }">
-        <i class="fa fa-chevron-left assist-breadcrumb-back" ></i>
-        <i class="fa assist-breadcrumb-text" data-bind="css: { 'fa-server': nonSqlType, 'fa-database': !nonSqlType }"></i>
-        <span class="assist-breadcrumb-text" data-bind="text: breadcrumb, attr: {'title': breadcrumb() +  (nonSqlType ? '' : ' (' + selectedSource().sourceType + ' ' + selectedSource().selectedNamespace().name + ')') }"></span>
-      </a>
-      <!-- /ko -->
-      <!-- ko ifnot: selectedSource().selectedNamespace().selectedDatabase() -->
-      <!-- ko if: window.HAS_MULTI_CLUSTER-->
-      <a data-bind="click: back">
-        <i class="fa fa-chevron-left assist-breadcrumb-back"></i>
-        <i class="fa fa-snowflake-o assist-breadcrumb-text"></i>
-        <span class="assist-breadcrumb-text" data-bind="text: breadcrumb, attr: {'title': breadcrumb() + ' (' + selectedSource().sourceType + ')' }"></span>
-      </a>
-      <!-- /ko -->
-      <!-- ko ifnot: window.HAS_MULTI_CLUSTER -->
-      <a data-bind="click: back">
-        <i class="fa fa-chevron-left assist-breadcrumb-back"></i>
-        <i class="fa fa-server assist-breadcrumb-text"></i>
-        <span class="assist-breadcrumb-text" data-bind="text: breadcrumb"></span>
-      </a>
-      <!-- /ko -->
-      <!-- /ko -->
-      <!-- /ko -->
-      <!-- ko ifnot: selectedSource().selectedNamespace() -->
-      <a data-bind="click: back">
-        <i class="fa fa-chevron-left assist-breadcrumb-back"></i>
-        <i class="fa fa-server assist-breadcrumb-text"></i>
-        <span class="assist-breadcrumb-text" data-bind="text: breadcrumb"></span>
-      </a>
-      <!-- /ko -->
-      <!-- /ko -->
-    </div>
-  </script>
-
-  <script type="text/html" id="assist-sql-inner-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: selectedNamespace, name: 'assist-namespaces-template' } --><!-- /ko -->
-      <!-- ko with: selectedNamespace -->
-        <!-- ko template: { ifnot: selectedDatabase, name: 'assist-databases-template' } --><!-- /ko -->
-        <!-- ko with: selectedDatabase -->
-          <!-- ko template: { name: 'assist-tables-template' } --><!-- /ko -->
-        <!-- /ko -->
-      <!-- /ko -->
-    <!-- /ko -->
-  </script>
-
-  <script type="text/html" id="assist-s3-header-actions">
-    <div class="assist-db-header-actions">
-      <a class="inactive-action" href="javascript:void(0)" data-bind="click: function () { huePubSub.publish('assist.s3.refresh'); }"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }" title="${_('Manual refresh')}"></i></a>
-    </div>
-  </script>
-
-  <script type="text/html" id="assist-s3-inner-panel">
-    <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
-    <!-- ko with: selectedS3Entry -->
-    <div class="assist-flex-header assist-breadcrumb" >
-      <!-- ko if: parent !== null -->
-      <a href="javascript: void(0);" data-bind="appAwareTemplateContextMenu: { template: 'hdfs-context-items', scrollContainer: '.assist-s3-scrollable' }, click: function () { huePubSub.publish('assist.selectS3Entry', parent); }">
-        <i class="fa fa-fw fa-chevron-left"></i>
-        <i class="fa fa-fw fa-folder-o"></i>
-        <span data-bind="text: definition.name, tooltip: {'title': path, 'placement': 'top' }"></span>
-      </a>
-      <!-- /ko -->
-      <!-- ko if: parent === null -->
-      <div>
-        <i class="fa fa-fw fa-folder-o"></i>
-        <span data-bind="text: path"></span>
-      </div>
-      <!-- /ko -->
-      <!-- ko template: 'assist-s3-header-actions' --><!-- /ko -->
-    </div>
-    <div class="assist-flex-search">
-      <div class="assist-filter"><input class="clearable" type="text" placeholder="${ _('Filter...') }" data-bind="clearable: filter, value: filter, valueUpdate: 'afterkeydown'"/></div>
-    </div>
-    <div class="assist-flex-fill assist-s3-scrollable" data-bind="delayedOverflow">
-      <div data-bind="visible: ! loading() && ! hasErrors()" style="position: relative;">
-        <!-- ko hueSpinner: { spin: loadingMore, overlay: true } --><!-- /ko -->
-        <ul class="assist-tables" data-bind="foreachVisible: { data: entries, minHeight: 22, container: '.assist-s3-scrollable', fetchMore: $data.fetchMore.bind($data) }">
-          <li class="assist-entry assist-table-link" style="position: relative;" data-bind="appAwareTemplateContextMenu: { template: 'hdfs-context-items', scrollContainer: '.assist-s3-scrollable' }, visibleOnHover: { override: contextPopoverVisible, 'selector': '.assist-actions' }">
-            <div class="assist-actions table-actions" style="opacity: 0;" >
-              <a style="padding: 0 3px;" class="inactive-action" href="javascript:void(0);" data-bind="click: showContextPopover, css: { 'blue': contextPopoverVisible }">
-                <i class='fa fa-info' title="${ _('Details') }"></i>
-              </a>
-            </div>
-
-            <a href="javascript:void(0)" class="assist-entry assist-table-link" data-bind="multiClick: { click: toggleOpen, dblClick: dblClick }, attr: {'title': definition.name }">
-              <!-- ko if: definition.type === 'dir' -->
-              <i class="fa fa-fw fa-folder-o muted valign-middle"></i>
-              <!-- /ko -->
-              <!-- ko if: definition.type === 'file' -->
-              <i class="fa fa-fw fa-file-o muted valign-middle"></i>
-              <!-- /ko -->
-              <span draggable="true" data-bind="text: definition.name, draggableText: { text: '\'' + path + '\'', meta: {'type': 's3', 'definition': definition} }"></span>
-            </a>
-          </li>
-        </ul>
-        <!-- ko if: !loading() && entries().length === 0 -->
-        <ul class="assist-tables">
-          <li class="assist-entry"><span class="assist-no-entries"><!-- ko if: filter() -->${_('No results found')}<!-- /ko --><!-- ko ifnot: filter() -->${_('Empty directory')}<!-- /ko --></span></li>
-        </ul>
-        <!-- /ko -->
-      </div>
-      <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
-      <div class="assist-errors" data-bind="visible: ! loading() && hasErrors()">
-        <span>${ _('Error loading contents.') }</span>
-      </div>
-    </div>
-    <!-- /ko -->
-  </script>
-
-  <script type="text/html" id="git-details-title">
-    <span data-bind="text: definition.name"></span>
-  </script>
-
-  <script type="text/html" id="assist-git-header-actions">
-    <div class="assist-db-header-actions">
-      <a class="inactive-action" href="javascript:void(0)" data-bind="click: function () { huePubSub.publish('assist.git.refresh'); }"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }" title="${_('Manual refresh')}"></i></a>
-    </div>
-  </script>
-
-  <script type="text/html" id="assist-git-inner-panel">
-    <!-- ko with: selectedGitEntry -->
-    <div class="assist-flex-header assist-breadcrumb" >
-      <!-- ko if: parent !== null -->
-      <a href="javascript: void(0);" data-bind="click: function () { huePubSub.publish('assist.selectGitEntry', parent); }">
-        <i class="fa fa-fw fa-chevron-left"></i>
-        <i class="fa fa-fw fa-folder-o"></i>
-        <span data-bind="text: path, attr: {'title': path }"></span>
-      </a>
-      <!-- /ko -->
-      <!-- ko if: parent === null -->
-      <div>
-        <i class="fa fa-fw fa-folder-o"></i>
-        <span data-bind="text: path"></span>
-      </div>
-      <!-- /ko -->
-      <!-- ko template: 'assist-git-header-actions' --><!-- /ko -->
-    </div>
-    <div class="assist-flex-fill assist-git-scrollable" data-bind="delayedOverflow">
-      <div data-bind="visible: ! loading() && ! hasErrors()" style="position: relative;">
-        <!-- ko hueSpinner: { spin: loadingMore, overlay: true } --><!-- /ko -->
-        <ul class="assist-tables" data-bind="foreachVisible: { data: entries, minHeight: 22, container: '.assist-git-scrollable' }">
-          <li class="assist-entry assist-table-link" style="position: relative;" data-bind="visibleOnHover: { 'selector': '.assist-actions' }">
-
-            <a href="javascript:void(0)" class="assist-entry assist-table-link" data-bind="multiClick: { click: toggleOpen, dblClick: dblClick }, attr: {'title': definition.name }">
-              <!-- ko if: definition.type === 'dir' -->
-              <i class="fa fa-fw fa-folder-o muted valign-middle"></i>
-              <!-- /ko -->
-              <!-- ko ifnot: definition.type === 'dir' -->
-              <i class="fa fa-fw fa-file-o muted valign-middle"></i>
-              <!-- /ko -->
-              <span draggable="true" data-bind="text: definition.name, draggableText: { text: '\'' + path + '\'', meta: {'type': 'git', 'definition': definition} }"></span>
-            </a>
-          </li>
-        </ul>
-        <!-- ko if: !loading() && entries().length === 0 -->
-        <ul class="assist-tables">
-          <li class="assist-entry"><span class="assist-no-entries">${_('Empty directory')}</span></li>
-        </ul>
-        <!-- /ko -->
-      </div>
-      <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
-      <div class="assist-errors" data-bind="visible: ! loading() && hasErrors()">
-        <span>${ _('Error loading contents.') }</span>
-      </div>
-    </div>
-    <!-- /ko -->
-  </script>
-
-  <script type="text/html" id="assist-hdfs-header-actions">
-    <div class="assist-db-header-actions">
-      <a class="inactive-action" href="javascript:void(0)" data-bind="click: goHome, attr: { title: I18n('Go to ' + window.USER_HOME_DIR) }"><i class="pointer fa fa-home"></i></a>
-      % if hasattr(SHOW_UPLOAD_BUTTON, 'get') and SHOW_UPLOAD_BUTTON.get():
-      <a class="inactive-action" data-bind="dropzone: {
-            url: '/filebrowser/upload/file?dest=' + path,
-            params: { dest: path },
-            paramName: 'hdfs_file',
-            onError: function(x, e){ $(document).trigger('error', e); },
-            onComplete: function () { huePubSub.publish('assist.hdfs.refresh'); huePubSub.publish('fb.hdfs.refresh', path); } }" title="${_('Upload file')}" href="javascript:void(0)">
-        <div class="dz-message inline" data-dz-message><i class="pointer fa fa-plus" title="${_('Upload file')}"></i></div>
-      </a>
-      % endif
-      <a class="inactive-action" href="javascript:void(0)" data-bind="click: function () { huePubSub.publish('assist.hdfs.refresh'); }" title="${_('Manual refresh')}"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }"></i></a>
-    </div>
-  </script>
-
-  <script type="text/html" id="assist-adls-header-actions">
-    <div class="assist-db-header-actions">
-      <a class="inactive-action" href="javascript:void(0)" data-bind="click: goHome, attr: { title: I18n('Go to ' + window.USER_HOME_DIR) }"><i class="pointer fa fa-home"></i></a>
-      % if hasattr(SHOW_UPLOAD_BUTTON, 'get') and SHOW_UPLOAD_BUTTON.get():
-      <a class="inactive-action" data-bind="dropzone: {
-            url: '/filebrowser/upload/file?dest=adl:' + path,
-            params: { dest: path },
-            paramName: 'hdfs_file',
-            onError: function(x, e){ $(document).trigger('error', e); },
-            onComplete: function () { huePubSub.publish('assist.adls.refresh'); } }" title="${_('Upload file')}" href="javascript:void(0)">
-        <div class="dz-message inline" data-dz-message><i class="pointer fa fa-plus" title="${_('Upload file')}"></i></div>
-      </a>
-      % endif
-      <a class="inactive-action" href="javascript:void(0)" data-bind="click: function () { huePubSub.publish('assist.adls.refresh'); }" title="${_('Manual refresh')}"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }"></i></a>
-    </div>
-  </script>
-
-  <script type="text/html" id="assist-hdfs-inner-panel">
-    <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
-    <!-- ko with: selectedHdfsEntry -->
-    <div class="assist-flex-header assist-breadcrumb" >
-      <!-- ko if: parent !== null -->
-      <a href="javascript: void(0);" data-bind="appAwareTemplateContextMenu: { template: 'hdfs-context-items', scrollContainer: '.assist-hdfs-scrollable' }, click: function () { huePubSub.publish('assist.selectHdfsEntry', parent); }">
-        <i class="fa fa-fw fa-chevron-left"></i>
-        <i class="fa fa-fw fa-folder-o"></i>
-        <span data-bind="text: definition.name, tooltip: {'title': path, 'placement': 'top' }"></span>
-      </a>
-      <!-- /ko -->
-      <!-- ko if: parent === null -->
-      <div>
-        <i class="fa fa-fw fa-folder-o"></i>
-        <span data-bind="text: path"></span>
-      </div>
-      <!-- /ko -->
-      <!-- ko template: 'assist-hdfs-header-actions' --><!-- /ko -->
-    </div>
-    <div class="assist-flex-search">
-      <div class="assist-filter"><input class="clearable" type="text" placeholder="${ _('Filter...') }" data-bind="clearable: filter, value: filter, valueUpdate: 'afterkeydown'"/></div>
-    </div>
-    <div class="assist-flex-fill assist-hdfs-scrollable" data-bind="delayedOverflow">
-      <div data-bind="visible: ! loading() && ! hasErrors()" style="position: relative;">
-        <!-- ko hueSpinner: { spin: loadingMore, overlay: true } --><!-- /ko -->
-        <ul class="assist-tables" data-bind="foreachVisible: { data: entries, minHeight: 22, container: '.assist-hdfs-scrollable', fetchMore: $data.fetchMore.bind($data) }">
-          <li class="assist-entry assist-table-link" style="position: relative;" data-bind="appAwareTemplateContextMenu: { template: 'hdfs-context-items', scrollContainer: '.assist-hdfs-scrollable' }, visibleOnHover: { override: contextPopoverVisible, 'selector': '.assist-actions' }">
-            <div class="assist-actions table-actions" style="opacity: 0;" >
-              <a style="padding: 0 3px;" class="inactive-action" href="javascript:void(0);" data-bind="click: showContextPopover, css: { 'blue': contextPopoverVisible }">
-                <i class='fa fa-info' title="${ _('Details') }"></i>
-              </a>
-            </div>
-
-            <a href="javascript:void(0)" class="assist-entry assist-table-link" data-bind="multiClick: { click: toggleOpen, dblClick: dblClick }, attr: {'title': definition.name }">
-              <!-- ko if: definition.type === 'dir' -->
-              <i class="fa fa-fw fa-folder-o muted valign-middle"></i>
-              <!-- /ko -->
-              <!-- ko if: definition.type === 'file' -->
-              <i class="fa fa-fw fa-file-o muted valign-middle"></i>
-              <!-- /ko -->
-              <span draggable="true" data-bind="text: definition.name, draggableText: { text: '\'' + path + '\'', meta: {'type': 'hdfs', 'definition': definition} }"></span>
-            </a>
-          </li>
-        </ul>
-        <!-- ko if: !loading() && entries().length === 0 -->
-        <ul class="assist-tables">
-          <li class="assist-entry"><span class="assist-no-entries"><!-- ko if: filter() -->${_('No results found')}<!-- /ko --><!-- ko ifnot: filter() -->${_('Empty directory')}<!-- /ko --></span></li>
-        </ul>
-        <!-- /ko -->
-      </div>
-      <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
-      <div class="assist-errors" data-bind="visible: ! loading() && hasErrors()">
-        <span>${ _('Error loading contents.') }</span>
-      </div>
-    </div>
-    <!-- /ko -->
-  </script>
-
-  <script type="text/html" id="assist-adls-inner-panel">
-    <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
-    <!-- ko with: selectedAdlsEntry -->
-    <div class="assist-flex-header assist-breadcrumb" >
-      <!-- ko if: parent !== null -->
-      <a href="javascript: void(0);" data-bind="appAwareTemplateContextMenu: { template: 'hdfs-context-items', scrollContainer: '.assist-adls-scrollable' }, click: function () { huePubSub.publish('assist.selectAdlsEntry', parent); }">
-        <i class="fa fa-fw fa-chevron-left"></i>
-        <i class="fa fa-fw fa-folder-o"></i>
-        <span data-bind="text: definition.name, tooltip: {'title': path, 'placement': 'top' }"></span>
-      </a>
-      <!-- /ko -->
-      <!-- ko if: parent === null -->
-      <div>
-        <i class="fa fa-fw fa-folder-o"></i>
-        <span data-bind="text: path"></span>
-      </div>
-      <!-- /ko -->
-      <!-- ko template: 'assist-adls-header-actions' --><!-- /ko -->
-    </div>
-    <div class="assist-flex-search">
-      <div class="assist-filter"><input class="clearable" type="text" placeholder="${ _('Filter...') }" data-bind="clearable: filter, value: filter, valueUpdate: 'afterkeydown'"/></div>
-    </div>
-    <div class="assist-flex-fill assist-adls-scrollable" data-bind="delayedOverflow">
-      <div data-bind="visible: ! loading() && ! hasErrors()" style="position: relative;">
-        <!-- ko hueSpinner: { spin: loadingMore, overlay: true } --><!-- /ko -->
-        <ul class="assist-tables" data-bind="foreachVisible: { data: entries, minHeight: 22, container: '.assist-adls-scrollable', fetchMore: $data.fetchMore.bind($data) }">
-          <li class="assist-entry assist-table-link" style="position: relative;" data-bind="appAwareTemplateContextMenu: { template: 'hdfs-context-items', scrollContainer: '.assist-adls-scrollable' }, visibleOnHover: { override: contextPopoverVisible, 'selector': '.assist-actions' }">
-            <div class="assist-actions table-actions" style="opacity: 0;" >
-              <a style="padding: 0 3px;" class="inactive-action" href="javascript:void(0);" data-bind="click: showContextPopover, css: { 'blue': contextPopoverVisible }">
-                <i class='fa fa-info' title="${ _('Details') }"></i>
-              </a>
-            </div>
-
-            <a href="javascript:void(0)" class="assist-entry assist-table-link" data-bind="multiClick: { click: toggleOpen, dblClick: dblClick }, attr: {'title': definition.name }">
-              <!-- ko if: definition.type === 'dir' -->
-              <i class="fa fa-fw fa-folder-o muted valign-middle"></i>
-              <!-- /ko -->
-              <!-- ko if: definition.type === 'file' -->
-              <i class="fa fa-fw fa-file-o muted valign-middle"></i>
-              <!-- /ko -->
-              <span draggable="true" data-bind="text: definition.name, draggableText: { text: '\'' + path + '\'', meta: {'type': 'adls', 'definition': definition} }"></span>
-            </a>
-          </li>
-        </ul>
-        <!-- ko if: !loading() && entries().length === 0 -->
-        <ul class="assist-tables">
-          <li class="assist-entry"><span class="assist-no-entries"><!-- ko if: filter() -->${_('No results found')}<!-- /ko --><!-- ko ifnot: filter() -->${_('Empty directory')}<!-- /ko --></span></li>
-        </ul>
-        <!-- /ko -->
-      </div>
-      <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
-      <div class="assist-errors" data-bind="visible: ! loading() && hasErrors()">
-        <span>${ _('Error loading contents.') }</span>
-      </div>
-    </div>
-    <!-- /ko -->
-  </script>
-
-  <script type="text/html" id="assist-document-header-actions">
-    <div class="assist-db-header-actions">
-      <!-- ko if: !loading() -->
-      <div class="highlightable" data-bind="css: { 'highlight': $parent.highlightTypeFilter() }, component: { name: 'hue-drop-down', params: { fixedPosition: true, value: typeFilter, searchable: true, entries: DOCUMENT_TYPES, linkTitle: '${ _ko('Document type') }' } }" style="display: inline-block"></div>
-      <!-- /ko -->
-      <span class="dropdown new-document-drop-down">
-
-        <a class="inactive-action dropdown-toggle" data-toggle="dropdown" data-bind="dropdown" href="javascript:void(0);">
-          <i class="pointer fa fa-plus" title="${ _('New document') }"></i>
-        </a>
-        <ul class="dropdown-menu less-padding document-types" style="margin-top:3px; margin-left:-140px; width: 175px;position: absolute;" role="menu">
-            % if 'beeswax' in apps:
-              <li>
-                <a title="${_('Hive Query')}"
-                % if is_embeddable:
-                  data-bind="click: function() { huePubSub.publish('open.editor.new.query', {type: 'hive', 'directoryUuid': $data.getDirectory()}); }" href="javascript:void(0);"
-                % else:
-                  data-bind="click: function() { $('.new-document-drop-down').removeClass('open');}, hueLink: $data.addDirectoryParamToUrl('${ url('notebook:editor') }?type=hive')"
-                % endif
-                >
-                  <!-- ko template: { name: 'app-icon-template', data: { icon: 'hive' } } --><!-- /ko --> ${_('Hive Query')}
-                </a>
-              </li>
-            % endif
-            % if 'impala' in apps:
-              <li>
-                <a title="${_('Impala Query')}" class="dropdown-item"
-                % if is_embeddable:
-                  data-bind="click: function() { huePubSub.publish('open.editor.new.query', {type: 'impala', 'directoryUuid': $data.getDirectory()}); }" href="javascript:void(0);"
-                % else:
-                  data-bind="click: function() { $('.new-document-drop-down').removeClass('open');}, hueLink: $data.addDirectoryParamToUrl('${ url('notebook:editor') }?type=impala')"
-                % endif
-                >
-                  <!-- ko template: { name: 'app-icon-template', data: { icon: 'impala' } } --><!-- /ko --> ${_('Impala Query')}
-                </a>
-            </li>
-            % endif
-            <%
-            from notebook.conf import SHOW_NOTEBOOKS
-            %>
-            % if SHOW_NOTEBOOKS.get():
-              <li>
-                <a title="${_('Notebook')}" data-bind="click: function() { $('.new-document-drop-down').removeClass('open');}, hueLink: $data.addDirectoryParamToUrl('${ url('notebook:index') }')">
-                  <!-- ko template: { name: 'app-icon-template', data: { icon: 'notebook' } } --><!-- /ko --> ${_('Notebook')}
-                </a>
-              </li>
-            % endif
-            % if 'pig' in apps:
-              <li>
-                <a title="${_('Pig Script')}" data-bind="click: function() { $('.new-document-drop-down').removeClass('open');}, hueLink: $data.addDirectoryParamToUrl('${ url('pig:index') }')">
-                  <!-- ko template: { name: 'app-icon-template', data: { icon: 'pig' } } --><!-- /ko --> ${_('Pig Script')}
-                </a>
-              </li>
-            % endif
-            % if 'oozie' in apps:
-              <li>
-                <a title="${_('Oozie Workflow')}" data-bind="click: function() { $('.new-document-drop-down').removeClass('open');}, hueLink: $data.addDirectoryParamToUrl('${ url('oozie:new_workflow') }')">
-                  <!-- ko template: { name: 'app-icon-template', data: { icon: 'oozie-workflow' } } --><!-- /ko --> ${_('Workflow') if is_embeddable else _('Oozie Workflow')}
-                </a>
-              </li>
-              <li>
-                <a title="${_('Oozie Schedule')}" data-bind="click: function() { $('.new-document-drop-down').removeClass('open');}, hueLink: $data.addDirectoryParamToUrl('${ url('oozie:new_coordinator') }')">
-                  <!-- ko template: { name: 'app-icon-template', data: { icon: 'oozie-coordinator' } } --><!-- /ko --> ${_('Schedule') if is_embeddable else _('Oozie Coordinator')}
-                </a>
-              </li>
-              <li>
-                <a title="${_('Oozie Bundle')}" data-bind="click: function() { $('.new-document-drop-down').removeClass('open');}, hueLink: $data.addDirectoryParamToUrl('${ url('oozie:new_bundle') }')">
-                  <!-- ko template: { name: 'app-icon-template', data: { icon: 'oozie-bundle' } } --><!-- /ko --> ${_('Bundle') if is_embeddable else _('Oozie Bundle')}
-                </a>
-              </li>
-            % endif
-            % if 'search' in apps:
-              <li>
-                <a title="${_('Solr Search')}" data-bind="click: function() { $('.new-document-drop-down').removeClass('open');}, hueLink: $data.addDirectoryParamToUrl('${ url('search:new_search') }')">
-                  <!-- ko template: { name: 'app-icon-template', data: { icon: 'dashboard' } } --><!-- /ko --> ${_('Dashboard')}
-                </a>
-              </li>
-            % endif
-            <li class="divider"></li>
-            <li data-bind="css: { 'disabled': $data.isTrash() || $data.isTrashed() || !$data.canModify() }">
-              <a href="javascript:void(0);" data-bind="click: function () { $('.new-document-drop-down').removeClass('open'); huePubSub.publish('show.create.directory.modal', $data); $data.showNewDirectoryModal()}"><svg class="hi"><use xlink:href="#hi-folder"></use><use xlink:href="#hi-plus-addon"></use></svg> ${_('New folder')}</a>
-            </li>
-          </ul>
-      </span>
-      <a class="inactive-action" href="javascript:void(0)" data-bind="click: function () { huePubSub.publish('assist.document.refresh'); }"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }" title="${_('Manual refresh')}"></i></a>
-    </div>
-  </script>
-
-  <script type="text/html" id="assist-documents-inner-panel">
-    <!-- ko with: activeEntry -->
-    <div class="assist-flex-header assist-breadcrumb" style="overflow: visible">
-      <!-- ko ifnot: isRoot -->
-      <a href="javascript: void(0);" data-bind="click: function () { if (loaded()) { parent.makeActive(); } }">
-        <i class="fa fa-fw fa-chevron-left"></i>
-        <i class="fa fa-fw fa-folder-o"></i>
-        <span data-bind="text: definition().name, attr: {'title': definition().name }"></span>
-      </a>
-      <!-- /ko -->
-      <!-- ko if: isRoot -->
-      <div>
-        <i class="fa fa-fw fa-folder-o"></i>
-        <span>/</span>
-      </div>
-      <!-- /ko -->
-      <!-- ko template: 'assist-document-header-actions' --><!-- /ko -->
-    </div>
-    <div class="assist-flex-search">
-      <div class="assist-filter"><input class="clearable" type="text" placeholder="${ _('Filter...') }" data-bind="clearable: filter, value: filter, valueUpdate: 'afterkeydown'"/></div>
-    </div>
-    <div class="assist-flex-fill assist-file-scrollable" data-bind="delayedOverflow">
-      <div data-bind="visible: ! loading() && ! hasErrors() && entries().length > 0">
-        <!-- ko if: filteredEntries().length == 0 -->
-        <ul class="assist-tables">
-          <li class="assist-entry"><span class="assist-no-entries">${_('No documents found')}</span></li>
-        </ul>
-        <!-- /ko -->
-        <ul class="assist-tables" data-bind="foreachVisible: { data: filteredEntries, minHeight: 27, container: '.assist-file-scrollable' }">
-          <li class="assist-entry assist-file-entry" data-bind="appAwareTemplateContextMenu: { template: 'document-context-items', scrollContainer: '.assist-file-scrollable', beforeOpen: beforeContextOpen }, assistFileDroppable, assistFileDraggable, visibleOnHover: { 'selector': '.assist-file-actions' }">
-            <div class="assist-file-actions table-actions">
-              <a class="inactive-action" href="javascript:void(0)" data-bind="click: showContextPopover, css: { 'blue': statsVisible }"><i class="fa fa-fw fa-info" title="${_('Show details')}"></i></a>
-            </div>
-            <a href="javascript:void(0)" class="assist-entry assist-document-link" data-bind="click: open, attr: {'title': name }">
-              <!-- ko template: { name: 'document-icon-template', data: { document: $data, showShareAddon: false } } --><!-- /ko -->
-              <span class="highlightable" data-bind="css: { 'highlight': highlight }, text: definition().name"></span>
-            </a>
-          </li>
-        </ul>
-      </div>
-      <div data-bind="visible: !loading() && ! hasErrors() && entries().length === 0">
-        <span class="assist-no-entries">${_('Empty directory')}</span>
-      </div>
-      <div class="center" data-bind="visible: loading() && ! hasErrors()">
-        <i class="fa fa-spinner fa-spin" style="font-size: 20px; color: #BBB"></i>
-      </div>
-      <div class="assist-errors" data-bind="visible: ! loading() && hasErrors()">
-        <span>${ _('Error loading contents.') }</span>
-      </div>
-    </div>
-    <!-- /ko -->
-  </script>
-
-  <script type="text/html" id="assist-hbase-header-actions">
-    <div class="assist-db-header-actions">
-      <a class="inactive-action" href="javascript:void(0)" data-bind="click: function () { huePubSub.publish('assist.hbase.refresh'); }"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }" title="${_('Manual refresh')}"></i></a>
-    </div>
-  </script>
-
-  <script type="text/html" id="assist-hbase-inner-panel">
-    <!-- ko with: selectedHBaseEntry -->
-    <div class="assist-inner-header assist-breadcrumb" >
-      <!-- ko if: definition.host !== '' -->
-      <a href="javascript: void(0);" data-bind="click: function () { huePubSub.publish('assist.clickHBaseRootItem'); }">
-        <i class="fa fa-fw fa-chevron-left"></i>
-        <i class="fa fa-fw fa-th-large"></i>
-        <span data-bind="text: definition.name"></span>
-      </a>
-      <!-- /ko -->
-      <!-- ko if: definition.host === '' -->
-      ${_('Clusters')}
-      <!-- /ko -->
-
-      <!-- ko template: 'assist-hbase-header-actions' --><!-- /ko -->
-    </div>
-    <div class="assist-flex-fill assist-hbase-scrollable" data-bind="delayedOverflow">
-      <div data-bind="visible: ! loading() && ! hasErrors()" style="position: relative;">
-        <ul class="assist-tables" data-bind="foreachVisible: { data: entries, minHeight: 22, container: '.assist-hbase-scrollable' }">
-          <li class="assist-entry assist-table-link" style="position: relative;" data-bind="appAwareTemplateContextMenu: { template: 'hbase-context-items', scrollContainer: '.assist-hbase-scrollable' }, visibleOnHover: { 'selector': '.assist-actions' }">
-            <a href="javascript:void(0)" class="assist-entry assist-table-link" data-bind="multiClick: { click: click, dblClick: dblClick }, attr: {'title': definition.name }">
-              <!-- ko if: definition.host -->
-              <i class="fa fa-fw fa-th-large muted valign-middle"></i>
-              <!-- /ko -->
-              <!-- ko ifnot: definition.host -->
-              <i class="fa fa-fw fa-th muted valign-middle"></i>
-              <!-- /ko -->
-              <span draggable="true" data-bind="text: definition.name, draggableText: { text: '\'' + definition.name + '\'', meta: {'type': 'collection', 'definition': definition} }"></span>
-            </a>
-          </li>
-        </ul>
-        <!-- ko if: !loading() && entries().length === 0 -->
-        <ul class="assist-tables">
-          <li class="assist-entry">
-            <span class="assist-no-entries">
-            <!-- ko if: definition.host === '' -->
-            ${_('No clusters available.')}
-            <!-- /ko -->
-            <!-- ko if: definition.host !== '' -->
-            ${_('No tables available.')}
-            <!-- /ko -->
-            </span>
-          </li>
-        </ul>
-        <!-- /ko -->
-      </div>
-      <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
-      <div class="assist-errors" data-bind="visible: ! loading() && hasErrors()">
-        <span>${ _('Error loading contents.') }</span>
-      </div>
-    </div>
-    <!-- /ko -->
-  </script>
-
-  <script type="text/html" id="ask-for-invalidate-title">
-    &nbsp;<a class="pull-right pointer close-popover inactive-action">&times;</a>
-  </script>
-
-  <script type="text/html" id="ask-for-invalidate-content">
-    <label class="radio">
-      <input type="radio" name="refreshImpala" value="cache" data-bind="checked: invalidateOnRefresh" />
-      ${ _('Clear cache') }
-    </label>
-    <label class="radio">
-      <input type="radio" name="refreshImpala" value="invalidate" data-bind="checked: invalidateOnRefresh" />
-      ${ _('Perform incremental metadata update.') }
-    </label>
-    <div class="assist-invalidate-description">${ _('This will sync missing tables.') }</div>
-    <label class="radio">
-      <input type="radio" name="refreshImpala" value="invalidateAndFlush" data-bind="checked: invalidateOnRefresh"  />
-      ${ _('Invalidate all metadata and rebuild index.') }
-    </label>
-    <div class="assist-invalidate-description">${ _('WARNING: This can be both resource and time-intensive.') }</div>
-    <div style="width: 100%; display: inline-block; margin-top: 5px;"><button class="pull-right btn btn-primary" data-bind="css: { 'btn-primary': invalidateOnRefresh() !== 'invalidateAndFlush', 'btn-danger': invalidateOnRefresh() === 'invalidateAndFlush' }, click: function (data, event) { huePubSub.publish('close.popover'); triggerRefresh(data, event); }, clickBubble: false">${ _('Refresh') }</button></div>
-  </script>
-
-  <script type="text/html" id="assist-namespace-header-actions">
-    <div class="assist-db-header-actions">
-      <!-- ko ifnot: loading -->
-      <span class="assist-tables-counter">(<span data-bind="text: filteredNamespaces().length"></span>)</span>
-      <!-- ko if: window.HAS_MULTI_CLUSTER -->
-      <a class="inactive-action" href="javascript:void(0)" data-bind="click: triggerRefresh"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }" title="${_('Refresh')}"></i></a>
-      <!-- /ko -->
-      <!-- /ko -->
-      <!-- ko if: loading -->
-      <i class="fa fa-refresh fa-spin blue" title="${_('Refresh')}"></i>
-      <!-- /ko -->
-    </div>
-  </script>
-
-  <script type="text/html" id="assist-db-header-actions">
-    <div class="assist-db-header-actions">
-      <!-- ko ifnot: loading -->
-      <span class="assist-tables-counter">(<span data-bind="text: filteredEntries().length"></span>)</span>
-      % if hasattr(ENABLE_NEW_CREATE_TABLE, 'get') and ENABLE_NEW_CREATE_TABLE.get() and not IS_EMBEDDED.get():
-        <!-- ko if: sourceType === 'hive' || sourceType === 'impala' -->
-        <!-- ko if: typeof databaseName !== 'undefined' -->
-          <a class="inactive-action" data-bind="hueLink: '${ url('indexer:importer_prefill', source_type='all', target_type='table') }' + databaseName + '/?sourceType=' + sourceType + '&namespace=' + assistDbNamespace.namespace.id" title="${_('Create table')}" href="javascript:void(0)">
-            <i class="pointer fa fa-plus" title="${_('Create table')}"></i>
-          </a>
-        <!-- /ko -->
-        <!-- ko if: typeof databases !== 'undefined' -->
-          <a class="inactive-action" data-bind="hueLink: '${ url('indexer:importer_prefill', source_type='manual', target_type='database') }' + '/?sourceType=' + sourceType + '&namespace=' + namespace.id" href="javascript:void(0)">
-            <i class="pointer fa fa-plus" title="${ _('Create database') }"></i>
-          </a>
-        <!-- /ko -->
-        <!-- /ko -->
-      % endif
-      <!-- ko if: sourceType === 'solr' -->
-      <a class="inactive-action" data-bind="hueLink: '/indexer/importer/prefill/all/index/'" title="${_('Create index')}">
-        <i class="pointer fa fa-plus" title="${_('Create index')}"></i>
-      </a>
-      <!-- /ko -->
-      <!-- ko if: sourceType === 'impala' -->
-      <a class="inactive-action" href="javascript:void(0)" data-bind="templatePopover : { contentTemplate: 'ask-for-invalidate-content', titleTemplate: 'ask-for-invalidate-title', trigger: 'click', minWidth: '320px' }"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }" title="${_('Refresh')}"></i></a>
-      <!-- /ko -->
-      <!-- ko if: sourceType !== 'impala' -->
-      <a class="inactive-action" href="javascript:void(0)" data-bind="click: triggerRefresh"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }" title="${_('Refresh')}"></i></a>
-      <!-- /ko -->
-      <!-- /ko -->
-      <!-- ko if: loading -->
-      <i class="fa fa-refresh fa-spin blue" title="${_('Refresh')}"></i>
-      <!-- /ko -->
-    </div>
-  </script>
-
-  <script type="text/html" id="assist-sources-template">
-    <div class="assist-flex-header">
-      <div class="assist-inner-header">
-        ${_('Sources')}
-      </div>
-    </div>
-    <div class="assist-flex-fill">
-      <ul class="assist-tables" data-bind="foreach: sources">
-        <li class="assist-table">
-          <a class="assist-table-link" href="javascript: void(0);" data-bind="click: function () { $parent.selectedSource($data); }"><i class="fa fa-fw fa-server muted valign-middle"></i> <span data-bind="text: name"></span></a>
-        </li>
-      </ul>
-    </div>
-  </script>
-
-  <script type="text/html" id="assist-namespaces-template">
-    <div class="assist-flex-header">
-      <div class="assist-inner-header">
-        ${_('Namespaces')}
-        <!-- ko template: 'assist-namespace-header-actions' --><!-- /ko -->
-      </div>
-    </div>
-    <div class="assist-flex-search" data-bind="visible: hasNamespaces() && !hasErrors()">
-      <div class="assist-filter">
-        <!-- ko component: {
-            name: 'inline-autocomplete',
-            params: {
-              querySpec: filter.querySpec,
-              facets: [],
-              knownFacetValues: {},
-              autocompleteFromEntries: autocompleteFromNamespaces
-            }
-          } --><!-- /ko -->
-      </div>
-    </div>
-    <div class="assist-flex-fill assist-db-scrollable" data-bind="visible: !hasErrors() && !loading() && hasNamespaces(), delayedOverflow">
-      <!-- ko if: !loading() && filteredNamespaces().length == 0 -->
-      <ul class="assist-tables">
-        <li class="assist-entry no-entries">${_('No results found')}</li>
-      </ul>
-      <!-- /ko -->
-      <ul class="assist-tables" data-bind="foreach: filteredNamespaces">
-        <li class="assist-table">
-          <!-- ko if: status() === 'STARTING' -->
-          <span class="assist-table-link" title="${_('Starting')}" data-bind="tooltip: { placement: 'bottom' }"><i class="fa fa-fw fa-spinner fa-spin muted valign-middle"></i> <span data-bind="text: name"></span></span>
-          <!-- /ko -->
-          <!-- ko if: status() !== 'STARTING' -->
-          <!-- ko if: namespace.computes.length -->
-          <a class="assist-table-link" href="javascript: void(0);" data-bind="click: function () { $parent.selectedNamespace($data); }"><i class="fa fa-fw fa-snowflake-o muted valign-middle"></i> <span data-bind="text: name"></span></a>
-          <!-- /ko -->
-          <!-- ko ifnot: namespace.computes.length -->
-          <span class="assist-table-link" title="${_('No related computes')}" data-bind="tooltip: { placement: 'bottom' }"><i class="fa fa-fw fa-warning muted valign-middle"></i> <span data-bind="text: name"></span></span>
-          <!-- /ko -->
-          <!-- /ko -->
-        </li>
-      </ul>
-    </div>
-    <div class="assist-flex-fill" data-bind="visible: loading">
-      <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
-    </div>
-    <div class="assist-flex-fill" data-bind="visible: hasErrors() && !loading()">
-      <span class="assist-errors">${ _('Error loading namespaces.') }</span>
-    </div>
-    <div class="assist-flex-fill" data-bind="visible: !hasErrors() && !loading() && !hasNamespaces()">
-      <span class="assist-errors">${ _('No namespaces found.') }</span>
-    </div>
-  </script>
-
-  <script type="text/html" id="assist-databases-template">
-    <div class="assist-flex-header" data-bind="visibleOnHover: { selector: '.hover-actions', override: loading() }">
-      <div class="assist-inner-header">
-        <!-- ko ifnot: sourceType === 'solr' || sourceType === 'kafka' -->
-        ${_('Databases')}
-        <!-- ko template: 'assist-db-header-actions' --><!-- /ko -->
-        <!-- /ko -->
-        <!-- ko if: sourceType === 'solr' || sourceType === 'kafka'-->
-        ${_('Sources')}
-        <!-- /ko -->
-      </div>
-    </div>
-    <div class="assist-flex-search" data-bind="visible: hasEntries() && ! hasErrors()">
-      <div class="assist-filter">
-        <!-- ko component: {
-            name: 'inline-autocomplete',
-            params: {
-              querySpec: filter.querySpec,
-              facets: [],
-              placeHolder: sourceType === 'solr' || sourceType === 'kafka' ? '${_('Filter sources...')}' : '${_('Filter databases...')}',
-              knownFacetValues: {},
-              autocompleteFromEntries: autocompleteFromEntries
-            }
-          } --><!-- /ko -->
-      </div>
-    </div>
-    <div class="assist-flex-fill assist-db-scrollable" data-bind="visible: ! hasErrors() && ! loading() && hasEntries(), delayedOverflow">
-      <!-- ko if: ! loading() && filteredEntries().length == 0 -->
-      <ul class="assist-tables">
-        <li class="assist-entry no-entries">${_('No results found')}</li>
-      </ul>
-      <!-- /ko -->
-      <ul class="assist-tables" data-bind="foreachVisible: {data: filteredEntries, minHeight: 23, container: '.assist-db-scrollable', skipScrollEvent: navigationSettings.rightAssist }">
-        <!-- ko template: { name: 'assist-database-entry' } --><!-- /ko -->
-      </ul>
-    </div>
-    <div class="assist-flex-fill" data-bind="visible: loading">
-      <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
-    </div>
-    <div class="assist-flex-fill" data-bind="visible: hasErrors() && ! loading()">
-      <span class="assist-errors">${ _('Error loading databases.') }</span>
-    </div>
-    <div class="assist-flex-fill" data-bind="visible: ! hasErrors() && ! loading() && ! hasEntries()">
-      <span class="assist-errors">${ _('No databases found.') }</span>
-    </div>
-  </script>
-
-  <script type="text/html" id="assist-tables-template">
-    <div class="assist-flex-header">
-      <div class="assist-inner-header" data-bind="visible: !$parent.loading() && !$parent.hasErrors()">
-        <!-- ko ifnot: sourceType === 'solr' || sourceType === 'kafka' -->
-          ${_('Tables')}
-        <!-- /ko -->
-        <!-- ko if: sourceType === 'solr' -->
-          <div data-bind="appAwareTemplateContextMenu: { template: 'collection-title-context-items', scrollContainer: '.assist-db-scrollable' }">${_('Indexes')}</div>
-        <!-- /ko -->
-        <!-- ko if: sourceType === 'kafka' -->
-          ${_('Topics')}
-        <!-- /ko -->
-        <!-- ko template: 'assist-db-header-actions' --><!-- /ko -->
-      </div>
-    </div>
-    <div class="assist-flex-search" data-bind="visible: hasEntries() && !$parent.loading() && !$parent.hasErrors()">
-      <div class="assist-filter">
-        <!-- ko component: {
-          name: 'inline-autocomplete',
-          params: {
-            querySpec: filter.querySpec,
-            facets: ['type'],
-            knownFacetValues: knownFacetValues.bind($data),
-            autocompleteFromEntries: autocompleteFromEntries
-          }
-        } --><!-- /ko -->
-      </div>
-    </div>
-    <div class="assist-flex-fill assist-db-scrollable" data-bind="visible: ! hasErrors() && ! loading(), delayedOverflow">
-      <!-- ko template: 'assist-db-entries' --><!-- /ko -->
-    </div>
-    <div class="assist-flex-fill" data-bind="visible: loading() || $parent.loading()">
-      <!-- ko hueSpinner: { spin: loading, center: true, size: 'large' } --><!-- /ko -->
-    </div>
-    <div class="assist-flex-fill" data-bind="visible: hasErrors() && ! loading()">
-      <span class="assist-errors">${ _('Error loading tables.') }</span>
-    </div>
-  </script>
-
-  <script type="text/html" id="assist-panel-inner-header">
-    <div class="assist-header assist-fixed-height" data-bind="visibleOnHover: { selector: '.assist-header-actions' }, css: { 'assist-resizer': $index() > 0 }" style="display:none;">
-      <span data-bind="text: $parent.name"></span>
-      <div class="assist-header-actions">
-        <div class="inactive-action" data-bind="click: function () { $parent.visible(false) }"><i class="fa fa-times"></i></div>
-      </div>
-    </div>
-  </script>
-
-  <script type="text/html" id="assist-panel-template">
-    <div class="assist-panel" data-bind="dropzone: { url: '/filebrowser/upload/file?dest=' + DROPZONE_HOME_DIR, params: {dest: DROPZONE_HOME_DIR}, clickable: false, paramName: 'hdfs_file', onComplete: function(path){ huePubSub.publish('assist.dropzone.complete', path); }, disabled: '${ not (hasattr(SHOW_UPLOAD_BUTTON, 'get') and SHOW_UPLOAD_BUTTON.get()) }' === 'True' }">
-      <!-- ko if: availablePanels().length > 1 -->
-      <div class="assist-panel-switches">
-        <!-- ko foreach: availablePanels -->
-        <div class="inactive-action assist-type-switch" data-bind="click: function () { $parent.visiblePanel($data); }, css: { 'blue': $parent.visiblePanel() === $data }, style: { 'float': rightAlignIcon ? 'right' : 'left' },  attr: { 'title': name }">
-          <!-- ko if: iconSvg --><span style="font-size:22px;"><svg class="hi"><use data-bind="attr: {'xlink:href': iconSvg }" xlink:href=''></use></svg></span><!-- /ko -->
-          <!-- ko if: !iconSvg --><i class="fa fa-fw valign-middle" data-bind="css: icon"></i><!-- /ko -->
-        </div>
-        <!-- /ko -->
-      </div>
-      <!-- /ko -->
-      <!-- ko with: visiblePanel -->
-      <div class="assist-panel-contents" data-bind="style: { 'padding-top': $parent.availablePanels().length > 1 ? '10px' : '5px' }">
-        <div class="assist-inner-panel">
-          <div class="assist-flex-panel">
-            <!-- ko template: { name: templateName, data: panelData } --><!-- /ko -->
-          </div>
-        </div>
-      </div>
-      <!-- /ko -->
-    </div>
-  </script>
-
-  <div class="assist-file-entry-drag">
-    <span class="drag-text"></span>
-  </div>
-
-  <script type="text/javascript">
-
-    var SQL_ASSIST_KNOWN_FACET_VALUES = {
-      'type': {'array': -1, 'table': -1, 'view': -1, 'boolean': -1, 'bigint': -1, 'binary': -1, 'char': -1, 'date': -1, 'double': -1, 'decimal': -1, 'float': -1, 'int': -1, 'map': -1, 'real': -1, 'smallint': -1, 'string': -1, 'struct': -1, 'timestamp': -1, 'tinyint': -1, 'varchar': -1 }
-    };
-
-    var SOLR_ASSIST_KNOWN_FACET_VALUES = {
-      'type': {'date': -1, 'tdate': -1, 'timestamp': -1, 'pdate': -1, 'int': -1, 'tint': -1, 'pint': -1, 'long': -1, 'tlong': -1, 'plong': -1, 'float': -1, 'tfloat': -1, 'pfloat': -1, 'double': -1, 'tdouble': -1, 'pdouble': -1, 'currency': -1, 'smallint': -1, 'bigint': -1, 'tinyint': -1, 'SpatialRecursivePrefixTreeFieldType': -1, 'string': -1, 'boolean': -1 }
-    };
-
-
-    (function () {
-
-      var NAV_FACET_ICON = 'fa-tags';
-      var NAV_TYPE_ICONS = {
-        'DATABASE': 'fa-database',
-        'TABLE': 'fa-table',
-        'VIEW': 'fa-eye',
-        'FIELD': 'fa-columns',
-        'PARTITION': 'fa-th',
-        'SOURCE': 'fa-server',
-        'OPERATION': 'fa-cogs',
-        'OPERATION_EXECUTION': 'fa-cog',
-        'DIRECTORY': 'fa-folder-o',
-        'FILE': 'fa-file-o',
-        'S3BUCKET': 'fa-cubes',
-        'SUB_OPERATION': 'fa-code-fork',
-        'COLLECTION': 'fa-search',
-        'HBASE': 'fa-th-large',
-        'HUE': 'fa-file-o'
-      };
-
-      /**
-       * @param {Object} params
-       * @param {string} params.user
-       * @param {boolean} params.onlySql - For the old query editors
-       * @param {string[]} params.visibleAssistPanels - Panels that will initially be shown regardless of total storage
-       * @param {Object} params.sql
-       * @param {Object[]} params.sql.sourceTypes - All the available SQL source types
-       * @param {string} params.sql.sourceTypes[].name - Example: Hive SQL
-       * @param {string} params.sql.sourceTypes[].type - Example: hive
-       * @param {string} [params.sql.activeSourceType] - Example: hive
-       * @param {Object} params.sql.navigationSettings - enable/disable the links
-       * @param {boolean} params.sql.navigationSettings.openItem - Example: true
-       * @param {boolean} params.sql.navigationSettings.showStats - Example: true
-       * @constructor
-       */
-      function AssistPanel (params) {
-        var self = this;
-        var i18n = {
-          errorLoadingDatabases: "${ _('There was a problem loading the databases') }",
-          errorLoadingTablePreview: "${ _('There was a problem loading the table preview') }"
-        };
-        var i18nCollections = {
-          errorLoadingDatabases: "${ _('There was a problem loading the indexes') }",
-          errorLoadingTablePreview: "${ _('There was a problem loading the index preview') }"
-        };
-
-        self.tabsEnabled = '${ USE_NEW_SIDE_PANELS.get() }' === 'True';
-
-        self.availablePanels = ko.observableArray();
-        self.visiblePanel = ko.observable();
-
-        self.lastOpenPanelType = ko.observable();
-        window.apiHelper.withTotalStorage('assist', 'last.open.panel', self.lastOpenPanelType);
-
-        huePubSub.subscribeOnce('cluster.config.set.config', function (clusterConfig) {
-          if (clusterConfig && clusterConfig['app_config']) {
-            var panels = [];
-            var appConfig = clusterConfig['app_config'];
-
-            if (appConfig['editor']) {
-              var sqlPanel = new AssistInnerPanel({
-                panelData: new AssistDbPanel($.extend({
-                  i18n: i18n
-                }, params.sql)),
-                name: '${ _("SQL") }',
-                type: 'sql',
-                icon: 'fa-database',
-                minHeight: 75
-              });
-              panels.push(sqlPanel);
-
-              huePubSub.subscribe('assist.show.sql', function () {
-                if (self.visiblePanel() !== sqlPanel) {
-                  self.visiblePanel(sqlPanel);
-                }
-              });
-            }
-
-            if (self.tabsEnabled) {
-
-              if (appConfig['browser'] && appConfig['browser']['interpreter_names'].indexOf('hdfs') != -1) {
-                panels.push(new AssistInnerPanel({
-                  panelData: new AssistHdfsPanel({}),
-                  name: '${ _("HDFS") }',
-                  type: 'hdfs',
-                  icon: 'fa-files-o',
-                  minHeight: 50
-                }));
-              }
-
-              if (appConfig['browser'] && appConfig['browser']['interpreter_names'].indexOf('s3') != -1) {
-                panels.push(new AssistInnerPanel({
-                  panelData: new AssistS3Panel({}),
-                  name: '${ _("S3") }',
-                  type: 's3',
-                  icon: 'fa-cubes',
-                  minHeight: 50
-                }));
-              }
-
-              if (appConfig['browser'] && appConfig['browser']['interpreter_names'].indexOf('adls') != -1) {
-                panels.push(new AssistInnerPanel({
-                  panelData: new AssistAdlsPanel({}),
-                  name: '${ _("ADLS") }',
-                  type: 'adls',
-                  icon: 'fa-windows',
-                  iconSvg: '#hi-adls',
-                  minHeight: 50
-                }));
-              }
-
-              if (appConfig['browser'] && appConfig['browser']['interpreter_names'].indexOf('indexes') != -1) {
-                var solrPanel = new AssistInnerPanel({
-                  panelData: new AssistDbPanel($.extend({
-                    i18n: i18nCollections,
-                    isSolr: true
-                  }, params.sql)),
-                  name: '${ _("Indexes") }',
-                  type: 'solr',
-                  icon: 'fa-search-plus',
-                  minHeight: 75
-                });
-                panels.push(solrPanel);
-                huePubSub.subscribe('assist.show.solr', function () {
-                  if (self.visiblePanel() !== solrPanel) {
-                    self.visiblePanel(solrPanel);
-                  }
-                });
-              }
-
-              if (appConfig['browser'] && appConfig['browser']['interpreter_names'].indexOf('kafka') != -1) {
-                var streamsPanel = new AssistInnerPanel({
-                  panelData: new AssistDbPanel($.extend({
-                    i18n: i18nCollections,
-                    isStreams: true
-                  }, params.sql)),
-                  name: '${ _("Streams") }',
-                  type: 'kafka',
-                  icon: 'fa-sitemap',
-                  minHeight: 75
-                });
-                panels.push(streamsPanel);
-              }
-
-              if (appConfig['browser'] && appConfig['browser']['interpreter_names'].indexOf('hbase') != -1) {
-                panels.push(new AssistInnerPanel({
-                  panelData: new AssistHBasePanel({}),
-                  name: '${ _("HBase") }',
-                  type: 'hbase',
-                  icon: 'fa-th-large',
-                  minHeight: 50
-                }));
-              }
-
-              % if not IS_EMBEDDED.get():
-              var documentsPanel = new AssistInnerPanel({
-                panelData: new AssistDocumentsPanel({
-                  user: params.user
-                }),
-                name: '${ _("Documents") }',
-                type: 'documents',
-                icon: 'fa-files-o',
-                iconSvg: '#hi-documents',
-                minHeight: 50,
-                rightAlignIcon: true,
-                visible: params.visibleAssistPanels && params.visibleAssistPanels.indexOf('documents') !== -1
-              });
-
-              panels.push(documentsPanel);
-
-              huePubSub.subscribe('assist.show.documents', function (docType) {
-                huePubSub.publish('left.assist.show');
-                if (self.visiblePanel() !== documentsPanel) {
-                  self.visiblePanel(documentsPanel);
-                }
-                if (docType) {
-                  documentsPanel.panelData.setTypeFilter(docType);
-                }
-              });
-              % endif
-
-              var vcsKeysLength = ${ len(VCS.keys()) };
-              if (vcsKeysLength > 0) {
-                panels.push(new AssistInnerPanel({
-                  panelData: new AssistGitPanel({}),
-                  name: '${ _("Git") }',
-                  type: 'git',
-                  icon: 'fa-github',
-                  minHeight: 50,
-                  rightAlignIcon: true
-                }));
-              }
-
-            }
-
-            self.availablePanels(panels);
-          } else {
-            self.availablePanels([new AssistInnerPanel({
-              panelData: new AssistDbPanel($.extend({
-                i18n: i18n
-              }, params.sql)),
-              name: '${ _("SQL") }',
-              type: 'sql',
-              icon: 'fa-database',
-              minHeight: 75
-            })]);
-          }
-
-          if (!self.lastOpenPanelType()) {
-            self.lastOpenPanelType(self.availablePanels()[0].type);
-          }
-
-          var lastFoundPanel = self.availablePanels().filter(function (panel) { return panel.type === self.lastOpenPanelType() });
-
-          // always forces the db panel to load
-          var dbPanel = self.availablePanels().filter(function (panel) { return panel.type === 'sql' });
-          if (dbPanel.length > 0) {
-            dbPanel[0].panelData.init();
-          }
-
-          self.visiblePanel.subscribe(function(newValue) {
-            self.lastOpenPanelType(newValue.type);
-            if (newValue.type !== 'sql' && !newValue.panelData.initialized) {
-              newValue.panelData.init();
-            }
-          });
-
-          self.visiblePanel(lastFoundPanel.length === 1 ? lastFoundPanel[0] : self.availablePanels()[0]);
-        });
-
-        window.setTimeout(function () {
-          // Main initialization trigger in hue.mako, this is for Hue 3
-          if (self.availablePanels().length === 0) {
-            huePubSub.publish('cluster.config.get.config');
-          }
-        }, 0);
-      }
-
-      ko.components.register('assist-panel', {
-        viewModel: AssistPanel,
-        template: { element: 'assist-panel-template' }
-      });
-    })();
-  </script>
-
   <script type="text/html" id="language-reference-topic-tree">
     <!-- ko if: $data.length -->
     <ul class="assist-docs-topic-tree " data-bind="foreach: $data">

+ 93 - 5
desktop/core/src/desktop/templates/global_js_constants.mako

@@ -18,14 +18,16 @@
   from django.utils.translation import ugettext as _
 
   from desktop import conf
-  from desktop.conf import IS_EMBEDDED, IS_K8S_ONLY, IS_MULTICLUSTER_ONLY
-  from desktop.models import hue_version
+  from desktop.conf import IS_EMBEDDED, IS_K8S_ONLY, IS_MULTICLUSTER_ONLY, USE_NEW_SIDE_PANELS, VCS
+  from desktop.models import hue_version, _get_apps
 
   from beeswax.conf import LIST_PARTITIONS_LIMIT
   from dashboard.conf import HAS_SQL_ENABLED
+  from filebrowser.conf import SHOW_UPLOAD_BUTTON
   from indexer.conf import ENABLE_NEW_INDEXER
   from metadata.conf import has_catalog, has_readonly_catalog, has_optimizer, has_workload_analytics, OPTIMIZER
-  from notebook.conf import ENABLE_NOTEBOOK_2, ENABLE_QUERY_ANALYSIS, ENABLE_QUERY_SCHEDULING, get_ordered_interpreters
+  from metastore.conf import ENABLE_NEW_CREATE_TABLE
+  from notebook.conf import ENABLE_NOTEBOOK_2, ENABLE_QUERY_ANALYSIS, ENABLE_QUERY_BUILDER, ENABLE_QUERY_SCHEDULING, get_ordered_interpreters, SHOW_NOTEBOOKS
 
   from metastore.views import has_write_access
 %>
@@ -57,16 +59,18 @@
   window.KNOX_BASE_PATH = window._KNOX_BASE_PATH.indexOf('KNOX_BASE_PATH_KNOX') < 0 ? window._KNOX_BASE_PATH_KNOX : '';
   window.KNOX_BASE_URL = window._KNOX_BASE_URL.indexOf('KNOX_BASE_URL') < 0 ? window._KNOX_BASE_URL : '';
 
+  window.HAS_GIT = ${ len(VCS.keys()) } > 0;
   window.HAS_MULTI_CLUSTER = '${ conf.has_multi_cluster() }' === 'True';
-
   window.HAS_SQL_DASHBOARD = '${ HAS_SQL_ENABLED.get() }' === 'True';
 
   window.DROPZONE_HOME_DIR = '${ user.get_home_directory() if not user.is_anonymous() else "" }';
 
   window.USER_HAS_METADATA_WRITE_PERM = '${ user.has_hue_permission(action="write", app="metadata") }' === 'True';
 
+  window.ENABLE_NEW_CREATE_TABLE = '${ hasattr(ENABLE_NEW_CREATE_TABLE, 'get') and ENABLE_NEW_CREATE_TABLE.get()}' === 'True';
   window.ENABLE_NOTEBOOK_2 = '${ ENABLE_NOTEBOOK_2.get() }' === 'True';
 
+  window.ENABLE_QUERY_BUILDER = '${ ENABLE_QUERY_BUILDER.get() }' === 'True';
   window.ENABLE_QUERY_SCHEDULING = '${ ENABLE_QUERY_SCHEDULING.get() }' === 'True';
 
   window.ENABLE_SQL_SYNTAX_CHECK = '${ conf.ENABLE_SQL_SYNTAX_CHECK.get() }' === 'True';
@@ -81,6 +85,9 @@
 
   window.HUE_CONTAINER = '${ IS_EMBEDDED.get() }' === 'True' ? '.hue-embedded-container' : 'body';
 
+  window.SHOW_NOTEBOOKS = '${ SHOW_NOTEBOOKS.get() }' === 'True'
+  window.SHOW_UPLOAD_BUTTON = '${ hasattr(SHOW_UPLOAD_BUTTON, 'get') and SHOW_UPLOAD_BUTTON.get() }' === 'True'
+
   window.IS_MULTICLUSTER_ONLY = '${ IS_MULTICLUSTER_ONLY.get() }' === 'True';
   window.IS_EMBEDDED = '${ IS_EMBEDDED.get() }' === 'True';
   window.IS_K8S_ONLY = '${ IS_K8S_ONLY.get() }' === 'True';
@@ -88,6 +95,18 @@
   window.JB_SINGLE_CHECK_INTERVAL_IN_MILLIS = 5000;
   window.JB_MULTI_CHECK_INTERVAL_IN_MILLIS = 20000;
 
+
+  window.HUE_URLS = {
+    IMPORTER_CREATE_TABLE: '${url('indexer:importer_prefill', source_type = 'all', target_type = 'table')}',
+    IMPORTER_CREATE_DATABASE: '${url('indexer:importer_prefill', source_type = 'manual', target_type = 'database')}',
+    NOTEBOOK_INDEX: '${url('notebook:index')}',
+    PIG_INDEX: '${url('pig:index')}',
+    OOZIE_NEW_WORKFLOW: '${url('oozie:new_workflow')}',
+    OOZIE_NEW_COORDINATOR: '${url('oozie:new_coordinator')}',
+    OOZIE_NEW_BUNDLE: '${url('oozie:new_bundle')}',
+    SEARCH_NEW_SEARCH: '${url('search:new_search')}',
+  }
+
   // TODO: Replace with json import
   window.HUE_I18n = {
     '1 hour':  '${ _('1 hour') }',
@@ -105,6 +124,7 @@
     'Add': '${ _('Add') }',
     'Admin': '${ _('Admin') }',
     'aggregate': '${ _('aggregate') }',
+    'Aggregate': '${ _('Aggregate') }',
     'alias': '${ _('alias') }',
     'All': '${_('All')}',
     'An error occurred refreshing the table stats. Please try again.': '${_('An error occurred refreshing the table stats. Please try again.')}',
@@ -122,10 +142,12 @@
     'Cancel upload': '${ _('Cancel upload') }',
     'Cancel': '${_('Cancel')}',
     'Choose...': '${ _('Choose...') }',
+    'Clear cache': '${ _('Clear cache') }',
     'Clear the query history': '${ _('Clear the query history') }',
     'Click for more details': '${ _('Click for more details') }',
     'Close': '${_('Close')}',
     'Cluster': '${ _('Cluster') }',
+    'Clusters': '${ _('Clusters') }',
     'CodeGen': '${ _('CodeGen') }',
     'Collection': '${ _('Collection') }',
     'column name...': '${_('column name...')}',
@@ -139,8 +161,11 @@
     'Could not find details for the function': '${_('Could not find details for the function')}',
     'Could not find': '${_('Could not find')}',
     'CPU': '${ _('CPU') }',
+    'Create database': '${_('Create database')}',
     'Create Directory': '${_('Create Directory')}',
     'Create folder': '${_('Create folder')}',
+    'Create index': '${_('Create index')}',
+    'Create table': '${_('Create table')}',
     'Create': '${ _('Create') }',
     'Created': '${ _('Created') }',
     'cte': '${ _('cte') }',
@@ -152,6 +177,7 @@
     'database': '${ _('database') }',
     'Databases': '${ _('Databases') }',
     'default': '${ _('default') }',
+    'Delete document': '${ _('Delete document') }',
     'Delete this privilege': '${ _('Delete this privilege') }',
     'Deleting...': '${ _('Deleting...') }',
     'Description': '${ _('Description') }',
@@ -163,6 +189,7 @@
     'DistCp Job': '${_('DistCp Job')}',
     'distinct': '${ _('distinct') }',
     'Do you really want to delete the following document(s)?': '${ _('Do you really want to delete the following document(s)?') }',
+    'Document type': '${ _('Document type') }',
     'Documents': '${ _('Documents') }',
     'Drop a SQL file here': '${_('Drop a SQL file here')}',
     'Drop iPython/Zeppelin notebooks here': '${_('Drop iPython/Zeppelin notebooks here')}',
@@ -170,9 +197,16 @@
     'Edit this privilege': '${ _('Edit this privilege') }',
     'Edit': '${ _('Edit') }',
     'Editor': '${ _('Editor') }',
+    'Empty directory': '${ _('Empty directory') }',
     'Empty file...': '${ _('Empty file...') }',
+    'Error loading columns.': '${ _('Error loading columns.') }',
+    'Error loading contents.': '${ _('Error loading contents.') }',
+    'Error loading databases.': '${ _('Error loading databases.') }',
     'Error loading entries': '${ _('Error loading entries') }',
+    'Error loading fields.': '${ _('Error loading fields.') }',
+    'Error loading namespaces.': '${ _('Error loading namespaces.') }',
     'Error loading samples': '${ _('Error loading samples') }',
+    'Error loading tables.': '${ _('Error loading tables.') }',
     'Error while copying results.': '${ _('Error while copying results.') }',
     'Example: SELECT * FROM tablename, or press CTRL + space': '${ _('Example: SELECT * FROM tablename, or press CTRL + space') }',
     'Execute a query to get query execution analysis.': '${ _('Execute a query to get query execution analysis.') }',
@@ -185,7 +219,10 @@
     'Fields': '${ _('Fields') }',
     'File Browser': '${ _('File Browser') }',
     'Files': '${ _('Files') }',
+    'Filter databases...': '${ _('Filter databases...') }',
+    'Filter sources...': '${ _('Filter sources...') }',
     'filter': '${ _('filter') }',
+    'Filter': '${ _('Filter') }',
     'Filter...': '${ _('Filter...') }',
     'Fix': '${ _('Fix') }',
     'Folder name': '${_('Folder name')}',
@@ -202,9 +239,12 @@
     'Ignore this type of error': '${_('Ignore this type of error')}',
     'Impala Query': '${_('Impala Query')}',
     'Import Job': '${_('Import Job')}',
+    'Indexes': '${ _('Indexes') }',
+    'Insert at cursor': '${_('Insert at cursor')}',
     'Insert in the editor': '${ _('Insert in the editor') }',
     'Insert value here': '${ _('Insert value here') }',
     'Insert': '${ _('Insert') }',
+    'Invalidate all metadata and rebuild index.': '${ _('Invalidate all metadata and rebuild index.') }',
     'IO': '${ _('IO') }',
     'It looks like you are offline or an unknown error happened. Please refresh the page.': '${ _('It looks like you are offline or an unknown error happened. Please refresh the page.') }',
     'Java Job': '${_('Java Job')}',
@@ -219,6 +259,7 @@
     'Loading metrics...': '${ _('Loading metrics...') }',
     'Lock this row': '${_('Lock this row')}',
     'MapReduce Job': '${_('MapReduce Job')}',
+    'Manual refresh': '${_('Manual refresh')}',
     'max': '${ _('max') }',
     'Memory': '${ _('Memory') }',
     'Metrics': '${ _('Metrics') }',
@@ -233,30 +274,50 @@
     'Missing y axis configuration.': '${ _('Missing y axis configuration.') }',
     'Name': '${ _('Name') }',
     'Namespace': '${ _('Namespace') }',
+    'Namespaces': '${ _('Namespaces') }',
+    'New document': '${ _('New document') }',
+    'New folder': '${ _('New folder') }',
+    'No clusters available': '${ _('No clusters available') }',
     'No clusters found': '${ _('No clusters found') }',
     'No columns found': '${ _('No columns found') }',
     'No computes found': '${ _('No computes found') }',
     'No Data Available.': '${ _('No Data Available.') }',
     'No databases found': '${ _('No databases found') }',
+    'No databases found.': '${ _('No databases found.') }',
+    'No documents found': '${ _('No documents found') }',
     'No entries found': '${ _('No entries found') }',
     'No logs available at this moment.': '${ _('No logs available at this moment.') }',
     'No match found': '${ _('No match found') }',
     'No namespaces found': '${ _('No namespaces found') }',
+    'No namespaces found.': '${ _('No namespaces found.') }',
     'No privileges found for the selected object.': '${ _('No privileges found for the selected object.') }',
+    'No results found': '${_('No results found')}',
     'No results found.': '${_('No results found.')}',
+    'No tables available.': '${ _('No tables available.') }',
+    'No tables found': '${ _('No tables found') }',
     'No task history.': '${_('Not task history.')}',
     'No': '${ _('No') }',
+    'No related computes': '${_('No related computes')}',
     'Not available': '${_('Not available')}',
     'Notebook': '${_('Notebook')}',
     'of': '${_('of')}',
     'Oozie Bundle': '${_('Oozie Bundle')}',
     'Oozie Schedule': '${_('Oozie Schedule')}',
     'Oozie Workflow': '${_('Oozie Workflow')}',
+    'Open cluster': '${_('Open cluster')}',
+    'Open document': '${_('Open document')}',
+    'Open folder': '${_('Open folder')}',
+    'Open in Browser': '${_('Open in Browser')}',
+    'Open in Dashboard': '${_('Open in Dashboard')}',
     'Open in Dashboard...': '${ _('Open in Dashboard...') }',
+    'Open in Editor': '${_('Open in Editor')}',
+    'Open in HBase': '${_('Open in HBase')}',
+    'Open in Importer': '${_('Open in Importer')}',
     'Open in File Browser...': '${ _('Open in File Browser...') }',
     'Open in Table Browser...': '${ _('Open in Table Browser...') }',
     'Open': '${ _('Open') }',
     'Options': '${ _('Options') }',
+    'Order': '${ _('Order') }',
     'order by': '${ _('order by') }',
     'other': '${ _('other') }',
     'Output': '${ _('Output') }',
@@ -264,6 +325,7 @@
     'Owner': '${ _('Owner') }',
     'Partition key': '${ _('Partition key') }',
     'Partitions': '${ _('Partitions') }',
+    'Perform incremental metadata update.': '${ _('Perform incremental metadata update.') }',
     'Permissions': '${ _('Permissions') }',
     'Pig Design': '${_('Pig Design')}',
     'Pig Script': '${_('Pig Script')}',
@@ -273,6 +335,7 @@
     'Popularity': '${ _('Popularity') }',
     'Preview': '${ _('Preview') }',
     'Primary key': '${ _('Primary key') }',
+    'Project': '${ _('Project') }',
     'Queries': '${ _('Queries') }',
     'Query browser': '${ _('Query browser') }',
     'Query failed': '${ _('Query failed') }',
@@ -291,6 +354,7 @@
     'sample': '${ _('sample') }',
     'Samples': '${ _('Samples') }',
     'Save': '${ _('Save') }',
+    'Schedule': '${ _('Schedule') }',
     'Search Dashboard': '${_('Search Dashboard')}',
     'Search data and saved documents...': '${ _('Search data and saved documents...') }',
     'Search saved documents...': '${_('Search saved documents...')}',
@@ -303,13 +367,17 @@
     'Show 50 more...': '${_('Show 50 more...')}',
     'Show advanced': '${_('Show advanced')}',
     'Show columns': '${_('Show columns')}',
+    'Show details': '${_('Show details')}',
     'Show in Assist...': '${_('Show in Assist...')}',
     'Show more...': '${_('Show more...')}',
     'Show row details': '${_('Show row details')}',
     'Show sample': '${_('Show sample')}',
     'Show view SQL': '${_('Show view SQL')}',
     'Size': '${ _('Size') }',
+    'Solr Search': '${ _('Solr Search') }',
+    'Sources': '${ _('Sources') }',
     'Spark Job': '${_('Spark Job')}',
+    'Starting': '${_('Starting')}',
     'Stats': '${_('Stats')}',
     'Summary': '${_('Summary')}',
     'Table Browser': '${ _('Table Browser') }',
@@ -321,20 +389,24 @@
     'Task History': '${ _('Task History') }',
     'Terms': '${ _('Terms') }',
     'The file has not been found': '${_('The file has not been found')}',
+    'The table has no columns': '${_('The table has no columns')}',
     'The trash is empty': '${_('The trash is empty')}',
     'The upload has been canceled': '${ _('The upload has been canceled') }',
     'There are no stats to be shown': '${ _('There are no stats to be shown') }',
     'There are no terms to be shown': '${ _('There are no terms to be shown') }',
     'This field does not support stats': '${ _('This field does not support stats') }',
+    'This will sync missing tables.': '${ _('This will sync missing tables.') }',
     'Timeline': '${ _('Timeline') }',
     'Top down analysis': '${ _('Top down analysis') }',
     'Top Nodes': '${ _('Top Nodes') }',
+    'Topics': '${ _('Topics') }',
     'Type': '${ _('Type') }',
     'UDFs': '${ _('UDFs') }',
     'Undo': '${ _('Undo') }',
     'Unlock this row': '${_('Unlock this row')}',
     'Unset from default application': '${_('Unset from default application')}',
     'Upload a file': '${_('Upload a file')}',
+    'Upload file': '${_('Upload file')}',
     'uploaded successfully': '${ _('uploaded successfully') }',
     'used by': '${ _('used by') }',
     'Value': '${ _('Value') }',
@@ -344,8 +416,10 @@
     'view': '${ _('view') }',
     'Views': '${ _('Views') }',
     'virtual': '${ _('virtual') }',
+    'WARNING: This can be both resource and time-intensive.': '${ _('WARNING: This can be both resource and time-intensive.') }',
     'With grant option': '${ _('With grant option') }',
     'With grant': '${ _('With grant') }',
+    'Workflow': '${ _('Workflow') }',
     'Yes': '${ _('Yes') }',
     'Yes, delete': '${ _('Yes, delete') }',
   };
@@ -387,6 +461,7 @@
       home_dir = '/'
   %>
 
+  window.USE_NEW_SIDE_PANELS = '${ USE_NEW_SIDE_PANELS.get() }' === 'True'
   window.USER_HOME_DIR = '${ home_dir }';
 
   // TODO: Refactor assist to fetch from config.
@@ -404,9 +479,14 @@
   % for group in user.groups.all():
     userGroups.push('${ group }');
   % endfor
-
   window.LOGGED_USERGROUPS = userGroups;
 
+  var hueApps = [];
+  % for app in _get_apps(request.user, ''):
+    hueApps.push('${ app }')
+  % endfor
+  window.HUE_APPS = hueApps;
+
   window.METASTORE_PARTITION_LIMIT = ${ hasattr(LIST_PARTITIONS_LIMIT, 'get') and LIST_PARTITIONS_LIMIT.get() or 1000 };
 
   window.SQL_COLUMNS_KNOWN_FACET_VALUES = {
@@ -415,5 +495,13 @@
       'timestamp': -1, 'tinyint': -1, 'varchar': -1 }
   };
 
+  window.SQL_ASSIST_KNOWN_FACET_VALUES = {
+    'type': {'array': -1, 'table': -1, 'view': -1, 'boolean': -1, 'bigint': -1, 'binary': -1, 'char': -1, 'date': -1, 'double': -1, 'decimal': -1, 'float': -1, 'int': -1, 'map': -1, 'real': -1, 'smallint': -1, 'string': -1, 'struct': -1, 'timestamp': -1, 'tinyint': -1, 'varchar': -1 }
+  };
+
+  window.SOLR_ASSIST_KNOWN_FACET_VALUES = {
+    'type': {'date': -1, 'tdate': -1, 'timestamp': -1, 'pdate': -1, 'int': -1, 'tint': -1, 'pint': -1, 'long': -1, 'tlong': -1, 'plong': -1, 'float': -1, 'tfloat': -1, 'pfloat': -1, 'double': -1, 'tdouble': -1, 'pdouble': -1, 'currency': -1, 'smallint': -1, 'bigint': -1, 'tinyint': -1, 'SpatialRecursivePrefixTreeFieldType': -1, 'string': -1, 'boolean': -1 }
+  };
+
   ${ sqlDocIndex.sqlDocIndex() }
 })();