Browse Source

HUE-8330 [frontend] Introduce contexts for SQL and Solr

This adds the necessary structures throughout to be able to use multiple contexts for SQL and Solr.
Johan Ahlen 7 years ago
parent
commit
069285b2eb
27 changed files with 381 additions and 232 deletions
  1. 10 2
      apps/metastore/src/metastore/static/metastore/js/metastore.ko.js
  2. 0 7
      apps/metastore/src/metastore/static/metastore/js/metastore.model.js
  3. 5 5
      apps/metastore/src/metastore/templates/metastore.mako
  4. 5 3
      apps/pig/src/pig/templates/app.mako
  5. 3 1
      desktop/core/src/desktop/static/desktop/js/assist/assistDbSource.js
  6. 49 0
      desktop/core/src/desktop/static/desktop/js/contextHelper.js
  7. 37 33
      desktop/core/src/desktop/static/desktop/js/dataCatalog.js
  8. 27 18
      desktop/core/src/desktop/static/desktop/js/jquery.hiveautocomplete.js
  9. 11 4
      desktop/core/src/desktop/static/desktop/js/ko.hue-bindings.js
  10. 13 10
      desktop/core/src/desktop/static/desktop/js/sqlAutocompleter2.js
  11. 11 10
      desktop/core/src/desktop/static/desktop/js/sqlAutocompleter3.js
  12. 4 1
      desktop/core/src/desktop/static/desktop/js/sqlUtils.js
  13. 3 0
      desktop/core/src/desktop/static/desktop/spec/sqlAutocompleter3Spec.js
  14. 1 0
      desktop/core/src/desktop/templates/ace_sql_location_worker.mako
  15. 116 99
      desktop/core/src/desktop/templates/assist.mako
  16. 1 1
      desktop/core/src/desktop/templates/common_dashboard.mako
  17. 0 2
      desktop/core/src/desktop/templates/global_js_constants.mako
  18. 1 4
      desktop/core/src/desktop/templates/hue.mako
  19. 16 10
      desktop/core/src/desktop/templates/ko_components/ko_context_popover.mako
  20. 1 1
      desktop/core/src/desktop/templates/ko_components/ko_history_panel.mako
  21. 7 0
      desktop/libs/dashboard/src/dashboard/static/dashboard/js/search.ko.js
  22. 2 2
      desktop/libs/dashboard/src/dashboard/templates/common_search.mako
  23. 13 6
      desktop/libs/indexer/src/indexer/templates/importer.mako
  24. 9 1
      desktop/libs/indexer/src/indexer/templates/indexes.mako
  25. 8 0
      desktop/libs/indexer/src/indexer/templates/topics.mako
  26. 24 9
      desktop/libs/notebook/src/notebook/static/notebook/js/notebook.ko.js
  27. 4 3
      desktop/libs/notebook/src/notebook/templates/editor_components.mako

+ 10 - 2
apps/metastore/src/metastore/static/metastore/js/metastore.ko.js

