浏览代码

[notebook] Turn all knockout dependencies into modules

For each of the modules I've also added a check for the existence of require/define, if it's not there they will work just as they did before, making requirejs optional for these files.
Johan Ahlen 10 年之前
父节点
当前提交
1d1cf67

+ 205 - 194
desktop/core/src/desktop/static/desktop/js/assistHelper.js

@@ -14,209 +14,220 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-var TIME_TO_LIVE_IN_MILLIS = 86400000; // 1 day
-var NOTEBOOK_API_PREFIX = "/notebook/api/autocomplete/";
-
-/**
- * @param options {object}
- * @param options.notebook
- * @param options.user
- * @param options.activeDatabase
- *
- * @constructor
- */
-function AssistHelper (options) {
-  var self = this;
-  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);
-    }
-  });
-}
-
-AssistHelper.prototype.load = function (snippet, callback) {
-  var self = this;
-  if (self.loading() || self.loaded()) {
-    return;
+(function (root, factory) {
+  if(typeof define === "function" && define.amd) {
+    define(['knockout'], factory);
+  } else {
+    root.AssistHelper = factory(ko);
   }
-  self.type = snippet.type();
-  self.loading(true);
-  self.loaded(false);
-  self.fetchAssistData(snippet, NOTEBOOK_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) {
-      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]);
+}(this, function (ko) {
+
+  var TIME_TO_LIVE_IN_MILLIS = 86400000; // 1 day
+  var NOTEBOOK_API_PREFIX = "/notebook/api/autocomplete/";
+
+  /**
+   * @param options {object}
+   * @param options.notebook
+   * @param options.user
+   * @param options.activeDatabase
+   *
+   * @constructor
+   */
+  function AssistHelper (options) {
+    var self = this;
+    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.loaded(true);
-    self.loading(false);
-    if (callback) {
-      callback();
-    }
-  }, function(message) {
-   if (message.status == 401) {
-      $(document).trigger("showAuthModal", {'type': self.type, 'callback': function() {self.loaded(false); self.load(snippet, callback) }});
-    } else if (message.statusText) {
-      $(document).trigger("error", "There was a problem loading the databases:" + message.statusText);
-    } else if (message) {
-      $(document).trigger("error", message);
-    } else {
-      $(document).trigger("error", "There was a problem loading the databases");
-    }
-    self.loaded(true);
-    self.loading(false);
-    if (callback) {
-      callback();
+  AssistHelper.prototype.load = function (snippet, callback) {
+    var self = this;
+    if (self.loading() || self.loaded()) {
+      return;
     }
-  });
-};
-
-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();
-  $.ajax({
-    url: "/" + app + "/api/table/" + self.activeDatabase() + "/" + tableName,
-    data: { "sample": true },
-    beforeSend: function (xhr) {
-      xhr.setRequestHeader("X-Requested-With", "Hue");
-    },
-    dataType: "html",
-    success: successCallback,
-    error: errorCallback
-  });
-};
-
-AssistHelper.prototype.refreshTableStats = function(snippet, tableName, columnName, successCallback, errorCallback) {
-  var self = this;
-  var pollRefresh = function (url) {
-    $.post(url, function (data) {
-      if (data.isSuccess) {
-        successCallback(data);
-      } else if (data.isFailure) {
-        errorCallback(data.message);
+    self.type = snippet.type();
+    self.loading(true);
+    self.loaded(false);
+    self.fetchAssistData(snippet, NOTEBOOK_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) {
+        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]);
+        }
+      }
+
+      self.loaded(true);
+      self.loading(false);
+      if (callback) {
+        callback();
+      }
+    }, function(message) {
+     if (message.status == 401) {
+        $(document).trigger("showAuthModal", {'type': self.type, 'callback': function() {self.loaded(false); self.load(snippet, callback) }});
+      } else if (message.statusText) {
+        $(document).trigger("error", "There was a problem loading the databases:" + message.statusText);
+      } else if (message) {
+        $(document).trigger("error", message);
       } else {
-        window.setTimeout(function () {
-          pollRefresh(url);
-        }, 1000);
+        $(document).trigger("error", "There was a problem loading the databases");
       }
-    }).fail(errorCallback);
+      self.loaded(true);
+      self.loading(false);
+      if (callback) {
+        callback();
+      }
+    });
   };
 
-  var app = snippet.type() == "hive" ? "beeswax" : snippet.type();
-  $.post("/" + app + "/api/analyze/" + self.activeDatabase() + "/" + tableName + "/"  + (columnName || ""), function (data) {
-    if (data.status == 0 && data.watch_url) {
-      pollRefresh(data.watch_url);
-    } else {
-      errorCallback(data.message);
-    }
-  }).fail(errorCallback);
-};
-
-AssistHelper.prototype.fetchStats = function(snippet, tableName, columnName, successCallback, errorCallback) {
-  var self = this;
-  var app = snippet.type() == "hive" ? "beeswax" : snippet.type();
-  $.ajax({
-    url: "/" + app + "/api/table/" + self.activeDatabase() + "/" + tableName + "/stats/" + (columnName || ""),
-    data: {},
-    beforeSend: function (xhr) {
-      xhr.setRequestHeader("X-Requested-With", "Hue");
-    },
-    dataType: "json",
-    success: successCallback,
-    error: errorCallback
-  });
-};
-
-AssistHelper.prototype.fetchTerms = function(snippet, tableName, columnName, prefixFilter, successCallback, errorCallback) {
-  var self = this;
-  var app = snippet.type() == "hive" ? "beeswax" : snippet.type();
-  $.ajax({
-    url: "/" + app + "/api/table/" + self.activeDatabase() + "/" + tableName + "/terms/" + columnName + "/" + (prefixFilter || ""),
-    data: {},
-    beforeSend: function (xhr) {
-      xhr.setRequestHeader("X-Requested-With", "Hue");
-    },
-    dataType: "json",
-    success: successCallback,
-    error: errorCallback
-  });
-};
-
-AssistHelper.prototype.fetchTables = function(snippet, successCallback, errorCallback) {
-  var self = this;
-  self.fetchAssistData(snippet, NOTEBOOK_API_PREFIX + self.activeDatabase(), successCallback, errorCallback);
-};
-
-AssistHelper.prototype.fetchFields = function(snippet, tableName, fields, successCallback, errorCallback, editor) {
-  var self = this;
-
-  var fieldPart = fields.length > 0 ? "/" + fields.join("/") : "";
-  self.fetchAssistData(snippet, NOTEBOOK_API_PREFIX + self.activeDatabase() + "/" + tableName + fieldPart, successCallback, errorCallback, editor);
-};
-
-AssistHelper.prototype.clearCache = function(snippet) {
-  var self = this;
-  $.totalStorage("hue.assist." + self.getTotalStorageUserPrefix(), {});
-};
-
-AssistHelper.prototype.fetchPanelData = function (snippet, hierarchy, successCallback, errorCallback) {
-  var self = this;
-  self.fetchAssistData(snippet, NOTEBOOK_API_PREFIX + hierarchy.join("/"), successCallback, errorCallback);
-};
-
-AssistHelper.prototype.fetchAssistData = function (snippet, url, successCallback, errorCallback, editor) {
-  var self = this;
-  var cachedData = $.totalStorage("hue.assist." + self.getTotalStorageUserPrefix()) || {};
-
-  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())
-    }, function (data) {
-      if (data.status == 0) {
-        successCallback(data);
+  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();
+    $.ajax({
+      url: "/" + app + "/api/table/" + self.activeDatabase() + "/" + tableName,
+      data: { "sample": true },
+      beforeSend: function (xhr) {
+        xhr.setRequestHeader("X-Requested-With", "Hue");
+      },
+      dataType: "html",
+      success: successCallback,
+      error: errorCallback
+    });
+  };
+
+  AssistHelper.prototype.refreshTableStats = function(snippet, tableName, columnName, successCallback, errorCallback) {
+    var self = this;
+    var pollRefresh = function (url) {
+      $.post(url, function (data) {
+        if (data.isSuccess) {
+          successCallback(data);
+        } else if (data.isFailure) {
+          errorCallback(data.message);
+        } else {
+          window.setTimeout(function () {
+            pollRefresh(url);
+          }, 1000);
+        }
+      }).fail(errorCallback);
+    };
+
+    var app = snippet.type() == "hive" ? "beeswax" : snippet.type();
+    $.post("/" + app + "/api/analyze/" + self.activeDatabase() + "/" + tableName + "/"  + (columnName || ""), function (data) {
+      if (data.status == 0 && data.watch_url) {
+        pollRefresh(data.watch_url);
       } else {
-        errorCallback(data);
+        errorCallback(data.message);
       }
-    }).fail(errorCallback).always(function () {
+    }).fail(errorCallback);
+  };
+
+  AssistHelper.prototype.fetchStats = function(snippet, tableName, columnName, successCallback, errorCallback) {
+    var self = this;
+    var app = snippet.type() == "hive" ? "beeswax" : snippet.type();
+    $.ajax({
+      url: "/" + app + "/api/table/" + self.activeDatabase() + "/" + tableName + "/stats/" + (columnName || ""),
+      data: {},
+      beforeSend: function (xhr) {
+        xhr.setRequestHeader("X-Requested-With", "Hue");
+      },
+      dataType: "json",
+      success: successCallback,
+      error: errorCallback
+    });
+  };
+
+  AssistHelper.prototype.fetchTerms = function(snippet, tableName, columnName, prefixFilter, successCallback, errorCallback) {
+    var self = this;
+    var app = snippet.type() == "hive" ? "beeswax" : snippet.type();
+    $.ajax({
+      url: "/" + app + "/api/table/" + self.activeDatabase() + "/" + tableName + "/terms/" + columnName + "/" + (prefixFilter || ""),
+      data: {},
+      beforeSend: function (xhr) {
+        xhr.setRequestHeader("X-Requested-With", "Hue");
+      },
+      dataType: "json",
+      success: successCallback,
+      error: errorCallback
+    });
+  };
+
+  AssistHelper.prototype.fetchTables = function(snippet, successCallback, errorCallback) {
+    var self = this;
+    self.fetchAssistData(snippet, NOTEBOOK_API_PREFIX + self.activeDatabase(), successCallback, errorCallback);
+  };
+
+  AssistHelper.prototype.fetchFields = function(snippet, tableName, fields, successCallback, errorCallback, editor) {
+    var self = this;
+
+    var fieldPart = fields.length > 0 ? "/" + fields.join("/") : "";
+    self.fetchAssistData(snippet, NOTEBOOK_API_PREFIX + self.activeDatabase() + "/" + tableName + fieldPart, successCallback, errorCallback, editor);
+  };
+
+  AssistHelper.prototype.clearCache = function(snippet) {
+    var self = this;
+    $.totalStorage("hue.assist." + self.getTotalStorageUserPrefix(), {});
+  };
+
+  AssistHelper.prototype.fetchPanelData = function (snippet, hierarchy, successCallback, errorCallback) {
+    var self = this;
+    self.fetchAssistData(snippet, NOTEBOOK_API_PREFIX + hierarchy.join("/"), successCallback, errorCallback);
+  };
+
+  AssistHelper.prototype.fetchAssistData = function (snippet, url, successCallback, errorCallback, editor) {
+    var self = this;
+    var cachedData = $.totalStorage("hue.assist." + self.getTotalStorageUserPrefix()) || {};
+
+    if (typeof cachedData[url] == "undefined" || self.hasExpired(cachedData[url].timestamp)) {
       if (editor) {
-        editor.hideSpinner();
+        editor.showSpinner();
       }
-    });
-  } else {
-    successCallback(cachedData[url].data);
-  }
-};
+      $.post(url, {
+        notebook: ko.mapping.toJSON(self.notebook.getContext()),
+        snippet: ko.mapping.toJSON(snippet.getContext())
+      }, function (data) {
+        if (data.status == 0) {
+          successCallback(data);
+        } else {
+          errorCallback(data);
+        }
+      }).fail(errorCallback).always(function () {
+        if (editor) {
+          editor.hideSpinner();
+        }
+      });
+    } else {
+      successCallback(cachedData[url].data);
+    }
+  };
+
+  return AssistHelper;
+}));

文件差异内容过多而无法显示
+ 862 - 603
desktop/core/src/desktop/static/desktop/js/ko.charts.js


文件差异内容过多而无法显示
+ 920 - 1056
desktop/core/src/desktop/static/desktop/js/ko.hue-bindings.js


+ 170 - 139
desktop/core/src/desktop/templates/ko_components.mako

@@ -272,7 +272,14 @@ from desktop.views import _ko
   </script>
 
   <script type="text/javascript" charset="utf-8">
