Преглед изворни кода

[assist] Remove assist dependency on notebook and snippets

Johan Ahlen пре 10 година
родитељ
комит
573b2d35c4

+ 9 - 2
apps/beeswax/src/beeswax/templates/execute.mako

@@ -45,7 +45,14 @@ ${ layout.menubar(section='query') }
       <div class="tab-pane active" id="navigatorTab">
         <div class="card card-small card-tab">
           <div class="card-body" style="margin-top: 0;">
-            <div class="assist" data-bind="component: { name: 'assist-panel', params: { notebookViewModel: editorViewModel } }"></div>
+            <div class="assist" data-bind="component: {
+              name: 'assist-panel',
+              params: {
+                user: HIVE_AUTOCOMPLETE_USER,
+                sourceTypes: editorViewModel.sqlSourceTypes,
+                activeSourceType: snippetType
+              }
+            }"></div>
           </div>
         </div>
       </div>
@@ -1137,7 +1144,7 @@ editorViewModelOptions.snippetViewSettings[snippetType] = {
 
 editorViewModelOptions.languages.push({
   type: snippetType,
-  name: snippetType
+  name: HIVE_AUTOCOMPLETE_APP == "impala" ? "Impala" : "Hive"
 });
 
 var i18n = {

+ 134 - 97
desktop/core/src/desktop/static/desktop/js/assist/assistEntry.js

@@ -22,6 +22,21 @@
   }
 }(this, function (ko) {
 
+  /**
+   * @param {Object} definition
+   * @param {string} definition.type
+   * @param {string} definition.name
+   * @param {boolean} definition.isColumn
+   * @param {boolean} definition.isTable
+   * @param {boolean} definition.isMapValue
+   * @param {boolean} definition.isArray
+   * @param {AssistEntry} parent
+   * @param {AssistSource} assistSource
+   * @param {function} [filter] - ko.observable
+   * @param {Object} i18n
+   * @param {string} i18n.errorLoadingTablePreview
+   * @constructor
+   */
   function AssistEntry (definition, parent, assistSource, filter, i18n) {
     var self = this;
     self.i18n = i18n;
@@ -30,6 +45,7 @@
     self.assistSource = assistSource;
     self.parent = parent;
     self.filter = filter;
+    self.isSearchVisible = ko.observable(false);
 
     self.expandable = typeof definition.type === "undefined" || definition.type === "struct" || definition.type === "array" || definition.type === "map";
 
@@ -87,7 +103,7 @@
           break;
         }
         if (entry.definition.isArray || entry.definition.isMapValue) {
-          if (self.assistSource.assistHelper.type === 'hive') {
+          if (self.assistSource.type === 'hive') {
             parts.push("[]");
           }
         } else {
@@ -110,100 +126,115 @@
     self.loading(true);
     self.entries([]);
 
-    // Defer this part to allow ko to react on empty entries and loading
-    window.setTimeout(function() {
-      self.assistSource.assistHelper.fetchPanelData(self.assistSource.snippet, self.getHierarchy(), function(data) {
-        if (typeof data.tables !== "undefined") {
-          self.entries($.map(data.tables, function(tableName) {
-            return self.createEntry({
-              name: tableName,
-              displayName: tableName,
-              title: tableName,
-              isTable: true
-            });
-          }));
-        } else if (typeof data.extended_columns !== "undefined" && data.extended_columns !== null) {
-          self.entries($.map(data.extended_columns, function (columnDef) {
-            var displayName = columnDef.name;
-            if (typeof columnDef.type !== "undefined" && columnDef.type !== null) {
-              displayName += ' (' + columnDef.type + ')'
-            }
-            var title = displayName;
-            if (typeof columnDef.comment !== "undefined" && columnDef.comment !== null) {
-              title += ' ' + columnDef.comment;
-            }
-            var shortType = null;
-            if (typeof columnDef.type !== "undefined" && columnDef.type !== null) {
-              shortType = columnDef.type.match(/^[^<]*/g)[0]; // everything before '<'
-            }
-            return self.createEntry({
-              name: columnDef.name,
-              displayName: displayName,
-              title: title,
-              isColumn: true,
-              type: shortType
-            });
-          }));
-        } else if (typeof data.columns !== "undefined" && data.columns !== null) {
-          self.entries($.map(data.columns, function(columnName) {
+    var successCallback = function(data) {
+      if (typeof data.tables !== "undefined") {
+        self.entries($.map(data.tables, function(tableName) {
+          return self.createEntry({
+            name: tableName,
+            displayName: tableName,
+            title: tableName,
+            isTable: true
+          });
+        }));
+      } else if (typeof data.extended_columns !== "undefined" && data.extended_columns !== null) {
+        self.entries($.map(data.extended_columns, function (columnDef) {
+          var displayName = columnDef.name;
+          if (typeof columnDef.type !== "undefined" && columnDef.type !== null) {
+            displayName += ' (' + columnDef.type + ')'
+          }
+          var title = displayName;
+          if (typeof columnDef.comment !== "undefined" && columnDef.comment !== null) {
+            title += ' ' + columnDef.comment;
+          }
+          var shortType = null;
+          if (typeof columnDef.type !== "undefined" && columnDef.type !== null) {
+            shortType = columnDef.type.match(/^[^<]*/g)[0]; // everything before '<'
+          }
+          return self.createEntry({
+            name: columnDef.name,
+            displayName: displayName,
+            title: title,
+            isColumn: true,
+            type: shortType
+          });
+        }));
+      } else if (typeof data.columns !== "undefined" && data.columns !== null) {
+        self.entries($.map(data.columns, function(columnName) {
+          return self.createEntry({
+            name: columnName,
+            displayName: columnName,
+            title: columnName,
+            isColumn: true
+          });
+        }));
+      } else if (typeof data.type !== "undefined" && data.type !== null) {
+        if (data.type === "map") {
+          self.entries([
+            self.createEntry({
+              name: "key",
+              displayName: "key (" + data.key.type + ")",
+              title: "key (" + data.key.type + ")",
+              type: data.key.type
+            }),
+            self.createEntry({
+              name: "value",
+              displayName: "value (" + data.value.type + ")",
+              title: "value (" + data.value.type + ")",
+              isMapValue: true,
+              type: data.value.type
+            })
+          ]);
+          self.entries()[1].open(true);
+        } else if (data.type == "struct") {
+          self.entries($.map(data.fields, function(field) {
             return self.createEntry({
-              name: columnName,
-              displayName: columnName,
-              title: columnName,
-              isColumn: true
+              name: field.name,
+              displayName: field.name + " (" + field.type + ")",
+              title: field.name + " (" + field.type + ")",
+              type: field.type
             });
           }));
-        } else if (typeof data.type !== "undefined" && data.type !== null) {
-          if (data.type === "map") {
-            self.entries([
-              self.createEntry({
-                name: "key",
-                displayName: "key (" + data.key.type + ")",
-                title: "key (" + data.key.type + ")",
-                type: data.key.type
-              }),
-              self.createEntry({
-                name: "value",
-                displayName: "value (" + data.value.type + ")",
-                title: "value (" + data.value.type + ")",
-                isMapValue: true,
-                type: data.value.type
-              })
-            ]);
-            self.entries()[1].open(true);
-          } else if (data.type == "struct") {
-            self.entries($.map(data.fields, function(field) {
-              return self.createEntry({
-                name: field.name,
-                displayName: field.name + " (" + field.type + ")",
-                title: field.name + " (" + field.type + ")",
-                type: field.type
-              });
-            }));
-          } else if (data.type == "array") {
-            self.entries([
-              self.createEntry({
-                name: "item",
-                displayName: "item (" + data.item.type + ")",
-                title: "item (" + data.item.type + ")",
-                isArray: true,
-                type: data.item.type
-              })
-            ]);
-            self.entries()[0].open(true);
-          }
+        } else if (data.type == "array") {
+          self.entries([
+            self.createEntry({
+              name: "item",
+              displayName: "item (" + data.item.type + ")",
+              title: "item (" + data.item.type + ")",
+              isArray: true,
+              type: data.item.type
+            })
+          ]);
+          self.entries()[0].open(true);
         }
-        self.loading(false);
-      }, function() {
-        self.assistSource.hasErrors(true);
-        self.loading(false);
-      });
-    }, 10);
+      }
+      self.loading(false);
+    };
+
+    var errorCallback = function () {
+      self.assistSource.hasErrors(true);
+      self.loading(false);
+    };
+
+    self.assistSource.assistHelper.fetchPanelData({
+      sourceType: self.assistSource.type,
+      hierarchy: self.getHierarchy(),
+      successCallback: successCallback,
+      errorCallback: errorCallback
+    });
   };
 
-  AssistEntry.prototype.createEntry = function(definition) {
+  /**
+   * @param {Object} definition
+   * @param {string} definition.type
+   * @param {string} definition.name
+   * @param {boolean} definition.isColumn
+   * @param {boolean} definition.isTable
+   * @param {boolean} definition.isMapValue
+   * @param {boolean} definition.isArray
+   */
+  AssistEntry.prototype.createEntry = function (definition) {
     var self = this;
-    return new AssistEntry(definition, self, self.assistSource, null)
+    return new AssistEntry(definition, self, self.assistSource, null, self.i18n)
   };
 
   AssistEntry.prototype.getHierarchy = function () {
@@ -218,7 +249,7 @@
     return parts;
   };
 
-  AssistEntry.prototype.dblClick = function (data, event) {
+  AssistEntry.prototype.dblClick = function () {
     var self = this;
     huePubSub.publish('assist.dblClickItem', self);
   };
@@ -237,17 +268,23 @@
     var tableName = hierarchy[1];
 
     $assistQuickLook.find(".tableName").text(self.definition.name);
-    $assistQuickLook.find(".tableLink").attr("href", "/metastore/table/" + self.assistSource.assistHelper.activeDatabase() + "/" + tableName);
+    $assistQuickLook.find(".tableLink").attr("href", "/metastore/table/" + databaseName + "/" + tableName);
     $assistQuickLook.find(".sample").empty("");
     $assistQuickLook.attr("style", "width: " + ($(window).width() - 120) + "px;margin-left:-" + (($(window).width() - 80) / 2) + "px!important;");
 
-    self.assistSource.assistHelper.fetchTableHtmlPreview(self.assistSource.snippet, tableName, function(data) {
-      $assistQuickLook.find(".loader").hide();
-      $assistQuickLook.find(".sample").html(data);
-    }, function(e) {
-      if (e.status == 500) {
-        $(document).trigger("error", self.i18n.errorLoadingTablePreview);
-        $("#assistQuickLook").modal("hide");
+    self.assistSource.assistHelper.fetchTableHtmlPreview({
+      sourceType: self.assistSource.type,
+      databaseName: databaseName,
+      tableName: tableName,
+      successCallback: function(data) {
+        $assistQuickLook.find(".loader").hide();
+        $assistQuickLook.find(".sample").html(data);
+      },
+      errorCallback: function(e) {
+        if (e.status == 500) {
+          $(document).trigger("error", self.i18n.errorLoadingTablePreview);
+          $("#assistQuickLook").modal("hide");
+        }
       }
     });
 

+ 143 - 110
desktop/core/src/desktop/static/desktop/js/assist/assistHelper.js

@@ -23,208 +23,241 @@
 }(this, function (ko) {
 
   var TIME_TO_LIVE_IN_MILLIS = 86400000; // 1 day
-  var NOTEBOOK_API_PREFIX = "/notebook/api/autocomplete/";
+  var API_PREFIX = "/notebook/api/autocomplete/";
 
   /**
-   * @param options {object}
-   * @param options.notebook
-   * @param options.user
-   * @param options.activeDatabase
+   * @param {Object} i18n
+   * @param {string} i18n.errorLoadingDatabases
+   * @param {string} user
    *
    * @constructor
    */
-  function AssistHelper (options, i18n) {
+  function AssistHelper (i18n, user) {
     var self = this;
     self.i18n = i18n;
-    self.activeDatabase = ko.observable();
-    self.initialDatabase = options.activeDatabase;
-    self.notebook = options.notebook;
-    self.user = options.user;
-    self.availableDatabases = ko.observableArray();
-    self.loaded = ko.observable(false);
-    self.loading = ko.observable(false);
-    self.type = null;
-    self.activeDatabase.subscribe(function (newValue) {
-      if (self.loaded()) {
-        $.totalStorage("hue.assist.lastSelectedDb." + self.getTotalStorageUserPrefix(), newValue);
-      }
-    });
+    self.user = user;
+    self.lastKnownDatabases = [];
   }
 
-  AssistHelper.prototype.load = function (snippet, callback) {
+  AssistHelper.prototype.hasExpired = function (timestamp) {
+    return (new Date()).getTime() - timestamp > TIME_TO_LIVE_IN_MILLIS;
+  };
+
+  /**
+   * @param {string} sourceType
+   * @returns {string}
+   */
+  AssistHelper.prototype.getTotalStorageUserPrefix = function (sourceType) {
+    var self = this;
+    return sourceType + "_" + self.user;
+  };
+
+  AssistHelper.prototype.clearCache = function (sourceType) {
     var self = this;
-    if (self.loading()) {
-      return;
-    }
-    self.type = snippet.type();
-    self.loading(true);
-    self.loaded(false);
-    self.fetchAssistData(snippet, NOTEBOOK_API_PREFIX, function(data) {
+    $.totalStorage("hue.assist." + self.getTotalStorageUserPrefix(sourceType), {});
+  };
 
+  /**
+   * @param {Object} options
+   * @param {string} options.sourceType
+   * @param {function} options.callback
+   */
+  AssistHelper.prototype.loadDatabases = function (options) {
+    var self = this;
+
+    self.fetchAssistData(options.sourceType, API_PREFIX, function(data) {
       var databases = data.databases || [];
       // Blacklist of system databases
-      self.availableDatabases($.grep(databases, function(database) { return database !== "_impala_builtins" }));
-
-      if ($.inArray(self.activeDatabase(), self.availableDatabases()) === -1) {
-        // Defer this, select2 will update the activeDatabase to undefined when loading so this should make sure we set
-        // it afterwards.
-        window.setTimeout(function() {
-          var lastSelectedDb = $.totalStorage("hue.assist.lastSelectedDb." + self.getTotalStorageUserPrefix());
-          if ($.inArray(self.initialDatabase, self.availableDatabases()) > -1) {
-            self.activeDatabase(self.initialDatabase);
-          } else if ($.inArray(lastSelectedDb, self.availableDatabases()) > -1) {
-            self.activeDatabase(lastSelectedDb);
-          } else if ($.inArray("default", self.availableDatabases()) > -1) {
-            self.activeDatabase("default");
-          } else if (self.availableDatabases().length > 0) {
-            self.activeDatabase(self.availableDatabases()[0]);
-          }
-        }, 1);
-      }
-
-      self.loaded(true);
-      self.loading(false);
-      if (callback) {
-        callback();
-      }
+      self.lastKnownDatabases = $.grep(databases, function(database) {
+        return database !== "_impala_builtins";
+      });
+      options.callback(self.lastKnownDatabases);
     }, function (message) {
-      self.loaded(true);
-      self.loading(false);
       if (message.status == 401) {
-        $(document).trigger("showAuthModal", {'type': self.type, 'callback': function() { self.load(snippet, callback) }});
+        $(document).trigger("showAuthModal", {'type': options.sourceType, 'callback': function() {
+          self.loadDatabases(options);
+        }});
       } else if (message.statusText) {
         $(document).trigger("error", self.i18n.errorLoadingDatabases + ":" + message.statusText);
+      } else if (message.message) {
+        $(document).trigger("error", self.i18n.errorLoadingDatabases + ":" + message.message);
       } else if (message) {
         $(document).trigger("error", message);
       } else {
         $(document).trigger("error", self.i18n.errorLoadingDatabases + ".");
       }
-      if (callback) {
-        callback();
-      }
+      self.lastKnownDatabases = [];
+      options.callback([]);
     });
   };
 
-  AssistHelper.prototype.hasExpired = function (timestamp) {
-    return (new Date()).getTime() - timestamp > TIME_TO_LIVE_IN_MILLIS;
-  };
-
-  AssistHelper.prototype.getTotalStorageUserPrefix = function () {
-    var self = this;
-    return self.type + "_" + self.user;
-  };
-
-  AssistHelper.prototype.fetchTableHtmlPreview = function(snippet, tableName, successCallback, errorCallback) {
-    var self = this;
-    var app = snippet.type() == "hive" ? "beeswax" : snippet.type();
+  /**
+   * @param {Object} options
+   * @param {string} options.sourceType
+   * @param {string} options.databaseName
+   * @param {string} options.tableName
+   * @param {function} options.successCallback
+   * @param {function} options.errorCallback
+   */
+  AssistHelper.prototype.fetchTableHtmlPreview = function (options) {
     $.ajax({
-      url: "/" + app + "/api/table/" + self.activeDatabase() + "/" + tableName,
+      url: "/" + options.sourceType + "/api/table/" + options.databaseName + "/" + options.tableName,
       data: { "sample": true },
       beforeSend: function (xhr) {
         xhr.setRequestHeader("X-Requested-With", "Hue");
       },
       dataType: "html",
-      success: successCallback,
-      error: errorCallback
+      success: options.successCallback,
+      error: options.errorCallback
     });
   };
 
-  AssistHelper.prototype.refreshTableStats = function(snippet, tableName, columnName, successCallback, errorCallback) {
-    var self = this;
+  /**
+   * @param {Object} options
+   * @param {string} options.sourceType
+   * @param {string} options.databaseName
+   * @param {string} options.tableName
+   * @param {string} options.columnName
+   * @param {function} options.successCallback
+   * @param {function} options.errorCallback
+   */
+  AssistHelper.prototype.refreshTableStats = function (options) {
     var pollRefresh = function (url) {
       $.post(url, function (data) {
         if (data.isSuccess) {
-          successCallback(data);
+          options.successCallback(data);
         } else if (data.isFailure) {
-          errorCallback(data.message);
+          options.errorCallback(data.message);
         } else {
           window.setTimeout(function () {
             pollRefresh(url);
           }, 1000);
         }
-      }).fail(errorCallback);
+      }).fail(options.errorCallback);
     };
 
-    var app = snippet.type() == "hive" ? "beeswax" : snippet.type();
-    $.post("/" + app + "/api/analyze/" + self.activeDatabase() + "/" + tableName + "/"  + (columnName || ""), function (data) {
+    $.post("/" + options.sourceType + "/api/analyze/" + options.databaseName + "/" + options.tableName + "/"  + (options.columnName || ""), function (data) {
       if (data.status == 0 && data.watch_url) {
         pollRefresh(data.watch_url);
       } else {
-        errorCallback(data.message);
+        options.errorCallback(data.message);
       }
-    }).fail(errorCallback);
+    }).fail(options.errorCallback);
   };
 
-  AssistHelper.prototype.fetchStats = function(snippet, tableName, columnName, successCallback, errorCallback) {
-    var self = this;
-    var app = snippet.type() == "hive" ? "beeswax" : snippet.type();
+  /**
+   * @param {Object} options
+   * @param {string} options.sourceType
+   * @param {string} options.databaseName
+   * @param {string} options.tableName
+   * @param {string} options.columnName
+   * @param {function} options.successCallback
+   * @param {function} options.errorCallback
+   */
+  AssistHelper.prototype.fetchStats = function (options) {
     $.ajax({
-      url: "/" + app + "/api/table/" + self.activeDatabase() + "/" + tableName + "/stats/" + (columnName || ""),
+      url: "/" + options.sourceType + "/api/table/" + options.databaseName + "/" + options.tableName + "/stats/" + ( options.columnName || ""),
       data: {},
       beforeSend: function (xhr) {
         xhr.setRequestHeader("X-Requested-With", "Hue");
       },
       dataType: "json",
-      success: successCallback,
-      error: errorCallback
+      success: options.successCallback,
+      error: options.errorCallback
     });
   };
 
-  AssistHelper.prototype.fetchTerms = function(snippet, tableName, columnName, prefixFilter, successCallback, errorCallback) {
-    var self = this;
-    var app = snippet.type() == "hive" ? "beeswax" : snippet.type();
+  /**
+   * @param {Object} options
+   * @param {Object} [options.prefixFilter]
+   * @param {string} options.sourceType
+   * @param {string} options.databaseName
+   * @param {string} options.tableName
+   * @param {string} options.columnName
+   * @param {function} options.successCallback
+   * @param {function} options.errorCallback
+   */
+  AssistHelper.prototype.fetchTerms = function (options) {
     $.ajax({
-      url: "/" + app + "/api/table/" + self.activeDatabase() + "/" + tableName + "/terms/" + columnName + "/" + (prefixFilter || ""),
+      url: "/" + options.sourceType + "/api/table/" + options.databaseName + "/" + options.tableName + "/terms/" + options.columnName + "/" + (options.prefixFilter || ""),
       data: {},
       beforeSend: function (xhr) {
         xhr.setRequestHeader("X-Requested-With", "Hue");
       },
       dataType: "json",
-      success: successCallback,
-      error: errorCallback
+      success: options.successCallback,
+      error: options.errorCallback
     });
   };
 
-  AssistHelper.prototype.fetchTables = function(snippet, database, successCallback, errorCallback) {
+  /**
+   * @param {Object} options
+   * @param {string} options.sourceType
+   * @param {string} options.databaseName
+   * @param {function} options.successCallback
+   * @param {function} options.errorCallback
+   */
+  AssistHelper.prototype.fetchTables = function (options) {
     var self = this;
-    self.fetchAssistData(snippet, NOTEBOOK_API_PREFIX + database, successCallback, errorCallback);
+    self.fetchAssistData(options.sourceType, API_PREFIX + options.databaseName, options.successCallback, options.errorCallback);
   };
 
-  AssistHelper.prototype.fetchFields = function(snippet, database, tableName, fields, successCallback, errorCallback, editor) {
+  /**
+   * @param {Object} options
+   * @param {string} options.sourceType
+   * @param {string} options.databaseName
+   * @param {string} options.tableName
+   * @param {string[]} options.fields
+   * @param {Object} [options.editor] - Ace editor
+   * @param {function} options.successCallback
+   * @param {function} options.errorCallback
+   */
+  AssistHelper.prototype.fetchFields = function (options) {
     var self = this;
-
     var fieldPart = fields.length > 0 ? "/" + fields.join("/") : "";
-    self.fetchAssistData(snippet, NOTEBOOK_API_PREFIX + database + "/" + tableName + fieldPart, successCallback, errorCallback, editor);
+    self.fetchAssistData(options.sourceType, API_PREFIX + options.databaseName + "/" + options.tableName + fieldPart, options.successCallback, options.errorCallback, options.editor);
   };
 
-  AssistHelper.prototype.clearCache = function(snippet) {
-    var self = this;
-    $.totalStorage("hue.assist." + self.getTotalStorageUserPrefix(), {});
-  };
-
-  AssistHelper.prototype.fetchPanelData = function (snippet, hierarchy, successCallback, errorCallback) {
+  /**
+   * @param {Object} options
+   * @param {string} options.sourceType
+   * @param {string[]} options.hierarchy
+   * @param {function} options.successCallback
+   * @param {function} options.errorCallback
+   */
+  AssistHelper.prototype.fetchPanelData = function (options) {
     var self = this;
-    self.fetchAssistData(snippet, NOTEBOOK_API_PREFIX + hierarchy.join("/"), successCallback, errorCallback);
+    self.fetchAssistData(options.sourceType, API_PREFIX + options.hierarchy.join("/"), options.successCallback, options.errorCallback);
   };
 
-  AssistHelper.prototype.fetchAssistData = function (snippet, url, successCallback, errorCallback, editor) {
+  /**
+   * @param {string} sourceType
+   * @param {string} url
+   * @param {function} successCallback
+   * @param {function} errorCallback
+   * @param {Object} [editor] - Ace editor
+   */
+  AssistHelper.prototype.fetchAssistData = function (sourceType, url, successCallback, errorCallback, editor) {
     var self = this;
-    var cachedData = $.totalStorage("hue.assist." + self.getTotalStorageUserPrefix()) || {};
+    if (!sourceType) { return };
+    var cachedData = $.totalStorage("hue.assist." + self.getTotalStorageUserPrefix(sourceType)) || {};
 
     if (typeof cachedData[url] == "undefined" || self.hasExpired(cachedData[url].timestamp)) {
       if (editor) {
         editor.showSpinner();
       }
       $.post(url, {
-        notebook: ko.mapping.toJSON(self.notebook.getContext()),
-        snippet: ko.mapping.toJSON(snippet.getContext())
+        notebook: {},
+        snippet: ko.mapping.toJSON({
+          type: sourceType
+        })
       }, function (data) {
         if (data.status == 0) {
           cachedData[url] = {
             timestamp: (new Date()).getTime(),
             data: data
           };
-          $.totalStorage("hue.assist." + self.getTotalStorageUserPrefix(), cachedData);
+          $.totalStorage("hue.assist." + self.getTotalStorageUserPrefix(sourceType), cachedData);
           successCallback(data);
         } else {
           errorCallback(data);

+ 32 - 58
desktop/core/src/desktop/static/desktop/js/assist/assistSource.js

@@ -22,12 +22,20 @@
   }
 }(this, function (ko, AssistEntry) {
 
-  function AssistSource(snippet, i18n) {
+  /**
+   * @param {Object} options
+   * @param {Object} options.i18n
+   * @param {AssistHelper} options.assistHelper
+   * @param {string} options.type
+   * @param {string} options.name
+   * @constructor
+   */
+  function AssistSource (options) {
     var self = this;
-    self.name = snippet.name();
-    self.i18n = i18n;
-    self.snippet = snippet;
-    self.assistHelper = snippet.getAssistHelper();
+    self.i18n = options.i18n;
+    self.assistHelper = options.assistHelper;
+    self.type = options.type;
+    self.name = options.name;
 
     self.hasErrors = ko.observable(false);
     self.simpleStyles = ko.observable(false);
@@ -38,16 +46,11 @@
       return self.filter().length !== 0;
     });
 
-    self.options = ko.mapping.fromJS($.extend({
-      isSearchVisible: false
-    }, $.totalStorage(snippet.type() + ".assist.options") || {}));
+    var storageSearchVisible = $.totalStorage(self.type + ".assist.searchVisible");
+    self.searchVisible = ko.observable(storageSearchVisible || false);
 
-    $.each(Object.keys(self.options), function (index, key) {
-      if (ko.isObservable(self.options[key])) {
-        self.options[key].subscribe(function() {
-          $.totalStorage(snippet.type() + ".assist.options", ko.mapping.toJS(self.options))
-        });
-      }
+    self.searchVisible.subscribe(function (newValue) {
+      $.totalStorage(self.type + ".assist.searchVisible", newValue);
     });
 
     self.databases = ko.observableArray();
@@ -65,8 +68,10 @@
       }
     });
 
+    self.loading = ko.observable(false);
     var dbIndex = {};
     var updateDatabases = function (names) {
+      var lastSelectedDb = self.selectedDatabase() ? self.selectedDatabase().definition.name : null;
       dbIndex = {};
       self.databases($.map(names, function(name) {
         var database = new AssistEntry({
@@ -76,42 +81,23 @@
           isDatabase: true
         }, null, self, self.filter, self.i18n);
         dbIndex[name] = database;
+        if (name === lastSelectedDb) {
+          self.selectedDatabase(database);
+        }
         return database;
       }));
+      self.reloading(false);
+      self.loading(false);
     };
 
-    if (dbIndex[self.assistHelper.activeDatabase()]) {
-      self.selectedDatabase(dbIndex[self.assistHelper.activeDatabase()]);
-    }
-
-    var subscribed = false;
-
-    var updateDbFromAssistHelper = function () {
-      var assistDb = self.assistHelper.activeDatabase();
-      if (dbIndex[assistDb] && (! self.selectedDatabase() || self.selectedDatabase().definition.name !== assistDb)) {
-        self.selectedDatabase(dbIndex[assistDb]);
-      }
-    };
-
-    self.assistHelper.activeDatabase.subscribe(function (newValue) {
-      updateDbFromAssistHelper();
-    });
-
-    var initDatabases = function () {
-      if (self.assistHelper.loaded()) {
-        updateDatabases(self.assistHelper.availableDatabases());
-        updateDbFromAssistHelper();
-      }
+    self.initDatabases = function () {
+      self.loading(true);
+      self.assistHelper.loadDatabases({
+        sourceType: self.type,
+        callback: updateDatabases
+      });
     };
 
-    initDatabases();
-
-    self.selectedDatabase.subscribe(function (newDatabase) {
-      if (newDatabase !== null) {
-        self.assistHelper.activeDatabase(newDatabase.definition.name);
-      }
-    });
-
     self.modalItem = ko.observable();
 
     self.repositionActions = function(data, event) {
@@ -122,21 +108,9 @@
     };
 
     self.reload = function() {
-      var lastSelectedDb = self.selectedDatabase() ? self.selectedDatabase().definition.name : null;
       self.reloading(true);
-      self.assistHelper.clearCache(self.snippet);
-      self.assistHelper.load(self.snippet, function() {
-        if (self.assistHelper.loaded()) {
-          updateDatabases(self.assistHelper.availableDatabases());
-          if (lastSelectedDb !== null) {
-            self.selectedDatabase(dbIndex[lastSelectedDb]);
-          } else {
-            self.selectedDatabase(null);
-          }
-        }
-
-        self.reloading(false);
-      });
+      self.assistHelper.clearCache(self.type);
+      self.initDatabases();
     };
 
     huePubSub.subscribe('assist.refresh', self.reload);

+ 96 - 55
desktop/core/src/desktop/static/desktop/js/assist/tableStats.js

@@ -22,10 +22,25 @@
   }
 }(this, function (ko) {
 
+  /**
+   *
+   * @param {Object} options
+   * @param {Object} options.i18n
+   * @param {string} options.i18n.errorLoadingStats
+   * @param {string} options.i18n.errorLoadingTerms
+   * @param {string} options.i18n.errorRefreshingStats
+   * @param {AssistHelper} options.assistHelper
+   * @param {string} options.sourceType
+   * @param {string} options.databaseName
+   * @param {string} options.tableName
+   * @param {string} options.columnName
+   * @param {string} options.type
+   * @constructor
+   */
   function TableStats (options) {
     var self = this;
     self.i18n = options.i18n;
-    self.snippet = options.snippet;
+    self.sourceType = options.sourceType;
     self.database = options.databaseName;
     self.table = options.tableName;
     self.column = options.columnName;
@@ -60,33 +75,44 @@
     var self = this;
     self.loading(true);
     self.hasError(false);
-    self.assistHelper.fetchStats(self.snippet, self.table, self.column != null ? self.column : null, function (data) {
-          if (data && data.status == 0) {
-            self.statRows(data.stats);
-            var inaccurate = true;
-            for(var i = 0; i < data.stats.length; i++) {
-              if (data.stats[i].data_type == "COLUMN_STATS_ACCURATE" && data.stats[i].comment == "true") {
-                inaccurate = false;
-                break;
-              }
-            }
-            self.inaccurate(inaccurate);
-          } else if (data && data.message) {
-            $(document).trigger("error", data.message);
-            self.hasError(true);
-          } else {
-            $(document).trigger("error", self.i18n.errorLoadingStats);
-            self.hasError(true);
-          }
-          self.loading(false);
-        },
-        function (e) {
-          if (e.status == 500) {
-            $(document).trigger("error", self.i18n.errorLoadingStats);
+
+    var successCallback = function (data) {
+      if (data && data.status == 0) {
+        self.statRows(data.stats);
+        var inaccurate = true;
+        for(var i = 0; i < data.stats.length; i++) {
+          if (data.stats[i].data_type == "COLUMN_STATS_ACCURATE" && data.stats[i].comment == "true") {
+            inaccurate = false;
+            break;
           }
-          self.hasError(true);
-          self.loading(false);
-        });
+        }
+        self.inaccurate(inaccurate);
+      } else if (data && data.message) {
+        $(document).trigger("error", data.message);
+        self.hasError(true);
+      } else {
+        $(document).trigger("error", self.i18n.errorLoadingStats);
+        self.hasError(true);
+      }
+      self.loading(false);
+    };
+
+    var errorCallback = function (e) {
+      if (e.status == 500) {
+        $(document).trigger("error", self.i18n.errorLoadingStats);
+      }
+      self.hasError(true);
+      self.loading(false);
+    };
+
+    self.assistHelper.fetchStats({
+      sourceType: self.sourceType,
+      databaseName: self.database,
+      tableName: self.table,
+      columnName: self.column,
+      successCallback: successCallback,
+      errorCallback: errorCallback
+    });
   };
 
   TableStats.prototype.refresh = function () {
@@ -97,45 +123,60 @@
     var shouldFetchTerms = self.termsTabActive() || self.terms().length > 0;
     self.refreshing(true);
 
-    self.assistHelper.refreshTableStats(self.snippet, self.table, self.column, function() {
-      self.refreshing(false);
-      self.fetchData();
-      if (shouldFetchTerms) {
-        self.fetchTerms();
+    self.assistHelper.refreshTableStats({
+      sourceType: self.sourceType,
+      databaseName: self.database,
+      tableName: self.table,
+      columnName: self.column,
+      successCallback: function() {
+        self.refreshing(false);
+        self.fetchData();
+        if (shouldFetchTerms) {
+          self.fetchTerms();
+        }
+      },
+      errorCallback: function(message) {
+        self.refreshing(false);
+        $(document).trigger("error", message || self.i18n.errorRefreshingStats);
       }
-    }, function(message) {
-      self.refreshing(false);
-      $(document).trigger("error", message || self.i18n.errorRefreshingStats);
     });
   };
 
   TableStats.prototype.fetchTerms = function () {
     var self = this;
-    if (self.column == null || (self.isComplexType && self.snippet.type() == "impala")) {
+    if (self.column == null || (self.isComplexType && self.sourceType == "impala")) {
       return;
     }
 
     self.loadingTerms(true);
-    self.assistHelper.fetchTerms(self.snippet, self.table, self.column, self.prefixFilter(), function (data) {
-      if (data && data.status == 0) {
-        self.terms($.map(data.terms, function (term) {
-          return {
-            name: term[0],
-            count: term[1],
-            percent: (parseFloat(term[1]) / parseFloat(data.terms[0][1])) * 100
-          }
-        }));
-      } else if (data && data.message) {
-        $(document).trigger("error", data.message);
-      } else {
-        $(document).trigger("error", self.i18n.errorLoadingTerms);
-      }
-      self.loadingTerms(false);
-    }, function (e) {
-      if (e.status == 500) {
-        $(document).trigger("error", self.i18n.errorLoadingTerms);
+    self.assistHelper.fetchTerms({
+      sourceType: self.sourceType,
+      databaseName: self.database,
+      tableName: self.table,
+      columnName: self.column,
+      prefixFilter: self.prefixFilter(),
+      successCallback: function (data) {
+        if (data && data.status == 0) {
+          self.terms($.map(data.terms, function (term) {
+            return {
+              name: term[0],
+              count: term[1],
+              percent: (parseFloat(term[1]) / parseFloat(data.terms[0][1])) * 100
+            }
+          }));
+        } else if (data && data.message) {
+          $(document).trigger("error", data.message);
+        } else {
+          $(document).trigger("error", self.i18n.errorLoadingTerms);
+        }
+        self.loadingTerms(false);
+      },
+      errorCallback: function (e) {
+        if (e.status == 500) {
+          $(document).trigger("error", self.i18n.errorLoadingTerms);
+        }
+        self.loadingTerms(false);
       }
-      self.loadingTerms(false);
     });
   };
 

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

@@ -1903,15 +1903,20 @@
 
       var refreshTables = function() {
         currentAssistTables = {};
-        if (typeof assistHelper.activeDatabase() !== "undefined" && assistHelper.activeDatabase() != null) {
-          assistHelper.fetchTables(snippet, assistHelper.activeDatabase(), function(data) {
-            $.each(data.tables, function(index, table) {
-              currentAssistTables[table] = true;
-            });
-          })
+        if (snippet.database()) {
+          assistHelper.fetchTables({
+            sourceType: snippet.type(),
+            databaseName: snippet.database(),
+            successCallback: function(data) {
+              $.each(data.tables, function(index, table) {
+                currentAssistTables[table] = true;
+              });
+            },
+            errorCallback: $.noop
+          });
         }
       };
-      assistHelper.activeDatabase.subscribe(refreshTables);
+      snippet.database.subscribe(refreshTables);
       refreshTables();
 
       ace.define("huelink", [], function (require, exports, module) {

+ 26 - 15
desktop/core/src/desktop/static/desktop/js/sqlAutocompleter.js

@@ -25,9 +25,9 @@
   var SQL_TERMS = /\b(FROM|TABLE|STATS|REFRESH|METADATA|DESCRIBE|ORDER BY|JOIN|ON|WHERE|SELECT|LIMIT|GROUP BY|SORT|USE|LOCATION|INPATH)\b/g;
 
   /**
-   * @param options {object}
-   * @param options.assistHelper
-   *
+   * @param {Object} options
+   * @param {Snippet} options.snippet
+   * @param {HdfsAutocompleter} options.hdfsAutocompleter
    * @constructor
    */
   function SqlAutocompleter(options) {
@@ -35,10 +35,12 @@
     self.snippet = options.snippet;
     self.hdfsAutocompleter = options.hdfsAutocompleter;
 
+
     var initDatabases = function () {
-      if (! self.snippet.getAssistHelper().loaded()) {
-        self.snippet.getAssistHelper().load(self.snippet);
-      }
+      self.snippet.getAssistHelper().loadDatabases({
+        sourceType: self.snippet.type(),
+        callback: $.noop
+      });
     };
     self.snippet.type.subscribe(function() {
       if (self.snippet.isSqlDialect()) {
@@ -64,7 +66,8 @@
       var refs = $.map(refsRaw.split(/\s*(?:,|\bJOIN\b)\s*/i), function (ref) {
         if (ref.indexOf('.') > 0) {
           var refParts = ref.split('.');
-          if(self.snippet.getAssistHelper().availableDatabases().indexOf(refParts[0]) > -1) {
+
+          if(self.snippet.getAssistHelper().lastKnownDatabases.indexOf(refParts[0]) > -1) {
             return {
               database: refParts.shift(),
               table: refParts.join('.')
@@ -325,9 +328,7 @@
     var hiveSyntax = self.snippet.type() === "hive";
     var impalaSyntax = self.snippet.type() === "impala";
 
-    if (typeof self.snippet.getAssistHelper().activeDatabase() == "undefined"
-      || self.snippet.getAssistHelper().activeDatabase() == null
-      || self.snippet.getAssistHelper().activeDatabase() == "") {
+    if (! self.snippet.database()) {
       onFailure();
       return;
     }
@@ -343,7 +344,7 @@
       return;
     }
 
-    var database = self.snippet.getAssistHelper().activeDatabase();
+    var database = self.snippet.database();
     for (var i = allStatements.length - 1; i >= 0; i--) {
       var useMatch = allStatements[i].match(/\s*use\s+([^\s;]+)\s*;?/i);
       if (useMatch) {
@@ -584,9 +585,8 @@
             if (!isValueCompletion) {
               fields.push(part);
             }
-            // For impala we have to fetch info about each field as we don't know
-            // whether it's a map or array for hive the [ and ] gives it away...
-            self.snippet.getAssistHelper().fetchFields(self.snippet, database, tableName, fields, function(data) {
+
+            var successCallback = function (data) {
               if (data.type === "map") {
                 remainingParts.unshift("value");
               } else if (data.type === "array") {
@@ -616,7 +616,18 @@
                 return;
               }
               getFields(database, remainingParts, fields);
-            }, onFailure, editor);
+            };
+            // For impala we have to fetch info about each field as we don't know
+            // whether it's a map or array for hive the [ and ] gives it away...
+            self.snippet.getAssistHelper().fetchFields({
+              sourceType: self.snippet.type(),
+              databaseName: database,
+              tableName: tableName,
+              fields: fields,
+              editor: editor,
+              successCallback: successCallback,
+              errorCallback: onFailure
+            });
             return; // break recursion, it'll be async above
           }
           fields.push(part);

+ 45 - 59
desktop/core/src/desktop/templates/assist.mako

@@ -198,14 +198,14 @@ from desktop.views import _ko
         <a class="inactive-action" href="javascript:void(0)" data-bind="click: reload"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin' : reloading }" title="${_('Manually refresh the databases list')}"></i></a>
       </div>
     </li>
-    <li data-bind="visible: ! hasErrors() && ! assistHelper.loading()" >
+    <li data-bind="visible: ! hasErrors()" >
       <ul class="assist-tables" data-bind="foreach: databases">
         <li class="assist-table pointer">
           <a class="assist-column-link assist-table-link" href="javascript: void(0);" data-bind="text: definition.name, click: function () { $parent.selectedDatabase($data) }"></a>
         </li>
       </ul>
     </li>
-    <li class="center" data-bind="visible: assistHelper.loading()" >
+    <li class="center" data-bind="visible: loading" >
       <!--[if !IE]><!--><i class="fa fa-spinner fa-spin" style="font-size: 20px; color: #BBB"></i><!--<![endif]-->
       <!--[if IE]><img src="${ static('desktop/art/spinner.gif') }"/><![endif]-->
     </li>
@@ -216,25 +216,25 @@ from desktop.views import _ko
 
   <script type="text/html" id="assist-tables-template">
     <div data-bind="visibleOnHover: { selector: '.hover-actions', override: $parent.reloading }" style="position: relative; width:100%">
-      <li class="nav-header" style="margin-top: 0" data-bind="visible: !$parent.assistHelper.loading() && !$parent.hasErrors()">
+      <li class="nav-header" style="margin-top: 0" data-bind="visible: !$parent.loading() && !$parent.hasErrors()">
         ${_('tables')}
-        <div class="pull-right hover-actions" data-bind="visible: hasEntries() && !$parent.assistHelper.loading() && !$parent.hasErrors()">
+        <div class="pull-right hover-actions" data-bind="visible: hasEntries() && !$parent.loading() && !$parent.hasErrors()">
           <span class="assist-tables-counter">(<span data-bind="text: filteredEntries().length"></span>)</span>
-          <a class="inactive-action" href="javascript:void(0)" data-bind="click: function () { $parent.options.isSearchVisible(!$parent.options.isSearchVisible()) }, css: { 'blue' : $parent.options.isSearchVisible() }"><i class="pointer fa fa-search" title="${_('Search')}"></i></a>
+          <a class="inactive-action" href="javascript:void(0)" data-bind="click: function () { isSearchVisible(!isSearchVisible()) }, css: { 'blue' : isSearchVisible() }"><i class="pointer fa fa-search" title="${_('Search')}"></i></a>
           <a class="inactive-action" href="javascript:void(0)" data-bind="click: $parent.reload"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : $parent.reloading }" title="${_('Manually refresh the table list')}"></i></a>
         </div>
       </li>
 
-      <li data-bind="slideVisible: hasEntries() && $parent.options.isSearchVisible() && !$parent.assistHelper.loading() && !$parent.hasErrors()">
+      <li data-bind="slideVisible: hasEntries() && isSearchVisible() && !$parent.loading() && !$parent.hasErrors()">
         <div><input type="text" placeholder="${ _('Table name...') }" style="width:90%;" data-bind="value: filter, valueUpdate: 'afterkeydown'"/></div>
       </li>
 
       <div class="table-container">
-        <div class="center" data-bind="visible: loading() || $parent.assistHelper.loading()">
+        <div class="center" data-bind="visible: loading() || $parent.loading()">
           <!--[if !IE]><!--><i class="fa fa-spinner fa-spin" style="font-size: 20px; color: #BBB"></i><!--<![endif]-->
           <!--[if IE]><img src="${ static('desktop/art/spinner.gif') }"/><![endif]-->
         </div>
-        <!-- ko template: { ifnot: loading() || $parent.assistHelper.loading(), name: 'assist-entries' } --><!-- /ko -->
+        <!-- ko template: { ifnot: loading() || $parent.loading(), name: 'assist-entries' } --><!-- /ko -->
       </div>
     </div>
 
@@ -262,73 +262,59 @@ from desktop.views import _ko
   <script type="text/javascript" charset="utf-8">
     (function (factory) {
       if(typeof require === "function") {
-        require(['knockout', 'desktop/js/assist/assistSource'], factory);
+        require(['knockout', 'desktop/js/assist/assistSource', 'desktop/js/assist/assistHelper'], factory);
       } else {
-        factory(ko, AssistSource);
+        factory(ko, AssistSource, AssistHelper);
       }
-    }(function (ko, AssistSource) {
-
-      function AssistPanel(params) {
+    }(function (ko, AssistSource, AssistHelper) {
+
+      /**
+       * @param {Object} params
+       * @param {Object[]} params.sourceTypes - All the available SQL source types
+       * @param {string} params.sourceTypes[].name - Example: Hive SQL
+       * @param {string} params.sourceTypes[].type - Example: hive
+       * @param {string} [params.activeSourceType] - Example: hive
+       * @param {string} params.user
+       * @constructor
+       */
+      function AssistPanel (params) {
         var self = this;
         var i18n = {
+          errorLoadingDatabases: "${ _('There was a problem loading the databases') }",
           errorLoadingTablePreview: "${ _('There was a problem loading the table preview.') }"
         };
-        var notebookViewModel = params.notebookViewModel;
-        var notebook = notebookViewModel.selectedNotebook();
-
-        var sqlSources = [];
-        $.each(notebookViewModel.availableSnippets(), function (index, snippet) {
-          var settings = notebookViewModel.getSnippetViewSettings(snippet.type());
-
-          var fakeSnippet = {
-            name: snippet.name,
-            type: snippet.type,
-            getContext: function() {
-              return {
-                type: snippet.type()
-              }
-            },
-            getAssistHelper: function() {
-              return notebook.getAssistHelper(snippet.type());
-            }
-          };
 
-          if (settings.sqlDialect) {
-            sqlSources.push(new AssistSource(fakeSnippet, self, i18n));
-          }
+        var assistHelper = new AssistHelper(i18n, params.user);
+        self.sources = ko.observableArray();
+        var sourceIndex = {};
+        $.each(params.sourceTypes, function (idx, sourceType) {
+          sourceIndex[sourceType.type] = new AssistSource({
+            assistHelper: assistHelper,
+            i18n: i18n,
+            type: sourceType.type,
+            name: sourceType.name
+          });
+          self.sources.push(sourceIndex[sourceType.type]);
         });
 
-        self.sources = ko.observableArray(sqlSources);
+        var storageSourceType =  $.totalStorage("hue.assist.lastSelectedSource." + params.user);
         self.selectedSource = ko.observable(null);
 
-        self.selectedSource.subscribe(function (source) {
-          if (source && ! source.assistHelper.loaded()) {
-            source.assistHelper.load(source.snippet);
+        self.selectedSource.subscribe(function (newSource) {
+          if (newSource) {
+            newSource.initDatabases();
+            $.totalStorage("hue.assist.lastSelectedSource." + self.user, newSource.name);
+          } else {
+            $.totalStorage("hue.assist.lastSelectedSource." + self.user, null);
           }
         });
 
-        var lastSelectedSourceName =  $.totalStorage("hue.assist.lastSelectedSource." + notebookViewModel.user);
-        if (lastSelectedSourceName !== null) {
-          var foundSource = $.grep(self.sources(), function (source) {
-            return source.name === lastSelectedSourceName;
-          });
-          if (foundSource.length === 1) {
-            self.selectedSource(foundSource[0]);
-          }
+        if (params.activeSourceType) {
+          self.selectedSource(sourceIndex[params.activeSourceType]);
+        } else if (storageSourceType && sourceIndex[storageSourceType]) {
+          self.selectedSource(sourceIndex[storageSourceType]);
         }
 
-        if (! self.selectedSource() && self.sources.length === 1) {
-          self.selectedSource(self.sources[0]);
-        }
-
-        self.selectedSource.subscribe(function (newSourceType) {
-          if (newSourceType !== null) {
-            $.totalStorage("hue.assist.lastSelectedSource." + notebookViewModel.user, newSourceType.name);
-          } else {
-            $.totalStorage("hue.assist.lastSelectedSource." + notebookViewModel.user, null);
-          }
-        });
-
         self.breadcrumb = ko.computed(function () {
           if (self.selectedSource()) {
             if (self.selectedSource().selectedDatabase()) {

+ 18 - 7
desktop/libs/notebook/src/notebook/static/notebook/js/notebook.ko.js

@@ -172,6 +172,7 @@
       return notebook.getAssistHelper(self.type());
     };
 
+    self.database = ko.observable(typeof snippet.database != "undefined" && snippet.database != null ? snippet.database : null);
     self.statement_raw = ko.observable(typeof snippet.statement_raw != "undefined" && snippet.statement_raw != null ? snippet.statement_raw : '');
     self.selectedStatement = ko.observable('');
     self.codemirrorSize = ko.observable(typeof snippet.codemirrorSize != "undefined" && snippet.codemirrorSize != null ? snippet.codemirrorSize : 100);
@@ -691,7 +692,6 @@
       }
     });
 
-    self.selectedDatabases = notebook.selectedDatabases != "undefined" && notebook.selectedDatabases != null ? notebook.selectedDatabases : {};
     self.assistHelpers = {};
     self.history = ko.observableArray([]);
     self.showHistory = ko.observable(typeof notebook.showHistory != "undefined" && notebook.showHistory != null ? notebook.showHistory : false);
@@ -704,14 +704,9 @@
     self.getAssistHelper = function (snippetType) {
       if (! self.assistHelpers[snippetType]) {
         self.assistHelpers[snippetType] = new AssistHelper({
-          notebook: self,
           user: vm.user,
-          activeDatabase: self.selectedDatabases[snippetType]
+          i18n: vm.i18n
         }, vm.i18n);
-
-        self.assistHelpers[snippetType].activeDatabase.subscribe(function (newActiveDatabase) {
-          self.selectedDatabases[snippetType] = newActiveDatabase;
-        })
       }
       return self.assistHelpers[snippetType]
     };
@@ -1023,6 +1018,22 @@
     self.combinedContent = ko.observable();
     self.isPlayerMode = ko.observable(false);
 
+    self.sqlSourceTypes = [];
+
+    $.each(options.languages, function (idx, language) {
+      var viewSettings = options.snippetViewSettings[language.type];
+      if (viewSettings && viewSettings.sqlDialect) {
+        self.sqlSourceTypes.push({
+          type: language.type,
+          name: language.name
+        })
+      }
+    });
+
+    if (self.sqlSourceTypes.length === 1) {
+      self.activeSqlSourceType = self.sqlSourceTypes[0].type;
+    }
+
     self.displayCombinedContent = function () {
       if (! self.selectedNotebook()) {
         self.combinedContent('');

+ 5 - 2
desktop/libs/notebook/src/notebook/templates/editor_components.mako

@@ -314,7 +314,11 @@ ${ require.config() }
     </a>
     <div class="assist" data-bind="component: {
         name: 'assist-panel',
-        params: { notebookViewModel: $root }
+        params: {
+          user: $root.user,
+          sourceTypes: $root.sqlSourceTypes,
+          activeSourceType: $root.activeSqlSourceType
+        }
       }"></div>
   </div>
   <div class="resizer" data-bind="visible: $root.isLeftPanelVisible() && $root.assistAvailable(), splitDraggable : { appName: 'notebook', leftPanelVisible: $root.isLeftPanelVisible }"><div class="resize-bar">&nbsp;</div></div>
@@ -1614,7 +1618,6 @@ ${ require.config() }
       var currentNotebook = viewModel.notebooks()[0];
       currentNotebook.name(notebook.name);
       currentNotebook.description(notebook.description);
-      currentNotebook.selectedDatabases = notebook.selectedDatabases;
       currentNotebook.selectedSnippet(notebook.selectedSnippet);
       notebook.snippets.forEach(function(snippet){
         var newSnippet = currentNotebook.addSnippet({