Browse Source

HUE-8330 [assist] Add a namespace level in the assist panel

This also takes care of some issues in the Table Browser and Editor.
Johan Ahlen 7 years ago
parent
commit
669768468f
35 changed files with 717 additions and 560 deletions
  1. 2 0
      apps/beeswax/src/beeswax/templates/create_database.mako
  2. 2 0
      apps/beeswax/src/beeswax/templates/create_table_manually.mako
  3. 2 0
      apps/beeswax/src/beeswax/templates/import_wizard_choose_delimiter.mako
  4. 2 0
      apps/beeswax/src/beeswax/templates/import_wizard_choose_file.mako
  5. 2 0
      apps/beeswax/src/beeswax/templates/import_wizard_define_columns.mako
  6. 44 12
      apps/metastore/src/metastore/static/metastore/js/metastore.ko.js
  7. 6 6
      apps/metastore/src/metastore/templates/metastore.mako
  8. 3 3
      apps/pig/src/pig/templates/app.mako
  9. 13 5
      desktop/core/src/desktop/static/desktop/js/apiHelper.js
  10. 16 25
      desktop/core/src/desktop/static/desktop/js/assist/assistDbEntry.js
  11. 17 0
      desktop/core/src/desktop/static/desktop/js/assist/assistDbNamespace.js
  12. 120 90
      desktop/core/src/desktop/static/desktop/js/assist/assistDbSource.js
  13. 145 0
      desktop/core/src/desktop/static/desktop/js/contextCatalog.js
  14. 0 118
      desktop/core/src/desktop/static/desktop/js/contextHelper.js
  15. 35 32
      desktop/core/src/desktop/static/desktop/js/dataCatalog.js
  16. 12 12
      desktop/core/src/desktop/static/desktop/js/jquery.hiveautocomplete.js
  17. 10 10
      desktop/core/src/desktop/static/desktop/js/ko.hue-bindings.js
  18. 13 13
      desktop/core/src/desktop/static/desktop/js/sqlAutocompleter2.js
  19. 11 11
      desktop/core/src/desktop/static/desktop/js/sqlAutocompleter3.js
  20. 3 3
      desktop/core/src/desktop/static/desktop/js/sqlUtils.js
  21. 2 2
      desktop/core/src/desktop/static/desktop/spec/sqlAutocompleter3Spec.js
  22. 1 1
      desktop/core/src/desktop/templates/ace_sql_location_worker.mako
  23. 169 151
      desktop/core/src/desktop/templates/assist.mako
  24. 1 1
      desktop/core/src/desktop/templates/common_dashboard.mako
  25. 1 1
      desktop/core/src/desktop/templates/hue.mako
  26. 11 11
      desktop/core/src/desktop/templates/ko_components/ko_context_popover.mako
  27. 4 4
      desktop/core/src/desktop/templates/ko_components/ko_drop_down.mako
  28. 1 1
      desktop/core/src/desktop/templates/ko_components/ko_history_panel.mako
  29. 4 4
      desktop/libs/dashboard/src/dashboard/static/dashboard/js/search.ko.js
  30. 2 2
      desktop/libs/dashboard/src/dashboard/templates/common_search.mako
  31. 6 6
      desktop/libs/indexer/src/indexer/templates/importer.mako
  32. 5 5
      desktop/libs/indexer/src/indexer/templates/indexes.mako
  33. 5 5
      desktop/libs/indexer/src/indexer/templates/topics.mako
  34. 40 19
      desktop/libs/notebook/src/notebook/static/notebook/js/notebook.ko.js
  35. 7 7
      desktop/libs/notebook/src/notebook/templates/editor_components.mako

+ 2 - 0
apps/beeswax/src/beeswax/templates/create_database.mako

@@ -235,10 +235,12 @@ ${ assist.assistPanel() }
       self.apiHelper.withTotalStorage('assist', 'assist_panel_visible', self.isLeftPanelVisible, true);
 
       huePubSub.subscribe("assist.table.selected", function (tableDef) {
+        // TODO: Handle namespaces
         location.href = '/metastore/table/' + tableDef.database + '/' + tableDef.name;
       });
 
       huePubSub.subscribe("assist.database.selected", function (databaseDef) {
+        // TODO: Handle namespaces
         location.href = '/metastore/tables/' + databaseDef.name;
       });
     }

+ 2 - 0
apps/beeswax/src/beeswax/templates/create_table_manually.mako

@@ -557,10 +557,12 @@ ${ assist.assistPanel() }
       self.apiHelper.withTotalStorage('assist', 'assist_panel_visible', self.isLeftPanelVisible, true);
 
       huePubSub.subscribe("assist.table.selected", function (tableDef) {
+        // TODO: Handle namespaces
         location.href = '/metastore/table/' + tableDef.database + '/' + tableDef.name;
       });
 
       huePubSub.subscribe("assist.database.selected", function (databaseDef) {
+        // TODO: Handle namespaces
         location.href = '/metastore/tables/' + databaseDef.name;
       });
     }

+ 2 - 0
apps/beeswax/src/beeswax/templates/import_wizard_choose_delimiter.mako

@@ -197,10 +197,12 @@ ${ assist.assistPanel() }
 
 
       huePubSub.subscribe("assist.table.selected", function (tableDef) {
+        // TODO: Handle namespaces
         location.href = '/metastore/table/' + tableDef.database + '/' + tableDef.name;
       });
 
       huePubSub.subscribe("assist.database.selected", function (databaseDef) {
+        // TODO: Handle namespaces
         location.href = '/metastore/tables/' + databaseDef.name;
       });
     }

+ 2 - 0
apps/beeswax/src/beeswax/templates/import_wizard_choose_file.mako

@@ -204,10 +204,12 @@ ${ assist.assistPanel() }
 
 
       huePubSub.subscribe("assist.table.selected", function (tableDef) {
+        // TODO: Handle namespaces
         location.href = '/metastore/table/' + tableDef.database + '/' + tableDef.name;
       });
 
       huePubSub.subscribe("assist.database.selected", function (databaseDef) {
+        // TODO: Handle namespaces
         location.href = '/metastore/tables/' + databaseDef.name;
       });
     }

+ 2 - 0
apps/beeswax/src/beeswax/templates/import_wizard_define_columns.mako

@@ -232,10 +232,12 @@ ${ assist.assistPanel() }
 
 
       huePubSub.subscribe("assist.table.selected", function (tableDef) {
+        // TODO: Handle namespaces
         location.href = '/metastore/table/' + tableDef.database + '/' + tableDef.name;
       });
 
       huePubSub.subscribe("assist.database.selected", function (databaseDef) {
+        // TODO: Handle namespaces
         location.href = '/metastore/tables/' + databaseDef.name;
       });
     }

+ 44 - 12
apps/metastore/src/metastore/static/metastore/js/metastore.ko.js