-    (function() {
+    (function (factory) {
+      if(typeof require === "function") {
+        require(['knockout', 'desktop/js/assistHelper'], factory);
+      } else {
+        factory(ko, AssistHelper);
+      }
+    }(function (ko, AssistHelper) {
+      (function() {
       function AssistEntry (definition, parent, assistSource, filter) {
         var self = this;
         self.definition = definition;
@@ -814,6 +821,7 @@ from desktop.views import _ko
         template: { element: 'assist-panel-template' }
       });
     }());
+    }));
   </script>
 </%def>
 
@@ -823,39 +831,47 @@ from desktop.views import _ko
   </script>
 
   <script type="text/javascript" charset="utf-8">
-    (function() {
-      var JVM_MEM_PATTERN = /([0-9]+)([MG])$/;
-      var UNITS = { 'MB' : 'M', 'GB' : 'G' };
-
-      function JvmMemoryInputViewModel(params) {
-        this.valueObservable = params.value;
-        this.units = Object.keys(UNITS);
-        this.selectedUnit = ko.observable();
-        this.value = ko.observable('');
-
-        var match = JVM_MEM_PATTERN.exec(this.valueObservable());
-        if (match && match.length === 3) {
-          this.value(match[1]);
-          this.selectedUnit(match[2] === 'M' ? 'MB' : 'GB');
-        }
-
-        this.value.subscribe(this.updateValueObservable, this);
-        this.selectedUnit.subscribe(this.updateValueObservable, this);
+    (function (factory) {
+      if(typeof require === "function") {
+        require(['knockout'], factory);
+      } else {
+        factory(ko);
       }
+    }(function (ko) {
+      (function () {
+        var JVM_MEM_PATTERN = /([0-9]+)([MG])$/;
+        var UNITS = {'MB': 'M', 'GB': 'G'};
+
+        function JvmMemoryInputViewModel(params) {
+          this.valueObservable = params.value;
+          this.units = Object.keys(UNITS);
+          this.selectedUnit = ko.observable();
+          this.value = ko.observable('');
+
+          var match = JVM_MEM_PATTERN.exec(this.valueObservable());
+          if (match && match.length === 3) {
+            this.value(match[1]);
+            this.selectedUnit(match[2] === 'M' ? 'MB' : 'GB');
+          }
 
-      JvmMemoryInputViewModel.prototype.updateValueObservable = function() {
-        if (isNaN(this.value()) || this.value() === '') {
-          this.valueObservable(undefined);
-        } else {
-          this.valueObservable(this.value() + UNITS[this.selectedUnit()]);
+          this.value.subscribe(this.updateValueObservable, this);
+          this.selectedUnit.subscribe(this.updateValueObservable, this);
         }
-      };
 
-      ko.components.register('jvm-memory-input', {
-        viewModel: JvmMemoryInputViewModel,
-        template: { element: 'jvm-memory-input-template' }
-      });
-    }());
+        JvmMemoryInputViewModel.prototype.updateValueObservable = function () {
+          if (isNaN(this.value()) || this.value() === '') {
+            this.valueObservable(undefined);
+          } else {
+            this.valueObservable(this.value() + UNITS[this.selectedUnit()]);
+          }
+        };
+
+        ko.components.register('jvm-memory-input', {
+          viewModel: JvmMemoryInputViewModel,
+          template: {element: 'jvm-memory-input-template'}
+        });
+      }());
+    }));
   </script>
 </%def>
 
@@ -884,52 +900,60 @@ from desktop.views import _ko
   </script>
 
   <script type="text/javascript" charset="utf-8">
-    (function() {
-      function CsvListInputViewModel (params) {
-        this.valueObservable = params.value;
-        this.isArray = $.isArray(this.valueObservable());
-        this.placeholder = params.placeholder || '';
-        this.inputTemplate = params.inputTemplate || null;
-
-        var initialValues;
-        if (this.isArray) {
-          initialValues = ko.mapping.toJS(this.valueObservable());
-        } else {
-          initialValues = this.valueObservable() != null ? this.valueObservable().split(",") : [];
-        }
-        for (var i = 0; i < initialValues.length; i++) {
-          initialValues[i] = { value: ko.observable(initialValues[i].trim()) };
-          initialValues[i].value.subscribe(this.updateValueObservable, this);
-        }
-        this.values = ko.observableArray(initialValues);
-        this.values.subscribe(this.updateValueObservable, this);
+    (function (factory) {
+      if(typeof require === "function") {
+        require(['knockout'], factory);
+      } else {
+        factory(ko);
       }
+    }(function (ko) {
+      (function () {
+        function CsvListInputViewModel(params) {
+          this.valueObservable = params.value;
+          this.isArray = $.isArray(this.valueObservable());
+          this.placeholder = params.placeholder || '';
+          this.inputTemplate = params.inputTemplate || null;
+
+          var initialValues;
+          if (this.isArray) {
+            initialValues = ko.mapping.toJS(this.valueObservable());
+          } else {
+            initialValues = this.valueObservable() != null ? this.valueObservable().split(",") : [];
+          }
+          for (var i = 0; i < initialValues.length; i++) {
+            initialValues[i] = {value: ko.observable(initialValues[i].trim())};
+            initialValues[i].value.subscribe(this.updateValueObservable, this);
+          }
+          this.values = ko.observableArray(initialValues);
+          this.values.subscribe(this.updateValueObservable, this);
+        }
 
-      CsvListInputViewModel.prototype.addValue = function () {
-        var newValue = { value: ko.observable('') };
-        newValue.value.subscribe(this.updateValueObservable, this);
-        this.values.push(newValue);
-      };
+        CsvListInputViewModel.prototype.addValue = function () {
+          var newValue = {value: ko.observable('')};
+          newValue.value.subscribe(this.updateValueObservable, this);
+          this.values.push(newValue);
+        };
 
-      CsvListInputViewModel.prototype.removeValue = function (valueToRemove) {
-        this.values.remove(valueToRemove);
-      };
+        CsvListInputViewModel.prototype.removeValue = function (valueToRemove) {
+          this.values.remove(valueToRemove);
+        };
 
-      CsvListInputViewModel.prototype.updateValueObservable = function () {
-        var cleanValues = $.map(this.values(), function (item) {
-          return item.value();
-        });
-        cleanValues = $.grep(cleanValues, function (value) {
-          return value;
-        });
-        this.valueObservable(this.isArray ? cleanValues : cleanValues.join(','));
-      };
+        CsvListInputViewModel.prototype.updateValueObservable = function () {
+          var cleanValues = $.map(this.values(), function (item) {
+            return item.value();
+          });
+          cleanValues = $.grep(cleanValues, function (value) {
+            return value;
+          });
+          this.valueObservable(this.isArray ? cleanValues : cleanValues.join(','));
+        };
 
-      ko.components.register('csv-list-input', {
-        viewModel: CsvListInputViewModel,
-        template: { element: 'csv-list-input-template' }
-      });
-    }());
+        ko.components.register('csv-list-input', {
+          viewModel: CsvListInputViewModel,
+          template: {element: 'csv-list-input-template'}
+        });
+      }());
+    }));
   </script>
 </%def>
 
@@ -975,87 +999,94 @@ from desktop.views import _ko
     </div>
   </script>
 
-
   <script type="text/javascript" charset="utf-8">
-    (function() {
-      var WHEEL_RADIUS = 75;
-      var PLUS_ICON_RADIUS = 27.859; // FA-5X
-
-      var calculatePositions = function(alternativeCount) {
-        var radius = WHEEL_RADIUS;
-        var radIncrements = 2 * Math.PI / alternativeCount;
-        var currentRad = -0.5 * Math.PI;
+    (function (factory) {
+      if(typeof require === "function") {
+        require(['knockout'], factory);
+      } else {
+        factory(ko);
+      }
+    }(function (ko) {
+      (function () {
+        var WHEEL_RADIUS = 75;
+        var PLUS_ICON_RADIUS = 27.859; // FA-5X
 
-        var result = [];
+        var calculatePositions = function (alternativeCount) {
+          var radius = WHEEL_RADIUS;
+          var radIncrements = 2 * Math.PI / alternativeCount;
+          var currentRad = -0.5 * Math.PI;
 
-        for (var i = 0; i < alternativeCount; i++) {
-          result.push({
-            left: radius * Math.cos(currentRad) + PLUS_ICON_RADIUS  + 'px',
-            top: radius * Math.sin(currentRad) + PLUS_ICON_RADIUS  + 'px'
-          });
-          currentRad += radIncrements;
-        }
+          var result = [];
 
-        return result;
-      };
+          for (var i = 0; i < alternativeCount; i++) {
+            result.push({
+              left: radius * Math.cos(currentRad) + PLUS_ICON_RADIUS + 'px',
+              top: radius * Math.sin(currentRad) + PLUS_ICON_RADIUS + 'px'
+            });
+            currentRad += radIncrements;
+          }
 
-      function AddSnippetMenuViewModel (params) {
-        var self = this;
-        self.notebook = params.notebook;
-        self.availableSnippets = params.availableSnippets;
-        self.snippetHistory = ko.observableArray([].concat(self.availableSnippets.slice(0,5)));
-        self.lastUsedSnippet = self.snippetHistory()[0];
-        self.roundCount = 0;
-        self.positions = calculatePositions(self.snippetHistory().length);
-        self.showingHistory = ko.observable(false);
-        self.hasAdditionalSnippets = params.availableSnippets().length > 5;
-        self.showingSelectSnippet = ko.observable(false);
-
-        self.addLastUsedSnippet = function() {
-          self.addNewSnippet(self.lastUsedSnippet);
+          return result;
         };
 
-        self.showSnippetModal = function () {
-          $("#addSnippetModal").modal('show');
-        };
+        function AddSnippetMenuViewModel(params) {
+          var self = this;
+          self.notebook = params.notebook;
+          self.availableSnippets = params.availableSnippets;
+          self.snippetHistory = ko.observableArray([].concat(self.availableSnippets.slice(0, 5)));
+          self.lastUsedSnippet = self.snippetHistory()[0];
+          self.roundCount = 0;
+          self.positions = calculatePositions(self.snippetHistory().length);
+          self.showingHistory = ko.observable(false);
+          self.hasAdditionalSnippets = params.availableSnippets().length > 5;
+          self.showingSelectSnippet = ko.observable(false);
+
+          self.addLastUsedSnippet = function () {
+            self.addNewSnippet(self.lastUsedSnippet);
+          };
 
-        self.addNewSnippet = function (alternative) {
-          clearTimeout(hideTimeout);
-          self.showingHistory(false);
-          self.showingSelectSnippet(false);
-          $("#addSnippetModal").modal('hide');
+          self.showSnippetModal = function () {
+            $("#addSnippetModal").modal('show');
+          };
 
-          // When fewer than 5 it's always in history
-          if (self.snippetHistory().indexOf(alternative) == -1) {
-            self.snippetHistory.splice(4 - self.roundCount, 1, alternative);
-            self.roundCount = (self.roundCount + 1) % 5;
-          }
+          self.addNewSnippet = function (alternative) {
+            clearTimeout(hideTimeout);
+            self.showingHistory(false);
+            self.showingSelectSnippet(false);
+            $("#addSnippetModal").modal('hide');
 
-          self.lastUsedSnippet = alternative;
-          self.notebook.newSnippet(alternative.type())
-        };
+            // When fewer than 5 it's always in history
+            if (self.snippetHistory().indexOf(alternative) == -1) {
+              self.snippetHistory.splice(4 - self.roundCount, 1, alternative);
+              self.roundCount = (self.roundCount + 1) % 5;
+            }
 
-        var hideTimeout = -1;
+            self.lastUsedSnippet = alternative;
+            self.notebook.newSnippet(alternative.type())
+          };
 
-        self.showHistory = function () {
-          clearTimeout(hideTimeout);
-          self.showingHistory(true);
-          self.showingSelectSnippet(true);
-        };
+          var hideTimeout = -1;
 
-        self.hideHistory = function () {
-          clearTimeout(hideTimeout);
-          hideTimeout = window.setTimeout(function () {
-            self.showingHistory(false);
-            self.showingSelectSnippet(false);
-          }, 500);
-        };
-      }
+          self.showHistory = function () {
+            clearTimeout(hideTimeout);
+            self.showingHistory(true);
+            self.showingSelectSnippet(true);
+          };
 
-      ko.components.register('add-snippet-menu', {
-        viewModel: AddSnippetMenuViewModel,
-        template: { element: 'add-snippet-menu-template' }
-      });
-    }());
+          self.hideHistory = function () {
+            clearTimeout(hideTimeout);
+            hideTimeout = window.setTimeout(function () {
+              self.showingHistory(false);
+              self.showingSelectSnippet(false);
+            }, 500);
+          };
+        }
+
+        ko.components.register('add-snippet-menu', {
+          viewModel: AddSnippetMenuViewModel,
+          template: {element: 'add-snippet-menu-template'}
+        });
+      }());
+    }));
   </script>
 </%def>

+ 910 - 906
desktop/libs/notebook/src/notebook/static/notebook/js/notebook.ko.js

@@ -14,1067 +14,1071 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+(function (root, factory) {
+  if(typeof define === "function" && define.amd) {
+    define(['knockout', 'desktop/js/assistHelper', 'knockout-mapping', 'ko.charts'], factory);
+  } else {
+    root.EditorViewModel = factory(ko, AssistHelper);
+  }
+}(this, function (ko, AssistHelper) {
+
+  var SPARK_MAPPING = {
+    ignore: ["ace", "images", "autocompleter", "selectedStatement", "assistHelpers", "user"]
+  };
 
-var SPARK_MAPPING = {
-  ignore: ["ace", "images", "autocompleter", "selectedStatement", "assistHelpers", "user"]
-};
-
-var Result = function (snippet, result) {
-  var self = this;
-
-  self.id = ko.observable(typeof result.id != "undefined" && result.id != null ? result.id : UUID());
-  self.type = ko.observable(typeof result.type != "undefined" && result.type != null ? result.type : 'table');
-  self.hasResultset = ko.observable(typeof result.hasResultset != "undefined" && result.hasResultset != null ? result.hasResultset : true)
-    .extend("throttle", 100);
-  self.handle = ko.observable(typeof result.handle != "undefined" && result.handle != null ? result.handle : {});
-  self.meta = ko.observableArray(typeof result.meta != "undefined" && result.meta != null ? result.meta : []);
-  self.cleanedMeta = ko.computed(function () {
-    return ko.utils.arrayFilter(self.meta(), function (item) {
-      return item.name != ''
+  var Result = function (snippet, result) {
+    var self = this;
+
+    self.id = ko.observable(typeof result.id != "undefined" && result.id != null ? result.id : UUID());
+    self.type = ko.observable(typeof result.type != "undefined" && result.type != null ? result.type : 'table');
+    self.hasResultset = ko.observable(typeof result.hasResultset != "undefined" && result.hasResultset != null ? result.hasResultset : true)
+      .extend("throttle", 100);
+    self.handle = ko.observable(typeof result.handle != "undefined" && result.handle != null ? result.handle : {});
+    self.meta = ko.observableArray(typeof result.meta != "undefined" && result.meta != null ? result.meta : []);
+    self.cleanedMeta = ko.computed(function () {
+      return ko.utils.arrayFilter(self.meta(), function (item) {
+        return item.name != ''
+      });
+    });
+    self.fetchedOnce = ko.observable(typeof result.fetchedOnce != "undefined" && result.fetchedOnce != null ? result.fetchedOnce : false);
+    self.startTime = ko.observable(typeof result.startTime != "undefined" && result.startTime != null ? new Date(result.startTime) : new Date());
+    self.endTime = ko.observable(typeof result.endTime != "undefined" && result.endTime != null ? new Date(result.endTime) : new Date());
+    self.executionTime = ko.computed(function () {
+      return self.endTime().getTime() - self.startTime().getTime();
     });
-  });
-  self.fetchedOnce = ko.observable(typeof result.fetchedOnce != "undefined" && result.fetchedOnce != null ? result.fetchedOnce : false);
-  self.startTime = ko.observable(typeof result.startTime != "undefined" && result.startTime != null ? new Date(result.startTime) : new Date());
-  self.endTime = ko.observable(typeof result.endTime != "undefined" && result.endTime != null ? new Date(result.endTime) : new Date());
-  self.executionTime = ko.computed(function () {
-    return self.endTime().getTime() - self.startTime().getTime();
-  });
-
-  function isNumericColumn(type) {
-    return $.inArray(type, ['TINYINT_TYPE', 'SMALLINT_TYPE', 'INT_TYPE', 'BIGINT_TYPE', 'FLOAT_TYPE', 'DOUBLE_TYPE', 'DECIMAL_TYPE', 'TIMESTAMP_TYPE', 'DATE_TYPE']) > -1;
-  }
 
-  function isDateTimeColumn(type) {
-    return $.inArray(type, ['TIMESTAMP_TYPE', 'DATE_TYPE']) > -1;
-  }
+    function isNumericColumn(type) {
+      return $.inArray(type, ['TINYINT_TYPE', 'SMALLINT_TYPE', 'INT_TYPE', 'BIGINT_TYPE', 'FLOAT_TYPE', 'DOUBLE_TYPE', 'DECIMAL_TYPE', 'TIMESTAMP_TYPE', 'DATE_TYPE']) > -1;
+    }
 
-  function isStringColumn(type) {
-    return !isNumericColumn(type) && !isDateTimeColumn(type);
-  }
+    function isDateTimeColumn(type) {
+      return $.inArray(type, ['TIMESTAMP_TYPE', 'DATE_TYPE']) > -1;
+    }
 
-  self.cleanedNumericMeta = ko.computed(function () {
-    return ko.utils.arrayFilter(self.meta(), function (item) {
-      return item.name != '' && isNumericColumn(item.type)
+    function isStringColumn(type) {
+      return !isNumericColumn(type) && !isDateTimeColumn(type);
+    }
+
+    self.cleanedNumericMeta = ko.computed(function () {
+      return ko.utils.arrayFilter(self.meta(), function (item) {
+        return item.name != '' && isNumericColumn(item.type)
+      });
     });
-  });
 
-  self.cleanedStringMeta = ko.computed(function () {
-    return ko.utils.arrayFilter(self.meta(), function (item) {
-      return item.name != '' && isStringColumn(item.type)
+    self.cleanedStringMeta = ko.computed(function () {
+      return ko.utils.arrayFilter(self.meta(), function (item) {
+        return item.name != '' && isStringColumn(item.type)
+      });
     });
-  });
 
-  self.cleanedDateTimeMeta = ko.computed(function () {
-    return ko.utils.arrayFilter(self.meta(), function (item) {
-      return item.name != '' && isDateTimeColumn(item.type)
+    self.cleanedDateTimeMeta = ko.computed(function () {
+      return ko.utils.arrayFilter(self.meta(), function (item) {
+        return item.name != '' && isDateTimeColumn(item.type)
+      });
     });
-  });
-
-  self.data = ko.observableArray(typeof result.data != "undefined" && result.data != null ? result.data : []);
-  self.data.extend({ rateLimit: 50 });
-  self.images = ko.observableArray(typeof result.images != "undefined" && result.images != null ? result.images : []);
-  self.images.extend({ rateLimit: 50 });
-  self.logs = ko.observable('');
-  self.logLines = 0;
-  self.hasSomeResults = ko.computed(function () {
-    return self.hasResultset() && self.data().length > 0; // status() == 'available'
-  });
-
-  self.getContext = function() {
-    return {
-        id: self.id,
-        type: self.type,
-        handle: self.handle
+
+    self.data = ko.observableArray(typeof result.data != "undefined" && result.data != null ? result.data : []);
+    self.data.extend({ rateLimit: 50 });
+    self.images = ko.observableArray(typeof result.images != "undefined" && result.images != null ? result.images : []);
+    self.images.extend({ rateLimit: 50 });
+    self.logs = ko.observable('');
+    self.logLines = 0;
+    self.hasSomeResults = ko.computed(function () {
+      return self.hasResultset() && self.data().length > 0; // status() == 'available'
+    });
+
+    self.getContext = function() {
+      return {
+          id: self.id,
+          type: self.type,
+          handle: self.handle
+      };
     };
-  };
 
-  self.clear = function () {
-    self.fetchedOnce(false);
-    self.data.removeAll();
-    self.images.removeAll();
-    self.logs('');
-    self.startTime(new Date());
-    self.endTime(new Date());
+    self.clear = function () {
+      self.fetchedOnce(false);
+      self.data.removeAll();
+      self.images.removeAll();
+      self.logs('');
+      self.startTime(new Date());
+      self.endTime(new Date());
+    };
   };
-};
 
-var getDefaultSnippetProperties = function (snippetType) {
-  var properties = {};
+  var getDefaultSnippetProperties = function (snippetType) {
+    var properties = {};
 
-  if (snippetType == 'jar' || snippetType == 'py') {
-    properties['driverCores'] = '';
-    properties['executorCores'] = '';
-    properties['numExecutors'] = '';
-    properties['queue'] = '';
-    properties['archives'] = [];
-  }
+    if (snippetType == 'jar' || snippetType == 'py') {
+      properties['driverCores'] = '';
+      properties['executorCores'] = '';
+      properties['numExecutors'] = '';
+      properties['queue'] = '';
+      properties['archives'] = [];
+    }
 
-  if (snippetType == 'jar') {
-    properties['app_jar'] = '';
-    properties['class'] = '';
-    properties['arguments'] = [];
-  }
-  else if (snippetType == 'py') {
-    properties['py_file'] = '';
-    properties['arguments'] = [];
-  }
-  else if (snippetType == 'hive') {
-    properties['settings'] = [];
-    properties['files'] = [];
-  }
-  else if (snippetType == 'pig') {
-    properties['parameters'] = [];
-    properties['hadoopProperties'] = [];
-    properties['resources'] = [];
-  }
+    if (snippetType == 'jar') {
+      properties['app_jar'] = '';
+      properties['class'] = '';
+      properties['arguments'] = [];
+    }
+    else if (snippetType == 'py') {
+      properties['py_file'] = '';
+      properties['arguments'] = [];
+    }
+    else if (snippetType == 'hive') {
+      properties['settings'] = [];
+      properties['files'] = [];
+    }
+    else if (snippetType == 'pig') {
+      properties['parameters'] = [];
+      properties['hadoopProperties'] = [];
+      properties['resources'] = [];
+    }
 
-  return properties;
-};
+    return properties;
+  };
 
-var ERROR_REGEX = /line ([0-9]+)/i;
+  var ERROR_REGEX = /line ([0-9]+)/i;
 
-var Snippet = function (vm, notebook, snippet) {
-  var self = this;
+  var Snippet = function (vm, notebook, snippet) {
+    var self = this;
 
-  self.id = ko.observable(typeof snippet.id != "undefined" && snippet.id != null ? snippet.id : UUID());
-  self.name = ko.observable(typeof snippet.name != "undefined" && snippet.name != null ? snippet.name : '');
-  self.type = ko.observable(typeof snippet.type != "undefined" && snippet.type != null ? snippet.type : 'hive');
-  self.subtype = ko.observable(typeof snippet.subtype != "undefined" && snippet.subtype != null ? snippet.subtype : '');
+    self.id = ko.observable(typeof snippet.id != "undefined" && snippet.id != null ? snippet.id : UUID());
+    self.name = ko.observable(typeof snippet.name != "undefined" && snippet.name != null ? snippet.name : '');
+    self.type = ko.observable(typeof snippet.type != "undefined" && snippet.type != null ? snippet.type : 'hive');
+    self.subtype = ko.observable(typeof snippet.subtype != "undefined" && snippet.subtype != null ? snippet.subtype : '');
 
-  //Ace stuff
-  self.ace = ko.observable(null);
-  self.errors = ko.observableArray([]);
+    //Ace stuff
+    self.ace = ko.observable(null);
+    self.errors = ko.observableArray([]);
 
-  self.getAceMode = function() {
-    return vm.getSnippetViewSettings(self.type()).aceMode;
-  };
+    self.getAceMode = function() {
+      return vm.getSnippetViewSettings(self.type()).aceMode;
+    };
 
-  self.isSqlDialect = function () {
-    return vm.getSnippetViewSettings(self.type()).sqlDialect;
-  };
+    self.isSqlDialect = function () {
+      return vm.getSnippetViewSettings(self.type()).sqlDialect;
+    };
 
-  self.getPlaceHolder = function() {
-    return vm.getSnippetViewSettings(self.type()).placeHolder;
-  };
+    self.getPlaceHolder = function() {
+      return vm.getSnippetViewSettings(self.type()).placeHolder;
+    };
 
-  self.getAssistHelper = function() {
-    return notebook.getAssistHelper(self.type());
-  };
+    self.getAssistHelper = function() {
+      return notebook.getAssistHelper(self.type());
+    };
 
-  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);
-  // self.statement_raw.extend({ rateLimit: 150 }); // Should prevent lag from typing but currently send the old query when using the key shortcut
-  self.status = ko.observable(typeof snippet.status != "undefined" && snippet.status != null ? snippet.status : 'loading');
-
-  self.properties = ko.observable(ko.mapping.fromJS(typeof snippet.properties != "undefined" && snippet.properties != null ? snippet.properties : getDefaultSnippetProperties(self.type())));
-  self.hasProperties = ko.computed(function() {
-    return Object.keys(ko.mapping.toJS(self.properties())).length > 0;
-  });
-
-  self.viewSettings = ko.computed(function() {
-    return vm.getSnippetViewSettings(self.type());
-  });
-
-  var previousProperties = {};
-  self.type.subscribe(function(oldValue) {
-    previousProperties[oldValue] = self.properties();
-  }, null, "beforeChange");
-
-  self.type.subscribe(function (newValue) {
-    if (typeof previousProperties[newValue] != "undefined") {
-      self.properties(previousProperties[newValue]);
-    } else {
-      self.properties(ko.mapping.fromJS(getDefaultSnippetProperties(newValue)));
-    }
-  });
+    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);
+    // self.statement_raw.extend({ rateLimit: 150 }); // Should prevent lag from typing but currently send the old query when using the key shortcut
+    self.status = ko.observable(typeof snippet.status != "undefined" && snippet.status != null ? snippet.status : 'loading');
+
+    self.properties = ko.observable(ko.mapping.fromJS(typeof snippet.properties != "undefined" && snippet.properties != null ? snippet.properties : getDefaultSnippetProperties(self.type())));
+    self.hasProperties = ko.computed(function() {
+      return Object.keys(ko.mapping.toJS(self.properties())).length > 0;
+    });
 
-  self.variables = ko.observableArray([]);
-  self.variableNames = ko.computed(function () {
-    var re = /(?:^|\W)\${(\w+)(?!\w)}/g;
+    self.viewSettings = ko.computed(function() {
+      return vm.getSnippetViewSettings(self.type());
+    });
 
-    var match, matches = [];
-    while (match = re.exec(self.statement_raw())) {
-      matches.push(match[1]);
-    }
-    return matches;
-  });
-  self.variableNames.extend({ rateLimit: 150 });
-  self.variableNames.subscribe(function (newVal) {
-    var toDelete = [];
-    var toAdd = [];
-
-    $.each(newVal, function (key, name) {
-      var match = ko.utils.arrayFirst(self.variables(), function (_var) {
-        return _var.name() == name;
-      });
-      if (!match) {
-        toAdd.push(name);
+    var previousProperties = {};
+    self.type.subscribe(function(oldValue) {
+      previousProperties[oldValue] = self.properties();
+    }, null, "beforeChange");
+
+    self.type.subscribe(function (newValue) {
+      if (typeof previousProperties[newValue] != "undefined") {
+        self.properties(previousProperties[newValue]);
+      } else {
+        self.properties(ko.mapping.fromJS(getDefaultSnippetProperties(newValue)));
       }
     });
-    $.each(self.variables(), function (key, _var) {
-      var match = ko.utils.arrayFirst(newVal, function (name) {
-        return _var.name() == name;
-      });
-      if (!match) {
-        toDelete.push(_var);
+
+    self.variables = ko.observableArray([]);
+    self.variableNames = ko.computed(function () {
+      var re = /(?:^|\W)\${(\w+)(?!\w)}/g;
+
+      var match, matches = [];
+      while (match = re.exec(self.statement_raw())) {
+        matches.push(match[1]);
       }
+      return matches;
     });
+    self.variableNames.extend({ rateLimit: 150 });
+    self.variableNames.subscribe(function (newVal) {
+      var toDelete = [];
+      var toAdd = [];
+
+      $.each(newVal, function (key, name) {
+        var match = ko.utils.arrayFirst(self.variables(), function (_var) {
+          return _var.name() == name;
+        });
+        if (!match) {
+          toAdd.push(name);
+        }
+      });
+      $.each(self.variables(), function (key, _var) {
+        var match = ko.utils.arrayFirst(newVal, function (name) {
+          return _var.name() == name;
+        });
+        if (!match) {
+          toDelete.push(_var);
+        }
+      });
+
+      $.each(toDelete, function (index, item) {
+        self.variables.remove(item);
+      });
+      $.each(toAdd, function (index, item) {
+        self.variables.push(ko.mapping.fromJS({'name': item, 'value': ''}));
+      });
 
-    $.each(toDelete, function (index, item) {
-      self.variables.remove(item);
+      self.variables.sort(function (left, right) {
+        var leftIndex = newVal.indexOf(left.name());
+        var rightIndex = newVal.indexOf(right.name());
+        return leftIndex == rightIndex ? 0 : (leftIndex < rightIndex ? -1 : 1);
+      });
+    });
+    self.statement = ko.computed(function () {
+      var statement = self.isSqlDialect() && self.selectedStatement() ? self.selectedStatement() : self.statement_raw();
+      $.each(self.variables(), function (index, variable) {
+        statement = statement.replace(RegExp("([^\\\\])?\\${" + variable.name() + "}", "g"), "$1" + variable.value());
+      });
+      return statement;
     });
-    $.each(toAdd, function (index, item) {
-      self.variables.push(ko.mapping.fromJS({'name': item, 'value': ''}));
+    self.result = new Result(snippet, snippet.result);
+    self.showGrid = ko.observable(typeof snippet.showGrid != "undefined" && snippet.showGrid != null ? snippet.showGrid : true);
+    self.showChart = ko.observable(typeof snippet.showChart != "undefined" && snippet.showChart != null ? snippet.showChart : false);
+    self.showLogs = ko.observable(typeof snippet.showLogs != "undefined" && snippet.showLogs != null ? snippet.showLogs : false);
+    self.progress = ko.observable(typeof snippet.progress != "undefined" && snippet.progress != null ? snippet.progress : 0);
+    self.jobs = ko.observableArray(typeof snippet.jobs != "undefined" && snippet.jobs != null ? snippet.jobs : []);
+
+    self.progress.subscribe(function (val) {
+      $(document).trigger("progress", {data: val, snippet: self});
     });
 
-    self.variables.sort(function (left, right) {
-      var leftIndex = newVal.indexOf(left.name());
-      var rightIndex = newVal.indexOf(right.name());
-      return leftIndex == rightIndex ? 0 : (leftIndex < rightIndex ? -1 : 1);
+    self.showGrid.subscribe(function (val) {
+      if (val) {
+        self.showChart(false);
+        $(document).trigger("gridShown", self);
+      }
     });
-  });
-  self.statement = ko.computed(function () {
-    var statement = self.isSqlDialect() && self.selectedStatement() ? self.selectedStatement() : self.statement_raw();
-    $.each(self.variables(), function (index, variable) {
-      statement = statement.replace(RegExp("([^\\\\])?\\${" + variable.name() + "}", "g"), "$1" + variable.value());
+    self.showChart.subscribe(function (val) {
+      if (val) {
+        self.showGrid(false);
+        self.isResultSettingsVisible(true);
+        $(document).trigger("forceChartDraw", self);
+        $(document).trigger("chartShown", self);
+      }
+    });
+    self.showLogs.subscribe(function (val) {
+      if (val) {
+        self.getLogs();
+      }
+    });
+    self.isLoading = ko.computed(function () {
+      return self.status() == "loading";
+    });
+    self.klass = ko.computed(function () {
+      return "snippet card card-widget" + (self.type() == "text" ? " snippet-text" : "");
     });
-    return statement;
-  });
-  self.result = new Result(snippet, snippet.result);
-  self.showGrid = ko.observable(typeof snippet.showGrid != "undefined" && snippet.showGrid != null ? snippet.showGrid : true);
-  self.showChart = ko.observable(typeof snippet.showChart != "undefined" && snippet.showChart != null ? snippet.showChart : false);
-  self.showLogs = ko.observable(typeof snippet.showLogs != "undefined" && snippet.showLogs != null ? snippet.showLogs : false);
-  self.progress = ko.observable(typeof snippet.progress != "undefined" && snippet.progress != null ? snippet.progress : 0);
-  self.jobs = ko.observableArray(typeof snippet.jobs != "undefined" && snippet.jobs != null ? snippet.jobs : []);
-
-  self.progress.subscribe(function (val) {
-    $(document).trigger("progress", {data: val, snippet: self});
-  });
-
-  self.showGrid.subscribe(function (val) {
-    if (val) {
-      self.showChart(false);
-      $(document).trigger("gridShown", self);
-    }
-  });
-  self.showChart.subscribe(function (val) {
-    if (val) {
-      self.showGrid(false);
-      self.isResultSettingsVisible(true);
-      $(document).trigger("forceChartDraw", self);
-      $(document).trigger("chartShown", self);
-    }
-  });
-  self.showLogs.subscribe(function (val) {
-    if (val) {
-      self.getLogs();
-    }
-  });
-  self.isLoading = ko.computed(function () {
-    return self.status() == "loading";
-  });
-  self.klass = ko.computed(function () {
-    return "snippet card card-widget" + (self.type() == "text" ? " snippet-text" : "");
-  });
-
-  self.resultsKlass = ko.computed(function () {
-    return "results " + self.type();
-  });
-
-  self.errorsKlass = ko.computed(function () {
-    return self.resultsKlass() + " alert alert-error";
-  });
-
-  self.chartType = ko.observable(typeof snippet.chartType != "undefined" && snippet.chartType != null ? snippet.chartType : ko.HUE_CHARTS.TYPES.BARCHART);
-  self.chartSorting = ko.observable(typeof snippet.chartSorting != "undefined" && snippet.chartSorting != null ? snippet.chartSorting : "none");
-  self.chartScatterGroup = ko.observable(typeof snippet.chartScatterGroup != "undefined" && snippet.chartScatterGroup != null ? snippet.chartScatterGroup : null);
-  self.chartScatterSize = ko.observable(typeof snippet.chartScatterSize != "undefined" && snippet.chartScatterSize != null ? snippet.chartScatterSize : null);
-  self.chartScope = ko.observable(typeof snippet.chartScope != "undefined" && snippet.chartScope != null ? snippet.chartScope : "world");
-  self.chartX = ko.observable(typeof snippet.chartX != "undefined" && snippet.chartX != null ? snippet.chartX : null);
-  self.chartYSingle = ko.observable(typeof snippet.chartYSingle != "undefined" && snippet.chartYSingle != null ? snippet.chartYSingle : null);
-  self.chartYMulti = ko.observableArray(typeof snippet.chartYMulti != "undefined" && snippet.chartYMulti != null ? snippet.chartYMulti : []);
-  self.chartData = ko.observableArray(typeof snippet.chartData != "undefined" && snippet.chartData != null ? snippet.chartData : []);
-  self.chartMapLabel = ko.observable(typeof snippet.chartMapLabel != "undefined" && snippet.chartMapLabel != null ? snippet.chartMapLabel : null);
-
-  self.hasDataForChart = ko.computed(function () {
-    if (self.chartType() == ko.HUE_CHARTS.TYPES.BARCHART || self.chartType() == ko.HUE_CHARTS.TYPES.LINECHART) {
-      return typeof self.chartX() != "undefined" && self.chartX() != null && self.chartYMulti().length > 0;
-    }
-    return typeof self.chartX() != "undefined" && self.chartX() != null && typeof self.chartYSingle() != "undefined" && self.chartYSingle() != null ;
-  });
 
-  self.hasDataForChart.subscribe(function(newValue) {
-    if (!newValue) {
-      self.isResultSettingsVisible(true);
-    }
-    self.chartX.notifySubscribers();
-    self.chartX.valueHasMutated();
-  });
-
-  self.chartType.subscribe(function (val) {
-    $(document).trigger("forceChartDraw", self);
-  });
-
-  self.previousChartOptions = {};
-
-  self.result.meta.subscribe(function(newValue) {
-    self.chartX(self.previousChartOptions.chartX);
-    self.chartYSingle(self.previousChartOptions.chartYSingle);
-    self.chartMapLabel(self.previousChartOptions.chartMapLabel);
-    self.chartYMulti(self.previousChartOptions.chartYMulti || []);
-  });
-
-  self.isResultSettingsVisible = ko.observable(typeof snippet.isResultSettingsVisible != "undefined" && snippet.isResultSettingsVisible != null ? snippet.isResultSettingsVisible : false);
-  self.toggleResultSettings = function () {
-    self.isResultSettingsVisible(!self.isResultSettingsVisible());
-    $(document).trigger("toggleResultSettings", self);
-  };
+    self.resultsKlass = ko.computed(function () {
+      return "results " + self.type();
+    });
 
-  self.codeVisible = ko.observable(typeof snippet.codeVisible != "undefined" && snippet.codeVisible != null ? snippet.codeVisible : true);
-  self.settingsVisible = ko.observable(typeof snippet.settingsVisible != "undefined" && snippet.settingsVisible != null ? snippet.settingsVisible : false);
+    self.errorsKlass = ko.computed(function () {
+      return self.resultsKlass() + " alert alert-error";
+    });
 
-  // We need to refresh codemirror the first time it's shown if it's initially hidden otherwise it'll be blank
-  if (!self.codeVisible()) {
-    var subscription = self.codeVisible.subscribe(function(newVal) {
-      if (newVal) {
-        $(document).trigger("refreshCodeMirror", self);
-        subscription.dispose();
+    self.chartType = ko.observable(typeof snippet.chartType != "undefined" && snippet.chartType != null ? snippet.chartType : ko.HUE_CHARTS.TYPES.BARCHART);
+    self.chartSorting = ko.observable(typeof snippet.chartSorting != "undefined" && snippet.chartSorting != null ? snippet.chartSorting : "none");
+    self.chartScatterGroup = ko.observable(typeof snippet.chartScatterGroup != "undefined" && snippet.chartScatterGroup != null ? snippet.chartScatterGroup : null);
+    self.chartScatterSize = ko.observable(typeof snippet.chartScatterSize != "undefined" && snippet.chartScatterSize != null ? snippet.chartScatterSize : null);
+    self.chartScope = ko.observable(typeof snippet.chartScope != "undefined" && snippet.chartScope != null ? snippet.chartScope : "world");
+    self.chartX = ko.observable(typeof snippet.chartX != "undefined" && snippet.chartX != null ? snippet.chartX : null);
+    self.chartYSingle = ko.observable(typeof snippet.chartYSingle != "undefined" && snippet.chartYSingle != null ? snippet.chartYSingle : null);
+    self.chartYMulti = ko.observableArray(typeof snippet.chartYMulti != "undefined" && snippet.chartYMulti != null ? snippet.chartYMulti : []);
+    self.chartData = ko.observableArray(typeof snippet.chartData != "undefined" && snippet.chartData != null ? snippet.chartData : []);
+    self.chartMapLabel = ko.observable(typeof snippet.chartMapLabel != "undefined" && snippet.chartMapLabel != null ? snippet.chartMapLabel : null);
+
+    self.hasDataForChart = ko.computed(function () {
+      if (self.chartType() == ko.HUE_CHARTS.TYPES.BARCHART || self.chartType() == ko.HUE_CHARTS.TYPES.LINECHART) {
+        return typeof self.chartX() != "undefined" && self.chartX() != null && self.chartYMulti().length > 0;
       }
+      return typeof self.chartX() != "undefined" && self.chartX() != null && typeof self.chartYSingle() != "undefined" && self.chartYSingle() != null ;
     });
-  }
 
-  self.checkStatusTimeout = null;
+    self.hasDataForChart.subscribe(function(newValue) {
+      if (!newValue) {
+        self.isResultSettingsVisible(true);
+      }
+      self.chartX.notifySubscribers();
+      self.chartX.valueHasMutated();
+    });
+
+    self.chartType.subscribe(function (val) {
+      $(document).trigger("forceChartDraw", self);
+    });
+
+    self.previousChartOptions = {};
+
+    self.result.meta.subscribe(function(newValue) {
+      self.chartX(self.previousChartOptions.chartX);
+      self.chartYSingle(self.previousChartOptions.chartYSingle);
+      self.chartMapLabel(self.previousChartOptions.chartMapLabel);
+      self.chartYMulti(self.previousChartOptions.chartYMulti || []);
+    });
 
-  self.getContext = function() {
-    return {
-      id: self.id,
-      type: self.type,
-      status: self.status,
-      statement: self.statement,
-      properties: self.properties,
-      result: self.result.getContext()
+    self.isResultSettingsVisible = ko.observable(typeof snippet.isResultSettingsVisible != "undefined" && snippet.isResultSettingsVisible != null ? snippet.isResultSettingsVisible : false);
+    self.toggleResultSettings = function () {
+      self.isResultSettingsVisible(!self.isResultSettingsVisible());
+      $(document).trigger("toggleResultSettings", self);
     };
-  };
 
-  self._ajaxError = function (data, callback) {
-    if (data.status == -2) { // Session expired
-      var existingSession = notebook.getSession(self.type());
-      if (existingSession) {
-        notebook.restartSession(existingSession, callback);
-      } else {
-        notebook.createSession(new Session(vm, {'type': self.type()}), callback);
-      }
-    }
-    else if (data.status == -3) { // Statement expired
-      self.status('expired');
-    }
-    else if (data.status == 401) { // Auth required
-      self.status('expired');
-      $(document).trigger("showAuthModal", {'type': self.type(), 'callback': self.execute});
-    }
-    else if (data.status == 1 || data.status == -1) {
-      self.status('failed');
-      var match = ERROR_REGEX.exec(data.message);
-      self.errors.push({
-        message: data.message,
-        line: match === null ? null : parseInt(match[1]) - 1
+    self.codeVisible = ko.observable(typeof snippet.codeVisible != "undefined" && snippet.codeVisible != null ? snippet.codeVisible : true);
+    self.settingsVisible = ko.observable(typeof snippet.settingsVisible != "undefined" && snippet.settingsVisible != null ? snippet.settingsVisible : false);
+
+    // We need to refresh codemirror the first time it's shown if it's initially hidden otherwise it'll be blank
+    if (!self.codeVisible()) {
+      var subscription = self.codeVisible.subscribe(function(newVal) {
+        if (newVal) {
+          $(document).trigger("refreshCodeMirror", self);
+          subscription.dispose();
+        }
       });
-    } else {
-      $(document).trigger("error", data.message);
-      self.status('failed');
     }
-  };
 
-  self.lastExecuted = 0;
+    self.checkStatusTimeout = null;
 
-  self.execute = function () {
-    var now = (new Date()).getTime(); // We don't allow fast clicks
-    if (self.status() == 'running' || self.status() == 'loading' || now - self.lastExecuted < 1000) {
-      return;
-    }
-    self.previousChartOptions = {
-      chartScope: typeof self.chartScope() !== "undefined" ? self.chartX() : self.previousChartOptions.chartX,
-      chartX: typeof self.chartX() !== "undefined" ? self.chartX() : self.previousChartOptions.chartX,
-      chartYSingle: typeof self.chartYSingle() !== "undefined" ? self.chartYSingle() : self.previousChartOptions.chartYSingle,
-      chartMapLabel: typeof self.chartMapLabel() !== "undefined" ? self.chartMapLabel() : self.previousChartOptions.chartMapLabel,
-      chartYMulti: typeof self.chartYMulti() !== "undefined" ? self.chartYMulti() : self.previousChartOptions.chartYMulti
+    self.getContext = function() {
+      return {
+        id: self.id,
+        type: self.type,
+        status: self.status,
+        statement: self.statement,
+        properties: self.properties,
+        result: self.result.getContext()
+      };
     };
-    $(document).trigger("executeStarted", self);
-    self.lastExecuted = now;
-    $(".jHueNotify").hide();
-    logGA('/execute/' + self.type());
-
-    self.status('running');
-    self.errors([]);
-    self.result.logLines = 0;
-    self.progress(0);
-    self.jobs([]);
-
-    if (self.result.fetchedOnce()) {
-      self.close();
-    }
 
-    $.post("/notebook/api/execute", {
-      notebook: ko.mapping.toJSON(notebook.getContext()),
-      snippet: ko.mapping.toJSON(self.getContext())
-    }, function (data) {
-      if (data.status == 0) {
-        self.result.clear();
-        self.result.handle(data.handle);
-        self.result.hasResultset(data.handle.has_result_set);
-        if (data.handle.sync) {
-          self.loadData(data.handle, 100);
-          self.status('success');
-          self.progress(100);
+    self._ajaxError = function (data, callback) {
+      if (data.status == -2) { // Session expired
+        var existingSession = notebook.getSession(self.type());
+        if (existingSession) {
+          notebook.restartSession(existingSession, callback);
         } else {
-          self.checkStatus();
+          notebook.createSession(new Session(vm, {'type': self.type()}), callback);
         }
+      }
+      else if (data.status == -3) { // Statement expired
+        self.status('expired');
+      }
+      else if (data.status == 401) { // Auth required
+        self.status('expired');
+        $(document).trigger("showAuthModal", {'type': self.type(), 'callback': self.execute});
+      }
+      else if (data.status == 1 || data.status == -1) {
+        self.status('failed');
+        var match = ERROR_REGEX.exec(data.message);
+        self.errors.push({
+          message: data.message,
+          line: match === null ? null : parseInt(match[1]) - 1
+        });
       } else {
-        self._ajaxError(data, self.execute);
+        $(document).trigger("error", data.message);
+        self.status('failed');
       }
-    }).fail(function (xhr, textStatus, errorThrown) {
-      $(document).trigger("error", xhr.responseText);
-      self.status('failed');
-    });
-  };
+    };
 
-  self.fetchResult = function (rows, startOver) {
-    if (typeof startOver == "undefined") {
-      startOver = true;
-    }
-    self.fetchResultData(rows, startOver);
-    //self.fetchResultMetadata(rows);
-  };
+    self.lastExecuted = 0;
 
-  self.fetchResultData = function (rows, startOver) {
-    $.post("/notebook/api/fetch_result_data", {
-      notebook: ko.mapping.toJSON(notebook.getContext()),
-      snippet: ko.mapping.toJSON(self.getContext()),
-      rows: rows,
-      startOver: startOver
-    }, function (data) {
-      if (data.status == 0) {
-        self.loadData(data, rows);
-      } else {
-        self._ajaxError(data);
-        $(document).trigger("renderDataError", {snippet: self});
+    self.execute = function () {
+      var now = (new Date()).getTime(); // We don't allow fast clicks
+      if (self.status() == 'running' || self.status() == 'loading' || now - self.lastExecuted < 1000) {
+        return;
       }
-    }).fail(function (xhr, textStatus, errorThrown) {
-      $(document).trigger("error", xhr.responseText);
-    });
-  };
+      self.previousChartOptions = {
+        chartScope: typeof self.chartScope() !== "undefined" ? self.chartX() : self.previousChartOptions.chartX,
+        chartX: typeof self.chartX() !== "undefined" ? self.chartX() : self.previousChartOptions.chartX,
+        chartYSingle: typeof self.chartYSingle() !== "undefined" ? self.chartYSingle() : self.previousChartOptions.chartYSingle,
+        chartMapLabel: typeof self.chartMapLabel() !== "undefined" ? self.chartMapLabel() : self.previousChartOptions.chartMapLabel,
+        chartYMulti: typeof self.chartYMulti() !== "undefined" ? self.chartYMulti() : self.previousChartOptions.chartYMulti
+      };
+      $(document).trigger("executeStarted", self);
+      self.lastExecuted = now;
+      $(".jHueNotify").hide();
+      logGA('/execute/' + self.type());
+
+      self.status('running');
+      self.errors([]);
+      self.result.logLines = 0;
+      self.progress(0);
+      self.jobs([]);
 
-  self.loadData = function (data, rows) {
-    rows -= data.result.data.length;
+      if (self.result.fetchedOnce()) {
+        self.close();
+      }
 
-    var _initialIndex = self.result.data().length;
-    var _tempData = [];
-    $.each(data.result.data, function (index, row) {
-      row.unshift(_initialIndex + index + 1);
-      self.result.data.push(row);
-      _tempData.push(row);
-    });
+      $.post("/notebook/api/execute", {
+        notebook: ko.mapping.toJSON(notebook.getContext()),
+        snippet: ko.mapping.toJSON(self.getContext())
+      }, function (data) {
+        if (data.status == 0) {
+          self.result.clear();
+          self.result.handle(data.handle);
+          self.result.hasResultset(data.handle.has_result_set);
+          if (data.handle.sync) {
+            self.loadData(data.handle, 100);
+            self.status('success');
+            self.progress(100);
+          } else {
+            self.checkStatus();
+          }
+        } else {
+          self._ajaxError(data, self.execute);
+        }
+      }).fail(function (xhr, textStatus, errorThrown) {
+        $(document).trigger("error", xhr.responseText);
+        self.status('failed');
+      });
+    };
+
+    self.fetchResult = function (rows, startOver) {
+      if (typeof startOver == "undefined") {
+        startOver = true;
+      }
+      self.fetchResultData(rows, startOver);
+      //self.fetchResultMetadata(rows);
+    };
+
+    self.fetchResultData = function (rows, startOver) {
+      $.post("/notebook/api/fetch_result_data", {
+        notebook: ko.mapping.toJSON(notebook.getContext()),
+        snippet: ko.mapping.toJSON(self.getContext()),
+        rows: rows,
+        startOver: startOver
+      }, function (data) {
+        if (data.status == 0) {
+          self.loadData(data, rows);
+        } else {
+          self._ajaxError(data);
+          $(document).trigger("renderDataError", {snippet: self});
+        }
+      }).fail(function (xhr, textStatus, errorThrown) {
+        $(document).trigger("error", xhr.responseText);
+      });
+    };
 
-    self.result.images(typeof data.result.images != "undefined" && data.result.images != null ? data.result.images : []);
+    self.loadData = function (data, rows) {
+      rows -= data.result.data.length;
 
-    $(document).trigger("renderData", {data: _tempData, snippet: self, initial: _initialIndex == 0});
+      var _initialIndex = self.result.data().length;
+      var _tempData = [];
+      $.each(data.result.data, function (index, row) {
+        row.unshift(_initialIndex + index + 1);
+        self.result.data.push(row);
+        _tempData.push(row);
+      });
 
-    if (! self.result.fetchedOnce()) {
-      data.result.meta.unshift({type: "INT_TYPE", name: "", comment: null});
-      self.result.meta(data.result.meta);
-      self.result.type(data.result.type);
-      self.result.fetchedOnce(true);
-    }
+      self.result.images(typeof data.result.images != "undefined" && data.result.images != null ? data.result.images : []);
 
-    if (data.result.has_more && rows > 0) {
-      setTimeout(function () {
-        self.fetchResultData(rows, false);
-      }, 500);
-    } else if (notebook.snippets()[notebook.snippets().length - 1] == self) {
-      notebook.newSnippet();
-    }
-  };
+      $(document).trigger("renderData", {data: _tempData, snippet: self, initial: _initialIndex == 0});
 
-  self.fetchResultMetadata = function () {
-    $.post("/notebook/api/fetch_result_metadata", {
-      notebook: ko.mapping.toJSON(notebook.getContext()),
-      snippet: ko.mapping.toJSON(self.getContext())
-    }, function (data) {
-      if (data.status == 0) {
+      if (! self.result.fetchedOnce()) {
+        data.result.meta.unshift({type: "INT_TYPE", name: "", comment: null});
         self.result.meta(data.result.meta);
-      } else {
-        $(document).trigger("error", data.message);
+        self.result.type(data.result.type);
+        self.result.fetchedOnce(true);
       }
-    }).fail(function (xhr, textStatus, errorThrown) {
-      $(document).trigger("error", xhr.responseText);
-      self.status('failed');
-    });
-  };
 
-  self.checkStatus = function () {
-    $.post("/notebook/api/check_status", {
-      notebook: ko.mapping.toJSON(notebook.getContext()),
-      snippet: ko.mapping.toJSON(self.getContext())
-    }, function (data) {
-      if (data.status == 0) {
-        self.status(data.query_status.status);
-        self.getLogs();
+      if (data.result.has_more && rows > 0) {
+        setTimeout(function () {
+          self.fetchResultData(rows, false);
+        }, 500);
+      } else if (notebook.snippets()[notebook.snippets().length - 1] == self) {
+        notebook.newSnippet();
+      }
+    };
 
-        if (self.status() == 'running' || self.status() == 'starting') {
-          self.result.endTime(new Date());
-          self.checkStatusTimeout = setTimeout(self.checkStatus, 1000);
-        }
-        else if (self.status() == 'available') {
-          self.fetchResult(100);
-          self.progress(100);
-        }
-        else if (self.status() == 'success') {
-          self.progress(99);
+    self.fetchResultMetadata = function () {
+      $.post("/notebook/api/fetch_result_metadata", {
+        notebook: ko.mapping.toJSON(notebook.getContext()),
+        snippet: ko.mapping.toJSON(self.getContext())
+      }, function (data) {
+        if (data.status == 0) {
+          self.result.meta(data.result.meta);
+        } else {
+          $(document).trigger("error", data.message);
         }
-      } else {
-        self._ajaxError(data);
-      }
-    }).fail(function (xhr, textStatus, errorThrown) {
-      $(document).trigger("error", xhr.responseText);
-      self.status('failed');
-    });
-  };
+      }).fail(function (xhr, textStatus, errorThrown) {
+        $(document).trigger("error", xhr.responseText);
+        self.status('failed');
+      });
+    };
 
-  self.cancel = function () {
-    if (self.checkStatusTimeout != null) {
-      clearTimeout(self.checkStatusTimeout);
-      self.checkStatusTimeout = null;
-    }
+    self.checkStatus = function () {
+      $.post("/notebook/api/check_status", {
+        notebook: ko.mapping.toJSON(notebook.getContext()),
+        snippet: ko.mapping.toJSON(self.getContext())
+      }, function (data) {
+        if (data.status == 0) {
+          self.status(data.query_status.status);
+          self.getLogs();
+
+          if (self.status() == 'running' || self.status() == 'starting') {
+            self.result.endTime(new Date());
+            self.checkStatusTimeout = setTimeout(self.checkStatus, 1000);
+          }
+          else if (self.status() == 'available') {
+            self.fetchResult(100);
+            self.progress(100);
+          }
+          else if (self.status() == 'success') {
+            self.progress(99);
+          }
+        } else {
+          self._ajaxError(data);
+        }
+      }).fail(function (xhr, textStatus, errorThrown) {
+        $(document).trigger("error", xhr.responseText);
+        self.status('failed');
+      });
+    };
 
-    $.post("/notebook/api/cancel_statement", {
-      notebook: ko.mapping.toJSON(notebook.getContext()),
-      snippet: ko.mapping.toJSON(self.getContext())
-    }, function (data) {
-      if (data.status == 0) {
-        self.status('canceled');
-      } else {
-        self._ajaxError(data);
+    self.cancel = function () {
+      if (self.checkStatusTimeout != null) {
+        clearTimeout(self.checkStatusTimeout);
+        self.checkStatusTimeout = null;
       }
-    }).fail(function (xhr, textStatus, errorThrown) {
-      $(document).trigger("error", xhr.responseText);
-      self.status('failed');
-    });
-  };
 
-  self.close = function () {
-    if (self.checkStatusTimeout != null) {
-      clearTimeout(self.checkStatusTimeout);
-      self.checkStatusTimeout = null;
-    }
+      $.post("/notebook/api/cancel_statement", {
+        notebook: ko.mapping.toJSON(notebook.getContext()),
+        snippet: ko.mapping.toJSON(self.getContext())
+      }, function (data) {
+        if (data.status == 0) {
+          self.status('canceled');
+        } else {
+          self._ajaxError(data);
+        }
+      }).fail(function (xhr, textStatus, errorThrown) {
+        $(document).trigger("error", xhr.responseText);
+        self.status('failed');
+      });
+    };
 
-    $.post("/notebook/api/close_statement", {
-      notebook: ko.mapping.toJSON(notebook.getContext()),
-      snippet: ko.mapping.toJSON(self.getContext())
-    }, function (data) {
-      if (data.status == 0) {
-        // self.status('closed'); // Keep as 'running' as currently it happens before running a new query
-      } else {
-        self._ajaxError(data);
+    self.close = function () {
+      if (self.checkStatusTimeout != null) {
+        clearTimeout(self.checkStatusTimeout);
+        self.checkStatusTimeout = null;
       }
-    }).fail(function (xhr, textStatus, errorThrown) {
-      $(document).trigger("error", xhr.responseText);
-      self.status('failed');
-    });
-  };
 
-  self.getLogs = function () {
-    $.post("/notebook/api/get_logs", {
-      notebook: ko.mapping.toJSON(notebook.getContext()),
-      snippet: ko.mapping.toJSON(self.getContext()),
-      from: self.result.logLines,
-      jobs: ko.mapping.toJSON(self.jobs)
-    }, function (data) {
-      if (data.status == 1) { // Append errors to the logs
-        data.status = 0;
-        data.logs = data.message;
-      }
-      if (data.status == 0) {
-        if (data.logs.length > 0) {
-          var logs = data.logs.split("\n");
-          self.result.logLines += logs.length;
-          var oldLogs = self.result.logs();
-          if (oldLogs === "") {
-            self.result.logs(data.logs);
-          } else {
-            self.result.logs(oldLogs + "\n" + data.logs);
-          }
+      $.post("/notebook/api/close_statement", {
+        notebook: ko.mapping.toJSON(notebook.getContext()),
+        snippet: ko.mapping.toJSON(self.getContext())
+      }, function (data) {
+        if (data.status == 0) {
+          // self.status('closed'); // Keep as 'running' as currently it happens before running a new query
+        } else {
+          self._ajaxError(data);
         }
-        if (data.jobs.length > 0) {
-          self.jobs(data.jobs);
+      }).fail(function (xhr, textStatus, errorThrown) {
+        $(document).trigger("error", xhr.responseText);
+        self.status('failed');
+      });
+    };
+
+    self.getLogs = function () {
+      $.post("/notebook/api/get_logs", {
+        notebook: ko.mapping.toJSON(notebook.getContext()),
+        snippet: ko.mapping.toJSON(self.getContext()),
+        from: self.result.logLines,
+        jobs: ko.mapping.toJSON(self.jobs)
+      }, function (data) {
+        if (data.status == 1) { // Append errors to the logs
+          data.status = 0;
+          data.logs = data.message;
         }
-        self.progress(data.progress);
-      } else {
-        self._ajaxError(data);
-      }
-    }).fail(function (xhr, textStatus, errorThrown) {
-      $(document).trigger("error", xhr.responseText);
-      self.status('failed');
-    });
-  };
+        if (data.status == 0) {
+          if (data.logs.length > 0) {
+            var logs = data.logs.split("\n");
+            self.result.logLines += logs.length;
+            var oldLogs = self.result.logs();
+            if (oldLogs === "") {
+              self.result.logs(data.logs);
+            } else {
+              self.result.logs(oldLogs + "\n" + data.logs);
+            }
+          }
+          if (data.jobs.length > 0) {
+            self.jobs(data.jobs);
+          }
+          self.progress(data.progress);
+        } else {
+          self._ajaxError(data);
+        }
+      }).fail(function (xhr, textStatus, errorThrown) {
+        $(document).trigger("error", xhr.responseText);
+        self.status('failed');
+      });
+    };
 
-  self.autocompleter = new Autocompleter({
-    snippet: self,
-    assistHelper: self.getAssistHelper()
-  });
+    self.autocompleter = new Autocompleter({
+      snippet: self,
+      assistHelper: self.getAssistHelper()
+    });
 
-  self.type.subscribe(function () {
-    self.autocompleter.setAssistHelper(self.getAssistHelper());
-  });
+    self.type.subscribe(function () {
+      self.autocompleter.setAssistHelper(self.getAssistHelper());
+    });
 
-  self.init = function () {
-    if (self.status() == 'running') {
-      self.checkStatus();
-    }
+    self.init = function () {
+      if (self.status() == 'running') {
+        self.checkStatus();
+      }
 
-    if (self.status() == 'loading') {
-      self.status('failed');
-      self.progress(0);
-      self.jobs([]);
-    }
+      if (self.status() == 'loading') {
+        self.status('failed');
+        self.progress(0);
+        self.jobs([]);
+      }
+    };
   };
-};
 
-var Session = function(vm, session) {
-  var self = this;
-  ko.mapping.fromJS(session, {}, self);
+  var Session = function(vm, session) {
+    var self = this;
+    ko.mapping.fromJS(session, {}, self);
 
-  self.selectedSessionProperty = ko.observable('');
+    self.selectedSessionProperty = ko.observable('');
 
-  self.restarting = ko.observable(false);
+    self.restarting = ko.observable(false);
 
-  if (! ko.isObservable(self.properties)) {
-    self.properties = ko.observableArray();
-  }
-
-  self.availableNewProperties = ko.computed(function() {
-    var addedIndex = {};
-    $.each(self.properties(), function(index, property) {
-      addedIndex[property.name()] = true;
-    });
-    var result = $.grep(vm.availableSessionProperties(), function(property) {
-      return ! addedIndex[property.name];
-    });
-    return result;
-  });
-};
-
-var Notebook = function (vm, notebook) {
-  var self = this;
-
-  self.id = ko.observable(typeof notebook.id != "undefined" && notebook.id != null ? notebook.id : null);
-  self.uuid = ko.observable(typeof notebook.uuid != "undefined" && notebook.uuid != null ? notebook.uuid : UUID());
-  self.name = ko.observable(typeof notebook.name != "undefined" && notebook.name != null ? notebook.name : 'My Notebook');
-  self.description = ko.observable(typeof notebook.description != "undefined" && notebook.description != null ? notebook.description: '');
-  self.snippets = ko.observableArray();
-  self.selectedSnippet = ko.observable(vm.availableSnippets().length > 0 ? vm.availableSnippets()[0].type() : 'NO_SNIPPETS');
-  self.creatingSessionLocks = ko.observableArray();
-  self.sessions = ko.mapping.fromJS(typeof notebook.sessions != "undefined" && notebook.sessions != null ? notebook.sessions : [], {
-    create: function(value) {
-      return new Session(vm, value.data);
+    if (! ko.isObservable(self.properties)) {
+      self.properties = ko.observableArray();
     }
-  });
-
-  self.selectedDatabases = notebook.selectedDatabases != "undefined" && notebook.selectedDatabases != null ? notebook.selectedDatabases : {};
-  self.assistHelpers = {};
 
-  self.getAssistHelper = function (snippetType) {
-    if (! self.assistHelpers[snippetType]) {
-      self.assistHelpers[snippetType] = new AssistHelper({
-        notebook: self,
-        user: vm.user,
-        activeDatabase: self.selectedDatabases[snippetType]
+    self.availableNewProperties = ko.computed(function() {
+      var addedIndex = {};
+      $.each(self.properties(), function(index, property) {
+        addedIndex[property.name()] = true;
       });
-
-      self.assistHelpers[snippetType].activeDatabase.subscribe(function (newActiveDatabase) {
-        self.selectedDatabases[snippetType] = newActiveDatabase;
-      })
-    }
-    return self.assistHelpers[snippetType]
-  };
-
-  self.getSession = function (session_type) {
-    var _s = null;
-    $.each(self.sessions(), function (index, s) {
-      if (s.type() == session_type) {
-        _s = s;
-        return false;
-      }
+      var result = $.grep(vm.availableSessionProperties(), function(property) {
+        return ! addedIndex[property.name];
+      });
+      return result;
     });
-    return _s;
   };
 
-  self.getSnippets = function(type) {
-    return $.grep(self.snippets(), function (snippet) {
-      return snippet.type() == type;
+  var Notebook = function (vm, notebook) {
+    var self = this;
+
+    self.id = ko.observable(typeof notebook.id != "undefined" && notebook.id != null ? notebook.id : null);
+    self.uuid = ko.observable(typeof notebook.uuid != "undefined" && notebook.uuid != null ? notebook.uuid : UUID());
+    self.name = ko.observable(typeof notebook.name != "undefined" && notebook.name != null ? notebook.name : 'My Notebook');
+    self.description = ko.observable(typeof notebook.description != "undefined" && notebook.description != null ? notebook.description: '');
+    self.snippets = ko.observableArray();
+    self.selectedSnippet = ko.observable(vm.availableSnippets().length > 0 ? vm.availableSnippets()[0].type() : 'NO_SNIPPETS');
+    self.creatingSessionLocks = ko.observableArray();
+    self.sessions = ko.mapping.fromJS(typeof notebook.sessions != "undefined" && notebook.sessions != null ? notebook.sessions : [], {
+      create: function(value) {
+        return new Session(vm, value.data);
+      }
     });
-  };
 
-  self.restartSession = function (session, callback) {
-    if (session.restarting()) {
-      return;
-    }
-    session.restarting(true);
-    var snippets = self.getSnippets(session.type());
+    self.selectedDatabases = notebook.selectedDatabases != "undefined" && notebook.selectedDatabases != null ? notebook.selectedDatabases : {};
+    self.assistHelpers = {};
 
-    $.each(snippets, function(index, snippet) {
-      snippet.status('loading');
-    });
+    self.getAssistHelper = function (snippetType) {
+      if (! self.assistHelpers[snippetType]) {
+        self.assistHelpers[snippetType] = new AssistHelper({
+          notebook: self,
+          user: vm.user,
+          activeDatabase: self.selectedDatabases[snippetType]
+        });
 
-    var sessionJson = ko.mapping.toJSON(session);
+        self.assistHelpers[snippetType].activeDatabase.subscribe(function (newActiveDatabase) {
+          self.selectedDatabases[snippetType] = newActiveDatabase;
+        })
+      }
+      return self.assistHelpers[snippetType]
+    };
 
-    self.closeSession (session, true, function() {
-      self.createSession(session, function () {
-        $.each(snippets, function(index, snippet) {
-          snippet.status('ready');
-        });
-        session.restarting(false);
-        if (callback) {
-          callback();
+    self.getSession = function (session_type) {
+      var _s = null;
+      $.each(self.sessions(), function (index, s) {
+        if (s.type() == session_type) {
+          _s = s;
+          return false;
         }
-      }, function () {
-        session.restarting(false);
       });
-    });
-  };
+      return _s;
+    };
+
+    self.getSnippets = function(type) {
+      return $.grep(self.snippets(), function (snippet) {
+        return snippet.type() == type;
+      });
+    };
 
-  self.addSession = function (session) {
-    var toRemove = []
-    $.each(self.sessions(), function (index, s) {
-      if (s.type() == session.type()) {
-        toRemove.push(s);
+    self.restartSession = function (session, callback) {
+      if (session.restarting()) {
+        return;
       }
-    });
+      session.restarting(true);
+      var snippets = self.getSnippets(session.type());
 
-    $.each(toRemove, function (index, s) {
-      self.sessions.remove(s);
-    });
+      $.each(snippets, function(index, snippet) {
+        snippet.status('loading');
+      });
 
-    self.sessions.push(session);
-  };
+      var sessionJson = ko.mapping.toJSON(session);
 
-  self.addSnippet = function (snippet, skipSession) {
-    var _snippet = new Snippet(vm, self, snippet);
-    self.snippets.push(_snippet);
-
-    if (self.getSession(_snippet.type()) == null && typeof skipSession == "undefined") {
-      window.setTimeout(function() {
-        _snippet.status('loading');
-        self.createSession(new Session(vm, {'type': _snippet.type()}));
-      }, 200);
-    } else {
-      _snippet.status('ready');
-    }
+      self.closeSession (session, true, function() {
+        self.createSession(session, function () {
+          $.each(snippets, function(index, snippet) {
+            snippet.status('ready');
+          });
+          session.restarting(false);
+          if (callback) {
+            callback();
+          }
+        }, function () {
+          session.restarting(false);
+        });
+      });
+    };
 
-    _snippet.init();
-    return _snippet;
-  };
+    self.addSession = function (session) {
+      var toRemove = []
+      $.each(self.sessions(), function (index, s) {
+        if (s.type() == session.type()) {
+          toRemove.push(s);
+        }
+      });
 
-  self.createSession = function (session, callback, failCallback) {
-    if (self.creatingSessionLocks().indexOf(session.type()) != -1) { // Create one type of session max
-      return;
-    } else {
-      self.creatingSessionLocks.push(session.type());
-    }
+      $.each(toRemove, function (index, s) {
+        self.sessions.remove(s);
+      });
 
-    $.each(self.getSnippets(session.type()), function(index, snippet) {
-      snippet.status('loading');
-    });
+      self.sessions.push(session);
+    };
 
-    var fail = function (message) {
-      $.each(self.getSnippets(session.type()), function(index, snippet) {
-        snippet.status('failed');
-      });
-      $(document).trigger("error", message);
-      if (failCallback) {
-        failCallback();
+    self.addSnippet = function (snippet, skipSession) {
+      var _snippet = new Snippet(vm, self, snippet);
+      self.snippets.push(_snippet);
+
+      if (self.getSession(_snippet.type()) == null && typeof skipSession == "undefined") {
+        window.setTimeout(function() {
+          _snippet.status('loading');
+          self.createSession(new Session(vm, {'type': _snippet.type()}));
+        }, 200);
+      } else {
+        _snippet.status('ready');
       }
+
+      _snippet.init();
+      return _snippet;
     };
 
-    $.post("/notebook/api/create_session", {
-      notebook: ko.mapping.toJSON(self.getContext()),
-      session: ko.mapping.toJSON(session) // e.g. {'type': 'hive', 'properties': [{'name': driverCores', 'value', '2'}]}
-    }, function (data) {
-      if (data.status == 0) {
-        ko.mapping.fromJS(data.session, {}, session);
-        if (self.getSession(session.type()) == null) {
-          self.addSession(session);
-        }
+    self.createSession = function (session, callback, failCallback) {
+      if (self.creatingSessionLocks().indexOf(session.type()) != -1) { // Create one type of session max
+        return;
+      } else {
+        self.creatingSessionLocks.push(session.type());
+      }
+
+      $.each(self.getSnippets(session.type()), function(index, snippet) {
+        snippet.status('loading');
+      });
+
+      var fail = function (message) {
         $.each(self.getSnippets(session.type()), function(index, snippet) {
-          snippet.status('ready');
+          snippet.status('failed');
         });
-        if (callback) {
-          setTimeout(callback, 500);
+        $(document).trigger("error", message);
+        if (failCallback) {
+          failCallback();
         }
-      } else if (data.status == 401) {
-        $(document).trigger("showAuthModal", {'type': session.type()});
-      }
-      else {
-        fail(data.message);
-      }
-    }).fail(function (xhr) {
-      fail(xhr.responseText);
-    }).complete(function(xhr, status) {
-      self.creatingSessionLocks.remove(session.type());
-    })
-  };
-
-  self.authSession = function () {
-    self.createSession(new Session(vm, {
-          'type': vm.authSessionType(),
-          'properties': [
-            {'name': 'user', 'value': vm.authSessionUsername()},
-            {'name': 'password', 'value': vm.authSessionPassword()}
-          ]
-        }),
-        vm.authSessionCallback()  // On new session we don't automatically execute the snippet after the aut. On session expiration we do or we refresh assist DB when login-in.
-    );
-  };
+      };
+
+      $.post("/notebook/api/create_session", {
+        notebook: ko.mapping.toJSON(self.getContext()),
+        session: ko.mapping.toJSON(session) // e.g. {'type': 'hive', 'properties': [{'name': driverCores', 'value', '2'}]}
+      }, function (data) {
+        if (data.status == 0) {
+          ko.mapping.fromJS(data.session, {}, session);
+          if (self.getSession(session.type()) == null) {
+            self.addSession(session);
+          }
+          $.each(self.getSnippets(session.type()), function(index, snippet) {
+            snippet.status('ready');
+          });
+          if (callback) {
+            setTimeout(callback, 500);
+          }
+        } else if (data.status == 401) {
+          $(document).trigger("showAuthModal", {'type': session.type()});
+        }
+        else {
+          fail(data.message);
+        }
+      }).fail(function (xhr) {
+        fail(xhr.responseText);
+      }).complete(function(xhr, status) {
+        self.creatingSessionLocks.remove(session.type());
+      })
+    };
 
-  self.newSnippet = function (type) {
-    if (type) {
-      self.selectedSnippet(type);
-    }
-    var snippet = self.addSnippet({
-      type: self.selectedSnippet(),
-      result: {}
-    });
+    self.authSession = function () {
+      self.createSession(new Session(vm, {
+            'type': vm.authSessionType(),
+            'properties': [
+              {'name': 'user', 'value': vm.authSessionUsername()},
+              {'name': 'password', 'value': vm.authSessionPassword()}
+            ]
+          }),
+          vm.authSessionCallback()  // On new session we don't automatically execute the snippet after the aut. On session expiration we do or we refresh assist DB when login-in.
+      );
+    };
 
-    window.setTimeout(function () {
-      var lastSnippet = snippet;
-      if (lastSnippet.ace() != null) {
-        lastSnippet.ace().focus();
+    self.newSnippet = function (type) {
+      if (type) {
+        self.selectedSnippet(type);
       }
-    }, 100);
+      var snippet = self.addSnippet({
+        type: self.selectedSnippet(),
+        result: {}
+      });
 
-    logGA('/add_snippet/' + type);
-    return snippet;
-  };
+      window.setTimeout(function () {
+        var lastSnippet = snippet;
+        if (lastSnippet.ace() != null) {
+          lastSnippet.ace().focus();
+        }
+      }, 100);
 
-  self.getContext = function() {
-   return {
-       id: self.id,
-       uuid: self.uuid,
-       sessions: self.sessions
+      logGA('/add_snippet/' + type);
+      return snippet;
     };
-  };
 
-  // Init
-  if (notebook.snippets) {
-    $.each(notebook.snippets, function (index, snippet) {
-      self.addSnippet(snippet);
-    });
-  }
+    self.getContext = function() {
+     return {
+         id: self.id,
+         uuid: self.uuid,
+         sessions: self.sessions
+      };
+    };
 
-  self.save = function () {
-    $.post("/notebook/api/notebook/save", {
-      "notebook": ko.mapping.toJSON(self, SPARK_MAPPING)
-    }, function (data) {
-      if (data.status == 0) {
-        self.id(data.id);
-        $(document).trigger("info", data.message);
-        if (window.location.search.indexOf("notebook") == -1) {
-          window.location.hash = '#notebook=' + data.id;
+    // Init
+    if (notebook.snippets) {
+      $.each(notebook.snippets, function (index, snippet) {
+        self.addSnippet(snippet);
+      });
+    }
+
+    self.save = function () {
+      $.post("/notebook/api/notebook/save", {
+        "notebook": ko.mapping.toJSON(self, SPARK_MAPPING)
+      }, function (data) {
+        if (data.status == 0) {
+          self.id(data.id);
+          $(document).trigger("info", data.message);
+          if (window.location.search.indexOf("notebook") == -1) {
+            window.location.hash = '#notebook=' + data.id;
+          }
         }
-      }
-      else {
-        $(document).trigger("error", data.message);
-      }
-    }).fail(function (xhr, textStatus, errorThrown) {
-      $(document).trigger("error", xhr.responseText);
-    });
-  };
+        else {
+          $(document).trigger("error", data.message);
+        }
+      }).fail(function (xhr, textStatus, errorThrown) {
+        $(document).trigger("error", xhr.responseText);
+      });
+    };
 
-  self.close = function () {
-    $.post("/notebook/api/notebook/close", {
-      "notebook": ko.mapping.toJSON(self, SPARK_MAPPING)
-    });
-  };
+    self.close = function () {
+      $.post("/notebook/api/notebook/close", {
+        "notebook": ko.mapping.toJSON(self, SPARK_MAPPING)
+      });
+    };
 
-  self.clearResults = function () {
-    $.each(self.snippets(), function (index, snippet) {
-      snippet.result.clear();
-      snippet.status('ready');
-    });
-  };
+    self.clearResults = function () {
+      $.each(self.snippets(), function (index, snippet) {
+        snippet.result.clear();
+        snippet.status('ready');
+      });
+    };
 
-  self.closeAndRemoveSession = function (session) {
-    self.closeSession (session, false, function() {
-      self.sessions.remove(session);
-    });
-  };
+    self.closeAndRemoveSession = function (session) {
+      self.closeSession (session, false, function() {
+        self.sessions.remove(session);
+      });
+    };
 
-  self.closeSession = function (session, silent, callback) {
-    $.post("/notebook/api/close_session", {
-      session: ko.mapping.toJSON(session)
-    }, function (data) {
-      if (!silent && data.status != 0 && data.status != -2) {
-        $(document).trigger("error", data.message);
-        return;
-      }
+    self.closeSession = function (session, silent, callback) {
+      $.post("/notebook/api/close_session", {
+        session: ko.mapping.toJSON(session)
+      }, function (data) {
+        if (!silent && data.status != 0 && data.status != -2) {
+          $(document).trigger("error", data.message);
+          return;
+        }
 
-      if (callback) {
-        callback();
-      }
-    }).fail(function (xhr) {
-      if (!silent) {
-        $(document).trigger("error", xhr.responseText);
-      }
-    });
-  };
-};
-
-
-function EditorViewModel(notebooks, options) {
-  var self = this;
-
-  self.user = options.user;
-  self.notebooks = ko.observableArray();
-  self.selectedNotebook = ko.observable();
-  self.combinedContent = ko.observable();
-  self.isPlayerMode = ko.observable(false);
-
-  self.displayCombinedContent = function () {
-    if (! self.selectedNotebook()) {
-      self.combinedContent('');
-    } else {
-      var statements = '';
-      $.each(self.selectedNotebook().snippets(), function (index, snippet) {
-        if (snippet.statement()) {
-          if (statements) {
-            statements += '\n\n';
-          }
-          statements += snippet.statement();
+        if (callback) {
+          callback();
+        }
+      }).fail(function (xhr) {
+        if (!silent) {
+          $(document).trigger("error", xhr.responseText);
         }
       });
-      self.combinedContent(statements);
-    }
-    $("#combinedContentModal").modal("show");
+    };
   };
 
-  self.isEditing = ko.observable(false);
-  self.isEditing.subscribe(function (newVal) {
-    $(document).trigger("editingToggled");
-  });
-  self.toggleEditing = function () {
-    self.isEditing(!self.isEditing());
-  };
 
-  self.authSessionUsername = ko.observable(); // UI popup
-  self.authSessionPassword = ko.observable();
-  self.authSessionType = ko.observable();
-  self.authSessionCallback = ko.observable();
+  function EditorViewModel(notebooks, options) {
+    var self = this;
 
-  self.removeSnippetConfirmation = ko.observable();
+    self.user = options.user;
+    self.notebooks = ko.observableArray();
+    self.selectedNotebook = ko.observable();
+    self.combinedContent = ko.observable();
+    self.isPlayerMode = ko.observable(false);
 
-  self.removeSnippet = function (notebook, snippet) {
-    var hasContent = snippet.statement_raw().length > 0;
-    if (!hasContent) {
-      $.each(snippet.properties(), function (key, value) {
-        hasContent = hasContent || (ko.isObservable(value) && value().length > 0);
-      });
-    }
-    if (hasContent) {
-      self.removeSnippetConfirmation({ notebook: notebook, snippet: snippet });
-      $("#removeSnippetModal").modal("show");
-    }
-    else {
-      notebook.snippets.remove(snippet);
-      window.setTimeout(function () {
-        $(document).trigger("editorSizeChanged");
-      }, 100);
-    }
-  };
+    self.displayCombinedContent = function () {
+      if (! self.selectedNotebook()) {
+        self.combinedContent('');
+      } else {
+        var statements = '';
+        $.each(self.selectedNotebook().snippets(), function (index, snippet) {
+          if (snippet.statement()) {
+            if (statements) {
+              statements += '\n\n';
+            }
+            statements += snippet.statement();
+          }
+        });
+        self.combinedContent(statements);
+      }
+      $("#combinedContentModal").modal("show");
+    };
+
+    self.isEditing = ko.observable(false);
+    self.isEditing.subscribe(function (newVal) {
+      $(document).trigger("editingToggled");
+    });
+    self.toggleEditing = function () {
+      self.isEditing(!self.isEditing());
+    };
 
-  self.assistAvailable = ko.observable(options.assistAvailable);
+    self.authSessionUsername = ko.observable(); // UI popup
+    self.authSessionPassword = ko.observable();
+    self.authSessionType = ko.observable();
+    self.authSessionCallback = ko.observable();
 
-  self.isLeftPanelVisible = ko.observable(self.assistAvailable() && $.totalStorage('spark_left_panel_visible') != null && $.totalStorage('spark_left_panel_visible'));
+    self.removeSnippetConfirmation = ko.observable();
 
-  self.isLeftPanelVisible.subscribe(function(newValue) {
-    $.totalStorage('spark_left_panel_visible', newValue);
-  });
+    self.removeSnippet = function (notebook, snippet) {
+      var hasContent = snippet.statement_raw().length > 0;
+      if (!hasContent) {
+        $.each(snippet.properties(), function (key, value) {
+          hasContent = hasContent || (ko.isObservable(value) && value().length > 0);
+        });
+      }
+      if (hasContent) {
+        self.removeSnippetConfirmation({ notebook: notebook, snippet: snippet });
+        $("#removeSnippetModal").modal("show");
+      }
+      else {
+        notebook.snippets.remove(snippet);
+        window.setTimeout(function () {
+          $(document).trigger("editorSizeChanged");
+        }, 100);
+      }
+    };
 
-  self.availableSnippets = ko.mapping.fromJS(options.languages);
+    self.assistAvailable = ko.observable(options.assistAvailable);
 
-  self.getSnippetViewSettings = function (snippetType) {
-    if (options.snippetViewSettings[snippetType]) {
-      return options.snippetViewSettings[snippetType];
-    }
-    return options.snippetViewSettings.default;
-  };
+    self.isLeftPanelVisible = ko.observable(self.assistAvailable() && $.totalStorage('spark_left_panel_visible') != null && $.totalStorage('spark_left_panel_visible'));
 
-  self.availableSessionProperties = ko.computed(function () { // Only Spark
-    return ko.utils.arrayFilter(options.session_properties, function (item) {
-        return item.name != ''; // Could filter out the ones already selected + yarn only or not
-      });
-  });
-  self.getSessionProperties = function(name) {
-    var _prop = null;
-    $.each(options.session_properties, function(index, prop) {
-      if (prop.name == name) {
-        _prop = prop;
-        return;
-      }
+    self.isLeftPanelVisible.subscribe(function(newValue) {
+      $.totalStorage('spark_left_panel_visible', newValue);
     });
-    return _prop;
-  };
 
-  self.getSnippetName = function(snippetType)  {
-    var availableSnippets = self.availableSnippets();
-    for (var i = 0; i < availableSnippets.length; i++) {
-      if (availableSnippets[i].type() === snippetType) {
-        return availableSnippets[i].name();
-      }
-    }
-    return '';
-  };
+    self.availableSnippets = ko.mapping.fromJS(options.languages);
 
-  self.init = function () {
-    $.each(notebooks, function (index, notebook) {
-      self.loadNotebook(notebook);
-      if (self.selectedNotebook() == null) {
-        self.selectedNotebook(self.notebooks()[0]);
+    self.getSnippetViewSettings = function (snippetType) {
+      if (options.snippetViewSettings[snippetType]) {
+        return options.snippetViewSettings[snippetType];
       }
+      return options.snippetViewSettings.default;
+    };
+
+    self.availableSessionProperties = ko.computed(function () { // Only Spark
+      return ko.utils.arrayFilter(options.session_properties, function (item) {
+          return item.name != ''; // Could filter out the ones already selected + yarn only or not
+        });
     });
-  };
+    self.getSessionProperties = function(name) {
+      var _prop = null;
+      $.each(options.session_properties, function(index, prop) {
+        if (prop.name == name) {
+          _prop = prop;
+          return;
+        }
+      });
+      return _prop;
+    };
 
-  self.loadNotebook = function (notebook) {
-    var _n = new Notebook(self, notebook);
-    self.notebooks.push(_n);
-    if (_n.snippets().length > 0) {
-      _n.selectedSnippet(_n.snippets()[_n.snippets().length - 1].type());
-      _n.snippets().forEach(function(snippet){
-        snippet.statement_raw.valueHasMutated();
+    self.getSnippetName = function(snippetType)  {
+      var availableSnippets = self.availableSnippets();
+      for (var i = 0; i < availableSnippets.length; i++) {
+        if (availableSnippets[i].type() === snippetType) {
+          return availableSnippets[i].name();
+        }
+      }
+      return '';
+    };
+
+    self.init = function () {
+      $.each(notebooks, function (index, notebook) {
+        self.loadNotebook(notebook);
+        if (self.selectedNotebook() == null) {
+          self.selectedNotebook(self.notebooks()[0]);
+        }
       });
-    }
-  };
+    };
 
-  self.newNotebook = function () {
-    var notebook = new Notebook(self, {});
-    self.notebooks.push(notebook);
-    self.selectedNotebook(notebook);
-    return notebook;
-  };
+    self.loadNotebook = function (notebook) {
+      var _n = new Notebook(self, notebook);
+      self.notebooks.push(_n);
+      if (_n.snippets().length > 0) {
+        _n.selectedSnippet(_n.snippets()[_n.snippets().length - 1].type());
+        _n.snippets().forEach(function(snippet){
+          snippet.statement_raw.valueHasMutated();
+        });
+      }
+    };
 
-  self.saveNotebook = function () {
-    self.selectedNotebook().save();
-  };
-}
+    self.newNotebook = function () {
+      var notebook = new Notebook(self, {});
+      self.notebooks.push(notebook);
+      self.selectedNotebook(notebook);
+      return notebook;
+    };
+
+    self.saveNotebook = function () {
+      self.selectedNotebook().save();
+    };
+  }
 
 
-function logGA(page) {
-  if (typeof trackOnGA == 'function') {
-    trackOnGA('notebook/' + page);
+  function logGA(page) {
+    if (typeof trackOnGA == 'function') {
+      trackOnGA('notebook/' + page);
+    }
   }
-}
 
-if (typeof define !== "undefined") {
-  define(function () {
-    return EditorViewModel;
-  });
-}
+  return EditorViewModel;
+}));

+ 38 - 19
desktop/libs/notebook/src/notebook/templates/editor_components.mako

@@ -39,14 +39,6 @@ from desktop.views import _ko
 <script src="${ static('desktop/ext/js/jquery/plugins/jquery.hotkeys.js') }"></script>
 
 <script src="${ static('desktop/ext/js/bootstrap-editable.min.js') }" type="text/javascript" charset="utf-8"></script>
-<script src="${ static('desktop/ext/js/jquery/plugins/jquery-ui-1.10.4.draggable-droppable-sortable.min.js') }" type="text/javascript" charset="utf-8"></script>
-<script src="${ static('desktop/ext/js/knockout.min.js') }" type="text/javascript" charset="utf-8"></script>
-<script src="${ static('desktop/ext/js/knockout-mapping.min.js') }" type="text/javascript" charset="utf-8"></script>
-<script src="${ static('desktop/ext/js/knockout-sortable.min.js') }" type="text/javascript" charset="utf-8"></script>
-<script src="${ static('desktop/ext/js/knockout-deferred-updates.min.js') }" type="text/javascript" charset="utf-8"></script>
-<script src="${ static('desktop/js/ko.editable.js') }" type="text/javascript" charset="utf-8"></script>
-<script src="${ static('desktop/js/hue.utils.js') }"></script>
-<script src="${ static('desktop/js/ko.hue-bindings.js') }" type="text/javascript" charset="utf-8"></script>
 <script src="${ static('desktop/ext/chosen/chosen.jquery.min.js') }" type="text/javascript" charset="utf-8"></script>
 
 <script src="${ static('notebook/js/assist.js') }" type="text/javascript" charset="utf-8"></script>
@@ -86,9 +78,6 @@ from desktop.views import _ko
 <script src="${ static('desktop/js/nv.d3.scatter.js') }" type="text/javascript" charset="utf-8"></script>
 <script src="${ static('desktop/js/nv.d3.scatterChart.js') }" type="text/javascript" charset="utf-8"></script>
 
-<script src="${ static('desktop/js/ko.charts.js') }" type="text/javascript" charset="utf-8"></script>
-<script src="${ static('desktop/js/ko.hue-bindings.js') }"> type="text/javascript" charset="utf-8"</script>
-
 <script src="${ static('desktop/ext/select2/select2.min.js') }" type="text/javascript" charset="utf-8"></script>
 
 <!--[if IE 9]>
@@ -99,7 +88,6 @@ from desktop.views import _ko
 <script src="${ static('desktop/js/ace/ace.js') }"></script>
 <script src="${ static('desktop/js/ace/ext-language_tools.js') }"></script>
 <script src="${ static('desktop/js/ace.extended.js') }"></script>
-<script src="${ static('desktop/js/assistHelper.js') }" type="text/javascript" charset="utf-8"></script>
 <script src="${ static('desktop/js/autocomplete.js') }" type="text/javascript" charset="utf-8"></script>
 
 <script type="text/x-mathjax-config">
@@ -112,16 +100,38 @@ from desktop.views import _ko
 
 <script type="text/javascript" src="//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
 
+<script src="${ static('desktop/js/hue.utils.js') }"></script>
 
-<script  src="${ static('desktop/ext/js/require.js') }"></script>
+
+<script src="${ static('desktop/ext/js/require.js') }"></script>
 <script>
+  define('jquery', [], function() {
+    return jQuery;
+  });
   require.config({
     baseUrl: "${ static('') }",
     paths: {
-      "jquery": "desktop/ext/js/jquery/jquery-2.1.1.min",
+      "jquery.ui.sortable": "desktop/ext/js/jquery/plugins/jquery-ui-1.10.4.draggable-droppable-sortable.min",
       "knockout": "desktop/ext/js/knockout.min",
+      "ko.charts" : "desktop/js/ko.charts",
+      "knockout-mapping" : "desktop/ext/js/knockout-mapping.min",
+      "knockout-sortable" : "desktop/ext/js/knockout-sortable.min",
+      "knockout-deferred-updates" : "desktop/ext/js/knockout-deferred-updates.min",
+      "ko.editable" : "desktop/js/ko.editable",
+      "ko.hue-bindings" : "desktop/js/ko.hue-bindings"
     },
     shim: {
+      "knockout": { exports: "ko" },
+      "knockout-mapping": { deps: ["knockout"] },
+      "knockout-sortable": { deps: ["knockout", "jquery", "jquery.ui.sortable"] },
+      "knockout-deferred-updates": { deps: ["knockout"] },
+      "ko.editable": { deps: ["knockout"] },
+      "ace.extended": { deps: ["ace"] },
+      "ace.ext-language-tools": { deps: ["ace"] }
+    },
+    deps: ["knockout", "knockout-mapping"],
+    callback: function(ko, mapping) {
+      ko.mapping = mapping;
     }
   });
 </script>
@@ -1360,9 +1370,16 @@ from desktop.views import _ko
     return _datum;
   }
 
-  requirejs([
-    'notebook/js/notebook.ko'
-  ], function (EditorViewModel) {
+  require([
+    "knockout",
+    "notebook/js/notebook.ko",
+    "ko.charts",
+    "knockout-mapping",
+    "knockout-sortable",
+    "knockout-deferred-updates",
+    "ko.editable",
+    "ko.hue-bindings"
+  ], function (ko, EditorViewModel) {
 
       var VIEW_MODEL_OPTIONS = $.extend(${ options_json | n,unicode }, {
       user: '${ user.username }',
@@ -1444,7 +1461,7 @@ from desktop.views import _ko
 
     var viewModel;
 
-    function redrawFixedHeaders () {
+    var redrawFixedHeaders = function () {
       viewModel.notebooks().forEach(function (notebook) {
         notebook.snippets().forEach(function (snippet) {
           var _el = $("#snippet_" + snippet.id()).find(".resultTable");
@@ -1456,7 +1473,9 @@ from desktop.views import _ko
           });
         });
       });
-    }
+    };
+
+    window.redrawFixedHeaders = redrawFixedHeaders;
 
     // Drag and drop iPython / Zeppelin notebooks
     if (window.FileReader) {

部分文件因为文件数量过多而无法显示