@@ -63,9 +63,17 @@ var MetastoreViewModel = (function () {
 
     self.currentTab = ko.observable('');
 
+    self.activeSourceContext = ko.observable();
+    self.sourceContexts = ko.observableArray();
+
     self.database = ko.observable(null);
 
-    self.loadDatabases();
+    contextHelper.getSourceContexts().done(function (sourceContexts) {
+      // TODO: Context selection
+      self.sourceContexts(sourceContexts);
+      self.activeSourceContext(sourceContexts[0]);
+      self.loadDatabases();
+    });
 
     huePubSub.subscribe('data.catalog.entry.refreshed', function (details) {
       var refreshedEntry = details.entry;
@@ -221,7 +229,7 @@ var MetastoreViewModel = (function () {
       self.loadingDatabases(false);
     });
 
-    DataCatalog.getEntry({ sourceType: self.sourceType(), path: [], definition: { type: 'source' } }).done(function (entry) {
+    DataCatalog.getEntry({ sourceContext: self.activeSourceContext(), sourceType: self.sourceType(), path: [], definition: { type: 'source' } }).done(function (entry) {
       self.catalogEntry(entry);
       entry.getChildren().done(function (databaseEntries) {
         self.databases($.map(databaseEntries, function (databaseEntry) {

+ 0 - 7
apps/metastore/src/metastore/static/metastore/js/metastore.model.js

@@ -35,13 +35,6 @@ var MetastoreDatabase = (function () {
     self.loadingTableComments = ko.observable(false);
     self.loadingTablePopularity = ko.observable(false);
 
-
-    self.contexts = ko.observableArray(['default', 'nightlyc6', 'selfserviceanalytics']);
-    self.context = ko.observable('default');
-    self.context.subscribe(function(newVal) {
-      window.context = newVal;
-    });
-
     self.tables = ko.observableArray();
 
     self.loading = ko.pureComputed(function () {

+ 5 - 5
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(), 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(), sourceContext: parents[1].catalogEntry.sourceContext, 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(), 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(), sourceContext: $parents[2].catalogEntry.sourceContext, 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(), 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(), sourceContext: $parents[2].catalogEntry.sourceContext, path: sourcePath, offset: { top: -3, left: 3 }}"></a></td>
           </tr>
           </tbody>
         </table>
@@ -1005,7 +1005,7 @@ ${ components.menubar(is_embeddable) }
               <a href="javascript: void(0);" class="btn btn-default" data-bind="click: reload" title="${_('Refresh the table')}"><i class="fa fa-refresh" data-bind="css: { 'fa-spin blue' : refreshing }"></i> ${_('Refresh')}</a>
               <!-- /ko -->
               <!-- ko if: !table() -->
-              <select data-bind="selectize: contexts, value: context" class="input-medium"></select>
+              <select data-bind="selectize: $parent.sourceContexts, value: $parent.sourceContext" class="input-medium"></select>
               <a href="javascript: void(0);" class="btn btn-default" data-bind="click: reload" title="${_('Refresh the database')}"><i class="fa fa-refresh" data-bind="css: { 'fa-spin blue' : refreshing }"></i> ${_('Refresh')}</a>
               <!-- /ko -->
               <!-- /ko -->
@@ -1233,7 +1233,7 @@ ${ components.menubar(is_embeddable) }
       ko.applyBindings(viewModel, $('#metastoreComponents')[0]);
 
       if (location.getParameter('refresh') === 'true') {
-        DataCatalog.getEntry({ sourceType: viewModel.sourceType(), path: [], definition: { type: 'source' }}).done(function (entry) {
+        DataCatalog.getEntry({ sourceContext: viewModel.activeSourceContext(), sourceType: viewModel.sourceType(), path: [], definition: { type: 'source' }}).done(function (entry) {
           entry.clearCache({ invalidate: sourceType() === 'impala' ? 'invalidate' : 'cache', silenceErrors: true });
           hueUtils.replaceURL('?');
         });

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

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

+ 3 - 1
desktop/core/src/desktop/static/desktop/js/assist/assistDbSource.js

@@ -38,6 +38,7 @@ var AssistDbSource = (function () {
    * @param {Object} options
    * @param {Object} options.i18n
    * @param {string} options.type
+   * @param {Observable} options.activeSourceContext
    * @param {string} options.name
    * @param {Object} options.navigationSettings
    * @constructor
@@ -50,6 +51,7 @@ var AssistDbSource = (function () {
     self.navigationSettings = options.navigationSettings;
     self.apiHelper = ApiHelper.getInstance();
     self.sourceType = options.type;
+    self.activeSourceContext = options.activeSourceContext;
     self.name = options.name;
     self.catalogEntry;
 
@@ -222,7 +224,7 @@ var AssistDbSource = (function () {
       self.selectedDatabase(null);
       self.databases([]);
 
-      DataCatalog.getEntry({ sourceType: self.sourceType, path : [], definition: { type: 'source' }}).done(function (catalogEntry) {
+      DataCatalog.getEntry({ sourceType: self.sourceType, sourceContext: self.activeSourceContext(), path : [], definition: { type: 'source' } }).done(function (catalogEntry) {
         self.catalogEntry = catalogEntry;
         self.catalogEntry.getChildren({ silenceErrors: self.navigationSettings.rightAssist }).done(function (databaseEntries) {
           self.dbIndex = {};

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

@@ -0,0 +1,49 @@
+// 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.
+
+var SourceContext = (function () {
+  function SourceContext(name) {
+    var self = this;
+    self.name = name;
+  }
+
+  return SourceContext;
+})();
+
+var contextHelper = (function () {
+
+  function ContextHelper () {
+    var self = this;
+    self.sourceContexts = [];
+
+    if (window.IS_EMBEDDED && window.embeddedSourceContext) {
+      self.sourceContexts.push(new SourceContext(window.embeddedSourceContext))
+    } else {
+      self.sourceContexts.push(new SourceContext('defaultNamespace')) // TODO: Drop when we fetch from backend
+    }
+  }
+
+  ContextHelper.prototype.getSourceContexts = function () {
+    var self = this;
+    var deferred = $.Deferred();
+
+    deferred.resolve(self.sourceContexts);
+
+    return deferred.promise();
+  };
+
+  return new ContextHelper(); // Singleton
+})();

+ 37 - 33
desktop/core/src/desktop/static/desktop/js/dataCatalog.js

@@ -122,19 +122,22 @@ var DataCatalog = (function () {
     /**
      * Clears the data catalog and cache for the given path and any children thereof.
      *
-     * @param {string} clusterId - The cluster ID
+     * @param {SourceContext} [sourceContext] - The source context
      * @param {string[]} rootPath - The path to clear
      */
-    DataCatalog.prototype.clearStorageCascade = function (clusterId, rootPath) {
+    DataCatalog.prototype.clearStorageCascade = function (sourceContext, rootPath) {
       var self = this;
       var deferred = $.Deferred();
-      if (rootPath.length === 0 && !clusterId) {
-        self.entries = {};
-        self.store.clear().then(deferred.resolve).catch(deferred.reject);
-        return deferred.promise();
+      if (!sourceContext) {
+        if (rootPath.length === 0) {
+          self.entries = {};
+          self.store.clear().then(deferred.resolve).catch(deferred.reject);
+          return deferred.promise();
+        }
+        return deferred.reject().promise();
       }
 
-      var keyPrefix = clusterId;
+      var keyPrefix = sourceContext.name;
       if (rootPath.length) {
         keyPrefix += '_' +  rootPath.join('.');
       }
@@ -174,7 +177,7 @@ var DataCatalog = (function () {
       }
       var deferred = $.Deferred();
 
-      var identifier = dataCatalogEntry.clusterId;
+      var identifier = dataCatalogEntry.sourceContext.name;
       if (dataCatalogEntry.path.length) {
         identifier += '_' + dataCatalogEntry.path.join('.');
       }
@@ -198,6 +201,7 @@ var DataCatalog = (function () {
      * Loads Navigator Optimizer popularity for multiple tables in one go.
      *
      * @param {Object} options
+     * @oaram {SourceContext} options.sourceContext
      * @param {string[][]} options.paths
      * @param {boolean} [options.silenceErrors] - Default true
      * @param {boolean} [options.cancellable] - Default false
@@ -216,7 +220,7 @@ var DataCatalog = (function () {
       var existingPromises = [];
       options.paths.forEach(function (path) {
         var existingDeferred = $.Deferred();
-        self.getEntry({ path: path }).done(function (tableEntry) {
+        self.getEntry({ sourceContext: options.sourceContext, path: path }).done(function (tableEntry) {
           if (tableEntry.navOptPopularityForChildrenPromise) {
             tableEntry.navOptPopularityForChildrenPromise.done(function (existingPopularEntries) {
               popularEntries = popularEntries.concat(existingPopularEntries);
@@ -276,7 +280,7 @@ var DataCatalog = (function () {
 
             Object.keys(perTable).forEach(function (path) {
               var tableDeferred = $.Deferred();
-              self.getEntry({ path: path }).done(function (entry) {
+              self.getEntry({ sourceContext: options.sourceContext, path: path }).done(function (entry) {
                 cancellablePromises.push(entry.trackedPromise('navOptPopularityForChildrenPromise', entry.applyNavOptResponseToChildren(perTable[path], options).done(function (entries) {
                   popularEntries = popularEntries.concat(entries);
                   tableDeferred.resolve();
@@ -330,21 +334,21 @@ var DataCatalog = (function () {
 
     /**
      * @param {Object} options
-     * @param {string} [options.clusterId] - Default: DEFAULT_CLUSTER_ID
+     * @param {SourceContext} options.sourceContext
      * @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.clusterId || DEFAULT_CLUSTER_ID) + (identifier ? '_' + identifier : '');
+      identifier = options.sourceContext.name + (identifier ? '_' + identifier : '');
       return self.entries[identifier];
     };
 
     /**
      * @param {Object} options
      * @param {string|string[]} options.path
-     * @param {string} [options.clusterId] - Default: DEFAULT_CLUSTER_ID
+     * @param {SourceContext} options.sourceContext
      * @param {Object} [options.definition] - The initial definition if not already set on the entry
      * @param {boolean} [options.cachedOnly] - Default: false
      * @return {Promise}
@@ -352,7 +356,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.clusterId || DEFAULT_CLUSTER_ID) + (identifier ? '_' + identifier : '');
+      identifier = options.sourceContext.name + (identifier ? '_' + identifier : '');
       if (self.entries[identifier]) {
         return self.entries[identifier];
       }
@@ -361,11 +365,11 @@ var DataCatalog = (function () {
       self.entries[identifier] = deferred.promise();
 
       if (!cacheEnabled) {
-        deferred.resolve(new DataCatalogEntry({ dataCatalog: self, clusterId: options.clusterId, path: options.path, definition: options.definition })).promise();
+        deferred.resolve(new DataCatalogEntry({ dataCatalog: self, sourceContext: options.sourceContext, 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, clusterId: options.clusterId, path: options.path, definition: definition });
+          var entry = new DataCatalogEntry({ dataCatalog: self, sourceContext: options.sourceContext, path: options.path, definition: definition });
           if (storeEntry) {
             mergeEntry(entry, storeEntry);
           } else if (!options.cachedOnly && options.definition) {
@@ -374,7 +378,7 @@ var DataCatalog = (function () {
           deferred.resolve(entry);
         }).catch(function (error) {
           console.warn(error);
-          var entry = new DataCatalogEntry({ dataCatalog: self, clusterId: options.clusterId, path: options.path, definition: options.definition });
+          var entry = new DataCatalogEntry({ dataCatalog: self, sourceContext: options.sourceContext, path: options.path, definition: options.definition });
           if (!options.cachedOnly && options.definition) {
             entry.saveLater();
           }
@@ -410,31 +414,31 @@ var DataCatalog = (function () {
     /**
      * Creates an identifier for the given paths with duplicates removed
      *
-     * @param {string} clusterId
+     * @param {SourceContext} sourceContext
      * @param {string[][]} paths
      * @return {string}
      */
-    var createMultiTableIdentifier = function (clusterId, paths) {
+    var createMultiTableIdentifier = function (sourceContext, paths) {
       var pathSet = {};
       paths.forEach(function (path) {
         pathSet[path.join('.')] = true;
       });
       var uniquePaths = Object.keys(pathSet);
       uniquePaths.sort();
-      return clusterId + '_' + uniquePaths.join(',');
+      return sourceContext.name + '_' + uniquePaths.join(',');
     };
 
     /**
      *
      * @param {Object} options
-     * @param {string} [options.clusterId] - Default: DEFAULT_CLUSTER_ID
+     * @param {SourceContext} options.sourceContext
      * @param {string[][]} options.paths
      *
      * @return {Promise}
      */
     DataCatalog.prototype.getMultiTableEntry = function (options) {
       var self = this;
-      var identifier = createMultiTableIdentifier(options.clusterId || DEFAULT_CLUSTER_ID, options.paths);
+      var identifier = createMultiTableIdentifier(options.sourceContext, options.paths);
       if (self.multiTableEntries[identifier]) {
         return self.multiTableEntries[identifier];
       }
@@ -591,7 +595,7 @@ var DataCatalog = (function () {
     /**
      * @param {DataCatalog} options.dataCatalog
      * @param {string|string[]} options.path
-     * @param {string} [options.clusterId] - Default: DEFAULT_CLUSTER_ID
+     * @param {SourceContext} options.sourceContext
      * @param {Object} options.definition - Initial known metadata on creation (normally comes from the parent entry)
      *
      * @constructor
@@ -599,8 +603,7 @@ var DataCatalog = (function () {
     function DataCatalogEntry(options) {
       var self = this;
 
-      // TODO: Make clusterId mandatory once implemented throughout
-      self.clusterId = options.clusterId || DEFAULT_CLUSTER_ID;
+      self.sourceContext = options.sourceContext;
       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;
@@ -651,7 +654,7 @@ var DataCatalog = (function () {
       self.childrenPromise = undefined;
       
       if (self.path.length) {
-        var parent = self.dataCatalog.getKnownEntry({ path: self.path.slice(0, self.path.length - 1) });
+        var parent = self.dataCatalog.getKnownEntry({ sourceContext: self.sourceContext, path: self.path.slice(0, self.path.length - 1) });
         if (parent) {
           parent.navigatorMetaForChildrenPromise = undefined;
           parent.navOptPopularityForChildrenPromise = undefined;
@@ -718,7 +721,7 @@ var DataCatalog = (function () {
       }
 
       self.reset();
-      var saveDeferred = options.cascade ? self.dataCatalog.clearStorageCascade(self.clusterId, self.path) : self.save();
+      var saveDeferred = options.cascade ? self.dataCatalog.clearStorageCascade(self.sourceContext, self.path) : self.save();
 
       var clearPromise = $.when(invalidatePromise, saveDeferred);
 
@@ -796,6 +799,7 @@ var DataCatalog = (function () {
           entities.forEach(function (entity) {
             if (!sourceMeta.databases || ((entity.name || entity) !== '_impala_builtins')) {
               promises.push(self.dataCatalog.getEntry({
+                sourceContext: self.sourceContext,
                 path: self.path.concat(entity.name || entity)
               }).done(function (catalogEntry) {
                 if (!catalogEntry.definition || typeof catalogEntry.definition.index === 'undefined') {
@@ -823,7 +827,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({ path: self.path.concat(path) }).done(function (catalogEntry) {
+              promises.push(self.dataCatalog.getEntry({ sourceContext: self.sourceContext, path: self.path.concat(path) }).done(function (catalogEntry) {
                 if (!catalogEntry.definition || typeof catalogEntry.definition.index === 'undefined') {
                   var definition = sourceMeta[path];
                   definition.index = index++;
@@ -1736,7 +1740,7 @@ var DataCatalog = (function () {
           }
         };
 
-        self.dataCatalog.getEntry({ path: self.path.slice(0, 2), definition: { type: 'table' } }).done(function (tableEntry) {
+        self.dataCatalog.getEntry({ sourceContext: self.sourceContext, path: self.path.slice(0, 2), definition: { type: 'table' } }).done(function (tableEntry) {
           if (tableEntry && tableEntry.samplePromise) {
             cancellablePromises.push(applyCancellable(tableEntry.samplePromise, options));
 
@@ -1801,7 +1805,7 @@ var DataCatalog = (function () {
         return deferred.reject();
       }
       var cancellablePromises = [];
-      catalogEntry.dataCatalog.getMultiTableEntry({ clusterId: catalogEntry.clusterId, paths: [ catalogEntry.path ] }).done(function (multiTableEntry) {
+      catalogEntry.dataCatalog.getMultiTableEntry({ sourceContext: catalogEntry.sourceContext, 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);
@@ -2144,8 +2148,8 @@ var DataCatalog = (function () {
       /**
        * @param {Object} options
        * @param {string} options.sourceType
+       * @param {SourceContext} options.sourceContext
        * @param {string|string[]} options.path
-       * @param {string} [options.clusterId] - Optional cluster ID default: DEFAULT_CLUSTER_ID
        * @param {Object} [options.definition] - Optional initial definition
        *
        * @return {Promise}
@@ -2157,7 +2161,7 @@ var DataCatalog = (function () {
       /**
        * @param {Object} options
        * @param {string} options.sourceType
-       * @param {string} [options.clusterId] - Default: DEFAULT_CLUSTER_ID
+       * @param {SourceContext} options.sourceContext
        * @param {string[][]} options.paths
        *
        * @return {Promise}
@@ -2172,7 +2176,7 @@ var DataCatalog = (function () {
        *
        * @param {Object} options
        * @param {string} options.sourceType
-       * @param {string} [options.clusterId]- Default: DEFAULT_CLUSTER_ID
+       * @param {SourceContext} options.sourceContext
        * @param {string|string[]} options.path
        * @param {Object} [options.definition] - Optional initial definition of the parent entry
        * @param {boolean} [options.silenceErrors]

+ 27 - 18
desktop/core/src/desktop/static/desktop/js/jquery.hiveautocomplete.js

@@ -43,17 +43,20 @@
     };
 
   function Plugin(element, options) {
-    this.element = element;
-    this.options = $.extend({}, defaults, options);
-    this._defaults = defaults;
-    this._name = pluginName;
-
-    this.apiHelper = null;
-
-    if (typeof ApiHelper !== 'undefined' && this.options.apiHelperUser !== '') {
-      this.apiHelper = ApiHelper.getInstance({
-        user: this.options.apiHelperUser
-      });
+    var self = this;
+    self.element = element;
+    self.options = $.extend({}, defaults, options);
+    self._defaults = defaults;
+    self._name = pluginName;
+
+    self.activeSourceContextDeferred = $.Deferred();
+    if (self.options.activeSourceContext) {
+      self.activeSourceContextDeferred.resolve(self.options.activeSourceContext);
+    } else {
+      contextHelper.getSourceContexts().done(function (sourceContexts) {
+        // TODO: Context selection in caller
+        self.activeSourceContextDeferred.resolve(sourceContexts[0]);
+      })
     }
 
     this.init();
@@ -237,22 +240,28 @@
 
     self.getDatabases = function (callback) {
       var self = this;
-      DataCatalog.getChildren({ sourceType: self.options.apiHelperType, path: [] }).done(function (dbEntries) {
-        callback($.map(dbEntries, function (entry) { return entry.name }));
-      });
+      self.activeSourceContextDeferred.done(function (sourceContext) {
+        DataCatalog.getChildren({ sourceType: self.options.apiHelperType, sourceContext: sourceContext, path: [] }).done(function (dbEntries) {
+          callback($.map(dbEntries, function (entry) { return entry.name }));
+        });
+      })
     };
 
     self.getTables = function (database, callback) {
       var self = this;
-      DataCatalog.getEntry({ sourceType: self.options.apiHelperType, path: [database] }).done(function (entry) {
-        entry.getSourceMeta().done(callback)
+      self.activeSourceContextDeferred.done(function (sourceContext) {
+        DataCatalog.getEntry({ sourceType: self.options.apiHelperType, sourceContext: sourceContext, path: [ database ] }).done(function (entry) {
+          entry.getSourceMeta().done(callback)
+        });
       });
     };
 
     self.getColumns = function (database, table, callback) {
       var self = this;
-      DataCatalog.getEntry({ sourceType: self.options.apiHelperType, path: [database, table] }).done(function (entry) {
-        entry.getSourceMeta().done(callback)
+      self.activeSourceContextDeferred.done(function (sourceContext) {
+        DataCatalog.getEntry({sourceType: self.options.apiHelperType, sourceContext: sourceContext, path: [database, table]}).done(function (entry) {
+          entry.getSourceMeta().done(callback)
+        });
       });
     };
 

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

@@ -2485,8 +2485,9 @@
    *
    * Parameters:
    *
-   * {string} path - the path, i.e. 'default.customers' or ['default', 'customers'
    * {string} sourceType - 'impala', 'hive' etc.
+   * {SourceContext} sourceContext
+   * {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
    * {number} [offset.top] - Offset in pixels
@@ -2494,8 +2495,8 @@
    *
    * Examples:
    *
-   * data-bind="sqlContextPopover: { sourceType: 'impala', path: 'default.customers' }"
-   * data-bind="sqlContextPopover: { sourceType: 'hive', path: 'default', orientation: 'bottom', offset: { top: 5 } }"
+   * 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 } }"
    *
    * @type {{init: ko.bindingHandlers.sqlContextPopover.init}}
    */
@@ -2504,7 +2505,7 @@
       ko.bindingHandlers.click.init(element, function () {
         return function () {
           var options = valueAccessor();
-          DataCatalog.getEntry({ sourceType: options.sourceType, path: options.path }).done(function (entry) {
+          DataCatalog.getEntry({ sourceType: options.sourceType, sourceContext: options.sourceContext, path: options.path }).done(function (entry) {
             var $source = $(element);
             var offset = $source.offset();
             if (options.offset) {
@@ -4362,6 +4363,7 @@
       var deferred = $.Deferred();
       DataCatalog.getChildren({
         sourceType: self.snippet.type(),
+        sourceContext: self.snippet.sourceContext(),
         path: $.map(identifierChain, function (identifier) { return identifier.name }),
         silenceErrors: true,
         cachedOnly: true
@@ -4442,6 +4444,7 @@
             if (typeof nextTable.subQuery === 'undefined') {
               DataCatalog.getChildren({
                 sourceType: self.snippet.type(),
+                sourceContext: self.snippet.sourceContext(),
                 path: $.map(nextTable.identifierChain, function (identifier) { return identifier.name }),
                 cachedOnly: true,
                 silenceErrors: true
@@ -4625,6 +4628,7 @@
         lastKnownLocations = {
           id: self.editorId,
           type: self.snippet.type(),
+          sourceContext: self.snippet.sourceContext(),
           defaultDatabase: self.snippet.database(),
           locations: e.data.locations,
           editorChangeTime: e.data.editorChangeTime,
@@ -4728,6 +4732,7 @@
             id: self.snippet.id(),
             statementDetails: statementDetails,
             type: self.snippet.type(),
+            sourceContext: self.snippet.sourceContext(),
             defaultDatabase: self.snippet.database()
           });
         }
@@ -5140,6 +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(),
                           path: $.map(tableChain, function (identifier) { return identifier.name })
                         }).done(function (entry) {
                           entry.getSourceMeta({ cachedOnly: true, silenceErrors: true }).done(function (sourceMeta) {
@@ -5294,6 +5300,7 @@
                   huePubSub.publish('context.popover.show', {
                     data: token.parseLocation,
                     sourceType: snippet.type(),
+                    sourceContext: snippet.sourceContext(),
                     defaultDatabase: snippet.database(),
                     pinEnabled: true,
                     source: source

+ 13 - 10
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(), paths: paths }).done(function (multiTableEntry) {
+        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), 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(), paths: paths }).done(function (multiTableEntry) {
+        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), 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(), paths: paths }).done(function (multiTableEntry) {
+          DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), 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(), paths: paths }).done(function (multiTableEntry) {
+        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), 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(), paths: paths }).done(function (multiTableEntry) {
+        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), 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(), path: [], silenceErrors: true }).done(function (dbEntries) {
+          DataCatalog.getChildren({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), 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(), paths: paths }).done(function (multiTableEntry) {
+          DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), 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]], silenceErrors: true }).done(function (popularTables) {
+        DataCatalog.getCatalog(self.snippet.type()).loadNavOptPopularityForTables({ paths: [[database]], sourceContext: self.snippet.sourceContext(), silenceErrors: true }).done(function (popularTables) {
           var popularityIndex = {};
           popularTables.forEach(function (popularTable) {
             if (popularTable.navOptPopularity) {
@@ -613,6 +613,7 @@ var SqlAutocompleter2 = (function () {
         var checkDbDeferral = $.Deferred();
         DataCatalog.getChildren({
           sourceType: self.snippet.type(),
+          sourceContext: self.snippet.sourceContext(),
           path: [],
           silenceErrors: true
         }).done(function (dbEntries) {
@@ -802,7 +803,7 @@ var SqlAutocompleter2 = (function () {
         identifierChain = identifierChain.slice(1);
       }
 
-      DataCatalog.getEntry({ sourceType: self.snippet.type(), path: [database, table].concat(fetchedFields) }).done(function (entry) {
+      DataCatalog.getEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), 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'
@@ -840,6 +841,7 @@ var SqlAutocompleter2 = (function () {
       if (identifierChain.length > 1) {
         DataCatalog.getChildren({
           sourceType: self.snippet.type(),
+          sourceContext: self.snippet.sourceContext(),
           path: [],
           silenceErrors: true
         }).done(function (dbEntries) {
@@ -876,7 +878,7 @@ var SqlAutocompleter2 = (function () {
         prefix += parseResult.lowerCase ? 'from ' : 'FROM ';
       }
 
-      DataCatalog.getChildren({ sourceType: self.snippet.type(), path: [databaseName], silenceErrors: true }).done(function (tableEntries) {
+      DataCatalog.getChildren({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), path: [databaseName], silenceErrors: true }).done(function (tableEntries) {
         var tables = [];
         tableEntries.forEach(function (tableEntry) {
           if (parseResult.suggestTables.onlyTables && tableEntry.isTable() ||
@@ -1047,6 +1049,7 @@ var SqlAutocompleter2 = (function () {
     }
     DataCatalog.getChildren({
       sourceType: self.snippet.type(),
+      sourceContext: self.snippet.sourceContext(),
       path: [],
       silenceErrors: true
     }).done(function (dbEntries) {

+ 11 - 10
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(), path: [] }).done(function (entry) {
+    DataCatalog.getEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), 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(), path: [ database ]}).done(function (dbEntry) {
+        DataCatalog.getEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), 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(), paths: paths }).done(function (multiTableEntry) {
+        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), 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(), paths: paths }).done(function (multiTableEntry) {
+        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), 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(), paths: paths }).done(function (multiTableEntry) {
+        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), 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({ paths: paths, silenceErrors: true, cancellable: true }).done(function (entries) {
+      .loadNavOptPopularityForTables({ sourceContext: self.snippet.sourceContext(), 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(), paths: paths }).done(function (multiTableEntry) {
+        DataCatalog.getMultiTableEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), 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(), path: [db]}).done(function (entry) {
+      DataCatalog.getEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), path: [ db ]}).done(function (entry) {
         self.cancellablePromises.push(entry.loadNavOptPopularityForChildren({ silenceErrors: true, cancellable: true }).done(function (childEntries) {
           var totalPopularity = 0;
           var popularityIndex = {};
@@ -1424,6 +1424,7 @@ var AutocompleteResults = (function () {
       });
 
       self.cancellablePromises.push(DataCatalog.getCatalog(self.snippet.type()).loadNavOptPopularityForTables({
+        sourceContext: self.snippet.sourceContext(),
         paths: paths,
         silenceErrors: true,
         cancellable: true
@@ -1585,7 +1586,7 @@ var AutocompleteResults = (function () {
         }
       }
 
-      DataCatalog.getEntry({ sourceType: self.snippet.type(), path: fetchedPath }).done(function (catalogEntry) {
+      DataCatalog.getEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), 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'
@@ -1615,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(), path: []}).done(function (catalogEntry) {
+      DataCatalog.getEntry({ sourceType: self.snippet.type(), sourceContext: self.snippet.sourceContext(), 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]);

+ 4 - 1
desktop/core/src/desktop/static/desktop/js/sqlUtils.js

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

+ 3 - 0
desktop/core/src/desktop/static/desktop/spec/sqlAutocompleter3Spec.js

@@ -25,6 +25,9 @@
           },
           database: function () {
             return 'default';
+          },
+          sourceContext: function () {
+            return 'defaultNamespace'
           }
         },
         editor: function () {

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

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

+ 116 - 99
desktop/core/src/desktop/templates/assist.mako

@@ -1078,6 +1078,7 @@ from desktop.views import _ko
         self.apiHelper = options.apiHelper;
         self.i18n = options.i18n;
         self.initialized = false;
+        self.initalizing = false;
 
         if (typeof options.sourceTypes === 'undefined') {
           options.sourceTypes = [];
@@ -1104,25 +1105,9 @@ from desktop.views import _ko
           }
         }
 
+        self.activeSourceContext = ko.observable();
         self.sources = ko.observableArray();
         self.sourceIndex = {};
-        $.each(options.sourceTypes, function (idx, 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.collections.refresh', function() {
-          DataCatalog.getEntry({ sourceType: 'solr', path: [] }).done(function (entry) {
-            entry.clearCache({ cascade: true });
-          });
-        });
-
         self.selectedSource = ko.observable(null);
 
         self.setDatabaseWhenLoaded = function (databaseName) {
@@ -1141,103 +1126,128 @@ from desktop.views import _ko
           }
         };
 
-        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);
-        });
+        contextHelper.getSourceContexts().done(function (sourceContexts) {
+          // TODO: Context selection
+          self.activeSourceContext(sourceContexts[0]);
 
-        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.name);
+          $.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]);
           });
 
-          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
-              });
-            } else {
-              huePubSub.publish('assist.database.set', {
-                source: source,
-                name: 'default'
+          huePubSub.subscribe('assist.collections.refresh', function() {
+            if (self.activeSourceContext()) {
+              DataCatalog.getEntry({ sourceType: 'solr', sourceContext: self.activeSourceContext(), path: [] }).done(function (entry) {
+                entry.clearCache({ cascade: true });
               });
             }
           });
 
-          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'
-              });
+          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.get.source', function () {
-            huePubSub.publish('assist.source.set', self.selectedSource() ? self.selectedSource().sourceType : undefined);
-          });
+          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.name);
+            });
 
-          huePubSub.subscribe('assist.set.source', function (source) {
-            if (self.sourceIndex[source]) {
-              self.selectedSource(self.sourceIndex[source]);
-            }
-          });
+            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
+                });
+              } else {
+                huePubSub.publish('assist.database.set', {
+                  source: source,
+                  name: 'default'
+                });
+              }
+            });
+
+            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'
+                });
+              }
+            });
+
+            huePubSub.subscribe('assist.get.source', function () {
+              huePubSub.publish('assist.source.set', self.selectedSource() ? self.selectedSource().sourceType : undefined);
+            });
 
-          huePubSub.publish('assist.db.panel.ready');
+            huePubSub.subscribe('assist.set.source', function (source) {
+              if (self.sourceIndex[source]) {
+                self.selectedSource(self.sourceIndex[source]);
+              }
+            });
 
-          huePubSub.subscribe('assist.is.db.panel.ready', function () {
             huePubSub.publish('assist.db.panel.ready');
-          });
 
-          self.selectedSource.subscribe(function (newSource) {
-            if (newSource) {
-              if (newSource.databases().length === 0) {
-                newSource.initDatabases();
+            huePubSub.subscribe('assist.is.db.panel.ready', function () {
+              huePubSub.publish('assist.db.panel.ready');
+            });
+
+            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');
               }
-              self.apiHelper.setInTotalStorage('assist', 'lastSelectedSource', newSource.sourceType);
-            } else {
-              self.apiHelper.setInTotalStorage('assist', 'lastSelectedSource');
-            }
-          });
-        }
+            });
+          }
+        });
 
         self.breadcrumb = ko.computed(function () {
           if (self.selectedSource()) {
@@ -2522,6 +2532,7 @@ from desktop.views import _ko
             sources[activeLocations.type] = {
               assistDbSource: new AssistDbSource({
                 i18n: i18n,
+                activeSourceContext: ko.observable(activeLocations.sourceContext),
                 type: activeLocations.type,
                 name: activeLocations.type,
                 navigationSettings: navigationSettings
@@ -2573,6 +2584,7 @@ from desktop.views import _ko
                   } else {
                     DataCatalog.getEntry({
                       sourceType: activeLocations.type,
+                      sourceContext: activeLocations.sourceContext,
                       path: [ database ],
                       definition: { type: 'database' }
                     }).done(function (catalogEntry) {
@@ -2628,7 +2640,7 @@ from desktop.views import _ko
                                   self.reloading(false)
                                 })
                               });
-                              DataCatalog.getEntry({ sourceType: activeLocations.type, path: []}).done(function (sourceEntry) {
+                              DataCatalog.getEntry({ sourceType: activeLocations.type, sourceContext: activeLocations.sourceContext, path: [] }).done(function (sourceEntry) {
                                 sourceEntry.getChildren().done(function (dbEntries) {
                                   var clearPromise;
                                    // Clear the database first if it exists without cascade
@@ -2952,6 +2964,7 @@ from desktop.views import _ko
 
         self.disposals = [];
         self.isSolr = ko.observable(true);
+        self.activeSourceContext = ko.observable();
 
         self.filter = {
           querySpec: ko.observable({
@@ -2983,9 +2996,11 @@ from desktop.views import _ko
           if (!collectionName) {
             return;
           }
+          self.activeSourceContext(collection.activeSourceContext);
 
           var assistDbSource = new AssistDbSource({
             i18n : i18n,
+            activeSourceContext: collection.activeSourceContext,
             type: collection.engine(),
             name: collection.engine(),
             navigationSettings: navigationSettings
@@ -2997,12 +3012,14 @@ from desktop.views import _ko
 
           DataCatalog.getEntry({
             sourceType: sourceType,
+            sourceContext: collection.activeSourceContext,
             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,
               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', path: 'default.' + $root.collection.name() + '.' + 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()  }">
                   <i class="fa fa-fw fa-info" title="${_('Show Details')}"/>
                 </a>
                 <!-- /ko -->

+ 0 - 2
desktop/core/src/desktop/templates/global_js_constants.mako

@@ -205,6 +205,4 @@
       'decimal': -1, 'float': -1, 'int': -1, 'map': -1, 'real': -1, 'smallint': -1, 'string': -1, 'struct': -1,
       'timestamp': -1, 'tinyint': -1, 'varchar': -1 }
   };
-
-  window.DEFAULT_CLUSTER_ID = window.DEFAULT_CLUSTER_ID || 'defaultCluster';
 })();

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

@@ -111,10 +111,6 @@
   var originalClearableImgUrl = '${ static('desktop/art/clearField@2x.png') }';
   var clearableImgUrl = typeof adaptHueEmbeddedUrls !== 'undefined' ? adaptHueEmbeddedUrls(originalClearableImgUrl) : originalClearableImgUrl;
   document.styleSheets[0].insertRule('.clearable { background: url(' + clearableImgUrl + ') no-repeat right -10px center; }');
-
-  if (window.embeddedClusterId) {
-    window.DEFAULT_CLUSTER_ID = window.embeddedClusterId;
-  }
 % endif
   </script>
 
@@ -126,6 +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>
 </head>
 
 <body>

+ 16 - 10
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({ path: this.path }).done(self.catalogEntry);
+                  self.catalogEntry().dataCatalog.getEntry({ sourceContext: self.catalogEntry().sourceContext, 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, defaultDatabase) {
+      function AsteriskData(data, sourceType, sourceContext, defaultDatabase) {
         var self = this;
         self.loading = ko.observable(true);
         self.hasErrors = ko.observable(false);
@@ -700,6 +700,7 @@ from metadata.conf import has_navigator
             }
             DataCatalog.getEntry({
               sourceType: sourceType,
+              sourceContext: sourceContext,
               path: path
             }).done(function (entry) {
               entry.getSourceMeta({ silenceErrors: true }).done(function (sourceMeta) {
@@ -738,9 +739,9 @@ from metadata.conf import has_navigator
         });
       }
 
-      function AsteriskContextTabs(data, sourceType, defaultDatabase) {
+      function AsteriskContextTabs(data, sourceType, sourceContext, defaultDatabase) {
         var self = this;
-        self.data = new AsteriskData(data, sourceType, defaultDatabase);
+        self.data = new AsteriskData(data, sourceType, sourceContext, defaultDatabase);
 
         self.tabs = [
           { id: 'details', label: '${ _("Details") }', template: 'context-popover-asterisk-details', templateData: self.data }
@@ -1202,6 +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.defaultDatabase = params.defaultDatabase;
         self.close = hidePopover;
         var orientation = params.orientation || 'bottom';
@@ -1287,6 +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.showInAssistEnabled = (typeof params.showInAssistEnabled !== 'undefined' ? params.showInAssistEnabled : true)
@@ -1310,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.defaultDatabase);
+          self.contents = new AsteriskContextTabs(self.data, self.sourceType, self.sourceContext, self.defaultDatabase);
           self.title = '*';
           self.iconClass = 'fa-table';
         } else if (self.isDocument) {
@@ -1466,11 +1469,14 @@ from metadata.conf import has_navigator
         }
 
         if (self.isCatalogEntry) {
-          DataCatalog.getEntry({ sourceType: sourceType, path: path, definition: { type: params.data.type.toLowerCase() }}).done(function (catalogEntry) {
-            catalogEntry.navigatorMeta = params.data;
-            catalogEntry.navigatorMetaPromise = $.Deferred().resolve(catalogEntry.navigatorMeta);
-            catalogEntry.saveLater();
-            self.contents(new DataCatalogContext({ popover: self, catalogEntry: catalogEntry }));
+          contextHelper.getSourceContexts().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) {
+              catalogEntry.navigatorMeta = params.data;
+              catalogEntry.navigatorMetaPromise = $.Deferred().resolve(catalogEntry.navigatorMeta);
+              catalogEntry.saveLater();
+              self.contents(new DataCatalogContext({ popover: self, catalogEntry: catalogEntry }));
+            });
           });
         } else if (self.isDocument) {
           self.contents(new DocumentContext(params.data));

+ 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(), path: [] }).done(function (entry) {
+                    DataCatalog.getEntry({ sourceType: snippet.type(), sourceContext: snippet.sourceContext(), path: [] }).done(function (entry) {
                       entry.clearCache({ invalidate: 'cache', cascade: true, silenceErrors: true });
                     });
                   } else if (notebook.onSuccessUrl()) {

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

@@ -556,6 +556,13 @@ 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();
+
+  contextHelper.getSourceContexts().done(function (sourceContexts) {
+    // TODO: Context selection
+    self.activeSourceContext(sourceContexts[0]);
+  });
+
   self.engine = ko.observable(typeof collection.engine != "undefined" && collection.engine != null ? collection.engine : "solr");
   self.engine.subscribe(function() {
     self.name(null);

+ 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', path: 'default.' + $root.collection.name() + '.' + $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()  }">
           <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', 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', sourceContext: $root.collection.activeSourceContext(), path: 'default.' + $root.collection.name() + '.' + name()  }" style="margin-left: 2px;">
               <i class="fa fa-info" title="${_('Show Details')}"/>
             </a>
         </li>

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

@@ -2048,6 +2048,13 @@ ${ assist.assistPanel() }
       var self = this;
       var guessFieldTypesXhr;
 
+      self.activeSourceContext = ko.observable();
+
+      contextHelper.getSourceContexts().done(function (sourceContexts) {
+        // TODO: Context selection for create wizard
+        self.activeSourceContext(sourceContexts[0]);
+      });
+
       self.fileType = ko.observable();
       self.fileType.subscribe(function (newType) {
         if (self.source.format()) {
@@ -2104,10 +2111,10 @@ ${ assist.assistPanel() }
       self.indexingStarted = ko.observable(false);
 
       self.isValidDestination = ko.pureComputed(function() {
-         return self.destination.name().length > 0 && (
-           (['table', 'database'].indexOf(self.destination.outputFormat()) == -1 || /^([a-zA-Z0-9_]+\.)?[a-zA-Z0-9_]+$/.test(self.destination.name())) &&
-           (['index'].indexOf(self.destination.outputFormat()) == -1 || /^[^\\/:]+$/.test(self.destination.name()))
-         );
+        return self.destination.name().length > 0 && (
+          (['table', 'database'].indexOf(self.destination.outputFormat()) == -1 || /^([a-zA-Z0-9_]+\.)?[a-zA-Z0-9_]+$/.test(self.destination.name())) &&
+          (['index'].indexOf(self.destination.outputFormat()) == -1 || /^[^\\/:]+$/.test(self.destination.name()))
+        );
       });
       self.readyToIndex = ko.computed(function () {
         var validFields = self.destination.columns().length || self.destination.outputFormat() == 'database';
@@ -2314,13 +2321,13 @@ ${ assist.assistPanel() }
                       var match = snippet.statement_raw().match(/CREATE TABLE `([^`]+)`/i);
                       if (match) {
                         var db = match[1];
-                        DataCatalog.getEntry({ sourceType: snippet.type(), path: [ db ]}).done(function (dbEntry) {
+                        DataCatalog.getEntry({ sourceType: snippet.type(), sourceContext: self.activeSourceContext(), 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(), path: []}).done(function (sourceEntry) {
+                        DataCatalog.getEntry({ sourceType: snippet.type(), sourceContext: self.activeSourceContext(), path: []}).done(function (sourceEntry) {
                           sourceEntry.clearCache({ silenceErrors: true }).done(function () {
                             window.location.href = self.editorVM.selectedNotebook().onSuccessUrl();
                           })

+ 9 - 1
desktop/libs/indexer/src/indexer/templates/indexes.mako

@@ -647,6 +647,13 @@ ${ assist.assistPanel() }
 
       self.baseURL = (IS_HUE_4 ? '/hue' : '') + '/indexer/indexes/';
 
+      self.activeSourceContext = ko.observable();
+
+      contextHelper.getSourceContexts().done(function (sourceContexts) {
+        // TODO: Context selection
+        self.activeSourceContext(sourceContexts[0]);
+      });
+
       self.assistAvailable = ko.observable(true);
       self.apiHelper = ApiHelper.getInstance();
       self.isHue4 = ko.observable(options.hue4);
@@ -802,7 +809,7 @@ ${ assist.assistPanel() }
           }
         });
         return found;
-      }
+      };
 
       self.fetchIndex = function (index) {
         $.post("${ url('indexer:list_index') }", {
@@ -847,6 +854,7 @@ ${ assist.assistPanel() }
 
         huePubSub.publish('context.popover.show', {
           data: {
+            sourceContext: self.activeSourceContext(),
             type: 'collection',
             identifierChain: [
               {}, // empty, needed by the context popover

+ 8 - 0
desktop/libs/indexer/src/indexer/templates/topics.mako

@@ -622,6 +622,13 @@ ${ assist.assistPanel() }
 
       self.baseURL = (IS_HUE_4 ? '/hue' : '') + '/indexer/topics/';
 
+      self.activeSourceContext = ko.observable();
+
+      contextHelper.getSourceContexts().done(function (sourceContexts) {
+        // TODO: Context selection
+        self.activeSourceContext(sourceContexts[0]);
+      });
+
       self.assistAvailable = ko.observable(true);
       self.apiHelper = ApiHelper.getInstance();
       self.isHue4 = ko.observable(options.hue4);
@@ -822,6 +829,7 @@ ${ assist.assistPanel() }
         huePubSub.publish('context.popover.show', {
           data: {
             type: 'collection',
+            sourceContext: self.activeSourceContext(),
             identifierChain: [
               {}, // empty, needed by the context popover
               ko.mapping.toJS(self.index),

+ 24 - 9
desktop/libs/notebook/src/notebook/static/notebook/js/notebook.ko.js

@@ -358,6 +358,8 @@ var EditorViewModel = (function() {
 
     self.database = ko.observable();
     var previousDatabase = null;
+    self.sourceContext = ko.observable(snippet.sourceContext || vm.sourceContext);
+
     self.database.subscribe(function (newValue) {
       if (newValue !== null) {
         self.getApiHelper().setInTotalStorage('editor', 'last.selected.database', newValue);
@@ -372,15 +374,28 @@ var EditorViewModel = (function() {
 
     self.updateDatabases = function () {
       if (self.isSqlDialect()) {
-        DataCatalog.getEntry({ sourceType: self.type(), path: [], definition: { type: 'source' }}).done(function (sourceEntry) {
-          sourceEntry.getChildren({ silenceErrors: true }).done(function (databaseEntries) {
-            var databaseNames = [];
-            databaseEntries.forEach(function (databaseEntry) {
-              databaseNames.push(databaseEntry.name);
+
+        var sourceContextDeferred = $.Deferred();
+        if (!self.sourceContext()) {
+          contextHelper.getSourceContexts().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) {
+            sourceEntry.getChildren({ silenceErrors: true }).done(function (databaseEntries) {
+              var databaseNames = [];
+              databaseEntries.forEach(function (databaseEntry) {
+                databaseNames.push(databaseEntry.name);
+              });
+              self.availableDatabases(databaseNames);
+            }).fail(function () {
+              self.availableDatabases([]);
             });
-            self.availableDatabases(databaseNames);
-          }).fail(function () {
-            self.availableDatabases([]);
           });
         });
       } else {
@@ -907,7 +922,7 @@ var EditorViewModel = (function() {
           }
         }
         ignoreNextAssistDatabaseUpdate = true;
-        DataCatalog.getEntry({ sourceType: self.type(), path: path }).done(function (entry) {
+        DataCatalog.getEntry({ sourceType: self.type(), sourceContext: self.sourceContext(), path: path }).done(function (entry) {
           entry.clearCache({ invalidate: 'invalidate', cascade: true, silenceErrors: true });
         });
       });

+ 4 - 3
desktop/libs/notebook/src/notebook/templates/editor_components.mako

@@ -3266,7 +3266,7 @@ function togglePresentation(value) {};
       ko.applyBindings(viewModel, $('#${ bindableElement }')[0]);
       viewModel.init();
 
-      var attachEntryResolver = function (location, sourceType) {
+      var attachEntryResolver = function (location, sourceType, sourceContext) {
         location.resolveCatalogEntry = function(options) {
           if (!options) {
             options = {};
@@ -3285,6 +3285,7 @@ function togglePresentation(value) {};
 
           var promise = SqlUtils.resolveCatalogEntry({
             sourceType: sourceType,
+            sourceContext: sourceContext,
             cancellable: options.cancellable,
             cachedOnly: options.cachedOnly,
             identifierChain: location.identifierChain || location.colRef.identifierChain,
@@ -3336,7 +3337,7 @@ function togglePresentation(value) {};
             } else {
               if (e.data.locations) {
                 e.data.locations.forEach(function (location) {
-                  attachEntryResolver(location, e.data.sourceType);
+                  attachEntryResolver(location, e.data.sourceType, e.data.sourceContext);
                 })
               }
               huePubSub.publish('ace.sql.location.worker.message', e);
@@ -3358,7 +3359,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);
+                attachEntryResolver(location, event.data.locationWorkerResponse.sourceType, event.data.locationWorkerResponse.sourceContext);
               })
             }
             huePubSub.publish('ace.sql.location.worker.message', { data: event.data.locationWorkerResponse });