@@ -45,6 +45,7 @@ var MetastoreViewModel = (function () {
     huePubSub.subscribe("assist.db.panel.ready", function () {
       huePubSub.publish('assist.set.database', {
         source: self.sourceType(),
+        namespace: self.activeNamespace(),
         name: null
       });
     });
@@ -64,20 +65,20 @@ var MetastoreViewModel = (function () {
 
     self.currentTab = ko.observable('');
 
-    self.activeSourceContext = ko.observable();
-    self.sourceContexts = ko.observableArray();
+    self.activeNamespace = ko.observable();
+    self.namespaces = ko.observableArray();
 
-    self.activeSourceContext.subscribe(function () {
+    self.activeNamespace.subscribe(function () {
       self.lastLoadDatabasesPromise = null;
       self.loadDatabases()
     });
 
     self.database = ko.observable(null);
 
-    ContextCatalog.getSourceContexts({ app: ContextCatalog.BROWSER_APP, sourceType: self.sourceType() }).done(function (sourceContexts) {
-      // TODO: Context selection
-      self.sourceContexts(sourceContexts);
-      self.activeSourceContext(sourceContexts[0]);
+    ContextCatalog.getNamespaces({ sourceType: self.sourceType() }).done(function (namespaces) {
+      // TODO: Namespace selection
+      self.namespaces(namespaces);
+      self.activeNamespace(namespaces[0]);
     });
 
     huePubSub.subscribe('data.catalog.entry.refreshed', function (details) {
@@ -149,7 +150,15 @@ var MetastoreViewModel = (function () {
       });
     });
 
-    huePubSub.subscribe("assist.database.selected", function (databaseDef) {
+    huePubSub.subscribe('assist.database.selected', function (databaseDef) {
+      if (self.sourceType() !== databaseDef.sourceType) {
+        console.log(databaseDef.sourceType);
+        self.sourceType(databaseDef.sourceType);
+      }
+      if (self.activeNamespace() !== databaseDef.namespace) {
+        self.activeNamespace(databaseDef.namespace);
+      }
+      // TODO: Handle namespaces + sourceType
       if (self.database()) {
         self.database().table(null);
       }
@@ -218,7 +227,7 @@ var MetastoreViewModel = (function () {
 
   MetastoreViewModel.prototype.loadDatabases = function () {
     var self = this;
-    if (self.loadingDatabases() && lastLoadDatabasesPromise) {
+    if (self.loadingDatabases() && self.lastLoadDatabasesPromise) {
       return self.lastLoadDatabasesPromise;
     }
 
@@ -232,7 +241,7 @@ var MetastoreViewModel = (function () {
       self.loadingDatabases(false);
     });
 
-    DataCatalog.getEntry({ sourceContext: self.activeSourceContext(), sourceType: self.sourceType(), path: [], definition: { type: 'source' } }).done(function (entry) {
+    DataCatalog.getEntry({ namespace: self.activeNamespace(), sourceType: self.sourceType(), path: [], definition: { type: 'source' } }).done(function (entry) {
       self.catalogEntry(entry);
       entry.getChildren().done(function (databaseEntries) {
         self.databases($.map(databaseEntries, function (databaseEntry) {
@@ -248,6 +257,12 @@ var MetastoreViewModel = (function () {
   MetastoreViewModel.prototype.loadTableDef = function (tableDef, callback) {
     var self = this;
     self.loadingTable(true);
+    if (self.sourceType() !== tableDef.sourceType) {
+      self.sourceType(tableDef.sourceType);
+    }
+    if (self.activeNamespace() !== tableDef.namespace) {
+      self.activeNamespace(tableDef.namespace);
+    }
     self.setDatabaseByName(tableDef.database, function () {
       if (self.database()) {
         if (self.database().table() && self.database().table().catalogEntry.name === tableDef.name) {
@@ -332,7 +347,23 @@ var MetastoreViewModel = (function () {
           whenLoaded(true);
         });
       } else {
-        whenLoaded(true);
+        if (self.databases().length) {
+          whenLoaded(true);
+        } else {
+          if (!self.activeNamespace()) {
+            // TODO: Handle missing namespace in Table Browser
+            ContextCatalog.getNamespaces({ sourceType: self.sourceType() }).done(function (namespaces) {
+              self.activeNamespace(namespaces[0]);
+              self.loadDatabases().done(function () {
+                whenLoaded(true);
+              })
+            });
+          } else {
+            self.loadDatabases().done(function () {
+              whenLoaded(true);
+            })
+          }
+        }
       }
 
     }, 0);
@@ -372,7 +403,8 @@ var MetastoreViewModel = (function () {
         }, 'metastore');
         self.loadTableDef({
           name: path[2],
-          database: path[1]
+          database: path[1],
+          sourceType: self.sourceType()
         }, function(){
           if (path.length > 3 && path[3] === 'partitions'){
             huePubSub.subscribe('metastore.loaded.partitions', function() {

+ 6 - 6
apps/metastore/src/metastore/templates/metastore.mako

@@ -862,14 +862,14 @@ ${ components.menubar(is_embeddable) }
     <!-- /ko -->
     <!-- ko foreach: topJoins -->
     <tr>
-      <td><a href="javscript:void(0);" data-bind="text: tableName, sqlContextPopover: { sourceType: $parents[1].catalogEntry.getSourceType(), sourceContext: parents[1].catalogEntry.sourceContext, path: tablePath, offset: { top: -3, left: 3 }}"></a></td>
+      <td><a href="javscript:void(0);" data-bind="text: tableName, sqlContextPopover: { sourceType: $parents[1].catalogEntry.getSourceType(), namespace: parents[1].catalogEntry.namespace, path: tablePath, offset: { top: -3, left: 3 }}"></a></td>
       <td>
         <table class="metastore-join-column-table">
           <tbody data-bind="foreach: joinCols">
           <tr>
-            <td><a href="javscript:void(0);" data-bind="text: target, sqlContextPopover: { sourceType: $parents[2].catalogEntry.getSourceType(), sourceContext: $parents[2].catalogEntry.sourceContext, path: targetPath, offset: { top: -3, left: 3 }}"></a></td>
+            <td><a href="javscript:void(0);" data-bind="text: target, sqlContextPopover: { sourceType: $parents[2].catalogEntry.getSourceType(), namespace: $parents[2].catalogEntry.namespace, path: targetPath, offset: { top: -3, left: 3 }}"></a></td>
             <td class="metastore-join-arrow"><i class="fa fa-arrows-h"></i></td>
-            <td><a href="javscript:void(0);" data-bind="text: source, sqlContextPopover: { sourceType: $parents[2].catalogEntry.getSourceType(), sourceContext: $parents[2].catalogEntry.sourceContext, path: sourcePath, offset: { top: -3, left: 3 }}"></a></td>
+            <td><a href="javscript:void(0);" data-bind="text: source, sqlContextPopover: { sourceType: $parents[2].catalogEntry.getSourceType(), namespace: $parents[2].catalogEntry.namespace, path: sourcePath, offset: { top: -3, left: 3 }}"></a></td>
           </tr>
           </tbody>
         </table>
@@ -988,8 +988,8 @@ ${ components.menubar(is_embeddable) }
                 name: 'hue-drop-down',
                 params: {
                   icon: 'fa-server',
-                  value: activeSourceContext,
-                  entries: sourceContexts,
+                  value: activeNamespace,
+                  entries: namespaces,
                   searchable: true,
                   labelAttribute: 'name',
                   linkTitle: 'Active context'
@@ -1243,7 +1243,7 @@ ${ components.menubar(is_embeddable) }
       ko.applyBindings(viewModel, $('#metastoreComponents')[0]);
 
       if (location.getParameter('refresh') === 'true') {
-        DataCatalog.getEntry({ sourceContext: viewModel.activeSourceContext(), sourceType: viewModel.sourceType(), path: [], definition: { type: 'source' }}).done(function (entry) {
+        DataCatalog.getEntry({ namespace: viewModel.activeNamespace(), sourceType: viewModel.sourceType(), path: [], definition: { type: 'source' }}).done(function (entry) {
           entry.clearCache({ invalidate: sourceType() === 'impala' ? 'invalidate' : 'cache', silenceErrors: true });
           hueUtils.replaceURL('?');
         });

+ 3 - 3
apps/pig/src/pig/templates/app.mako

@@ -1020,9 +1020,9 @@ ${ commonshare() | n,unicode }
       var apiHelper = ApiHelper.getInstance({
         user: '${ user }'
       });
-      ContextCatalog.getSourceContexts({ app: ContextCatalog.BROWSER_APP, sourceType: 'hive' }).done(function (sourceContexts) {
-        // TODO: Context selection
-        DataCatalog.getChildren({ sourceContext: sourceContexts[0], sourceType: 'hive', path: ['default'], silenceErrors: true }).done(function (childEntries) {
+      ContextCatalog.getNamespaces({ sourceType: 'hive' }).done(function (namespaces) {
+        // TODO: Namespace selection
+        DataCatalog.getChildren({ namespace: namespaces[0], sourceType: 'hive', path: ['default'], silenceErrors: true }).done(function (childEntries) {
           availableTables = $.map(childEntries, function (entry) { return entry.name }).join(' ');
         });
       });

+ 13 - 5
desktop/core/src/desktop/static/desktop/js/apiHelper.js

@@ -1273,8 +1273,9 @@ var ApiHelper = (function () {
   /**
    * @param {Object} options
    * @param {string} options.sourceType
-   * @param {string} options.sourceContext
+   * @param {ContextNamespace} options.namespace
    * @param {boolean} [options.silenceErrors]
+   * @param {number} [options.timeout]
    *
    * @param {string[]} [options.path] - The path to fetch
    *
@@ -1296,7 +1297,7 @@ var ApiHelper = (function () {
           type: sourceType,
           source: isQuery ? 'query' : 'data',
         }),
-        cluster: '"' + options.sourceContext.id + '"'
+        cluster: '"' + options.namespace.id + '"'
       },
       timeout: options.timeout
     }).success(function (data) {
@@ -1368,6 +1369,7 @@ var ApiHelper = (function () {
    *
    * @param {Object} options
    * @param {boolean} [options.silenceErrors]
+   * @param {ContextNamespace} [options.namespace]
    *
    * @param {string[]} options.path
    *
@@ -1397,8 +1399,8 @@ var ApiHelper = (function () {
       'format' : 'json'
     }
 
-    if (options.sourceContext && options.sourceContext.id) {
-      params['cluster'] = options.sourceContext.id;
+    if (options.namespace && options.namespace.id) {
+      params['cluster'] = options.namespace.id;
     }
 
     var request = self.simpleGet(url, params, {
@@ -1949,12 +1951,18 @@ var ApiHelper = (function () {
     return new CancellablePromise(deferred, request);
   };
 
-  ApiHelper.prototype.fetchSourceContexts = function (options) { // TODO rename
+  ApiHelper.prototype.fetchContextNamespaces = function (options) {
     var self = this;
     var url = '/desktop/api2/context/namespaces/' + options.sourceType;
     return self.simpleGet(url, undefined, options);
   };
 
+  ApiHelper.prototype.fetchContextComputes = function (options) {
+    var self = this;
+    var url = '/desktop/api2/context/computes/' + options.sourceType;
+    return self.simpleGet(url, undefined, options);
+  };
+
   ApiHelper.prototype.getClusterConfig = function (data) {
     return $.post(FETCH_CONFIG, data);
   };

+ 16 - 25
desktop/core/src/desktop/static/desktop/js/assist/assistDbEntry.js

@@ -18,7 +18,7 @@ var AssistDbEntry = (function () {
   /**
    * @param {DataCatalogEntry} catalogEntry
    * @param {AssistDbEntry} parent
-   * @param {AssistDbSource} assistDbSource
+   * @param {AssistDbNamespace} assistDbNamespace
    * @param {Object} filter
    * @param {function} filter.querySpec (observable)
    * @param {Object} i18n
@@ -26,19 +26,17 @@ var AssistDbEntry = (function () {
    * @param {Object} navigationSettings
    * @constructor
    */
-  function AssistDbEntry (catalogEntry, parent, assistDbSource, filter, i18n, navigationSettings) {
+  function AssistDbEntry (catalogEntry, parent, assistDbNamespace, filter, i18n, navigationSettings) {
     var self = this;
     self.catalogEntry = catalogEntry;
     self.parent = parent;
-    self.assistDbSource = assistDbSource;
+    self.assistDbNamespace = assistDbNamespace;
     self.filter = filter;
     self.i18n = i18n;
     self.navigationSettings = navigationSettings;
 
-    self.sourceType = assistDbSource.sourceType;
-    self.invalidateOnRefresh =  assistDbSource.invalidateOnRefresh;
-    self.sortFunctions = assistDbSource.sortFunctions;
-    self.activeSort = assistDbSource.activeSort;
+    self.sourceType = assistDbNamespace.sourceType;
+    self.invalidateOnRefresh =  assistDbNamespace.invalidateOnRefresh;
 
     self.expandable = self.catalogEntry.hasPossibleChildren();
 
@@ -64,10 +62,6 @@ var AssistDbEntry = (function () {
       return self.entries().length > 0;
     });
 
-    self.assistDbSource.activeSort.subscribe(function (newSort) {
-      self.entries.sort(self.sortFunctions[newSort]);
-    });
-
     self.filteredEntries = ko.pureComputed(function () {
       var facets = self.filter.querySpec().facets;
       var facetMatch = !facets || Object.keys(facets).length === 0 || !facets['type']; // So far only type facet is used for SQL
@@ -152,7 +146,7 @@ var AssistDbEntry = (function () {
     var types = {};
     if (self.parent === null) { // Only find facets on the DB level
       self.entries().forEach(function (tableEntry) {
-        if (self.assistDbSource.sourceType !== 'solr') {
+        if (self.assistDbNamespace.sourceType !== 'solr') {
           if (tableEntry.catalogEntry.isTable()) {
             types.table =  types.table ? types.table + 1 : 1;
           } else if (tableEntry.catalogEntry.isView()) {
@@ -323,7 +317,6 @@ var AssistDbEntry = (function () {
             self.entries(newEntries);
             self.entries()[0].open(true);
           } else {
-            newEntries.sort(self.sortFunctions[self.assistDbSource.activeSort()]);
             self.entries(newEntries);
           }
 
@@ -337,7 +330,7 @@ var AssistDbEntry = (function () {
           self.hasErrors(true);
         });
 
-        if (self.assistDbSource.sourceType !== 'solr') {
+        if (self.assistDbNamespace.sourceType !== 'solr') {
           self.catalogEntry.loadNavigatorMetaForChildren({ silenceErrors: self.navigationSettings.rightAssist });
         }
       } else {
@@ -357,7 +350,7 @@ var AssistDbEntry = (function () {
       loadEntriesDeferred.resolve([]);
     };
 
-    if (!self.navigationSettings.rightAssist && HAS_OPTIMIZER && (self.catalogEntry.isTable() || self.catalogEntry.isDatabase()) && self.assistDbSource.sourceType !== 'solr') {
+    if (!self.navigationSettings.rightAssist && HAS_OPTIMIZER && (self.catalogEntry.isTable() || self.catalogEntry.isDatabase()) && self.assistDbNamespace.sourceType !== 'solr') {
       self.catalogEntry.loadNavOptPopularityForChildren({ silenceErrors: true }).done(function () {
         loadEntriesDeferred.done(function () {
           if (!self.hasErrors()) {
@@ -370,13 +363,8 @@ var AssistDbEntry = (function () {
                 } else if (entry.catalogEntry.navOptPopularity.selectColumn) {
                   entry.popularity(entry.catalogEntry.navOptPopularity.selectColumn.columnCount);
                 }
-
               }
             });
-
-            if (self.assistDbSource.activeSort() === 'popular') {
-              self.entries.sort(self.sortFunctions.popular);
-            }
           }
         })
       });
@@ -390,7 +378,7 @@ var AssistDbEntry = (function () {
    */
   AssistDbEntry.prototype.createEntry = function (catalogEntry) {
     var self = this;
-    return new AssistDbEntry(catalogEntry, self, self.assistDbSource, self.filter, self.i18n, self.navigationSettings)
+    return new AssistDbEntry(catalogEntry, self, self.assistDbNamespace, self.filter, self.i18n, self.navigationSettings)
   };
 
   AssistDbEntry.prototype.getHierarchy = function () {
@@ -415,7 +403,7 @@ var AssistDbEntry = (function () {
       huePubSub.publish('open.link', '/hue/dashboard/browse/' + self.catalogEntry.name);
     }
     else {
-      huePubSub.publish('open.link', '/hue/dashboard/browse/' + self.getDatabaseName() + '.' + self.getTableName() + '?engine=' + self.assistDbSource.sourceType);
+      huePubSub.publish('open.link', '/hue/dashboard/browse/' + self.getDatabaseName() + '.' + self.getTableName() + '?engine=' + self.assistDbNamespace.sourceType);
     }
   };
 
@@ -479,13 +467,16 @@ var AssistDbEntry = (function () {
   AssistDbEntry.prototype.openItem = function () {
     var self = this;
     if (self.catalogEntry.isTableOrView()) {
-      huePubSub.publish("assist.table.selected", {
+      huePubSub.publish('assist.table.selected', {
+        sourceType: self.assistDbNamespace.sourceType,
+        namespace: self.assistDbNamespace.namespace,
         database: self.databaseName,
         name: self.catalogEntry.name
       })
     } else if (self.catalogEntry.isDatabase()) {
-      huePubSub.publish("assist.database.selected", {
-        source: self.assistDbSource.sourceType,
+      huePubSub.publish('assist.database.selected', {
+        sourceType: self.assistDbNamespace.sourceType,
+        namespace: self.assistDbNamespace.namespace,
         name: self.catalogEntry.name
       })
     }

+ 17 - 0
desktop/core/src/desktop/static/desktop/js/assist/assistDbNamespace.js

@@ -0,0 +1,17 @@
+// 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.
+
+

+ 120 - 90
desktop/core/src/desktop/static/desktop/js/assist/assistDbSource.js

@@ -15,63 +15,129 @@
 // limitations under the License.
 
 var AssistDbSource = (function () {
-
-  var sortFunctions = {
-    alpha: function (a, b) {
-      return a.catalogEntry.name.localeCompare(b.catalogEntry.name);
-    },
-    creation: function (a, b) {
-      if (a.catalogEntry.isField()) {
-        return a.catalogEntry.getIndex() - b.catalogEntry.getIndex();
-      }
-      return sortFunctions.alpha(a, b);
-    },
-    popular: function (a, b) {
-      if (a.popularity() === b.popularity()) {
-        return sortFunctions.creation(a, b);
-      }
-      return b.popularity() - a.popularity();
-    }
-  };
-
   /**
    * @param {Object} options
    * @param {Object} options.i18n
    * @param {string} options.type
-   * @param {Observable} options.activeSourceContext
+   * @param {ContextNamespace} [options.initialNamespace] - Optional initial namespace to use
    * @param {string} options.name
    * @param {Object} options.navigationSettings
    * @constructor
    */
   function AssistDbSource (options) {
-
     var self = this;
-    self.isSource = true;
-    self.i18n = options.i18n;
-    self.navigationSettings = options.navigationSettings;
-    self.apiHelper = ApiHelper.getInstance();
+
     self.sourceType = options.type;
-    self.activeSourceContext = options.activeSourceContext;
     self.name = options.name;
-    self.catalogEntry;
+    self.i18n = options.i18n;
+    self.navigationSettings = options.navigationSettings;
+    self.initialNamespace = options.activeNamespace;
 
+    self.selectedNamespace = ko.observable();
+    self.namespaces = ko.observableArray();
+
+    self.loadedDeferred = $.Deferred();
+    self.loading = ko.observable(false);
     self.hasErrors = ko.observable(false);
-    self.simpleStyles = ko.observable(false);
-    self.sortFunctions = sortFunctions;
+
+    self.selectedNamespace.subscribe(function (namespace) {
+      if (namespace && !namespace.loaded() && !namespace.loading()) {
+        namespace.initDatabases();
+      }
+    });
+
+    self.hasNamespaces = ko.pureComputed(function () {
+      return self.namespaces().length > 0;
+    });
+  }
+
+  AssistDbSource.prototype.whenLoaded = function (callback) {
+    var self = this;
+    self.loadedDeferred.done(callback);
+  };
+
+  AssistDbSource.prototype.loadNamespaces = function () {
+    var self = this;
+
+    self.loading(true);
+
+    ContextCatalog.getNamespaces({ sourceType: self.sourceType }).done(function (namespaces) {
+      var assistNamespaces = [];
+      var activeNamespace;
+      namespaces.forEach(function (namespace) {
+        var assistNamespace = new AssistDbNamespace({
+          sourceType: self.sourceType,
+          namespace: namespace,
+          i18n: self.i18n,
+          navigationSettings: self.navigationSettings
+        });
+
+        if (self.initialNamespace && namespace.id === self.initialNamespace.id) {
+          activeNamespace = assistNamespace;
+        }
+        assistNamespaces.push(assistNamespace);
+      });
+      self.namespaces(assistNamespaces);
+      self.selectedNamespace(activeNamespace || assistNamespaces.length && assistNamespaces[0]);
+    }).fail(function () {
+      self.hasErrors(true);
+    }).always(function () {
+      self.loadedDeferred.resolve();
+      self.loading(false);
+    })
+
+  };
+
+  AssistDbSource.prototype.highlightInside = function (path) {
+    // TODO: Highlight from namespace
+  };
+
+  AssistDbSource.prototype.triggerRefresh = function (data, event) {
+    // TODO: Refresh namespaces
+  };
+
+  return AssistDbSource;
+})();
+
+
+
+var AssistDbNamespace = (function () {
+
+  /**
+   * @param {Object} options
+   * @param {Object} options.i18n
+   * @param {string} options.sourceType
+   * @param {ContextNamespace} options.namespace
+   * @param {Object} options.navigationSettings
+   * @constructor
+   */
+  function AssistDbNamespace (options) {
+    var self = this;
+
+    self.i18n = options.i18n;
+    self.navigationSettings = options.navigationSettings;
+    self.sourceType = options.sourceType;
+
+    self.namespace = options.namespace;
+    self.name = options.namespace.name;
+
+    self.catalogEntry;
+    self.dbIndex = {};
+    self.databases = ko.observableArray();
+    self.selectedDatabase = ko.observable();
 
     self.highlight = ko.observable(false);
 
+    self.loadedPromise = $.Deferred();
+    self.loadedDeferred = $.Deferred();
+    self.loaded = ko.observable(false);
+    self.loading = ko.observable(false);
+    self.reloading = ko.observable(false);
+    self.hasErrors = ko.observable(false);
     self.invalidateOnRefresh = ko.observable('cache');
 
-    self.activeSort = ko.observable('creation');
-
-    self.activeSort.subscribe(function (newSort) {
-      if (newSort === 'popular') {
-        // TODO: Sort popular databases
-        self.databases.sort(sortFunctions.alpha)
-      } else {
-        self.databases.sort(sortFunctions[newSort]);
-      }
+    self.loadingTables = ko.pureComputed(function() {
+      return typeof self.selectedDatabase() !== 'undefined' && self.selectedDatabase() !== null && self.selectedDatabase().loading();
     });
 
     self.filter = {
@@ -82,15 +148,6 @@ var AssistDbSource = (function () {
       return self.filter.querySpec() && self.filter.querySpec().query !== '';
     });
 
-    var storageSearchVisible = $.totalStorage(self.sourceType + ".assist.searchVisible");
-    self.searchVisible = ko.observable(storageSearchVisible || false);
-
-    self.searchVisible.subscribe(function (newValue) {
-      $.totalStorage(self.sourceType + ".assist.searchVisible", newValue);
-    });
-
-    self.databases = ko.observableArray();
-
     self.hasEntries = ko.pureComputed(function() {
       return self.databases().length > 0;
     });
@@ -99,13 +156,9 @@ var AssistDbSource = (function () {
       if (!self.filter.querySpec() || typeof self.filter.querySpec().query === 'undefined' || !self.filter.querySpec().query) {
         return self.databases();
       }
-      var result = [];
-      $.each(self.databases(), function (index, database) {
-        if (database.catalogEntry.name.toLowerCase().indexOf(self.filter.querySpec().query.toLowerCase()) > -1) {
-          result.push(database);
-        }
+      return self.databases().filter(function (database) {
+        return database.catalogEntry.name.toLowerCase().indexOf(self.filter.querySpec().query.toLowerCase()) !== -1
       });
-      return result;
     });
 
     self.autocompleteFromEntries = function (nonPartial, partial) {
@@ -119,8 +172,6 @@ var AssistDbSource = (function () {
       return result;
     };
 
-    self.selectedDatabase = ko.observable();
-
     self.selectedDatabase.subscribe(function () {
       var db = self.selectedDatabase();
       if (HAS_OPTIMIZER && db && !db.popularityIndexSet && self.sourceType !== 'solr') {
@@ -131,9 +182,6 @@ var AssistDbSource = (function () {
                 entry.popularity(entry.catalogEntry.navOptPopularity.popularity )
               }
             });
-            if (self.activeSort() === 'popular') {
-              db.entries.sort(sortFunctions.popular);
-            }
           };
 
           if (db.loading()) {
@@ -155,40 +203,22 @@ var AssistDbSource = (function () {
       }
     });
 
-    if (!self.navigationSettings.rightAssist) {
-      huePubSub.subscribe('assist.database.get', function (callback) {
-        callback(self.selectedDatabase());
-      });
-    }
-
-    self.reloading = ko.observable(false);
-
-    self.loadingTables = ko.pureComputed(function() {
-      return typeof self.selectedDatabase() !== 'undefined' && self.selectedDatabase() !== null && self.selectedDatabase().loading();
-    });
-
-    self.loadingSamples = ko.observable(true);
-    self.samples = ko.observable();
-
     self.selectedDatabaseChanged = function () {
       if (self.selectedDatabase()) {
         if (!self.selectedDatabase().hasEntries() && !self.selectedDatabase().loading()) {
           self.selectedDatabase().loadEntries()
         }
         if (!self.navigationSettings.rightAssist) {
-          self.apiHelper.setInTotalStorage('assist_' + self.sourceType, 'lastSelectedDb', self.selectedDatabase().catalogEntry.name);
-          huePubSub.publish("assist.database.set", {
+          ApiHelper.getInstance().setInTotalStorage('assist_' + self.sourceType, 'lastSelectedDb', self.selectedDatabase().catalogEntry.name);
+          huePubSub.publish('assist.database.set', {
             source: self.sourceType,
+            namespace: self.namespace.namespace,
             name: self.selectedDatabase().catalogEntry.name
           })
         }
       }
     };
 
-    self.loaded = ko.observable(false);
-    self.loading = ko.observable(false);
-    self.dbIndex = {};
-
     var nestedFilter = {
       querySpec: ko.observable({}).extend({ rateLimit: 300 })
     };
@@ -202,7 +232,7 @@ var AssistDbSource = (function () {
         self.selectedDatabaseChanged();
         return;
       }
-      var lastSelectedDb = self.apiHelper.getFromTotalStorage('assist_' + self.sourceType, 'lastSelectedDb', 'default');
+      var lastSelectedDb = ApiHelper.getInstance().getFromTotalStorage('assist_' + self.sourceType, 'lastSelectedDb', 'default');
       if (lastSelectedDb && self.dbIndex[lastSelectedDb]) {
         self.selectedDatabase(self.dbIndex[lastSelectedDb]);
         self.selectedDatabaseChanged();
@@ -224,7 +254,7 @@ var AssistDbSource = (function () {
       self.selectedDatabase(null);
       self.databases([]);
 
-      DataCatalog.getEntry({ sourceType: self.sourceType, sourceContext: self.activeSourceContext(), path : [], definition: { type: 'source' } }).done(function (catalogEntry) {
+      DataCatalog.getEntry({ sourceType: self.sourceType, namespace: self.namespace, path : [], definition: { type: 'source' } }).done(function (catalogEntry) {
         self.catalogEntry = catalogEntry;
         self.catalogEntry.getChildren({ silenceErrors: self.navigationSettings.rightAssist }).done(function (databaseEntries) {
           self.dbIndex = {};
@@ -245,7 +275,6 @@ var AssistDbSource = (function () {
           if (!hasNavMeta && self.sourceType !== 'solr') {
             self.catalogEntry.loadNavigatorMetaForChildren({ silenceErrors: true });
           }
-          dbs.sort(sortFunctions[self.activeSort()]);
           self.databases(dbs);
 
           if (typeof callback === 'function') {
@@ -255,6 +284,7 @@ var AssistDbSource = (function () {
           self.hasErrors(true);
         }).always(function () {
           self.loaded(true);
+          self.loadedDeferred.resolve();
           self.loading(false);
           self.reloading(false);
         });
@@ -264,11 +294,6 @@ var AssistDbSource = (function () {
 
     self.modalItem = ko.observable();
 
-    self.repositionActions = function(data, event) {
-      var $container = $(event.target);
-      $container.find(".assist-actions, .assist-db-header-actions").css('right', -$container.scrollLeft() + 'px');
-    };
-
     if (!self.navigationSettings.rightAssist) {
       huePubSub.subscribe('data.catalog.entry.refreshed', function (details) {
         if (self.catalogEntry === details.entry) {
@@ -289,7 +314,12 @@ var AssistDbSource = (function () {
     }
   }
 
-  AssistDbSource.prototype.highlightInside = function (path) {
+  AssistDbNamespace.prototype.whenLoaded = function (callback) {
+    var self = this;
+    self.loadedDeferred.done(callback);
+  };
+
+  AssistDbNamespace.prototype.highlightInside = function (path) {
     var self = this;
 
     if (self.navigationSettings.rightAssist) {
@@ -347,12 +377,12 @@ var AssistDbSource = (function () {
     }
   };
 
-  AssistDbSource.prototype.triggerRefresh = function (data, event) {
+  AssistDbNamespace.prototype.triggerRefresh = function (data, event) {
     var self = this;
     if (self.catalogEntry) {
       self.catalogEntry.clearCache({ invalidate: self.invalidateOnRefresh() });
     }
   };
 
-  return AssistDbSource;
+  return AssistDbNamespace;
 })();

+ 145 - 0
desktop/core/src/desktop/static/desktop/js/contextCatalog.js

@@ -0,0 +1,145 @@
+// 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.
+
+/**
+ * @typedef {Object} ContextNamespace
+ * @property {string} id
+ * @property {string} name
+ */
+
+var ContextCatalog = (function () {
+
+  var CONTEXT_CATALOG_VERSION = 1;
+
+  var ContextCatalog = (function () {
+
+    function ContextCatalog() {
+      var self = this;
+      self.namespaces = {};
+      self.namespacePromises = {};
+
+      self.computes = {};
+      self.computePromises = {};
+
+      // TODO: Add caching
+    }
+
+    /**
+     * @param {Object} options
+     * @param {string} options.sourceType
+     * @param {boolean} [options.silenceErrors]
+     * @return {Promise}
+     */
+    ContextCatalog.prototype.getNamespaces = function (options) {
+      var self = this;
+
+      if (self.namespacePromises[options.sourceType]) {
+        return self.namespacePromises[options.sourceType];
+      }
+
+      if (self.namespaces[options.sourceType]) {
+        self.namespacePromises[options.sourceType] = $.Deferred().resolve(self.namespaces[options.sourceType]).promise();
+        return self.namespacePromises[options.sourceType];
+      }
+
+      var deferred = $.Deferred();
+      self.namespacePromises[options.sourceType] = deferred.promise();
+
+      ApiHelper.getInstance().fetchContextNamespaces(options).done(function (namespaces) {
+        if (namespaces[options.sourceType]) {
+          var namespaces = namespaces[options.sourceType];
+          if (namespaces) {
+            self.namespaces[self.sourceType] = namespaces;
+            deferred.resolve(self.namespaces[self.sourceType])
+            // TODO: save
+          } else {
+            deferred.reject();
+          }
+        } else {
+          deferred.reject();
+        }
+      });
+
+      return self.namespacePromises[options.sourceType];
+    };
+
+    /**
+     * @param {Object} options
+     * @param {string} options.sourceType
+     * @param {boolean} [options.silenceErrors]
+     * @return {Promise}
+     */
+    ContextCatalog.prototype.getComputes = function (options) {
+      var self = this;
+
+      if (self.computePromises[options.sourceType]) {
+        return self.computePromises[options.sourceType];
+      }
+
+      if (self.computes[options.sourceType]) {
+        self.computePromises[options.sourceType] = $.Deferred().resolve(self.computes[options.sourceType]).promise();
+        return self.computePromises[options.sourceType];
+      }
+
+      var deferred = $.Deferred();
+      self.computePromises[options.sourceType] = deferred.promise();
+
+      ApiHelper.getInstance().fetchContextComputes(options).done(function (computes) {
+        if (computes[options.sourceType]) {
+          var computes = computes[options.sourceType];
+          if (computes) {
+            self.computes[self.sourceType] = computes;
+            deferred.resolve(self.computes[self.sourceType])
+            // TODO: save
+          } else {
+            deferred.reject();
+          }
+        } else {
+          deferred.reject();
+        }
+      });
+
+      return self.computePromises[options.sourceType];
+    };
+
+    return ContextCatalog;
+  })();
+
+  return (function () {
+    var contextCatalog = new ContextCatalog();
+
+    return {
+      /**
+       * @param {Object} options
+       * @param {string} options.sourceType
+       * @param {boolean} [options.silenceErrors]
+       * @return {Promise}
+       */
+      getNamespaces: function (options) {
+        return contextCatalog.getNamespaces(options);
+      },
+      /**
+       * @param {Object} options
+       * @param {string} options.sourceType
+       * @param {boolean} [options.silenceErrors]
+       * @return {Promise}
+       */
+      getComputes: function (options) {
+        return contextCatalog.getComputes(options);
+      }
+    }
+  })();
+})();

+ 0 - 118
desktop/core/src/desktop/static/desktop/js/contextHelper.js

@@ -1,118 +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.
-
-/**
- * @typedef {Object} SourceContext
- * @property {string} id
- * @property {string} name
- */
-
-var ContextCatalog = (function () {
-
-  var CONTEXT_CATALOG_VERSION = 1;
-
-  var ContextCatalog = (function () {
-    function ContextCatalog() {
-      var self = this;
-      self.entries = {};
-
-      // TODO: Add caching
-    }
-
-    ContextCatalog.prototype.getContextCatalogEntry = function (app) {
-      var self = this;
-      if (!self.entries[app]) {
-        self.entries[app] = new ContextCatalogEntry(app);
-      }
-      return self.entries[app];
-    };
-
-    return ContextCatalog;
-  })();
-
-
-  var ContextCatalogEntry = (function () {
-    var ContextCatalogEntry = function (app) {
-      var self = this;
-      self.app = app;
-      self.reset();
-    };
-
-    ContextCatalogEntry.prototype.reset = function () {
-      var self = this;
-      self.sourceContexts = {}; // TODO: Cache this
-      self.sourceContextsPromises = {};
-    };
-
-    /**
-     *
-     * @param {Object} options
-     * @param {string} options.sourceType
-     * @param {boolean} [options.silenceErrors]
-     * @return {Promise}
-     */
-    ContextCatalogEntry.prototype.getSourceContexts = function (options) {
-      var self = this;
-
-      if (self.sourceContextsPromises[options.sourceType]) {
-        return self.sourceContextsPromises[options.sourceType];
-      }
-
-      var deferred = $.Deferred();
-      self.sourceContextsPromises[options.sourceType] = deferred.promise();
-
-      ApiHelper.getInstance().fetchSourceContexts(options).done(function (sourceContexts) {
-        if (sourceContexts[options.sourceType]) {
-          var namespaces = sourceContexts[options.sourceType];
-          // TODO: For now we only care about namespaces.
-          if (namespaces) {
-            self.sourceContexts[self.sourceType] = namespaces;
-            deferred.resolve(self.sourceContexts[self.sourceType])
-            // TODO: save
-          } else {
-            deferred.reject();
-          }
-        } else {
-          deferred.reject();
-        }
-      });
-
-      return deferred.promise({ name: 'foo' }); // TODO
-    };
-
-    return ContextCatalogEntry;
-  })();
-
-  return (function () {
-    var contextCatalog = new ContextCatalog();
-
-    return {
-      BROWSER_APP: 'browser',
-      EDITOR_APP: 'editor',
-
-      /**
-       * @param {Object} options
-       * @param {string} options.app
-       * @param {string} options.sourceType
-       * @param {boolean} [options.silenceErrors]
-       * @return {Promise}
-       */
-      getSourceContexts: function (options) {
-        return contextCatalog.getContextCatalogEntry(options.app).getSourceContexts(options);
-      }
-    }
-  })();
-})();

+ 35 - 32
desktop/core/src/desktop/static/desktop/js/dataCatalog.js

@@ -66,7 +66,7 @@ var DataCatalog = (function () {
   var fetchAndSave = function (apiHelperFunction, attributeName, entry, apiOptions) {
     return ApiHelper.getInstance()[apiHelperFunction]({
       sourceType: entry.dataCatalog.sourceType,
-      sourceContext: entry.sourceContext,
+      namespace: entry.namespace,
       path: entry.path, // Set for DataCatalogEntry
       paths: entry.paths, // Set for MultiTableEntry
       silenceErrors: apiOptions && apiOptions.silenceErrors,
@@ -123,13 +123,13 @@ var DataCatalog = (function () {
     /**
      * Clears the data catalog and cache for the given path and any children thereof.
      *
-     * @param {SourceContext} [sourceContext] - The source context
+     * @param {ContextNamespace} [namespace] - The context namespace
      * @param {string[]} rootPath - The path to clear
      */
-    DataCatalog.prototype.clearStorageCascade = function (sourceContext, rootPath) {
+    DataCatalog.prototype.clearStorageCascade = function (namespace, rootPath) {
       var self = this;
       var deferred = $.Deferred();
-      if (!sourceContext) {
+      if (!namespace) {
         if (rootPath.length === 0) {
           self.entries = {};
           self.store.clear().then(deferred.resolve).catch(deferred.reject);
@@ -138,7 +138,7 @@ var DataCatalog = (function () {
         return deferred.reject().promise();
       }
 
-      var keyPrefix = sourceContext.id;
+      var keyPrefix = namespace.id;
       if (rootPath.length) {
         keyPrefix += '_' +  rootPath.join('.');
       }
@@ -178,7 +178,7 @@ var DataCatalog = (function () {
       }
       var deferred = $.Deferred();
 
-      var identifier = dataCatalogEntry.sourceContext.id;
+      var identifier = dataCatalogEntry.namespace.id;
       if (dataCatalogEntry.path.length) {
         identifier += '_' + dataCatalogEntry.path.join('.');
       }
@@ -202,7 +202,7 @@ var DataCatalog = (function () {
      * Loads Navigator Optimizer popularity for multiple tables in one go.
      *
      * @param {Object} options
-     * @oaram {SourceContext} options.sourceContext
+     * @param {ContextNamespace} options.namespace - The context namespace
      * @param {string[][]} options.paths
      * @param {boolean} [options.silenceErrors] - Default true
      * @param {boolean} [options.cancellable] - Default false
@@ -221,7 +221,7 @@ var DataCatalog = (function () {
       var existingPromises = [];
       options.paths.forEach(function (path) {
         var existingDeferred = $.Deferred();
-        self.getEntry({ sourceContext: options.sourceContext, path: path }).done(function (tableEntry) {
+        self.getEntry({ namespace: options.namespace, path: path }).done(function (tableEntry) {
           if (tableEntry.navOptPopularityForChildrenPromise) {
             tableEntry.navOptPopularityForChildrenPromise.done(function (existingPopularEntries) {
               popularEntries = popularEntries.concat(existingPopularEntries);
@@ -281,7 +281,7 @@ var DataCatalog = (function () {
 
             Object.keys(perTable).forEach(function (path) {
               var tableDeferred = $.Deferred();
-              self.getEntry({ sourceContext: options.sourceContext, path: path }).done(function (entry) {
+              self.getEntry({ namespace: options.namespace, path: path }).done(function (entry) {
                 cancellablePromises.push(entry.trackedPromise('navOptPopularityForChildrenPromise', entry.applyNavOptResponseToChildren(perTable[path], options).done(function (entries) {
                   popularEntries = popularEntries.concat(entries);
                   tableDeferred.resolve();
@@ -335,21 +335,21 @@ var DataCatalog = (function () {
 
     /**
      * @param {Object} options
-     * @param {SourceContext} options.sourceContext
+     * @param {ContextNamespace} options.namespace - The context namespace
      * @param {string|string[]} options.path
      * @return {DataCatalogEntry}
      */
     DataCatalog.prototype.getKnownEntry = function (options) {
       var self = this;
       var identifier = typeof options.path === 'string' ? options.path : options.path.join('.');
-      identifier = options.sourceContext.id + (identifier ? '_' + identifier : '');
+      identifier = options.namespace.id + (identifier ? '_' + identifier : '');
       return self.entries[identifier];
     };
 
     /**
      * @param {Object} options
      * @param {string|string[]} options.path
-     * @param {SourceContext} options.sourceContext
+     * @param {ContextNamespace} options.namespace - The context namespace
      * @param {Object} [options.definition] - The initial definition if not already set on the entry
      * @param {boolean} [options.cachedOnly] - Default: false
      * @return {Promise}
@@ -357,7 +357,7 @@ var DataCatalog = (function () {
     DataCatalog.prototype.getEntry = function (options) {
       var self = this;
       var identifier = typeof options.path === 'string' ? options.path : options.path.join('.');
-      identifier = options.sourceContext.id + (identifier ? '_' + identifier : '');
+      identifier = options.namespace.id + (identifier ? '_' + identifier : '');
       if (self.entries[identifier]) {
         return self.entries[identifier];
       }
@@ -366,11 +366,11 @@ var DataCatalog = (function () {
       self.entries[identifier] = deferred.promise();
 
       if (!cacheEnabled) {
-        deferred.resolve(new DataCatalogEntry({ dataCatalog: self, sourceContext: options.sourceContext, path: options.path, definition: options.definition })).promise();
+        deferred.resolve(new DataCatalogEntry({ dataCatalog: self, namespace: options.namespace, path: options.path, definition: options.definition })).promise();
       } else {
         self.store.getItem(identifier).then(function (storeEntry) {
           var definition = storeEntry ? storeEntry.definition : options.definition;
-          var entry = new DataCatalogEntry({ dataCatalog: self, sourceContext: options.sourceContext, path: options.path, definition: definition });
+          var entry = new DataCatalogEntry({ dataCatalog: self, namespace: options.namespace, path: options.path, definition: definition });
           if (storeEntry) {
             mergeEntry(entry, storeEntry);
           } else if (!options.cachedOnly && options.definition) {
@@ -379,7 +379,7 @@ var DataCatalog = (function () {
           deferred.resolve(entry);
         }).catch(function (error) {
           console.warn(error);
-          var entry = new DataCatalogEntry({ dataCatalog: self, sourceContext: options.sourceContext, path: options.path, definition: options.definition });
+          var entry = new DataCatalogEntry({ dataCatalog: self, namespace: options.namespace, path: options.path, definition: options.definition });
           if (!options.cachedOnly && options.definition) {
             entry.saveLater();
           }
@@ -415,31 +415,31 @@ var DataCatalog = (function () {
     /**
      * Creates an identifier for the given paths with duplicates removed
      *
-     * @param {SourceContext} sourceContext
+     * @param {ContextNamespace} options.namespace - The context namespace
      * @param {string[][]} paths
      * @return {string}
      */
-    var createMultiTableIdentifier = function (sourceContext, paths) {
+    var createMultiTableIdentifier = function (namespace, paths) {
       var pathSet = {};
       paths.forEach(function (path) {
         pathSet[path.join('.')] = true;
       });
       var uniquePaths = Object.keys(pathSet);
       uniquePaths.sort();
-      return sourceContext.id + '_' + uniquePaths.join(',');
+      return namespace.id + '_' + uniquePaths.join(',');
     };
 
     /**
      *
      * @param {Object} options
-     * @param {SourceContext} options.sourceContext
+     * @param {ContextNamespace} options.namespace - The context namespace
      * @param {string[][]} options.paths
      *
      * @return {Promise}
      */
     DataCatalog.prototype.getMultiTableEntry = function (options) {
       var self = this;
-      var identifier = createMultiTableIdentifier(options.sourceContext, options.paths);
+      var identifier = createMultiTableIdentifier(options.namespace, options.paths);
       if (self.multiTableEntries[identifier]) {
         return self.multiTableEntries[identifier];
       }
@@ -596,7 +596,7 @@ var DataCatalog = (function () {
     /**
      * @param {DataCatalog} options.dataCatalog
      * @param {string|string[]} options.path
-     * @param {SourceContext} options.sourceContext
+     * @param {ContextNamespace} options.namespace - The context namespace
      * @param {Object} options.definition - Initial known metadata on creation (normally comes from the parent entry)
      *
      * @constructor
@@ -604,7 +604,7 @@ var DataCatalog = (function () {
     function DataCatalogEntry(options) {
       var self = this;
 
-      self.sourceContext = options.sourceContext;
+      self.namespace = options.namespace;
       self.dataCatalog = options.dataCatalog;
       self.path = typeof options.path === 'string' && options.path ? options.path.split('.') : options.path || [];
       self.name = self.path.length ? self.path[self.path.length - 1] : options.dataCatalog.sourceType;
@@ -655,7 +655,7 @@ var DataCatalog = (function () {
       self.childrenPromise = undefined;
       
       if (self.path.length) {
-        var parent = self.dataCatalog.getKnownEntry({ sourceContext: self.sourceContext, path: self.path.slice(0, self.path.length - 1) });
+        var parent = self.dataCatalog.getKnownEntry({ namespace: self.namespace, path: self.path.slice(0, self.path.length - 1) });
         if (parent) {
           parent.navigatorMetaForChildrenPromise = undefined;
           parent.navOptPopularityForChildrenPromise = undefined;
@@ -722,7 +722,7 @@ var DataCatalog = (function () {
       }
 
       self.reset();
-      var saveDeferred = options.cascade ? self.dataCatalog.clearStorageCascade(self.sourceContext, self.path) : self.save();
+      var saveDeferred = options.cascade ? self.dataCatalog.clearStorageCascade(self.namespace, self.path) : self.save();
 
       var clearPromise = $.when(invalidatePromise, saveDeferred);
 
@@ -800,7 +800,7 @@ var DataCatalog = (function () {
           entities.forEach(function (entity) {
             if (!sourceMeta.databases || ((entity.name || entity) !== '_impala_builtins')) {
               promises.push(self.dataCatalog.getEntry({
-                sourceContext: self.sourceContext,
+                namespace: self.namespace,
                 path: self.path.concat(entity.name || entity)
               }).done(function (catalogEntry) {
                 if (!catalogEntry.definition || typeof catalogEntry.definition.index === 'undefined') {
@@ -828,7 +828,7 @@ var DataCatalog = (function () {
         if ((self.getSourceType() === 'impala' || self.getSourceType() === 'hive') && self.isComplex()) {
           (sourceMeta.type === 'map' ? ['key', 'value'] : ['item']).forEach(function (path) {
             if (sourceMeta[path]) {
-              promises.push(self.dataCatalog.getEntry({ sourceContext: self.sourceContext, path: self.path.concat(path) }).done(function (catalogEntry) {
+              promises.push(self.dataCatalog.getEntry({ namespace: self.namespace, path: self.path.concat(path) }).done(function (catalogEntry) {
                 if (!catalogEntry.definition || typeof catalogEntry.definition.index === 'undefined') {
                   var definition = sourceMeta[path];
                   definition.index = index++;
@@ -1741,7 +1741,7 @@ var DataCatalog = (function () {
           }
         };
 
-        self.dataCatalog.getEntry({ sourceContext: self.sourceContext, path: self.path.slice(0, 2), definition: { type: 'table' } }).done(function (tableEntry) {
+        self.dataCatalog.getEntry({ namespace: self.namespace, path: self.path.slice(0, 2), definition: { type: 'table' } }).done(function (tableEntry) {
           if (tableEntry && tableEntry.samplePromise) {
             cancellablePromises.push(applyCancellable(tableEntry.samplePromise, options));
 
@@ -1806,7 +1806,7 @@ var DataCatalog = (function () {
         return deferred.reject();
       }
       var cancellablePromises = [];
-      catalogEntry.dataCatalog.getMultiTableEntry({ sourceContext: catalogEntry.sourceContext, paths: [ catalogEntry.path ] }).done(function (multiTableEntry) {
+      catalogEntry.dataCatalog.getMultiTableEntry({ namespace: catalogEntry.namespace, paths: [ catalogEntry.path ] }).done(function (multiTableEntry) {
         cancellablePromises.push(multiTableEntry[functionName](options).done(deferred.resolve).fail(deferred.reject));
       }).fail(deferred.reject);
       return new CancellablePromise(deferred, undefined, cancellablePromises);
@@ -2141,6 +2141,9 @@ var DataCatalog = (function () {
      * @return {DataCatalog}
      */
     var getCatalog = function (sourceType) {
+      if (!sourceType) {
+        throw new Error('getCatalog called without sourceType');
+      }
       return sourceBoundCatalogs[sourceType] || (sourceBoundCatalogs[sourceType] = new DataCatalog(sourceType));
     };
 
@@ -2149,7 +2152,7 @@ var DataCatalog = (function () {
       /**
        * @param {Object} options
        * @param {string} options.sourceType
-       * @param {SourceContext} options.sourceContext
+       * @param {ContextNamespace} options.namespace - The context namespace
        * @param {string|string[]} options.path
        * @param {Object} [options.definition] - Optional initial definition
        *
@@ -2162,7 +2165,7 @@ var DataCatalog = (function () {
       /**
        * @param {Object} options
        * @param {string} options.sourceType
-       * @param {SourceContext} options.sourceContext
+       * @param {ContextNamespace} options.namespace - The context namespace
        * @param {string[][]} options.paths
        *
        * @return {Promise}
@@ -2177,7 +2180,7 @@ var DataCatalog = (function () {
        *
        * @param {Object} options
        * @param {string} options.sourceType
-       * @param {SourceContext} options.sourceContext
+       * @param {ContextNamespace} options.namespace - The context namespace
        * @param {string|string[]} options.path
        * @param {Object} [options.definition] - Optional initial definition of the parent entry
        * @param {boolean} [options.silenceErrors]

+ 12 - 12
desktop/core/src/desktop/static/desktop/js/jquery.hiveautocomplete.js

@@ -49,13 +49,13 @@
     self._defaults = defaults;
     self._name = pluginName;
 
-    self.activeSourceContextDeferred = $.Deferred();
-    if (self.options.activeSourceContext) {
-      self.activeSourceContextDeferred.resolve(self.options.activeSourceContext);
+    self.activeContextNamespaceDeferred = $.Deferred();
+    if (self.options.activeNamespace) {
+      self.activeContextNamespaceDeferred.resolve(self.options.activeNamespace);
     } else {
-      ContextCatalog.getSourceContexts({ app: ContextCatalog.BROWSER_APP, sourceType: 'hive' }).done(function (sourceContexts) {
-        // TODO: Context selection in caller
-        self.activeSourceContextDeferred.resolve(sourceContexts[0]);
+      ContextCatalog.getNamespaces({ sourceType: 'hive' }).done(function (namespaces) {
+        // TODO: Namespace selection in caller
+        self.activeContextNamespaceDeferred.resolve(namespaces[0]);
       })
     }
 
@@ -240,8 +240,8 @@
 
     self.getDatabases = function (callback) {
       var self = this;
-      self.activeSourceContextDeferred.done(function (sourceContext) {
-        DataCatalog.getChildren({ sourceType: self.options.apiHelperType, sourceContext: sourceContext, path: [] }).done(function (dbEntries) {
+      self.activeContextNamespaceDeferred.done(function (namespace) {
+        DataCatalog.getChildren({ sourceType: self.options.apiHelperType, namespace: namespace, path: [] }).done(function (dbEntries) {
           callback($.map(dbEntries, function (entry) { return entry.name }));
         });
       })
@@ -249,8 +249,8 @@
 
     self.getTables = function (database, callback) {
       var self = this;
-      self.activeSourceContextDeferred.done(function (sourceContext) {
-        DataCatalog.getEntry({ sourceType: self.options.apiHelperType, sourceContext: sourceContext, path: [ database ] }).done(function (entry) {
+      self.activeContextNamespaceDeferred.done(function (namespace) {
+        DataCatalog.getEntry({ sourceType: self.options.apiHelperType, namespace: namespace, path: [ database ] }).done(function (entry) {
           entry.getSourceMeta().done(callback)
         });
       });
@@ -258,8 +258,8 @@
 
     self.getColumns = function (database, table, callback) {
       var self = this;
-      self.activeSourceContextDeferred.done(function (sourceContext) {
-        DataCatalog.getEntry({sourceType: self.options.apiHelperType, sourceContext: sourceContext, path: [database, table]}).done(function (entry) {
+      self.activeContextNamespaceDeferred.done(function (namespace) {
+        DataCatalog.getEntry({sourceType: self.options.apiHelperType, namespace: namespace, path: [ database, table ]}).done(function (entry) {
           entry.getSourceMeta().done(callback)
         });
       });

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

@@ -2486,7 +2486,7 @@
    * Parameters:
    *
    * {string} sourceType - 'impala', 'hive' etc.
-   * {SourceContext} sourceContext
+   * {ContextNamespace} namespace
    * {string} path - the path, i.e. 'default.customers' or ['default', 'customers'
    * {string} [orientation] - 'top', 'right', 'bottom', 'left'. Default 'right'
    * {Object} [offset] - Optional offset from the element
@@ -2495,8 +2495,8 @@
    *
    * Examples:
    *
-   * data-bind="sqlContextPopover: { sourceType: 'impala', sourceContext: { name: 'mySourceContext' }, path: 'default.customers' }"
-   * data-bind="sqlContextPopover: { sourceType: 'hive', sourceContext: { name: 'mySourceContext' }, path: 'default', orientation: 'bottom', offset: { top: 5 } }"
+   * data-bind="sqlContextPopover: { sourceType: 'impala', namespace: { id: 'myNamespace' }, path: 'default.customers' }"
+   * data-bind="sqlContextPopover: { sourceType: 'hive', namespace: { id: 'myNamespace' }, path: 'default', orientation: 'bottom', offset: { top: 5 } }"
    *
    * @type {{init: ko.bindingHandlers.sqlContextPopover.init}}
    */
@@ -2505,7 +2505,7 @@
       ko.bindingHandlers.click.init(element, function () {
         return function () {
           var options = valueAccessor();
-          DataCatalog.getEntry({ sourceType: options.sourceType, sourceContext: options.sourceContext, path: options.path }).done(function (entry) {
+          DataCatalog.getEntry({ sourceType: options.sourceType, namespace: options.namespace, path: options.path }).done(function (entry) {
             var $source = $(element);
             var offset = $source.offset();
             if (options.offset) {
@@ -4363,7 +4363,7 @@
       var deferred = $.Deferred();
       DataCatalog.getChildren({
         sourceType: self.snippet.type(),
-        sourceContext: self.snippet.sourceContext(),
+        namespace: self.snippet.namespace(),
         path: $.map(identifierChain, function (identifier) { return identifier.name }),
         silenceErrors: true,
         cachedOnly: true
@@ -4444,7 +4444,7 @@
             if (typeof nextTable.subQuery === 'undefined') {
               DataCatalog.getChildren({
                 sourceType: self.snippet.type(),
-                sourceContext: self.snippet.sourceContext(),
+                namespace: self.snippet.namespace(),
                 path: $.map(nextTable.identifierChain, function (identifier) { return identifier.name }),
                 cachedOnly: true,
                 silenceErrors: true
@@ -4628,7 +4628,7 @@
         lastKnownLocations = {
           id: self.editorId,
           type: self.snippet.type(),
-          sourceContext: self.snippet.sourceContext(),
+          namespace: self.snippet.namespace(),
           defaultDatabase: self.snippet.database(),
           locations: e.data.locations,
           editorChangeTime: e.data.editorChangeTime,
@@ -4732,7 +4732,7 @@
             id: self.snippet.id(),
             statementDetails: statementDetails,
             type: self.snippet.type(),
-            sourceContext: self.snippet.sourceContext(),
+            namespace: self.snippet.namespace(),
             defaultDatabase: self.snippet.database()
           });
         }
@@ -5145,7 +5145,7 @@
                         // Note, as cachedOnly is set to true it will call the successCallback right away (or not at all)
                         DataCatalog.getEntry({
                           sourceType: snippet.type(),
-                          sourceContext: snippet.sourceContext(),
+                          namespace: snippet.namespace(),
                           path: $.map(tableChain, function (identifier) { return identifier.name })
                         }).done(function (entry) {
                           entry.getSourceMeta({ cachedOnly: true, silenceErrors: true }).done(function (sourceMeta) {
@@ -5300,7 +5300,7 @@
                   huePubSub.publish('context.popover.show', {
                     data: token.parseLocation,
                     sourceType: snippet.type(),
-                    sourceContext: snippet.sourceContext(),
+                    namespace: snippet.namespace(),
                     defaultDatabase: snippet.database(),
                     pinEnabled: true,
                     source: source

+ 13 - 13
desktop/core/src/desktop/static/desktop/js/sqlAutocompleter2.js

@@ -148,7 +148,7 @@ var SqlAutocompleter2 = (function () {
 
       var paths = self.tableIdentifierChainsToPaths(parseResult.suggestJoins.tables, database);
       if (paths.length) {
-        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), paths: paths }).done(function (multiTableEntry) {
+        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), namespace: self.snippet.namespace(), paths: paths }).done(function (multiTableEntry) {
           multiTableEntry.getTopJoins({ silenceErrors: true }).done(function (topJoins) {
             if (topJoins.values) {
               topJoins.values.forEach(function (value) {
@@ -210,7 +210,7 @@ var SqlAutocompleter2 = (function () {
 
       var paths = self.tableIdentifierChainsToPaths(parseResult.suggestJoinConditions.tables, database);
       if (paths.length) {
-        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), paths: paths }).done(function (multiTableEntry) {
+        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), namespace: self.snippet.namespace(), paths: paths }).done(function (multiTableEntry) {
           multiTableEntry.getTopJoins({ silenceErrors: true }).done(function (topJoins) {
             if (topJoins.values) {
               topJoins.values.forEach(function (value) {
@@ -263,7 +263,7 @@ var SqlAutocompleter2 = (function () {
 
         var paths = self.tableIdentifierChainsToPaths(parseResult.suggestAggregateFunctions.tables, database);
         if (paths.length) {
-          DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), paths: paths }).done(function (multiTableEntry) {
+          DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), namespace: self.snippet.namespace(), paths: paths }).done(function (multiTableEntry) {
             multiTableEntry.getTopAggs({ silenceErrors: true }).done(function (topAggs) {
               if (topAggs.values.length > 0) {
 
@@ -397,7 +397,7 @@ var SqlAutocompleter2 = (function () {
 
       var paths = self.tableIdentifierChainsToPaths(parseResult.suggestFilters.tables, database);
       if (paths.length) {
-        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), paths: paths }).done(function (multiTableEntry) {
+        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), namespace: self.snippet.namespace(), paths: paths }).done(function (multiTableEntry) {
           multiTableEntry.getTopFilters({ silenceErrors: true }).done(function (topFilters) {
             if (topFilters.values) {
               topFilters.values.forEach(function (value) {
@@ -442,7 +442,7 @@ var SqlAutocompleter2 = (function () {
 
       var paths = self.tableIdentifierChainsToPaths(tables, database);
       if (paths.length) {
-        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), paths: paths }).done(function (multiTableEntry) {
+        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), namespace: self.snippet.namespace(), paths: paths }).done(function (multiTableEntry) {
           multiTableEntry.getTopColumns({ silenceErrors: true }).done(function (topColumns) {
             if (topColumns.values && parseResult.suggestGroupBys && typeof topColumns.values.groupbyColumns !== 'undefined') {
               var prefix = parseResult.suggestGroupBys.prefix ? (parseResult.lowerCase ? parseResult.suggestGroupBys.prefix.toLowerCase() : parseResult.suggestGroupBys.prefix) + ' ' : '';
@@ -485,7 +485,7 @@ var SqlAutocompleter2 = (function () {
 
       $.when(topColumnsDeferral, suggestColumnsDeferral).then(function (topColumns, suggestions) {
         if (topColumns.length > 0) {
-          DataCatalog.getChildren({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), path: [], silenceErrors: true }).done(function (dbEntries) {
+          DataCatalog.getChildren({ sourceType: self.snippet.type(), namespace: self.snippet.namespace(), path: [], silenceErrors: true }).done(function (dbEntries) {
             var databases = $.map(dbEntries, function (dbEntry) { return dbEntry.name });
             suggestions.forEach(function (suggestion) {
               var path = '';
@@ -535,7 +535,7 @@ var SqlAutocompleter2 = (function () {
       if (HAS_OPTIMIZER && typeof parseResult.suggestColumns.source !== 'undefined') {
         var paths = self.tableIdentifierChainsToPaths(parseResult.suggestColumns.tables, database);
         if (paths.length) {
-          DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), paths: paths }).done(function (multiTableEntry) {
+          DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), namespace: self.snippet.namespace(), paths: paths }).done(function (multiTableEntry) {
             multiTableEntry.getTopColumns({ silenceErrors: true }).done(function (topColumns) {
               var values = [];
               if (topColumns.values) {
@@ -584,7 +584,7 @@ var SqlAutocompleter2 = (function () {
         var topTablesDeferral = $.Deferred();
         deferrals.push(topTablesDeferral);
 
-        DataCatalog.getCatalog(self.snippet.type()).loadNavOptPopularityForTables({ paths: [[database]], sourceContext: self.snippet.sourceContext(), silenceErrors: true }).done(function (popularTables) {
+        DataCatalog.getCatalog(self.snippet.type()).loadNavOptPopularityForTables({ paths: [[database]], namespace: self.snippet.namespace(), silenceErrors: true }).done(function (popularTables) {
           var popularityIndex = {};
           popularTables.forEach(function (popularTable) {
             if (popularTable.navOptPopularity) {
@@ -613,7 +613,7 @@ var SqlAutocompleter2 = (function () {
         var checkDbDeferral = $.Deferred();
         DataCatalog.getChildren({
           sourceType: self.snippet.type(),
-          sourceContext: self.snippet.sourceContext(),
+          namespace: self.snippet.namespace(),
           path: [],
           silenceErrors: true
         }).done(function (dbEntries) {
@@ -803,7 +803,7 @@ var SqlAutocompleter2 = (function () {
         identifierChain = identifierChain.slice(1);
       }
 
-      DataCatalog.getEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), path: [database, table].concat(fetchedFields) }).done(function (entry) {
+      DataCatalog.getEntry({ sourceType: self.snippet.type(), namespace: self.snippet.namespace(), path: [database, table].concat(fetchedFields) }).done(function (entry) {
         entry.getSourceMeta({ silenceErrors: true }).done(function (sourceMeta) {
           if (self.snippet.type() === 'hive'
             && typeof sourceMeta.extended_columns !== 'undefined'
@@ -841,7 +841,7 @@ var SqlAutocompleter2 = (function () {
       if (identifierChain.length > 1) {
         DataCatalog.getChildren({
           sourceType: self.snippet.type(),
-          sourceContext: self.snippet.sourceContext(),
+          namespace: self.snippet.namespace(),
           path: [],
           silenceErrors: true
         }).done(function (dbEntries) {
@@ -878,7 +878,7 @@ var SqlAutocompleter2 = (function () {
         prefix += parseResult.lowerCase ? 'from ' : 'FROM ';
       }
 
-      DataCatalog.getChildren({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), path: [databaseName], silenceErrors: true }).done(function (tableEntries) {
+      DataCatalog.getChildren({ sourceType: self.snippet.type(), namespace: self.snippet.namespace(), path: [databaseName], silenceErrors: true }).done(function (tableEntries) {
         var tables = [];
         tableEntries.forEach(function (tableEntry) {
           if (parseResult.suggestTables.onlyTables && tableEntry.isTable() ||
@@ -1049,7 +1049,7 @@ var SqlAutocompleter2 = (function () {
     }
     DataCatalog.getChildren({
       sourceType: self.snippet.type(),
-      sourceContext: self.snippet.sourceContext(),
+      namespace: self.snippet.namespace(),
       path: [],
       silenceErrors: true
     }).done(function (dbEntries) {

+ 11 - 11
desktop/core/src/desktop/static/desktop/js/sqlAutocompleter3.js

@@ -326,7 +326,7 @@ var AutocompleteResults = (function () {
   AutocompleteResults.prototype.loadDatabases = function () {
     var self = this;
     var databasesDeferred = $.Deferred();
-    DataCatalog.getEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), path: [] }).done(function (entry) {
+    DataCatalog.getEntry({ sourceType: self.snippet.type(), namespace: self.snippet.namespace(), path: [] }).done(function (entry) {
       self.cancellablePromises.push(entry.getChildren({ silenceErrors: true, cancellable: true }).done(function (databases) {
         databasesDeferred.resolve(databases);
       }).fail(databasesDeferred.reject));
@@ -544,7 +544,7 @@ var AutocompleteResults = (function () {
 
         var database = suggestTables.identifierChain && suggestTables.identifierChain.length === 1 ? suggestTables.identifierChain[0].name : self.activeDatabase;
 
-        DataCatalog.getEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), path: [ database ]}).done(function (dbEntry) {
+        DataCatalog.getEntry({ sourceType: self.snippet.type(), namespace: self.snippet.namespace(), path: [ database ]}).done(function (dbEntry) {
           self.cancellablePromises.push(dbEntry.getChildren({ silenceErrors: true, cancellable: true }).done(function (tableEntries) {
             var tableSuggestions = [];
 
@@ -984,7 +984,7 @@ var AutocompleteResults = (function () {
 
       var paths = self.tableIdentifierChainsToPaths(suggestJoins.tables);
       if (paths.length) {
-        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), paths: paths }).done(function (multiTableEntry) {
+        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), namespace: self.snippet.namespace(), paths: paths }).done(function (multiTableEntry) {
         self.cancellablePromises.push(multiTableEntry.getTopJoins({ silenceErrors: true, cancellable: true  }).done(function (topJoins) {
           var joinSuggestions = [];
           var totalCount = 0;
@@ -1065,7 +1065,7 @@ var AutocompleteResults = (function () {
 
       var paths = self.tableIdentifierChainsToPaths(suggestJoinConditions.tables);
       if (paths.length) {
-        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), paths: paths }).done(function (multiTableEntry) {
+        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), namespace: self.snippet.namespace(), paths: paths }).done(function (multiTableEntry) {
           self.cancellablePromises.push(multiTableEntry.getTopJoins({ silenceErrors: true, cancellable: true }).done(function (topJoins) {
           var joinConditionSuggestions = [];
           var totalCount = 0;
@@ -1121,7 +1121,7 @@ var AutocompleteResults = (function () {
 
       var paths = self.tableIdentifierChainsToPaths(suggestAggregateFunctions.tables);
       if (paths.length) {
-        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), paths: paths }).done(function (multiTableEntry) {
+        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), namespace: self.snippet.namespace(), paths: paths }).done(function (multiTableEntry) {
           self.cancellablePromises.push(multiTableEntry.getTopAggs({ silenceErrors: true, cancellable: true }).done(function (topAggs) {
             var aggregateFunctionsSuggestions = [];
             if (topAggs.values && topAggs.values.length > 0) {
@@ -1227,7 +1227,7 @@ var AutocompleteResults = (function () {
     });
 
     self.cancellablePromises.push(DataCatalog.getCatalog(self.snippet.type())
-      .loadNavOptPopularityForTables({ sourceContext: self.snippet.sourceContext(), paths: paths, silenceErrors: true, cancellable: true }).done(function (entries) {
+      .loadNavOptPopularityForTables({ namespace: self.snippet.namespace(), paths: paths, silenceErrors: true, cancellable: true }).done(function (entries) {
         var totalColumnCount = 0;
         var matchedEntries = [];
         var prefix = suggestSpec.prefix ? (self.parseResult.lowerCase ? suggestSpec.prefix.toLowerCase() : suggestSpec.prefix) + ' ' : '';
@@ -1303,7 +1303,7 @@ var AutocompleteResults = (function () {
 
       var paths = self.tableIdentifierChainsToPaths(suggestFilters.tables);
       if (paths.length) {
-        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), paths: paths }).done(function (multiTableEntry) {
+        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), namespace: self.snippet.namespace(), paths: paths }).done(function (multiTableEntry) {
           self.cancellablePromises.push(multiTableEntry.getTopFilters({ silenceErrors: true, cancellable: true }).done(function (topFilters) {
             var filterSuggestions = [];
             var totalCount = 0;
@@ -1364,7 +1364,7 @@ var AutocompleteResults = (function () {
         && self.parseResult.suggestTables.identifierChain.length === 1
         && self.parseResult.suggestTables.identifierChain[0].name ? self.parseResult.suggestTables.identifierChain[0].name : self.activeDatabase;
 
-      DataCatalog.getEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), path: [ db ]}).done(function (entry) {
+      DataCatalog.getEntry({ sourceType: self.snippet.type(), namespace: self.snippet.namespace(), path: [ db ]}).done(function (entry) {
         self.cancellablePromises.push(entry.loadNavOptPopularityForChildren({ silenceErrors: true, cancellable: true }).done(function (childEntries) {
           var totalPopularity = 0;
           var popularityIndex = {};
@@ -1424,7 +1424,7 @@ var AutocompleteResults = (function () {
       });
 
       self.cancellablePromises.push(DataCatalog.getCatalog(self.snippet.type()).loadNavOptPopularityForTables({
-        sourceContext: self.snippet.sourceContext(),
+        namespace: self.snippet.namespace(),
         paths: paths,
         silenceErrors: true,
         cancellable: true
@@ -1586,7 +1586,7 @@ var AutocompleteResults = (function () {
         }
       }
 
-      DataCatalog.getEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), path: fetchedPath }).done(function (catalogEntry) {
+      DataCatalog.getEntry({ sourceType: self.snippet.type(), namespace: self.snippet.namespace(), path: fetchedPath }).done(function (catalogEntry) {
         self.cancellablePromises.push(catalogEntry.getSourceMeta({ silenceErrors: true, cancellable: true }).done(function (sourceMeta) {
           if (self.snippet.type() === 'hive'
               && typeof sourceMeta.extended_columns !== 'undefined'
@@ -1616,7 +1616,7 @@ var AutocompleteResults = (function () {
     // For Hive it could be either:
     // SELECT col.struct FROM db.tbl -or- SELECT col.struct FROM tbl
     if (path.length > 1 && (self.snippet.type() === 'impala' || self.snippet.type() === 'hive')) {
-      DataCatalog.getEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), path: [] }).done(function (catalogEntry) {
+      DataCatalog.getEntry({ sourceType: self.snippet.type(), namespace: self.snippet.namespace(), path: [] }).done(function (catalogEntry) {
         self.cancellablePromises.push(catalogEntry.getChildren({ silenceErrors: true, cancellable: true }).done(function (databaseEntries) {
           var firstIsDb = databaseEntries.some(function (dbEntry) {
             return hueUtils.equalIgnoreCase(dbEntry.name, path[0]);

+ 3 - 3
desktop/core/src/desktop/static/desktop/js/sqlUtils.js

@@ -120,7 +120,7 @@ var SqlUtils = (function () {
    *
    * @param {Object} options
    * @param {String} options.sourceType
-   * @param {SourceContext} options.sourceContext
+   * @param {ContextNamespace} options.namespace
    * @param {Object[]} [options.identifierChain]
    * @param {Object[]} [options.tables]
    * @param {Object} [options.cancellable]
@@ -190,7 +190,7 @@ var SqlUtils = (function () {
 
       cancellablePromises.push(DataCatalog.getChildren({
         sourceType: options.sourceType,
-        sourceContext: options.sourceContext,
+        namespace: options.namespace,
         path: SqlUtils.identifierChainToPath(nextTable.identifierChain),
         cachedOnly: options && options.cachedOnly,
         cancellable: options && options.cancellable,
@@ -219,7 +219,7 @@ var SqlUtils = (function () {
     } else {
       DataCatalog.getEntry({
         sourceType: options.sourceType,
-        sourceContext: options.sourceContext,
+        namespace: options.namespace,
         path: [],
         cachedOnly: options && options.cachedOnly,
         cancellable: options && options.cancellable,

+ 2 - 2
desktop/core/src/desktop/static/desktop/spec/sqlAutocompleter3Spec.js

@@ -26,8 +26,8 @@
           database: function () {
             return 'default';
           },
-          sourceContext: function () {
-            return 'defaultNamespace'
+          namespace: function () {
+            return { id: 'defaultNamespace' }
           }
         },
         editor: function () {

+ 1 - 1
desktop/core/src/desktop/templates/ace_sql_location_worker.mako

@@ -101,7 +101,7 @@ importScripts(scriptPrefix + '${ static('desktop/js/sqlFunctions.js') }');
         postMessage({
           id: msg.data.id,
           sourceType: msg.data.type,
-          sourceContext: msg.data.sourceContext,
+          namespace: msg.data.namespace,
           editorChangeTime: msg.data.statementDetails.editorChangeTime,
           locations: locations,
           activeStatementLocations: activeStatementLocations,

+ 169 - 151
desktop/core/src/desktop/templates/assist.mako

@@ -299,24 +299,31 @@ from desktop.views import _ko
 
   <script type="text/html" id="assist-db-breadcrumb">
     <div class="assist-flex-header assist-breadcrumb">
-      <!-- ko if: selectedSource() && !selectedSource().selectedDatabase() && sources().length === 1 -->
-      <i class="fa fa-server assist-breadcrumb-text"></i>
-      <span class="assist-breadcrumb-text" data-bind="text: breadcrumb, attr: {'title': breadcrumb() + ' (' + selectedSource().sourceType + ')' }"></span>
+      <!-- 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 fa-database assist-breadcrumb-text"></i>
+        <span class="assist-breadcrumb-text" data-bind="text: breadcrumb, attr: {'title': breadcrumb() + ' (' + selectedSource().sourceType + ' ' + selectedSource().selectedNamespace().name + ')' }"></span>
+      </a>
       <!-- /ko -->
-      <!-- ko if: selectedSource() && !selectedSource().selectedDatabase() && sources().length > 1 -->
+      <!-- ko ifnot: selectedSource().selectedNamespace().selectedDatabase() -->
       <a data-bind="click: back">
         <i class="fa fa-chevron-left assist-breadcrumb-back"></i>
-        <i class="fa fa-server assist-breadcrumb-text"></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 if: selectedSource() && selectedSource().selectedDatabase() -->
-      <a data-bind="click: back, appAwareTemplateContextMenu: { template: 'sql-context-items', viewModel: selectedSource().selectedDatabase() }">
-        <i class="fa fa-chevron-left assist-breadcrumb-back" ></i>
-        <i class="fa fa-database assist-breadcrumb-text"></i>
-        <span class="assist-breadcrumb-text" data-bind="text: breadcrumb, attr: {'title': breadcrumb() + ' (' + selectedSource().sourceType + ')' }"></span>
+      <!-- /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>
 
@@ -324,10 +331,13 @@ from desktop.views import _ko
     <!-- ko template: { if: breadcrumb() !== null, name: 'assist-db-breadcrumb' } --><!-- /ko -->
     <!-- ko template: { ifnot: selectedSource, name: 'assist-sources-template' } --><!-- /ko -->
     <!-- ko with: selectedSource -->
-    <!-- ko template: { ifnot: selectedDatabase, name: 'assist-databases-template' }--><!-- /ko -->
-    <!-- ko with: selectedDatabase -->
-    <!-- ko template: { name: 'assist-tables-template' } --><!-- /ko -->
-    <!-- /ko -->
+      <!-- ko 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>
 
@@ -747,6 +757,21 @@ from desktop.views import _ko
     </div>
   </script>
 
+  <script type="text/html" id="assist-namespaces-template">
+    <div class="assist-flex-header">
+      <div class="assist-inner-header">
+        ${_('Namespaces')}
+      </div>
+    </div>
+    <div class="assist-flex-fill">
+      <ul class="assist-tables" data-bind="foreach: namespaces">
+        <li class="assist-table">
+          <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>
+        </li>
+      </ul>
+    </div>
+  </script>
+
   <script type="text/html" id="ask-for-invalidate-title">
     &nbsp;<a class="pull-right pointer close-popover inactive-action">&times;</a>
   </script>
@@ -1105,155 +1130,147 @@ from desktop.views import _ko
           }
         }
 
-        self.activeSourceContext = ko.observable();
         self.sources = ko.observableArray();
         self.sourceIndex = {};
         self.selectedSource = ko.observable(null);
 
-        self.setDatabaseWhenLoaded = function (databaseName) {
-          if (self.selectedSource().loaded()) {
-            self.selectedSource().setDatabase(databaseName);
-          } else {
-            var subscription = self.selectedSource().loaded.subscribe(function (newValue) {
-              if (newValue) {
-                self.selectedSource().setDatabase(databaseName);
-                subscription.dispose();
-              }
-            });
-            if (!self.selectedSource().loaded() && !self.selectedSource().loading()) {
-              self.selectedSource().initDatabases();
+        self.setDatabaseWhenLoaded = function (namespace, databaseName) {
+          self.selectedSource().whenLoaded(function () {
+            if (self.selectedSource().selectedNamespace() && self.selectedSource().selectedNamespace().namespace.id !== namespace.id) {
+              self.selectedSource().namespaces().some(function (otherNamespace) {
+                if (otherNamespace.namespace.id === namespace.id) {
+                  self.selectedSource().selectedNamespace(otherNamespace);
+                  return true;
+                }
+              })
             }
-          }
-        };
 
-        // TODO: Create AssistDbContext objects and replace AssistDbSource below
-        ContextCatalog.getSourceContexts({ app: ContextCatalog.BROWSER_APP, sourceType: 'hive' }).done(function (sourceContexts) {
-          // TODO: Context selection
-          self.activeSourceContext(sourceContexts[0]);
-
-          $.each(options.sourceTypes, function (idx, sourceType) {
-            self.sourceIndex[sourceType.type] = new AssistDbSource({
-              apiHelper: self.apiHelper,
-              i18n: self.i18n,
-              type: sourceType.type,
-              activeSourceContext: self.activeSourceContext,
-              name: sourceType.name,
-              navigationSettings: options.navigationSettings
-            });
-            self.sources.push(self.sourceIndex[sourceType.type]);
+            if (self.selectedSource().selectedNamespace()) {
+              self.selectedSource().selectedNamespace().whenLoaded(function () {
+                self.selectedSource().selectedNamespace().setDatabase(databaseName);
+              })
+            }
           });
+        };
 
-          huePubSub.subscribe('assist.collections.refresh', function() {
-            if (self.activeSourceContext()) {
-              DataCatalog.getEntry({ sourceType: 'solr', sourceContext: self.activeSourceContext(), path: [] }).done(function (entry) {
-                entry.clearCache({ cascade: true });
-              });
-            }
+        options.sourceTypes.forEach(function (sourceType) {
+          self.sourceIndex[sourceType.type] = new AssistDbSource({
+            apiHelper: self.apiHelper,
+            i18n: self.i18n,
+            type: sourceType.type,
+            name: sourceType.name,
+            navigationSettings: options.navigationSettings
           });
+          self.sources.push(self.sourceIndex[sourceType.type]);
+        });
 
-          huePubSub.subscribe('assist.db.highlight', function (location) {
-            huePubSub.publish('left.assist.show');
-            if (location.sourceType === 'solr') {
-              huePubSub.publish('assist.show.solr');
-            }
-            else {
-              huePubSub.publish('assist.show.sql');
-            }
-            huePubSub.publish('context.popover.hide');
-            window.setTimeout(function () {
-              var foundSource;
-              $.each(self.sources(), function (idx, source) {
-                if (source.sourceType === location.sourceType) {
-                  foundSource = source;
-                  return false;
-                }
-              });
-              if (foundSource) {
-                var whenLoaded = function () {
-                  if (self.selectedSource() !== foundSource) {
-                    self.selectedSource(foundSource);
-                  }
-                  foundSource.highlightInside(location.path);
-                };
-                if (foundSource.hasEntries()) {
-                  whenLoaded();
-                } else {
-                  foundSource.initDatabases(whenLoaded);
-                }
-              }
-            }, 0);
+        huePubSub.subscribe('assist.collections.refresh', function() {
+          DataCatalog.getEntry({ sourceType: 'solr', namespace: self.sourceIndex['solr'].activeNamespace(), path: [] }).done(function (entry) {
+            entry.clearCache({ cascade: true });
           });
+        });
 
-          if (!options.isSolr) {
-            huePubSub.subscribe('assist.set.database', function (databaseDef) {
-              if (!databaseDef.source || !self.sourceIndex[databaseDef.source]) {
-                return;
+        huePubSub.subscribe('assist.db.highlight', function (location) {
+          huePubSub.publish('left.assist.show');
+          if (location.sourceType === 'solr') {
+            huePubSub.publish('assist.show.solr');
+          }
+          else {
+            huePubSub.publish('assist.show.sql');
+          }
+          huePubSub.publish('context.popover.hide');
+          window.setTimeout(function () {
+            var foundSource;
+            $.each(self.sources(), function (idx, source) {
+              if (source.sourceType === location.sourceType) {
+                foundSource = source;
+                return false;
               }
-              self.selectedSource(self.sourceIndex[databaseDef.source]);
-              self.setDatabaseWhenLoaded(databaseDef.name);
             });
-
-            huePubSub.subscribe('assist.get.database', function (source) {
-              if (self.sourceIndex[source] && self.sourceIndex[source].selectedDatabase()) {
-                huePubSub.publish('assist.database.set', {
-                  source: source,
-                  name: self.sourceIndex[source].selectedDatabase().databaseName
-                });
+            if (foundSource) {
+              var whenLoaded = function () {
+                if (self.selectedSource() !== foundSource) {
+                  self.selectedSource(foundSource);
+                }
+                foundSource.highlightInside(location.path);
+              };
+              if (foundSource.hasEntries()) {
+                whenLoaded();
               } else {
-                huePubSub.publish('assist.database.set', {
-                  source: source,
-                  name: 'default'
-                });
+                foundSource.initDatabases(whenLoaded);
               }
-            });
+            }
+          }, 0);
+        });
 
-            huePubSub.subscribe('assist.get.database.callback',  function (options) {
-              if (self.sourceIndex[options.source] && self.sourceIndex[options.source].selectedDatabase()) {
-                options.callback({
-                  source: options.source,
-                  name: self.sourceIndex[options.source].selectedDatabase().databaseName
-                });
-              } else {
-                options.callback({
-                  source: options.source,
-                  name: 'default'
-                });
+        if (!options.isSolr) {
+          huePubSub.subscribe('assist.set.database', function (databaseDef) {
+            if (!databaseDef.source || !self.sourceIndex[databaseDef.source]) {
+              return;
+            }
+            self.selectedSource(self.sourceIndex[databaseDef.source]);
+            self.setDatabaseWhenLoaded(databaseDef.namespace, databaseDef.name);
+          });
+
+          var getPubDb  = function (source) {
+            var result = {
+              source: source,
+              namespace: { id: 'default' },
+              name: 'default'
+            };
+
+            if (self.sourceIndex[source] && self.sourceIndex[source].selectedNamespace()) {
+              result.namespace = self.sourceIndex[source].selectedNamespace().namespace;
+              if (self.sourceIndex[source].selectedNamespace().selectedDatabase()) {
+                result.name = self.sourceIndex[source].selectedNamespace().selectedDatabase().databaseName;
               }
-            });
+            }
+            return result;
+          };
 
-            huePubSub.subscribe('assist.get.source', function () {
-              huePubSub.publish('assist.source.set', self.selectedSource() ? self.selectedSource().sourceType : undefined);
-            });
+          huePubSub.subscribe('assist.get.database', function (source) {
+            huePubSub.publish('assist.database.set', getPubDb(source));
+          });
 
-            huePubSub.subscribe('assist.set.source', function (source) {
-              if (self.sourceIndex[source]) {
-                self.selectedSource(self.sourceIndex[source]);
-              }
-            });
+          huePubSub.subscribe('assist.get.database.callback',  function (options) {
+            options.callback(getPubDb(options.source));
+          });
 
-            huePubSub.publish('assist.db.panel.ready');
+          huePubSub.subscribe('assist.get.source', function () {
+            huePubSub.publish('assist.source.set', self.selectedSource() ? self.selectedSource().sourceType : undefined);
+          });
 
-            huePubSub.subscribe('assist.is.db.panel.ready', function () {
-              huePubSub.publish('assist.db.panel.ready');
-            });
+          huePubSub.subscribe('assist.set.source', function (source) {
+            if (self.sourceIndex[source]) {
+              self.selectedSource(self.sourceIndex[source]);
+            }
+          });
 
-            self.selectedSource.subscribe(function (newSource) {
-              if (newSource) {
-                if (newSource.databases().length === 0) {
-                  newSource.initDatabases();
-                }
-                self.apiHelper.setInTotalStorage('assist', 'lastSelectedSource', newSource.sourceType);
-              } else {
-                self.apiHelper.setInTotalStorage('assist', 'lastSelectedSource');
+          huePubSub.publish('assist.db.panel.ready');
+
+          huePubSub.subscribe('assist.is.db.panel.ready', function () {
+            huePubSub.publish('assist.db.panel.ready');
+          });
+
+          self.selectedSource.subscribe(function (newSource) {
+            if (newSource) {
+              if (newSource.namespaces().length === 0) {
+                newSource.loadNamespaces();
               }
-            });
-          }
-        });
+              self.apiHelper.setInTotalStorage('assist', 'lastSelectedSource', newSource.sourceType);
+            } else {
+              self.apiHelper.setInTotalStorage('assist', 'lastSelectedSource');
+            }
+          });
+        }
 
         self.breadcrumb = ko.computed(function () {
           if (self.selectedSource()) {
-            if (self.selectedSource().selectedDatabase()) {
-              return self.selectedSource().selectedDatabase().catalogEntry.name;
+            if (self.selectedSource().selectedNamespace()) {
+              if (self.selectedSource().selectedNamespace().selectedDatabase()) {
+                return self.selectedSource().selectedNamespace().selectedDatabase().catalogEntry.name
+              }
+              return self.selectedSource().selectedNamespace().name
             }
             return self.selectedSource().name;
           }
@@ -1263,10 +1280,16 @@ from desktop.views import _ko
 
       AssistDbPanel.prototype.back = function () {
         var self = this;
-        if (self.selectedSource() && self.selectedSource().selectedDatabase()) {
-          self.selectedSource().selectedDatabase(null)
-        } else if (self.selectedSource()) {
-          self.selectedSource(null);
+        if (self.selectedSource()) {
+          if (self.selectedSource() && self.selectedSource().selectedNamespace()) {
+            if (self.selectedSource().selectedNamespace().selectedDatabase()) {
+              self.selectedSource().selectedNamespace().selectedDatabase(null);
+            } else {
+              self.selectedSource().selectedNamespace(null)
+            }
+          } else {
+            self.selectedSource(null);
+          }
         }
       };
 
@@ -1277,16 +1300,13 @@ from desktop.views import _ko
         }
         if (self.options.isSolr) {
           self.selectedSource(self.sourceIndex['solr']);
-          self.setDatabaseWhenLoaded();
         } else {
           var storageSourceType = self.apiHelper.getFromTotalStorage('assist', 'lastSelectedSource');
           if (!self.selectedSource()) {
             if (self.options.activeSourceType) {
               self.selectedSource(self.sourceIndex[self.options.activeSourceType]);
-              self.setDatabaseWhenLoaded();
             } else if (storageSourceType && self.sourceIndex[storageSourceType]) {
               self.selectedSource(self.sourceIndex[storageSourceType]);
-              self.setDatabaseWhenLoaded();
             }
           }
         }
@@ -2533,7 +2553,7 @@ from desktop.views import _ko
             sources[activeLocations.type] = {
               assistDbSource: new AssistDbSource({
                 i18n: i18n,
-                activeSourceContext: ko.observable(activeLocations.sourceContext),
+                initialNamespace: activeLocations.namespace,
                 type: activeLocations.type,
                 name: activeLocations.type,
                 navigationSettings: navigationSettings
@@ -2585,7 +2605,7 @@ from desktop.views import _ko
                   } else {
                     DataCatalog.getEntry({
                       sourceType: activeLocations.type,
-                      sourceContext: activeLocations.sourceContext,
+                      namespace: activeLocations.namespace,
                       path: [ database ],
                       definition: { type: 'database' }
                     }).done(function (catalogEntry) {
@@ -2641,7 +2661,7 @@ from desktop.views import _ko
                                   self.reloading(false)
                                 })
                               });
-                              DataCatalog.getEntry({ sourceType: activeLocations.type, sourceContext: activeLocations.sourceContext, path: [] }).done(function (sourceEntry) {
+                              DataCatalog.getEntry({ sourceType: activeLocations.type, namespace: activeLocations.namespace, path: [] }).done(function (sourceEntry) {
                                 sourceEntry.getChildren().done(function (dbEntries) {
                                   var clearPromise;
                                    // Clear the database first if it exists without cascade
@@ -2965,7 +2985,6 @@ from desktop.views import _ko
 
         self.disposals = [];
         self.isSolr = ko.observable(true);
-        self.activeSourceContext = ko.observable();
 
         self.filter = {
           querySpec: ko.observable({
@@ -2997,11 +3016,10 @@ from desktop.views import _ko
           if (!collectionName) {
             return;
           }
-          self.activeSourceContext(collection.activeSourceContext);
 
           var assistDbSource = new AssistDbSource({
             i18n : i18n,
-            activeSourceContext: collection.activeSourceContext,
+            initialNamespace: collection.activeNamespace,
             type: collection.engine(),
             name: collection.engine(),
             navigationSettings: navigationSettings
@@ -3013,14 +3031,14 @@ from desktop.views import _ko
 
           DataCatalog.getEntry({
             sourceType: sourceType,
-            sourceContext: collection.activeSourceContext,
+            namespace: collection.activeNamespace,
             path: [ fakeParentName ],
             definition: { type: 'database' }
           }).done(function (fakeDbCatalogEntry) {
             var assistFakeDb = new AssistDbEntry(fakeDbCatalogEntry, null, assistDbSource, self.filter, i18n, navigationSettings);
             DataCatalog.getEntry({
               sourceType: sourceType,
-              sourceContext: collection.activeSourceContext,
+              namespace: collection.activeNamespace,
               path: [fakeParentName, collectionName.indexOf('.') > -1 ? collectionName.split('.')[1] : collectionName],
               definition: { type: 'table' }
             }).done(function (collectionCatalogEntry) {

+ 1 - 1
desktop/core/src/desktop/templates/common_dashboard.mako

@@ -232,7 +232,7 @@
                 <select data-bind="selectize: fieldOperations, optionsText: 'label', optionsValue: 'value', value: fieldOperation" class="input-small"></select>
                 <select data-bind="selectize: $root.collection.template.filteredModalFields().sort(function (l, r) { return l.name() > r.name() ? 1 : -1 }), value: fieldName, optionsValue: 'name', optionsText: 'name', optionsCaption: '${ _ko('Field...') }'" class="hit-options input-small" style="margin-bottom: 0"></select>
                 <!-- ko if: fieldName -->
-                <a class="inactive-action context-popover-icon" href="javascript:void(0);" data-bind="sqlContextPopover: { sourceType: 'solr', sourceContext: $root.collection.activeSourceContext(), path: 'default.' + $root.collection.name() + '.' + fieldName()  }">
+                <a class="inactive-action context-popover-icon" href="javascript:void(0);" data-bind="sqlContextPopover: { sourceType: 'solr', namespace: $root.collection.activeNamespace(), path: 'default.' + $root.collection.name() + '.' + fieldName()  }">
                   <i class="fa fa-fw fa-info" title="${_('Show Details')}"/>
                 </a>
                 <!-- /ko -->

+ 1 - 1
desktop/core/src/desktop/templates/hue.mako

@@ -122,7 +122,7 @@
   <script src="${ static('desktop/js/hue.errorcatcher.js') }"></script>
   % endif
   <script src="${ static('desktop/js/hue4.utils.js') }"></script>
-  <script src="${ static('desktop/js/contextHelper.js') }"></script>
+  <script src="${ static('desktop/js/contextCatalog.js') }"></script>
 </head>
 
 <body>

+ 11 - 11
desktop/core/src/desktop/templates/ko_components/ko_context_popover.mako

@@ -534,7 +534,7 @@ from metadata.conf import has_navigator
                 path: catalogEntry.path.slice(0, i + 1),
                 catalogEntry: self.catalogEntry,
                 makeActive: function () {
-                  self.catalogEntry().dataCatalog.getEntry({ sourceContext: self.catalogEntry().sourceContext, path: this.path }).done(self.catalogEntry);
+                  self.catalogEntry().dataCatalog.getEntry({ namespace: self.catalogEntry().namespace, path: this.path }).done(self.catalogEntry);
                 }
               })
             }
@@ -638,7 +638,7 @@ from metadata.conf import has_navigator
         huePubSub.publish('global.search.close');
       };
 
-      function AsteriskData(data, sourceType, sourceContext, defaultDatabase) {
+      function AsteriskData(data, sourceType, namespace, defaultDatabase) {
         var self = this;
         self.loading = ko.observable(true);
         self.hasErrors = ko.observable(false);
@@ -700,7 +700,7 @@ from metadata.conf import has_navigator
             }
             DataCatalog.getEntry({
               sourceType: sourceType,
-              sourceContext: sourceContext,
+              namespace: namespace,
               path: path
             }).done(function (entry) {
               entry.getSourceMeta({ silenceErrors: true }).done(function (sourceMeta) {
@@ -739,9 +739,9 @@ from metadata.conf import has_navigator
         });
       }
 
-      function AsteriskContextTabs(data, sourceType, sourceContext, defaultDatabase) {
+      function AsteriskContextTabs(data, sourceType, namespace, defaultDatabase) {
         var self = this;
-        self.data = new AsteriskData(data, sourceType, sourceContext, defaultDatabase);
+        self.data = new AsteriskData(data, sourceType, namespace, defaultDatabase);
 
         self.tabs = [
           { id: 'details', label: '${ _("Details") }', template: 'context-popover-asterisk-details', templateData: self.data }
@@ -1203,7 +1203,7 @@ from metadata.conf import has_navigator
         self.topAdjust = ko.observable(0);
         self.data = params.data;
         self.sourceType = params.sourceType;
-        self.sourceContext = params.sourceContext;
+        self.namespace = params.namespace;
         self.defaultDatabase = params.defaultDatabase;
         self.close = hidePopover;
         var orientation = params.orientation || 'bottom';
@@ -1289,7 +1289,7 @@ from metadata.conf import has_navigator
         if (self.isCatalogEntry && params.data.catalogEntry.getSourceType() === 'solr' && params.data.catalogEntry.isField()) {
           self.isCollection = true;
           self.isCatalogEntry = false;
-          self.sourceContext = params.data.catalogEntry.sourceContext;
+          self.namespace = params.data.catalogEntry.namespace;
         }
 
         self.showInAssistEnabled = (typeof params.showInAssistEnabled !== 'undefined' ? params.showInAssistEnabled : true)
@@ -1313,7 +1313,7 @@ from metadata.conf import has_navigator
           self.titleTemplate = 'context-storage-entry-title';
           self.contentsTemplate = 'context-storage-entry-contents';
         } else if (self.isAsterisk) {
-          self.contents = new AsteriskContextTabs(self.data, self.sourceType, self.sourceContext, self.defaultDatabase);
+          self.contents = new AsteriskContextTabs(self.data, self.sourceType, self.namespace, self.defaultDatabase);
           self.title = '*';
           self.iconClass = 'fa-table';
         } else if (self.isDocument) {
@@ -1469,9 +1469,9 @@ from metadata.conf import has_navigator
         }
 
         if (self.isCatalogEntry) {
-          ContextCatalog.getSourceContexts({ app: ContextCatalog.BROWSER_APP, sourceType: sourceType }).done(function (sourceContexts) {
-            // TODO: Context selection for global search results?
-            DataCatalog.getEntry({ sourceType: sourceType, sourceContext: sourceContexts[0], path: path, definition: { type: params.data.type.toLowerCase() }}).done(function (catalogEntry) {
+          ContextCatalog.getNamespaces({ sourceType: sourceType }).done(function (namespaces) {
+            // TODO: Namespace selection for global search results?
+            DataCatalog.getEntry({ sourceType: sourceType, namespace: namespaces[0], path: path, definition: { type: params.data.type.toLowerCase() }}).done(function (catalogEntry) {
               catalogEntry.navigatorMeta = params.data;
               catalogEntry.navigatorMetaPromise = $.Deferred().resolve(catalogEntry.navigatorMeta);
               catalogEntry.saveLater();

+ 4 - 4
desktop/core/src/desktop/templates/ko_components/ko_drop_down.mako

@@ -29,7 +29,7 @@ from desktop.views import _ko
     <a class="inactive-action hue-drop-down-active" href="javascript:void(0)" data-bind="toggle: dropDownVisible, css: { 'blue': dropDownVisible }">
       <!-- ko if: icon --><i class="fa" data-bind="css: icon"></i><!-- /ko -->
       <!-- ko if: !noLabel && value -->
-      <span data-bind="text: typeof value()[labelAttribute] !== 'undefined' ? value()[labelAttribute] : value(), visible: ! dropDownVisible() || !searchable, attr: { 'title': linkTitle }" ></span>
+      <span data-bind="text: value() && typeof value()[labelAttribute] !== 'undefined' ? value()[labelAttribute] : value(), visible: ! dropDownVisible() || !searchable, attr: { 'title': linkTitle }" ></span>
       <!-- /ko -->
       <i class="fa fa-caret-down"></i>
     </a>
@@ -46,7 +46,7 @@ from desktop.views import _ko
           <li class="divider"></li>
           <!-- /ko -->
           <!-- ko if: typeof $data.divider === 'undefined' || !$data.divider -->
-          <li><a href="javascript:void(0)" data-bind="text: typeof $data[$parent.labelAttribute] !== 'undefined' ? $data[$parent.labelAttribute] : $data, click: function () { $parent.value($data); }"></a></li>
+          <li><a href="javascript:void(0)" data-bind="text: $data && typeof $data[$parent.labelAttribute] !== 'undefined' ? $data[$parent.labelAttribute] : $data, click: function () { $parent.value($data); }"></a></li>
           <!-- /ko -->
         </ul>
         <!-- /ko -->
@@ -56,7 +56,7 @@ from desktop.views import _ko
           <li class="divider"></li>
           <!-- /ko -->
           <!-- ko if: typeof $data.divider === 'undefined' || !$data.divider -->
-          <li><a href="javascript:void(0)" data-bind="text: typeof $data[$parent.labelAttribute] !== 'undefined' ? $data[$parent.labelAttribute] : $data, click: function () { $parent.value($data); }"></a></li>
+          <li><a href="javascript:void(0)" data-bind="text: $data && typeof $data[$parent.labelAttribute] !== 'undefined' ? $data[$parent.labelAttribute] : $data, click: function () { $parent.value($data); }"></a></li>
           <!-- /ko -->
         </ul>
         <!-- /ko -->
@@ -168,7 +168,7 @@ from desktop.views import _ko
         self.fixedPosition = !!params.fixedPosition;
 
         self.inputPlaceHolder = ko.pureComputed(function () {
-          return typeof self.value() === 'object' ? self.value()[self.labelAttribute] : self.value();
+          return self.value() && typeof self.value() === 'object' ? self.value()[self.labelAttribute] : self.value();
         });
 
         var closeOnOutsideClick = function (e) {

+ 1 - 1
desktop/core/src/desktop/templates/ko_components/ko_history_panel.mako

@@ -236,7 +236,7 @@ from desktop.views import _ko
                   }
 
                   if (notebook.onSuccessUrl() === 'assist.db.refresh') {
-                    DataCatalog.getEntry({ sourceType: snippet.type(), sourceContext: snippet.sourceContext(), path: [] }).done(function (entry) {
+                    DataCatalog.getEntry({ sourceType: snippet.type(), namespace: snippet.namespace(), path: [] }).done(function (entry) {
                       entry.clearCache({ invalidate: 'cache', cascade: true, silenceErrors: true });
                     });
                   } else if (notebook.onSuccessUrl()) {

+ 4 - 4
desktop/libs/dashboard/src/dashboard/static/dashboard/js/search.ko.js

@@ -556,11 +556,11 @@ var Collection = function (vm, collection) {
   self.label = ko.mapping.fromJS(collection.label);
   self.description = ko.observable(typeof collection.description != "undefined" && collection.description != null ? collection.description : "");
   self.suggest = ko.mapping.fromJS(collection.suggest);
-  self.activeSourceContext = ko.observable();
+  self.activeNamespace = ko.observable();
 
-  ContextCatalog.getSourceContexts({ app: ContextCatalog.BROWSER_APP, sourceType: collection.engine || 'solr' }).done(function (sourceContexts) {
-    // TODO: Context selection
-    self.activeSourceContext(sourceContexts[0]);
+  ContextCatalog.getNamespaces({ sourceType: collection.engine || 'solr' }).done(function (namespaces) {
+    // TODO: Namespace selection
+    self.activeNamespace(namespaces[0]);
   });
 
   self.engine = ko.observable(typeof collection.engine != "undefined" && collection.engine != null ? collection.engine : "solr");

+ 2 - 2
desktop/libs/dashboard/src/dashboard/templates/common_search.mako

@@ -2248,7 +2248,7 @@ ${ dashboard.layout_skeleton(suffix='search') }
       <!-- ko if: $data.function() != 'formula' -->
       <select data-bind="selectize: facetFieldsNames, value: $parent.field, optionsValue: 'name', optionsText: 'name', optionsCaption: '${ _ko('Field...') }'" class="hit-options input-small" style="margin-bottom: 0"></select>
         <!-- ko if: $parent.field -->
-        <a class="inactive-action context-popover-icon" href="javascript:void(0);" data-bind="sqlContextPopover: { sourceType: 'solr', sourceContext: $root.collection.activeSourceContext(), path: 'default.' + $root.collection.name() + '.' + $parent.field()  }">
+        <a class="inactive-action context-popover-icon" href="javascript:void(0);" data-bind="sqlContextPopover: { sourceType: 'solr', namespace: $root.collection.activeNamespace(), path: 'default.' + $root.collection.name() + '.' + $parent.field()  }">
           <i class="fa fa-fw fa-info" title="${_('Show Details')}"/>
         </a>
         <!-- /ko -->
@@ -2715,7 +2715,7 @@ ${ dashboard.layout_skeleton(suffix='search') }
           class="unstyled inline fields-chooser" style="height: 100px; overflow-y: auto">
         <li data-bind="visibleOnHover: { 'selector': '.entity-field-picker' }">
             <span class="badge badge-info" data-bind="text: name(), attr: {'title': type()}, click: addFacetDemiModalFieldPreview"></span>
-            <a class="entity-field-picker inactive-action margin-right-10" href="javascript:void(0);" data-bind="sqlContextPopover: { sourceType: 'solr', sourceContext: $root.collection.activeSourceContext(), path: 'default.' + $root.collection.name() + '.' + name()  }" style="margin-left: 2px;">
+            <a class="entity-field-picker inactive-action margin-right-10" href="javascript:void(0);" data-bind="sqlContextPopover: { sourceType: 'solr', namespace: $root.collection.activeNamespace(), path: 'default.' + $root.collection.name() + '.' + name()  }" style="margin-left: 2px;">
               <i class="fa fa-info" title="${_('Show Details')}"/>
             </a>
         </li>

+ 6 - 6
desktop/libs/indexer/src/indexer/templates/importer.mako

@@ -2048,12 +2048,12 @@ ${ assist.assistPanel() }
       var self = this;
       var guessFieldTypesXhr;
 
-      self.activeSourceContext = ko.observable();
+      self.activeNamespace = ko.observable();
 
       // TODO: sourceType?
-      ContextCatalog.getSourceContexts({ app: ContextCatalog.BROWSER_APP, sourceType: 'hive' }).done(function (sourceContexts) {
-        // TODO: Context selection for create wizard
-        self.activeSourceContext(sourceContexts[0]);
+      ContextCatalog.getNamespaces({ sourceType: 'hive' }).done(function (namespaces) {
+        // TODO: Namespace selection for create wizard
+        self.activeNamespace(namespaces[0]);
       });
 
       self.fileType = ko.observable();
@@ -2322,13 +2322,13 @@ ${ assist.assistPanel() }
                       var match = snippet.statement_raw().match(/CREATE TABLE `([^`]+)`/i);
                       if (match) {
                         var db = match[1];
-                        DataCatalog.getEntry({ sourceType: snippet.type(), sourceContext: self.activeSourceContext(), path: [ db ]}).done(function (dbEntry) {
+                        DataCatalog.getEntry({ sourceType: snippet.type(), namespace: self.activeNamespace(), path: [ db ]}).done(function (dbEntry) {
                           dbEntry.clearCache({ invalidate: 'invalidate', silenceErrors: true }).done(function () {
                             window.location.href = self.editorVM.selectedNotebook().onSuccessUrl();
                           })
                         });
                       } else {
-                        DataCatalog.getEntry({ sourceType: snippet.type(), sourceContext: self.activeSourceContext(), path: []}).done(function (sourceEntry) {
+                        DataCatalog.getEntry({ sourceType: snippet.type(), namespace: self.activeNamespace(), path: []}).done(function (sourceEntry) {
                           sourceEntry.clearCache({ silenceErrors: true }).done(function () {
                             window.location.href = self.editorVM.selectedNotebook().onSuccessUrl();
                           })

+ 5 - 5
desktop/libs/indexer/src/indexer/templates/indexes.mako

@@ -647,12 +647,12 @@ ${ assist.assistPanel() }
 
       self.baseURL = (IS_HUE_4 ? '/hue' : '') + '/indexer/indexes/';
 
-      self.activeSourceContext = ko.observable();
+      self.activeNamespace = ko.observable();
 
 
-      ContextCatalog.getSourceContexts({ app: ContextCatalog.BROWSER_APP, sourceType: 'solr' }).done(function (sourceContexts) {
-        // TODO: Context selection
-        self.activeSourceContext(sourceContexts[0]);
+      ContextCatalog.getNamespaces({ sourceType: 'solr' }).done(function (namespaces) {
+        // TODO: Namespace selection
+        self.activeNamespace(namespaces[0]);
       });
 
       self.assistAvailable = ko.observable(true);
@@ -855,7 +855,7 @@ ${ assist.assistPanel() }
 
         huePubSub.publish('context.popover.show', {
           data: {
-            sourceContext: self.activeSourceContext(),
+            namespace: self.activeNamespace(),
             type: 'collection',
             identifierChain: [
               {}, // empty, needed by the context popover

+ 5 - 5
desktop/libs/indexer/src/indexer/templates/topics.mako

@@ -622,11 +622,11 @@ ${ assist.assistPanel() }
 
       self.baseURL = (IS_HUE_4 ? '/hue' : '') + '/indexer/topics/';
 
-      self.activeSourceContext = ko.observable();
+      self.activeNamespace = ko.observable();
 
-      ContextCatalog.getSourceContexts({ app: ContextCatalog.BROWSER_APP, sourceType: 'solr' }).done(function (sourceContexts) {
-        // TODO: Context selection
-        self.activeSourceContext(sourceContexts[0]);
+      ContextCatalog.getNamespaces({ sourceType: 'solr' }).done(function (namespaces) {
+        // TODO: Namespace selection
+        self.activeNamespace(namespaces[0]);
       });
 
       self.assistAvailable = ko.observable(true);
@@ -829,7 +829,7 @@ ${ assist.assistPanel() }
         huePubSub.publish('context.popover.show', {
           data: {
             type: 'collection',
-            sourceContext: self.activeSourceContext(),
+            namespace: self.activeNamespace(),
             identifierChain: [
               {}, // empty, needed by the context popover
               ko.mapping.toJS(self.index),

+ 40 - 19
desktop/libs/notebook/src/notebook/static/notebook/js/notebook.ko.js

@@ -356,12 +356,36 @@ var EditorViewModel = (function() {
       return ApiHelper.getInstance(vm);
     };
 
-    self.computes = ko.observableArray(['default', 'nightlyc6', 'selfserviceanalytics']);
-    self.selectedCompute = ko.observable(typeof snippet.selectedCompute != "undefined" && snippet.selectedCompute != null ? snippet.selectedCompute : self.computes().length > 0 ? self.computes()[0] : null);
+    self.avaiableNamespaces = ko.observableArray();
+    self.namespace = ko.observable();
+    self.availableComputes = ko.observableArray();
+    self.compute = ko.observable();
+
+    var computesPromise = ContextCatalog.getComputes({ sourceType: self.type() }).done(function (computes) {
+      self.availableComputes(computes);
+      if (!snippet.compute || !computes.some(function (compute) {
+        if (compute.id === snippet.compute.id) {
+          self.compute(compute);
+          return true;
+        }
+      })) {
+        self.compute(computes[0]);
+      }
+    });
+
+    var namespacesPromise = ContextCatalog.getNamespaces({ sourceType: self.type() }).done(function (namespaces) {
+      self.avaiableNamespaces(namespaces);
+      if (!snippet.namespace || !namespaces.some(function (namespace) {
+        if (namespace.id === snippet.namespace.id) {
+          self.namespace(namespace);
+          return true;
+        }})) {
+        self.namespace(namespaces[0]);
+      }
+    });
 
     self.database = ko.observable();
     var previousDatabase = null;
-    self.sourceContext = ko.observable(snippet.sourceContext || vm.sourceContext);
 
     self.database.subscribe(function (newValue) {
       if (newValue !== null) {
@@ -378,18 +402,8 @@ var EditorViewModel = (function() {
     self.updateDatabases = function () {
       if (self.isSqlDialect()) {
 
-        var sourceContextDeferred = $.Deferred();
-        if (!self.sourceContext()) {
-          ContextCatalog.getSourceContexts({ app: ContextCatalog.BROWSER_APP, sourceType: self.type() }).done(function (sourceContexts) {
-            // TODO: Context selection for the notebook
-            self.sourceContext(sourceContexts[0]);
-            sourceContextDeferred.resolve();
-          });
-        } else {
-          sourceContextDeferred.resolve();
-        }
-        sourceContextDeferred.done(function () {
-          DataCatalog.getEntry({ sourceType: self.type(), sourceContext: self.sourceContext(), path: [], definition: { type: 'source' }}).done(function (sourceEntry) {
+        $.when(computesPromise, namespacesPromise).done(function () {
+          DataCatalog.getEntry({ sourceType: self.type(), namespace: self.namespace(), path: [], definition: { type: 'source' }}).done(function (sourceEntry) {
             sourceEntry.getChildren({ silenceErrors: true }).done(function (databaseEntries) {
               var databaseNames = [];
               databaseEntries.forEach(function (databaseEntry) {
@@ -498,8 +512,13 @@ var EditorViewModel = (function() {
     self.handleAssistSelection = function (databaseDef) {
       if (ignoreNextAssistDatabaseUpdate) {
         ignoreNextAssistDatabaseUpdate = false;
-      } else if (databaseDef.source === self.type() && self.database() !== databaseDef.name) {
-        self.database(databaseDef.name);
+      } else if (databaseDef.sourceType === self.type()) {
+        if (self.namespace() !== databaseDef.namespace) {
+          self.namespace(databaseDef.namespace)
+        }
+        if (self.database() !== databaseDef.name) {
+          self.database(databaseDef.name);
+        }
       }
     };
 
@@ -925,7 +944,7 @@ var EditorViewModel = (function() {
           }
         }
         ignoreNextAssistDatabaseUpdate = true;
-        DataCatalog.getEntry({ sourceType: self.type(), sourceContext: self.sourceContext(), path: path }).done(function (entry) {
+        DataCatalog.getEntry({ sourceType: self.type(), namespace: self.namespace(), path: path }).done(function (entry) {
           entry.clearCache({ invalidate: 'invalidate', cascade: true, silenceErrors: true });
         });
       });
@@ -1131,7 +1150,7 @@ var EditorViewModel = (function() {
         properties: self.properties,
         result: self.result.getContext(),
         database: self.database,
-        selectedCompute: self.selectedCompute(),
+        compute: self.compute(),
         wasBatchExecuted: self.wasBatchExecuted()
       };
     };
@@ -2922,6 +2941,7 @@ var EditorViewModel = (function() {
         if (self.snippets().length === 1) {
           huePubSub.publish('assist.set.database', {
             source: self.snippets()[0].type(),
+            namespace: self.snippets()[0].namespace(),
             name: self.snippets()[0].database()
           });
         } else {
@@ -2929,6 +2949,7 @@ var EditorViewModel = (function() {
             if (snippets.length === 1) {
               huePubSub.publish('assist.set.database', {
                 source: self.snippets()[0].type(),
+                namespace: self.snippets()[0].namespace(),
                 name: self.snippets()[0].database()
               });
             }

+ 7 - 7
desktop/libs/notebook/src/notebook/templates/editor_components.mako

@@ -941,9 +941,9 @@ ${ sqlSyntaxDropdown.sqlSyntaxDropdown() }
     <!-- ko template: { name: 'longer-operation${ suffix }' } --><!-- /ko -->
     <span class="execution-timer" data-bind="visible: type() != 'text' && status() != 'ready' && status() != 'loading', text: result.executionTime().toHHMMSS()" title="${ _('Execution time') }"></span>
 
-    <select data-bind="selectize: computes, value: selectedCompute" class="input-medium"></select>
-    ## <div class="margin-left-10" data-bind="component: { name: 'hue-drop-down', params: { icon: 'fa-cluster', value: selectedCompute, entries: computes, labelAttribute: 'name', foreachVisible: true, searchable: true, linkTitle: '${ _ko('Active compute') }' } }" style="display: inline-block"></div>
-
+    <!-- ko if: availableComputes().length > 1 -->
+    <div class="margin-left-10" data-bind="component: { name: 'hue-drop-down', params: { icon: 'fa-snowflake-o', value: compute, entries: availableComputes, labelAttribute: 'name', foreachVisible: true, searchable: true, linkTitle: '${ _ko('Active compute') }' } }" style="display: inline-block"></div>
+    <!-- /ko -->
     <!-- ko if: availableDatabases().length > 0 && isSqlDialect() -->
     <div class="margin-left-10" data-bind="component: { name: 'hue-drop-down', params: { icon: 'fa-database', value: database, entries: availableDatabases, foreachVisible: true, searchable: true, linkTitle: '${ _ko('Active database') }' } }" style="display: inline-block"></div>
     <!-- /ko -->
@@ -3270,7 +3270,7 @@ function togglePresentation(value) {};
       ko.applyBindings(viewModel, $('#${ bindableElement }')[0]);
       viewModel.init();
 
-      var attachEntryResolver = function (location, sourceType, sourceContext) {
+      var attachEntryResolver = function (location, sourceType, namespace) {
         location.resolveCatalogEntry = function(options) {
           if (!options) {
             options = {};
@@ -3289,7 +3289,7 @@ function togglePresentation(value) {};
 
           var promise = SqlUtils.resolveCatalogEntry({
             sourceType: sourceType,
-            sourceContext: sourceContext,
+            namespace: namespace,
             cancellable: options.cancellable,
             cachedOnly: options.cachedOnly,
             identifierChain: location.identifierChain || location.colRef.identifierChain,
@@ -3341,7 +3341,7 @@ function togglePresentation(value) {};
             } else {
               if (e.data.locations) {
                 e.data.locations.forEach(function (location) {
-                  attachEntryResolver(location, e.data.sourceType, e.data.sourceContext);
+                  attachEntryResolver(location, e.data.sourceType, e.data.namespace);
                 })
               }
               huePubSub.publish('ace.sql.location.worker.message', e);
@@ -3363,7 +3363,7 @@ function togglePresentation(value) {};
           if (event.data.locationWorkerResponse) {
             if (event.data.locationWorkerResponse.locations) {
               event.data.locationWorkerResponse.locations.forEach(function (location) {
-                attachEntryResolver(location, event.data.locationWorkerResponse.sourceType, event.data.locationWorkerResponse.sourceContext);
+                attachEntryResolver(location, event.data.locationWorkerResponse.sourceType, event.data.locationWorkerResponse.namespace);
               })
             }
             huePubSub.publish('ace.sql.location.worker.message', { data: event.data.locationWorkerResponse });