Browse Source

HUE-8330 [metastore] Introduce a namespace level in the Table Browser

Johan Ahlen 7 years ago
parent
commit
8836c1bb81

+ 121 - 203
apps/metastore/src/metastore/static/metastore/js/metastore.ko.js

@@ -33,8 +33,6 @@ var MetastoreViewModel = (function () {
     self.navigatorEnabled = ko.observable(options.navigatorEnabled || false);
     self.navigatorEnabled = ko.observable(options.navigatorEnabled || false);
     self.sourceType = ko.observable(options.sourceType || 'hive');
     self.sourceType = ko.observable(options.sourceType || 'hive');
 
 
-    self.catalogEntry = ko.observable();
-
     self.navigatorEnabled.subscribe(function (newValue) {
     self.navigatorEnabled.subscribe(function (newValue) {
       huePubSub.publish('meta.navigator.enabled', newValue);
       huePubSub.publish('meta.navigator.enabled', newValue);
     });
     });
@@ -45,40 +43,24 @@ var MetastoreViewModel = (function () {
     huePubSub.subscribe("assist.db.panel.ready", function () {
     huePubSub.subscribe("assist.db.panel.ready", function () {
       huePubSub.publish('assist.set.database', {
       huePubSub.publish('assist.set.database', {
         source: self.sourceType(),
         source: self.sourceType(),
-        namespace: self.activeNamespace(),
+        namespace: self.namespace(),
         name: null
         name: null
       });
       });
     });
     });
 
 
     self.reloading = ko.observable(false);
     self.reloading = ko.observable(false);
-    self.loadingDatabases = ko.observable(false);
-    self.loadingTable = ko.observable(false);
-    self.lastLoadDatabasesPromise = null;
-
-    self.loading = ko.pureComputed(function () {
-      return self.loadingDatabases() || self.loadingTable() || self.reloading();
-    });
-
-    self.databases = ko.observableArray();
-
-    self.selectedDatabases = ko.observableArray();
+    self.loading = ko.observable(false);
 
 
     self.currentTab = ko.observable('');
     self.currentTab = ko.observable('');
 
 
-    self.activeNamespace = ko.observable();
+    self.lastLoadNamespacesDeferred = $.Deferred();
+    self.namespace = ko.observable();
     self.namespaces = ko.observableArray();
     self.namespaces = ko.observableArray();
 
 
-    self.activeNamespace.subscribe(function () {
-      self.lastLoadDatabasesPromise = null;
-      self.loadDatabases()
-    });
-
-    self.database = ko.observable(null);
-
-    ContextCatalog.getNamespaces({ sourceType: self.sourceType() }).done(function (namespaces) {
-      // TODO: Namespace selection
-      self.namespaces(namespaces);
-      self.activeNamespace(namespaces[0]);
+    self.namespace.subscribe(function () {
+      if (self.namespace() && self.namespace().databases().length === 0) {
+        self.namespace().loadDatabases();
+      }
     });
     });
 
 
     huePubSub.subscribe('data.catalog.entry.refreshed', function (details) {
     huePubSub.subscribe('data.catalog.entry.refreshed', function (details) {
@@ -88,20 +70,29 @@ var MetastoreViewModel = (function () {
         return;
         return;
       }
       }
 
 
+      var prevNamespaceId = null;
       var prevDbName = null;
       var prevDbName = null;
       var prevTableName = null;
       var prevTableName = null;
-      if (self.database()) {
-        prevDbName = self.database().catalogEntry.name;
-        if (self.database().table()) {
-          prevTableName = self.database().table().catalogEntry.name;
+      if (self.namespace()) {
+        prevNamespaceId = self.namespace().namespace.id;
+        if (self.namespace().database()) {
+          prevDbName = self.namespace().database().catalogEntry.name;
+          if (self.namespace().database().table()) {
+            prevTableName = self.namespace().database().table().catalogEntry.name;
+          }
+
         }
         }
       }
       }
 
 
       var setPrevious = function () {
       var setPrevious = function () {
-        if (prevDbName) {
-          self.setDatabaseByName(prevDbName, function () {
-            if (self.database() && prevTableName) {
-              self.database().setTableByName(prevTableName);
+        if (prevNamespaceId) {
+          self.setNamespaceById(prevNamespaceId).done(function () {
+            if (prevDbName) {
+              self.namespace().setDatabaseByName(prevDbName, function () {
+                if (self.namespace().database() && prevTableName) {
+                  self.namespace().database().setTableByName(prevTableName);
+                }
+              });
             }
             }
           });
           });
         }
         }
@@ -109,28 +100,31 @@ var MetastoreViewModel = (function () {
 
 
       var completeRefresh = function () {
       var completeRefresh = function () {
         self.reloading(true);
         self.reloading(true);
-        if (self.database() && self.database().table()) {
-          self.database().table(null);
+        if (self.namespace() && self.namespace().database() && self.namespace().database().table()) {
+          self.namespace().database().table(null);
         }
         }
-        if (self.database()) {
-          self.database(null);
+        if (self.namespace() && self.namespace().database()) {
+          self.namespace().database(null);
         }
         }
-        self.loadDatabases().done(setPrevious).always(function () {
+        if (self.namespace()) {
+          self.namespace(null);
+        }
+        self.loadNamespaces().done(setPrevious).always(function () {
           self.reloading(false);
           self.reloading(false);
         });
         });
       };
       };
 
 
       if (refreshedEntry.isSource()) {
       if (refreshedEntry.isSource()) {
         completeRefresh();
         completeRefresh();
-      } else if (refreshedEntry.isDatabase()) {
-        self.databases().some(function (database) {
+      } else if (refreshedEntry.isDatabase() && self.namespace()) {
+        self.namespace().databases().some(function (database) {
           if (database.catalogEntry === refreshedEntry) {
           if (database.catalogEntry === refreshedEntry) {
             database.load(setPrevious, self.optimizerEnabled(), self.navigatorEnabled());
             database.load(setPrevious, self.optimizerEnabled(), self.navigatorEnabled());
             return true;
             return true;
           }
           }
         })
         })
       } else if (refreshedEntry.isTableOrView()) {
       } else if (refreshedEntry.isTableOrView()) {
-        self.databases().some(function (database) {
+        self.namespace().databases().some(function (database) {
           if (database.catalogEntry.name === refreshedEntry.path[0]) {
           if (database.catalogEntry.name === refreshedEntry.path[0]) {
             database.tables().some(function (table) {
             database.tables().some(function (table) {
               if (table.catalogEntry.name === refreshedEntry.name) {
               if (table.catalogEntry.name === refreshedEntry.name) {
@@ -154,12 +148,12 @@ var MetastoreViewModel = (function () {
       if (self.sourceType() !== databaseDef.sourceType) {
       if (self.sourceType() !== databaseDef.sourceType) {
         self.sourceType(databaseDef.sourceType);
         self.sourceType(databaseDef.sourceType);
       }
       }
-      if (self.activeNamespace() !== databaseDef.namespace) {
-        self.activeNamespace(databaseDef.namespace);
+      if (self.namespace() !== databaseDef.namespace) {
+        self.namespace(databaseDef.namespace);
       }
       }
       // TODO: Handle namespaces + sourceType
       // TODO: Handle namespaces + sourceType
-      if (self.database()) {
-        self.database().table(null);
+      if (self.namespace() && self.database()) {
+        self.namespace().database().table(null);
       }
       }
       self.setDatabaseByName(databaseDef.name, function () {
       self.setDatabaseByName(databaseDef.name, function () {
         huePubSub.publish('metastore.url.change')
         huePubSub.publish('metastore.url.change')
@@ -171,101 +165,103 @@ var MetastoreViewModel = (function () {
       if (self.isHue4()){
       if (self.isHue4()){
         prefix = '/hue' + prefix;
         prefix = '/hue' + prefix;
       }
       }
-      if (self.database() && self.database().table()) {
-        hueUtils.changeURL(prefix + 'table/' + self.database().table().catalogEntry.path.join('/'));
-      }
-      else if (self.database()) {
-        hueUtils.changeURL(prefix + 'tables/' + self.database().catalogEntry.name);
-      }
-      else {
-        hueUtils.changeURL(prefix + 'databases');
+      if (self.namespace()) {
+        if (self.namespace().database() && self.namespace().database().table()) {
+          hueUtils.changeURL(prefix + 'table/' + self.namespace().database().table().catalogEntry.path.join('/'));
+        }
+        else if (self.namespace().database()) {
+          hueUtils.changeURL(prefix + 'tables/' + self.namespace().database().catalogEntry.name);
+        }
+        else {
+          hueUtils.changeURL(prefix + 'databases');
+        }
       }
       }
     });
     });
 
 
-    self.loadURL();
+    self.loadNamespaces().done(function () {
+      self.loadUrl();
+    });
 
 
     window.onpopstate = function () {
     window.onpopstate = function () {
       if (window.location.pathname.indexOf('/metastore') > -1) {
       if (window.location.pathname.indexOf('/metastore') > -1) {
-        self.loadURL();
+        self.loadUrl();
       }
       }
     };
     };
 
 
+    self.namespacesBreadcrumb = function () {
+      if (self.namespace() && self.namespace().database()) {
+        self.namespace().database().table(null);
+      }
+      if (self.namespace()) {
+        self.namespace().database(null)
+      }
+      self.namespace(null);
+      huePubSub.publish('metastore.url.change');
+    };
+
     self.databasesBreadcrumb = function () {
     self.databasesBreadcrumb = function () {
-      if (self.database()) {
-        self.database().table(null);
+      if (self.namespace().database()) {
+        self.namespace().database().table(null);
       }
       }
-      self.database(null);
+      self.namespace().database(null);
       huePubSub.publish('metastore.url.change');
       huePubSub.publish('metastore.url.change');
     };
     };
 
 
     self.tablesBreadcrumb = function () {
     self.tablesBreadcrumb = function () {
-      self.database().table(null);
+      self.namespace().database().table(null);
       huePubSub.publish('metastore.url.change')
       huePubSub.publish('metastore.url.change')
     }
     }
   }
   }
 
 
-  MetastoreViewModel.prototype.onDatabaseClick = function (catalogEntry) {
+  MetastoreViewModel.prototype.loadNamespaces = function () {
     var self = this;
     var self = this;
-
-    self.databases().some(function (database) {
-      if (database.catalogEntry === catalogEntry) {
-        self.setDatabase(database, function() { huePubSub.publish('metastore.url.change') });
-        return true;
-      }
-    });
-  };
-
-  MetastoreViewModel.prototype.reload = function () {
-    var self = this;
-    if (!self.reloading() && self.catalogEntry()) {
-      self.reloading(true);
-      // Clear will publish when done
-      self.catalogEntry().clearCache({ invalidate: self.catalogEntry().getSourceType() === 'impala' ? 'invalidate' : 'cache' });
-    }
+    self.lastLoadNamespacesDeferred = $.Deferred();
+    ContextCatalog.getNamespaces({ sourceType: self.sourceType() }).done(function (namespaces) {
+      self.namespaces($.map(namespaces, function (namespace) {
+        return new MetastoreNamespace({
+          metastoreViewModel: self,
+          sourceType: self.sourceType,
+          navigatorEnabled: self.navigatorEnabled,
+          optimizerEnabled: self.optimizerEnabled,
+          namespace: namespace
+        });
+      }));
+      self.namespace(self.namespaces()[0]);
+      self.lastLoadNamespacesDeferred.resolve();
+    }).fail(self.lastLoadNamespacesDeferred.reject);
+    return self.lastLoadNamespacesDeferred;
   };
   };
 
 
-  MetastoreViewModel.prototype.loadDatabases = function () {
+  MetastoreViewModel.prototype.setNamespaceById = function (namespaceId) {
     var self = this;
     var self = this;
-    if (self.loadingDatabases() && self.lastLoadDatabasesPromise) {
-      return self.lastLoadDatabasesPromise;
-    }
-
-    self.loadingDatabases(true);
     var deferred = $.Deferred();
     var deferred = $.Deferred();
-    self.lastLoadDatabasesPromise = deferred.promise();
-
-    deferred.fail(function () {
-      self.databases([]);
-    }).always(function () {
-      self.loadingDatabases(false);
-    });
-
-    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) {
-          return new MetastoreDatabase({ catalogEntry: databaseEntry, optimizerEnabled: self.optimizerEnabled, metastoreViewModel: self });
-        }));
-        deferred.resolve();
-      }).fail(deferred.reject);
-    });
+    self.lastLoadNamespacesDeferred.done(function () {
+      var found = self.namespaces().some(function (namespace) {
+        if (namespace.namespace.id === namespaceId) {
+          self.namespace(namespace);
+          return true;
+          deferred.resolve();
+        }
+      });
+      if (!found) {
+        deferred.reject();
+      }
+    }).fail(deferred.reject);
+    return deferred.promise();
+  }
 
 
-    return self.lastLoadDatabasesPromise;
-  };
 
 
   MetastoreViewModel.prototype.loadTableDef = function (tableDef, callback) {
   MetastoreViewModel.prototype.loadTableDef = function (tableDef, callback) {
     var self = this;
     var self = this;
-    self.loadingTable(true);
     if (self.sourceType() !== tableDef.sourceType) {
     if (self.sourceType() !== tableDef.sourceType) {
       self.sourceType(tableDef.sourceType);
       self.sourceType(tableDef.sourceType);
     }
     }
-    if (self.activeNamespace() !== tableDef.namespace) {
-      self.activeNamespace(tableDef.namespace);
+    if (self.namespace() !== tableDef.namespace) {
+      self.namespace(tableDef.namespace);
     }
     }
-    self.setDatabaseByName(tableDef.database, function () {
-      if (self.database()) {
-        if (self.database().table() && self.database().table().catalogEntry.name === tableDef.name) {
-          self.loadingTable(false);
+    self.namespace().setDatabaseByName(tableDef.database, function () {
+      if (self.namespace() && self.namespace().database()) {
+        if (self.namespace().database().table() && self.namespace().database().table().catalogEntry.name === tableDef.name) {
           if (callback) {
           if (callback) {
             callback();
             callback();
           }
           }
@@ -273,15 +269,14 @@ var MetastoreViewModel = (function () {
         }
         }
 
 
         var setTableAfterLoad = function (clearDbCacheOnMissing) {
         var setTableAfterLoad = function (clearDbCacheOnMissing) {
-          var foundTables = self.database().tables().filter(function (table) {
+          var foundTables = self.namespace().database().tables().filter(function (table) {
             return table.catalogEntry.name === tableDef.name;
             return table.catalogEntry.name === tableDef.name;
           });
           });
           if (foundTables.length === 1) {
           if (foundTables.length === 1) {
-            self.loadingTable(false);
-            self.database().setTable(foundTables[0], callback);
+            self.namespace().database().setTable(foundTables[0], callback);
           } else if (clearDbCacheOnMissing) {
           } else if (clearDbCacheOnMissing) {
-            self.database().catalogEntry.clearCache({ invalidate: 'invalidate', silenceErrors: true }).done(function () {
-              self.database().load(function () {
+            self.namespace().database().catalogEntry.clearCache({ invalidate: 'invalidate', silenceErrors: true }).done(function () {
+              self.namespace().database().load(function () {
                 setTableAfterLoad(false);
                 setTableAfterLoad(false);
               });
               });
             });
             });
@@ -290,8 +285,8 @@ var MetastoreViewModel = (function () {
           }
           }
         };
         };
 
 
-        if (!self.database().loaded()) {
-          var doOnce = self.database().loaded.subscribe(function () {
+        if (!self.namespace().database().loaded()) {
+          var doOnce = self.namespace().database().loaded.subscribe(function () {
             setTableAfterLoad(true);
             setTableAfterLoad(true);
             doOnce.dispose();
             doOnce.dispose();
           });
           });
@@ -302,73 +297,7 @@ var MetastoreViewModel = (function () {
     });
     });
   };
   };
 
 
-  MetastoreViewModel.prototype.setDatabaseByName = function (databaseName, callback) {
-    var self = this;
-
-    var whenLoaded = function (clearCacheOnMissing) {
-      if (!databaseName) {
-        databaseName = self.apiHelper.getFromTotalStorage('editor', 'last.selected.database') ||
-            self.apiHelper.getFromTotalStorage('metastore', 'last.selected.database') || 'default';
-        clearCacheOnMissing = false;
-      }
-      if (self.database() && self.database().catalogEntry.name === databaseName) {
-        if (callback) {
-          callback();
-        }
-        return;
-      }
-      var foundDatabases = self.databases().filter(function (database) {
-        return database.catalogEntry.name === databaseName;
-      });
-      if (foundDatabases.length === 1) {
-        self.setDatabase(foundDatabases[0], callback);
-      } else if (clearCacheOnMissing) {
-        self.catalogEntry().clearCache({ invalidate: 'invalidate', silenceErrors: true }).done(function () {
-          self.loadDatabases().done(function () {
-            whenLoaded(false)
-          })
-        })
-      } else {
-        foundDatabases = self.databases().filter(function (database) {
-          return database.catalogEntry.name === 'default';
-        });
-
-        if (foundDatabases.length === 1) {
-          self.setDatabase(foundDatabases[0], callback);
-        } else {
-        }
-      }
-    };
-
-    window.setTimeout(function () {
-      if (self.loadingDatabases() && self.lastLoadDatabasesPromise !== null) {
-        self.lastLoadDatabasesPromise.done(function () {
-          whenLoaded(true);
-        });
-      } else {
-        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);
-  };
-
-  MetastoreViewModel.prototype.loadURL = function () {
+  MetastoreViewModel.prototype.loadUrl = function () {
     var self = this;
     var self = this;
 
 
     var path = (IS_HUE_4 ? window.location.pathname.substr(4) : window.location.pathname);
     var path = (IS_HUE_4 ? window.location.pathname.substr(4) : window.location.pathname);
@@ -382,28 +311,29 @@ var MetastoreViewModel = (function () {
     if (path[0] === 'metastore') {
     if (path[0] === 'metastore') {
       path.shift();
       path.shift();
     }
     }
+    var namespace = self.namespace();
     switch (path[0]) {
     switch (path[0]) {
       case 'databases':
       case 'databases':
-        if (self.database()) {
-          self.database().table(null);
-          self.database(null);
+        if (namespace.database()) {
+          namespace.database().table(null);
+          namespace.database(null);
         }
         }
         break;
         break;
       case 'tables':
       case 'tables':
-        if (self.database()) {
-          self.database().table(null);
+        if (namespace.database()) {
+          namespace.database().table(null);
         }
         }
-        self.setDatabaseByName(path[1]);
+        namespace.setDatabaseByName(path[1]);
         break;
         break;
       case 'table':
       case 'table':
-
         huePubSub.subscribe('metastore.loaded.table', function() {
         huePubSub.subscribe('metastore.loaded.table', function() {
           self.currentTab('overview');
           self.currentTab('overview');
         }, 'metastore');
         }, 'metastore');
         self.loadTableDef({
         self.loadTableDef({
           name: path[2],
           name: path[2],
           database: path[1],
           database: path[1],
-          sourceType: self.sourceType()
+          sourceType: self.sourceType(),
+          namespace: namespace
         }, function(){
         }, function(){
           if (path.length > 3 && path[3] === 'partitions'){
           if (path.length > 3 && path[3] === 'partitions'){
             huePubSub.subscribe('metastore.loaded.partitions', function() {
             huePubSub.subscribe('metastore.loaded.partitions', function() {
@@ -414,17 +344,5 @@ var MetastoreViewModel = (function () {
     }
     }
   };
   };
 
 
-  MetastoreViewModel.prototype.setDatabase = function (metastoreDatabase, callback) {
-    var self = this;
-    huePubSub.publish('metastore.scroll.to.top');
-    self.database(metastoreDatabase);
-
-    if (!metastoreDatabase.loaded()) {
-      metastoreDatabase.load(callback, self.optimizerEnabled(), self.navigatorEnabled(), self.sourceType());
-    } else if (callback) {
-      callback();
-    }
-  };
-
   return MetastoreViewModel;
   return MetastoreViewModel;
 })();
 })();

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

@@ -14,6 +14,145 @@
 // See the License for the specific language governing permissions and
 // See the License for the specific language governing permissions and
 // limitations under the License.
 // limitations under the License.
 
 
+var MetastoreNamespace = (function () {
+
+  var MetastoreNamespace = function (options) {
+    var self = this;
+    self.namespace = options.namespace;
+    self.metastoreViewModel = options.metastoreViewModel;
+    self.sourceType = options.sourceType;
+    self.navigatorEnabled = options.navigatorEnabled;
+    self.optimizerEnabled = options.optimizerEnabled;
+
+    self.catalogEntry = ko.observable();
+
+    self.database = ko.observable();
+    self.databases = ko.observableArray();
+    self.selectedDatabases = ko.observableArray();
+    self.loadingDatabases = ko.observable(false);
+    self.lastLoadDatabasesPromise = undefined;
+  };
+
+  MetastoreNamespace.prototype.loadDatabases = function () {
+    var self = this;
+    if (self.loadingDatabases() && self.lastLoadDatabasesPromise) {
+      return self.lastLoadDatabasesPromise;
+    }
+
+    self.loadingDatabases(true);
+    var deferred = $.Deferred();
+    self.lastLoadDatabasesPromise = deferred.promise();
+
+    deferred.fail(function () {
+      self.databases([]);
+    }).always(function () {
+      self.loadingDatabases(false);
+    });
+
+    DataCatalog.getEntry({ namespace: self.namespace, sourceType: self.sourceType(), path: [], definition: { type: 'source' } }).done(function (entry) {
+      self.catalogEntry(entry);
+      entry.getChildren().done(function (databaseEntries) {
+        self.databases($.map(databaseEntries, function (databaseEntry) {
+          return new MetastoreDatabase({ catalogEntry: databaseEntry, optimizerEnabled: self.optimizerEnabled, metastoreViewModel: self.metastoreViewModel });
+        }));
+        deferred.resolve();
+      }).fail(deferred.reject);
+    });
+
+    return self.lastLoadDatabasesPromise;
+  };
+
+  MetastoreNamespace.prototype.reload = function () {
+    var self = this;
+    if (!self.reloading() && self.catalogEntry()) {
+      self.reloading(true);
+      // Clear will publish when done
+      self.catalogEntry().clearCache({ invalidate: self.catalogEntry().getSourceType() === 'impala' ? 'invalidate' : 'cache' });
+    }
+  };
+
+  MetastoreNamespace.prototype.setDatabase = function (metastoreDatabase, callback) {
+    var self = this;
+    huePubSub.publish('metastore.scroll.to.top');
+    self.database(metastoreDatabase);
+
+    if (!metastoreDatabase.loaded()) {
+      metastoreDatabase.load(callback, self.optimizerEnabled(), self.navigatorEnabled(), self.sourceType());
+    } else if (callback) {
+      callback();
+    }
+  };
+
+  MetastoreNamespace.prototype.onDatabaseClick = function (catalogEntry) {
+    var self = this;
+
+    self.databases().some(function (database) {
+      if (database.catalogEntry === catalogEntry) {
+        self.setDatabase(database, function() { huePubSub.publish('metastore.url.change') });
+        return true;
+      }
+    });
+  };
+
+  MetastoreNamespace.prototype.setDatabaseByName = function (databaseName, callback) {
+    var self = this;
+
+    var whenLoaded = function (clearCacheOnMissing) {
+      if (!databaseName) {
+        databaseName = self.apiHelper.getFromTotalStorage('editor', 'last.selected.database') ||
+          self.apiHelper.getFromTotalStorage('metastore', 'last.selected.database') || 'default';
+        clearCacheOnMissing = false;
+      }
+      if (self.database() && self.database().catalogEntry.name === databaseName) {
+        if (callback) {
+          callback();
+        }
+        return;
+      }
+      var foundDatabases = self.databases().filter(function (database) {
+        return database.catalogEntry.name === databaseName;
+      });
+
+      if (foundDatabases.length === 1) {
+        self.setDatabase(foundDatabases[0], callback);
+      } else if (clearCacheOnMissing) {
+        self.catalogEntry().clearCache({ invalidate: 'invalidate', silenceErrors: true }).done(function () {
+          self.loadDatabases().done(function () {
+            whenLoaded(false)
+          })
+        })
+      } else {
+        foundDatabases = self.databases().filter(function (database) {
+          return database.catalogEntry.name === 'default';
+        });
+
+        if (foundDatabases.length === 1) {
+          self.setDatabase(foundDatabases[0], callback);
+        } else {
+        }
+      }
+    };
+
+    window.setTimeout(function () {
+      if (self.loadingDatabases() && self.lastLoadDatabasesPromise !== null) {
+        self.lastLoadDatabasesPromise.done(function () {
+          whenLoaded(true);
+        });
+      } else {
+        if (self.databases().length) {
+          whenLoaded(true);
+        } else {
+          self.loadDatabases().done(function () {
+            whenLoaded(true);
+          })
+        }
+      }
+    }, 0);
+  };
+
+  return MetastoreNamespace;
+})();
+
 var MetastoreDatabase = (function () {
 var MetastoreDatabase = (function () {
   /**
   /**
    * @param {object} options
    * @param {object} options

+ 58 - 14
apps/metastore/src/metastore/templates/metastore.mako

@@ -83,13 +83,35 @@ ${ components.menubar(is_embeddable) }
 
 
 <script type="text/html" id="metastore-breadcrumbs">
 <script type="text/html" id="metastore-breadcrumbs">
   <ul class="nav nav-pills hue-breadcrumbs-bar" id="breadcrumbs">
   <ul class="nav nav-pills hue-breadcrumbs-bar" id="breadcrumbs">
+    <!-- ko if: namespaces().length > 1-->
+    <li>
+      <a href="javascript:void(0);" data-bind="click: namespacesBreadcrumb">${_('Namespaces')}
+        <!-- ko if: namespace -->
+        <span class="divider">&gt;</span>
+        <!-- /ko -->
+      </a>
+    </li>
+    <!-- /ko -->
+    <!-- ko if: namespaces().length <= 1-->
     <li>
     <li>
       <a href="javascript:void(0);" data-bind="click: databasesBreadcrumb">${_('Databases')}
       <a href="javascript:void(0);" data-bind="click: databasesBreadcrumb">${_('Databases')}
+        <!-- ko if: namespace() && namespace().database() -->
+        <span class="divider">&gt;</span>
+        <!-- /ko -->
+      </a>
+    </li>
+    <!-- /ko -->
+    <!-- ko with: namespace -->
+    <!-- ko if: $parent.namespaces().length > 1 -->
+    <li>
+      <a href="javascript:void(0);" data-bind="click: $root.databasesBreadcrumb">
+        <span data-bind="text: namespace.name"></span>
         <!-- ko if: database -->
         <!-- ko if: database -->
         <span class="divider">&gt;</span>
         <span class="divider">&gt;</span>
         <!-- /ko -->
         <!-- /ko -->
       </a>
       </a>
     </li>
     </li>
+    <!-- /ko -->
     <!-- ko with: database -->
     <!-- ko with: database -->
     <li>
     <li>
       <a href="javascript:void(0);" data-bind="click: $root.tablesBreadcrumb">
       <a href="javascript:void(0);" data-bind="click: $root.tablesBreadcrumb">
@@ -112,6 +134,7 @@ ${ components.menubar(is_embeddable) }
       <!-- /ko -->
       <!-- /ko -->
     <!-- /ko -->
     <!-- /ko -->
     <!-- /ko -->
     <!-- /ko -->
+    <!-- /ko -->
   </ul>
   </ul>
 </script>
 </script>
 
 
@@ -330,6 +353,33 @@ ${ components.menubar(is_embeddable) }
   <!-- /ko -->
   <!-- /ko -->
 </script>
 </script>
 
 
+<script type="text/html" id="metastore-namespaces">
+  <div class="entries-table-container">
+    <div class="actionbar-actions">
+
+    </div>
+    <div class="entries-table-no-header">
+      <div class="catalog-entries-list-container">
+        <!-- ko hueSpinner: { spin: loading, center: true, size: 'xlarge' } --><!-- /ko -->
+        <!-- ko if: !loading() -->
+        <table id="entryTable" class="table table-condensed table-nowrap">
+          <thead>
+            <tr>
+              <th>${ _("Namespace") }</th>
+            </tr>
+          </thead>
+          <tbody data-bind="foreach: namespaces">
+            <tr>
+              <td><a href="javascript: void(0);" data-bind="text: namespace.name, click: function () { $parent.namespace($data) }"></a></td>
+            </tr>
+          </tbody>
+        </table>
+        <!-- /ko -->
+      </div>
+    </div>
+  </div>
+</script>
+
 <script type="text/html" id="metastore-databases">
 <script type="text/html" id="metastore-databases">
   <div class="entries-table-container">
   <div class="entries-table-container">
     <div class="actionbar-actions">
     <div class="actionbar-actions">
@@ -664,7 +714,6 @@ ${ components.menubar(is_embeddable) }
   <!-- /ko -->
   <!-- /ko -->
 </script>
 </script>
 
 
-
 <script type="text/html" id="metastore-sample-tab">
 <script type="text/html" id="metastore-sample-tab">
   <!-- ko with: samples -->
   <!-- ko with: samples -->
   <!-- ko if: loading -->
   <!-- ko if: loading -->
@@ -683,7 +732,6 @@ ${ components.menubar(is_embeddable) }
   <!-- /ko -->
   <!-- /ko -->
 </script>
 </script>
 
 
-
 <script type="text/html" id="metastore-permissions-tab">
 <script type="text/html" id="metastore-permissions-tab">
   <div class="acl-panel-content" style="height: 988px;">
   <div class="acl-panel-content" style="height: 988px;">
     <div class="pull-right">
     <div class="pull-right">
@@ -984,17 +1032,7 @@ ${ components.menubar(is_embeddable) }
           <!-- ko ifnot: loading -->
           <!-- ko ifnot: loading -->
           <h1>
           <h1>
             <div class="inline-block pull-right">
             <div class="inline-block pull-right">
-              <!-- ko component: {
-                name: 'hue-drop-down',
-                params: {
-                  icon: 'fa-snowflake-o',
-                  value: activeNamespace,
-                  entries: namespaces,
-                  searchable: true,
-                  labelAttribute: 'name',
-                  linkTitle: 'Active context'
-                 }
-               } --><!-- /ko -->
+              <!-- ko with: namespace -->
               <!-- ko with: database -->
               <!-- ko with: database -->
               <!-- ko with: table -->
               <!-- ko with: table -->
               % if USE_NEW_EDITOR.get():
               % if USE_NEW_EDITOR.get():
@@ -1022,10 +1060,14 @@ ${ components.menubar(is_embeddable) }
               <!-- ko if: !database() -->
               <!-- ko if: !database() -->
               <a href="javascript: void(0);" class="btn btn-default" data-bind="click: reload" title="${_('Refresh')}"><i class="fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }"></i> ${_('Refresh')}</a>
               <a href="javascript: void(0);" class="btn btn-default" data-bind="click: reload" title="${_('Refresh')}"><i class="fa fa-refresh" data-bind="css: { 'fa-spin blue' : loading }"></i> ${_('Refresh')}</a>
               <!-- /ko -->
               <!-- /ko -->
+              <!-- /ko -->
             </div>
             </div>
 
 
             <!-- ko template: 'metastore-breadcrumbs' --><!-- /ko -->
             <!-- ko template: 'metastore-breadcrumbs' --><!-- /ko -->
           </h1>
           </h1>
+
+          <!-- ko template: { if: !loading() && namespace() === null, name: 'metastore-namespaces' } --><!-- /ko -->
+          <!-- ko with: namespace -->
           <!-- ko template: { if: !loading() && database() === null, name: 'metastore-databases' } --><!-- /ko -->
           <!-- ko template: { if: !loading() && database() === null, name: 'metastore-databases' } --><!-- /ko -->
           <!-- ko with: database -->
           <!-- ko with: database -->
           <i data-bind="visible: loading" class="fa fa-spinner fa-spin fa-2x margin-left-10" style="color: #999; display: none;"></i>
           <i data-bind="visible: loading" class="fa fa-spinner fa-spin fa-2x margin-left-10" style="color: #999; display: none;"></i>
@@ -1035,11 +1077,13 @@ ${ components.menubar(is_embeddable) }
           <!-- /ko -->
           <!-- /ko -->
           <!-- /ko -->
           <!-- /ko -->
           <!-- /ko -->
           <!-- /ko -->
+          <!-- /ko -->
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>
   </div>
   </div>
 
 
+  <!-- ko with: namespace() -->
   <div id="dropSingleTable" class="modal hide fade">
   <div id="dropSingleTable" class="modal hide fade">
     % if is_embeddable:
     % if is_embeddable:
     <form data-bind="submit: dropAndWatch" method="POST">
     <form data-bind="submit: dropAndWatch" method="POST">
@@ -1064,7 +1108,7 @@ ${ components.menubar(is_embeddable) }
       </div>
       </div>
     </form>
     </form>
   </div>
   </div>
-
+  <!-- /ko -->
   <div id="import-data-modal" class="modal hide fade" style="display: block;width: 680px;margin-left: -340px!important;"></div>
   <div id="import-data-modal" class="modal hide fade" style="display: block;width: 680px;margin-left: -340px!important;"></div>
 </div>
 </div>
 </span>
 </span>