浏览代码

HUE-8687 [frontend] Add initial notebook bundle

Johan Ahlen 7 年之前
父节点
当前提交
d0969cd303
共有 43 个文件被更改,包括 6007 次插入4227 次删除
  1. 2 1
      .eslintignore
  2. 22 0
      desktop/core/src/desktop/js/apps/notebook/notebook.js
  3. 4500 0
      desktop/core/src/desktop/js/apps/notebook/notebook.ko.js
  4. 21 0
      desktop/core/src/desktop/js/ext/bootstrap-datepicker.min.js
  5. 102 0
      desktop/core/src/desktop/js/ext/jquery.hotkeys.js
  6. 5 1
      desktop/core/src/desktop/js/hue.js
  7. 0 0
      desktop/core/src/desktop/js/jquery/jquery.common.js
  8. 366 0
      desktop/core/src/desktop/js/jquery/plugins/jquery.hdfstree.js
  9. 1 1
      desktop/core/src/desktop/js/login.js
  10. 209 204
      desktop/core/src/desktop/js/utils/json.bigDataParse.js
  11. 22 0
      desktop/core/src/desktop/static/desktop/js/bundles/hue-bundle-678f24deba1e052d3c21.js
  12. 0 0
      desktop/core/src/desktop/static/desktop/js/bundles/hue-bundle-678f24deba1e052d3c21.js.map
  13. 0 22
      desktop/core/src/desktop/static/desktop/js/bundles/hue-bundle-b61705ac5fe672f1dbee.js
  14. 23 0
      desktop/core/src/desktop/static/desktop/js/bundles/hue~notebook-bundle-678f24deba1e052d3c21.js
  15. 0 0
      desktop/core/src/desktop/static/desktop/js/bundles/hue~notebook-bundle-678f24deba1e052d3c21.js.map
  16. 27 0
      desktop/core/src/desktop/static/desktop/js/bundles/login-bundle-678f24deba1e052d3c21.js
  17. 0 0
      desktop/core/src/desktop/static/desktop/js/bundles/login-bundle-678f24deba1e052d3c21.js.map
  18. 0 22
      desktop/core/src/desktop/static/desktop/js/bundles/login-bundle-b61705ac5fe672f1dbee.js
  19. 26 0
      desktop/core/src/desktop/static/desktop/js/bundles/notebook-bundle-678f24deba1e052d3c21.js
  20. 0 0
      desktop/core/src/desktop/static/desktop/js/bundles/notebook-bundle-678f24deba1e052d3c21.js.map
  21. 22 0
      desktop/core/src/desktop/static/desktop/js/bundles/sqlSyntaxWebWorker-bundle-678f24deba1e052d3c21.js
  22. 0 0
      desktop/core/src/desktop/static/desktop/js/bundles/sqlSyntaxWebWorker-bundle-678f24deba1e052d3c21.js.map
  23. 0 22
      desktop/core/src/desktop/static/desktop/js/bundles/sqlSyntaxWebWorker-bundle-b61705ac5fe672f1dbee.js
  24. 23 0
      desktop/core/src/desktop/static/desktop/js/bundles/vendors~hue-bundle-678f24deba1e052d3c21.js
  25. 0 0
      desktop/core/src/desktop/static/desktop/js/bundles/vendors~hue-bundle-678f24deba1e052d3c21.js.map
  26. 50 0
      desktop/core/src/desktop/static/desktop/js/bundles/vendors~hue~notebook-bundle-678f24deba1e052d3c21.js
  27. 0 0
      desktop/core/src/desktop/static/desktop/js/bundles/vendors~hue~notebook-bundle-678f24deba1e052d3c21.js.map
  28. 23 0
      desktop/core/src/desktop/static/desktop/js/bundles/vendors~notebook-bundle-678f24deba1e052d3c21.js
  29. 0 0
      desktop/core/src/desktop/static/desktop/js/bundles/vendors~notebook-bundle-678f24deba1e052d3c21.js.map
  30. 0 308
      desktop/core/src/desktop/static/desktop/js/jquery.hdfstree.js
  31. 3 0
      desktop/core/src/desktop/templates/common_header.mako
  32. 11 1
      desktop/core/src/desktop/templates/hue.mako
  33. 0 2
      desktop/libs/indexer/src/indexer/templates/importer.mako
  34. 0 2
      desktop/libs/indexer/src/indexer/templates/indexer.mako
  35. 0 3628
      desktop/libs/notebook/src/notebook/static/notebook/js/notebook.ko.js
  36. 4 5
      desktop/libs/notebook/src/notebook/templates/editor_components.mako
  37. 0 1
      desktop/libs/notebook/src/notebook/templates/editor_m.mako
  38. 1 0
      ext/thirdparty/README.md
  39. 518 0
      package-lock.json
  40. 10 1
      package.json
  41. 4 0
      tools/scripts/hue-review
  42. 0 1
      webpack-stats.json
  43. 12 5
      webpack.config.js

+ 2 - 1
.eslintignore

@@ -1,2 +1,3 @@
 /desktop/core/src/desktop/js/ext/**
-/desktop/core/src/desktop/js/sql/parse/sqlSyntaxParser.js
+/desktop/core/src/desktop/js/sql/parse/sqlSyntaxParser.js
+/desktop/core/src/desktop/js/utils/json.bigDataParse.js

+ 22 - 0
desktop/core/src/desktop/js/apps/notebook/notebook.js

@@ -0,0 +1,22 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'ext/bootstrap-datepicker.min';
+import 'jquery/plugins/jquery.hdfstree';
+import 'ext/jquery.hotkeys';
+import Clipboard from 'clipboard';
+
+window.Clipboard = Clipboard;

+ 4500 - 0
desktop/core/src/desktop/js/apps/notebook/notebook.ko.js

@@ -0,0 +1,4500 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import $ from 'jquery';
+import ko from 'knockout';
+import komapping from 'knockout.mapping';
+
+import apiHelper from 'api/apiHelper';
+import dataCatalog from 'catalog/dataCatalog';
+import hueAnalytics from 'utils/hueAnalytics';
+import huePubSub from 'utils/huePubSub';
+import hueUtils from 'utils/hueUtils';
+
+// TODO: import Autocompleter
+
+const NOTEBOOK_MAPPING = {
+  ignore: [
+    'ace',
+    'aceMode',
+    'autocompleter',
+    'availableDatabases',
+    'availableSnippets',
+    'avoidClosing',
+    'canWrite',
+    'cleanedDateTimeMeta',
+    'cleanedMeta',
+    'cleanedNumericMeta',
+    'cleanedStringMeta',
+    'dependents',
+    'errorLoadingQueries',
+    'hasProperties',
+    'history',
+    'images',
+    'inFocus',
+    'queries',
+    'saveResultsModalVisible',
+    'selectedStatement',
+    'snippetImage',
+    'user',
+    'positionStatement',
+    'lastExecutedStatement',
+    'downloadResultViewModel'
+  ]
+};
+
+const COMPATIBILITY_SOURCE_PLATFORMS = {
+  teradata: { name: 'Teradata', value: 'teradata' },
+  oracle: { name: 'Oracle', value: 'oracle' },
+  netezza: { name: 'Netezza', value: 'netezza' },
+  impala: { name: 'Impala', value: 'impala' },
+  hive: { name: 'Hive', value: 'hive' },
+  db2: { name: 'DB2', value: 'db2' },
+  greenplum: { name: 'Greenplum', value: 'greenplum' },
+  mysql: { name: 'MySQL', value: 'mysql' },
+  postgresql: { name: 'PostgreSQL', value: 'postgresql' },
+  informix: { name: 'Informix', value: 'informix' },
+  sqlserver: { name: 'SQL Server', value: 'sqlserver' },
+  sybase: { name: 'Sybase', value: 'sybase' },
+  access: { name: 'Access', value: 'access' },
+  firebird: { name: 'Firebird', value: 'firebird' },
+  ansisql: { name: 'ANSISQL', value: 'ansisql' },
+  generic: { name: 'Generic', value: 'generic' }
+};
+
+const COMPATIBILITY_TARGET_PLATFORMS = {
+  impala: { name: 'Impala', value: 'impala' },
+  hive: { name: 'Hive', value: 'hive' }
+};
+
+const Result = function(snippet, result) {
+  const self = this;
+
+  $.extend(
+    snippet,
+    snippet.chartType == 'lines' && {
+      // Retire line chart
+      chartType: 'bars',
+      chartTimelineType: 'line'
+    }
+  );
+  self.id = ko.observable(
+    typeof result.id != 'undefined' && result.id != null ? result.id : hueUtils.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 : []
+  );
+
+  const adaptMeta = function() {
+    let i = 0;
+    self.meta().forEach(item => {
+      if (typeof item.checked === 'undefined') {
+        item.checked = ko.observable(true);
+        item.checked.subscribe(() => {
+          self.filteredMetaChecked(
+            self.filteredMeta().some(item => {
+              return item.checked();
+            })
+          );
+        });
+      }
+      item.type = item.type.replace(/_type/i, '').toLowerCase();
+      if (typeof item.originalIndex === 'undefined') {
+        item.originalIndex = i;
+      }
+      i++;
+    });
+  };
+
+  adaptMeta();
+  self.meta.subscribe(adaptMeta);
+
+  self.rows = ko.observable(
+    typeof result.rows != 'undefined' && result.rows != null ? result.rows : null
+  );
+  self.hasMore = ko.observable(
+    typeof result.hasMore != 'undefined' && result.hasMore != null ? result.hasMore : false
+  );
+  self.statement_id = ko.observable(
+    typeof result.statement_id != 'undefined' && result.statement_id != null
+      ? result.statement_id
+      : 0
+  );
+  self.statement_range = ko.observable(
+    typeof result.statement_range != 'undefined' && result.statement_range != null
+      ? result.statement_range
+      : {
+          start: {
+            row: 0,
+            column: 0
+          },
+          end: {
+            row: 0,
+            column: 0
+          }
+        }
+  );
+  // We don't keep track of any previous selection so prevent entering into batch execution mode after load by setting
+  // statements_count to 1. For the case when a selection is not starting at row 0.
+  self.statements_count = ko.observable(1);
+  self.previous_statement_hash = ko.observable(
+    typeof result.previous_statement_hash != 'undefined' && result.previous_statement_hash != null
+      ? result.previous_statement_hash
+      : null
+  );
+  self.cleanedMeta = ko.computed(() => {
+    return ko.utils.arrayFilter(self.meta(), item => {
+      return item.name != '';
+    });
+  });
+  self.metaFilter = ko.observable();
+
+  self.isMetaFilterVisible = ko.observable(false);
+  self.filteredMetaChecked = ko.observable(true);
+  self.filteredMeta = ko.pureComputed(() => {
+    if (!self.metaFilter() || self.metaFilter().query === '') {
+      return self.meta();
+    }
+
+    return self.meta().filter(item => {
+      const facets = self.metaFilter().facets;
+      const isFacetMatch = !facets || Object.keys(facets).length === 0 || !facets['type']; // So far only type facet is used for SQL
+      const isTextMatch = !self.metaFilter().text || self.metaFilter().text.length === 0;
+      let match = true;
+
+      if (!isFacetMatch) {
+        match = !!facets['type'][item.type];
+      }
+
+      if (match && !isTextMatch) {
+        match = self.metaFilter().text.every(text => {
+          return item.name.toLowerCase().indexOf(text.toLowerCase()) !== -1;
+        });
+      }
+      return match;
+    });
+  });
+
+  self.autocompleteFromEntries = function(nonPartial, partial) {
+    const result = [];
+    const partialLower = partial.toLowerCase();
+    self.meta().forEach(column => {
+      if (column.name.toLowerCase().indexOf(partialLower) === 0) {
+        result.push(nonPartial + partial + column.name.substring(partial.length));
+      } else if (column.name.toLowerCase().indexOf('.' + partialLower) !== -1) {
+        result.push(
+          nonPartial +
+            partial +
+            column.name.substring(
+              partial.length + column.name.toLowerCase().indexOf('.' + partialLower) + 1
+            )
+        );
+      }
+    });
+
+    return result;
+  };
+  self.clickFilteredMetaCheck = function() {
+    self.filteredMeta().forEach(item => {
+      item.checked(self.filteredMetaChecked());
+    });
+  };
+
+  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(() => {
+    return self.endTime().getTime() - self.startTime().getTime();
+  });
+
+  function isNumericColumn(type) {
+    return (
+      $.inArray(type, [
+        'tinyint',
+        'smallint',
+        'int',
+        'bigint',
+        'float',
+        'double',
+        'decimal',
+        'real'
+      ]) > -1
+    );
+  }
+
+  function isDateTimeColumn(type) {
+    return $.inArray(type, ['timestamp', 'date', 'datetime']) > -1;
+  }
+
+  function isComplexColumn(type) {
+    return $.inArray(type, ['array', 'map', 'struct']) > -1;
+  }
+
+  function isStringColumn(type) {
+    return !isNumericColumn(type) && !isDateTimeColumn(type) && !isComplexColumn(type);
+  }
+
+  self.cleanedNumericMeta = ko.computed(() => {
+    return ko.utils.arrayFilter(self.meta(), item => {
+      return item.name != '' && isNumericColumn(item.type);
+    });
+  });
+
+  self.cleanedStringMeta = ko.computed(() => {
+    return ko.utils.arrayFilter(self.meta(), item => {
+      return item.name != '' && isStringColumn(item.type);
+    });
+  });
+
+  self.cleanedDateTimeMeta = ko.computed(() => {
+    return ko.utils.arrayFilter(self.meta(), 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.explanation = ko.observable(
+    typeof result.explanation != 'undefined' && result.explanation != null ? result.explanation : ''
+  );
+  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(() => {
+    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.hasMore(false);
+    self.statement_range({
+      start: {
+        row: 0,
+        column: 0
+      },
+      end: {
+        row: 0,
+        column: 0
+      }
+    });
+    self.meta.removeAll();
+    self.data.removeAll();
+    self.images.removeAll();
+    self.logs('');
+    self.handle({
+      // Keep multiquery indexing
+      has_more_statements: self.handle()['has_more_statements'],
+      statement_id: self.handle()['statement_id'],
+      statements_count: self.handle()['statements_count'],
+      previous_statement_hash: self.handle()['previous_statement_hash']
+    });
+    self.startTime(new Date());
+    self.endTime(new Date());
+    self.explanation('');
+    self.logLines = 0;
+    self.rows(null);
+  };
+};
+
+Result.prototype.cancelBatchExecution = function() {
+  const self = this;
+  self.statements_count(1);
+  self.hasMore(false);
+  self.statement_range({
+    start: {
+      row: 0,
+      column: 0
+    },
+    end: {
+      row: 0,
+      column: 0
+    }
+  });
+  self.handle()['statement_id'] = 0;
+  self.handle()['start'] = {
+    row: 0,
+    column: 0
+  };
+  self.handle()['end'] = {
+    row: 0,
+    column: 0
+  };
+  self.handle()['has_more_statements'] = false;
+  self.handle()['previous_statement_hash'] = '';
+};
+
+const getDefaultSnippetProperties = function(snippetType) {
+  const properties = {};
+
+  if (snippetType == 'jar' || snippetType == 'py') {
+    properties['driverCores'] = '';
+    properties['executorCores'] = '';
+    properties['numExecutors'] = '';
+    properties['queue'] = '';
+    properties['archives'] = [];
+    properties['files'] = [];
+  }
+
+  if (snippetType == 'java') {
+    properties['archives'] = [];
+    properties['files'] = [];
+    properties['capture_output'] = false;
+  }
+
+  if (snippetType == 'shell') {
+    properties['archives'] = [];
+    properties['files'] = [];
+  }
+
+  if (snippetType == 'mapreduce') {
+    properties['app_jar'] = '';
+    properties['hadoopProperties'] = [];
+    properties['jars'] = [];
+    properties['files'] = [];
+    properties['archives'] = [];
+  }
+
+  if (snippetType == 'spark2') {
+    properties['app_name'] = '';
+    properties['class'] = '';
+    properties['jars'] = [];
+    properties['spark_opts'] = [];
+    properties['spark_arguments'] = [];
+    properties['files'] = [];
+  }
+
+  if (snippetType == 'sqoop1') {
+    properties['files'] = [];
+  }
+
+  if (snippetType == 'jar' || snippetType == 'java') {
+    properties['app_jar'] = '';
+    properties['class'] = '';
+    properties['arguments'] = [];
+  } else if (snippetType == 'distcp') {
+    properties['source_path'] = '';
+    properties['destination_path'] = '';
+  } else if (snippetType == 'shell') {
+    properties['command_path'] = '';
+    properties['arguments'] = [];
+    properties['env_var'] = [];
+    properties['capture_output'] = true;
+  } else if (snippetType == 'py') {
+    properties['py_file'] = '';
+    properties['arguments'] = [];
+  } else if (snippetType == 'hive') {
+    properties['settings'] = [];
+    properties['files'] = [];
+    properties['functions'] = [];
+    properties['arguments'] = [];
+  } else if (snippetType == 'impala') {
+    properties['settings'] = [];
+  } else if (snippetType == 'pig') {
+    properties['parameters'] = [];
+    properties['hadoopProperties'] = [];
+    properties['resources'] = [];
+  }
+
+  return properties;
+};
+
+const ERROR_REGEX = /line ([0-9]+)(\:([0-9]+))?/i;
+
+const Snippet = function(vm, notebook, snippet) {
+  const self = this;
+
+  self.id = ko.observable(
+    typeof snippet.id != 'undefined' && snippet.id != null ? snippet.id : hueUtils.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.type.subscribe(newVal => {
+    self.status('ready');
+  });
+
+  self.isBatchable = ko.computed(() => {
+    return (
+      self.type() == 'hive' ||
+      self.type() == 'impala' ||
+      $.grep(vm.availableLanguages, language => {
+        return language.type == self.type() && language.interface == 'oozie';
+      }).length > 0
+    );
+  });
+
+  self.autocompleteSettings = {
+    temporaryOnly: false
+  };
+
+  // Ace stuff
+  self.aceCursorPosition = ko.observable(notebook.isHistory() ? snippet.aceCursorPosition : null);
+
+  let aceEditor = null;
+
+  self.ace = function(newVal) {
+    if (newVal) {
+      aceEditor = newVal;
+      if (!notebook.isPresentationMode()) {
+        aceEditor.focus();
+      }
+    }
+    return aceEditor;
+  };
+  self.errors = ko.observableArray([]);
+
+  self.aceErrorsHolder = ko.observableArray([]);
+  self.aceWarningsHolder = ko.observableArray([]);
+
+  self.aceErrors = ko.pureComputed(() => {
+    return self.showOptimizer() ? self.aceErrorsHolder() : [];
+  });
+  self.aceWarnings = ko.pureComputed(() => {
+    return self.showOptimizer() ? self.aceWarningsHolder() : [];
+  });
+
+  self.availableSnippets = vm.availableSnippets();
+  self.inFocus = ko.observable(false);
+
+  self.inFocus.subscribe(newValue => {
+    if (newValue) {
+      huePubSub.publish('active.snippet.type.changed', self.type());
+    }
+  });
+
+  self.editorMode = vm.editorMode;
+
+  self.getAceMode = function() {
+    return vm.getSnippetViewSettings(self.type()).aceMode;
+  };
+
+  self.dbSelectionVisible = ko.observable(false);
+
+  self.showExecutionAnalysis = ko.observable(false);
+
+  self.isSqlDialect = ko.pureComputed(() => {
+    return vm.getSnippetViewSettings(self.type()).sqlDialect;
+  });
+
+  self.getPlaceHolder = function() {
+    return vm.getSnippetViewSettings(self.type()).placeHolder;
+  };
+
+  self.getApiHelper = function() {
+    return apiHelper;
+  };
+
+  // namespace and compute might be initialized as empty object {}
+  self.namespace = ko.observable(
+    snippet.namespace && snippet.namespace.id ? snippet.namespace : undefined
+  );
+  self.compute = ko.observable(snippet.compute && snippet.compute.id ? snippet.compute : undefined);
+
+  self.whenContextSet = function() {
+    let namespaceSub;
+    const namespaceDeferred = $.Deferred();
+    if (self.namespace()) {
+      namespaceDeferred.resolve(self.namespace());
+    } else {
+      namespaceSub = self.namespace.subscribe(newVal => {
+        if (newVal) {
+          namespaceDeferred.resolve(newVal);
+          namespaceSub.dispose();
+        }
+      });
+    }
+    let computeSub;
+    const computeDeferred = $.Deferred();
+    if (self.compute()) {
+      computeDeferred.resolve(self.compute());
+    } else {
+      computeSub = self.compute.subscribe(newVal => {
+        if (newVal) {
+          computeDeferred.resolve(newVal);
+          computeSub.dispose();
+        }
+      });
+    }
+
+    const result = $.when(namespaceDeferred, computeDeferred);
+
+    result.dispose = function() {
+      if (namespaceSub) {
+        namespaceSub.dispose();
+      }
+      if (computeSub) {
+        computeSub.dispose();
+      }
+      namespaceDeferred.reject();
+      computeDeferred.reject();
+    };
+
+    return result;
+  };
+
+  self.availableDatabases = ko.observableArray();
+  self.database = ko.observable();
+  let previousDatabase = null;
+
+  self.database.subscribe(newValue => {
+    if (newValue !== null) {
+      apiHelper.setInTotalStorage('editor', 'last.selected.database', newValue);
+      if (previousDatabase !== null && previousDatabase !== newValue) {
+        huePubSub.publish('editor.refresh.statement.locations', self);
+      }
+      previousDatabase = newValue;
+    }
+  });
+
+  self.database(
+    typeof snippet.database !== 'undefined' && snippet.database != null ? snippet.database : null
+  );
+
+  // History is currently in Notebook, same with saved queries by snippets, might be better in assist
+  self.currentQueryTab = ko.observable(
+    typeof snippet.currentQueryTab != 'undefined' && snippet.currentQueryTab != null
+      ? snippet.currentQueryTab
+      : 'queryHistory'
+  );
+  self.pinnedContextTabs = ko.observableArray(
+    typeof snippet.pinnedContextTabs != 'undefined' && snippet.pinnedContextTabs != null
+      ? snippet.pinnedContextTabs
+      : []
+  );
+
+  self.removeContextTab = function(context) {
+    if (context.tabId === self.currentQueryTab()) {
+      self.currentQueryTab('queryHistory');
+    }
+    self.pinnedContextTabs.remove(context);
+  };
+
+  self.errorLoadingQueries = ko.observable(false);
+  self.loadingQueries = ko.observable(false);
+
+  self.queriesHasErrors = ko.observable(false);
+  self.queriesCurrentPage = ko.observable(
+    vm.selectedNotebook() && vm.selectedNotebook().snippets().length > 0
+      ? vm
+          .selectedNotebook()
+          .snippets()[0]
+          .queriesCurrentPage()
+      : 1
+  );
+  self.queriesTotalPages = ko.observable(
+    vm.selectedNotebook() && vm.selectedNotebook().snippets().length > 0
+      ? vm
+          .selectedNotebook()
+          .snippets()[0]
+          .queriesTotalPages()
+      : 1
+  );
+  self.queries = ko.observableArray([]);
+
+  self.queriesFilter = ko.observable('');
+  self.queriesFilterVisible = ko.observable(false);
+  self.queriesFilter.extend({ rateLimit: { method: 'notifyWhenChangesStop', timeout: 900 } });
+  self.queriesFilter.subscribe(val => {
+    self.fetchQueries();
+  });
+
+  let lastFetchQueriesRequest = null;
+
+  self.fetchQueries = function() {
+    apiHelper.cancelActiveRequest(lastFetchQueriesRequest);
+
+    const QUERIES_PER_PAGE = 50;
+    lastQueriesPage = self.queriesCurrentPage();
+    self.loadingQueries(true);
+    self.queriesHasErrors(false);
+    lastFetchQueriesRequest = apiHelper.searchDocuments({
+      successCallback: function(result) {
+        self.queriesTotalPages(Math.ceil(result.count / QUERIES_PER_PAGE));
+        self.queries(komapping.fromJS(result.documents)());
+        self.loadingQueries(false);
+        self.queriesHasErrors(false);
+      },
+      errorCallback: function() {
+        self.loadingQueries(false);
+        self.queriesHasErrors(true);
+      },
+      page: self.queriesCurrentPage(),
+      limit: QUERIES_PER_PAGE,
+      type: 'query-' + self.type(),
+      query: self.queriesFilter(),
+      include_trashed: false
+    });
+  };
+
+  let lastQueriesPage = 1;
+  self.currentQueryTab.subscribe(newValue => {
+    huePubSub.publish('redraw.fixed.headers');
+    huePubSub.publish('tab.switched', newValue);
+    if (
+      newValue === 'savedQueries' &&
+      (self.queries().length === 0 || lastQueriesPage !== self.queriesCurrentPage())
+    ) {
+      self.fetchQueries();
+    }
+  });
+
+  self.prevQueriesPage = function() {
+    if (self.queriesCurrentPage() !== 1) {
+      self.queriesCurrentPage(self.queriesCurrentPage() - 1);
+      self.fetchQueries();
+    }
+  };
+
+  self.nextQueriesPage = function() {
+    if (self.queriesCurrentPage() !== self.queriesTotalPages()) {
+      self.queriesCurrentPage(self.queriesCurrentPage() + 1);
+      self.fetchQueries();
+    }
+  };
+
+  huePubSub.subscribeOnce(
+    'assist.source.set',
+    source => {
+      if (source !== self.type()) {
+        huePubSub.publish('assist.set.source', self.type());
+      }
+    },
+    vm.huePubSubId
+  );
+
+  huePubSub.publish('assist.get.source');
+
+  let ignoreNextAssistDatabaseUpdate = false;
+  self.handleAssistSelection = function(databaseDef) {
+    if (ignoreNextAssistDatabaseUpdate) {
+      ignoreNextAssistDatabaseUpdate = false;
+    } else if (databaseDef.sourceType === self.type()) {
+      if (self.namespace() !== databaseDef.namespace) {
+        self.namespace(databaseDef.namespace);
+      }
+      if (self.database() !== databaseDef.name) {
+        self.database(databaseDef.name);
+      }
+    }
+  };
+
+  if (!self.database()) {
+    huePubSub.publish('assist.get.database.callback', {
+      source: self.type(),
+      callback: function(databaseDef) {
+        self.handleAssistSelection(databaseDef);
+      }
+    });
+  }
+
+  self.statementType = ko.observable(
+    typeof snippet.statementType != 'undefined' && snippet.statementType != null
+      ? snippet.statementType
+      : 'text'
+  );
+  self.statementTypes = ko.observableArray(['text', 'file']); // Maybe computed later for Spark
+  if (!vm.editorMode()) {
+    self.statementTypes.push('document');
+  }
+  self.statementPath = ko.observable(
+    typeof snippet.statementPath != 'undefined' && snippet.statementPath != null
+      ? snippet.statementPath
+      : ''
+  );
+  self.statementPath.subscribe(newVal => {
+    self.getExternalStatement();
+  });
+  self.externalStatementLoaded = ko.observable(false);
+  self.getExternalStatement = function() {
+    self.externalStatementLoaded(false);
+    $.post(
+      '/notebook/api/get_external_statement',
+      {
+        notebook: komapping.toJSON(notebook.getContext()),
+        snippet: komapping.toJSON(self.getContext())
+      },
+      data => {
+        if (data.status == 0) {
+          self.externalStatementLoaded(true);
+          self.statement_raw(data.statement);
+          self.ace().setValue(self.statement_raw(), 1);
+        } else {
+          self._ajaxError(data);
+        }
+      }
+    );
+  };
+  self.associatedDocumentLoading = ko.observable(true);
+  self.associatedDocument = ko.observable();
+  self.associatedDocumentUuid = ko.observable(
+    typeof snippet.associatedDocumentUuid != 'undefined' && snippet.associatedDocumentUuid != null
+      ? snippet.associatedDocumentUuid
+      : null
+  );
+  self.associatedDocumentUuid.subscribe(val => {
+    if (val !== '') {
+      self.getExternalStatement();
+    } else {
+      self.statement_raw('');
+      self.ace().setValue('', 1);
+    }
+  });
+  self.statement_raw = ko.observable(
+    typeof snippet.statement_raw != 'undefined' && snippet.statement_raw != null
+      ? snippet.statement_raw
+      : ''
+  );
+  self.selectedStatement = ko.observable('');
+  self.positionStatement = ko.observable(null);
+  self.lastExecutedStatement = ko.observable(null);
+  self.statementsList = ko.observableArray();
+
+  huePubSub.subscribe(
+    'editor.active.statement.changed',
+    statementDetails => {
+      if (self.ace() && self.ace().container.id === statementDetails.id) {
+        if (statementDetails.activeStatement) {
+          self.positionStatement(statementDetails.activeStatement);
+        } else {
+          self.positionStatement(null);
+        }
+
+        if (statementDetails.activeStatement) {
+          const _statements = [];
+          statementDetails.precedingStatements.forEach(statement => {
+            _statements.push(statement.statement);
+          });
+          _statements.push(statementDetails.activeStatement.statement);
+          statementDetails.followingStatements.forEach(statement => {
+            _statements.push(statement.statement);
+          });
+          self.statementsList(_statements); // Or fetch on demand via editor.refresh.statement.locations and remove observableArray?
+        } else {
+          self.statementsList([]);
+        }
+        if (!notebook.isPresentationModeInitialized()) {
+          if (notebook.isPresentationModeDefault()) {
+            // When switching to presentation mode, the snippet in non presentation mode cannot get status notification.
+            // On initiailization, status is set to loading and does not get updated, because we moved to presentation mode.
+            self.status('ready');
+          }
+          // Changing to presentation mode requires statementsList to be initialized. statementsList is initialized asynchronously.
+          // When presentation mode is default, we cannot change before statementsList has been calculated.
+          // Cleaner implementation would be to make toggleEditorMode statementsList asynchronous
+          // However this is currently impossible due to delete _notebook.presentationSnippets()[key];
+          notebook.isPresentationModeInitialized(true);
+          notebook.isPresentationMode(notebook.isPresentationModeDefault());
+        }
+      }
+    },
+    vm.huePubSubId
+  );
+
+  self.aceSize = ko.observable(
+    typeof snippet.aceSize != 'undefined' && snippet.aceSize != null ? snippet.aceSize : 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.statusForButtons = ko.observable('executed');
+
+  self.properties = ko.observable(
+    komapping.fromJS(
+      typeof snippet.properties != 'undefined' && snippet.properties != null
+        ? snippet.properties
+        : getDefaultSnippetProperties(self.type())
+    )
+  );
+  self.hasProperties = ko.computed(() => {
+    return Object.keys(komapping.toJS(self.properties())).length > 0;
+  });
+
+  self.viewSettings = ko.computed(() => {
+    return vm.getSnippetViewSettings(self.type());
+  });
+
+  const previousProperties = {};
+  self.type.subscribe(
+    oldValue => {
+      previousProperties[oldValue] = self.properties();
+    },
+    null,
+    'beforeChange'
+  );
+
+  self.type.subscribe(newValue => {
+    if (typeof previousProperties[newValue] != 'undefined') {
+      self.properties(previousProperties[newValue]);
+    } else {
+      self.properties(komapping.fromJS(getDefaultSnippetProperties(newValue)));
+    }
+    self.result.clear();
+    window.setTimeout(() => {
+      if (self.ace() !== null) {
+        self.ace().focus();
+      }
+    }, 100);
+  });
+  if (snippet.variables) {
+    snippet.variables.forEach(variable => {
+      variable.meta = (typeof variable.defaultValue === 'object' && variable.defaultValue) || {
+        type: 'text',
+        placeholder: ''
+      };
+      variable.value = variable.value || '';
+      variable.type = variable.type || 'text';
+      variable.sample = [];
+      variable.sampleUser = variable.sampleUser || [];
+      variable.path = variable.path || '';
+      variable.step = '';
+      delete variable.defaultValue;
+    });
+  }
+  self.variables = komapping.fromJS(
+    typeof snippet.variables != 'undefined' && snippet.variables != null ? snippet.variables : []
+  );
+  self.variables.subscribe(newValue => {
+    $(document).trigger('updateResultHeaders', self);
+  });
+  self.hasCurlyBracketParameters = ko.computed(() => {
+    return self.type() != 'pig';
+  });
+  self.getPigParameters = function() {
+    const params = {};
+    const variables = self.statement_raw().match(/([^\\]|^)\$[^\d'"](\w*)/g);
+    const declares = self.statement_raw().match(/%declare +([^ ])+/gi);
+    const defaults = self.statement_raw().match(/%default +([^;])+/gi);
+    const macro_defines = self.statement_raw().match(/define [^ ]+ *\(([^\)]*)\)/gi); // no multiline
+    const macro_returns = self.statement_raw().match(/returns +([^\{]*)/gi); // no multiline
+
+    if (variables) {
+      $.each(variables, (index, param) => {
+        const p = param.substring(param.indexOf('$') + 1);
+        params[p] = '';
+      });
+    }
+    if (declares) {
+      $.each(declares, (index, param) => {
+        param = param.match(/(\w+)/g);
+        if (param && param.length >= 2) {
+          delete params[param[1]];
+        }
+      });
+    }
+    if (defaults) {
+      $.each(defaults, (index, param) => {
+        const line = param.match(/(\w+)/g);
+        if (line && line.length >= 2) {
+          const name = line[1];
+          params[name] = param.substring(param.indexOf(name) + name.length + 1);
+        }
+      });
+    }
+    if (macro_defines) {
+      $.each(macro_defines, (index, params_line) => {
+        const param_line = params_line.match(/(\w+)/g);
+        if (param_line && param_line.length > 2) {
+          $.each(param_line, (index, param) => {
+            if (index >= 2) {
+              // Skips define NAME
+              delete params[param];
+            }
+          });
+        }
+      });
+    }
+    if (macro_returns) {
+      $.each(macro_returns, (index, params_line) => {
+        const param_line = params_line.match(/(\w+)/g);
+        if (param_line) {
+          $.each(param_line, (index, param) => {
+            if (index >= 1) {
+              // Skip returns
+              delete params[param];
+            }
+          });
+        }
+      });
+    }
+
+    return params;
+  };
+  self.variableNames = ko.computed(() => {
+    let match,
+      matches = {},
+      matchList;
+    if (self.type() == 'pig') {
+      matches = self.getPigParameters();
+    } else {
+      const re = /(?:^|\W)\${(\w*)\=?([^{}]*)}/g;
+      const reComment = /(^\s*--.*)|(\/\*[\s\S]*?\*\/)/gm;
+      const reList = /(?!\s*$)\s*(?:(?:([^,|()\\]*)\(\s*([^,|()\\]*)\)(?:\\[\S\s][^,|()\\]*)?)|([^,|\\]*(?:\\[\S\s][^,|\\]*)*))\s*(?:,|\||$)/g;
+      const statement = self.statement_raw();
+      let matchComment = reComment.exec(statement);
+      // if re is n & reComment is m
+      // finding variables is O(n+m)
+      while ((match = re.exec(statement))) {
+        while (matchComment && match.index > matchComment.index + matchComment[0].length) {
+          // Comments before our match
+          matchComment = reComment.exec(statement);
+        }
+        const isWithinComment = matchComment && match.index >= matchComment.index;
+        if (isWithinComment) {
+          continue;
+        }
+
+        // If 1 match, text value
+        // If multiple matches, list value
+        const value = { type: 'text', placeholder: '' };
+        while ((matchList = reList.exec(match[2]))) {
+          const option = {
+            text: matchList[2] || matchList[3],
+            value: matchList[3] || matchList[1]
+          };
+          option.text = option.text && option.text.trim();
+          option.value =
+            option.value &&
+            option.value
+              .trim()
+              .replace(',', ',')
+              .replace('(', '(')
+              .replace(')', ')');
+
+          if (value.placeholder || matchList[2]) {
+            if (!value.options) {
+              value.options = [];
+              value.type = 'select';
+            }
+            value.options.push(option);
+          }
+          if (!value.placeholder) {
+            value.placeholder = option.value;
+          }
+        }
+        const isPlaceholderInOptions =
+          !value.options ||
+          value.options.some(current => {
+            return current.value == value.placeholder;
+          });
+        if (!isPlaceholderInOptions) {
+          value.options.unshift({ text: value.placeholder, value: value.placeholder });
+        }
+        matches[match[1]] = matches[match[1]] || value;
+      }
+    }
+    return $.map(matches, (match, key) => {
+      const isMatchObject = typeof matches[key] === 'object';
+      const meta = isMatchObject ? matches[key] : { type: 'text', placeholder: matches[key] };
+      return { name: key, meta: meta };
+    });
+  });
+  self.variableValues = {};
+  self.variableNames.extend({ rateLimit: 150 });
+  self.variableNames.subscribe(newVal => {
+    const variablesLength = self.variables().length;
+    const diffLengthVariables = variablesLength - newVal.length;
+    const needsMore = diffLengthVariables < 0;
+    const needsLess = diffLengthVariables > 0;
+    self.variableValues = self.variables().reduce((variableValues, variable) => {
+      if (!variableValues[variable.name()]) {
+        variableValues[variable.name()] = { sampleUser: [] };
+      }
+      variableValues[variable.name()].value = variable.value();
+      variableValues[variable.name()].sampleUser = variable.sampleUser();
+      variableValues[variable.name()].catalogEntry = variable.catalogEntry;
+      variableValues[variable.name()].path = variable.path();
+      variableValues[variable.name()].type = variable.type();
+      return variableValues;
+    }, self.variableValues);
+    if (needsMore) {
+      for (let i = 0, length = Math.abs(diffLengthVariables); i < length; i++) {
+        self.variables.push(
+          komapping.fromJS({
+            name: '',
+            value: '',
+            meta: { type: 'text', placeholder: '', options: [] },
+            sample: [],
+            sampleUser: [],
+            type: 'text',
+            step: '',
+            path: ''
+          })
+        );
+      }
+    } else if (needsLess) {
+      self.variables.splice(self.variables().length - diffLengthVariables, diffLengthVariables);
+    }
+    newVal.forEach((item, index) => {
+      const variable = self.variables()[index];
+      variable.name(item.name);
+      setTimeout(() => {
+        variable.value(
+          self.variableValues[item.name]
+            ? self.variableValues[item.name].value
+            : (!needsMore && variable.value()) || ''
+        );
+      }, 0);
+      variable.meta = komapping.fromJS(item.meta, {}, variable.meta);
+      variable.sample(
+        variable.meta.options
+          ? variable.meta.options().concat(variable.sampleUser())
+          : variable.sampleUser()
+      );
+      variable.sampleUser(
+        self.variableValues[item.name] ? self.variableValues[item.name].sampleUser : []
+      );
+      variable.type(
+        self.variableValues[item.name] ? self.variableValues[item.name].type || 'text' : 'text'
+      );
+      variable.path(
+        self.variableValues[item.name] ? self.variableValues[item.name].path || '' : ''
+      );
+      variable.catalogEntry =
+        self.variableValues[item.name] && self.variableValues[item.name].catalogEntry;
+    });
+  });
+
+  const activeSourcePromises = [];
+  huePubSub.subscribe('ace.sql.location.worker.message', e => {
+    while (activeSourcePromises.length) {
+      const promise = activeSourcePromises.pop();
+      if (promise.cancel) {
+        promise.cancel();
+      }
+    }
+    const oLocations = e.data.locations
+      .filter(location => {
+        return location.type === 'variable' && location.colRef;
+      })
+      .reduce((variables, location) => {
+        const re = /\${(\w*)\=?([^{}]*)}/g;
+        const name = re.exec(location.value)[1];
+        variables[name] = location;
+        return variables;
+      }, {});
+    const updateVariableType = function(variable, sourceMeta) {
+      let type;
+      if (sourceMeta && sourceMeta.type) {
+        type = sourceMeta.type.toLowerCase();
+      } else {
+        type = 'string';
+      }
+      const variablesValues = {};
+      const value = variable.value();
+      switch (type) {
+        case 'timestamp':
+          variablesValues.type = 'datetime-local';
+          variablesValues.step = '1';
+          variablesValues.value =
+            (value && moment.utc(value).format('YYYY-MM-DD HH:mm:ss.S')) ||
+            moment(Date.now()).format('YYYY-MM-DD 00:00:00.0');
+          break;
+        case 'decimal':
+        case 'double':
+        case 'float':
+          variablesValues.type = 'number';
+          variablesValues.step = 'any';
+          break;
+        case 'int':
+        case 'smallint':
+        case 'tinyint':
+        case 'bigint':
+          variablesValues.type = 'number';
+          variablesValues.step = '1';
+          break;
+        case 'date':
+          variablesValues.type = 'date';
+          variablesValues.step = '';
+          variablesValues.value =
+            (value && moment.utc(value).format('YYYY-MM-DD')) ||
+            moment(Date.now()).format('YYYY-MM-DD');
+          break;
+        case 'boolean':
+          variablesValues.type = 'checkbox';
+          variablesValues.step = '';
+          break;
+        default:
+          variablesValues.type = 'text';
+          variablesValues.step = '';
+      }
+      if (variablesValues.value) {
+        setTimeout(() => {
+          variable.value(variablesValues.value);
+        }, 0);
+      }
+      variable.type(variablesValues.type);
+      variable.step(variablesValues.step);
+    };
+    self.variables().forEach(variable => {
+      if (oLocations[variable.name()]) {
+        activeSourcePromises.push(
+          oLocations[variable.name()].resolveCatalogEntry({ cancellable: true }).done(entry => {
+            variable.path(entry.path.join('.'));
+            variable.catalogEntry = entry;
+
+            activeSourcePromises.push(
+              entry
+                .getSourceMeta({
+                  silenceErrors: true,
+                  cancellable: true
+                })
+                .then(updateVariableType.bind(self, variable))
+            );
+          })
+        );
+      } else {
+        updateVariableType(variable, {
+          type: 'text'
+        });
+      }
+    });
+  });
+  self.statement = ko.computed(() => {
+    let statement = self.isSqlDialect()
+      ? self.selectedStatement()
+        ? self.selectedStatement()
+        : self.positionStatement() !== null
+        ? self.positionStatement().statement
+        : self.statement_raw()
+      : self.statement_raw();
+    const variables = self.variables().reduce((variables, variable) => {
+      variables[variable.name()] = variable;
+      return variables;
+    }, {});
+    if (self.variables().length) {
+      const variablesString = self
+        .variables()
+        .map(variable => {
+          return variable.name();
+        })
+        .join('|');
+      statement = statement.replace(
+        RegExp(
+          '([^\\\\])?\\$' +
+            (self.hasCurlyBracketParameters() ? '{(' : '(') +
+            variablesString +
+            ')(=[^}]*)?' +
+            (self.hasCurlyBracketParameters() ? '}' : ''),
+          'g'
+        ),
+        (match, p1, p2) => {
+          const variable = variables[p2];
+          const pad =
+            variable.type() == 'datetime-local' && variable.value().length == 16 ? ':00' : ''; // Chrome drops the seconds from the timestamp when it's at 0 second.
+          const value = variable.value();
+          return (
+            p1 +
+            (value !== undefined && value !== null
+              ? value + pad
+              : variable.meta.placeholder && variable.meta.placeholder())
+          );
+        }
+      );
+    }
+    return statement;
+  });
+
+  self.result = new Result(snippet, snippet.result);
+  if (!self.result.hasSomeResults()) {
+    self.currentQueryTab('queryHistory');
+  }
+  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
+  );
+  let defaultShowLogs = true;
+  if (vm.editorMode() && $.totalStorage('hue.editor.showLogs')) {
+    defaultShowLogs = $.totalStorage('hue.editor.showLogs');
+  }
+  self.showLogs = ko.observable(
+    typeof snippet.showLogs !== 'undefined' && snippet.showLogs != null
+      ? snippet.showLogs
+      : defaultShowLogs
+  );
+  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.executeNextTimeout = -1;
+  const refreshTimeouts = {};
+  self.onDdlExecute = function() {
+    if (self.result.handle() && self.result.handle().has_more_statements) {
+      window.clearTimeout(self.executeNextTimeout);
+      self.executeNextTimeout = setTimeout(() => {
+        self.execute(true); // Execute next, need to wait as we disabled fast click
+      }, 1000);
+    }
+    if (
+      self.lastExecutedStatement() &&
+      /CREATE|DROP/i.test(self.lastExecutedStatement().firstToken)
+    ) {
+      let match = self
+        .statement()
+        .match(
+          /(?:CREATE|DROP)\s+TABLE\s+(?:IF\s+(?:NOT\s+)?EXISTS\s+)?(?:`([^`]+)`|([^;\s]+))\..*/i
+        );
+      const path = [];
+      if (match) {
+        path.push(match[1] || match[2]); // group 1 backticked db name, group 2 regular db name
+      } else {
+        match = self
+          .statement()
+          .match(
+            /(?:CREATE|DROP)\s+(?:DATABASE|SCHEMA)\s+(?:IF\s+(?:NOT\s+)?EXISTS\s+)?(?:`([^`]+)`|([^;\s]+))/i
+          );
+        if (match) {
+          path.push(match[1] || match[2]); // group 1 backticked db name, group 2 regular db name
+        } else if (self.database()) {
+          path.push(self.database());
+        }
+      }
+
+      if (path.length) {
+        window.clearTimeout(refreshTimeouts[path.join('.')]);
+        refreshTimeouts[path.join('.')] = window.setTimeout(() => {
+          ignoreNextAssistDatabaseUpdate = true;
+          dataCatalog
+            .getEntry({
+              sourceType: self.type(),
+              namespace: self.namespace(),
+              compute: self.compute(),
+              path: path
+            })
+            .done(entry => {
+              entry.clearCache({ invalidate: 'invalidate', cascade: true, silenceErrors: true });
+            });
+        }, 5000);
+      }
+    }
+  };
+
+  self.progress.subscribe(val => {
+    $(document).trigger('progress', { data: val, snippet: self });
+  });
+
+  self.showGrid.subscribe(val => {
+    if (val) {
+      self.showChart(false);
+      huePubSub.publish('editor.grid.shown', self);
+    }
+  });
+
+  function prepopulateChart() {
+    const type = self.chartType();
+    hueAnalytics.log('notebook', 'chart/' + type);
+
+    if (type === ko.HUE_CHARTS.TYPES.MAP && self.result.cleanedNumericMeta().length >= 2) {
+      if (self.chartX() === null || typeof self.chartX() === 'undefined') {
+        let name = self.result.cleanedNumericMeta()[0].name;
+        self.result.cleanedNumericMeta().forEach(fld => {
+          if (
+            fld.name.toLowerCase().indexOf('lat') > -1 ||
+            fld.name.toLowerCase().indexOf('ltd') > -1
+          ) {
+            name = fld.name;
+          }
+        });
+        self.chartX(name);
+      }
+      if (self.chartYSingle() === null || typeof self.chartYSingle() === 'undefined') {
+        let name = self.result.cleanedNumericMeta()[1].name;
+        self.result.cleanedNumericMeta().forEach(fld => {
+          if (
+            fld.name.toLowerCase().indexOf('lon') > -1 ||
+            fld.name.toLowerCase().indexOf('lng') > -1
+          ) {
+            name = fld.name;
+          }
+        });
+        self.chartYSingle(name);
+      }
+      return;
+    }
+
+    if (
+      (self.chartX() === null || typeof self.chartX() === 'undefined') &&
+      (type == ko.HUE_CHARTS.TYPES.BARCHART ||
+        type == ko.HUE_CHARTS.TYPES.PIECHART ||
+        type == ko.HUE_CHARTS.TYPES.GRADIENTMAP) &&
+      self.result.cleanedStringMeta().length >= 1
+    ) {
+      self.chartX(self.result.cleanedStringMeta()[0].name);
+    }
+
+    if (self.result.cleanedNumericMeta().length > 0) {
+      if (
+        self.chartYMulti().length === 0 &&
+        (type === ko.HUE_CHARTS.TYPES.BARCHART || type === ko.HUE_CHARTS.TYPES.LINECHART)
+      ) {
+        self.chartYMulti.push(
+          self.result.cleanedNumericMeta()[Math.min(self.result.cleanedNumericMeta().length - 1, 1)]
+            .name
+        );
+      } else if (
+        (self.chartYSingle() === null || typeof self.chartYSingle() === 'undefined') &&
+        (type === ko.HUE_CHARTS.TYPES.PIECHART ||
+          type === ko.HUE_CHARTS.TYPES.MAP ||
+          type === ko.HUE_CHARTS.TYPES.GRADIENTMAP ||
+          type === ko.HUE_CHARTS.TYPES.SCATTERCHART ||
+          (type === ko.HUE_CHARTS.TYPES.BARCHART && self.chartXPivot() !== null))
+      ) {
+        if (self.chartYMulti().length === 0) {
+          self.chartYSingle(
+            self.result.cleanedNumericMeta()[
+              Math.min(self.result.cleanedNumericMeta().length - 1, 1)
+            ].name
+          );
+        } else {
+          self.chartYSingle(self.chartYMulti()[0]);
+        }
+      }
+    }
+  }
+
+  self.showChart.subscribe(val => {
+    if (val) {
+      self.showGrid(false);
+      self.isResultSettingsVisible(true);
+      $(document).trigger('forceChartDraw', self);
+      huePubSub.publish('editor.chart.shown', self);
+      prepopulateChart();
+    }
+  });
+  self.showLogs.subscribe(val => {
+    huePubSub.publish('redraw.fixed.headers');
+    if (val) {
+      self.getLogs();
+    }
+    if (vm.editorMode()) {
+      $.totalStorage('hue.editor.showLogs', val);
+    }
+  });
+
+  self.isLoading = ko.computed(() => {
+    return self.status() == 'loading';
+  });
+
+  self.resultsKlass = ko.computed(() => {
+    return 'results ' + self.type();
+  });
+
+  self.errorsKlass = ko.computed(() => {
+    return self.resultsKlass() + ' alert alert-error';
+  });
+
+  self.is_redacted = ko.observable(
+    typeof snippet.is_redacted != 'undefined' && snippet.is_redacted != null
+      ? snippet.is_redacted
+      : false
+  );
+
+  self.chartType = ko.observable(
+    typeof snippet.chartType != 'undefined' && snippet.chartType != null
+      ? snippet.chartType
+      : ko.HUE_CHARTS.TYPES.BARCHART
+  );
+  self.chartType.subscribe(prepopulateChart);
+  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.chartTimelineType = ko.observable(
+    typeof snippet.chartTimelineType != 'undefined' && snippet.chartTimelineType != null
+      ? snippet.chartTimelineType
+      : 'bar'
+  );
+  self.chartLimits = ko.observableArray([5, 10, 25, 50, 100]);
+  self.chartLimit = ko.observable(
+    typeof snippet.chartLimit != 'undefined' && snippet.chartLimit != null
+      ? snippet.chartLimit
+      : null
+  );
+  self.chartLimit.extend({ notify: 'always' });
+  self.chartX = ko.observable(
+    typeof snippet.chartX != 'undefined' && snippet.chartX != null ? snippet.chartX : null
+  );
+  self.chartX.extend({ notify: 'always' });
+  self.chartXPivot = ko.observable(
+    typeof snippet.chartXPivot != 'undefined' && snippet.chartXPivot != null
+      ? snippet.chartXPivot
+      : null
+  );
+  self.chartXPivot.extend({ notify: 'always' });
+  self.chartXPivot.subscribe(prepopulateChart);
+  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.chartMapType = ko.observable(
+    typeof snippet.chartMapType != 'undefined' && snippet.chartMapType != null
+      ? snippet.chartMapType
+      : 'marker'
+  );
+  self.chartMapLabel = ko.observable(
+    typeof snippet.chartMapLabel != 'undefined' && snippet.chartMapLabel != null
+      ? snippet.chartMapLabel
+      : null
+  );
+  self.chartMapHeat = ko.observable(
+    typeof snippet.chartMapHeat != 'undefined' && snippet.chartMapHeat != null
+      ? snippet.chartMapHeat
+      : null
+  );
+  self.hideStacked = ko.computed(() => {
+    return self.chartYMulti().length <= 1;
+  });
+
+  self.hasDataForChart = ko.computed(() => {
+    if (
+      self.chartType() == ko.HUE_CHARTS.TYPES.BARCHART ||
+      self.chartType() == ko.HUE_CHARTS.TYPES.LINECHART ||
+      self.chartType() == ko.HUE_CHARTS.TYPES.TIMELINECHART
+    ) {
+      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(newValue => {
+    self.chartX.notifySubscribers();
+    self.chartX.valueHasMutated();
+  });
+
+  self.chartType.subscribe(val => {
+    $(document).trigger('forceChartDraw', self);
+  });
+
+  self.previousChartOptions = {};
+
+  function guessMetaField(field) {
+    let _fld = null;
+    if (field) {
+      if (self.result.cleanedMeta().length > 0) {
+        self.result.cleanedMeta().forEach(fld => {
+          if (
+            fld.name.toLowerCase() === field.toLowerCase() ||
+            field.toLowerCase() === fld.name.toLowerCase()
+          ) {
+            _fld = fld.name;
+          }
+        });
+      }
+    }
+    return _fld;
+  }
+
+  function guessMetaFields(fields) {
+    const _fields = [];
+    if (fields) {
+      fields.forEach(fld => {
+        const _field = guessMetaField(fld);
+        if (_field) {
+          _fields.push(_field);
+        }
+      });
+    }
+    return _fields;
+  }
+
+  self.result.meta.subscribe(newValue => {
+    self.chartLimit(self.previousChartOptions.chartLimit);
+    self.chartX(guessMetaField(self.previousChartOptions.chartX));
+    self.chartXPivot(self.previousChartOptions.chartXPivot);
+    self.chartYSingle(guessMetaField(self.previousChartOptions.chartYSingle));
+    self.chartMapType(self.previousChartOptions.chartMapType);
+    self.chartMapLabel(guessMetaField(self.previousChartOptions.chartMapLabel));
+    self.chartMapHeat(self.previousChartOptions.chartMapHeat);
+    self.chartYMulti(guessMetaFields(self.previousChartOptions.chartYMulti) || []);
+    self.chartSorting(self.previousChartOptions.chartSorting);
+    self.chartScatterGroup(self.previousChartOptions.chartScatterGroup);
+    self.chartScatterSize(self.previousChartOptions.chartScatterSize);
+    self.chartScope(self.previousChartOptions.chartScope);
+    self.chartTimelineType(self.previousChartOptions.chartTimelineType);
+  });
+
+  self.isResultSettingsVisible = ko.observable(
+    typeof snippet.isResultSettingsVisible != 'undefined' && snippet.isResultSettingsVisible != null
+      ? snippet.isResultSettingsVisible
+      : false
+  );
+  self.toggleResultSettings = function() {
+    self.isResultSettingsVisible(!self.isResultSettingsVisible());
+  };
+  self.isResultSettingsVisible.subscribe(() => {
+    $(document).trigger('toggleResultSettings', self);
+  });
+
+  self.settingsVisible = ko.observable(
+    typeof snippet.settingsVisible != 'undefined' && snippet.settingsVisible != null
+      ? snippet.settingsVisible
+      : false
+  );
+  self.saveResultsModalVisible = ko.observable(false);
+
+  self.checkStatusTimeout = null;
+
+  self.getContext = function() {
+    return {
+      id: self.id,
+      type: self.type,
+      status: self.status,
+      statementType: self.statementType,
+      statement: self.statement,
+      aceCursorPosition: self.aceCursorPosition,
+      statementPath: self.statementPath,
+      associatedDocumentUuid: self.associatedDocumentUuid,
+      properties: self.properties,
+      result: self.result.getContext(),
+      database: self.database,
+      compute: self.compute(),
+      wasBatchExecuted: self.wasBatchExecuted()
+    };
+  };
+
+  self.complexity = ko.observable();
+  self.hasComplexity = ko.pureComputed(() => {
+    return self.complexity() && Object.keys(self.complexity()).length > 0;
+  });
+  self.hasRisks = ko.pureComputed(() => {
+    return (
+      self.hasComplexity() && self.complexity()['hints'] && self.complexity()['hints'].length > 0
+    );
+  });
+  self.topRisk = ko.pureComputed(() => {
+    if (self.hasRisks()) {
+      return self.complexity()['hints'][0];
+    } else {
+      return null;
+    }
+  });
+
+  self.suggestion = ko.observable('');
+  self.hasSuggestion = ko.observable(null);
+
+  self.compatibilityCheckRunning = ko.observable(false);
+
+  self.compatibilitySourcePlatforms = [];
+  Object.keys(COMPATIBILITY_SOURCE_PLATFORMS).forEach(key => {
+    self.compatibilitySourcePlatforms.push(COMPATIBILITY_SOURCE_PLATFORMS[key]);
+  });
+
+  self.compatibilitySourcePlatform = ko.observable(COMPATIBILITY_SOURCE_PLATFORMS[self.type()]);
+  self.compatibilitySourcePlatform.subscribe(newValue => {
+    if (newValue && newValue.value !== self.type()) {
+      self.hasSuggestion(null);
+      self.compatibilityTargetPlatform(COMPATIBILITY_TARGET_PLATFORMS[self.type()]);
+      self.queryCompatibility();
+    }
+  });
+
+  self.compatibilityTargetPlatforms = [];
+  Object.keys(COMPATIBILITY_TARGET_PLATFORMS).forEach(key => {
+    self.compatibilityTargetPlatforms.push(COMPATIBILITY_TARGET_PLATFORMS[key]);
+  });
+  self.compatibilityTargetPlatform = ko.observable(COMPATIBILITY_TARGET_PLATFORMS[self.type()]);
+
+  self.showOptimizer = ko.observable(
+    apiHelper.getFromTotalStorage('editor', 'show.optimizer', false)
+  );
+  self.showOptimizer.subscribe(newValue => {
+    if (newValue !== null) {
+      apiHelper.setInTotalStorage('editor', 'show.optimizer', newValue);
+    }
+  });
+
+  if (HAS_OPTIMIZER && !vm.isNotificationManager()) {
+    let lastComplexityRequest;
+    let lastCheckedComplexityStatement;
+    const knownResponses = [];
+
+    self.delayedStatement = ko
+      .pureComputed(self.statement)
+      .extend({ rateLimit: { method: 'notifyWhenChangesStop', timeout: 2000 } });
+
+    const handleRiskResponse = function(data) {
+      if (data.status == 0) {
+        self.hasSuggestion('');
+        self.complexity(data.query_complexity);
+      } else {
+        self.hasSuggestion('error');
+        self.complexity({ hints: [] });
+      }
+      huePubSub.publish('editor.active.risks', {
+        editor: self.ace(),
+        risks: self.complexity() || {}
+      });
+      lastCheckedComplexityStatement = self.statement();
+    };
+
+    const clearActiveRisks = function() {
+      if (self.hasSuggestion() !== null && typeof self.hasSuggestion() !== 'undefined') {
+        self.hasSuggestion(null);
+      }
+
+      if (self.suggestion() !== '') {
+        self.suggestion('');
+      }
+
+      if (self.complexity() !== {}) {
+        self.complexity(undefined);
+        huePubSub.publish('editor.active.risks', {
+          editor: self.ace(),
+          risks: {}
+        });
+      }
+    };
+
+    self.positionStatement.subscribe(newStatement => {
+      if (newStatement) {
+        const hash = newStatement.statement.hashCode();
+        const unknownResponse = knownResponses.every(knownResponse => {
+          if (knownResponse.hash === hash) {
+            handleRiskResponse(knownResponse.data);
+            return false;
+          }
+          return true;
+        });
+        if (unknownResponse) {
+          clearActiveRisks();
+        }
+      }
+    });
+
+    self.checkComplexity = function() {
+      if (!self.inFocus() || lastCheckedComplexityStatement === self.statement()) {
+        return;
+      }
+
+      // The syntaxError property is only set if the syntax checker is active and has found an
+      // error, see AceLocationHandler.
+      if (self.positionStatement() && self.positionStatement().syntaxError) {
+        return;
+      }
+
+      apiHelper.cancelActiveRequest(lastComplexityRequest);
+
+      hueAnalytics.log('notebook', 'get_query_risk');
+      clearActiveRisks();
+
+      const changeSubscription = self.statement.subscribe(() => {
+        changeSubscription.dispose();
+        apiHelper.cancelActiveRequest(lastComplexityRequest);
+      });
+
+      const hash = self.statement().hashCode();
+
+      const unknownResponse = knownResponses.every(knownResponse => {
+        if (knownResponse.hash === hash) {
+          handleRiskResponse(knownResponse.data);
+          return false;
+        }
+        return true;
+      });
+      if (unknownResponse) {
+        lastComplexityRequest = $.ajax({
+          type: 'POST',
+          url: '/notebook/api/optimizer/statement/risk',
+          timeout: 30000, // 30 seconds
+          data: {
+            notebook: komapping.toJSON(notebook.getContext()),
+            snippet: komapping.toJSON(self.getContext())
+          },
+          success: function(data) {
+            knownResponses.unshift({
+              hash: hash,
+              data: data
+            });
+            if (knownResponses.length > 50) {
+              knownResponses.pop();
+            }
+            handleRiskResponse(data);
+          },
+          always: function(data) {
+            changeSubscription.dispose();
+          }
+        });
+      }
+    };
+
+    if (self.type() === 'hive' || self.type() === 'impala') {
+      if (self.statement_raw()) {
+        window.setTimeout(() => {
+          self.checkComplexity();
+        }, 2000);
+      }
+      self.delayedStatement.subscribe(() => {
+        self.checkComplexity();
+      });
+    }
+  }
+
+  self._ajaxError = function(data, callback) {
+    if (data.status == -2) {
+      // Session expired
+      const 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');
+      if (data.message) {
+        self.errors.push({ message: data.message, help: null, line: null, col: null });
+        huePubSub.publish('editor.snippet.result.normal', self);
+      }
+    } else if (data.status == -4) {
+      // Operation timed out
+      notebook.retryModalCancel = function() {
+        self.status('failed');
+        huePubSub.publish('hide.retry.modal');
+      };
+      notebook.retryModalConfirm = function() {
+        if (callback) {
+          callback();
+        }
+        huePubSub.publish('hide.retry.modal');
+      };
+      huePubSub.publish('show.retry.modal');
+    } 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');
+      const match = ERROR_REGEX.exec(data.message);
+      if (match) {
+        let errorLine = parseInt(match[1]);
+        let errorCol;
+        if (typeof match[3] !== 'undefined') {
+          errorCol = parseInt(match[3]);
+        }
+        if (self.positionStatement()) {
+          if (errorCol && errorLine === 1) {
+            errorCol += self.positionStatement().location.first_column;
+          }
+          errorLine += self.positionStatement().location.first_line - 1;
+        }
+
+        self.errors.push({
+          message: data.message.replace(
+            match[0],
+            'line ' + errorLine + (errorCol !== null ? ':' + errorCol : '')
+          ),
+          help: null,
+          line: errorLine - 1,
+          col: errorCol
+        });
+      } else {
+        self.errors.push({
+          message: data.message,
+          help: data.help,
+          line: null,
+          col: null
+        });
+      }
+    } else {
+      $(document).trigger('error', data.message);
+      self.status('failed');
+    }
+  };
+
+  self.wasBatchExecuted = ko.observable(
+    typeof snippet.wasBatchExecuted != 'undefined' && snippet.wasBatchExecuted != null
+      ? snippet.wasBatchExecuted
+      : false
+  );
+  self.isReady = ko.computed(() => {
+    return (
+      (self.statementType() == 'text' &&
+        ((self.isSqlDialect() && self.statement() !== '') ||
+          (['jar', 'java', 'spark2', 'distcp'].indexOf(self.type()) == -1 &&
+            self.statement() !== '') ||
+          (['jar', 'java'].indexOf(self.type()) != -1 &&
+            (self.properties().app_jar() != '' && self.properties().class() != '')) ||
+          (['spark2'].indexOf(self.type()) != -1 && self.properties().jars().length > 0) ||
+          (['shell'].indexOf(self.type()) != -1 && self.properties().command_path().length > 0) ||
+          (['mapreduce'].indexOf(self.type()) != -1 && self.properties().app_jar().length > 0) ||
+          (['distcp'].indexOf(self.type()) != -1 &&
+            self.properties().source_path().length > 0 &&
+            self.properties().destination_path().length > 0))) ||
+      (self.statementType() == 'file' && self.statementPath().length > 0) ||
+      (self.statementType() == 'document' &&
+        self.associatedDocumentUuid() &&
+        self.associatedDocumentUuid().length > 0)
+    );
+  });
+  self.lastExecuted = ko.observable(
+    typeof snippet.lastExecuted != 'undefined' && snippet.lastExecuted != null
+      ? snippet.lastExecuted
+      : 0
+  );
+  self.lastAceSelectionRowOffset = ko.observable(snippet.lastAceSelectionRowOffset || 0);
+
+  self.executingBlockingOperation = null; // A ExecuteStatement()
+  self.showLongOperationWarning = ko.observable(false);
+  self.showLongOperationWarning.subscribe(newValue => {
+    if (newValue) {
+      hueAnalytics.convert('editor', 'showLongOperationWarning');
+    }
+  });
+
+  let longOperationTimeout = -1;
+
+  function startLongOperationTimeout() {
+    longOperationTimeout = window.setTimeout(() => {
+      self.showLongOperationWarning(true);
+    }, 2000);
+  }
+
+  function stopLongOperationTimeout() {
+    window.clearTimeout(longOperationTimeout);
+    self.showLongOperationWarning(false);
+  }
+
+  self.lastExecutedStatements = undefined;
+  self.lastExecutedSelectionRange = undefined;
+
+  self.execute = function(automaticallyTriggered) {
+    if (!automaticallyTriggered && self.ace()) {
+      const selectionRange = self.ace().getSelectionRange();
+
+      if (
+        self.lastExecutedSelectionRange &&
+        (selectionRange.start.row !== selectionRange.end.row &&
+          selectionRange.start.column !== selectionRange.end.column) &&
+        (selectionRange.start.row !== self.lastExecutedSelectionRange.start.row ||
+          selectionRange.start.column !== self.lastExecutedSelectionRange.start.column ||
+          selectionRange.end.row !== self.lastExecutedSelectionRange.end.row ||
+          selectionRange.end.column !== self.lastExecutedSelectionRange.end.column)
+      ) {
+        // Manual execute and there is a selection that is different from the last execute
+        self.result.cancelBatchExecution();
+      }
+      self.lastExecutedSelectionRange = selectionRange;
+    }
+
+    if (self.isCanceling()) {
+      return;
+    }
+    const now = new Date().getTime();
+    if (now - self.lastExecuted() < 1000 || !self.isReady()) {
+      return; // Prevent fast clicks
+    }
+
+    if (!automaticallyTriggered) {
+      // Do not cancel statements that are parts of a set of steps to execute (e.g. import). Only cancel statements as requested by user
+      if (self.status() === 'running' || self.status() === 'loading') {
+        self.cancel(); // TODO: Wait for cancel to finish
+      } else {
+        self.result.clear();
+      }
+    }
+
+    if (self.type() === 'impala') {
+      self.showExecutionAnalysis(false);
+      huePubSub.publish('editor.clear.execution.analysis');
+    }
+
+    self.status('running');
+    self.statusForButtons('executing');
+
+    if (self.isSqlDialect()) {
+      huePubSub.publish('editor.refresh.statement.locations', self);
+    }
+
+    self.lastExecutedStatements = self.statement();
+
+    if (self.ace()) {
+      huePubSub.publish('ace.set.autoexpand', { autoExpand: false, snippet: self });
+      const selectionRange = self.ace().getSelectionRange();
+      self.lastAceSelectionRowOffset(Math.min(selectionRange.start.row, selectionRange.end.row));
+    }
+
+    self.previousChartOptions = vm._getPreviousChartOptions(self);
+    $(document).trigger('executeStarted', { vm: vm, snippet: self });
+    self.lastExecuted(now);
+    $('.jHueNotify').remove();
+    hueAnalytics.log('notebook', 'execute/' + self.type());
+
+    notebook.forceHistoryInitialHeight(true);
+
+    if (self.result.handle()) {
+      self.close();
+    }
+
+    self.errors([]);
+    huePubSub.publish('editor.clear.highlighted.errors', self.ace());
+    self.result.clear();
+    self.progress(0);
+    self.jobs([]);
+    self.result.logs('');
+    self.result.statement_range({
+      start: {
+        row: 0,
+        column: 0
+      },
+      end: {
+        row: 0,
+        column: 0
+      }
+    });
+    notebook.historyCurrentPage(1);
+
+    // TODO: rename startLongOperationTimeout to startBlockingOperationTimeout
+    // TODO: stop blocking operation UI if there is one
+    // TODO: offer to stop blocking submit or fetch operation UI if there is one (add a new call to function for cancelBlockingOperation)
+    // TODO: stop current blocking operation if there is one
+    // TODO: handle jquery.dataTables.1.8.2.min.js:150 Uncaught TypeError: Cannot read property 'asSorting' of undefined on some cancels
+    // TODO: we should cancel blocking operation when leaving notebook (similar to unload())
+    // TODO: we should test when we go back to a query history of a blocking operation that we left
+    startLongOperationTimeout();
+
+    self.currentQueryTab('queryHistory');
+
+    self.executingBlockingOperation = $.post(
+      '/notebook/api/execute/' + self.type(),
+      {
+        notebook: vm.editorMode()
+          ? komapping.toJSON(notebook, NOTEBOOK_MAPPING)
+          : komapping.toJSON(notebook.getContext()),
+        snippet: komapping.toJSON(self.getContext())
+      },
+      data => {
+        try {
+          if (self.isSqlDialect() && data && data.handle) {
+            self.lastExecutedStatement(sqlStatementsParser.parse(data.handle.statement)[0]);
+          } else {
+            self.lastExecutedStatement(null);
+          }
+        } catch (e) {
+          self.lastExecutedStatement(null);
+        }
+        self.statusForButtons('executed');
+        huePubSub.publish('ace.set.autoexpand', { autoExpand: true, snippet: self });
+        stopLongOperationTimeout();
+
+        if (vm.editorMode() && data.history_id) {
+          if (!vm.isNotificationManager()) {
+            let url =
+              '/notebook/editor' + (vm.isMobile() ? '_m' : '') + '?editor=' + data.history_id;
+            if (vm.isHue4()) {
+              url = vm.URLS.hue4 + '?editor=' + data.history_id;
+            }
+            vm.changeURL(url);
+          }
+          notebook.id(data.history_id);
+          notebook.uuid(data.history_uuid);
+          notebook.isHistory(true);
+          notebook.parentSavedQueryUuid(data.history_parent_uuid);
+        }
+
+        if (data.status === 0) {
+          self.result.handle(data.handle);
+          self.result.hasResultset(data.handle.has_result_set);
+          if (data.handle.sync) {
+            self.loadData(data.result, 100);
+            self.status('available');
+            self.progress(100);
+            self.result.endTime(new Date());
+          } else if (!notebook.unloaded()) {
+            self.checkStatus();
+          }
+          if (vm.isOptimizerEnabled()) {
+            huePubSub.publish('editor.upload.query', data.history_id);
+          }
+        } else {
+          self._ajaxError(data, self.execute);
+          notebook.isExecutingAll(false);
+        }
+
+        if (data.handle) {
+          if (vm.editorMode()) {
+            if (vm.isNotificationManager()) {
+              // Update task status
+              const tasks = $.grep(notebook.history(), row => {
+                return row.uuid() == notebook.uuid();
+              });
+              if (tasks.length === 1) {
+                tasks[0].status(self.status());
+                self.result.logs(data.message);
+              }
+            } else {
+              notebook.history.unshift(
+                notebook._makeHistoryRecord(
+                  undefined,
+                  data.handle.statement,
+                  self.lastExecuted(),
+                  self.status(),
+                  notebook.name(),
+                  notebook.uuid()
+                )
+              );
+            }
+          }
+
+          if (data.handle.statements_count != null) {
+            self.result.statements_count(data.handle.statements_count);
+            self.result.statement_id(data.handle.statement_id);
+            self.result.previous_statement_hash(data.previous_statement_hash);
+
+            if (
+              data.handle.statements_count > 1 &&
+              data.handle.start != null &&
+              data.handle.end != null
+            ) {
+              self.result.statement_range({
+                start: data.handle.start,
+                end: data.handle.end
+              });
+            }
+          }
+        }
+      }
+    )
+      .fail((xhr, textStatus, errorThrown) => {
+        if (self.statusForButtons() != 'canceled' && xhr.status !== 502) {
+          // No error when manually canceled
+          $(document).trigger('error', xhr.responseText);
+        }
+        self.status('failed');
+        self.statusForButtons('executed');
+      })
+      .always(() => {
+        self.executingBlockingOperation = null;
+      });
+  };
+
+  self.reexecute = function() {
+    self.result.cancelBatchExecution();
+    self.execute();
+  };
+
+  self.formatEnabled = ko.pureComputed(() => {
+    return (
+      self.statement_raw && self.statement_raw() != null && self.statement_raw().length < 400000
+    ); // ie: 5000 lines at 80 chars per line
+  });
+
+  self.format = function() {
+    if (self.isSqlDialect()) {
+      apiHelper
+        .formatSql({
+          statements:
+            self.ace().getSelectedText() != '' ? self.ace().getSelectedText() : self.statement_raw()
+        })
+        .done(data => {
+          if (data.status == 0) {
+            if (self.ace().getSelectedText() != '') {
+              self
+                .ace()
+                .session.replace(
+                  self.ace().session.selection.getRange(),
+                  data.formatted_statements
+                );
+            } else {
+              self.statement_raw(data.formatted_statements);
+              self.ace().setValue(self.statement_raw(), 1);
+            }
+          } else {
+            self._ajaxError(data);
+          }
+        });
+    }
+    hueAnalytics.log('notebook', 'format');
+  };
+
+  self.clear = function() {
+    hueAnalytics.log('notebook', 'clear');
+    self.ace().setValue('', 1);
+    self.result.clear();
+    self.status('ready');
+  };
+
+  self.explain = function() {
+    hueAnalytics.log('notebook', 'explain');
+
+    if (self.statement() == '' || self.status() == 'running' || self.status() === 'loading') {
+      return;
+    }
+
+    self.result.explanation('');
+    self.errors([]);
+    self.progress(0);
+    self.status('ready');
+
+    $.post(
+      '/notebook/api/explain',
+      {
+        notebook: komapping.toJSON(notebook.getContext()),
+        snippet: komapping.toJSON(self.getContext())
+      },
+      data => {
+        if (data.status == 0) {
+          self.currentQueryTab('queryExplain');
+          self.result.fetchedOnce(true);
+          self.result.explanation(data.explanation);
+        } else {
+          self._ajaxError(data);
+        }
+      }
+    );
+  };
+
+  let lastCompatibilityRequest;
+
+  self.checkCompatibility = function() {
+    self.hasSuggestion(null);
+    self.compatibilitySourcePlatform(COMPATIBILITY_SOURCE_PLATFORMS[self.type()]);
+    self.compatibilityTargetPlatform(
+      COMPATIBILITY_TARGET_PLATFORMS[self.type() === 'hive' ? 'impala' : 'hive']
+    );
+    self.queryCompatibility();
+  };
+
+  self.queryCompatibility = function(targetPlatform) {
+    apiHelper.cancelActiveRequest(lastCompatibilityRequest);
+
+    hueAnalytics.log('notebook', 'compatibility');
+    self.compatibilityCheckRunning(targetPlatform != self.type());
+    self.hasSuggestion(null);
+    const positionStatement = self.positionStatement();
+
+    lastCompatibilityRequest = $.post(
+      '/notebook/api/optimizer/statement/compatibility',
+      {
+        notebook: komapping.toJSON(notebook.getContext()),
+        snippet: komapping.toJSON(self.getContext()),
+        sourcePlatform: self.compatibilitySourcePlatform().value,
+        targetPlatform: self.compatibilityTargetPlatform().value
+      },
+      data => {
+        if (data.status === 0) {
+          self.aceErrorsHolder([]);
+          self.aceWarningsHolder([]);
+          self.suggestion(komapping.fromJS(data.query_compatibility));
+          if (self.suggestion().queryError && self.suggestion().queryError.errorString()) {
+            const match = ERROR_REGEX.exec(self.suggestion().queryError.errorString());
+            let line = null;
+            if (match) {
+              if (positionStatement) {
+                line = positionStatement.location.first_line + parseInt(match[1]) + 1;
+              } else {
+                line = parseInt(match[1]) - 1;
+              }
+            }
+            self.aceWarningsHolder.push({
+              message: self.suggestion().queryError.errorString(),
+              line: line,
+              col:
+                match === null ? null : typeof match[3] !== 'undefined' ? parseInt(match[3]) : null
+            });
+            self.status('with-optimizer-report');
+          }
+          if (self.suggestion().parseError()) {
+            const match = ERROR_REGEX.exec(self.suggestion().parseError());
+            self.aceErrorsHolder.push({
+              message: self.suggestion().parseError(),
+              line: match === null ? null : parseInt(match[1]) - 1,
+              col:
+                match === null ? null : typeof match[3] !== 'undefined' ? parseInt(match[3]) : null
+            });
+            self.status('with-optimizer-report');
+          }
+          self.showOptimizer(true);
+          self.hasSuggestion(true);
+        } else {
+          $(document).trigger('error', data.message);
+        }
+      }
+    )
+      .fail((xhr, textStatus, errorThrown) => {
+        if (xhr.status !== 502) {
+          $(document).trigger('error', xhr.responseText);
+        }
+      })
+      .always(() => {
+        self.compatibilityCheckRunning(false);
+      });
+  };
+
+  self.fetchResult = function(rows, startOver) {
+    if (typeof startOver == 'undefined') {
+      startOver = true;
+    }
+    self.fetchResultData(rows, startOver);
+    //self.fetchResultMetadata(rows);
+  };
+
+  self.isFetchingData = false;
+
+  self.fetchExecutionAnalysis = function() {
+    if (self.type() === 'impala') {
+      // TODO: Use real query ID
+      huePubSub.publish('editor.update.execution.analysis', {
+        analysisPossible: true,
+        compute: self.compute(),
+        queryId: notebook.getContext().id(),
+        name: self.jobs()[0] && self.jobs()[0].name
+      });
+    } else {
+      huePubSub.publish('editor.update.execution.analysis', {
+        analysisPossible: false
+      });
+    }
+  };
+
+  self.fetchResultData = function(rows, startOver) {
+    if (!self.isFetchingData) {
+      if (self.status() === 'available') {
+        startLongOperationTimeout();
+        self.isFetchingData = true;
+        hueAnalytics.log('notebook', 'fetchResult/' + rows + '/' + startOver);
+        $.post(
+          '/notebook/api/fetch_result_data',
+          {
+            notebook: komapping.toJSON(notebook.getContext()),
+            snippet: komapping.toJSON(self.getContext()),
+            rows: rows,
+            startOver: startOver
+          },
+          data => {
+            stopLongOperationTimeout();
+            data = JSON.bigdataParse(data);
+            if (data.status === 0) {
+              self.showExecutionAnalysis(true);
+              self.loadData(data.result, rows);
+            } else {
+              self._ajaxError(data, () => {
+                self.isFetchingData = false;
+                self.fetchResultData(rows, startOver);
+              });
+              $(document).trigger('renderDataError', { snippet: self });
+            }
+          },
+          'text'
+        )
+          .fail((xhr, textStatus, errorThrown) => {
+            if (xhr.status !== 502) {
+              $(document).trigger('error', xhr.responseText);
+            }
+          })
+          .always(() => {
+            self.isFetchingData = false;
+          });
+      } else {
+        huePubSub.publish('editor.snippet.result.normal', self);
+      }
+    }
+  };
+
+  self.loadData = function(result, rows) {
+    rows -= result.data.length;
+
+    if (result.data.length > 0) {
+      self.currentQueryTab('queryResults');
+    }
+
+    const _initialIndex = self.result.data().length;
+    const _tempData = [];
+    $.each(result.data, (index, row) => {
+      row.unshift(_initialIndex + index + 1);
+      self.result.data.push(row);
+      _tempData.push(row);
+    });
+
+    if (self.result.rows() == null || (self.result.rows() + '').indexOf('+') != -1) {
+      self.result.rows(self.result.data().length + (result.has_more ? '+' : ''));
+    }
+
+    self.result.images(
+      typeof result.images != 'undefined' && result.images != null ? result.images : []
+    );
+
+    huePubSub.publish('editor.render.data', {
+      data: _tempData,
+      snippet: self,
+      initial: _initialIndex == 0
+    });
+
+    if (!self.result.fetchedOnce()) {
+      result.meta.unshift({ type: 'INT_TYPE', name: '', comment: null });
+      self.result.meta(result.meta);
+      self.result.type(result.type);
+      self.result.fetchedOnce(true);
+    }
+
+    self.result.meta().forEach(meta => {
+      if (
+        $.inArray(meta.type, [
+          'TINYINT_TYPE',
+          'SMALLINT_TYPE',
+          'INT_TYPE',
+          'BIGINT_TYPE',
+          'FLOAT_TYPE',
+          'DOUBLE_TYPE',
+          'DECIMAL_TYPE'
+        ]) > -1
+      ) {
+        meta.cssClass = 'sort-numeric';
+      } else if ($.inArray(meta.type, ['TIMESTAMP_TYPE', 'DATE_TYPE', 'DATETIME_TYPE']) > -1) {
+        meta.cssClass = 'sort-date';
+      } else {
+        meta.cssClass = 'sort-string';
+      }
+    });
+
+    self.result.hasMore(result.has_more);
+
+    if (result.has_more && rows > 0) {
+      setTimeout(() => {
+        self.fetchResultData(rows, false);
+      }, 500);
+    } else if (
+      !vm.editorMode() &&
+      !notebook.isPresentationMode() &&
+      notebook.snippets()[notebook.snippets().length - 1] == self
+    ) {
+      notebook.newSnippet();
+    }
+  };
+
+  self.fetchResultMetadata = function() {
+    $.post(
+      '/notebook/api/fetch_result_metadata',
+      {
+        notebook: komapping.toJSON(notebook.getContext()),
+        snippet: komapping.toJSON(self.getContext())
+      },
+      data => {
+        if (data.status == 0) {
+          self.result.meta(data.result.meta);
+        } else {
+          $(document).trigger('error', data.message);
+        }
+      }
+    ).fail((xhr, textStatus, errorThrown) => {
+      if (xhr.status !== 502) {
+        $(document).trigger('error', xhr.responseText);
+      }
+      self.status('failed');
+    });
+  };
+
+  self.fetchResultSize = function(n, query_id) {
+    $.post(
+      '/notebook/api/fetch_result_size',
+      {
+        notebook: komapping.toJSON(notebook.getContext()),
+        snippet: komapping.toJSON(self.getContext())
+      },
+      data => {
+        if (query_id == notebook.id()) {
+          // If still on the same result
+          if (data.status == 0) {
+            if (data.result.rows != null) {
+              self.result.rows(data.result.rows);
+            } else if (self.type() == 'impala' && n > 0) {
+              setTimeout(() => {
+                self.fetchResultSize(n - 1, query_id);
+              }, 1000);
+            }
+          } else if (data.status == 5) {
+            // No supported yet for this snippet
+          } else {
+            //$(document).trigger("error", data.message);
+          }
+        }
+      }
+    ).fail((xhr, textStatus, errorThrown) => {
+      //$(document).trigger("error", xhr.responseText);
+    });
+  };
+
+  self.checkStatus = function() {
+    $.post(
+      '/notebook/api/check_status',
+      {
+        notebook: komapping.toJSON(notebook.getContext()),
+        snippet: komapping.toJSON(self.getContext())
+      },
+      data => {
+        if (self.statusForButtons() == 'canceling' || self.status() == 'canceled') {
+          // Query was canceled in the meantime, do nothing
+        } else {
+          self.result.endTime(new Date());
+
+          if (data.status === 0) {
+            self.status(data.query_status.status);
+
+            if (
+              self.status() == 'running' ||
+              self.status() == 'starting' ||
+              self.status() == 'waiting'
+            ) {
+              const delay = self.result.executionTime() > 45000 ? 5000 : 1000; // 5s if more than 45s
+              if (!notebook.unloaded()) {
+                self.checkStatusTimeout = setTimeout(self.checkStatus, delay);
+              }
+            } else if (self.status() === 'available') {
+              self.fetchResult(100);
+              self.progress(100);
+              if (self.isSqlDialect()) {
+                if (self.result.handle().has_result_set) {
+                  const _query_id = notebook.id();
+                  setTimeout(() => {
+                    // Delay until we get IMPALA-5555
+                    self.fetchResultSize(10, _query_id);
+                  }, 2000);
+                  self.checkDdlNotification(); // DDL CTAS with Impala
+                } else if (self.lastExecutedStatement()) {
+                  self.checkDdlNotification();
+                } else {
+                  self.onDdlExecute();
+                }
+              }
+              if (notebook.isExecutingAll()) {
+                notebook.executingAllIndex(notebook.executingAllIndex() + 1);
+                if (notebook.executingAllIndex() < notebook.snippets().length) {
+                  notebook.snippets()[notebook.executingAllIndex()].execute();
+                } else {
+                  notebook.isExecutingAll(false);
+                }
+              }
+              if (!self.result.handle().has_more_statements && vm.successUrl()) {
+                window.location.href = vm.successUrl(); // Not used anymore in Hue 4
+              }
+            } else if (self.status() === 'success') {
+              self.progress(99);
+            }
+          } else if (data.status === -3) {
+            self.status('expired');
+            notebook.isExecutingAll(false);
+          } else {
+            self._ajaxError(data);
+            notebook.isExecutingAll(false);
+          }
+          self.getLogs(); // Need to execute at the end, because updating the status impacts log progress results
+        }
+      }
+    ).fail((xhr, textStatus, errorThrown) => {
+      if (xhr.status !== 502) {
+        $(document).trigger('error', xhr.responseText || textStatus);
+      }
+      self.status('failed');
+      notebook.isExecutingAll(false);
+    });
+  };
+
+  self.checkDdlNotification = function() {
+    if (
+      self.lastExecutedStatement() &&
+      /ALTER|CREATE|DELETE|DROP|GRANT|INSERT|LOAD|SET|TRUNCATE|UPDATE|UPSERT|USE/i.test(
+        self.lastExecutedStatement().firstToken
+      )
+    ) {
+      self.onDdlExecute();
+    } else {
+      window.clearTimeout(self.executeNextTimeout);
+    }
+  };
+
+  self.isCanceling = ko.observable(false);
+
+  self.cancel = function() {
+    window.clearTimeout(self.executeNextTimeout);
+    self.isCanceling(true);
+    if (self.checkStatusTimeout != null) {
+      clearTimeout(self.checkStatusTimeout);
+      self.checkStatusTimeout = null;
+    }
+    hueAnalytics.log('notebook', 'cancel');
+
+    if (self.executingBlockingOperation != null) {
+      self.executingBlockingOperation.abort();
+      self.executingBlockingOperation = null;
+    }
+
+    if ($.isEmptyObject(self.result.handle())) {
+      // Query was not even submitted yet
+      self.statusForButtons('canceled');
+      self.status('failed');
+      self.isCanceling(false);
+      notebook.isExecutingAll(false);
+    } else {
+      self.statusForButtons('canceling');
+      $.post(
+        '/notebook/api/cancel_statement',
+        {
+          notebook: komapping.toJSON(notebook.getContext()),
+          snippet: komapping.toJSON(self.getContext())
+        },
+        data => {
+          self.statusForButtons('canceled');
+          if (data.status == 0) {
+            self.status('canceled');
+            notebook.isExecutingAll(false);
+          } else {
+            self._ajaxError(data);
+          }
+        }
+      )
+        .fail((xhr, textStatus, errorThrown) => {
+          if (xhr.status !== 502) {
+            $(document).trigger('error', xhr.responseText);
+          }
+          self.statusForButtons('canceled');
+          self.status('failed');
+          notebook.isExecutingAll(false);
+        })
+        .always(() => {
+          self.isCanceling(false);
+        });
+    }
+  };
+
+  self.close = function() {
+    if (self.checkStatusTimeout != null) {
+      clearTimeout(self.checkStatusTimeout);
+      self.checkStatusTimeout = null;
+    }
+
+    $.post(
+      '/notebook/api/close_statement',
+      {
+        notebook: komapping.toJSON(notebook.getContext()),
+        snippet: komapping.toJSON(self.getContext())
+      },
+      data => {
+        if (data.status == 0) {
+          // self.status('closed'); // Keep as 'running' as currently it happens before running a new query
+        } else {
+          // self._ajaxError(data);
+        }
+      }
+    ).fail((xhr, textStatus, errorThrown) => {
+      if (xhr.status !== 502) {
+        // $(document).trigger("error", xhr.responseText);
+      }
+      // self.status('failed'); // Can conflict with slow close and new query execution
+    });
+  };
+
+  self.getLogs = function() {
+    $.post(
+      '/notebook/api/get_logs',
+      {
+        notebook: komapping.toJSON(notebook.getContext()),
+        snippet: komapping.toJSON(self.getContext()),
+        from: self.result.logLines,
+        jobs: komapping.toJSON(self.jobs, { ignore: ['percentJob'] }),
+        full_log: self.result.logs
+      },
+      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) {
+            const logs = data.logs.split('\n');
+            self.result.logLines += logs.length;
+            const oldLogs = self.result.logs();
+            if (
+              data.logs &&
+              (oldLogs === '' ||
+                (self.wasBatchExecuted() && data.logs.indexOf('Unable to locate') == -1) ||
+                data.isFullLogs)
+            ) {
+              self.result.logs(data.logs);
+            } else {
+              self.result.logs(oldLogs + '\n' + data.logs);
+            }
+          }
+
+          self.jobs().forEach(job => {
+            if (typeof job.percentJob === 'undefined') {
+              job.percentJob = ko.observable(-1);
+            }
+          });
+
+          if (data.jobs && data.jobs.length > 0) {
+            data.jobs.forEach(job => {
+              const _found = ko.utils.arrayFilter(self.jobs(), item => {
+                return item.name === job.name;
+              });
+              if (_found.length === 0) {
+                if (typeof job.percentJob === 'undefined') {
+                  job.percentJob = ko.observable(-1);
+                } else {
+                  job.percentJob = ko.observable(job.percentJob);
+                }
+                self.jobs.push(job);
+              } else if (typeof job.percentJob !== 'undefined') {
+                for (let i = 0; i < _found.length; i++) {
+                  _found[i].percentJob(job.percentJob);
+                }
+              }
+            });
+            self.jobs().forEach(job => {
+              const _found = ko.utils.arrayFilter(self.jobs(), item => {
+                return item.name === job.name;
+              });
+              if (_found.length === 0) {
+                self.jobs.remove(job);
+              }
+            });
+          }
+          if (self.status() == 'running') {
+            // Maybe the query finished or failed in the meantime
+            self.progress(data.progress);
+          }
+        } else {
+          self._ajaxError(data);
+        }
+      }
+    ).fail((xhr, textStatus, errorThrown) => {
+      if (xhr.status !== 502) {
+        $(document).trigger('error', xhr.responseText || textStatus);
+      }
+      self.status('failed');
+    });
+  };
+
+  self.uploadQueryHistory = function(n) {
+    hueAnalytics.log('notebook', 'upload_query_history');
+
+    $.post(
+      '/metadata/api/optimizer/upload/history',
+      {
+        n: typeof n != 'undefined' ? n : null,
+        sourcePlatform: self.type()
+      },
+      data => {
+        if (data.status == 0) {
+          $(document).trigger(
+            'info',
+            data.upload_history[self.type()].count +
+              ' queries uploaded successfully. Processing them...'
+          );
+          self.watchUploadStatus(data.upload_history[self.type()].status.workloadId);
+        } else {
+          $(document).trigger('error', data.message);
+        }
+      }
+    );
+  };
+
+  self.uploadQuery = function(query_id) {
+    $.post('/metadata/api/optimizer/upload/query', {
+      query_id: query_id,
+      sourcePlatform: self.type()
+    });
+  };
+
+  self.uploadTableStats = function(options) {
+    hueAnalytics.log('notebook', 'load_table_stats');
+    if (options.showProgress) {
+      $(document).trigger('info', 'Preparing table data...');
+    }
+
+    $.post(
+      '/metadata/api/optimizer/upload/table_stats',
+      {
+        db_tables: komapping.toJSON(
+          $.map(options.activeTables, table => {
+            return table.databaseName + '.' + table.tableName;
+          })
+        ),
+        sourcePlatform: komapping.toJSON(self.type()),
+        with_ddl: komapping.toJSON(true),
+        with_table_stats: komapping.toJSON(true),
+        with_columns_stats: komapping.toJSON(true)
+      },
+      data => {
+        if (data.status == 0) {
+          if (options.showProgress) {
+            $(document).trigger(
+              'info',
+              $.map(options.activeTables, table => {
+                return table.tableName;
+              }) + ' stats sent to analyse'
+            );
+          }
+          if (data.upload_table_ddl && options.showProgress) {
+            // With showProgress only currently as can be very slow
+            self.watchUploadStatus(data.upload_table_ddl.status.workloadId, options.showProgress);
+          }
+        } else if (options.showProgress) {
+          $(document).trigger('error', data.message);
+        }
+      }
+    ).always(() => {
+      if (options.callback) {
+        options.callback();
+      }
+    });
+  };
+
+  self.watchUploadStatus = function(workloadId, showProgress) {
+    $.post(
+      '/metadata/api/optimizer/upload/status',
+      {
+        workloadId: workloadId
+      },
+      data => {
+        if (data.status == 0) {
+          if (showProgress) {
+            $(document).trigger('info', 'Query processing: ' + data.upload_status.status.state);
+          }
+          if (['WAITING', 'IN_PROGRESS'].indexOf(data.upload_status.status.state) != -1) {
+            window.setTimeout(() => {
+              self.watchUploadStatus(workloadId);
+            }, 2000);
+          } else if (showProgress) {
+            $(document).trigger(
+              'warn',
+              data.upload_status.status.statusMsg +
+                (data.upload_status.status.failedQueries > 0
+                  ? '. ' +
+                    data.upload_status.status.failQueryDetails.map(query => {
+                      return query.error;
+                    })
+                  : '')
+            );
+          }
+        } else if (showProgress) {
+          $(document).trigger('error', data.message);
+        }
+      }
+    );
+  };
+
+  self.getSimilarQueries = function() {
+    hueAnalytics.log('notebook', 'get_query_similarity');
+
+    $.post(
+      '/notebook/api/optimizer/statement/similarity',
+      {
+        notebook: komapping.toJSON(notebook.getContext()),
+        snippet: komapping.toJSON(self.getContext()),
+        sourcePlatform: self.type()
+      },
+      data => {
+        if (data.status == 0) {
+          console.log(data.statement_similarity);
+        } else {
+          $(document).trigger('error', data.message);
+        }
+      }
+    );
+  };
+
+  self.autocompleter = new Autocompleter({
+    snippet: self,
+    user: vm.user,
+    optEnabled: false,
+    timeout: vm.autocompleteTimeout,
+    useNewAutocompleter: vm.useNewAutocompleter
+  });
+
+  self.init = function() {
+    if ((self.status() == 'running' || self.status() == 'available') && notebook.isHistory()) {
+      self.checkStatus();
+    } else if (self.status() == 'loading') {
+      self.status('failed');
+      self.progress(0);
+      self.jobs([]);
+    } else if (self.status() == 'ready-execute') {
+      self.execute();
+    }
+  };
+
+  self.onKeydownInVariable = function(context, e) {
+    if ((e.ctrlKey || e.metaKey) && e.which === 13) {
+      // Ctrl-enter
+      self.ace().commands.commands['execute'].exec();
+    } else if ((e.ctrlKey || e.metaKey) && e.which === 83) {
+      // Ctrl-s
+      self.ace().commands.commands['save'].exec();
+      e.preventDefault(); // Prevent browser page save dialog
+    }
+    return true;
+  };
+};
+
+const Session = function(vm, session) {
+  const self = this;
+  komapping.fromJS(session, {}, self);
+
+  self.selectedSessionProperty = ko.observable('');
+
+  self.restarting = ko.observable(false);
+
+  if (!ko.isObservable(self.properties)) {
+    self.properties = ko.observableArray();
+  }
+
+  self.availableNewProperties = ko.computed(() => {
+    const addedIndex = {};
+    $.each(self.properties(), (index, property) => {
+      addedIndex[property.key] = true;
+    });
+    const result = $.grep(vm.availableSessionProperties(), property => {
+      return !addedIndex[property.name];
+    });
+    return result;
+  });
+};
+
+const Notebook = function(vm, notebook) {
+  const 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 : hueUtils.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.type = ko.observable(
+    typeof notebook.type != 'undefined' && notebook.type != null ? notebook.type : 'notebook'
+  );
+  self.initialType = self.type().replace('query-', '');
+  self.coordinatorUuid = ko.observable(
+    typeof notebook.coordinatorUuid != 'undefined' && notebook.coordinatorUuid != null
+      ? notebook.coordinatorUuid
+      : null
+  );
+  self.isHistory = ko.observable(
+    typeof notebook.is_history != 'undefined' && notebook.is_history != null
+      ? notebook.is_history
+      : false
+  );
+  self.isManaged = ko.observable(
+    typeof notebook.isManaged != 'undefined' && notebook.isManaged != null
+      ? notebook.isManaged
+      : false
+  );
+  self.parentSavedQueryUuid = ko.observable(
+    typeof notebook.parentSavedQueryUuid != 'undefined' && notebook.parentSavedQueryUuid != null
+      ? notebook.parentSavedQueryUuid
+      : null
+  ); // History parent
+  self.isSaved = ko.observable(
+    typeof notebook.isSaved != 'undefined' && notebook.isSaved != null ? notebook.isSaved : false
+  );
+  self.canWrite = ko.observable(
+    typeof notebook.can_write != 'undefined' && notebook.can_write != null
+      ? notebook.can_write
+      : true
+  );
+  self.onSuccessUrl = ko.observable(
+    typeof notebook.onSuccessUrl != 'undefined' && notebook.onSuccessUrl != null
+      ? notebook.onSuccessUrl
+      : null
+  );
+  self.pubSubUrl = ko.observable(
+    typeof notebook.pubSubUrl != 'undefined' && notebook.pubSubUrl != null
+      ? notebook.pubSubUrl
+      : null
+  );
+  self.isPresentationModeDefault = ko.observable(
+    typeof notebook.isPresentationModeDefault != 'undefined' &&
+      notebook.isPresentationModeDefault != null
+      ? notebook.isPresentationModeDefault
+      : false
+  );
+  self.isPresentationMode = ko.observable(false);
+  self.isPresentationModeInitialized = ko.observable(false);
+  self.isPresentationMode.subscribe(newValue => {
+    if (!newValue) {
+      self.cancelExecutingAll();
+    }
+    huePubSub.publish('editor.presentation.operate.toggle', newValue); // Problem with headers / row numbers redraw on full screen results
+    vm.togglePresentationMode();
+    if (newValue) {
+      hueAnalytics.convert('editor', 'presentation');
+    }
+  });
+  self.presentationSnippets = ko.observable({});
+  self.isHidingCode = ko.observable(
+    typeof notebook.isHidingCode != 'undefined' && notebook.isHidingCode != null
+      ? notebook.isHidingCode
+      : false
+  );
+
+  self.snippets = ko.observableArray();
+  self.selectedSnippet = ko.observable(vm.editorType()); // Aka selectedSnippetType
+  self.creatingSessionLocks = ko.observableArray();
+  self.sessions = komapping.fromJS(
+    typeof notebook.sessions != 'undefined' && notebook.sessions != null ? notebook.sessions : [],
+    {
+      create: function(value) {
+        return new Session(vm, value.data);
+      }
+    }
+  );
+  self.directoryUuid = ko.observable(
+    typeof notebook.directoryUuid != 'undefined' && notebook.directoryUuid != null
+      ? notebook.directoryUuid
+      : null
+  );
+  self.dependents = komapping.fromJS(
+    typeof notebook.dependents != 'undefined' && notebook.dependents != null
+      ? notebook.dependents
+      : []
+  );
+  self.dependentsCoordinator = ko.computed(() => {
+    return $.grep(self.dependents(), doc => {
+      return doc.type() == 'oozie-coordinator2' && doc.is_managed() == true;
+    });
+  });
+  if (self.dependentsCoordinator().length > 0 && !self.coordinatorUuid()) {
+    self.coordinatorUuid(self.dependentsCoordinator()[0].uuid());
+  }
+  self.history = ko.observableArray(
+    vm.selectedNotebook() &&
+      vm.selectedNotebook().history().length > 0 &&
+      vm.selectedNotebook().history()[0].type == self.type()
+      ? vm.selectedNotebook().history()
+      : []
+  );
+  self.history.subscribe(val => {
+    if (
+      self.id() == null &&
+      val.length == 0 &&
+      self.historyFilter() === '' &&
+      !vm.isNotificationManager()
+    ) {
+      self
+        .snippets()[0]
+        .currentQueryTab(
+          typeof IS_EMBEDDED !== 'undefined' && IS_EMBEDDED ? 'queryHistory' : 'savedQueries'
+        );
+    }
+  });
+  self.historyFilter = ko.observable('');
+  self.historyFilterVisible = ko.observable(false);
+  self.historyFilter.extend({ rateLimit: { method: 'notifyWhenChangesStop', timeout: 900 } });
+  self.historyFilter.subscribe(val => {
+    if (self.historyCurrentPage() != 1) {
+      self.historyCurrentPage(1);
+    } else {
+      self.fetchHistory();
+    }
+  });
+  self.loadingHistory = ko.observable(self.history().length == 0);
+  self.historyInitialHeight = ko.observable(0).extend({ throttle: 1000 });
+  self.forceHistoryInitialHeight = ko.observable(false);
+  self.historyCurrentPage = ko.observable(
+    vm.selectedNotebook() ? vm.selectedNotebook().historyCurrentPage() : 1
+  );
+  self.historyCurrentPage.subscribe(val => {
+    self.fetchHistory();
+  });
+  self.historyTotalPages = ko.observable(
+    vm.selectedNotebook() ? vm.selectedNotebook().historyTotalPages() : 1
+  );
+
+  self.schedulerViewModel = null;
+  self.schedulerViewModelIsLoaded = ko.observable(false);
+  self.schedulerViewerViewModel = ko.observable();
+  self.isBatchable = ko.computed(() => {
+    return (
+      self.snippets().length > 0 &&
+      $.grep(self.snippets(), snippet => {
+        return snippet.isBatchable();
+      }).length == self.snippets().length
+    );
+  });
+
+  self.isExecutingAll = ko.observable(!!notebook.isExecutingAll);
+  self.cancelExecutingAll = function() {
+    const index = self.executingAllIndex();
+    if (self.isExecutingAll() && self.snippets()[index]) {
+      self.snippets()[index].cancel();
+    }
+  };
+  self.executingAllIndex = ko.observable(notebook.executingAllIndex || 0);
+
+  self.retryModalConfirm = null;
+  self.retryModalCancel = null;
+
+  self.avoidClosing = false;
+
+  self.canSave = vm.canSave;
+
+  self.getSession = function(session_type) {
+    let _s = null;
+    $.each(self.sessions(), (index, s) => {
+      if (s.type() == session_type) {
+        _s = s;
+        return false;
+      }
+    });
+    return _s;
+  };
+
+  self.getSnippets = function(type) {
+    return $.grep(self.snippets(), snippet => {
+      return snippet.type() == type;
+    });
+  };
+
+  self.unloaded = ko.observable(false);
+  self.unload = function() {
+    self.unloaded(true);
+    let currentQueries = null;
+    self.snippets().forEach(snippet => {
+      if (snippet.checkStatusTimeout != null) {
+        clearTimeout(snippet.checkStatusTimeout);
+        snippet.checkStatusTimeout = null;
+      }
+      if (currentQueries == null) {
+        currentQueries = snippet.queries();
+      }
+    });
+    return currentQueries;
+  };
+
+  self.restartSession = function(session, callback) {
+    if (session.restarting()) {
+      return;
+    }
+    session.restarting(true);
+    const snippets = self.getSnippets(session.type());
+
+    $.each(snippets, (index, snippet) => {
+      snippet.status('loading');
+    });
+
+    self.closeSession(session, true, () => {
+      self.createSession(
+        session,
+        () => {
+          $.each(snippets, (index, snippet) => {
+            snippet.status('ready');
+          });
+          session.restarting(false);
+          if (callback) {
+            callback();
+          }
+        },
+        () => {
+          session.restarting(false);
+        }
+      );
+    });
+  };
+
+  self.addSession = function(session) {
+    const toRemove = [];
+    $.each(self.sessions(), (index, s) => {
+      if (s.type() == session.type()) {
+        toRemove.push(s);
+      }
+    });
+
+    $.each(toRemove, (index, s) => {
+      self.sessions.remove(s);
+    });
+
+    self.sessions.push(session);
+  };
+
+  self.addSnippet = function(snippet, skipSession) {
+    const _snippet = new Snippet(vm, self, snippet);
+    self.snippets.push(_snippet);
+
+    if (self.getSession(_snippet.type()) == null && typeof skipSession == 'undefined') {
+      window.setTimeout(() => {
+        _snippet.status('loading');
+        self.createSession(new Session(vm, { type: _snippet.type() }));
+      }, 200);
+    }
+
+    _snippet.init();
+    return _snippet;
+  };
+
+  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());
+    }
+
+    let compute = null;
+    $.each(self.getSnippets(session.type()), (index, snippet) => {
+      snippet.status('loading');
+      if (index == 0) {
+        compute = snippet.compute();
+      }
+    });
+
+    const fail = function(message) {
+      $.each(self.getSnippets(session.type()), (index, snippet) => {
+        snippet.status('failed');
+      });
+      $(document).trigger('error', message);
+      if (failCallback) {
+        failCallback();
+      }
+    };
+
+    $.post(
+      '/notebook/api/create_session',
+      {
+        notebook: komapping.toJSON(self.getContext()),
+        session: komapping.toJSON(session), // e.g. {'type': 'pyspark', 'properties': [{'name': driverCores', 'value', '2'}]}
+        cluster: komapping.toJSON(compute ? compute : '')
+      },
+      data => {
+        if (data.status == 0) {
+          komapping.fromJS(data.session, {}, session);
+          if (self.getSession(session.type()) == null) {
+            self.addSession(session);
+          }
+          $.each(self.getSnippets(session.type()), (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(xhr => {
+        if (xhr.status !== 502) {
+          fail(xhr.responseText);
+        }
+      })
+      .always(() => {
+        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.
+    );
+  };
+
+  self.newSnippet = function(type) {
+    if (type) {
+      self.selectedSnippet(type);
+    }
+    const snippet = self.addSnippet({
+      type: self.selectedSnippet(),
+      result: {}
+    });
+
+    window.setTimeout(() => {
+      const lastSnippet = snippet;
+      if (lastSnippet.ace() != null) {
+        lastSnippet.ace().focus();
+      }
+    }, 100);
+
+    hueAnalytics.log('notebook', 'add_snippet/' + (type ? type : self.selectedSnippet()));
+    return snippet;
+  };
+
+  self.newSnippetAbove = function(id) {
+    self.newSnippet();
+    let idx = 0;
+    self.snippets().forEach((snippet, cnt) => {
+      if (snippet.id() == id) {
+        idx = cnt;
+      }
+    });
+    self.snippets(self.snippets().move(self.snippets().length - 1, idx));
+  };
+
+  self.getContext = function() {
+    return {
+      id: self.id,
+      uuid: self.uuid,
+      parentSavedQueryUuid: self.parentSavedQueryUuid,
+      isSaved: self.isSaved,
+      sessions: self.sessions,
+      type: self.type,
+      name: self.name
+    };
+  };
+
+  self.save = function(callback) {
+    hueAnalytics.log('notebook', 'save');
+
+    // Remove the result data from the snippets
+    // Also do it for presentation mode
+    const cp = komapping.toJS(self, NOTEBOOK_MAPPING);
+    $.each(
+      cp.snippets.concat(
+        Object.keys(cp.presentationSnippets).map(key => {
+          return cp.presentationSnippets[key];
+        })
+      ),
+      (index, snippet) => {
+        snippet.result.data.length = 0; // snippet.result.clear() does not work for some reason
+        snippet.result.meta.length = 0;
+        snippet.result.logs = '';
+        snippet.result.fetchedOnce = false;
+        snippet.progress = 0; // Remove progress
+        snippet.jobs.length = 0;
+      }
+    );
+    if (cp.schedulerViewModel) {
+      cp.schedulerViewModel.availableTimezones = [];
+    }
+    const editorMode =
+      vm.editorMode() || (self.isPresentationMode() && vm.editorType() != 'notebook'); // Editor should not convert to Notebook in presentation mode
+
+    $.post(
+      '/notebook/api/notebook/save',
+      {
+        notebook: komapping.toJSON(cp, NOTEBOOK_MAPPING),
+        editorMode: editorMode
+      },
+      data => {
+        if (data.status == 0) {
+          self.id(data.id);
+          self.isSaved(true);
+          const wasHistory = self.isHistory();
+          self.isHistory(false);
+          $(document).trigger('info', data.message);
+          if (editorMode) {
+            if (!data.save_as) {
+              const existingQuery = self
+                .snippets()[0]
+                .queries()
+                .filter(item => {
+                  return item.uuid() === data.uuid;
+                });
+              if (existingQuery.length > 0) {
+                existingQuery[0].name(data.name);
+                existingQuery[0].description(data.description);
+                existingQuery[0].last_modified(data.last_modified);
+              }
+            } else if (self.snippets()[0].queries().length > 0) {
+              // Saved queries tab already loaded
+              self.snippets()[0].queries.unshift(komapping.fromJS(data));
+            }
+
+            if (self.coordinatorUuid() && self.schedulerViewModel) {
+              self.saveScheduler();
+              self.schedulerViewModel.coordinator.refreshParameters();
+            }
+            if (wasHistory || data.save_as) {
+              self.loadScheduler();
+            }
+
+            if (
+              self.snippets()[0].downloadResultViewModel &&
+              self
+                .snippets()[0]
+                .downloadResultViewModel()
+                .saveTarget() === 'dashboard'
+            ) {
+              huePubSub.publish(
+                'open.link',
+                vm.URLS.report +
+                  '&uuid=' +
+                  data.uuid +
+                  '&statement=' +
+                  self.snippets()[0].result.handle().statement_id
+              );
+            } else if (vm.isHue4()) {
+              vm.changeURL(vm.URLS.hue4 + '?editor=' + data.id);
+            } else {
+              vm.changeURL('/notebook/editor' + (vm.isMobile() ? '_m' : '') + '?editor=' + data.id);
+            }
+          } else if (vm.isHue4()) {
+            vm.changeURL(vm.URLS.hue4_notebook + '?notebook=' + data.id);
+          } else {
+            vm.changeURL('/notebook/notebook?notebook=' + data.id);
+          }
+          if (typeof callback == 'function') {
+            callback();
+          }
+        } else {
+          $(document).trigger('error', data.message);
+        }
+      }
+    ).fail((xhr, textStatus, errorThrown) => {
+      if (xhr.status !== 502) {
+        $(document).trigger('error', xhr.responseText);
+      }
+    });
+  };
+
+  self.close = function() {
+    hueAnalytics.log('notebook', 'close');
+    $.post('/notebook/api/notebook/close', {
+      notebook: komapping.toJSON(self, NOTEBOOK_MAPPING),
+      editorMode: vm.editorMode()
+    });
+  };
+
+  self.clearResults = function() {
+    $.each(self.snippets(), (index, snippet) => {
+      snippet.result.clear();
+      snippet.status('ready');
+    });
+  };
+
+  self.executeAll = function() {
+    if (self.isExecutingAll() || self.snippets().length === 0) {
+      return;
+    }
+
+    self.isExecutingAll(true);
+    self.executingAllIndex(0);
+
+    self.snippets()[self.executingAllIndex()].execute();
+  };
+
+  self.saveDefaultUserProperties = function(session) {
+    apiHelper.saveConfiguration({
+      app: session.type(),
+      properties: session.properties,
+      userId: vm.userId
+    });
+  };
+
+  self.closeAndRemoveSession = function(session) {
+    self.closeSession(session, false, () => {
+      self.sessions.remove(session);
+    });
+  };
+
+  self.closeSession = function(session, silent, callback) {
+    $.post(
+      '/notebook/api/close_session',
+      {
+        session: komapping.toJSON(session)
+      },
+      data => {
+        if (!silent && data && data.status != 0 && data.status != -2 && data.message) {
+          $(document).trigger('error', data.message);
+        }
+
+        if (callback) {
+          callback();
+        }
+      }
+    ).fail(xhr => {
+      if (!silent && xhr.status !== 502) {
+        $(document).trigger('error', xhr.responseText);
+      }
+    });
+  };
+
+  self.fetchHistory = function(callback) {
+    const QUERIES_PER_PAGE = 50;
+    self.loadingHistory(true);
+
+    $.get(
+      '/notebook/api/get_history',
+      {
+        doc_type: self.selectedSnippet(),
+        limit: QUERIES_PER_PAGE,
+        page: self.historyCurrentPage(),
+        doc_text: self.historyFilter(),
+        is_notification_manager: vm.isNotificationManager()
+      },
+      data => {
+        const parsedHistory = [];
+        if (data && data.history) {
+          data.history.forEach(nbk => {
+            parsedHistory.push(
+              self._makeHistoryRecord(
+                nbk.absoluteUrl,
+                nbk.data.statement,
+                nbk.data.lastExecuted,
+                nbk.data.status,
+                nbk.name,
+                nbk.uuid
+              )
+            );
+          });
+        }
+        self.history(parsedHistory);
+        self.historyTotalPages(Math.ceil(data.count / QUERIES_PER_PAGE));
+      }
+    ).always(() => {
+      self.loadingHistory(false);
+      if (callback) {
+        callback();
+      }
+    });
+  };
+
+  self.prevHistoryPage = function() {
+    if (self.historyCurrentPage() !== 1) {
+      self.historyCurrentPage(self.historyCurrentPage() - 1);
+    }
+  };
+
+  self.nextHistoryPage = function() {
+    if (self.historyCurrentPage() < self.historyTotalPages()) {
+      self.historyCurrentPage(self.historyCurrentPage() + 1);
+    }
+  };
+
+  self.updateHistoryFailed = false;
+  self.updateHistory = function(statuses, interval) {
+    let items = $.grep(self.history(), item => {
+      return statuses.indexOf(item.status()) != -1;
+    }).slice(0, 25);
+
+    function updateHistoryCall(item) {
+      $.post('/notebook/api/check_status', {
+        notebook: komapping.toJSON({ id: item.uuid() })
+      })
+        .done(data => {
+          const status =
+            data.status == -3 ? 'expired' : data.status == 0 ? data.query_status.status : 'failed';
+          if (status && item.status() != status) {
+            item.status(status);
+          }
+        })
+        .fail(xhr => {
+          items = [];
+          self.updateHistoryFailed = true;
+          console.warn('Lost connectivity to the Hue history refresh backend.');
+        })
+        .always(() => {
+          if (items.length > 0) {
+            window.setTimeout(() => {
+              updateHistoryCall(items.pop());
+            }, 1000);
+          } else if (!self.updateHistoryFailed) {
+            window.setTimeout(() => {
+              self.updateHistory(statuses, interval);
+            }, interval);
+          }
+        });
+    }
+
+    if (items.length > 0) {
+      updateHistoryCall(items.pop());
+    } else if (!self.updateHistoryFailed) {
+      window.setTimeout(() => {
+        self.updateHistory(statuses, interval);
+      }, interval);
+    }
+  };
+
+  self._makeHistoryRecord = function(url, statement, lastExecuted, status, name, uuid) {
+    return komapping.fromJS({
+      url: url,
+      query: statement.substring(0, 1000) + (statement.length > 1000 ? '...' : ''),
+      lastExecuted: lastExecuted,
+      status: status,
+      name: name,
+      uuid: uuid
+    });
+  };
+
+  self.clearHistory = function(type) {
+    hueAnalytics.log('notebook', 'clearHistory');
+    $.post(
+      '/notebook/api/clear_history',
+      {
+        notebook: komapping.toJSON(self.getContext()),
+        doc_type: self.selectedSnippet(),
+        is_notification_manager: vm.isNotificationManager()
+      },
+      data => {
+        self.history.removeAll();
+        if (self.isHistory()) {
+          self.id(null);
+          self.uuid(hueUtils.UUID());
+          if (vm.isHue4()) {
+            vm.changeURL(vm.URLS.hue4 + '?type=' + vm.editorType());
+          } else {
+            vm.changeURL(
+              '/notebook/editor' + (vm.isMobile() ? '_m' : '') + '?type=' + vm.editorType()
+            );
+          }
+        }
+      }
+    ).fail(xhr => {
+      if (xhr.status !== 502) {
+        $(document).trigger('error', xhr.responseText);
+      }
+    });
+    $(document).trigger('hideHistoryModal');
+  };
+
+  self.loadScheduler = function() {
+    if (typeof vm.CoordinatorEditorViewModel !== 'undefined' && self.isBatchable()) {
+      let _action;
+      if (self.coordinatorUuid()) {
+        _action = 'edit';
+      } else {
+        _action = 'new';
+      }
+      hueAnalytics.log('notebook', 'schedule/' + _action);
+
+      const getCoordinator = function() {
+        $.get(
+          '/oozie/editor/coordinator/' + _action + '/',
+          {
+            format: 'json',
+            document: self.uuid(),
+            coordinator: self.coordinatorUuid()
+          },
+          data => {
+            if ($('#schedulerEditor').length > 0) {
+              huePubSub.publish('hue4.process.headers', {
+                response: data.layout,
+                callback: function(r) {
+                  $('#schedulerEditor').html(r);
+
+                  self.schedulerViewModel = new vm.CoordinatorEditorViewModel(
+                    data.coordinator,
+                    data.credentials,
+                    data.workflows,
+                    data.can_edit
+                  );
+
+                  ko.cleanNode($('#schedulerEditor')[0]);
+                  ko.applyBindings(self.schedulerViewModel, $('#schedulerEditor')[0]);
+                  $(document).off('showSubmitPopup');
+                  $(document).on('showSubmitPopup', (event, data) => {
+                    $('.submit-modal-editor').html(data);
+                    $('.submit-modal-editor').modal('show');
+                    $('.submit-modal-editor').on('hidden', () => {
+                      huePubSub.publish('hide.datepicker');
+                    });
+                    const _sel = $('.submit-form .control-group[rel!="popover"]:visible');
+                    if (_sel.length > 0) {
+                      $('.submit-modal-editor .modal-body').height(
+                        $('.submit-modal-editor .modal-body').height() + 60
+                      );
+                    }
+                  });
+
+                  huePubSub.publish('render.jqcron');
+
+                  self.schedulerViewModel.coordinator.properties.cron_advanced.valueHasMutated(); // Update jsCron enabled status
+                  self.schedulerViewModel.coordinator.tracker().markCurrentStateAsClean();
+                  self.schedulerViewModel.isEditing(true);
+
+                  self.schedulerViewModelIsLoaded(true);
+
+                  if (_action == 'new') {
+                    self.schedulerViewModel.coordinator.properties.document(self.uuid()); // Expected for triggering the display
+                  }
+                }
+              });
+            }
+          }
+        ).fail(xhr => {
+          if (xhr.status !== 502) {
+            $(document).trigger('error', xhr.responseText);
+          }
+        });
+      };
+
+      if (!IS_HUE_4) {
+        huePubSub.subscribe(
+          'hue4.process.headers',
+          opts => {
+            opts.callback(opts.response);
+          },
+          vm.huePubSubId
+        );
+      }
+
+      getCoordinator();
+    }
+  };
+
+  self.saveScheduler = function() {
+    if (
+      self.isBatchable() &&
+      (!self.coordinatorUuid() || self.schedulerViewModel.coordinator.isDirty())
+    ) {
+      self.schedulerViewModel.coordinator.isManaged(true);
+      self.schedulerViewModel.coordinator.properties.document(self.uuid());
+      self.schedulerViewModel.save(data => {
+        if (!self.coordinatorUuid()) {
+          self.coordinatorUuid(data.uuid);
+          self.save();
+        }
+      });
+    }
+  };
+
+  self.showSubmitPopup = function() {
+    $.get(
+      '/oozie/editor/coordinator/submit/' + self.coordinatorUuid(),
+      {
+        format: 'json'
+      },
+      data => {
+        $(document).trigger('showSubmitPopup', data);
+      }
+    ).fail((xhr, textStatus, errorThrown) => {
+      if (xhr.status !== 502) {
+        $(document).trigger('error', xhr.responseText);
+      }
+    });
+  };
+
+  self.viewSchedulerId = ko.observable(
+    typeof notebook.viewSchedulerId != 'undefined' && notebook.viewSchedulerId != null
+      ? notebook.viewSchedulerId
+      : ''
+  );
+  self.viewSchedulerId.subscribe(newVal => {
+    self.save();
+  });
+  self.isSchedulerJobRunning = ko.observable();
+  self.loadingScheduler = ko.observable(false);
+
+  // Init
+  if (notebook.snippets) {
+    $.each(notebook.snippets, (index, snippet) => {
+      self.addSnippet(snippet);
+    });
+    if (
+      typeof notebook.presentationSnippets != 'undefined' &&
+      notebook.presentationSnippets != null
+    ) {
+      // Load
+      $.each(notebook.presentationSnippets, (key, snippet) => {
+        snippet.status = 'ready'; // Protect from storm of check_statuses
+        const _snippet = new Snippet(vm, self, snippet);
+        _snippet.init();
+        _snippet.previousChartOptions = vm._getPreviousChartOptions(_snippet);
+        self.presentationSnippets()[key] = _snippet;
+      });
+    }
+    if (vm.editorMode() && self.history().length == 0) {
+      self.fetchHistory(() => {
+        self.updateHistory(['starting', 'running'], 30000);
+        self.updateHistory(['available'], 60000 * 5);
+      });
+    }
+  }
+
+  huePubSub.subscribeOnce(
+    'assist.db.panel.ready',
+    () => {
+      if (self.type().indexOf('query') === 0) {
+        const whenDatabaseAvailable = function(snippet) {
+          huePubSub.publish('assist.set.database', {
+            source: snippet.type(),
+            namespace: snippet.namespace(),
+            name: snippet.database()
+          });
+        };
+
+        const whenNamespaceAvailable = function(snippet) {
+          if (snippet.database()) {
+            whenDatabaseAvailable(snippet);
+          } else {
+            const databaseSub = snippet.database.subscribe(() => {
+              databaseSub.dispose();
+              whenDatabaseAvailable(snippet);
+            });
+          }
+        };
+
+        const whenSnippetAvailable = function(snippet) {
+          if (snippet.namespace()) {
+            whenNamespaceAvailable(snippet);
+          } else {
+            const namespaceSub = snippet.namespace.subscribe(() => {
+              namespaceSub.dispose();
+              whenNamespaceAvailable(snippet);
+            });
+          }
+        };
+
+        if (self.snippets().length === 1) {
+          whenSnippetAvailable(self.snippets()[0]);
+        } else {
+          const snippetsSub = self.snippets.subscribe(snippets => {
+            if (snippets.length === 1) {
+              whenSnippetAvailable(snippets[0]);
+            }
+            snippetsSub.dispose();
+          });
+        }
+      }
+    },
+    vm.huePubSubId
+  );
+
+  huePubSub.publish('assist.is.db.panel.ready');
+};
+
+function EditorViewModel(
+  editor_id,
+  notebooks,
+  options,
+  CoordinatorEditorViewModel,
+  RunningCoordinatorModel
+) {
+  const self = this;
+
+  self.URLS = {
+    editor: '/notebook/editor',
+    editorMobile: '/notebook/editor_m',
+    notebook: '/notebook/notebook',
+    hue4: '/hue/editor',
+    hue4_notebook: '/hue/notebook',
+    report: '/hue/dashboard/new_search?engine=report'
+  };
+
+  self.huePubSubId = options.huePubSubId || 'editor';
+  self.user = options.user;
+  self.userId = options.userId;
+  self.suffix = options.suffix;
+  self.isMobile = ko.observable(options.mobile);
+  self.isHue4 = ko.observable(options.hue4);
+  self.isNotificationManager = ko.observable(options.is_notification_manager || false);
+  self.editorType = ko.observable(options.editor_type);
+  self.editorType.subscribe(newVal => {
+    self.editorMode(newVal != 'notebook');
+    hueUtils.changeURLParameter('type', newVal);
+    if (self.editorMode()) {
+      self.selectedNotebook().fetchHistory(); // Js error if notebook did not have snippets
+    }
+  });
+  self.preEditorTogglingSnippet = ko.observable();
+  self.toggleEditorMode = function() {
+    const _notebook = self.selectedNotebook();
+    const _newSnippets = [];
+
+    if (self.editorType() != 'notebook') {
+      self.editorType('notebook');
+      self.preEditorTogglingSnippet(_notebook.snippets()[0]);
+      const _variables = _notebook.snippets()[0].variables();
+      const _statementKeys = [];
+      // Split statements
+      _notebook.type('notebook');
+      _notebook
+        .snippets()[0]
+        .statementsList()
+        .forEach(sql_statement => {
+          let _snippet;
+          if (sql_statement.hashCode() in _notebook.presentationSnippets()) {
+            _snippet = _notebook.presentationSnippets()[sql_statement.hashCode()]; // Persist result
+            _snippet.variables(_variables);
+          } else {
+            const _title = [];
+            const _statement = [];
+            sql_statement
+              .trim()
+              .split('\n')
+              .forEach(line => {
+                if (line.trim().startsWith('--') && _statement.length === 0) {
+                  _title.push(line.substr(2));
+                } else {
+                  _statement.push(line);
+                }
+              });
+            _snippet = new Snippet(self, _notebook, {
+              type: _notebook.initialType,
+              statement_raw: _statement.join('\n'),
+              result: {},
+              name: _title.join('\n'),
+              variables: komapping.toJS(_variables)
+            });
+            _snippet.variables = _notebook.snippets()[0].variables;
+            _snippet.init();
+            _notebook.presentationSnippets()[sql_statement.hashCode()] = _snippet;
+          }
+          _statementKeys.push(sql_statement.hashCode());
+          _newSnippets.push(_snippet);
+        });
+      $.each(_notebook.presentationSnippets(), key => {
+        // Dead statements
+        if (!key in _statementKeys) {
+          delete _notebook.presentationSnippets()[key];
+        }
+      });
+    } else {
+      self.editorType(_notebook.initialType);
+      // Revert to one statement
+      _newSnippets.push(self.preEditorTogglingSnippet());
+      _notebook.type('query-' + _notebook.initialType);
+    }
+    _notebook.snippets(_newSnippets);
+    _newSnippets.forEach(snippet => {
+      huePubSub.publish('editor.redraw.data', { snippet: snippet });
+    });
+  };
+  self.togglePresentationMode = function() {
+    if (self.selectedNotebook().initialType !== 'notebook') {
+      self.toggleEditorMode();
+    }
+  };
+  self.editorTypeTitle = ko.pureComputed(() => {
+    const foundInterpreter = $.grep(options.languages, interpreter => {
+      return interpreter.type === self.editorType();
+    });
+    return foundInterpreter.length > 0 ? foundInterpreter[0].name : self.editorType();
+  });
+  self.useNewAutocompleter = options.useNewAutocompleter || false;
+  self.autocompleteTimeout = options.autocompleteTimeout;
+  self.selectedNotebook = ko.observable();
+
+  self.combinedContent = ko.observable();
+  self.isPresentationModeEnabled = ko.pureComputed(() => {
+    return (
+      self.selectedNotebook() &&
+      self.selectedNotebook().snippets().length === 1 &&
+      self
+        .selectedNotebook()
+        .snippets()[0]
+        .isSqlDialect()
+    );
+  });
+  self.isResultFullScreenMode = ko.observable(false);
+  self.isPresentationMode = ko.computed(() => {
+    return self.selectedNotebook() && self.selectedNotebook().isPresentationMode();
+  });
+  self.isHidingCode = ko.computed(() => {
+    return self.selectedNotebook() && self.selectedNotebook().isHidingCode();
+  });
+  self.successUrl = ko.observable(options.success_url); // Deprecated
+  self.isOptimizerEnabled = ko.observable(options.is_optimizer_enabled);
+  self.isNavigatorEnabled = ko.observable(options.is_navigator_enabled);
+
+  self.CoordinatorEditorViewModel = CoordinatorEditorViewModel;
+  self.RunningCoordinatorModel = RunningCoordinatorModel;
+
+  self.canSave = ko.computed(() => {
+    // Saved query or history but history coming from a saved query
+    return (
+      self.selectedNotebook() &&
+      self.selectedNotebook().canWrite() &&
+      (self.selectedNotebook().isSaved() ||
+        (self.selectedNotebook().isHistory() && self.selectedNotebook().parentSavedQueryUuid()))
+    );
+  });
+
+  // TODO: Drop the SQL source types from the notebook. They're now set in AssistDbPanel.
+  self.sqlSourceTypes = [];
+  self.availableLanguages = [];
+
+  if (options.languages && options.snippetViewSettings) {
+    $.each(options.languages, (idx, language) => {
+      self.availableLanguages.push({
+        type: language.type,
+        name: language.name,
+        interface: language.interface
+      });
+      const viewSettings = options.snippetViewSettings[language.type];
+      if (viewSettings && viewSettings.sqlDialect) {
+        self.sqlSourceTypes.push({
+          type: language.type,
+          name: language.name
+        });
+      }
+    });
+  }
+
+  const sqlSourceTypes = $.grep(self.sqlSourceTypes, language => {
+    return language.type == self.editorType();
+  });
+  if (sqlSourceTypes.length > 0) {
+    self.activeSqlSourceType = sqlSourceTypes[0].type;
+  } else {
+    self.activeSqlSourceType = null;
+  }
+
+  self.displayCombinedContent = function() {
+    if (!self.selectedNotebook()) {
+      self.combinedContent('');
+    } else {
+      let statements = '';
+      $.each(self.selectedNotebook().snippets(), (index, snippet) => {
+        if (snippet.statement()) {
+          if (statements) {
+            statements += '\n\n';
+          }
+          statements += snippet.statement();
+        }
+      });
+      self.combinedContent(statements);
+    }
+    $('#combinedContentModal' + self.suffix).modal('show');
+  };
+
+  self.isEditing = ko.observable(false);
+  self.isEditing.subscribe(() => {
+    $(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();
+
+  self.removeSnippetConfirmation = ko.observable();
+
+  self.removeSnippet = function(notebook, snippet) {
+    let hasContent = snippet.statement_raw().length > 0;
+    if (!hasContent) {
+      $.each(snippet.properties(), (key, value) => {
+        hasContent = hasContent || (ko.isObservable(value) && value().length > 0);
+      });
+    }
+    if (hasContent) {
+      self.removeSnippetConfirmation({ notebook: notebook, snippet: snippet });
+      $('#removeSnippetModal' + self.suffix).modal('show');
+    } else {
+      notebook.snippets.remove(snippet);
+      window.setTimeout(() => {
+        $(document).trigger('editorSizeChanged');
+      }, 100);
+    }
+  };
+
+  self.assistAvailable = ko.observable(options.assistAvailable);
+
+  self.assistWithoutStorage = ko.observable(false);
+
+  self.isLeftPanelVisible = ko.observable(
+    apiHelper.getFromTotalStorage('assist', 'assist_panel_visible', true)
+  );
+  self.isLeftPanelVisible.subscribe(val => {
+    if (!self.assistWithoutStorage()) {
+      apiHelper.setInTotalStorage('assist', 'assist_panel_visible', val);
+    }
+  });
+
+  self.isRightPanelAvailable = ko.observable(options.assistAvailable && HAS_OPTIMIZER);
+  self.isRightPanelVisible = ko.observable(
+    apiHelper.getFromTotalStorage('assist', 'right_assist_panel_visible', true)
+  );
+  self.isRightPanelVisible.subscribe(val => {
+    if (!self.assistWithoutStorage()) {
+      apiHelper.setInTotalStorage('assist', 'right_assist_panel_visible', val);
+    }
+  });
+
+  const withActiveSnippet = function(callback) {
+    const notebook = self.selectedNotebook();
+    let foundSnippet;
+    if (notebook) {
+      if (notebook.snippets().length === 1) {
+        foundSnippet = notebook.snippets()[0];
+      } else {
+        notebook.snippets().every(snippet => {
+          if (snippet.inFocus()) {
+            foundSnippet = snippet;
+            return false;
+          }
+          return true;
+        });
+      }
+    }
+    if (foundSnippet) {
+      callback(foundSnippet);
+    }
+  };
+
+  huePubSub.subscribe('assist.highlight.risk.suggestions', () => {
+    if (self.isRightPanelAvailable() && !self.isRightPanelVisible()) {
+      self.isRightPanelVisible(true);
+    }
+  });
+
+  self.isContextPanelVisible = ko.observable(false);
+  self.isContextPanelVisible.subscribe(newValue => {
+    huePubSub.publish('context.panel.visible', newValue);
+  });
+
+  huePubSub.subscribe('context.panel.visible.editor', self.isContextPanelVisible);
+
+  huePubSub.subscribe(
+    'get.active.snippet.type',
+    () => {
+      withActiveSnippet(activeSnippet => {
+        huePubSub.publish('set.active.snippet.type', activeSnippet.type());
+      });
+    },
+    self.huePubSubId
+  );
+
+  huePubSub.subscribe(
+    'save.snippet.to.file',
+    () => {
+      withActiveSnippet(activeSnippet => {
+        const data = {
+          path: activeSnippet.statementPath(),
+          contents: activeSnippet.statement()
+        };
+        const options = {
+          successCallback: function(result) {
+            if (result && result.exists) {
+              $(document).trigger('info', result.path + ' saved successfully.');
+            } else {
+              self._ajaxError(result);
+            }
+          }
+        };
+        apiHelper.saveSnippetToFile(data, options);
+      });
+    },
+    self.huePubSubId
+  );
+
+  huePubSub.subscribe(
+    'sql.context.pin',
+    contextData => {
+      withActiveSnippet(activeSnippet => {
+        contextData.tabId = 'context' + activeSnippet.pinnedContextTabs().length;
+        activeSnippet.pinnedContextTabs.push(contextData);
+        activeSnippet.currentQueryTab(contextData.tabId);
+      });
+    },
+    self.huePubSubId
+  );
+
+  huePubSub.subscribe(
+    'assist.database.set',
+    databaseDef => {
+      withActiveSnippet(activeSnippet => {
+        activeSnippet.handleAssistSelection(databaseDef);
+      });
+    },
+    self.huePubSubId
+  );
+
+  huePubSub.subscribe(
+    'assist.database.selected',
+    databaseDef => {
+      withActiveSnippet(activeSnippet => {
+        activeSnippet.handleAssistSelection(databaseDef);
+      });
+    },
+    self.huePubSubId
+  );
+
+  self.availableSnippets = komapping.fromJS(options.languages);
+
+  self.editorMode = ko.observable(options.mode == 'editor');
+
+  self.getSnippetViewSettings = function(snippetType) {
+    if (options.snippetViewSettings[snippetType]) {
+      return options.snippetViewSettings[snippetType];
+    }
+    return options.snippetViewSettings.default;
+  };
+
+  self.availableSessionProperties = ko.computed(() => {
+    // Only Spark
+    return ko.utils.arrayFilter(options.session_properties, item => {
+      return item.name != ''; // Could filter out the ones already selected + yarn only or not
+    });
+  });
+  self.getSessionProperties = function(name) {
+    let _prop = null;
+    $.each(options.session_properties, (index, prop) => {
+      if (prop.name == name) {
+        _prop = prop;
+        return;
+      }
+    });
+    return _prop;
+  };
+
+  self.getSnippetName = function(snippetType) {
+    const availableSnippets = self.availableSnippets();
+    for (let i = 0; i < availableSnippets.length; i++) {
+      if (availableSnippets[i].type() === snippetType) {
+        return availableSnippets[i].name();
+      }
+    }
+    return '';
+  };
+
+  self.changeURL = function(url) {
+    if (!self.isNotificationManager()) {
+      hueUtils.changeURL(url);
+    }
+  };
+
+  self.init = function() {
+    if (editor_id) {
+      self.openNotebook(editor_id);
+    } else if (window.location.getParameter('editor') !== '') {
+      self.openNotebook(window.location.getParameter('editor'));
+    } else if (notebooks.length > 0) {
+      self.loadNotebook(notebooks[0]); // Old way of loading json for /browse
+    } else if (window.location.getParameter('type') !== '') {
+      self.newNotebook(window.location.getParameter('type'));
+    } else {
+      self.newNotebook();
+    }
+  };
+
+  self.loadNotebook = function(notebookRaw, queryTab) {
+    let currentQueries;
+    if (self.selectedNotebook() != null) {
+      currentQueries = self.selectedNotebook().unload();
+    }
+
+    const notebook = new Notebook(self, notebookRaw);
+
+    if (notebook.snippets().length > 0) {
+      huePubSub.publish('detach.scrolls', notebook.snippets()[0]);
+      notebook.selectedSnippet(notebook.snippets()[notebook.snippets().length - 1].type());
+      if (currentQueries != null) {
+        notebook.snippets()[0].queries(currentQueries);
+      }
+      notebook.snippets().forEach(snippet => {
+        snippet.aceAutoExpand = false;
+        snippet.statement_raw.valueHasMutated();
+        if (
+          snippet.result.handle().statements_count > 1 &&
+          snippet.result.handle().start != null &&
+          snippet.result.handle().end != null
+        ) {
+          const aceLineOffset = snippet.result.handle().aceLineOffset || 0;
+          snippet.result.statement_range({
+            start: {
+              row: snippet.result.handle().start.row + aceLineOffset,
+              column: snippet.result.handle().start.column
+            },
+            end: {
+              row: snippet.result.handle().end.row + aceLineOffset,
+              column: snippet.result.handle().end.column
+            }
+          });
+          snippet.result.statement_range.valueHasMutated();
+        }
+
+        snippet.previousChartOptions = self._getPreviousChartOptions(snippet);
+      });
+
+      if (notebook.snippets()[0].result.data().length > 0) {
+        $(document).trigger('redrawResults');
+      } else if (queryTab) {
+        notebook.snippets()[0].currentQueryTab(queryTab);
+      }
+
+      if (notebook.isSaved()) {
+        notebook.snippets()[0].currentQueryTab('savedQueries');
+        if (notebook.snippets()[0].queries().length === 0) {
+          notebook.snippets()[0].fetchQueries(); // Subscribe not updating yet
+        }
+      }
+    }
+
+    self.selectedNotebook(notebook);
+    huePubSub.publish('check.job.browser');
+    huePubSub.publish('recalculate.name.description.width');
+  };
+
+  self._getPreviousChartOptions = function(snippet) {
+    return {
+      chartLimit:
+        typeof snippet.chartLimit() !== 'undefined'
+          ? snippet.chartLimit()
+          : snippet.previousChartOptions.chartLimit,
+      chartX:
+        typeof snippet.chartX() !== 'undefined'
+          ? snippet.chartX()
+          : snippet.previousChartOptions.chartX,
+      chartXPivot:
+        typeof snippet.chartXPivot() !== 'undefined'
+          ? snippet.chartXPivot()
+          : snippet.previousChartOptions.chartXPivot,
+      chartYSingle:
+        typeof snippet.chartYSingle() !== 'undefined'
+          ? snippet.chartYSingle()
+          : snippet.previousChartOptions.chartYSingle,
+      chartMapType:
+        typeof snippet.chartMapType() !== 'undefined'
+          ? snippet.chartMapType()
+          : snippet.previousChartOptions.chartMapType,
+      chartMapLabel:
+        typeof snippet.chartMapLabel() !== 'undefined'
+          ? snippet.chartMapLabel()
+          : snippet.previousChartOptions.chartMapLabel,
+      chartMapHeat:
+        typeof snippet.chartMapHeat() !== 'undefined'
+          ? snippet.chartMapHeat()
+          : snippet.previousChartOptions.chartMapHeat,
+      chartYMulti:
+        typeof snippet.chartYMulti() !== 'undefined'
+          ? snippet.chartYMulti()
+          : snippet.previousChartOptions.chartYMulti,
+      chartScope:
+        typeof snippet.chartScope() !== 'undefined'
+          ? snippet.chartScope()
+          : snippet.previousChartOptions.chartScope,
+      chartTimelineType:
+        typeof snippet.chartTimelineType() !== 'undefined'
+          ? snippet.chartTimelineType()
+          : snippet.previousChartOptions.chartTimelineType,
+      chartSorting:
+        typeof snippet.chartSorting() !== 'undefined'
+          ? snippet.chartSorting()
+          : snippet.previousChartOptions.chartSorting,
+      chartScatterGroup:
+        typeof snippet.chartScatterGroup() !== 'undefined'
+          ? snippet.chartScatterGroup()
+          : snippet.previousChartOptions.chartScatterGroup,
+      chartScatterSize:
+        typeof snippet.chartScatterSize() !== 'undefined'
+          ? snippet.chartScatterSize()
+          : snippet.previousChartOptions.chartScatterSize
+    };
+  };
+
+  self.openNotebook = function(uuid, queryTab, skipUrlChange, callback) {
+    const deferredOpen = new $.Deferred();
+    $.get(
+      '/desktop/api2/doc/',
+      {
+        uuid: uuid,
+        data: true,
+        dependencies: true
+      },
+      data => {
+        if (data.status == 0) {
+          data.data.dependents = data.dependents;
+          data.data.can_write = data.user_perms.can_write;
+          const notebook = data.data;
+          self.loadNotebook(notebook, queryTab);
+          if (typeof skipUrlChange === 'undefined' && !self.isNotificationManager()) {
+            if (self.editorMode()) {
+              self.editorType(data.document.type.substring('query-'.length));
+              huePubSub.publish('active.snippet.type.changed', self.editorType());
+              if (self.isHue4()) {
+                self.changeURL(
+                  self.URLS.hue4 + '?editor=' + data.document.id + '&type=' + self.editorType()
+                );
+              } else {
+                self.changeURL(
+                  (self.isMobile() ? self.URLS.editorMobile : self.URLS.editor) +
+                    '?editor=' +
+                    data.document.id +
+                    '&type=' +
+                    self.editorType()
+                );
+              }
+            } else if (self.isHue4()) {
+              self.changeURL(self.URLS.hue4_notebook + '?notebook=' + data.document.id);
+            } else {
+              self.changeURL(self.URLS.notebook + '?notebook=' + data.document.id);
+            }
+          }
+          if (typeof callback !== 'undefined') {
+            callback();
+          }
+          deferredOpen.resolve();
+        } else {
+          $(document).trigger('error', data.message);
+          deferredOpen.reject();
+          self.newNotebook();
+        }
+      }
+    );
+    return deferredOpen.promise();
+  };
+
+  self.newNotebook = function(editorType, callback, queryTab) {
+    huePubSub.publish('active.snippet.type.changed', editorType);
+    $.post(
+      '/notebook/api/create_notebook',
+      {
+        type: editorType || options.editor_type,
+        directory_uuid: window.location.getParameter('directory_uuid')
+      },
+      data => {
+        self.loadNotebook(data.notebook);
+        if (self.editorMode() && !self.isNotificationManager()) {
+          const snippet = self.selectedNotebook().newSnippet(self.editorType());
+          if (
+            queryTab &&
+            ['queryHistory', 'savedQueries', 'queryBuilderTab'].indexOf(queryTab) > -1
+          ) {
+            snippet.currentQueryTab(queryTab);
+          }
+          huePubSub.publish('detach.scrolls', self.selectedNotebook().snippets()[0]);
+          if (window.location.getParameter('type') === '') {
+            hueUtils.changeURLParameter('type', self.editorType());
+          }
+          huePubSub.publish('active.snippet.type.changed', editorType);
+        }
+
+        if (typeof callback !== 'undefined' && callback !== null) {
+          callback();
+        }
+      }
+    );
+  };
+
+  self.saveNotebook = function() {
+    self.selectedNotebook().save();
+  };
+
+  self.saveAsNotebook = function() {
+    self.selectedNotebook().id(null);
+    self.selectedNotebook().uuid(hueUtils.UUID());
+    self.selectedNotebook().parentSavedQueryUuid(null);
+    self.selectedNotebook().save(() => {
+      huePubSub.publish('assist.document.refresh');
+    });
+  };
+
+  self.showContextPopover = function(field, event) {
+    const $source = $(
+      event.target && event.target.nodeName !== 'A' ? event.target.parentElement : event.target
+    );
+    const offset = $source.offset();
+    huePubSub.publish('context.popover.show', {
+      data: {
+        type: 'catalogEntry',
+        catalogEntry: field.catalogEntry
+      },
+      onSampleClick: field.value,
+      showInAssistEnabled: true,
+      sourceType: self.editorType(),
+      orientation: 'bottom',
+      defaultDatabase: 'default',
+      pinEnabled: false,
+      source: {
+        element: event.target,
+        left: offset.left,
+        top: offset.top - 3,
+        right: offset.left + $source.width() + 1,
+        bottom: offset.top + $source.height() - 3
+      }
+    });
+  };
+}
+
+export default EditorViewModel;

文件差异内容过多而无法显示
+ 21 - 0
desktop/core/src/desktop/js/ext/bootstrap-datepicker.min.js


+ 102 - 0
desktop/core/src/desktop/js/ext/jquery.hotkeys.js

@@ -0,0 +1,102 @@
+/*
+ * jQuery Hotkeys Plugin
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
+ * Based upon the plugin by Tzury Bar Yochay:
+ * http://github.com/tzuryby/hotkeys
+ *
+ * Original idea by:
+ * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
+*/
+
+import jQuery from 'jquery';
+
+(function(jQuery){
+	
+	jQuery.hotkeys = {
+		version: "0.8",
+
+		specialKeys: {
+			8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
+			20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
+			37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 
+			96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
+			104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", 
+			112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", 
+			120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta"
+		},
+	
+		shiftNums: {
+			"`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", 
+			"8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", 
+			".": ">",  "/": "?",  "\\": "|"
+		}
+	};
+
+	function keyHandler( handleObj ) {
+		// Only care when a possible input has been specified
+		if ( typeof handleObj.data !== "string" ) {
+			return;
+		}
+		
+		var origHandler = handleObj.handler,
+			keys = handleObj.data.toLowerCase().split(" "),
+			textAcceptingInputTypes = ["text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", "datetime-local", "search", "color"];
+	
+		handleObj.handler = function( event ) {
+			// Don't fire in text-accepting inputs that we didn't directly bind to
+			if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
+				jQuery.inArray(event.target.type, textAcceptingInputTypes) > -1 ) ) {
+				return;
+			}
+			
+			// Keypress represents characters, not special keys
+			var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ],
+				character = String.fromCharCode( event.which ).toLowerCase(),
+				key, modif = "", possible = {};
+
+			// check combinations (alt|ctrl|shift+anything)
+			if ( event.altKey && special !== "alt" ) {
+				modif += "alt+";
+			}
+
+			if ( event.ctrlKey && special !== "ctrl" ) {
+				modif += "ctrl+";
+			}
+			
+			// TODO: Need to make sure this works consistently across platforms
+			if ( event.metaKey && !event.ctrlKey && special !== "meta" ) {
+				modif += "meta+";
+			}
+
+			if ( event.shiftKey && special !== "shift" ) {
+				modif += "shift+";
+			}
+
+			if ( special ) {
+				possible[ modif + special ] = true;
+
+			} else {
+				possible[ modif + character ] = true;
+				possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true;
+
+				// "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
+				if ( modif === "shift+" ) {
+					possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
+				}
+			}
+
+			for ( var i = 0, l = keys.length; i < l; i++ ) {
+				if ( possible[ keys[i] ] ) {
+					return origHandler.apply( this, arguments );
+				}
+			}
+		};
+	}
+
+	jQuery.each([ "keydown", "keyup", "keypress" ], function() {
+		jQuery.event.special[ this ] = { add: keyHandler };
+	});
+
+})( jQuery );

+ 5 - 1
desktop/core/src/desktop/js/hue.js

@@ -14,7 +14,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import 'jquery/jquery.all';
+import 'jquery/jquery.common';
 import 'ext/bootstrap.2.3.2.min';
 import _ from 'lodash';
 import Dropzone from 'dropzone';
@@ -47,6 +47,9 @@ import sqlWorkerHandler from 'sql/sqlWorkerHandler';
 
 import 'assist/assistViewModel';
 
+// TODO: Move to notebook.js
+import EditorViewModel from 'apps/notebook/notebook.ko';
+
 // TODO: Migrate away
 window._ = _;
 window.AceLocationHandler = AceLocationHandler;
@@ -55,6 +58,7 @@ window.CancellablePromise = CancellablePromise;
 window.contextCatalog = contextCatalog;
 window.dataCatalog = dataCatalog;
 window.Dropzone = Dropzone;
+window.EditorViewModel = EditorViewModel;
 window.filesize = filesize;
 window.hueUtils = hueUtils;
 window.hueAnalytics = hueAnalytics;

+ 0 - 0
desktop/core/src/desktop/js/jquery/jquery.all.js → desktop/core/src/desktop/js/jquery/jquery.common.js


+ 366 - 0
desktop/core/src/desktop/js/jquery/plugins/jquery.hdfstree.js

@@ -0,0 +1,366 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import jQuery from 'jquery';
+
+import hueUtils from '../../utils/hueUtils';
+
+// TODO: don't create multiple jquery instances from chunks
+const $ = window.$ || jQuery;
+
+/*
+ * jHue HDFS tree plugin
+ * shows a tree HDFS picker, if initialPath is set it pre-populates the path
+ * if home is specified a home link will appear
+ * use attached to an element, ie:
+ * $("#el").jHueHdfsTree({
+ *    initialPath: "/user",
+ *    home: "/user/hue"
+ *    onPathChange: function (path) {
+ *      console.log(path);
+ *    }
+ * });
+ */
+
+const pluginName = 'jHueHdfsTree',
+  defaults = {
+    home: '',
+    initialPath: '/',
+    isS3: false,
+    withTopPadding: true,
+    onPathChange: function() {},
+    createFolder: true,
+    labels: {
+      CREATE_FOLDER: 'Create folder',
+      FOLDER_NAME: 'Folder name',
+      CANCEL: 'Cancel',
+      HOME: 'Home'
+    }
+  };
+
+function Plugin(element, options) {
+  this.element = element;
+  this.options = $.extend({}, defaults, options);
+  this.options.labels = $.extend(
+    {},
+    defaults.labels,
+    HUE_I18n.jHueHdfsTree,
+    options ? options.labels : {}
+  );
+  this._defaults = defaults;
+  this._name = pluginName;
+  this.lastPath = '';
+  this.previousPath = '';
+  this.init();
+}
+
+Plugin.prototype.init = function(optionalPath) {
+  const _this = this;
+
+  if (typeof optionalPath != 'undefined') {
+    _this.options.initialPath = optionalPath;
+  }
+  const _el = $(_this.element);
+  _el.empty();
+  _el.addClass('jHueHdfsTree');
+
+  let _homeLink;
+  if (_this.options.home != '') {
+    _homeLink = $('<a>')
+      .html('<i class="fa fa-home"></i> ' + _this.options.labels.HOME)
+      .click(() => {
+        const _path = _this.options.home;
+        _this.options.onPathChange(_path);
+        _this.lastPath = _path;
+        _tree.find('a').removeClass('selected');
+        const _paths = [];
+        const _re = /\//g;
+        let match;
+        while ((match = _re.exec(_path)) != null) {
+          _paths.push(_path.substr(0, match.index));
+        }
+        _paths.push(_path);
+
+        showHdfsLeaf({
+          paths: _paths,
+          scroll: true
+        });
+      });
+    _homeLink.css({
+      cursor: 'pointer',
+      position: 'fixed',
+      'padding-bottom': '4px',
+      'font-size': '16px',
+      'border-bottom': '1px solid #FFF',
+      'background-color': '#FFF',
+      width: 560 - hueUtils.scrollbarWidth() + 'px'
+    });
+  }
+
+  const _tree = $('<ul>')
+    .addClass('content unstyled')
+    .html('<li><a class="pointer"><i class="fa fa-folder-open-o"></i> /</a></li>');
+  if (_this.options.withTopPadding) {
+    _tree.css('padding-top', '30px');
+  }
+
+  if (_this.options.home != '') {
+    _homeLink.appendTo(_el);
+    _el.parent().on('scroll', () => {
+      if (_el.parent().scrollTop() > 0) {
+        _homeLink.css({
+          'border-bottom': '1px solid #EEE'
+        });
+      } else {
+        _homeLink.css({
+          'border-bottom': '1px solid #FFF'
+        });
+      }
+    });
+  }
+  _tree.appendTo(_el);
+
+  _tree.find('a').on('click', () => {
+    _this.options.onPathChange('/');
+    _tree.find('a').removeClass('selected');
+    _tree.find('a:eq(0)').addClass('selected');
+  });
+
+  const _root = $('<ul>')
+    .addClass('content unstyled')
+    .attr('data-path', '__JHUEHDFSTREE__ROOT__')
+    .attr('data-loaded', 'true');
+  _root.appendTo(_tree.find('li'));
+
+  const BASE_PATH = '/filebrowser/view=';
+  let _currentFiles = [];
+
+  function escapeSingleQuote(path) {
+    return path.replace(/\'/gi, "\\'");
+  }
+
+  function removeLeadingSlash(path) {
+    if (path.indexOf('/') == 0) {
+      return path.substr(1);
+    }
+    return path;
+  }
+
+  function showHdfsLeaf(options) {
+    let autocompleteUrl = BASE_PATH,
+      currentPath = '';
+
+    if (options.paths != null && options.paths.length > 0) {
+      const shiftedPath = options.paths.shift();
+      if (_this.options.isS3) {
+        currentPath = shiftedPath;
+      } else {
+        currentPath = shiftedPath != '' ? shiftedPath : '/';
+      }
+    } else {
+      currentPath = options.leaf != null ? options.leaf : '/';
+    }
+    autocompleteUrl += currentPath;
+    $.getJSON(autocompleteUrl + '?pagesize=1000&format=json', data => {
+      _currentFiles = [];
+      if (data.error == null) {
+        const filteredCurrentPath = _this.options.isS3 ? currentPath.substr(5) : currentPath;
+        const _dataPathForCurrent =
+          filteredCurrentPath != ''
+            ? removeLeadingSlash(filteredCurrentPath)
+            : '__JHUEHDFSTREE__ROOT__';
+        _el
+          .find("[data-path='" + escapeSingleQuote(_dataPathForCurrent) + "']")
+          .attr('data-loaded', true);
+        _el
+          .find("[data-path='" + escapeSingleQuote(_dataPathForCurrent) + "']")
+          .siblings('a')
+          .find('.fa-folder-o')
+          .removeClass('fa-folder-o')
+          .addClass('fa-folder-open-o');
+        _tree.find('a').removeClass('selected');
+        _el
+          .find("[data-path='" + escapeSingleQuote(_dataPathForCurrent) + "']")
+          .siblings('a')
+          .addClass('selected');
+
+        if (options.scroll) {
+          _el.parent().scrollTop(
+            _el
+              .find("[data-path='" + escapeSingleQuote(_dataPathForCurrent) + "']")
+              .siblings('a')
+              .position().top +
+              _el.parent().scrollTop() -
+              30
+          );
+        }
+        $(data.files).each((cnt, item) => {
+          if (item.name != '.' && item.name != '..' && item.type == 'dir') {
+            const _path = item.path;
+            const filteredPath = _this.options.isS3 ? _path.substr(5) : _path;
+            const _escapedPath = escapeSingleQuote(filteredPath);
+            if (_el.find("[data-path='" + removeLeadingSlash(_escapedPath) + "']").length == 0) {
+              const _li = $('<li>').html(
+                '<a class="pointer"><i class="fa fa-folder-o"></i> ' +
+                  item.name +
+                  '</a><ul class="content unstyled" data-path="' +
+                  removeLeadingSlash(_escapedPath) +
+                  '" data-loaded="false"></ul>'
+              );
+              let _destination = filteredPath.substr(0, filteredPath.lastIndexOf('/'));
+              if (_destination == '') {
+                _destination = '__JHUEHDFSTREE__ROOT__';
+              }
+              _destination = removeLeadingSlash(_destination);
+              _li.appendTo(_el.find("[data-path='" + escapeSingleQuote(_destination) + "']"));
+              _li.find('a').on('click', () => {
+                _this.options.onPathChange(_path);
+                _this.lastPath = _path;
+                _tree.find('a').removeClass('selected');
+                _li.find('a:eq(0)').addClass('selected');
+                if (_li.find('.content').attr('data-loaded') == 'false') {
+                  showHdfsLeaf({
+                    leaf: _path,
+                    scroll: false
+                  });
+                } else if (_li.find('.content').is(':visible')) {
+                  _li.find('.content').hide();
+                } else {
+                  _li.find('.content').show();
+                }
+              });
+            }
+          }
+        });
+        if (_this.options.createFolder) {
+          const filteredCurrentPath = _this.options.isS3 ? currentPath.substr(5) : currentPath;
+          const _createFolderLi = $('<li>').html(
+            '<a class="pointer"><i class="fa fa-plus-square-o"></i> ' +
+              _this.options.labels.CREATE_FOLDER +
+              '</a>'
+          );
+          _createFolderLi.appendTo(
+            _el.find(
+              "[data-path='" + removeLeadingSlash(escapeSingleQuote(filteredCurrentPath)) + "']"
+            )
+          );
+
+          const _createFolderDetails = $('<form>')
+            .css('margin-top', '10px')
+            .addClass('form-inline');
+          _createFolderDetails.hide();
+          const _folderName = $('<input>')
+            .attr('type', 'text')
+            .attr('placeholder', _this.options.labels.FOLDER_NAME)
+            .appendTo(_createFolderDetails);
+          $('<span> </span>').appendTo(_createFolderDetails);
+          const _folderBtn = $('<input>')
+            .attr('type', 'button')
+            .attr('value', _this.options.labels.CREATE_FOLDER)
+            .addClass('btn primary')
+            .appendTo(_createFolderDetails);
+          $('<span> </span>').appendTo(_createFolderDetails);
+          const _folderCancel = $('<input>')
+            .attr('type', 'button')
+            .attr('value', _this.options.labels.CANCEL)
+            .addClass('btn')
+            .appendTo(_createFolderDetails);
+          _folderCancel.click(() => {
+            _createFolderDetails.slideUp();
+          });
+          _folderBtn.click(() => {
+            $.ajax({
+              type: 'POST',
+              url: '/filebrowser/mkdir',
+              data: {
+                name: _folderName.val(),
+                path: currentPath
+              },
+              beforeSend: function(xhr) {
+                xhr.setRequestHeader('X-Requested-With', 'Hue'); // need to override the default one because otherwise Django returns HTTP 500
+              },
+              success: function(xhr, status) {
+                if (status == 'success') {
+                  _createFolderDetails.slideUp();
+                  const _newFolder = currentPath + '/' + _folderName.val();
+                  _this.init(_newFolder);
+                  _this.options.onPathChange(_newFolder);
+                }
+              }
+            });
+          });
+          _createFolderDetails.appendTo(
+            _el.find(
+              "[data-path='" + removeLeadingSlash(escapeSingleQuote(filteredCurrentPath)) + "']"
+            )
+          );
+
+          _createFolderLi.find('a').on('click', () => {
+            _createFolderDetails.slideDown();
+          });
+        }
+        if (options.paths != null && options.paths.length > 0) {
+          showHdfsLeaf({
+            paths: options.paths,
+            scroll: options.scroll
+          });
+        }
+      } else {
+        $.jHueNotify.error(data.error);
+      }
+    });
+  }
+
+  Plugin.prototype.showHdfsLeaf = showHdfsLeaf;
+
+  const _paths = [];
+  if (_this.options.initialPath != '/') {
+    const _re = /\//g;
+    let match;
+    while ((match = _re.exec(_this.options.initialPath)) != null) {
+      _paths.push(_this.options.initialPath.substr(0, match.index));
+    }
+    _paths.push(_this.options.initialPath);
+  }
+
+  if (_this.options.isS3) {
+    _paths.shift();
+    _paths[0] = 's3a://';
+  }
+
+  showHdfsLeaf({
+    paths: _paths
+  });
+};
+
+Plugin.prototype.setOptions = function(options) {
+  this.options = $.extend({}, defaults, options);
+};
+
+$.fn[pluginName] = function(options) {
+  return this.each(function() {
+    if (!$.data(this, 'plugin_' + pluginName)) {
+      $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
+    }
+  });
+};
+
+$[pluginName] = function(options) {
+  if (typeof console != 'undefined') {
+    console.warn('$(elem).jHueHdfsTree() is a preferred call method.');
+  }
+  $(options.element).jHueHdfsTree(options);
+};

+ 1 - 1
desktop/core/src/desktop/js/login.js

@@ -22,4 +22,4 @@ import huePubSub from 'utils/huePubSub';
 
 window.Dropzone = Dropzone;
 window.hueAnalytics = hueAnalytics;
-window.huePubSub = huePubSub;
+window.huePubSub = huePubSub;

+ 209 - 204
desktop/core/src/desktop/js/utils/json.bigDataParse.js

@@ -28,246 +28,251 @@ JSON.bigdataParse = (function() {
   // We are defining the function inside of another function to avoid creating
   // global variables.
 
-  let at; // The index of the current character
-  let ch; // The current character
-  const escapee = {
-    '"': '"',
-    '\\': '\\',
-    '/': '/',
-    b: '\b',
-    f: '\f',
-    n: '\n',
-    r: '\r',
-    t: '\t'
-  };
-  let text;
-  const error = function(m) {
-    // Call error when something is wrong.
+  let at, // The index of the current character
+    ch, // The current character
+    escapee = {
+      '"': '"',
+      '\\': '\\',
+      '/': '/',
+      b: '\b',
+      f: '\f',
+      n: '\n',
+      r: '\r',
+      t: '\t'
+    },
+    text,
+    error = function(m) {
+      // Call error when something is wrong.
 
-    throw {
-      name: 'SyntaxError',
-      message: m,
-      at: at,
-      text: text
-    };
-  };
-  const next = function(c) {
-    // If a c parameter is provided, verify that it matches the current character.
+      throw {
+        name: 'SyntaxError',
+        message: m,
+        at: at,
+        text: text
+      };
+    },
+    next = function(c) {
+      // If a c parameter is provided, verify that it matches the current character.
 
-    if (c && c !== ch) {
-      error("Expected '" + c + "' instead of '" + ch + "'");
-    }
+      if (c && c !== ch) {
+        error("Expected '" + c + "' instead of '" + ch + "'");
+      }
 
-    // Get the next character. When there are no more characters,
-    // return the empty string.
-    try {
-      ch = text.charAt(at);
-      at += 1;
-      return ch;
-    } catch (e) {
-      return '';
-    }
-  };
-  const number = function() {
-    // Parse a number value.
+      // Get the next character. When there are no more characters,
+      // return the empty string.
+      try {
+        ch = text.charAt(at);
+        at += 1;
+        return ch;
+      } catch (e) {
+        return '';
+      }
+    },
+    number = function() {
+      // Parse a number value.
 
-    let string = '';
+      let number,
+        string = '';
 
-    if (ch === '-') {
-      string = '-';
-      next('-');
-    }
-    while (ch >= '0' && ch <= '9') {
-      string += ch;
-      next();
-    }
-    if (ch === '.') {
-      string += '.';
-      while (next() && ch >= '0' && ch <= '9') {
-        string += ch;
+      if (ch === '-') {
+        string = '-';
+        next('-');
       }
-    }
-    if (ch === 'e' || ch === 'E') {
-      string += ch;
-      next();
-      if (ch === '-' || ch === '+') {
+      while (ch >= '0' && ch <= '9') {
         string += ch;
         next();
       }
-      while (ch >= '0' && ch <= '9') {
+      if (ch === '.') {
+        string += '.';
+        while (next() && ch >= '0' && ch <= '9') {
+          string += ch;
+        }
+      }
+      if (ch === 'e' || ch === 'E') {
         string += ch;
         next();
+        if (ch === '-' || ch === '+') {
+          string += ch;
+          next();
+        }
+        while (ch >= '0' && ch <= '9') {
+          string += ch;
+          next();
+        }
       }
-    }
-    const numberInt = +string;
-    if (!isFinite(number)) {
-      error('Bad number');
-    } else {
-      //if (number > 9007199254740992 || number < -9007199254740992)
-      // Bignumber has stricter check: everything with length > 15 digits disallowed
-      if (string.length > 15) {
-        return string;
+      number = +string;
+      if (!isFinite(number)) {
+        error('Bad number');
+      } else {
+        //if (number > 9007199254740992 || number < -9007199254740992)
+        // Bignumber has stricter check: everything with length > 15 digits disallowed
+        if (string.length > 15) {
+          return string;
+        }
+        return number;
       }
-      return numberInt;
-    }
-  };
-  const string = function() {
-    // Parse a string value.
+    },
+    string = function() {
+      // Parse a string value.
 
-    let hex,
-      i,
-      string = '',
-      uffff;
+      let hex,
+        i,
+        string = '',
+        uffff;
 
-    // When parsing for string values, we must look for " and \ characters.
+      // When parsing for string values, we must look for " and \ characters.
 
-    if (ch === '"') {
-      while (next()) {
-        if (ch === '"') {
-          next();
-          return string;
-        }
-        if (ch === '\\') {
-          next();
-          if (ch === 'u') {
-            uffff = 0;
-            for (i = 0; i < 4; i += 1) {
-              hex = parseInt(next(), 16);
-              if (!isFinite(hex)) {
-                break;
+      if (ch === '"') {
+        while (next()) {
+          if (ch === '"') {
+            next();
+            return string;
+          }
+          if (ch === '\\') {
+            next();
+            if (ch === 'u') {
+              uffff = 0;
+              for (i = 0; i < 4; i += 1) {
+                hex = parseInt(next(), 16);
+                if (!isFinite(hex)) {
+                  break;
+                }
+                uffff = uffff * 16 + hex;
               }
-              uffff = uffff * 16 + hex;
+              string += String.fromCharCode(uffff);
+            } else if (typeof escapee[ch] === 'string') {
+              string += escapee[ch];
+            } else {
+              break;
             }
-            string += String.fromCharCode(uffff);
-          } else if (typeof escapee[ch] === 'string') {
-            string += escapee[ch];
           } else {
-            break;
+            string += ch;
           }
-        } else {
-          string += ch;
         }
       }
-    }
-    error('Bad string');
-  };
-  const white = function() {
-    // Skip whitespace.
-
-    while (ch && ch <= ' ') {
-      next();
-    }
-  };
-  const value = function() {
-    // Parse a JSON value. It could be an object, an array, a string, a number,
-    // or a word.
+      error('Bad string');
+    },
+    white = function() {
+      // Skip whitespace.
 
-    white();
-    switch (ch) {
-      case '{':
-        return object();
-      case '[':
-        return array();
-      case '"':
-        return string();
-      case '-':
-        return number();
-      default:
-        return ch >= '0' && ch <= '9' ? number() : word();
-    }
-  };
-  const word = function() {
-    // true, false, or null.
+      while (ch && ch <= ' ') {
+        next();
+      }
+    },
+    word = function() {
+      // true, false, or null.
 
-    switch (ch) {
-      case 't':
-        next('t');
-        next('r');
-        next('u');
-        next('e');
-        return true;
-      case 'f':
-        next('f');
-        next('a');
-        next('l');
-        next('s');
-        next('e');
-        return false;
-      case 'n':
-        next('n');
-        next('u');
-        next('l');
-        next('l');
-        return null;
-    }
-    error("Unexpected '" + ch + "'");
-  };
-  const array = function() {
-    // Parse an array value.
+      switch (ch) {
+        case 't':
+          next('t');
+          next('r');
+          next('u');
+          next('e');
+          return true;
+        case 'f':
+          next('f');
+          next('a');
+          next('l');
+          next('s');
+          next('e');
+          return false;
+        case 'n':
+          next('n');
+          next('u');
+          next('l');
+          next('l');
+          return null;
+      }
+      error("Unexpected '" + ch + "'");
+    },
+    value, // Place holder for the value function.
+    array = function() {
+      // Parse an array value.
 
-    const array = [];
+      const array = [];
 
-    if (ch === '[') {
-      next('[');
-      white();
-      if (ch === ']') {
-        next(']');
-        return array; // empty array
-      }
-      while (ch) {
-        array.push(value());
+      if (ch === '[') {
+        next('[');
         white();
         if (ch === ']') {
           next(']');
-          return array;
+          return array; // empty array
+        }
+        while (ch) {
+          array.push(value());
+          white();
+          if (ch === ']') {
+            next(']');
+            return array;
+          }
+          next(',');
+          white();
         }
-        next(',');
-        white();
       }
-    }
-    error('Bad array');
-  };
-  const object = function() {
-    // Parse an object value.
+      error('Bad array');
+    },
+    object = function() {
+      // Parse an object value.
 
-    let key;
-    const object = {};
+      let key,
+        object = {};
 
-    if (ch === '{') {
-      next('{');
-      white();
-      if (ch === '}') {
-        next('}');
-        return object; // empty object
-      }
-      while (ch) {
-        key = string();
-        white();
-        next(':');
-        if (Object.hasOwnProperty.call(object, key)) {
-          error('Duplicate key "' + key + '"');
-        }
-        object[key] = value();
+      if (ch === '{') {
+        next('{');
         white();
         if (ch === '}') {
           next('}');
-          return object;
+          return object; // empty object
+        }
+        while (ch) {
+          key = string();
+          white();
+          next(':');
+          if (Object.hasOwnProperty.call(object, key)) {
+            error('Duplicate key "' + key + '"');
+          }
+          object[key] = value();
+          white();
+          if (ch === '}') {
+            next('}');
+            return object;
+          }
+          next(',');
+          white();
         }
-        next(',');
-        white();
       }
+      error('Bad object');
+    };
+
+  value = function() {
+    // Parse a JSON value. It could be an object, an array, a string, a number,
+    // or a word.
+
+    white();
+    switch (ch) {
+      case '{':
+        return object();
+      case '[':
+        return array();
+      case '"':
+        return string();
+      case '-':
+        return number();
+      default:
+        return ch >= '0' && ch <= '9' ? number() : word();
     }
-    error('Bad object');
   };
 
   // Return the json_parse function. It will have access to all of the above
   // functions and variables.
 
   return function(source, reviver) {
+    let result;
+
     text = source;
     at = 0;
     ch = ' ';
-    const res = value();
+    result = value();
     white();
     if (ch) {
       error('Syntax error');
@@ -281,23 +286,23 @@ JSON.bigdataParse = (function() {
 
     return typeof reviver === 'function'
       ? (function walk(holder, key) {
-          let k;
-          let v;
-          const val = holder[key];
-          if (val && typeof val === 'object') {
-            for (k in val) {
-              if (Object.prototype.hasOwnProperty.call(val, k)) {
-                v = walk(val, k);
+          let k,
+            v,
+            value = holder[key];
+          if (value && typeof value === 'object') {
+            for (k in value) {
+              if (Object.prototype.hasOwnProperty.call(value, k)) {
+                v = walk(value, k);
                 if (v !== undefined) {
-                  val[k] = v;
+                  value[k] = v;
                 } else {
-                  delete val[k];
+                  delete value[k];
                 }
               }
             }
           }
-          return reviver.call(holder, key, val);
-        })({ '': res }, '')
-      : res;
+          return reviver.call(holder, key, value);
+        })({ '': result }, '')
+      : result;
   };
 })();

文件差异内容过多而无法显示
+ 22 - 0
desktop/core/src/desktop/static/desktop/js/bundles/hue-bundle-678f24deba1e052d3c21.js


文件差异内容过多而无法显示
+ 0 - 0
desktop/core/src/desktop/static/desktop/js/bundles/hue-bundle-678f24deba1e052d3c21.js.map


文件差异内容过多而无法显示
+ 0 - 22
desktop/core/src/desktop/static/desktop/js/bundles/hue-bundle-b61705ac5fe672f1dbee.js


文件差异内容过多而无法显示
+ 23 - 0
desktop/core/src/desktop/static/desktop/js/bundles/hue~notebook-bundle-678f24deba1e052d3c21.js


文件差异内容过多而无法显示
+ 0 - 0
desktop/core/src/desktop/static/desktop/js/bundles/hue~notebook-bundle-678f24deba1e052d3c21.js.map


文件差异内容过多而无法显示
+ 27 - 0
desktop/core/src/desktop/static/desktop/js/bundles/login-bundle-678f24deba1e052d3c21.js


文件差异内容过多而无法显示
+ 0 - 0
desktop/core/src/desktop/static/desktop/js/bundles/login-bundle-678f24deba1e052d3c21.js.map


文件差异内容过多而无法显示
+ 0 - 22
desktop/core/src/desktop/static/desktop/js/bundles/login-bundle-b61705ac5fe672f1dbee.js


文件差异内容过多而无法显示
+ 26 - 0
desktop/core/src/desktop/static/desktop/js/bundles/notebook-bundle-678f24deba1e052d3c21.js


文件差异内容过多而无法显示
+ 0 - 0
desktop/core/src/desktop/static/desktop/js/bundles/notebook-bundle-678f24deba1e052d3c21.js.map


文件差异内容过多而无法显示
+ 22 - 0
desktop/core/src/desktop/static/desktop/js/bundles/sqlSyntaxWebWorker-bundle-678f24deba1e052d3c21.js


文件差异内容过多而无法显示
+ 0 - 0
desktop/core/src/desktop/static/desktop/js/bundles/sqlSyntaxWebWorker-bundle-678f24deba1e052d3c21.js.map


文件差异内容过多而无法显示
+ 0 - 22
desktop/core/src/desktop/static/desktop/js/bundles/sqlSyntaxWebWorker-bundle-b61705ac5fe672f1dbee.js


文件差异内容过多而无法显示
+ 23 - 0
desktop/core/src/desktop/static/desktop/js/bundles/vendors~hue-bundle-678f24deba1e052d3c21.js


文件差异内容过多而无法显示
+ 0 - 0
desktop/core/src/desktop/static/desktop/js/bundles/vendors~hue-bundle-678f24deba1e052d3c21.js.map


文件差异内容过多而无法显示
+ 50 - 0
desktop/core/src/desktop/static/desktop/js/bundles/vendors~hue~notebook-bundle-678f24deba1e052d3c21.js


文件差异内容过多而无法显示
+ 0 - 0
desktop/core/src/desktop/static/desktop/js/bundles/vendors~hue~notebook-bundle-678f24deba1e052d3c21.js.map


文件差异内容过多而无法显示
+ 23 - 0
desktop/core/src/desktop/static/desktop/js/bundles/vendors~notebook-bundle-678f24deba1e052d3c21.js


文件差异内容过多而无法显示
+ 0 - 0
desktop/core/src/desktop/static/desktop/js/bundles/vendors~notebook-bundle-678f24deba1e052d3c21.js.map


+ 0 - 308
desktop/core/src/desktop/static/desktop/js/jquery.hdfstree.js

@@ -1,308 +0,0 @@
-// Licensed to Cloudera, Inc. under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  Cloudera, Inc. licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-/*
- * jHue HDFS tree plugin
- * shows a tree HDFS picker, if initialPath is set it pre-populates the path
- * if home is specified a home link will appear
- * use attached to an element, ie:
- * $("#el").jHueHdfsTree({
- *    initialPath: "/user",
- *    home: "/user/hue"
- *    onPathChange: function (path) {
- *      console.log(path);
- *    }
- * });
- */
-
-(function ($, window, document, undefined) {
-  var pluginName = "jHueHdfsTree",
-      defaults = {
-        home: "",
-        initialPath: "/",
-        isS3: false,
-        withTopPadding: true,
-        onPathChange: function () {
-        },
-        createFolder: true,
-        labels: {
-          CREATE_FOLDER: "Create folder",
-          FOLDER_NAME: "Folder name",
-          CANCEL: "Cancel",
-          HOME: "Home"
-        }
-      };
-
-  function Plugin(element, options) {
-    this.element = element;
-    this.options = $.extend({}, defaults, options);
-    this.options.labels = $.extend({}, defaults.labels, HUE_I18n.jHueHdfsTree, options ? options.labels : {});
-    this._defaults = defaults;
-    this._name = pluginName;
-    this.lastPath = "";
-    this.previousPath = "";
-    this.init();
-  }
-
-  Plugin.prototype.init = function (optionalPath) {
-    var _this = this;
-
-    if (typeof optionalPath != "undefined") {
-      _this.options.initialPath = optionalPath;
-    }
-    var _el = $(_this.element);
-    _el.empty();
-    _el.addClass("jHueHdfsTree");
-
-    var _homeLink;
-    if (_this.options.home != "") {
-        _homeLink = $("<a>").html('<i class="fa fa-home"></i> ' + _this.options.labels.HOME).click(function () {
-        var _path = _this.options.home;
-        _this.options.onPathChange(_path);
-        _this.lastPath = _path;
-        _tree.find("a").removeClass("selected");
-        var _paths = [];
-        var _re = /\//g;
-        while ((match = _re.exec(_path)) != null) {
-          _paths.push(_path.substr(0, match.index));
-        }
-        _paths.push(_path);
-
-        showHdfsLeaf({
-          paths: _paths,
-          scroll: true
-        });
-      });
-      _homeLink.css({
-        "cursor": "pointer",
-        "position": "fixed",
-        "padding-bottom": '4px',
-        "font-size": "16px",
-        "border-bottom": "1px solid #FFF",
-        "background-color": "#FFF",
-        "width": (560 - hueUtils.scrollbarWidth()) + "px"
-      })
-    }
-
-    var _tree = $("<ul>").addClass("content unstyled").html('<li><a class="pointer"><i class="fa fa-folder-open-o"></i> /</a></li>');
-    if (_this.options.withTopPadding) {
-      _tree.css("padding-top", "30px");
-    }
-
-    if (_this.options.home != "") {
-      _homeLink.appendTo(_el);
-      _el.parent().on('scroll', function(){
-        if (_el.parent().scrollTop() > 0){
-          _homeLink.css({
-            "border-bottom": "1px solid #EEE"
-          });
-        }
-        else {
-          _homeLink.css({
-            "border-bottom": "1px solid #FFF"
-          });
-        }
-      });
-    }
-    _tree.appendTo(_el);
-
-
-    _tree.find("a").on("click", function () {
-      _this.options.onPathChange("/");
-      _tree.find("a").removeClass("selected");
-      _tree.find("a:eq(0)").addClass("selected");
-    });
-
-    var _root = $("<ul>").addClass("content unstyled").attr("data-path", "__JHUEHDFSTREE__ROOT__").attr("data-loaded", "true");
-    _root.appendTo(_tree.find("li"));
-
-    var BASE_PATH = "/filebrowser/view=";
-    var _currentFiles = [];
-
-    function escapeSingleQuote(path) {
-      return path.replace(/\'/gi, "\\'");
-    }
-
-    function removeLeadingSlash(path) {
-      if (path.indexOf("/") == 0) {
-        return path.substr(1);
-      }
-      return path;
-    }
-
-    function showHdfsLeaf(options) {
-      var autocompleteUrl = BASE_PATH,
-          currentPath = "";
-
-      if (options.paths != null && options.paths.length > 0) {
-        var shiftedPath = options.paths.shift();
-        if (_this.options.isS3){
-          currentPath = shiftedPath;
-        }
-        else {
-          currentPath = (shiftedPath != "" ? shiftedPath : "/");
-        }
-      }
-      else {
-        currentPath = (options.leaf != null ? options.leaf : "/");
-      }
-      autocompleteUrl += currentPath;
-      $.getJSON(autocompleteUrl + "?pagesize=1000&format=json", function (data) {
-        _currentFiles = [];
-        if (data.error == null) {
-          var filteredCurrentPath = _this.options.isS3 ? currentPath.substr(5) : currentPath;
-          var _dataPathForCurrent = filteredCurrentPath != "" ? removeLeadingSlash(filteredCurrentPath) : "__JHUEHDFSTREE__ROOT__";
-          _el.find("[data-path='" + escapeSingleQuote(_dataPathForCurrent) + "']").attr("data-loaded", true);
-          _el.find("[data-path='" + escapeSingleQuote(_dataPathForCurrent) + "']").siblings("a").find(".fa-folder-o").removeClass("fa-folder-o").addClass("fa-folder-open-o");
-          _tree.find("a").removeClass("selected");
-          _el.find("[data-path='" + escapeSingleQuote(_dataPathForCurrent) + "']").siblings("a").addClass("selected");
-
-          if (options.scroll) {
-            _el.parent().scrollTop(_el.find("[data-path='" + escapeSingleQuote(_dataPathForCurrent) + "']").siblings("a").position().top + _el.parent().scrollTop() - 30);
-          }
-          $(data.files).each(function (cnt, item) {
-            if (item.name != "." && item.name != ".." && item.type == "dir") {
-              var _path = item.path;
-              var filteredPath = _this.options.isS3 ? _path.substr(5) : _path;
-              var _escapedPath = escapeSingleQuote(filteredPath);
-              if (_el.find("[data-path='" + removeLeadingSlash(_escapedPath) + "']").length == 0) {
-                var _li = $("<li>").html('<a class="pointer"><i class="fa fa-folder-o"></i> ' + item.name + '</a><ul class="content unstyled" data-path="' + removeLeadingSlash(_escapedPath) + '" data-loaded="false"></ul>');
-                var _destination = filteredPath.substr(0, filteredPath.lastIndexOf("/"));
-                if (_destination == "") {
-                  _destination = "__JHUEHDFSTREE__ROOT__";
-                }
-                _destination = removeLeadingSlash(_destination);
-                _li.appendTo(_el.find("[data-path='" + escapeSingleQuote(_destination) + "']"));
-                _li.find("a").on("click", function () {
-                  _this.options.onPathChange(_path);
-                  _this.lastPath = _path;
-                  _tree.find("a").removeClass("selected");
-                  _li.find("a:eq(0)").addClass("selected");
-                  if (_li.find(".content").attr("data-loaded") == "false") {
-                    showHdfsLeaf({
-                      leaf: _path,
-                      scroll: false
-                    });
-                  }
-                  else {
-                    if (_li.find(".content").is(":visible")) {
-                      _li.find(".content").hide();
-                    }
-                    else {
-                      _li.find(".content").show();
-                    }
-                  }
-                });
-              }
-            }
-          });
-          if (_this.options.createFolder) {
-            var filteredCurrentPath = _this.options.isS3 ? currentPath.substr(5) : currentPath;
-            var _createFolderLi = $("<li>").html('<a class="pointer"><i class="fa fa-plus-square-o"></i> ' + _this.options.labels.CREATE_FOLDER + '</a>');
-            _createFolderLi.appendTo(_el.find("[data-path='" + removeLeadingSlash(escapeSingleQuote(filteredCurrentPath)) + "']"));
-
-            var _createFolderDetails = $("<form>").css("margin-top", "10px").addClass("form-inline");
-            _createFolderDetails.hide();
-            var _folderName = $("<input>").attr("type", "text").attr("placeholder", _this.options.labels.FOLDER_NAME).appendTo(_createFolderDetails);
-            $("<span> </span>").appendTo(_createFolderDetails);
-            var _folderBtn = $("<input>").attr("type", "button").attr("value", _this.options.labels.CREATE_FOLDER).addClass("btn primary").appendTo(_createFolderDetails);
-            $("<span> </span>").appendTo(_createFolderDetails);
-            var _folderCancel = $("<input>").attr("type", "button").attr("value", _this.options.labels.CANCEL).addClass("btn").appendTo(_createFolderDetails);
-            _folderCancel.click(function () {
-              _createFolderDetails.slideUp();
-            });
-            _folderBtn.click(function () {
-              $.ajax({
-                type: "POST",
-                url: "/filebrowser/mkdir",
-                data: {
-                  name: _folderName.val(),
-                  path: currentPath
-                },
-                beforeSend: function (xhr) {
-                  xhr.setRequestHeader("X-Requested-With", "Hue"); // need to override the default one because otherwise Django returns HTTP 500
-                },
-                success: function (xhr, status) {
-                  if (status == "success") {
-                    _createFolderDetails.slideUp();
-                    var _newFolder = currentPath + "/" + _folderName.val();
-                    _this.init(_newFolder);
-                    _this.options.onPathChange(_newFolder);
-                  }
-                }
-              });
-
-            });
-            _createFolderDetails.appendTo(_el.find("[data-path='" + removeLeadingSlash(escapeSingleQuote(filteredCurrentPath)) + "']"));
-
-            _createFolderLi.find("a").on("click", function () {
-              _createFolderDetails.slideDown();
-            });
-          }
-          if (options.paths != null && options.paths.length > 0) {
-            showHdfsLeaf({
-              paths: options.paths,
-              scroll: options.scroll
-            });
-          }
-        }
-        else {
-          $.jHueNotify.error(data.error);
-        }
-      });
-    }
-
-    Plugin.prototype.showHdfsLeaf = showHdfsLeaf;
-
-    var _paths = [];
-    if (_this.options.initialPath != "/") {
-      var _re = /\//g;
-      while ((match = _re.exec(_this.options.initialPath)) != null) {
-        _paths.push(_this.options.initialPath.substr(0, match.index));
-      }
-      _paths.push(_this.options.initialPath);
-    }
-
-    if (_this.options.isS3){
-      _paths.shift();
-      _paths[0] = 's3a://';
-    }
-
-    showHdfsLeaf({
-      paths: _paths
-    });
-
-  };
-
-  Plugin.prototype.setOptions = function (options) {
-    this.options = $.extend({}, defaults, options);
-  };
-
-  $.fn[pluginName] = function (options) {
-    return this.each(function () {
-      if (!$.data(this, 'plugin_' + pluginName)) {
-        $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
-      }
-    });
-  }
-
-  $[pluginName] = function (options) {
-    if (typeof console != "undefined") {
-      console.warn("$(elem).jHueHdfsTree() is a preferred call method.");
-    }
-    $(options.element).jHueHdfsTree(options);
-  };
-
-})(jQuery, window, document);

+ 3 - 0
desktop/core/src/desktop/templates/common_header.mako

@@ -138,6 +138,9 @@ if USE_NEW_EDITOR.get():
   % if section == "login":
     ${ render_bundle('login') | n,unicode }
   %else:
+    ${ render_bundle('vendors~hue~notebook') | n,unicode }
+    ${ render_bundle('vendors~hue') | n,unicode }
+    ${ render_bundle('hue~notebook') | n,unicode }
     ${ render_bundle('hue') | n,unicode }
   % endif
 

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

@@ -450,6 +450,9 @@ ${ hueIcons.symbols() }
 
 ${ commonshare() | n,unicode }
 
+${ render_bundle('vendors~hue~notebook') | n,unicode }
+${ render_bundle('vendors~hue') | n,unicode }
+${ render_bundle('hue~notebook') | n,unicode }
 ${ render_bundle('hue') | n,unicode }
 
 <script src="${ static('desktop/js/polyfills.js') }"></script>
@@ -483,7 +486,6 @@ ${ render_bundle('hue') | n,unicode }
 <script src="${ static('desktop/js/sqlAutocompleter3.js') }"></script>
 <script src="${ static('desktop/js/hdfsAutocompleter.js') }"></script>
 <script src="${ static('desktop/js/autocompleter.js') }"></script>
-<script src="${ static('notebook/js/notebook.ko.js') }"></script>
 <script src="${ static('metastore/js/metastore.model.js') }"></script>
 
 <script>
@@ -739,6 +741,14 @@ ${ smart_unicode(login_modal(request).content) | n,unicode }
         });
 
         var loadScript = function (scriptUrl) {
+          if (scriptUrl.indexOf('-bundle') !== -1) {
+            var s = document.createElement('script');
+            s.src = scriptUrl;
+            s.type = "text/javascript";
+            s.async = false;
+            document.getElementsByTagName('head')[0].appendChild(s);
+            return $.Deferred().resolve({ url: scriptUrl, head: true }).promise();
+          }
           var deferred = $.Deferred();
           $.ajax({
             url: scriptUrl,

+ 0 - 2
desktop/libs/indexer/src/indexer/templates/importer.mako

@@ -42,8 +42,6 @@ ${ commonheader(_("Importer"), "indexer", user, request, "60px") | n,unicode }
 
 ${ assist.assistJSModels() }
 
-<script src="${ static('notebook/js/notebook.ko.js') }"></script>
-
 <link rel="stylesheet" href="${ static('notebook/css/notebook.css') }">
 <link rel="stylesheet" href="${ static('notebook/css/notebook-layout.css') }">
 ${ assist.assistPanel() }

+ 0 - 2
desktop/libs/indexer/src/indexer/templates/indexer.mako

@@ -31,8 +31,6 @@ ${ commonheader(_("Solr Indexes"), "search", user, request, "60px") | n,unicode
 
 ${ assist.assistJSModels() }
 
-<script src="${ static('notebook/js/notebook.ko.js') }"></script>
-
 <link rel="stylesheet" href="${ static('notebook/css/notebook.css') }">
 <link rel="stylesheet" href="${ static('notebook/css/notebook-layout.css') }">
 

+ 0 - 3628
desktop/libs/notebook/src/notebook/static/notebook/js/notebook.ko.js

@@ -1,3628 +0,0 @@
-// Licensed to Cloudera, Inc. under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  Cloudera, Inc. licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-var EditorViewModel = (function() {
-
-  var NOTEBOOK_MAPPING = {
-    ignore: [
-      'ace', 'aceMode', 'autocompleter', 'availableDatabases', 'availableSnippets', 'avoidClosing', 'canWrite',
-      'cleanedDateTimeMeta', 'cleanedMeta', 'cleanedNumericMeta', 'cleanedStringMeta', 'dependents', 'errorLoadingQueries',
-      'hasProperties', 'history', 'images', 'inFocus', 'queries', 'saveResultsModalVisible', 'selectedStatement',
-      'snippetImage', 'user', 'positionStatement', 'lastExecutedStatement', 'downloadResultViewModel'
-    ]
-  };
-
-  var COMPATIBILITY_SOURCE_PLATFORMS ={
-    teradata: { name: 'Teradata', value: 'teradata' },
-    oracle: { name: 'Oracle', value: 'oracle' },
-    netezza: { name: 'Netezza', value: 'netezza' },
-    impala: { name: 'Impala', value: 'impala' },
-    hive: { name: 'Hive', value: 'hive' },
-    db2: { name: 'DB2', value: 'db2' },
-    greenplum: { name: 'Greenplum', value: 'greenplum' },
-    mysql: { name: 'MySQL', value: 'mysql' },
-    postgresql: { name: 'PostgreSQL', value: 'postgresql' },
-    informix: { name: 'Informix', value: 'informix' },
-    sqlserver: { name: 'SQL Server', value: 'sqlserver' },
-    sybase: { name: 'Sybase', value: 'sybase' },
-    access: { name: 'Access', value: 'access' },
-    firebird: { name: 'Firebird', value: 'firebird' },
-    ansisql: { name: 'ANSISQL', value: 'ansisql' },
-    generic: { name: 'Generic', value: 'generic' }
-  };
-
-  var COMPATIBILITY_TARGET_PLATFORMS ={
-    impala: { name: 'Impala', value: 'impala' },
-    hive: { name: 'Hive', value: 'hive' }
-  };
-
-  var Result = function (snippet, result) {
-    var self = this;
-
-    $.extend(snippet, snippet.chartType == 'lines' && { // Retire line chart
-        chartType: 'bars',
-        chartTimelineType: 'line'
-    });
-    self.id = ko.observable(typeof result.id != "undefined" && result.id != null ? result.id : hueUtils.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 : []);
-
-    var adaptMeta = function () {
-      var i = 0;
-      self.meta().forEach(function (item) {
-        if (typeof item.checked === 'undefined') {
-          item.checked = ko.observable(true);
-          item.checked.subscribe(function () {
-            self.filteredMetaChecked(self.filteredMeta().some(function (item) { return item.checked(); }));
-          });
-        }
-        item.type = item.type.replace(/_type/i, '').toLowerCase();
-        if (typeof item.originalIndex === 'undefined') {
-          item.originalIndex = i;
-        }
-        i++;
-      })
-    };
-
-    adaptMeta();
-    self.meta.subscribe(adaptMeta);
-
-
-    self.rows = ko.observable(typeof result.rows != "undefined" && result.rows != null ? result.rows : null);
-    self.hasMore = ko.observable(typeof result.hasMore != "undefined" && result.hasMore != null ? result.hasMore : false);
-    self.statement_id = ko.observable(typeof result.statement_id != "undefined" && result.statement_id != null ? result.statement_id : 0);
-    self.statement_range = ko.observable(typeof result.statement_range != "undefined" && result.statement_range != null ? result.statement_range : {
-      start: {
-        row: 0,
-        column: 0
-      },
-      end: {
-        row: 0,
-        column: 0
-      }
-    });
-    // We don't keep track of any previous selection so prevent entering into batch execution mode after load by setting
-    // statements_count to 1. For the case when a selection is not starting at row 0.
-    self.statements_count = ko.observable(1);
-    self.previous_statement_hash = ko.observable(typeof result.previous_statement_hash != "undefined" && result.previous_statement_hash != null ? result.previous_statement_hash : null);
-    self.cleanedMeta = ko.computed(function () {
-      return ko.utils.arrayFilter(self.meta(), function (item) {
-        return item.name != ''
-      });
-    });
-    self.metaFilter = ko.observable();
-
-    self.isMetaFilterVisible = ko.observable(false);
-    self.filteredMetaChecked = ko.observable(true);
-    self.filteredMeta = ko.pureComputed(function () {
-      if (!self.metaFilter() || self.metaFilter().query === '') {
-        return self.meta();
-      }
-
-      return self.meta().filter(function (item) {
-        var facets = self.metaFilter().facets;
-        var isFacetMatch = !facets || Object.keys(facets).length === 0 || !facets['type']; // So far only type facet is used for SQL
-        var isTextMatch = !self.metaFilter().text || self.metaFilter().text.length === 0;
-        var match = true;
-
-        if (!isFacetMatch) {
-          match = !!facets['type'][item.type];
-        }
-
-        if (match && !isTextMatch) {
-          match = self.metaFilter().text.every(function (text) {
-            return item.name.toLowerCase().indexOf(text.toLowerCase()) !== -1;
-          });
-        }
-        return match;
-      });
-    });
-
-    self.autocompleteFromEntries = function (nonPartial, partial) {
-      var result = [];
-      var partialLower = partial.toLowerCase();
-      self.meta().forEach(function (column) {
-        if (column.name.toLowerCase().indexOf(partialLower) === 0) {
-          result.push(nonPartial + partial + column.name.substring(partial.length))
-        } else if (column.name.toLowerCase().indexOf('.' + partialLower) !== -1) {
-          result.push(nonPartial + partial + column.name.substring(partial.length + column.name.toLowerCase().indexOf('.' + partialLower) + 1))
-        }
-      });
-
-      return result;
-    };
-    self.clickFilteredMetaCheck = function () {
-      self.filteredMeta().forEach(function (item) {
-        item.checked(self.filteredMetaChecked());
-      });
-    };
-
-    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', 'smallint', 'int', 'bigint', 'float', 'double', 'decimal', 'real']) > -1;
-    }
-
-    function isDateTimeColumn(type) {
-      return $.inArray(type, ['timestamp', 'date', 'datetime']) > -1;
-    }
-
-    function isComplexColumn(type) {
-      return $.inArray(type, ['array', 'map', 'struct']) > -1;
-    }
-
-    function isStringColumn(type) {
-      return !isNumericColumn(type) && !isDateTimeColumn(type) && !isComplexColumn(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.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.explanation = ko.observable(typeof result.explanation != "undefined" && result.explanation != null ? result.explanation : '');
-    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.hasMore(false);
-      self.statement_range({
-        start: {
-          row: 0,
-          column: 0
-        },
-        end: {
-          row: 0,
-          column: 0
-        }
-      });
-      self.meta.removeAll();
-      self.data.removeAll();
-      self.images.removeAll();
-      self.logs('');
-      self.handle({ // Keep multiquery indexing
-          has_more_statements: self.handle()['has_more_statements'],
-          statement_id: self.handle()['statement_id'],
-          statements_count: self.handle()['statements_count'],
-          previous_statement_hash: self.handle()['previous_statement_hash'],
-      });
-      self.startTime(new Date());
-      self.endTime(new Date());
-      self.explanation('');
-      self.logLines = 0;
-      self.rows(null);
-    };
-  };
-
-  Result.prototype.cancelBatchExecution = function () {
-    var self = this;
-    self.statements_count(1);
-    self.hasMore(false);
-    self.statement_range({
-      start: {
-        row: 0,
-        column: 0
-      },
-      end: {
-        row: 0,
-        column: 0
-      }
-    });
-    self.handle()['statement_id'] = 0;
-    self.handle()['start'] = {
-      row: 0,
-      column: 0
-    };
-    self.handle()['end'] = {
-      row: 0,
-      column: 0
-    };
-    self.handle()['has_more_statements'] = false;
-    self.handle()['previous_statement_hash'] = '';
-  };
-
-  var getDefaultSnippetProperties = function (snippetType) {
-    var properties = {};
-
-    if (snippetType == 'jar' || snippetType == 'py') {
-      properties['driverCores'] = '';
-      properties['executorCores'] = '';
-      properties['numExecutors'] = '';
-      properties['queue'] = '';
-      properties['archives'] = [];
-      properties['files'] = [];
-    }
-
-    if (snippetType == 'java') {
-      properties['archives'] = [];
-      properties['files'] = [];
-      properties['capture_output'] = false;
-    }
-
-    if (snippetType == 'shell') {
-      properties['archives'] = [];
-      properties['files'] = [];
-    }
-
-    if (snippetType == 'mapreduce') {
-      properties['app_jar'] = '';
-      properties['hadoopProperties'] = [];
-      properties['jars'] = [];
-      properties['files'] = [];
-      properties['archives'] = [];
-    }
-
-    if (snippetType == 'spark2') {
-      properties['app_name'] = '';
-      properties['class'] = '';
-      properties['jars'] = [];
-      properties['spark_opts'] = [];
-      properties['spark_arguments'] = [];
-      properties['files'] = [];
-    }
-
-    if (snippetType == 'sqoop1') {
-      properties['files'] = [];
-    }
-
-    if (snippetType == 'jar' || snippetType == 'java') {
-      properties['app_jar'] = '';
-      properties['class'] = '';
-      properties['arguments'] = [];
-    } else if (snippetType == 'distcp') {
-      properties['source_path'] = '';
-      properties['destination_path'] = '';
-    } else if (snippetType == 'shell') {
-      properties['command_path'] = '';
-      properties['arguments'] = [];
-      properties['env_var'] = [];
-      properties['capture_output'] = true;
-    } else if (snippetType == 'py') {
-      properties['py_file'] = '';
-      properties['arguments'] = [];
-    } else if (snippetType == 'hive') {
-      properties['settings'] = [];
-      properties['files'] = [];
-      properties['functions'] = [];
-      properties['arguments'] = [];
-    } else if (snippetType == 'impala') {
-      properties['settings'] = [];
-    } else if (snippetType == 'pig') {
-      properties['parameters'] = [];
-      properties['hadoopProperties'] = [];
-      properties['resources'] = [];
-    }
-
-    return properties;
-  };
-
-  var ERROR_REGEX = /line ([0-9]+)(\:([0-9]+))?/i;
-
-  var Snippet = function (vm, notebook, snippet) {
-    var self = this;
-
-    self.id = ko.observable(typeof snippet.id != "undefined" && snippet.id != null ? snippet.id : hueUtils.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.type.subscribe(function(newVal) {
-      self.status('ready');
-    });
-
-    self.isBatchable = ko.computed(function() {
-      return self.type() == 'hive'
-          || self.type() == 'impala'
-          || $.grep(vm.availableLanguages, function(language) { return language.type == self.type() && language.interface == 'oozie'; }).length > 0;
-    });
-
-    self.autocompleteSettings = {
-      temporaryOnly: false
-    };
-
-    // Ace stuff
-    self.aceCursorPosition = ko.observable(notebook.isHistory() ? snippet.aceCursorPosition : null);
-
-    var aceEditor = null;
-
-    self.ace = function (newVal) {
-      if (newVal) {
-        aceEditor = newVal;
-        if (!notebook.isPresentationMode()) {
-          aceEditor.focus();
-        }
-      }
-      return aceEditor
-    };
-    self.errors = ko.observableArray([]);
-
-    self.aceErrorsHolder = ko.observableArray([]);
-    self.aceWarningsHolder = ko.observableArray([]);
-
-    self.aceErrors = ko.pureComputed(function(){
-      return self.showOptimizer() ? self.aceErrorsHolder() : [];
-    });
-    self.aceWarnings = ko.pureComputed(function(){
-      return self.showOptimizer() ? self.aceWarningsHolder() : [];
-    });
-
-    self.availableSnippets = vm.availableSnippets();
-    self.inFocus = ko.observable(false);
-
-    self.inFocus.subscribe(function (newValue) {
-      if (newValue) {
-        huePubSub.publish('active.snippet.type.changed', self.type());
-      }
-    });
-
-    self.editorMode = vm.editorMode;
-
-    self.getAceMode = function() {
-      return vm.getSnippetViewSettings(self.type()).aceMode;
-    };
-
-    self.dbSelectionVisible = ko.observable(false);
-
-    self.showExecutionAnalysis = ko.observable(false);
-
-    self.isSqlDialect = ko.pureComputed(function () {
-      return vm.getSnippetViewSettings(self.type()).sqlDialect;
-    });
-
-    self.getPlaceHolder = function() {
-      return vm.getSnippetViewSettings(self.type()).placeHolder;
-    };
-
-    self.getApiHelper = function() {
-      return window.apiHelper;
-    };
-
-    // namespace and compute might be initialized as empty object {}
-    self.namespace = ko.observable(snippet.namespace && snippet.namespace.id ? snippet.namespace : undefined);
-    self.compute = ko.observable(snippet.compute && snippet.compute.id ? snippet.compute : undefined);
-
-    self.whenContextSet = function () {
-      var namespaceSub;
-      var namespaceDeferred = $.Deferred();
-      if (self.namespace()) {
-        namespaceDeferred.resolve(self.namespace());
-      } else {
-        namespaceSub = self.namespace.subscribe(function (newVal) {
-          if (newVal) {
-            namespaceDeferred.resolve(newVal);
-            namespaceSub.dispose();
-          }
-        })
-      }
-      var computeSub;
-      var computeDeferred = $.Deferred();
-      if (self.compute()) {
-        computeDeferred.resolve(self.compute());
-      } else {
-        computeSub = self.compute.subscribe(function (newVal) {
-          if (newVal) {
-            computeDeferred.resolve(newVal);
-            computeSub.dispose();
-          }
-        })
-      }
-
-      var result = $.when(namespaceDeferred, computeDeferred);
-
-      result.dispose = function () {
-        if (namespaceSub) {
-          namespaceSub.dispose();
-        }
-        if (computeSub) {
-          computeSub.dispose();
-        }
-        namespaceDeferred.reject();
-        computeDeferred.reject();
-      };
-
-      return result;
-    };
-
-    self.availableDatabases = ko.observableArray();
-    self.database = ko.observable();
-    var previousDatabase = null;
-
-    self.database.subscribe(function (newValue) {
-      if (newValue !== null) {
-        self.getApiHelper().setInTotalStorage('editor', 'last.selected.database', newValue);
-        if (previousDatabase !== null && previousDatabase !== newValue) {
-          huePubSub.publish('editor.refresh.statement.locations', self);
-        }
-        previousDatabase = newValue;
-      }
-    });
-
-    self.database(typeof snippet.database !== "undefined" && snippet.database != null ? snippet.database : null);
-
-    // History is currently in Notebook, same with saved queries by snippets, might be better in assist
-    self.currentQueryTab = ko.observable(typeof snippet.currentQueryTab != "undefined" && snippet.currentQueryTab != null ? snippet.currentQueryTab : 'queryHistory');
-    self.pinnedContextTabs = ko.observableArray(typeof snippet.pinnedContextTabs != "undefined" && snippet.pinnedContextTabs != null ? snippet.pinnedContextTabs : []);
-
-    self.removeContextTab = function (context) {
-      if (context.tabId === self.currentQueryTab()) {
-        self.currentQueryTab('queryHistory');
-      }
-      self.pinnedContextTabs.remove(context);
-    };
-
-    self.errorLoadingQueries = ko.observable(false);
-    self.loadingQueries = ko.observable(false);
-
-    self.queriesHasErrors = ko.observable(false);
-    self.queriesCurrentPage = ko.observable(vm.selectedNotebook() && vm.selectedNotebook().snippets().length > 0 ? vm.selectedNotebook().snippets()[0].queriesCurrentPage() : 1);
-    self.queriesTotalPages = ko.observable(vm.selectedNotebook() && vm.selectedNotebook().snippets().length > 0 ? vm.selectedNotebook().snippets()[0].queriesTotalPages() : 1);
-    self.queries = ko.observableArray([]);
-
-    self.queriesFilter = ko.observable('');
-    self.queriesFilterVisible = ko.observable(false);
-    self.queriesFilter.extend({ rateLimit: { method: "notifyWhenChangesStop", timeout: 900 } });
-    self.queriesFilter.subscribe(function(val){
-      self.fetchQueries();
-    });
-
-    var lastFetchQueriesRequest = null;
-
-    self.fetchQueries = function () {
-      self.getApiHelper().cancelActiveRequest(lastFetchQueriesRequest);
-
-      var QUERIES_PER_PAGE = 50;
-      lastQueriesPage = self.queriesCurrentPage();
-      self.loadingQueries(true);
-      self.queriesHasErrors(false);
-      lastFetchQueriesRequest = self.getApiHelper().searchDocuments({
-        successCallback: function (result) {
-          self.queriesTotalPages(Math.ceil(result.count / QUERIES_PER_PAGE));
-          self.queries(ko.mapping.fromJS(result.documents)());
-          self.loadingQueries(false);
-          self.queriesHasErrors(false);
-        },
-        errorCallback: function () {
-          self.loadingQueries(false);
-          self.queriesHasErrors(true);
-        },
-        page: self.queriesCurrentPage(),
-        limit: QUERIES_PER_PAGE,
-        type: 'query-' + self.type(),
-        query: self.queriesFilter(),
-        include_trashed: false
-      });
-    };
-
-    var lastQueriesPage = 1;
-    self.currentQueryTab.subscribe(function (newValue) {
-      huePubSub.publish('redraw.fixed.headers');
-      huePubSub.publish('tab.switched', newValue);
-      if (newValue === 'savedQueries' && (self.queries().length === 0 || lastQueriesPage !== self.queriesCurrentPage())) {
-        self.fetchQueries();
-      }
-    });
-
-    self.prevQueriesPage = function () {
-      if (self.queriesCurrentPage() !== 1) {
-        self.queriesCurrentPage(self.queriesCurrentPage() - 1);
-        self.fetchQueries();
-      }
-    };
-
-    self.nextQueriesPage = function () {
-      if (self.queriesCurrentPage() !== self.queriesTotalPages()) {
-        self.queriesCurrentPage(self.queriesCurrentPage() + 1);
-        self.fetchQueries();
-      }
-    };
-
-    huePubSub.subscribeOnce('assist.source.set', function (source) {
-      if (source !== self.type()) {
-        huePubSub.publish('assist.set.source', self.type());
-      }
-    }, vm.huePubSubId);
-
-    huePubSub.publish('assist.get.source');
-
-    var ignoreNextAssistDatabaseUpdate = false;
-    self.handleAssistSelection = function (databaseDef) {
-      if (ignoreNextAssistDatabaseUpdate) {
-        ignoreNextAssistDatabaseUpdate = false;
-      } else if (databaseDef.sourceType === self.type()) {
-        if (self.namespace() !== databaseDef.namespace) {
-          self.namespace(databaseDef.namespace)
-        }
-        if (self.database() !== databaseDef.name) {
-          self.database(databaseDef.name);
-        }
-      }
-    };
-
-    if (!self.database()) {
-      huePubSub.publish('assist.get.database.callback', { source: self.type(), callback: function (databaseDef) {
-        self.handleAssistSelection(databaseDef);
-      }});
-    }
-
-    self.statementType = ko.observable(typeof snippet.statementType != "undefined" && snippet.statementType != null ? snippet.statementType : 'text');
-    self.statementTypes = ko.observableArray(['text', 'file']); // Maybe computed later for Spark
-    if (! vm.editorMode()) {
-      self.statementTypes.push('document');
-    }
-    self.statementPath = ko.observable(typeof snippet.statementPath != "undefined" && snippet.statementPath != null ? snippet.statementPath : '');
-    self.statementPath.subscribe(function(newVal) {
-      self.getExternalStatement();
-    });
-    self.externalStatementLoaded = ko.observable(false);
-    self.getExternalStatement = function() {
-      self.externalStatementLoaded(false);
-      $.post("/notebook/api/get_external_statement", {
-        notebook: ko.mapping.toJSON(notebook.getContext()),
-        snippet: ko.mapping.toJSON(self.getContext())
-      }, function(data) {
-        if (data.status == 0) {
-          self.externalStatementLoaded(true);
-          self.statement_raw(data.statement);
-          self.ace().setValue(self.statement_raw(), 1);
-        } else {
-          self._ajaxError(data);
-        }
-      });
-    };
-    self.associatedDocumentLoading = ko.observable(true);
-    self.associatedDocument = ko.observable();
-    self.associatedDocumentUuid = ko.observable(typeof snippet.associatedDocumentUuid != "undefined" && snippet.associatedDocumentUuid != null ? snippet.associatedDocumentUuid : null);
-    self.associatedDocumentUuid.subscribe(function(val){
-      if (val !== ''){
-        self.getExternalStatement();
-      }
-      else {
-        self.statement_raw('');
-        self.ace().setValue('', 1);
-      }
-    });
-    self.statement_raw = ko.observable(typeof snippet.statement_raw != "undefined" && snippet.statement_raw != null ? snippet.statement_raw : '');
-    self.selectedStatement = ko.observable('');
-    self.positionStatement = ko.observable(null);
-    self.lastExecutedStatement = ko.observable(null);
-    self.statementsList = ko.observableArray();
-
-    huePubSub.subscribe('editor.active.statement.changed', function (statementDetails) {
-      if (self.ace() && self.ace().container.id === statementDetails.id) {
-        if (statementDetails.activeStatement) {
-          self.positionStatement(statementDetails.activeStatement);
-        } else {
-          self.positionStatement(null);
-        }
-
-        if (statementDetails.activeStatement) {
-          var _statements = [];
-           statementDetails.precedingStatements.forEach(function (statement) {
-             _statements.push(statement.statement);
-           });
-           _statements.push(statementDetails.activeStatement.statement);
-           statementDetails.followingStatements.forEach(function (statement) {
-             _statements.push(statement.statement);
-          });
-          self.statementsList(_statements); // Or fetch on demand via editor.refresh.statement.locations and remove observableArray?
-        } else {
-          self.statementsList([]);
-        }
-        if (!notebook.isPresentationModeInitialized()) {
-          if (notebook.isPresentationModeDefault()) {
-            // When switching to presentation mode, the snippet in non presentation mode cannot get status notification.
-            // On initiailization, status is set to loading and does not get updated, because we moved to presentation mode.
-            self.status('ready');
-          }
-          // Changing to presentation mode requires statementsList to be initialized. statementsList is initialized asynchronously.
-          // When presentation mode is default, we cannot change before statementsList has been calculated.
-          // Cleaner implementation would be to make toggleEditorMode statementsList asynchronous
-          // However this is currently impossible due to delete _notebook.presentationSnippets()[key];
-          notebook.isPresentationModeInitialized(true);
-          notebook.isPresentationMode(notebook.isPresentationModeDefault());
-        }
-      }
-
-    }, vm.huePubSubId);
-
-    self.aceSize = ko.observable(typeof snippet.aceSize != "undefined" && snippet.aceSize != null ? snippet.aceSize : 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.statusForButtons = ko.observable('executed');
-
-    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.result.clear();
-      window.setTimeout(function () {
-        if (self.ace() !== null) {
-          self.ace().focus();
-        }
-      }, 100);
-    });
-    if (snippet.variables) {
-      snippet.variables.forEach(function (variable) {
-        variable.meta = (typeof variable.defaultValue === "object" && variable.defaultValue) || {type: "text", placeholder: ""};
-        variable.value = variable.value || '';
-        variable.type = variable.type || 'text';
-        variable.sample = [];
-        variable.sampleUser = variable.sampleUser || [];
-        variable.path = variable.path || '';
-        variable.step = '';
-        delete variable.defaultValue;
-      });
-    }
-    self.variables = ko.mapping.fromJS(typeof snippet.variables != "undefined" && snippet.variables != null ? snippet.variables : []);
-    self.variables.subscribe(function (newValue) {
-      $(document).trigger("updateResultHeaders", self);
-    });
-    self.hasCurlyBracketParameters = ko.computed(function() {
-      return self.type() != 'pig';
-    });
-    self.getPigParameters = function() {
-      var params = {};
-      var variables = self.statement_raw().match(/([^\\]|^)\$[^\d'"](\w*)/g);
-      var declares = self.statement_raw().match(/%declare +([^ ])+/gi);
-      var defaults = self.statement_raw().match(/%default +([^;])+/gi);
-      var macro_defines = self.statement_raw().match(/define [^ ]+ *\(([^\)]*)\)/gi); // no multiline
-      var macro_returns = self.statement_raw().match(/returns +([^\{]*)/gi); // no multiline
-
-      if (variables) {
-        $.each(variables, function(index, param) {
-          var p = param.substring(param.indexOf('$') + 1);
-          params[p] = '';
-        });
-      }
-      if (declares) {
-        $.each(declares, function(index, param) {
-          param = param.match(/(\w+)/g);
-          if (param && param.length >= 2) {
-            delete params[param[1]];
-          }
-        });
-      }
-      if (defaults) {
-        $.each(defaults, function(index, param) {
-          var line = param.match(/(\w+)/g);
-          if (line && line.length >= 2) {
-            var name = line[1];
-            params[name] = param.substring(param.indexOf(name) + name.length + 1);
-          }
-        });
-      }
-      if (macro_defines) {
-        $.each(macro_defines, function(index, params_line) {
-          var param_line = params_line.match(/(\w+)/g);
-          if (param_line && param_line.length > 2) {
-            $.each(param_line, function(index, param) {
-              if (index >= 2) { // Skips define NAME
-                delete params[param];
-              }
-            });
-          }
-        });
-      }
-      if (macro_returns) {
-        $.each(macro_returns, function(index, params_line) {
-          var param_line = params_line.match(/(\w+)/g);
-          if (param_line) {
-            $.each(param_line, function(index, param) {
-              if (index >= 1) { // Skip returns
-                delete params[param];
-              }
-            });
-          }
-        });
-      }
-
-      return params;
-    };
-    self.variableNames = ko.computed(function () {
-      var match, matches = {}, matchList;
-      if (self.type() == 'pig') {
-        matches = self.getPigParameters();
-      } else {
-        var re = /(?:^|\W)\${(\w*)\=?([^{}]*)}/g;
-        var reComment = /(^\s*--.*)|(\/\*[\s\S]*?\*\/)/gm;
-        var reList = /(?!\s*$)\s*(?:(?:([^,|()\\]*)\(\s*([^,|()\\]*)\)(?:\\[\S\s][^,|()\\]*)?)|([^,|\\]*(?:\\[\S\s][^,|\\]*)*))\s*(?:,|\||$)/g
-        var statement = self.statement_raw();
-        var matchComment = reComment.exec(statement);
-        // if re is n & reComment is m
-        // finding variables is O(n+m)
-        while (match = re.exec(statement)) {
-          while (matchComment && match.index > matchComment.index + matchComment[0].length) { // Comments before our match
-            matchComment = reComment.exec(statement);
-          }
-          var isWithinComment = matchComment && match.index >= matchComment.index;
-          if (isWithinComment) continue;
-
-          // If 1 match, text value
-          // If multiple matches, list value
-          var value = { type: 'text', placeholder: '' };
-          while (matchList = reList.exec(match[2])) {
-            var option = {text:matchList[2] || matchList[3], value:matchList[3] || matchList[1]};
-            option.text = option.text && option.text.trim();
-            option.value = option.value && option.value.trim().replace('\,', ',').replace('\(', '(').replace('\)', ')');
-
-            if (value.placeholder || matchList[2]) {
-              if (!value.options) {
-                value.options = [];
-                value.type = 'select';
-              }
-              value.options.push(option);
-            }
-            if (!value.placeholder) value.placeholder = option.value;
-          }
-          var isPlaceholderInOptions = !value.options || value.options.some(function (current){
-            return current.value == value.placeholder;
-          });
-          if (!isPlaceholderInOptions) {
-            value.options.unshift({ text: value.placeholder, value: value.placeholder });
-          }
-          matches[match[1]] = matches[match[1]] || value;
-        }
-      }
-      return $.map(matches, function (match, key) {
-        var isMatchObject = typeof matches[key] === 'object';
-        var meta = isMatchObject ? matches[key] : { type: 'text', placeholder: matches[key] };
-        return { name: key, meta: meta };
-      });
-    });
-    self.variableValues = {};
-    self.variableNames.extend({ rateLimit: 150 });
-    self.variableNames.subscribe(function (newVal) {
-      var variablesLength = self.variables().length;
-      var diffLengthVariables = variablesLength - newVal.length;
-      var needsMore = diffLengthVariables < 0;
-      var needsLess = diffLengthVariables > 0;
-      self.variableValues = self.variables().reduce(function (variableValues, variable) {
-        if (!variableValues[variable.name()]) {
-          variableValues[variable.name()] = { sampleUser: [] };
-        }
-        variableValues[variable.name()].value = variable.value();
-        variableValues[variable.name()].sampleUser = variable.sampleUser();
-        variableValues[variable.name()].catalogEntry = variable.catalogEntry;
-        variableValues[variable.name()].path = variable.path();
-        variableValues[variable.name()].type = variable.type();
-        return variableValues;
-      }, self.variableValues);
-      if (needsMore) {
-        for (var i = 0, length = Math.abs(diffLengthVariables); i < length; i++) {
-          self.variables.push(ko.mapping.fromJS({ name: '', value: '', meta: { type: 'text', placeholder: '', options: [] }, sample: [], sampleUser: [], type: 'text', step: '', path: ''}));
-        }
-      } else if (needsLess) {
-        self.variables.splice(self.variables().length - diffLengthVariables, diffLengthVariables);
-      }
-      newVal.forEach(function (item, index) {
-        var variable = self.variables()[index];
-        variable.name(item.name);
-        setTimeout(function(){
-          variable.value(self.variableValues[item.name] ? self.variableValues[item.name].value : (!needsMore && variable.value()) || '');
-        },0);
-        variable.meta = ko.mapping.fromJS(item.meta, {}, variable.meta);
-        variable.sample(variable.meta.options ? variable.meta.options().concat(variable.sampleUser()) : variable.sampleUser())
-        variable.sampleUser(self.variableValues[item.name] ? self.variableValues[item.name].sampleUser : []);
-        variable.type(self.variableValues[item.name] ? self.variableValues[item.name].type || 'text' : 'text');
-        variable.path(self.variableValues[item.name] ? self.variableValues[item.name].path || '' : '');
-        variable.catalogEntry = self.variableValues[item.name] && self.variableValues[item.name].catalogEntry;
-      });
-    });
-
-    var activeSourcePromises = [];
-    huePubSub.subscribe('ace.sql.location.worker.message', function (e) {
-      while (activeSourcePromises.length) {
-        var promise = activeSourcePromises.pop();
-        if (promise.cancel) {
-          promise.cancel();
-        }
-      }
-      var sourceType = self.type();
-      var oLocations = e.data.locations
-      .filter(function (location) {
-        return location.type === 'variable' && location.colRef;
-      })
-      .reduce(function (variables, location) {
-        var re = /\${(\w*)\=?([^{}]*)}/g;
-        var name = re.exec(location.value)[1];
-        variables[name] = location;
-        return variables;
-      }, {});
-      var updateVariableType = function (variable, sourceMeta) {
-        var type;
-        if (sourceMeta && sourceMeta.type) {
-          type = sourceMeta.type.toLowerCase();
-        } else {
-          type = 'string';
-        }
-        var variablesValues = {};
-        var value = variable.value();
-        switch (type) {
-          case 'timestamp':
-            variablesValues.type = 'datetime-local';
-            variablesValues.step = '1';
-            variablesValues.value = value && moment.utc(value).format("YYYY-MM-DD HH:mm:ss.S") || moment(Date.now()).format("YYYY-MM-DD 00:00:00.0");
-            break;
-          case 'decimal':
-          case 'double':
-          case 'float':
-            variablesValues.type = 'number';
-            variablesValues.step = 'any';
-            break;
-          case 'int':
-          case 'smallint':
-          case 'tinyint':
-          case 'bigint':
-            variablesValues.type = 'number';
-            variablesValues.step = '1';
-            break;
-          case 'date':
-            variablesValues.type = 'date';
-            variablesValues.step = '';
-            variablesValues.value = value && moment.utc(value).format("YYYY-MM-DD") || moment(Date.now()).format("YYYY-MM-DD");
-            break;
-          case 'boolean':
-            variablesValues.type = 'checkbox';
-            variablesValues.step = '';
-            break;
-          default:
-            variablesValues.type = 'text';
-            variablesValues.step = '';
-        }
-        if (variablesValues.value) {
-          setTimeout(function () {
-            variable.value(variablesValues.value);
-          }, 0);
-        }
-        variable.type(variablesValues.type);
-        variable.step(variablesValues.step);
-      };
-      self.variables().forEach(function (variable) {
-        if (oLocations[variable.name()]) {
-          activeSourcePromises.push(oLocations[variable.name()].resolveCatalogEntry({ cancellable: true }).done(function (entry) {
-            variable.path(entry.path.join('.'));
-            variable.catalogEntry = entry;
-
-            activeSourcePromises.push(entry.getSourceMeta({
-              silenceErrors: true,
-              cancellable: true
-            }).then(updateVariableType.bind(self, variable)));
-          }));
-        } else {
-          updateVariableType(variable, {
-            type: 'text'
-          });
-        }
-      });
-    });
-    self.statement = ko.computed(function () {
-      var statement = self.isSqlDialect() ? (self.selectedStatement() ? self.selectedStatement() : (self.positionStatement() !== null ? self.positionStatement().statement : self.statement_raw())) : self.statement_raw();
-      var variables = self.variables().reduce(function (variables, variable) {
-        variables[variable.name()] = variable;
-        return variables;
-      }, {});
-      if (self.variables().length) {
-        var variablesString = self.variables().map(function(variable) { return variable.name(); }).join("|");
-        statement = statement.replace(RegExp("([^\\\\])?\\$" + (self.hasCurlyBracketParameters() ? "{(" : "(") + variablesString + ")(=[^}]*)?" + (self.hasCurlyBracketParameters() ? "}" : ""), "g"), function(match, p1, p2){
-          var variable = variables[p2];
-          var pad = variable.type() == 'datetime-local' && variable.value().length == 16 ? ':00' : ''; // Chrome drops the seconds from the timestamp when it's at 0 second.
-          var value = variable.value();
-          return p1 + (value !== undefined && value !== null ? value + pad : variable.meta.placeholder && variable.meta.placeholder());
-        });
-      }
-      return statement;
-    });
-
-    self.result = new Result(snippet, snippet.result);
-    if (! self.result.hasSomeResults()) {
-      self.currentQueryTab('queryHistory');
-    }
-    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);
-    var defaultShowLogs = true;
-    if (vm.editorMode() && $.totalStorage('hue.editor.showLogs')) {
-      defaultShowLogs = $.totalStorage('hue.editor.showLogs');
-    }
-    self.showLogs = ko.observable(typeof snippet.showLogs !== "undefined" && snippet.showLogs != null ? snippet.showLogs : defaultShowLogs);
-    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.executeNextTimeout = -1;
-    var refreshTimeouts = {};
-    self.onDdlExecute = function () {
-      if (self.result.handle() && self.result.handle().has_more_statements) {
-        window.clearTimeout(self.executeNextTimeout);
-        self.executeNextTimeout = setTimeout(function () {
-          self.execute(true); // Execute next, need to wait as we disabled fast click
-        }, 1000);
-      }
-      if (self.lastExecutedStatement() && /CREATE|DROP/i.test(self.lastExecutedStatement().firstToken)) {
-        var match = self.statement().match(/(?:CREATE|DROP)\s+TABLE\s+(?:IF\s+(?:NOT\s+)?EXISTS\s+)?(?:`([^`]+)`|([^;\s]+))\..*/i);
-        var path = [];
-        if (match) {
-          path.push(match[1] || match[2]); // group 1 backticked db name, group 2 regular db name
-        } else {
-          match = self.statement().match(/(?:CREATE|DROP)\s+(?:DATABASE|SCHEMA)\s+(?:IF\s+(?:NOT\s+)?EXISTS\s+)?(?:`([^`]+)`|([^;\s]+))/i);
-          if (match) {
-            path.push(match[1] || match[2]); // group 1 backticked db name, group 2 regular db name
-          } else if (self.database()) {
-            path.push(self.database());
-          }
-        }
-
-        if (path.length) {
-          window.clearTimeout(refreshTimeouts[path.join('.')]);
-          refreshTimeouts[path.join('.')] = window.setTimeout(function () {
-            ignoreNextAssistDatabaseUpdate = true;
-            dataCatalog.getEntry({ sourceType: self.type(), namespace: self.namespace(), compute: self.compute(), path: path }).done(function (entry) {
-              entry.clearCache({ invalidate: 'invalidate', cascade: true, silenceErrors: true });
-            });
-          }, 5000);
-        }
-      }
-    };
-
-    self.progress.subscribe(function (val) {
-      $(document).trigger("progress", {data: val, snippet: self});
-    });
-
-    self.showGrid.subscribe(function (val) {
-      if (val) {
-        self.showChart(false);
-        huePubSub.publish('editor.grid.shown', self);
-      }
-    });
-
-    function prepopulateChart() {
-      var type = self.chartType();
-      hueAnalytics.log('notebook', 'chart/' + type);
-
-      if (type === ko.HUE_CHARTS.TYPES.MAP && self.result.cleanedNumericMeta().length >= 2) {
-        if ((self.chartX() === null || typeof self.chartX() === 'undefined')) {
-          var name = self.result.cleanedNumericMeta()[0].name;
-          self.result.cleanedNumericMeta().forEach(function (fld) {
-            if (fld.name.toLowerCase().indexOf('lat') > -1 || fld.name.toLowerCase().indexOf('ltd') > -1) {
-              name = fld.name;
-            }
-          });
-          self.chartX(name);
-        }
-        if ((self.chartYSingle() === null || typeof self.chartYSingle() === 'undefined')) {
-          var name = self.result.cleanedNumericMeta()[1].name;
-          self.result.cleanedNumericMeta().forEach(function (fld) {
-            if (fld.name.toLowerCase().indexOf('lon') > -1 || fld.name.toLowerCase().indexOf('lng') > -1) {
-              name = fld.name;
-            }
-          });
-          self.chartYSingle(name);
-        }
-        return;
-      }
-
-      if ((self.chartX() === null || typeof self.chartX() === 'undefined') && (type == ko.HUE_CHARTS.TYPES.BARCHART || type == ko.HUE_CHARTS.TYPES.PIECHART || type == ko.HUE_CHARTS.TYPES.GRADIENTMAP) && self.result.cleanedStringMeta().length >= 1) {
-        self.chartX(self.result.cleanedStringMeta()[0].name);
-      }
-
-      if (self.result.cleanedNumericMeta().length > 0) {
-        if (self.chartYMulti().length === 0 && (type === ko.HUE_CHARTS.TYPES.BARCHART || type === ko.HUE_CHARTS.TYPES.LINECHART)) {
-          self.chartYMulti.push(self.result.cleanedNumericMeta()[Math.min(self.result.cleanedNumericMeta().length - 1, 1)].name);
-        } else if ((self.chartYSingle() === null || typeof self.chartYSingle() === 'undefined') && (type === ko.HUE_CHARTS.TYPES.PIECHART || type === ko.HUE_CHARTS.TYPES.MAP || type === ko.HUE_CHARTS.TYPES.GRADIENTMAP || type === ko.HUE_CHARTS.TYPES.SCATTERCHART || (type === ko.HUE_CHARTS.TYPES.BARCHART && self.chartXPivot() !== null))) {
-          if (self.chartYMulti().length === 0) {
-            self.chartYSingle(self.result.cleanedNumericMeta()[Math.min(self.result.cleanedNumericMeta().length - 1, 1)].name);
-          }
-          else {
-            self.chartYSingle(self.chartYMulti()[0]);
-          }
-        }
-      }
-    }
-
-    self.showChart.subscribe(function (val) {
-      if (val) {
-        self.showGrid(false);
-        self.isResultSettingsVisible(true);
-        $(document).trigger("forceChartDraw", self);
-        huePubSub.publish('editor.chart.shown', self);
-        prepopulateChart();
-      }
-    });
-    self.showLogs.subscribe(function (val) {
-      huePubSub.publish('redraw.fixed.headers');
-      if (val) {
-        self.getLogs();
-      }
-      if (vm.editorMode()) {
-        $.totalStorage('hue.editor.showLogs', val);
-      }
-    });
-
-    self.isLoading = ko.computed(function () {
-      return self.status() == "loading";
-    });
-
-    self.resultsKlass = ko.computed(function () {
-      return "results " + self.type();
-    });
-
-    self.errorsKlass = ko.computed(function () {
-      return self.resultsKlass() + " alert alert-error";
-    });
-
-    self.is_redacted = ko.observable(typeof snippet.is_redacted != "undefined" && snippet.is_redacted != null ? snippet.is_redacted : false);
-
-    self.chartType = ko.observable(typeof snippet.chartType != "undefined" && snippet.chartType != null ? snippet.chartType : ko.HUE_CHARTS.TYPES.BARCHART);
-    self.chartType.subscribe(prepopulateChart);
-    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.chartTimelineType = ko.observable(typeof snippet.chartTimelineType != "undefined" && snippet.chartTimelineType != null ? snippet.chartTimelineType : "bar");
-    self.chartLimits = ko.observableArray([5, 10, 25, 50, 100]);
-    self.chartLimit = ko.observable(typeof snippet.chartLimit != "undefined" && snippet.chartLimit != null ? snippet.chartLimit : null);
-    self.chartLimit.extend({notify: 'always'});
-    self.chartX = ko.observable(typeof snippet.chartX != "undefined" && snippet.chartX != null ? snippet.chartX : null);
-    self.chartX.extend({notify: 'always'});
-    self.chartXPivot = ko.observable(typeof snippet.chartXPivot != "undefined" && snippet.chartXPivot != null ? snippet.chartXPivot : null);
-    self.chartXPivot.extend({notify: 'always'});
-    self.chartXPivot.subscribe(prepopulateChart);
-    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.chartMapType = ko.observable(typeof snippet.chartMapType != "undefined" && snippet.chartMapType != null ? snippet.chartMapType : 'marker');
-    self.chartMapLabel = ko.observable(typeof snippet.chartMapLabel != "undefined" && snippet.chartMapLabel != null ? snippet.chartMapLabel : null);
-    self.chartMapHeat = ko.observable(typeof snippet.chartMapHeat != "undefined" && snippet.chartMapHeat != null ? snippet.chartMapHeat : null);
-    self.hideStacked = ko.computed(function() {
-      return self.chartYMulti().length <= 1;
-    });
-
-    self.hasDataForChart = ko.computed(function () {
-      if (self.chartType() == ko.HUE_CHARTS.TYPES.BARCHART || self.chartType() == ko.HUE_CHARTS.TYPES.LINECHART || self.chartType() == ko.HUE_CHARTS.TYPES.TIMELINECHART) {
-        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) {
-      self.chartX.notifySubscribers();
-      self.chartX.valueHasMutated();
-    });
-
-    self.chartType.subscribe(function (val) {
-      $(document).trigger("forceChartDraw", self);
-    });
-
-    self.previousChartOptions = {};
-
-    function guessMetaField(field) {
-      var _fld = null;
-      if (field) {
-        if (self.result.cleanedMeta().length > 0) {
-          self.result.cleanedMeta().forEach(function (fld) {
-            if (fld.name.toLowerCase() === field.toLowerCase() || field.toLowerCase() === fld.name.toLowerCase()) {
-              _fld = fld.name;
-            }
-          });
-        }
-      }
-      return _fld;
-    }
-
-    function guessMetaFields(fields) {
-      var _fields = [];
-      if (fields) {
-        fields.forEach(function (fld) {
-          var _field = guessMetaField(fld);
-          if (_field) {
-            _fields.push(_field);
-          }
-        });
-      }
-      return _fields;
-    }
-
-    self.result.meta.subscribe(function (newValue) {
-      self.chartLimit(self.previousChartOptions.chartLimit);
-      self.chartX(guessMetaField(self.previousChartOptions.chartX));
-      self.chartXPivot(self.previousChartOptions.chartXPivot);
-      self.chartYSingle(guessMetaField(self.previousChartOptions.chartYSingle));
-      self.chartMapType(self.previousChartOptions.chartMapType);
-      self.chartMapLabel(guessMetaField(self.previousChartOptions.chartMapLabel));
-      self.chartMapHeat(self.previousChartOptions.chartMapHeat);
-      self.chartYMulti(guessMetaFields(self.previousChartOptions.chartYMulti) || []);
-      self.chartSorting(self.previousChartOptions.chartSorting);
-      self.chartScatterGroup(self.previousChartOptions.chartScatterGroup);
-      self.chartScatterSize(self.previousChartOptions.chartScatterSize);
-      self.chartScope(self.previousChartOptions.chartScope);
-      self.chartTimelineType(self.previousChartOptions.chartTimelineType);
-    });
-
-    self.isResultSettingsVisible = ko.observable(typeof snippet.isResultSettingsVisible != "undefined" && snippet.isResultSettingsVisible != null ? snippet.isResultSettingsVisible : false);
-    self.toggleResultSettings = function () {
-      self.isResultSettingsVisible(!self.isResultSettingsVisible());
-    };
-    self.isResultSettingsVisible.subscribe(function(){
-      $(document).trigger("toggleResultSettings", self);
-    });
-
-    self.settingsVisible = ko.observable(typeof snippet.settingsVisible != "undefined" && snippet.settingsVisible != null ? snippet.settingsVisible : false);
-    self.saveResultsModalVisible = ko.observable(false);
-
-    self.checkStatusTimeout = null;
-
-    self.getContext = function() {
-      return {
-        id: self.id,
-        type: self.type,
-        status: self.status,
-        statementType: self.statementType,
-        statement: self.statement,
-        aceCursorPosition: self.aceCursorPosition,
-        statementPath: self.statementPath,
-        associatedDocumentUuid: self.associatedDocumentUuid,
-        properties: self.properties,
-        result: self.result.getContext(),
-        database: self.database,
-        compute: self.compute(),
-        wasBatchExecuted: self.wasBatchExecuted()
-      };
-    };
-
-    self.complexity = ko.observable();
-    self.hasComplexity = ko.pureComputed(function () {
-      return self.complexity() && Object.keys(self.complexity()).length > 0;
-    });
-    self.hasRisks = ko.pureComputed(function () {
-      return self.hasComplexity() && self.complexity()['hints'] && self.complexity()['hints'].length > 0;
-    });
-    self.topRisk = ko.pureComputed(function () {
-      if (self.hasRisks()) {
-        return self.complexity()['hints'][0];
-      } else {
-        return null;
-      }
-    });
-
-    self.suggestion = ko.observable('');
-    self.hasSuggestion = ko.observable(null);
-
-    self.compatibilityCheckRunning = ko.observable(false);
-
-    self.compatibilitySourcePlatforms = [];
-    Object.keys(COMPATIBILITY_SOURCE_PLATFORMS).forEach(function (key) {
-      self.compatibilitySourcePlatforms.push(COMPATIBILITY_SOURCE_PLATFORMS[key]);
-    });
-
-    self.compatibilitySourcePlatform = ko.observable(COMPATIBILITY_SOURCE_PLATFORMS[self.type()]);
-    self.compatibilitySourcePlatform.subscribe(function(newValue) {
-      if (newValue && newValue.value !== self.type()) {
-        self.hasSuggestion(null);
-        self.compatibilityTargetPlatform(COMPATIBILITY_TARGET_PLATFORMS[self.type()]);
-        self.queryCompatibility();
-      }
-    });
-
-    self.compatibilityTargetPlatforms = [];
-    Object.keys(COMPATIBILITY_TARGET_PLATFORMS).forEach(function (key) {
-      self.compatibilityTargetPlatforms.push(COMPATIBILITY_TARGET_PLATFORMS[key]);
-    });
-    self.compatibilityTargetPlatform = ko.observable(COMPATIBILITY_TARGET_PLATFORMS[self.type()]);
-
-    self.showOptimizer = ko.observable(self.getApiHelper().getFromTotalStorage('editor', 'show.optimizer', false));
-    self.showOptimizer.subscribe(function (newValue) {
-      if (newValue !== null) {
-        self.getApiHelper().setInTotalStorage('editor', 'show.optimizer', newValue);
-      }
-    });
-
-    if (HAS_OPTIMIZER && ! vm.isNotificationManager()) {
-      var lastComplexityRequest;
-      var lastCheckedComplexityStatement;
-      var knownResponses = [];
-
-      self.delayedStatement = ko.pureComputed(self.statement).extend({ rateLimit: { method: "notifyWhenChangesStop", timeout: 2000 } });
-
-      var handleRiskResponse = function(data) {
-        if (data.status == 0) {
-          self.hasSuggestion('');
-          self.complexity(data.query_complexity);
-        } else {
-          self.hasSuggestion('error');
-          self.complexity({'hints': []});
-        }
-        huePubSub.publish('editor.active.risks', {
-          editor: self.ace(),
-          risks: self.complexity() || {}
-        });
-        lastCheckedComplexityStatement = self.statement();
-      };
-
-      var clearActiveRisks = function () {
-        if (self.hasSuggestion() !== null && typeof self.hasSuggestion() !== 'undefined') {
-          self.hasSuggestion(null);
-        }
-
-        if (self.suggestion() !== '') {
-          self.suggestion('');
-        }
-
-        if (self.complexity() !== {}) {
-          self.complexity(undefined);
-          huePubSub.publish('editor.active.risks', {
-            editor: self.ace(),
-            risks: {}
-          });
-        }
-      };
-
-      self.positionStatement.subscribe(function (newStatement) {
-        if (newStatement) {
-          var hash = newStatement.statement.hashCode();
-          var unknownResponse = knownResponses.every(function (knownResponse) {
-            if (knownResponse.hash === hash) {
-              handleRiskResponse(knownResponse.data);
-              return false;
-            }
-            return true;
-          });
-          if (unknownResponse) {
-            clearActiveRisks();
-          }
-        }
-      });
-
-      self.checkComplexity = function () {
-        if (!self.inFocus() || lastCheckedComplexityStatement === self.statement()) {
-          return;
-        }
-
-        // The syntaxError property is only set if the syntax checker is active and has found an
-        // error, see AceLocationHandler.
-        if (self.positionStatement() && self.positionStatement().syntaxError) {
-          return;
-        }
-
-        self.getApiHelper().cancelActiveRequest(lastComplexityRequest);
-
-        hueAnalytics.log('notebook', 'get_query_risk');
-        clearActiveRisks();
-
-        var changeSubscription = self.statement.subscribe(function () {
-          changeSubscription.dispose();
-          self.getApiHelper().cancelActiveRequest(lastComplexityRequest);
-        });
-
-        var hash = self.statement().hashCode();
-
-        var unknownResponse = knownResponses.every(function (knownResponse) {
-          if (knownResponse.hash === hash) {
-            handleRiskResponse(knownResponse.data);
-            return false;
-          }
-          return true;
-        });
-        if (unknownResponse) {
-          lastComplexityRequest = $.ajax({
-            type: 'POST',
-            url: '/notebook/api/optimizer/statement/risk',
-            timeout: 30000, // 30 seconds
-            data: {
-              notebook: ko.mapping.toJSON(notebook.getContext()),
-              snippet: ko.mapping.toJSON(self.getContext())
-            },
-            success: function (data) {
-              knownResponses.unshift({
-                hash: hash,
-                data: data
-              });
-              if (knownResponses.length > 50) {
-                knownResponses.pop();
-              }
-              handleRiskResponse(data);
-            },
-            always: function(data) {
-              changeSubscription.dispose();
-            }
-          });
-        }
-      };
-
-      if (self.type() === 'hive' || self.type() === 'impala') {
-        if (self.statement_raw()) {
-          window.setTimeout(function(){
-            self.checkComplexity();
-          }, 2000);
-        }
-        self.delayedStatement.subscribe(function () {
-          self.checkComplexity();
-        });
-      }
-    }
-
-    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');
-        if (data.message) {
-          self.errors.push({message: data.message, help: null, line: null, col: null});
-          huePubSub.publish('editor.snippet.result.normal', self);
-        }
-      } else if (data.status == -4) { // Operation timed out
-        notebook.retryModalCancel = function () {
-          self.status('failed');
-          huePubSub.publish('hide.retry.modal');
-        };
-        notebook.retryModalConfirm = function () {
-          if (callback) {
-            callback();
-          }
-          huePubSub.publish('hide.retry.modal');
-        };
-        huePubSub.publish('show.retry.modal');
-      } 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);
-        if (match) {
-          var errorLine = parseInt(match[1]);
-          var errorCol;
-          if (typeof match[3] !== 'undefined') {
-            errorCol = parseInt(match[3]);
-          }
-          if (self.positionStatement()) {
-            if (errorCol && errorLine === 1) {
-              errorCol += self.positionStatement().location.first_column;
-            }
-            errorLine += self.positionStatement().location.first_line - 1;
-          }
-
-          self.errors.push({
-            message: data.message.replace(match[0], 'line ' + errorLine + (errorCol !== null ? ':' + errorCol : '')),
-            help: null,
-            line: errorLine - 1,
-            col: errorCol
-          })
-        } else {
-          self.errors.push({
-            message: data.message,
-            help: data.help,
-            line: null,
-            col: null
-          });
-        }
-      } else {
-        $(document).trigger("error", data.message);
-        self.status('failed');
-      }
-    };
-
-    self.wasBatchExecuted = ko.observable(typeof snippet.wasBatchExecuted != "undefined" && snippet.wasBatchExecuted != null ? snippet.wasBatchExecuted : false);
-    self.isReady = ko.computed(function() {
-      return (self.statementType() == 'text' && (
-          (self.isSqlDialect() && self.statement() !== '') ||
-          (['jar', 'java', 'spark2', 'distcp'].indexOf(self.type()) == -1 && self.statement() !== '') ||
-          (['jar', 'java'].indexOf(self.type()) != -1 && (self.properties().app_jar() != '' && self.properties().class() != '')) ||
-          (['spark2'].indexOf(self.type()) != -1 && self.properties().jars().length > 0) ||
-          (['shell'].indexOf(self.type()) != -1 && self.properties().command_path().length > 0) ||
-          (['mapreduce'].indexOf(self.type()) != -1 && self.properties().app_jar().length > 0) ||
-          (['distcp'].indexOf(self.type()) != -1 && self.properties().source_path().length > 0 && self.properties().destination_path().length > 0))) ||
-        (self.statementType() == 'file' && self.statementPath().length > 0) ||
-        (self.statementType() == 'document' && self.associatedDocumentUuid() && self.associatedDocumentUuid().length > 0);
-    });
-    self.lastExecuted = ko.observable(typeof snippet.lastExecuted != "undefined" && snippet.lastExecuted != null ? snippet.lastExecuted : 0);
-    self.lastAceSelectionRowOffset = ko.observable(snippet.lastAceSelectionRowOffset || 0);
-
-    self.executingBlockingOperation = null; // A ExecuteStatement()
-    self.showLongOperationWarning = ko.observable(false);
-    self.showLongOperationWarning.subscribe(function(newValue) {
-      if (newValue) {
-        hueAnalytics.convert('editor', 'showLongOperationWarning');
-      }
-    });
-
-    var longOperationTimeout = -1;
-
-    function startLongOperationTimeout() {
-      longOperationTimeout = window.setTimeout(function () {
-        self.showLongOperationWarning(true);
-      }, 2000);
-    }
-
-    function stopLongOperationTimeout() {
-      window.clearTimeout(longOperationTimeout);
-      self.showLongOperationWarning(false);
-    }
-
-    self.lastExecutedStatements = undefined;
-    self.lastExecutedSelectionRange = undefined;
-
-    self.execute = function (automaticallyTriggered) {
-      if (!automaticallyTriggered && self.ace()) {
-        var selectionRange = self.ace().getSelectionRange();
-
-        if (self.lastExecutedSelectionRange &&
-          (selectionRange.start.row !== selectionRange.end.row && selectionRange.start.column !== selectionRange.end.column) &&
-          (selectionRange.start.row !== self.lastExecutedSelectionRange.start.row ||
-            selectionRange.start.column !== self.lastExecutedSelectionRange.start.column ||
-            selectionRange.end.row !== self.lastExecutedSelectionRange.end.row ||
-            selectionRange.end.column !== self.lastExecutedSelectionRange.end.column)) {
-         // Manual execute and there is a selection that is different from the last execute
-          self.result.cancelBatchExecution();
-        }
-        self.lastExecutedSelectionRange = selectionRange;
-      }
-
-      if (self.isCanceling()) {
-        return;
-      }
-      var now = (new Date()).getTime();
-      if (now - self.lastExecuted() < 1000 || ! self.isReady()) {
-        return; // Prevent fast clicks
-      }
-
-      if (!automaticallyTriggered) {
-        // Do not cancel statements that are parts of a set of steps to execute (e.g. import). Only cancel statements as requested by user
-        if (self.status() === 'running' || self.status() === 'loading') {
-          self.cancel(); // TODO: Wait for cancel to finish
-        } else {
-          self.result.clear()
-        }
-      }
-
-      if (self.type() === 'impala') {
-        self.showExecutionAnalysis(false);
-        huePubSub.publish('editor.clear.execution.analysis');
-      }
-
-      self.status('running');
-      self.statusForButtons('executing');
-
-      if (self.isSqlDialect()) {
-        huePubSub.publish('editor.refresh.statement.locations', self);
-      }
-
-      self.lastExecutedStatements = self.statement();
-
-      if (self.ace()) {
-        huePubSub.publish('ace.set.autoexpand', { autoExpand: false, snippet: self });
-        var selectionRange = self.ace().getSelectionRange();
-        self.lastAceSelectionRowOffset(Math.min(selectionRange.start.row, selectionRange.end.row));
-      }
-
-      self.previousChartOptions = vm._getPreviousChartOptions(self);
-      $(document).trigger("executeStarted", {vm: vm, snippet: self});
-      self.lastExecuted(now);
-      $(".jHueNotify").remove();
-      hueAnalytics.log('notebook', 'execute/' + self.type());
-
-      notebook.forceHistoryInitialHeight(true);
-
-      if (self.result.handle()) {
-        self.close();
-      }
-
-      self.errors([]);
-      huePubSub.publish('editor.clear.highlighted.errors', self.ace());
-      self.result.clear();
-      self.progress(0);
-      self.jobs([]);
-      self.result.logs('');
-      self.result.statement_range ({
-        start: {
-          row: 0,
-          column: 0
-        },
-        end: {
-          row: 0,
-          column: 0
-        }
-      });
-      notebook.historyCurrentPage(1);
-
-      // TODO: rename startLongOperationTimeout to startBlockingOperationTimeout
-      // TODO: stop blocking operation UI if there is one
-      // TODO: offer to stop blocking submit or fetch operation UI if there is one (add a new call to function for cancelBlockingOperation)
-      // TODO: stop current blocking operation if there is one
-      // TODO: handle jquery.dataTables.1.8.2.min.js:150 Uncaught TypeError: Cannot read property 'asSorting' of undefined on some cancels
-      // TODO: we should cancel blocking operation when leaving notebook (similar to unload())
-      // TODO: we should test when we go back to a query history of a blocking operation that we left
-      startLongOperationTimeout();
-
-      self.currentQueryTab('queryHistory');
-
-      self.executingBlockingOperation = $.post("/notebook/api/execute/" + self.type(), {
-        notebook: vm.editorMode() ? ko.mapping.toJSON(notebook, NOTEBOOK_MAPPING) : ko.mapping.toJSON(notebook.getContext()),
-        snippet: ko.mapping.toJSON(self.getContext())
-      }, function (data) {
-        try {
-          if (self.isSqlDialect() && data && data.handle) {
-            self.lastExecutedStatement(sqlStatementsParser.parse(data.handle.statement)[0]);
-          } else {
-            self.lastExecutedStatement(null);
-          }
-        } catch (e) {
-          self.lastExecutedStatement(null);
-        }
-        self.statusForButtons('executed');
-        huePubSub.publish('ace.set.autoexpand', { autoExpand: true, snippet: self });
-        stopLongOperationTimeout();
-
-        if (vm.editorMode() && data.history_id) {
-          if (! vm.isNotificationManager()) {
-            var url = '/notebook/editor' + (vm.isMobile() ? '_m' : '') + '?editor=' + data.history_id;
-            if (vm.isHue4()){
-              url = vm.URLS.hue4 + '?editor=' + data.history_id;
-            }
-            vm.changeURL(url);
-          }
-          notebook.id(data.history_id);
-          notebook.uuid(data.history_uuid);
-          notebook.isHistory(true);
-          notebook.parentSavedQueryUuid(data.history_parent_uuid);
-        }
-
-        if (data.status === 0) {
-          self.result.handle(data.handle);
-          self.result.hasResultset(data.handle.has_result_set);
-          if (data.handle.sync) {
-            self.loadData(data.result, 100);
-            self.status('available');
-            self.progress(100);
-            self.result.endTime(new Date());
-          } else {
-            if (! notebook.unloaded()) {
-              self.checkStatus();
-            }
-          }
-          if (vm.isOptimizerEnabled()) {
-            huePubSub.publish('editor.upload.query', data.history_id);
-          }
-        } else {
-          self._ajaxError(data, self.execute);
-          notebook.isExecutingAll(false);
-        }
-
-        if (data.handle) {
-          if (vm.editorMode()) {
-            if (vm.isNotificationManager()) { // Update task status
-              var tasks = $.grep(notebook.history(), function(row) { return row.uuid() == notebook.uuid()});
-              if (tasks.length === 1) {
-                tasks[0].status(self.status());
-                self.result.logs(data.message);
-              }
-            } else {
-              notebook.history.unshift(
-                notebook._makeHistoryRecord(
-                  url,
-                  data.handle.statement,
-                  self.lastExecuted(),
-                  self.status(),
-                  notebook.name(),
-                  notebook.uuid()
-                )
-              );
-            }
-          }
-
-          if (data.handle.statements_count != null) {
-            self.result.statements_count(data.handle.statements_count);
-            self.result.statement_id(data.handle.statement_id);
-            self.result.previous_statement_hash(data.previous_statement_hash);
-
-            if (data.handle.statements_count > 1 && data.handle.start != null && data.handle.end != null) {
-              self.result.statement_range({
-                start: data.handle.start,
-                end: data.handle.end
-              });
-            }
-          }
-        }
-      }).fail(function (xhr, textStatus, errorThrown) {
-        if (self.statusForButtons() != 'canceled' && xhr.status !== 502) { // No error when manually canceled
-          $(document).trigger("error", xhr.responseText);
-        }
-        self.status('failed');
-        self.statusForButtons('executed');
-      }).always(function () {
-        self.executingBlockingOperation = null;
-      });
-    };
-
-    self.reexecute = function () {
-      self.result.cancelBatchExecution();
-      self.execute();
-    };
-
-    self.formatEnabled = ko.pureComputed(function () {
-      return self.statement_raw && self.statement_raw() != null && self.statement_raw().length < 400000; // ie: 5000 lines at 80 chars per line
-    });
-
-    self.format = function () {
-      if (self.isSqlDialect()) {
-        self.getApiHelper().formatSql({ statements: self.ace().getSelectedText() != '' ? self.ace().getSelectedText() : self.statement_raw() }).done(function (data) {
-          if (data.status == 0) {
-            if (self.ace().getSelectedText() != '') {
-              self.ace().session.replace(self.ace().session.selection.getRange(), data.formatted_statements);
-            } else {
-              self.statement_raw(data.formatted_statements);
-              self.ace().setValue(self.statement_raw(), 1);
-            }
-          } else {
-            self._ajaxError(data);
-          }
-        });
-      }
-      hueAnalytics.log('notebook', 'format');
-    };
-
-    self.clear = function () {
-      hueAnalytics.log('notebook', 'clear');
-      self.ace().setValue('', 1);
-      self.result.clear();
-      self.status('ready');
-    };
-
-    self.explain = function () {
-      hueAnalytics.log('notebook', 'explain');
-
-      if (self.statement() == '' || self.status() == 'running' || self.status() === 'loading') {
-        return;
-      }
-
-      self.result.explanation('');
-      self.errors([]);
-      self.progress(0);
-      self.status('ready');
-
-      $.post("/notebook/api/explain", {
-        notebook: ko.mapping.toJSON(notebook.getContext()),
-        snippet: ko.mapping.toJSON(self.getContext())
-      }, function(data) {
-        if (data.status == 0) {
-          self.currentQueryTab('queryExplain');
-          self.result.fetchedOnce(true);
-          self.result.explanation(data.explanation);
-        } else {
-          self._ajaxError(data);
-        }
-      });
-    };
-
-    var lastCompatibilityRequest;
-
-    self.checkCompatibility = function () {
-      self.hasSuggestion(null);
-      self.compatibilitySourcePlatform(COMPATIBILITY_SOURCE_PLATFORMS[self.type()]);
-      self.compatibilityTargetPlatform(COMPATIBILITY_TARGET_PLATFORMS[self.type() === 'hive' ? 'impala' : 'hive']);
-      self.queryCompatibility();
-    };
-
-    self.queryCompatibility = function (targetPlatform) {
-      self.getApiHelper().cancelActiveRequest(lastCompatibilityRequest);
-
-      hueAnalytics.log('notebook', 'compatibility');
-      self.compatibilityCheckRunning(targetPlatform != self.type());
-      self.hasSuggestion(null);
-      var positionStatement = self.positionStatement();
-
-      lastCompatibilityRequest = $.post("/notebook/api/optimizer/statement/compatibility", {
-        notebook: ko.mapping.toJSON(notebook.getContext()),
-        snippet: ko.mapping.toJSON(self.getContext()),
-        sourcePlatform: self.compatibilitySourcePlatform().value,
-        targetPlatform: self.compatibilityTargetPlatform().value
-      }, function (data) {
-        if (data.status === 0) {
-          self.aceErrorsHolder([]);
-          self.aceWarningsHolder([]);
-          self.suggestion(ko.mapping.fromJS(data.query_compatibility));
-          if (self.suggestion().queryError && self.suggestion().queryError.errorString()) {
-            var match = ERROR_REGEX.exec(self.suggestion().queryError.errorString());
-            var line = null;
-            if (match) {
-              if (positionStatement) {
-                line = positionStatement.location.first_line + parseInt(match[1]) + 1;
-              } else {
-                line = parseInt(match[1]) - 1
-              }
-            }
-            self.aceWarningsHolder.push({
-              message: self.suggestion().queryError.errorString(),
-              line: line,
-              col: match === null ? null : (typeof match[3] !== 'undefined' ? parseInt(match[3]) : null)
-            });
-            self.status('with-optimizer-report');
-          }
-          if (self.suggestion().parseError()) {
-            var match = ERROR_REGEX.exec(self.suggestion().parseError());
-            self.aceErrorsHolder.push({
-              message: self.suggestion().parseError(),
-              line: match === null ? null : parseInt(match[1]) - 1,
-              col: match === null ? null : (typeof match[3] !== 'undefined' ? parseInt(match[3]) : null)
-            });
-            self.status('with-optimizer-report');
-          }
-          self.showOptimizer(true);
-          self.hasSuggestion(true);
-        } else {
-          $(document).trigger("error", data.message);
-        }
-      }).fail(function (xhr, textStatus, errorThrown) {
-        if (xhr.status !== 502) {
-          $(document).trigger("error", xhr.responseText);
-        }
-      }).always(function () {
-        self.compatibilityCheckRunning(false);
-      });
-    };
-
-    self.fetchResult = function (rows, startOver) {
-      if (typeof startOver == "undefined") {
-        startOver = true;
-      }
-      self.fetchResultData(rows, startOver);
-      //self.fetchResultMetadata(rows);
-    };
-
-    self.isFetchingData = false;
-
-    self.fetchExecutionAnalysis = function() {
-      if (self.type() === 'impala') {
-        // TODO: Use real query ID
-        huePubSub.publish('editor.update.execution.analysis', {
-          analysisPossible: true,
-          compute: self.compute(),
-          queryId: notebook.getContext().id(),
-          name: self.jobs()[0] && self.jobs()[0].name
-        });
-      } else {
-        huePubSub.publish('editor.update.execution.analysis', {
-          analysisPossible: false
-        });
-      }
-    };
-
-    self.fetchResultData = function (rows, startOver) {
-      if (! self.isFetchingData) {
-        if (self.status() === 'available') {
-          startLongOperationTimeout();
-          self.isFetchingData = true;
-          hueAnalytics.log('notebook', 'fetchResult/' + 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) {
-            stopLongOperationTimeout();
-            data = JSON.bigdataParse(data);
-            if (data.status === 0) {
-              self.showExecutionAnalysis(true);
-              self.loadData(data.result, rows);
-            } else {
-              self._ajaxError(data, function() {self.isFetchingData = false; self.fetchResultData(rows, startOver); });
-              $(document).trigger("renderDataError", {snippet: self});
-            }
-          }, 'text').fail(function (xhr, textStatus, errorThrown) {
-            if (xhr.status !== 502) {
-              $(document).trigger("error", xhr.responseText);
-            }
-          }).always(function () {
-            self.isFetchingData = false;
-          });
-        } else {
-          huePubSub.publish('editor.snippet.result.normal', self);
-        }
-      }
-    };
-
-    self.loadData = function (result, rows) {
-      rows -= result.data.length;
-
-      if (result.data.length > 0) {
-        self.currentQueryTab('queryResults');
-      }
-
-      var _initialIndex = self.result.data().length;
-      var _tempData = [];
-      $.each(result.data, function (index, row) {
-        row.unshift(_initialIndex + index + 1);
-        self.result.data.push(row);
-        _tempData.push(row);
-      });
-
-      if (self.result.rows() == null || (self.result.rows() + '').indexOf('+') != -1) {
-        self.result.rows(self.result.data().length + (result.has_more ? '+' : ''));
-      }
-
-      self.result.images(typeof result.images != "undefined" && result.images != null ? result.images : []);
-
-      huePubSub.publish('editor.render.data', {data: _tempData, snippet: self, initial: _initialIndex == 0});
-
-      if (! self.result.fetchedOnce()) {
-        result.meta.unshift({type: "INT_TYPE", name: "", comment: null});
-        self.result.meta(result.meta);
-        self.result.type(result.type);
-        self.result.fetchedOnce(true);
-      }
-
-      self.result.meta().forEach(function (meta) {
-        if ($.inArray(meta.type, ['TINYINT_TYPE', 'SMALLINT_TYPE', 'INT_TYPE', 'BIGINT_TYPE', 'FLOAT_TYPE', 'DOUBLE_TYPE', 'DECIMAL_TYPE']) > -1) {
-          meta.cssClass = 'sort-numeric';
-        } else if ($.inArray(meta.type, ['TIMESTAMP_TYPE', 'DATE_TYPE', 'DATETIME_TYPE']) > -1) {
-          meta.cssClass = 'sort-date';
-        } else {
-          meta.cssClass = 'sort-string';
-        }
-      });
-
-      self.result.hasMore(result.has_more);
-
-      if (result.has_more && rows > 0) {
-        setTimeout(function () {
-          self.fetchResultData(rows, false);
-        }, 500);
-      } else if (! vm.editorMode() && ! notebook.isPresentationMode() && notebook.snippets()[notebook.snippets().length - 1] == self) {
-        notebook.newSnippet();
-      }
-    };
-
-    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);
-        }
-      }).fail(function (xhr, textStatus, errorThrown) {
-        if (xhr.status !== 502) {
-          $(document).trigger("error", xhr.responseText);
-        }
-        self.status('failed');
-      });
-    };
-
-    self.fetchResultSize = function(n, query_id) {
-      $.post("/notebook/api/fetch_result_size", {
-        notebook: ko.mapping.toJSON(notebook.getContext()),
-        snippet: ko.mapping.toJSON(self.getContext())
-      }, function (data) {
-        if (query_id == notebook.id()) { // If still on the same result
-          if (data.status == 0) {
-            if (data.result.rows != null) {
-              self.result.rows(data.result.rows);
-            } else if (self.type() == 'impala' && n > 0) {
-              setTimeout(function () {
-                self.fetchResultSize(n - 1, query_id);
-              }, 1000);
-            }
-          } else if (data.status == 5) {
-            // No supported yet for this snippet
-          } else {
-            //$(document).trigger("error", data.message);
-          }
-        }
-      }).fail(function (xhr, textStatus, errorThrown) {
-        //$(document).trigger("error", xhr.responseText);
-      });
-    };
-
-    self.checkStatus = function () {
-      $.post("/notebook/api/check_status", {
-        notebook: ko.mapping.toJSON(notebook.getContext()),
-        snippet: ko.mapping.toJSON(self.getContext())
-      }, function (data) {
-        if (self.statusForButtons() == 'canceling' || self.status() == 'canceled') {
-          // Query was canceled in the meantime, do nothing
-        } else {
-          self.result.endTime(new Date());
-
-          if (data.status === 0) {
-            self.status(data.query_status.status);
-
-            if (self.status() == 'running' || self.status() == 'starting' || self.status() == 'waiting') {
-              var delay = self.result.executionTime() > 45000 ? 5000 : 1000; // 5s if more than 45s
-              if (! notebook.unloaded()) {
-                self.checkStatusTimeout = setTimeout(self.checkStatus, delay);
-              }
-            } else if (self.status() === 'available') {
-              self.fetchResult(100);
-              self.progress(100);
-              if (self.isSqlDialect()) {
-                if (self.result.handle().has_result_set) {
-                  var _query_id = notebook.id();
-                  setTimeout(function () { // Delay until we get IMPALA-5555
-                    self.fetchResultSize(10, _query_id);
-                  }, 2000);
-                  self.checkDdlNotification(); // DDL CTAS with Impala
-                } else if (self.lastExecutedStatement()) {
-                  self.checkDdlNotification();
-                } else {
-                  self.onDdlExecute();
-                }
-              }
-              if (notebook.isExecutingAll()) {
-                notebook.executingAllIndex(notebook.executingAllIndex() + 1);
-                if (notebook.executingAllIndex() < notebook.snippets().length) {
-                  notebook.snippets()[notebook.executingAllIndex()].execute();
-                } else {
-                  notebook.isExecutingAll(false);
-                }
-              }
-              if (! self.result.handle().has_more_statements && vm.successUrl()) {
-                window.location.href = vm.successUrl(); // Not used anymore in Hue 4
-              }
-            } else if (self.status() === 'success') {
-              self.progress(99);
-            }
-          } else if (data.status === -3) {
-            self.status('expired');
-            notebook.isExecutingAll(false);
-          } else {
-            self._ajaxError(data);
-            notebook.isExecutingAll(false);
-          }
-          self.getLogs(); // Need to execute at the end, because updating the status impacts log progress results
-        }
-      }).fail(function (xhr, textStatus, errorThrown) {
-        if (xhr.status !== 502) {
-          $(document).trigger("error", xhr.responseText || textStatus);
-        }
-        self.status('failed');
-        notebook.isExecutingAll(false);
-      });
-    };
-
-    self.checkDdlNotification = function() {
-      if (self.lastExecutedStatement() && /ALTER|CREATE|DELETE|DROP|GRANT|INSERT|LOAD|SET|TRUNCATE|UPDATE|UPSERT|USE/i.test(self.lastExecutedStatement().firstToken)) {
-        self.onDdlExecute();
-      } else {
-        window.clearTimeout(self.executeNextTimeout);
-      }
-    };
-
-    self.isCanceling = ko.observable(false);
-
-    self.cancel = function () {
-      window.clearTimeout(self.executeNextTimeout);
-      self.isCanceling(true);
-      if (self.checkStatusTimeout != null) {
-        clearTimeout(self.checkStatusTimeout);
-        self.checkStatusTimeout = null;
-      }
-      hueAnalytics.log('notebook', 'cancel');
-
-      if (self.executingBlockingOperation != null) {
-        self.executingBlockingOperation.abort();
-        self.executingBlockingOperation = null;
-      }
-
-      if ($.isEmptyObject(self.result.handle())) { // Query was not even submitted yet
-        self.statusForButtons('canceled');
-        self.status('failed');
-        self.isCanceling(false);
-        notebook.isExecutingAll(false);
-      } else {
-        self.statusForButtons('canceling');
-        $.post("/notebook/api/cancel_statement", {
-          notebook: ko.mapping.toJSON(notebook.getContext()),
-          snippet: ko.mapping.toJSON(self.getContext())
-        }, function (data) {
-          self.statusForButtons('canceled');
-          if (data.status == 0) {
-            self.status('canceled');
-            notebook.isExecutingAll(false);
-          } else {
-            self._ajaxError(data);
-          }
-        }).fail(function (xhr, textStatus, errorThrown) {
-          if (xhr.status !== 502) {
-            $(document).trigger("error", xhr.responseText);
-          }
-          self.statusForButtons('canceled');
-          self.status('failed');
-          notebook.isExecutingAll(false);
-        }).always(function (){
-          self.isCanceling(false);
-        });
-      }
-    };
-
-    self.close = function () {
-      if (self.checkStatusTimeout != null) {
-        clearTimeout(self.checkStatusTimeout);
-        self.checkStatusTimeout = null;
-      }
-
-      $.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);
-        }
-      }).fail(function (xhr, textStatus, errorThrown) {
-        if (xhr.status !== 502) {
-          // $(document).trigger("error", xhr.responseText);
-        }
-        // self.status('failed'); // Can conflict with slow close and new query execution
-      });
-    };
-
-    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, { ignore: ['percentJob'] }),
-        full_log: self.result.logs
-      }, 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 (data.logs && (
-                oldLogs === "" || (self.wasBatchExecuted() && data.logs.indexOf('Unable to locate') == -1) || data.isFullLogs)) {
-              self.result.logs(data.logs);
-            } else {
-              self.result.logs(oldLogs + "\n" + data.logs);
-            }
-          }
-
-          self.jobs().forEach(function(job){
-            if (typeof job.percentJob === 'undefined'){
-              job.percentJob = ko.observable(-1);
-            }
-          });
-
-          if (data.jobs && data.jobs.length > 0) {
-            data.jobs.forEach(function (job) {
-              var _found = ko.utils.arrayFilter(self.jobs(), function (item) {
-                return item.name === job.name;
-              });
-              if (_found.length === 0) {
-                if (typeof job.percentJob === 'undefined') {
-                  job.percentJob = ko.observable(-1);
-                } else {
-                  job.percentJob = ko.observable(job.percentJob);
-                }
-                self.jobs.push(job);
-              } else if (typeof job.percentJob !== 'undefined') {
-                for (var i = 0; i < _found.length; i++) {
-                  _found[i].percentJob(job.percentJob)
-                }
-              }
-            });
-            self.jobs().forEach(function (job) {
-              var _found = ko.utils.arrayFilter(self.jobs(), function (item) {
-                return item.name === job.name;
-              });
-              if (_found.length === 0) {
-                self.jobs.remove(job);
-              }
-            });
-          }
-          if (self.status() == 'running') { // Maybe the query finished or failed in the meantime
-            self.progress(Math.max(data.progress, self.progress()))
-          };
-        } else {
-          self._ajaxError(data);
-        }
-      }).fail(function (xhr, textStatus, errorThrown) {
-        if (xhr.status !== 502) {
-          $(document).trigger("error", xhr.responseText || textStatus);
-        }
-        self.status('failed');
-      });
-    };
-
-    self.uploadQueryHistory = function (n) {
-      hueAnalytics.log('notebook', 'upload_query_history');
-
-      $.post("/metadata/api/optimizer/upload/history", {
-        n: typeof n != "undefined" ? n : null,
-        sourcePlatform: self.type()
-      }, function(data) {
-        if (data.status == 0) {
-          $(document).trigger("info", data.upload_history[self.type()].count + " queries uploaded successfully. Processing them...");
-          self.watchUploadStatus(data.upload_history[self.type()].status.workloadId);
-        } else {
-          $(document).trigger("error", data.message);
-        }
-      });
-    };
-
-    self.uploadQuery = function (query_id) {
-      $.post("/metadata/api/optimizer/upload/query", {
-        query_id: query_id,
-        sourcePlatform: self.type()
-      });
-    };
-
-    self.uploadTableStats = function (options) {
-      hueAnalytics.log('notebook', 'load_table_stats');
-      if (options.showProgress) {
-        $(document).trigger("info", "Preparing table data...");
-      }
-
-      $.post("/metadata/api/optimizer/upload/table_stats", {
-        db_tables: ko.mapping.toJSON($.map(options.activeTables, function(table) {
-          return table.databaseName + '.' + table.tableName;
-        })),
-        sourcePlatform: ko.mapping.toJSON(self.type()),
-        with_ddl: ko.mapping.toJSON(true),
-        with_table_stats: ko.mapping.toJSON(true),
-        with_columns_stats: ko.mapping.toJSON(true)
-      }, function(data) {
-        if (data.status == 0) {
-          if (options.showProgress) {
-            $(document).trigger("info", $.map(options.activeTables, function(table) { return table.tableName; }) + " stats sent to analyse");
-          }
-          if (data.upload_table_ddl && options.showProgress) { // With showProgress only currently as can be very slow
-            self.watchUploadStatus(data.upload_table_ddl.status.workloadId, options.showProgress);
-          }
-        } else {
-          if (options.showProgress) {
-            $(document).trigger("error", data.message);
-          }
-        }
-      }).always(function () {
-        if (options.callback) {
-          options.callback();
-        }
-      });
-    };
-
-    self.watchUploadStatus = function (workloadId, showProgress) {
-      $.post("/metadata/api/optimizer/upload/status", {
-        workloadId: workloadId
-      }, function(data) {
-        if (data.status == 0) {
-          if (showProgress) {
-            $(document).trigger("info", "Query processing: " + data.upload_status.status.state);
-          }
-          if (['WAITING', 'IN_PROGRESS'].indexOf(data.upload_status.status.state) != -1) {
-            window.setTimeout(function () {
-              self.watchUploadStatus(workloadId);
-            }, 2000);
-          } else {
-            if (showProgress) {
-              $(document).trigger("warn", data.upload_status.status.statusMsg + (data.upload_status.status.failedQueries > 0 ? '. ' + data.upload_status.status.failQueryDetails.map(function(query) { return query.error; }) : ''));
-            }
-          }
-        } else {
-          if (showProgress) {
-            $(document).trigger("error", data.message);
-          }
-        }
-      });
-    };
-
-    self.getSimilarQueries = function () {
-      hueAnalytics.log('notebook', 'get_query_similarity');
-
-      $.post("/notebook/api/optimizer/statement/similarity", {
-        notebook: ko.mapping.toJSON(notebook.getContext()),
-        snippet: ko.mapping.toJSON(self.getContext()),
-        sourcePlatform: self.type()
-      }, function(data) {
-        if (data.status == 0) {
-          console.log(data.statement_similarity);
-        } else {
-          $(document).trigger("error", data.message);
-        }
-      });
-    };
-
-    self.autocompleter = new Autocompleter({
-      snippet: self,
-      user: vm.user,
-      optEnabled: false,
-      timeout: vm.autocompleteTimeout,
-      useNewAutocompleter: vm.useNewAutocompleter
-    });
-
-    self.init = function () {
-      if ((self.status() == 'running' || self.status() == 'available') && notebook.isHistory()) {
-        self.checkStatus();
-      } else if (self.status() == 'loading') {
-        self.status('failed');
-        self.progress(0);
-        self.jobs([]);
-      } else if (self.status() == 'ready-execute') {
-        self.execute();
-      }
-    };
-
-    self.onKeydownInVariable = function (context, e) {
-      if ((e.ctrlKey || e.metaKey) && e.which === 13) { // Ctrl-enter
-        self.ace().commands.commands['execute'].exec();
-      } else if ((e.ctrlKey || e.metaKey) && e.which === 83) { // Ctrl-s
-        self.ace().commands.commands['save'].exec();
-        e.preventDefault(); // Prevent browser page save dialog
-      }
-      return true;
-    };
-  };
-
-  var Session = function(vm, session) {
-    var self = this;
-    ko.mapping.fromJS(session, {}, self);
-
-    self.selectedSessionProperty = ko.observable('');
-
-    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.key] = 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 : hueUtils.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.type = ko.observable(typeof notebook.type != "undefined" && notebook.type != null ? notebook.type : 'notebook');
-    self.initialType = self.type().replace('query-', '');
-    self.coordinatorUuid = ko.observable(typeof notebook.coordinatorUuid != "undefined" && notebook.coordinatorUuid != null ? notebook.coordinatorUuid : null);
-    self.isHistory = ko.observable(typeof notebook.is_history != "undefined" && notebook.is_history != null ? notebook.is_history : false);
-    self.isManaged = ko.observable(typeof notebook.isManaged != "undefined" && notebook.isManaged != null ? notebook.isManaged : false);
-    self.parentSavedQueryUuid = ko.observable(typeof notebook.parentSavedQueryUuid != "undefined" && notebook.parentSavedQueryUuid != null ? notebook.parentSavedQueryUuid : null); // History parent
-    self.isSaved = ko.observable(typeof notebook.isSaved != "undefined" && notebook.isSaved != null ? notebook.isSaved : false);
-    self.canWrite = ko.observable(typeof notebook.can_write != "undefined" && notebook.can_write != null ? notebook.can_write : true);
-    self.onSuccessUrl = ko.observable(typeof notebook.onSuccessUrl != "undefined" && notebook.onSuccessUrl != null ? notebook.onSuccessUrl : null);
-    self.pubSubUrl = ko.observable(typeof notebook.pubSubUrl != "undefined" && notebook.pubSubUrl != null ? notebook.pubSubUrl : null);
-    self.isPresentationModeDefault = ko.observable(typeof notebook.isPresentationModeDefault != "undefined" && notebook.isPresentationModeDefault != null ? notebook.isPresentationModeDefault : false);
-    self.isPresentationMode = ko.observable(false);
-    self.isPresentationModeInitialized = ko.observable(false);
-    self.isPresentationMode.subscribe(function(newValue) {
-      if (! newValue) {
-        self.cancelExecutingAll();
-      }
-      huePubSub.publish('editor.presentation.operate.toggle', newValue); // Problem with headers / row numbers redraw on full screen results
-      vm.togglePresentationMode();
-      if (newValue) {
-        hueAnalytics.convert('editor', 'presentation');
-      }
-    });
-    self.presentationSnippets = ko.observable({});
-    self.isHidingCode = ko.observable(typeof notebook.isHidingCode != "undefined" && notebook.isHidingCode != null ? notebook.isHidingCode : false);
-
-    self.snippets = ko.observableArray();
-    self.selectedSnippet = ko.observable(vm.editorType()); // Aka selectedSnippetType
-    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.directoryUuid = ko.observable(typeof notebook.directoryUuid != "undefined" && notebook.directoryUuid != null ? notebook.directoryUuid : null);
-    self.dependents = ko.mapping.fromJS(typeof notebook.dependents != "undefined" && notebook.dependents != null ? notebook.dependents : []);
-    self.dependentsCoordinator = ko.computed(function() {
-      return $.grep(self.dependents(), function(doc) { return doc.type() == 'oozie-coordinator2' && doc.is_managed() == true ;})
-    });
-    if (self.dependentsCoordinator().length > 0 && ! self.coordinatorUuid()) {
-      self.coordinatorUuid(self.dependentsCoordinator()[0].uuid());
-    }
-    self.history = ko.observableArray(vm.selectedNotebook() && vm.selectedNotebook().history().length > 0 && vm.selectedNotebook().history()[0].type == self.type() ? vm.selectedNotebook().history() : []);
-    self.history.subscribe(function(val) {
-      if (self.id() == null && val.length == 0 && self.historyFilter() === '' && ! vm.isNotificationManager()) {
-        self.snippets()[0].currentQueryTab((typeof IS_EMBEDDED !== 'undefined' && IS_EMBEDDED) ? 'queryHistory' : 'savedQueries');
-      }
-    });
-    self.historyFilter = ko.observable('');
-    self.historyFilterVisible = ko.observable(false);
-    self.historyFilter.extend({ rateLimit: { method: "notifyWhenChangesStop", timeout: 900 } });
-    self.historyFilter.subscribe(function(val){
-      if (self.historyCurrentPage() != 1) {
-        self.historyCurrentPage(1);
-      } else {
-        self.fetchHistory();
-      }
-    });
-    self.loadingHistory = ko.observable(self.history().length == 0);
-    self.historyInitialHeight = ko.observable(0).extend({ throttle: 1000 });
-    self.forceHistoryInitialHeight = ko.observable(false);
-    self.historyCurrentPage = ko.observable(vm.selectedNotebook() ? vm.selectedNotebook().historyCurrentPage() : 1);
-    self.historyCurrentPage.subscribe(function(val) {
-      self.fetchHistory();
-    });
-    self.historyTotalPages = ko.observable(vm.selectedNotebook() ? vm.selectedNotebook().historyTotalPages() : 1);
-
-    self.schedulerViewModel = null;
-    self.schedulerViewModelIsLoaded = ko.observable(false);
-    self.schedulerViewerViewModel = ko.observable();
-    self.isBatchable = ko.computed(function() {
-      return self.snippets().length > 0
-        && $.grep(self.snippets(), function (snippet) {
-          return snippet.isBatchable();
-        }).length == self.snippets().length;
-    });
-
-    self.isExecutingAll = ko.observable(!! notebook.isExecutingAll);
-    self.cancelExecutingAll = function() {
-      var index = self.executingAllIndex();
-      if (self.isExecutingAll() && self.snippets()[index]) {
-        self.snippets()[index].cancel();
-      }
-    };
-    self.executingAllIndex = ko.observable(notebook.executingAllIndex || 0);
-
-    self.retryModalConfirm = null;
-    self.retryModalCancel = null;
-
-    self.avoidClosing = false;
-
-    self.canSave = vm.canSave;
-    self.configuration = ko.observable(typeof notebook.configuration != "undefined" && notebook.configuration != null ? notebook.configuration : null);
-
-
-    self.getSession = function (session_type) {
-      var _s = null;
-      $.each(self.sessions(), function (index, s) {
-        if (s.type() == session_type) {
-          _s = s;
-          return false;
-        }
-      });
-      return _s;
-    };
-
-    self.getSnippets = function(type) {
-      return $.grep(self.snippets(), function (snippet) {
-        return snippet.type() == type;
-      });
-    };
-
-    self.unloaded = ko.observable(false);
-    self.unload = function() {
-      self.unloaded(true);
-      var currentQueries = null;
-      self.snippets().forEach(function(snippet){
-        if (snippet.checkStatusTimeout != null) {
-          clearTimeout(snippet.checkStatusTimeout);
-          snippet.checkStatusTimeout = null;
-        }
-        if (currentQueries == null) {
-          currentQueries = snippet.queries();
-        }
-      });
-      return currentQueries;
-    }
-
-    self.restartSession = function (session, callback) {
-      if (session.restarting()) {
-        return;
-      }
-      session.restarting(true);
-      var snippets = self.getSnippets(session.type());
-
-      $.each(snippets, function(index, snippet) {
-        snippet.status('loading');
-      });
-
-      var sessionJson = ko.mapping.toJSON(session);
-
-      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);
-        });
-      });
-    };
-
-    self.addSession = function (session) {
-      var toRemove = [];
-      $.each(self.sessions(), function (index, s) {
-        if (s.type() == session.type()) {
-          toRemove.push(s);
-        }
-      });
-
-      $.each(toRemove, function (index, s) {
-        self.sessions.remove(s);
-      });
-
-      self.sessions.push(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);
-      }
-
-      _snippet.init();
-      return _snippet;
-    };
-
-    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());
-      }
-
-      var compute = null;
-      $.each(self.getSnippets(session.type()), function(index, snippet) {
-        snippet.status('loading');
-        if (index == 0) {
-          compute = snippet.compute();
-        }
-      });
-
-      var fail = function (message) {
-        $.each(self.getSnippets(session.type()), function(index, snippet) {
-          snippet.status('failed');
-        });
-        $(document).trigger("error", message);
-        if (failCallback) {
-          failCallback();
-        }
-      };
-
-      $.post("/notebook/api/create_session", {
-        notebook: ko.mapping.toJSON(self.getContext()),
-        session: ko.mapping.toJSON(session), // e.g. {'type': 'pyspark', 'properties': [{'name': driverCores', 'value', '2'}]}
-        cluster: ko.mapping.toJSON(compute ? compute : '')
-      }, 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) {
-        if (xhr.status !== 502) {
-          fail(xhr.responseText);
-        }
-      }).always(function() {
-        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.
-      );
-    };
-
-    self.newSnippet = function (type) {
-      if (type) {
-        self.selectedSnippet(type);
-      }
-      var snippet = self.addSnippet({
-        type: self.selectedSnippet(),
-        result: {}
-      });
-
-      window.setTimeout(function () {
-        var lastSnippet = snippet;
-        if (lastSnippet.ace() != null) {
-          lastSnippet.ace().focus();
-        }
-      }, 100);
-
-      hueAnalytics.log('notebook', 'add_snippet/' + (type ? type : self.selectedSnippet()));
-      return snippet;
-    };
-
-    self.newSnippetAbove = function (id) {
-      self.newSnippet();
-      var idx = 0;
-      self.snippets().forEach(function (snippet, cnt) {
-        if (snippet.id() == id) idx = cnt;
-      });
-      self.snippets(self.snippets().move(self.snippets().length - 1, idx));
-    };
-
-    self.getContext = function() {
-     return {
-         id: self.id,
-         uuid: self.uuid,
-         parentSavedQueryUuid: self.parentSavedQueryUuid,
-         isSaved: self.isSaved,
-         sessions: self.sessions,
-         type: self.type,
-         name: self.name
-      };
-    };
-
-    self.save = function (callback) {
-      hueAnalytics.log('notebook', 'save');
-
-      // Remove the result data from the snippets
-      // Also do it for presentation mode
-      var cp = ko.mapping.toJS(self, NOTEBOOK_MAPPING);
-      $.each(cp.snippets.concat(Object.keys(cp.presentationSnippets).map(function(key){
-          return cp.presentationSnippets[key];
-        })), function(index, snippet) {
-        snippet.result.data.length = 0; // snippet.result.clear() does not work for some reason
-        snippet.result.meta.length = 0;
-        snippet.result.logs = '';
-        snippet.result.fetchedOnce = false;
-        snippet.progress = 0; // Remove progress
-        snippet.jobs.length = 0;
-      });
-      if (cp.schedulerViewModel) {
-        cp.schedulerViewModel.availableTimezones = [];
-      }
-      var editorMode = vm.editorMode() || (self.isPresentationMode() && vm.editorType() != 'notebook'); // Editor should not convert to Notebook in presentation mode
-
-      $.post("/notebook/api/notebook/save", {
-        "notebook": ko.mapping.toJSON(cp, NOTEBOOK_MAPPING),
-        "editorMode": editorMode
-      }, function (data) {
-        if (data.status == 0) {
-          self.id(data.id);
-          self.isSaved(true);
-          var wasHistory = self.isHistory();
-          self.isHistory(false);
-          $(document).trigger("info", data.message);
-          if (editorMode) {
-            if (! data.save_as) {
-              var existingQuery = self.snippets()[0].queries().filter(function (item) {
-                return item.uuid() === data.uuid
-              });
-              if (existingQuery.length > 0) {
-                existingQuery[0].name(data.name);
-                existingQuery[0].description(data.description);
-                existingQuery[0].last_modified(data.last_modified);
-              }
-            } else if (self.snippets()[0].queries().length > 0) { // Saved queries tab already loaded
-              self.snippets()[0].queries.unshift(ko.mapping.fromJS(data));
-            }
-
-            if (self.coordinatorUuid() && self.schedulerViewModel) {
-              self.saveScheduler();
-              self.schedulerViewModel.coordinator.refreshParameters();
-            }
-            if (wasHistory || data.save_as) {
-              self.loadScheduler();
-            }
-
-            if (self.snippets()[0].downloadResultViewModel && self.snippets()[0].downloadResultViewModel().saveTarget() === 'dashboard') {
-              huePubSub.publish('open.link', vm.URLS.report + '&uuid=' + data.uuid + '&statement=' + self.snippets()[0].result.handle().statement_id);
-            }
-            else {
-              if (vm.isHue4()){
-                vm.changeURL(vm.URLS.hue4 + '?editor=' + data.id);
-              } else {
-                vm.changeURL('/notebook/editor' + (vm.isMobile() ? '_m' : '') + '?editor=' + data.id);
-              }
-            }
-          } else {
-            if (vm.isHue4()){
-              vm.changeURL(vm.URLS.hue4_notebook + '?notebook=' + data.id);
-            } else {
-              vm.changeURL('/notebook/notebook?notebook=' + data.id);
-            }
-          }
-          if (typeof callback == "function") {
-            callback();
-          }
-        } else {
-          $(document).trigger("error", data.message);
-        }
-      }).fail(function (xhr, textStatus, errorThrown) {
-        if (xhr.status !== 502) {
-          $(document).trigger("error", xhr.responseText);
-        }
-      });
-    };
-
-    self.close = function () {
-      hueAnalytics.log('notebook', 'close');
-      $.post("/notebook/api/notebook/close", {
-        "notebook": ko.mapping.toJSON(self, NOTEBOOK_MAPPING),
-        "editorMode": vm.editorMode()
-      });
-    };
-
-    self.clearResults = function () {
-      $.each(self.snippets(), function (index, snippet) {
-        snippet.result.clear();
-        snippet.status('ready');
-      });
-    };
-
-    self.executeAll = function () {
-      if (self.isExecutingAll() || self.snippets().length === 0) {
-        return;
-      }
-
-      self.isExecutingAll(true);
-      self.executingAllIndex(0);
-
-      self.snippets()[self.executingAllIndex()].execute();
-    };
-
-    self.saveDefaultUserProperties = function (session) {
-      var apiHelper = window.apiHelper;
-      apiHelper.saveConfiguration({
-        app: session.type(),
-        properties: session.properties,
-        userId: vm.userId
-      });
-    };
-
-    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 && data.status != 0 && data.status != -2 && data.message) {
-          $(document).trigger("error", data.message);
-        }
-
-        if (callback) {
-          callback();
-        }
-      }).fail(function (xhr) {
-        if (!silent && xhr.status !== 502) {
-          $(document).trigger("error", xhr.responseText);
-        }
-      });
-    };
-
-    self.fetchHistory = function (callback) {
-      var QUERIES_PER_PAGE = 50;
-      self.loadingHistory(true);
-
-      $.get("/notebook/api/get_history", {
-        doc_type: self.selectedSnippet(),
-        limit: QUERIES_PER_PAGE,
-        page: self.historyCurrentPage(),
-        doc_text: self.historyFilter(),
-        is_notification_manager: vm.isNotificationManager()
-      }, function(data) {
-        var parsedHistory = [];
-        if (data && data.history){
-          data.history.forEach(function(nbk){
-            parsedHistory.push(
-              self._makeHistoryRecord(
-                nbk.absoluteUrl,
-                nbk.data.statement,
-                nbk.data.lastExecuted,
-                nbk.data.status,
-                nbk.name,
-                nbk.uuid
-              )
-            );
-          });
-        }
-        self.history(parsedHistory);
-        self.historyTotalPages(Math.ceil(data.count / QUERIES_PER_PAGE));
-      }).always(function(){
-        self.loadingHistory(false);
-        if (callback) {
-          callback();
-        }
-      });
-    };
-
-    self.prevHistoryPage = function () {
-      if (self.historyCurrentPage() !== 1) {
-        self.historyCurrentPage(self.historyCurrentPage() - 1);
-      }
-    };
-
-    self.nextHistoryPage = function () {
-      if (self.historyCurrentPage() < self.historyTotalPages()) {
-        self.historyCurrentPage(self.historyCurrentPage() + 1);
-      }
-    };
-
-
-    self.updateHistoryFailed = false;
-    self.updateHistory = function (statuses, interval) {
-      var items = $.grep(self.history(), function (item) {
-        return statuses.indexOf(item.status()) != -1;
-      }).slice(0, 25);
-
-      function updateHistoryCall(item) {
-        $.post("/notebook/api/check_status", {
-          notebook: ko.mapping.toJSON({id: item.uuid()}),
-        }).done(function (data) {
-            var status = data.status == -3 ? 'expired' : (data.status == 0 ? data.query_status.status : 'failed');
-            if (status && item.status() != status) {
-              item.status(status);
-            }
-          }).fail(function (xhr) {
-            items = [];
-            self.updateHistoryFailed = true;
-            console.warn('Lost connectivity to the Hue history refresh backend.');
-          }).always(function () {
-            if (items.length > 0) {
-              window.setTimeout(function () {
-                updateHistoryCall(items.pop());
-              }, 1000);
-            } else {
-              if (!self.updateHistoryFailed){
-                window.setTimeout(function () {
-                  self.updateHistory(statuses, interval);
-                }, interval);
-              }
-            }
-          });
-      }
-
-      if (items.length > 0) {
-        updateHistoryCall(items.pop());
-      } else {
-        if (!self.updateHistoryFailed) {
-          window.setTimeout(function () {
-            self.updateHistory(statuses, interval);
-          }, interval);
-        }
-      }
-    };
-
-    self._makeHistoryRecord = function(url, statement, lastExecuted, status, name, uuid) {
-      return ko.mapping.fromJS({
-          url: url,
-          query: statement.substring(0, 1000) + (statement.length > 1000 ? '...' : ''),
-          lastExecuted: lastExecuted,
-          status: status,
-          name: name,
-          uuid: uuid
-      });
-    };
-
-    self.clearHistory = function (type) {
-      hueAnalytics.log('notebook', 'clearHistory');
-      $.post("/notebook/api/clear_history", {
-        notebook: ko.mapping.toJSON(self.getContext()),
-        doc_type: self.selectedSnippet(),
-        is_notification_manager: vm.isNotificationManager(),
-      }, function (data) {
-        self.history.removeAll();
-        if (self.isHistory()) {
-          self.id(null);
-          self.uuid(hueUtils.UUID());
-          if (vm.isHue4()) {
-            vm.changeURL(vm.URLS.hue4 + '?type=' + vm.editorType());
-          }
-          else {
-            vm.changeURL('/notebook/editor' + (vm.isMobile() ? '_m' : '') + '?type=' + vm.editorType());
-          }
-        }
-      }).fail(function (xhr) {
-        if (xhr.status !== 502) {
-          $(document).trigger("error", xhr.responseText);
-        }
-      });
-      $(document).trigger("hideHistoryModal");
-    };
-
-    self.loadScheduler = function () {
-      if (typeof vm.CoordinatorEditorViewModel !== 'undefined' && self.isBatchable()) {
-        var _action;
-        if (self.coordinatorUuid()) {
-          _action = 'edit';
-        } else {
-          _action = 'new';
-        }
-        hueAnalytics.log('notebook', 'schedule/' + _action);
-
-        function getCoordinator() {
-          $.get('/oozie/editor/coordinator/' + _action + '/', {
-            format: 'json',
-            document: self.uuid(),
-            coordinator: self.coordinatorUuid()
-          }, function (data) {
-            if ($("#schedulerEditor").length > 0) {
-              huePubSub.publish('hue4.process.headers', {
-                response: data.layout,
-                callback: function (r) {
-                  $("#schedulerEditor").html(r);
-
-                  self.schedulerViewModel = new vm.CoordinatorEditorViewModel(data.coordinator, data.credentials, data.workflows, data.can_edit);
-
-                  ko.cleanNode($("#schedulerEditor")[0]);
-                  ko.applyBindings(self.schedulerViewModel, $("#schedulerEditor")[0]);
-                  $(document).off("showSubmitPopup");
-                  $(document).on("showSubmitPopup", function (event, data) {
-                    $('.submit-modal-editor').html(data);
-                    $('.submit-modal-editor').modal('show');
-                    $('.submit-modal-editor').on('hidden', function () {
-                      huePubSub.publish('hide.datepicker');
-                    });
-                    var _sel = $('.submit-form .control-group[rel!="popover"]:visible');
-                    if (_sel.length > 0) {
-                      $('.submit-modal-editor .modal-body').height($('.submit-modal-editor .modal-body').height() + 60);
-                    }
-                  });
-
-                  huePubSub.publish('render.jqcron');
-
-                  self.schedulerViewModel.coordinator.properties.cron_advanced.valueHasMutated(); // Update jsCron enabled status
-                  self.schedulerViewModel.coordinator.tracker().markCurrentStateAsClean();
-                  self.schedulerViewModel.isEditing(true);
-
-                  self.schedulerViewModelIsLoaded(true);
-
-                  if (_action == 'new') {
-                    self.schedulerViewModel.coordinator.properties.document(self.uuid()); // Expected for triggering the display
-                  }
-                }
-              });
-
-            }
-          }).fail(function (xhr) {
-            if (xhr.status !== 502) {
-              $(document).trigger("error", xhr.responseText);
-            }
-          });
-        }
-
-        if (!IS_HUE_4) {
-          huePubSub.subscribe('hue4.process.headers', function (opts) {
-            opts.callback(opts.response);
-          }, vm.huePubSubId);
-        }
-
-        getCoordinator();
-      }
-    };
-
-    self.saveScheduler = function() {
-      if (self.isBatchable() && (! self.coordinatorUuid() || self.schedulerViewModel.coordinator.isDirty())) {
-        self.schedulerViewModel.coordinator.isManaged(true);
-        self.schedulerViewModel.coordinator.properties.document(self.uuid());
-        self.schedulerViewModel.save(function(data) {
-          if (! self.coordinatorUuid()) {
-            self.coordinatorUuid(data.uuid);
-            self.save();
-          }
-        });
-      }
-    };
-
-    self.showSubmitPopup = function () {
-      $.get('/oozie/editor/coordinator/submit/' + self.coordinatorUuid(), {
-        format: 'json'
-      }, function (data) {
-        $(document).trigger("showSubmitPopup", data);
-      }).fail(function (xhr, textStatus, errorThrown) {
-        if (xhr.status !== 502) {
-          $(document).trigger("error", xhr.responseText);
-        }
-      });
-    };
-
-    self.viewSchedulerId = ko.observable(typeof notebook.viewSchedulerId != "undefined" && notebook.viewSchedulerId != null ? notebook.viewSchedulerId : '');
-    self.viewSchedulerId.subscribe(function(newVal) {
-      self.save();
-    });
-    self.isSchedulerJobRunning = ko.observable();
-    self.loadingScheduler = ko.observable(false);
-
-
-    // Init
-    if (notebook.snippets) {
-      $.each(notebook.snippets, function (index, snippet) {
-        self.addSnippet(snippet);
-      });
-      if (typeof notebook.presentationSnippets != "undefined" && notebook.presentationSnippets != null) { // Load
-        $.each(notebook.presentationSnippets, function(key, snippet) {
-          snippet.status = 'ready' // Protect from storm of check_statuses
-          var _snippet = new Snippet(vm, self, snippet);
-          _snippet.init();
-          _snippet.previousChartOptions = vm._getPreviousChartOptions(_snippet);
-          self.presentationSnippets()[key] = _snippet;
-        });
-      }
-      if (vm.editorMode() && self.history().length == 0) {
-        self.fetchHistory(function() {
-          self.updateHistory(['starting', 'running'], 30000);
-          self.updateHistory(['available'], 60000 * 5);
-        });
-      }
-    }
-
-    huePubSub.subscribeOnce('assist.db.panel.ready', function () {
-      if (self.type().indexOf('query') === 0) {
-
-        var whenDatabaseAvailable = function (snippet) {
-          huePubSub.publish('assist.set.database', {
-            source: snippet.type(),
-            namespace: snippet.namespace(),
-            name: snippet.database()
-          });
-        };
-
-        var whenNamespaceAvailable = function (snippet) {
-          if (snippet.database()) {
-            whenDatabaseAvailable(snippet);
-          } else {
-            var databaseSub = snippet.database.subscribe(function () {
-              databaseSub.dispose();
-              whenDatabaseAvailable(snippet);
-            })
-          }
-        };
-
-        var whenSnippetAvailable = function (snippet) {
-          if (snippet.namespace()) {
-            whenNamespaceAvailable(snippet);
-          } else {
-            var namespaceSub = snippet.namespace.subscribe(function () {
-              namespaceSub.dispose();
-              whenNamespaceAvailable(snippet);
-            })
-          }
-        };
-
-        if (self.snippets().length === 1) {
-          whenSnippetAvailable(self.snippets()[0]);
-        } else {
-          var snippetsSub = self.snippets.subscribe(function (snippets) {
-            if (snippets.length === 1) {
-              whenSnippetAvailable(snippets[0])
-            }
-            snippetsSub.dispose();
-          })
-        }
-      }
-    }, vm.huePubSubId);
-
-    huePubSub.publish('assist.is.db.panel.ready');
-  };
-
-  function EditorViewModel(editor_id, notebooks, options, CoordinatorEditorViewModel, RunningCoordinatorModel) {
-    var self = this;
-
-    self.URLS = {
-      editor: '/notebook/editor',
-      editorMobile: '/notebook/editor_m',
-      notebook: '/notebook/notebook',
-      hue4: '/hue/editor',
-      hue4_notebook: '/hue/notebook',
-      report: '/hue/dashboard/new_search?engine=report'
-    };
-
-    self.huePubSubId = options.huePubSubId || 'editor';
-    self.user = options.user;
-    self.userId = options.userId;
-    self.suffix = options.suffix;
-    self.isMobile = ko.observable(options.mobile);
-    self.isHue4 = ko.observable(options.hue4);
-    self.isNotificationManager = ko.observable(options.is_notification_manager || false);
-    self.editorType = ko.observable(options.editor_type);
-    self.editorType.subscribe(function(newVal) {
-      self.editorMode(newVal != 'notebook');
-      hueUtils.changeURLParameter('type', newVal);
-      if (self.editorMode()) {
-        self.selectedNotebook().fetchHistory(); // Js error if notebook did not have snippets
-      }
-    });
-    self.preEditorTogglingSnippet = ko.observable();
-    self.toggleEditorMode = function() {
-      var _notebook = self.selectedNotebook();
-      var _newSnippets = [];
-
-      if (self.editorType() != 'notebook') {
-        self.editorType('notebook');
-        self.preEditorTogglingSnippet(_notebook.snippets()[0]);
-        var _variables = _notebook.snippets()[0].variables();
-        var _statementKeys = [];
-        // Split statements
-        _notebook.type('notebook');
-        _notebook.snippets()[0].statementsList().forEach(function (sql_statement) {
-          var _snippet;
-          if (sql_statement.hashCode() in _notebook.presentationSnippets()) {
-            _snippet = _notebook.presentationSnippets()[sql_statement.hashCode()]; // Persist result
-            _snippet.variables(_variables);
-          } else {
-            var _title = [];
-            var _statement = [];
-            sql_statement.trim().split('\n').forEach(function(line) {
-              if (line.trim().startsWith('--') && _statement.length == 0) {
-                _title.push(line.substr(2));
-              } else {
-                _statement.push(line);
-              }
-            });
-            _snippet = new Snippet(self, _notebook, {type: _notebook.initialType, statement_raw: _statement.join('\n'), result: {}, name: _title.join('\n'), variables: ko.mapping.toJS(_variables)}, skipSession=true);
-            _snippet.variables = _notebook.snippets()[0].variables;
-            _snippet.init();
-            _notebook.presentationSnippets()[sql_statement.hashCode()] = _snippet;
-          }
-          _statementKeys.push(sql_statement.hashCode());
-          _newSnippets.push(_snippet);
-        });
-        $.each(_notebook.presentationSnippets(), function(key, statement) { // Dead statements
-          if (! key in _statementKeys) {
-            delete _notebook.presentationSnippets()[key];
-          }
-        });
-      } else {
-        self.editorType(_notebook.initialType);
-        // Revert to one statement
-        _newSnippets.push(self.preEditorTogglingSnippet());
-        _notebook.type('query-' + _notebook.initialType);
-      }
-      _notebook.snippets(_newSnippets);
-      _newSnippets.forEach(function (snippet) {
-        huePubSub.publish('editor.redraw.data', {snippet: snippet});
-      });
-    };
-    self.togglePresentationMode = function() {
-      if (self.selectedNotebook().initialType != 'notebook') {
-        self.toggleEditorMode();
-      }
-    };
-    self.editorTypeTitle = ko.pureComputed(function () {
-      var foundInterpreter = $.grep(options.languages, function (interpreter) {
-        return interpreter.type === self.editorType();
-      });
-      return foundInterpreter.length > 0 ? foundInterpreter[0].name : self.editorType();
-    });
-    self.useNewAutocompleter = options.useNewAutocompleter || false;
-    self.autocompleteTimeout = options.autocompleteTimeout;
-    self.selectedNotebook = ko.observable();
-
-    self.combinedContent = ko.observable();
-    self.isPresentationModeEnabled = ko.pureComputed(function () {
-      return self.selectedNotebook() && self.selectedNotebook().snippets().length === 1 && self.selectedNotebook().snippets()[0].isSqlDialect()
-    });
-    self.isResultFullScreenMode = ko.observable(false);
-    self.isPresentationMode = ko.computed(function() {
-      return self.selectedNotebook() && self.selectedNotebook().isPresentationMode();
-    });
-    self.isHidingCode = ko.computed(function() {
-      return self.selectedNotebook() && self.selectedNotebook().isHidingCode();
-    })
-    self.successUrl = ko.observable(options.success_url); // Deprecated
-    self.isOptimizerEnabled = ko.observable(options.is_optimizer_enabled);
-    self.isNavigatorEnabled = ko.observable(options.is_navigator_enabled);
-
-    self.CoordinatorEditorViewModel = CoordinatorEditorViewModel;
-    self.RunningCoordinatorModel = RunningCoordinatorModel;
-
-    self.canSave = ko.computed(function() {
-      // Saved query or history but history coming from a saved query
-      return self.selectedNotebook() && self.selectedNotebook().canWrite() && (
-          self.selectedNotebook().isSaved() ||
-          (self.selectedNotebook().isHistory() && self.selectedNotebook().parentSavedQueryUuid())
-      );
-    });
-
-    // TODO: Drop the SQL source types from the notebook. They're now set in AssistDbPanel.
-    self.sqlSourceTypes = [];
-    self.availableLanguages = [];
-
-    if (options.languages && options.snippetViewSettings) {
-      $.each(options.languages, function (idx, language) {
-        self.availableLanguages.push({
-          type: language.type,
-          name: language.name,
-          interface: language.interface
-        });
-        var viewSettings = options.snippetViewSettings[language.type];
-        if (viewSettings && viewSettings.sqlDialect) {
-          self.sqlSourceTypes.push({
-            type: language.type,
-            name: language.name
-          })
-        }
-      });
-    }
-
-    var sqlSourceTypes = $.grep(self.sqlSourceTypes, function(language) { return language.type == self.editorType(); });
-    if (sqlSourceTypes.length > 0) {
-      self.activeSqlSourceType = sqlSourceTypes[0].type;
-    } else {
-      self.activeSqlSourceType = null;
-    }
-
-    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" + self.suffix).modal("show");
-    };
-
-    self.isEditing = ko.observable(false);
-    self.isEditing.subscribe(function () {
-      $(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();
-
-    self.removeSnippetConfirmation = ko.observable();
-
-    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" + self.suffix).modal("show");
-      }
-      else {
-        notebook.snippets.remove(snippet);
-        window.setTimeout(function () {
-          $(document).trigger("editorSizeChanged");
-        }, 100);
-      }
-    };
-
-    self.assistAvailable = ko.observable(options.assistAvailable);
-
-    self.assistWithoutStorage = ko.observable(false);
-
-    self.isLeftPanelVisible = ko.observable(window.apiHelper.getFromTotalStorage('assist', 'assist_panel_visible', true));
-    self.isLeftPanelVisible.subscribe(function (val) {
-      if (!self.assistWithoutStorage()){
-        window.apiHelper.setInTotalStorage('assist', 'assist_panel_visible', val);
-      }
-    });
-
-    self.isRightPanelAvailable = ko.observable(options.assistAvailable && HAS_OPTIMIZER);
-    self.isRightPanelVisible = ko.observable(window.apiHelper.getFromTotalStorage('assist', 'right_assist_panel_visible', true));
-    self.isRightPanelVisible.subscribe(function (val) {
-      if (!self.assistWithoutStorage()){
-        window.apiHelper.setInTotalStorage('assist', 'right_assist_panel_visible', val);
-      }
-    });
-
-    var withActiveSnippet = function (callback) {
-      var notebook = self.selectedNotebook();
-      var foundSnippet;
-      if (notebook) {
-        if (notebook.snippets().length === 1) {
-          foundSnippet = notebook.snippets()[0];
-        } else {
-          notebook.snippets().every(function (snippet) {
-            if (snippet.inFocus()) {
-              foundSnippet = snippet;
-              return false;
-            }
-            return true;
-          });
-        }
-      }
-      if (foundSnippet) {
-        callback(foundSnippet);
-      }
-    };
-
-    huePubSub.subscribe('assist.highlight.risk.suggestions', function () {
-      if (self.isRightPanelAvailable() && !self.isRightPanelVisible()) {
-        self.isRightPanelVisible(true);
-      }
-    });
-
-    self.isContextPanelVisible = ko.observable(false);
-    self.isContextPanelVisible.subscribe(function (newValue) {
-      huePubSub.publish('context.panel.visible', newValue);
-    });
-
-    huePubSub.subscribe('context.panel.visible.editor', self.isContextPanelVisible);
-
-    huePubSub.subscribe('get.active.snippet.type', function () {
-      withActiveSnippet(function (activeSnippet) {
-        huePubSub.publish('set.active.snippet.type', activeSnippet.type());
-      })
-    }, self.huePubSubId);
-
-    huePubSub.subscribe('save.snippet.to.file', function() {
-      withActiveSnippet(function (activeSnippet) {
-        var data = {
-          path: activeSnippet.statementPath(),
-          contents: activeSnippet.statement()
-        };
-        var options = {
-          successCallback: function (result) {
-            if (result && result.exists) {
-              $(document).trigger("info", result.path + ' saved successfully.');
-            } else {
-              self._ajaxError(result);
-            }
-          }
-        };
-        window.apiHelper.saveSnippetToFile(data, options);
-      });
-    }, self.huePubSubId);
-
-    huePubSub.subscribe('sql.context.pin', function (contextData) {
-      withActiveSnippet(function (activeSnippet) {
-        contextData.tabId = 'context' + activeSnippet.pinnedContextTabs().length;
-        activeSnippet.pinnedContextTabs.push(contextData);
-        activeSnippet.currentQueryTab(contextData.tabId);
-      });
-    }, self.huePubSubId);
-
-    huePubSub.subscribe("assist.database.set", function (databaseDef) {
-      withActiveSnippet(function (activeSnippet) {
-        activeSnippet.handleAssistSelection(databaseDef);
-      });
-    }, self.huePubSubId);
-
-    huePubSub.subscribe("assist.database.selected", function (databaseDef) {
-      withActiveSnippet(function (activeSnippet) {
-        activeSnippet.handleAssistSelection(databaseDef);
-      });
-    }, self.huePubSubId);
-
-    self.availableSnippets = ko.mapping.fromJS(options.languages);
-
-    self.editorMode = ko.observable(options.mode == 'editor');
-
-    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.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.changeURL = function (url) {
-      if (!self.isNotificationManager()) {
-        hueUtils.changeURL(url);
-      }
-    };
-
-    self.init = function () {
-      if (editor_id) {
-        self.openNotebook(editor_id);
-      } else if (window.location.getParameter('editor') !== ''){
-        self.openNotebook(window.location.getParameter('editor'));
-      } else if (notebooks.length > 0) {
-        self.loadNotebook(notebooks[0]); // Old way of loading json for /browse
-      } else if (window.location.getParameter('type') !== '') {
-        self.newNotebook(window.location.getParameter('type'));
-      } else {
-        self.newNotebook();
-      }
-    };
-
-    self.loadNotebook = function (notebook, queryTab) {
-      var currentQueries;
-      if (self.selectedNotebook() != null) {
-        currentQueries = self.selectedNotebook().unload();
-      }
-
-      var notebook = new Notebook(self, notebook);
-
-      if (notebook.snippets().length > 0) {
-        huePubSub.publish('detach.scrolls', notebook.snippets()[0]);
-        notebook.selectedSnippet(notebook.snippets()[notebook.snippets().length - 1].type());
-        if (currentQueries != null) {
-          notebook.snippets()[0].queries(currentQueries);
-        }
-        notebook.snippets().forEach(function (snippet) {
-          snippet.aceAutoExpand = false;
-          snippet.statement_raw.valueHasMutated();
-          if (snippet.result.handle().statements_count > 1 && snippet.result.handle().start != null && snippet.result.handle().end != null) {
-            var aceLineOffset = snippet.result.handle().aceLineOffset || 0;
-            snippet.result.statement_range({
-              start: { row: snippet.result.handle().start.row + aceLineOffset, column: snippet.result.handle().start.column },
-              end: { row: snippet.result.handle().end.row + aceLineOffset, column: snippet.result.handle().end.column }
-            });
-            snippet.result.statement_range.valueHasMutated();
-          }
-
-          snippet.previousChartOptions = self._getPreviousChartOptions(snippet);
-        });
-
-        if (notebook.snippets()[0].result.data().length > 0) {
-          $(document).trigger("redrawResults");
-        } else if (queryTab) {
-          notebook.snippets()[0].currentQueryTab(queryTab);
-        }
-
-        if (notebook.isSaved()) {
-          notebook.snippets()[0].currentQueryTab('savedQueries');
-          if (notebook.snippets()[0].queries().length === 0) {
-            notebook.snippets()[0].fetchQueries(); // Subscribe not updating yet
-          }
-        }
-      }
-
-      self.selectedNotebook(notebook);
-      huePubSub.publish('check.job.browser');
-      huePubSub.publish('recalculate.name.description.width');
-    };
-
-    self._getPreviousChartOptions = function(snippet) {
-      return {
-          chartLimit: typeof snippet.chartLimit() !== "undefined" ? snippet.chartLimit() : snippet.previousChartOptions.chartLimit,
-          chartX: typeof snippet.chartX() !== "undefined" ? snippet.chartX() : snippet.previousChartOptions.chartX,
-          chartXPivot: typeof snippet.chartXPivot() !== "undefined" ? snippet.chartXPivot() : snippet.previousChartOptions.chartXPivot,
-          chartYSingle: typeof snippet.chartYSingle() !== "undefined" ? snippet.chartYSingle() : snippet.previousChartOptions.chartYSingle,
-          chartMapType: typeof snippet.chartMapType() !== "undefined" ? snippet.chartMapType() : snippet.previousChartOptions.chartMapType,
-          chartMapLabel: typeof snippet.chartMapLabel() !== "undefined" ? snippet.chartMapLabel() : snippet.previousChartOptions.chartMapLabel,
-          chartMapHeat: typeof snippet.chartMapHeat() !== "undefined" ? snippet.chartMapHeat() : snippet.previousChartOptions.chartMapHeat,
-          chartYMulti: typeof snippet.chartYMulti() !== "undefined" ? snippet.chartYMulti() : snippet.previousChartOptions.chartYMulti,
-          chartScope: typeof snippet.chartScope() !== "undefined" ? snippet.chartScope() : snippet.previousChartOptions.chartScope,
-          chartTimelineType: typeof snippet.chartTimelineType() !== "undefined" ? snippet.chartTimelineType() : snippet.previousChartOptions.chartTimelineType,
-          chartSorting: typeof snippet.chartSorting() !== "undefined" ? snippet.chartSorting() : snippet.previousChartOptions.chartSorting,
-          chartScatterGroup: typeof snippet.chartScatterGroup() !== "undefined" ? snippet.chartScatterGroup() : snippet.previousChartOptions.chartScatterGroup,
-          chartScatterSize: typeof snippet.chartScatterSize() !== "undefined" ? snippet.chartScatterSize() : snippet.previousChartOptions.chartScatterSize
-        };
-    };
-
-    self.openNotebook = function (uuid, queryTab, skipUrlChange, callback) {
-      var deferredOpen = new $.Deferred();
-      $.get('/desktop/api2/doc/', {
-        uuid: uuid,
-        data: true,
-        dependencies: true
-      }, function (data) {
-        if (data.status == 0) {
-          data.data.dependents = data.dependents;
-          data.data.can_write = data.user_perms.can_write;
-          var notebook = data.data;
-          self.loadNotebook(notebook, queryTab);
-          if (typeof skipUrlChange === 'undefined' && ! self.isNotificationManager()){
-            if (self.editorMode()) {
-              self.editorType(data.document.type.substring('query-'.length));
-              huePubSub.publish('active.snippet.type.changed', self.editorType());
-              if (self.isHue4()){
-                self.changeURL(self.URLS.hue4 + '?editor=' + data.document.id + '&type=' + self.editorType());
-              } else {
-                self.changeURL((self.isMobile() ? self.URLS.editorMobile : self.URLS.editor) + '?editor=' + data.document.id + '&type=' + self.editorType());
-              }
-            } else {
-              if (self.isHue4()){
-                self.changeURL(self.URLS.hue4_notebook + '?notebook=' + data.document.id);
-              } else {
-                self.changeURL(self.URLS.notebook + '?notebook=' + data.document.id);
-              }
-            }
-          }
-          if (typeof callback !== 'undefined'){
-            callback();
-          }
-          deferredOpen.resolve();
-        }
-        else {
-          $(document).trigger("error", data.message);
-          deferredOpen.reject();
-          self.newNotebook();
-        }
-      });
-      return deferredOpen.promise();
-    };
-
-    self.newNotebook = function (editorType, callback, queryTab) {
-      huePubSub.publish('active.snippet.type.changed', editorType);
-      $.post("/notebook/api/create_notebook", {
-        type: editorType || options.editor_type,
-        directory_uuid: window.location.getParameter('directory_uuid')
-      }, function (data) {
-        self.loadNotebook(data.notebook);
-        if (self.editorMode() && !self.isNotificationManager()) {
-          var snippet = self.selectedNotebook().newSnippet(self.editorType());
-          if (queryTab && ['queryHistory', 'savedQueries', 'queryBuilderTab'].indexOf(queryTab) > -1) {
-            snippet.currentQueryTab(queryTab);
-          }
-          huePubSub.publish('detach.scrolls', self.selectedNotebook().snippets()[0]);
-          if (window.location.getParameter('type') === '') {
-            hueUtils.changeURLParameter('type', self.editorType());
-          }
-          huePubSub.publish('active.snippet.type.changed', editorType);
-        }
-
-        if (typeof callback !== 'undefined' && callback !== null){
-          callback();
-        }
-      });
-    };
-
-    self.saveNotebook = function () {
-      self.selectedNotebook().save();
-    };
-
-    self.saveAsNotebook = function () {
-      self.selectedNotebook().id(null);
-      self.selectedNotebook().uuid(hueUtils.UUID());
-      self.selectedNotebook().parentSavedQueryUuid(null);
-      self.selectedNotebook().save(function () {
-        huePubSub.publish('assist.document.refresh');
-      });
-    };
-
-    self.showContextPopover = function (field, event) {
-      var $source = $(event.target && event.target.nodeName !== 'A' ? event.target.parentElement : event.target);
-      var offset = $source.offset();
-      huePubSub.publish('context.popover.show', {
-        data: {
-          type: 'catalogEntry',
-          catalogEntry: field.catalogEntry
-        },
-        onSampleClick: field.value,
-        showInAssistEnabled: true,
-        sourceType: self.editorType(),
-        orientation: 'bottom',
-        defaultDatabase: 'default',
-        pinEnabled: false,
-        source: {
-          element: event.target,
-          left: offset.left,
-          top: offset.top - 3,
-          right: offset.left + $source.width() + 1,
-          bottom: offset.top + $source.height() - 3
-        }
-      });
-    };
-  }
-
-  return EditorViewModel;
-})();

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

@@ -25,6 +25,7 @@ from desktop.conf import IS_EMBEDDED
 from metadata.conf import has_optimizer, OPTIMIZER
 from notebook.conf import ENABLE_QUERY_BUILDER, ENABLE_QUERY_SCHEDULING, ENABLE_BATCH_EXECUTE, ENABLE_EXTERNAL_STATEMENT, ENABLE_PRESENTATION
 from desktop.auth.backend import is_admin
+from webpack_loader.templatetags.webpack_loader import render_bundle
 %>
 
 <%def name="includes(is_embeddable=False, suffix='')">
@@ -41,14 +42,13 @@ from desktop.auth.backend import is_admin
 <link rel="stylesheet" href="${ static('desktop/css/bootstrap-medium-editor.css') }">
 <link rel="stylesheet" href="${ static('desktop/ext/css/bootstrap-datepicker.min.css') }">
 
-<script src="${ static('desktop/ext/js/bootstrap-datepicker.min.js') }" type="text/javascript" charset="utf-8"></script>
+${ render_bundle('vendors~notebook') | n,unicode }
+${ render_bundle('notebook') | n,unicode }
+
 % if not is_embeddable:
 <script src="${ static('desktop/js/share2.vm.js') }"></script>
 % endif
 
-<script src="${ static('desktop/ext/js/clipboard.min.js') }"></script>
-<script src="${ static('notebook/js/notebook.ko.js') }"></script>
-
 % if ENABLE_QUERY_SCHEDULING.get():
 <script src="${ static('oozie/js/coordinator-editor.ko.js') }"></script>
 <script src="${ static('oozie/js/list-oozie-coordinator.ko.js') }"></script>
@@ -61,7 +61,6 @@ from desktop.auth.backend import is_admin
 <script src="${ static('desktop/js/sqlAutocompleter3.js') }"></script>
 <script src="${ static('desktop/js/hdfsAutocompleter.js') }"></script>
 <script src="${ static('desktop/js/autocompleter.js') }"></script>
-<script src="${ static('desktop/js/jquery.hdfstree.js') }"></script>
 <script src="${ static('desktop/ext/js/markdown.min.js') }"></script>
 <script src="${ static('desktop/ext/js/jquery/plugins/jquery.hotkeys.js') }"></script>
 <script src="${ static('desktop/ext/js/jquery/plugins/jquery.mousewheel.min.js') }"></script>

+ 0 - 1
desktop/libs/notebook/src/notebook/templates/editor_m.mako

@@ -167,7 +167,6 @@ ${ commonheader_m(editor_type, editor_type, user, request, "68px") | n,unicode }
 <script src="${ static('desktop/js/sqlAutocompleter3.js') }"></script>
 <script src="${ static('desktop/js/hdfsAutocompleter.js') }"></script>
 <script src="${ static('desktop/js/autocompleter.js') }"></script>
-<script src="${ static('notebook/js/notebook.ko.js') }"></script>
 
 ${ assist.assistJSModels() }
 

+ 1 - 0
ext/thirdparty/README.md

@@ -191,6 +191,7 @@ Frontend third party dependencies (some checked in and some via npm)
 |TouchSwipe|1.6.18|MIT|https://github.com/mattbryson/TouchSwipe-Jquery-Plugin|
 |tzdetect.js|?|MIT|https://github.com/Canop/tzdetect.js|
 |Webpack|4.29.0|MIT|https://www.npmjs.com/package/webpack|
+|Webpack Bundle Analyzer|3.0.4|MIT|https://www.npmjs.com/package/webpack-bundle-analyzer|
 |Webpack Bundel Tracker|0.4.2-beta|MIT|https://www.npmjs.com/package/webpack-bundle-tracker|
 |Webpack cli|3.2.1|MIT|https://www.npmjs.com/package/webpack-cli|
 |Webpack clean plugin|1.0.1|MIT|https://www.npmjs.com/package/clean-webpack-plugin|

+ 518 - 0
package-lock.json

@@ -1180,6 +1180,16 @@
       "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
       "dev": true
     },
+    "accepts": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
+      "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
+      "dev": true,
+      "requires": {
+        "mime-types": "~2.1.18",
+        "negotiator": "0.6.1"
+      }
+    },
     "acorn": {
       "version": "6.0.5",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.5.tgz",
@@ -1406,6 +1416,12 @@
       "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
       "dev": true
     },
+    "array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
+      "dev": true
+    },
     "array-union": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
@@ -1514,6 +1530,12 @@
       "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
       "dev": true
     },
+    "async-limiter": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
+      "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==",
+      "dev": true
+    },
     "asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -2042,6 +2064,18 @@
         "tweetnacl": "^0.14.3"
       }
     },
+    "bfj": {
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.1.tgz",
+      "integrity": "sha512-+GUNvzHR4nRyGybQc2WpNJL4MJazMuvf92ueIyA0bIkPRwhhQu3IfZQ2PSoVPpCBJfmoSdOxu5rnotfFLlvYRQ==",
+      "dev": true,
+      "requires": {
+        "bluebird": "^3.5.1",
+        "check-types": "^7.3.0",
+        "hoopy": "^0.1.2",
+        "tryer": "^1.0.0"
+      }
+    },
     "big.js": {
       "version": "5.2.2",
       "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@@ -2078,6 +2112,53 @@
         "safe-json-parse": "~1.0.1"
       }
     },
+    "body-parser": {
+      "version": "1.18.3",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
+      "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
+      "dev": true,
+      "requires": {
+        "bytes": "3.0.0",
+        "content-type": "~1.0.4",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "http-errors": "~1.6.3",
+        "iconv-lite": "0.4.23",
+        "on-finished": "~2.3.0",
+        "qs": "6.5.2",
+        "raw-body": "2.3.3",
+        "type-is": "~1.6.16"
+      },
+      "dependencies": {
+        "bytes": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+          "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=",
+          "dev": true
+        },
+        "iconv-lite": {
+          "version": "0.4.23",
+          "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
+          "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
+          "dev": true,
+          "requires": {
+            "safer-buffer": ">= 2.1.2 < 3"
+          }
+        },
+        "raw-body": {
+          "version": "2.3.3",
+          "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
+          "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
+          "dev": true,
+          "requires": {
+            "bytes": "3.0.0",
+            "http-errors": "1.6.3",
+            "iconv-lite": "0.4.23",
+            "unpipe": "1.0.0"
+          }
+        }
+      }
+    },
     "brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -2360,6 +2441,12 @@
       "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
       "dev": true
     },
+    "check-types": {
+      "version": "7.4.0",
+      "resolved": "https://registry.npmjs.org/check-types/-/check-types-7.4.0.tgz",
+      "integrity": "sha512-YbulWHdfP99UfZ73NcUDlNJhEIDgm9Doq9GhpyXbF+7Aegi3CVV7qqMCKTTqJxlvEvnQBp9IA+dxsGN6xK/nSg==",
+      "dev": true
+    },
     "chokidar": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
@@ -2470,6 +2557,16 @@
       "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
       "dev": true
     },
+    "clipboard": {
+      "version": "1.7.1",
+      "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz",
+      "integrity": "sha1-Ng1taUbpmnof7zleQrqStem1oWs=",
+      "requires": {
+        "good-listener": "^1.2.2",
+        "select": "^1.1.2",
+        "tiny-emitter": "^2.0.0"
+      }
+    },
     "cliui": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
@@ -2600,6 +2697,18 @@
       "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
       "dev": true
     },
+    "content-disposition": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+      "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=",
+      "dev": true
+    },
+    "content-type": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+      "dev": true
+    },
     "continuable-cache": {
       "version": "0.3.1",
       "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz",
@@ -2615,6 +2724,18 @@
         "safe-buffer": "~5.1.1"
       }
     },
+    "cookie": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+      "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=",
+      "dev": true
+    },
+    "cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
+      "dev": true
+    },
     "copy-concurrently": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
@@ -2862,6 +2983,17 @@
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
       "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
     },
+    "delegate": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
+      "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
+    },
+    "depd": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
+      "dev": true
+    },
     "des.js": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
@@ -2872,6 +3004,12 @@
         "minimalistic-assert": "^1.0.0"
       }
     },
+    "destroy": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
+      "dev": true
+    },
     "detect-file": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
@@ -2963,6 +3101,18 @@
         "safer-buffer": "^2.1.0"
       }
     },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
+      "dev": true
+    },
+    "ejs": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz",
+      "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==",
+      "dev": true
+    },
     "electron-to-chromium": {
       "version": "1.3.106",
       "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.106.tgz",
@@ -2990,6 +3140,12 @@
       "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
       "dev": true
     },
+    "encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
+      "dev": true
+    },
     "end-of-stream": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
@@ -3062,6 +3218,12 @@
         "is-symbol": "^1.0.2"
       }
     },
+    "escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
+      "dev": true
+    },
     "escape-string-regexp": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@@ -3305,6 +3467,12 @@
       "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
       "dev": true
     },
+    "etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
+      "dev": true
+    },
     "eventemitter2": {
       "version": "0.4.14",
       "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz",
@@ -3461,6 +3629,52 @@
       "integrity": "sha512-iPowgKUZkTPX5PznYsmifVj9Bob0w2wTHVkt/eYNPSzyebkUgIedmskf/kcfEIWpiWjg3JRjnW+a17XypySMuw==",
       "dev": true
     },
+    "express": {
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
+      "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==",
+      "dev": true,
+      "requires": {
+        "accepts": "~1.3.5",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.18.3",
+        "content-disposition": "0.5.2",
+        "content-type": "~1.0.4",
+        "cookie": "0.3.1",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "1.1.1",
+        "fresh": "0.5.2",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.2",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.4",
+        "qs": "6.5.2",
+        "range-parser": "~1.2.0",
+        "safe-buffer": "5.1.2",
+        "send": "0.16.2",
+        "serve-static": "1.13.2",
+        "setprototypeof": "1.1.0",
+        "statuses": "~1.4.0",
+        "type-is": "~1.6.16",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "dependencies": {
+        "path-to-regexp": {
+          "version": "0.1.7",
+          "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+          "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
+          "dev": true
+        }
+      }
+    },
     "extend": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@@ -3661,6 +3875,21 @@
         }
       }
     },
+    "finalhandler": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
+      "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
+      "dev": true,
+      "requires": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.2",
+        "statuses": "~1.4.0",
+        "unpipe": "~1.0.0"
+      }
+    },
     "find-cache-dir": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.0.0.tgz",
@@ -3760,6 +3989,12 @@
         "mime-types": "^2.1.12"
       }
     },
+    "forwarded": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+      "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=",
+      "dev": true
+    },
     "fragment-cache": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
@@ -3769,6 +4004,12 @@
         "map-cache": "^0.2.2"
       }
     },
+    "fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
+      "dev": true
+    },
     "from2": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
@@ -4558,6 +4799,14 @@
         }
       }
     },
+    "good-listener": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
+      "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
+      "requires": {
+        "delegate": "^3.1.2"
+      }
+    },
     "graceful-fs": {
       "version": "4.1.15",
       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
@@ -4942,12 +5191,30 @@
       "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=",
       "dev": true
     },
+    "hoopy": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
+      "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==",
+      "dev": true
+    },
     "hosted-git-info": {
       "version": "2.7.1",
       "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
       "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==",
       "dev": true
     },
+    "http-errors": {
+      "version": "1.6.3",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+      "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+      "dev": true,
+      "requires": {
+        "depd": "~1.1.2",
+        "inherits": "2.0.3",
+        "setprototypeof": "1.1.0",
+        "statuses": ">= 1.4.0 < 2"
+      }
+    },
     "http-parser-js": {
       "version": "0.5.0",
       "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.0.tgz",
@@ -5182,6 +5449,12 @@
       "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
       "dev": true
     },
+    "ipaddr.js": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz",
+      "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=",
+      "dev": true
+    },
     "is-accessor-descriptor": {
       "version": "0.1.6",
       "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
@@ -5497,6 +5770,11 @@
       "resolved": "https://registry.npmjs.org/jquery.cookie/-/jquery.cookie-1.4.1.tgz",
       "integrity": "sha1-1j3OIJ6raR/mMxbbCMqeR+D5OFs="
     },
+    "jquery.hotkeys": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/jquery.hotkeys/-/jquery.hotkeys-0.1.0.tgz",
+      "integrity": "sha1-qI9jZJT+dd5B5LdToFnlpSO4MnM="
+    },
     "js-levenshtein": {
       "version": "1.1.6",
       "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
@@ -5945,6 +6223,12 @@
         "safe-buffer": "^5.1.2"
       }
     },
+    "media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
+      "dev": true
+    },
     "medium-editor": {
       "version": "5.23.3",
       "resolved": "https://registry.npmjs.org/medium-editor/-/medium-editor-5.23.3.tgz",
@@ -5989,6 +6273,18 @@
         "trim-newlines": "^1.0.0"
       }
     },
+    "merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=",
+      "dev": true
+    },
+    "methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
+      "dev": true
+    },
     "micromatch": {
       "version": "3.1.10",
       "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
@@ -6200,6 +6496,12 @@
       "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
       "dev": true
     },
+    "negotiator": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
+      "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=",
+      "dev": true
+    },
     "neo-async": {
       "version": "2.6.0",
       "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz",
@@ -6402,6 +6704,15 @@
         "isobject": "^3.0.1"
       }
     },
+    "on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "dev": true,
+      "requires": {
+        "ee-first": "1.1.1"
+      }
+    },
     "once": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -6420,6 +6731,12 @@
         "mimic-fn": "^1.0.0"
       }
     },
+    "opener": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz",
+      "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==",
+      "dev": true
+    },
     "optimist": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
@@ -6640,6 +6957,12 @@
       "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
       "dev": true
     },
+    "parseurl": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
+      "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=",
+      "dev": true
+    },
     "pascalcase": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
@@ -6906,6 +7229,16 @@
       "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
       "dev": true
     },
+    "proxy-addr": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz",
+      "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==",
+      "dev": true,
+      "requires": {
+        "forwarded": "~0.1.2",
+        "ipaddr.js": "1.8.0"
+      }
+    },
     "prr": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
@@ -7032,6 +7365,12 @@
         "safe-buffer": "^5.1.0"
       }
     },
+    "range-parser": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
+      "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=",
+      "dev": true
+    },
     "raw-body": {
       "version": "1.1.7",
       "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz",
@@ -7613,6 +7952,11 @@
         "ajv-keywords": "^3.1.0"
       }
     },
+    "select": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
+      "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0="
+    },
     "selectize": {
       "version": "0.12.6",
       "resolved": "https://registry.npmjs.org/selectize/-/selectize-0.12.6.tgz",
@@ -7633,12 +7977,53 @@
       "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
       "dev": true
     },
+    "send": {
+      "version": "0.16.2",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
+      "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
+      "dev": true,
+      "requires": {
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "destroy": "~1.0.4",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "~1.6.2",
+        "mime": "1.4.1",
+        "ms": "2.0.0",
+        "on-finished": "~2.3.0",
+        "range-parser": "~1.2.0",
+        "statuses": "~1.4.0"
+      },
+      "dependencies": {
+        "mime": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
+          "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==",
+          "dev": true
+        }
+      }
+    },
     "serialize-javascript": {
       "version": "1.6.1",
       "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.6.1.tgz",
       "integrity": "sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==",
       "dev": true
     },
+    "serve-static": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
+      "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
+      "dev": true,
+      "requires": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.2",
+        "send": "0.16.2"
+      }
+    },
     "set-blocking": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -7674,6 +8059,12 @@
       "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
       "dev": true
     },
+    "setprototypeof": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
+      "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
+      "dev": true
+    },
     "sha.js": {
       "version": "2.4.11",
       "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
@@ -8008,6 +8399,12 @@
         }
       }
     },
+    "statuses": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
+      "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==",
+      "dev": true
+    },
     "stream-browserify": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
@@ -8238,6 +8635,11 @@
         "setimmediate": "^1.0.4"
       }
     },
+    "tiny-emitter": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
+      "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
+    },
     "tiny-lr": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz",
@@ -8362,6 +8764,12 @@
       "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
       "dev": true
     },
+    "tryer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz",
+      "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==",
+      "dev": true
+    },
     "tslib": {
       "version": "1.9.3",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
@@ -8397,6 +8805,16 @@
         "prelude-ls": "~1.1.2"
       }
     },
+    "type-is": {
+      "version": "1.6.16",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
+      "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
+      "dev": true,
+      "requires": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.18"
+      }
+    },
     "typedarray": {
       "version": "0.0.6",
       "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
@@ -8519,6 +8937,12 @@
         "imurmurhash": "^0.1.4"
       }
     },
+    "unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
+      "dev": true
+    },
     "unset-value": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
@@ -8646,6 +9070,12 @@
         "object.getownpropertydescriptors": "^2.0.3"
       }
     },
+    "utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
+      "dev": true
+    },
     "uuid": {
       "version": "3.3.2",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
@@ -8677,6 +9107,12 @@
         "spdx-expression-parse": "^3.0.0"
       }
     },
+    "vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
+      "dev": true
+    },
     "verror": {
       "version": "1.10.0",
       "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
@@ -8740,6 +9176,79 @@
         "webpack-sources": "^1.3.0"
       }
     },
+    "webpack-bundle-analyzer": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.0.4.tgz",
+      "integrity": "sha512-ggDUgtKuQki4vmc93Ej65GlYxeCUR/0THa7gA+iqAGC2FFAxO+r+RM9sAUa8HWdw4gJ3/NZHX/QUcVgRjdIsDg==",
+      "dev": true,
+      "requires": {
+        "acorn": "^5.7.3",
+        "bfj": "^6.1.1",
+        "chalk": "^2.4.1",
+        "commander": "^2.18.0",
+        "ejs": "^2.6.1",
+        "express": "^4.16.3",
+        "filesize": "^3.6.1",
+        "gzip-size": "^5.0.0",
+        "lodash": "^4.17.10",
+        "mkdirp": "^0.5.1",
+        "opener": "^1.5.1",
+        "ws": "^6.0.0"
+      },
+      "dependencies": {
+        "acorn": {
+          "version": "5.7.3",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
+          "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==",
+          "dev": true
+        },
+        "ansi-styles": {
+          "version": "3.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+          "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+          "dev": true,
+          "requires": {
+            "color-convert": "^1.9.0"
+          }
+        },
+        "chalk": {
+          "version": "2.4.2",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+          "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
+          }
+        },
+        "filesize": {
+          "version": "3.6.1",
+          "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz",
+          "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==",
+          "dev": true
+        },
+        "gzip-size": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.0.0.tgz",
+          "integrity": "sha512-5iI7omclyqrnWw4XbXAmGhPsABkSIDQonv2K0h61lybgofWa6iZyvrI3r2zsJH4P8Nb64fFVzlvfhs0g7BBxAA==",
+          "dev": true,
+          "requires": {
+            "duplexer": "^0.1.1",
+            "pify": "^3.0.0"
+          }
+        },
+        "supports-color": {
+          "version": "5.5.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+          "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+          "dev": true,
+          "requires": {
+            "has-flag": "^3.0.0"
+          }
+        }
+      }
+    },
     "webpack-bundle-tracker": {
       "version": "0.4.2-beta",
       "resolved": "https://registry.npmjs.org/webpack-bundle-tracker/-/webpack-bundle-tracker-0.4.2-beta.tgz",
@@ -8952,6 +9461,15 @@
         "mkdirp": "^0.5.1"
       }
     },
+    "ws": {
+      "version": "6.1.3",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.3.tgz",
+      "integrity": "sha512-tbSxiT+qJI223AP4iLfQbkbxkwdFcneYinM2+x46Gx2wgvbaOMO36czfdfVUBRTHvzAMRhDd98sA5d/BuWbQdg==",
+      "dev": true,
+      "requires": {
+        "async-limiter": "~1.0.0"
+      }
+    },
     "xtend": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",

+ 10 - 1
package.json

@@ -9,7 +9,14 @@
   },
   "author": "Cloudera, Inc.",
   "contributors": [
-    "Johan Ahlen <johan.ahlen@cloudera.com>"
+    "Romain Rigaux <romain.rigaux@cloudera.com>",
+    "Jean-François Desjeans Gauthier <jgauthier@cloudera.com>",
+    "Prakash Ranade <ranade@cloudera.com>",
+    "Ying Chen <yingchen@cloudera.com>",
+    "Weixia Xu <weixia@cloudera.com>",
+    "Joe Heyming <jheyming@cloudera.com>",
+    "Johan Ahlen <johan.ahlen@cloudera.com>",
+    "Andrew Yao <andyao@cloudera.com>"
   ],
   "license": "Apache",
   "private": true,
@@ -17,6 +24,7 @@
     "node": ">=0.10.0"
   },
   "dependencies": {
+    "clipboard": "1.7.1",
     "dropzone": "5.5.1",
     "filesize": "4.0.0",
     "jquery": "3.3.1",
@@ -59,6 +67,7 @@
     "load-grunt-tasks": "4.0.0",
     "prettier": "1.16.1",
     "webpack": "4.29.0",
+    "webpack-bundle-analyzer": "^3.0.4",
     "webpack-bundle-tracker": "0.4.2-beta",
     "webpack-clean-obsolete-chunks": "^0.4.0",
     "webpack-cli": "^3.2.1"

+ 4 - 0
tools/scripts/hue-review

@@ -20,6 +20,10 @@ shift 3;
 exec $RBT post \
 	-o \
 	-X /'*.css' \
+	-X /'*/js/bundles/**' \
+	-X /'*AutocompleteParser.js' \
+	-X /'*.json' \
+	-X /'*SyntaxParser.js' \
 	--description="$(git whatchanged $REVLIST)" \
 	--target-groups=hue \
 	--target-people="$REVIEWER" \

文件差异内容过多而无法显示
+ 0 - 1
webpack-stats.json


+ 12 - 5
webpack.config.js

@@ -1,14 +1,12 @@
 const webpack = require('webpack');
 const BundleTracker = require('webpack-bundle-tracker');
 const CleanWebpackPlugin = require('clean-webpack-plugin');
-var CleanObsoleteChunks = require('webpack-clean-obsolete-chunks');
+const CleanObsoleteChunks = require('webpack-clean-obsolete-chunks');
+const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
 
 module.exports = {
   devtool: 'source-map',
   mode: 'development',
-  optimization: {
-    minimize: true
-  },
   performance: {
     maxEntrypointSize: 400 * 1024, // 400kb
     maxAssetSize: 400 * 1024 // 400kb
@@ -26,8 +24,17 @@ module.exports = {
   entry: {
     hue: ['./desktop/core/src/desktop/js/hue.js'],
     login: ['./desktop/core/src/desktop/js/login.js'],
+    notebook: ['./desktop/core/src/desktop/js/apps/notebook/notebook.js'],
     sqlSyntaxWebWorker: ['./desktop/core/src/desktop/js/sql/sqlSyntaxWebWorker.js'],
   },
+  optimization: {
+    minimize: true,
+    splitChunks: {
+      chunks (chunk) {
+        return chunk.name !== 'sqlSyntaxWebWorker' && chunk.name !== 'login';
+      }
+    }
+  },
   output: {
     path:  __dirname + '/desktop/core/src/desktop/static/desktop/js/bundles',
     filename: '[name]-bundle-[hash].js'
@@ -48,7 +55,7 @@ module.exports = {
   },
 
   plugins: [
-    // new WebpackShellPlugin({ onBuildStart:[__dirname + '/tools/scripts/clean_js_bundles.sh ' +  __dirname ] }),
+    // new BundleAnalyzerPlugin({ analyzerPort: 9000 }),
     new CleanObsoleteChunks(),
     new CleanWebpackPlugin([__dirname + '/desktop/core/src/desktop/static/desktop/js/bundles']),
     new BundleTracker({ filename: './webpack-stats.json' }),

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