Эх сурвалжийг харах

[core] Improved AceEditor autocomplete

Fixed keydown handler exception
Support for Hive table and field autocomplete
Enrico Berti 10 жил өмнө
parent
commit
fbf99fbd79

+ 11 - 10
apps/spark/src/spark/static/spark/js/ace.autocomplete.js

@@ -14,8 +14,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-(function() {
-
   var TIME_TO_LIVE_IN_MILLIS = 86400000; // 1 day
 
   var hasExpired = function(timestamp){
@@ -53,7 +51,7 @@
         type: "GET",
         url: _url + "?" + Math.random(),
         success: options.onDataReceived,
-        async: options.sync == "undefined"
+        async: typeof options.sync == "undefined"
       });
     }
   };
@@ -115,16 +113,16 @@
               self.errorHandler(data);
             }
             else {
-              $.totalStorage(getTotalStorageUserPrefix() + 'columns_' + databaseName + '_' + tableName, (data.columns ? "* " + data.columns.join(" ") : "*"));
-              $.totalStorage(getTotalStorageUserPrefix() + 'extended_columns_' + databaseName + '_' + tableName, (data.extended_columns ? data.extended_columns : []));
-              $.totalStorage(getTotalStorageUserPrefix() + 'timestamp_columns_' + databaseName + '_' + tableName, (new Date()).getTime());
+              $.totalStorage(self.getTotalStorageUserPrefix() + 'columns_' + databaseName + '_' + tableName, (data.columns ? "* " + data.columns.join(" ") : "*"));
+              $.totalStorage(self.getTotalStorageUserPrefix() + 'extended_columns_' + databaseName + '_' + tableName, (data.extended_columns ? data.extended_columns : []));
+              $.totalStorage(self.getTotalStorageUserPrefix() + 'timestamp_columns_' + databaseName + '_' + tableName, (new Date()).getTime());
             }
           }
         });
       }
     }
     else {
-      self.jsoncalls({
+      self.jsonCalls({
         database: databaseName,
         table: tableName,
         onDataReceived: function (data) {
@@ -203,7 +201,9 @@
   Autocomplete.prototype.getDatabases = function (callback) {
     var self = this;
     if ($.totalStorage(self.getTotalStorageUserPrefix() + 'databases') != null) {
-      callback($.totalStorage(self.getTotalStorageUserPrefix() + 'databases'));
+      if (callback) {
+        callback($.totalStorage(self.getTotalStorageUserPrefix() + 'databases'));
+      }
       if ($.totalStorage(self.getTotalStorageUserPrefix() + 'timestamp_databases') == null || hasExpired($.totalStorage(self.getTotalStorageUserPrefix() + 'timestamp_databases'))){
         this.jsonCalls({
           onDataReceived: function (data) {
@@ -234,7 +234,9 @@
             if (data.databases) {
               $.totalStorage(self.getTotalStorageUserPrefix() + 'databases', data.databases);
               $.totalStorage(self.getTotalStorageUserPrefix() + 'timestamp_databases', (new Date()).getTime());
-              callback($.totalStorage(self.getTotalStorageUserPrefix() + 'databases'));
+              if (callback) {
+                callback($.totalStorage(self.getTotalStorageUserPrefix() + 'databases'));
+              }
             }
           }
         }
@@ -254,7 +256,6 @@
       }
     }
   }
-})();
 
 
 

+ 138 - 46
apps/spark/src/spark/templates/ace.mako

@@ -42,7 +42,7 @@ ${ commonheader(_("Notebooks"), "spark", user, "60px") | n,unicode }
 </style>
 
 <!-- ko foreach: editors -->
-  <div class="editor" data-bind="attr: {id: UUID()}, aceEditor: {value: snippet, ace: ace, mode: mode, onChange: changeEditor, onAfterExec: textInputHandler, extraCompleters: completers, errors: errors }">
+  <div class="editor" data-bind="attr: {id: UUID()}, aceEditor: {value: snippet, aceInstance: ace, mode: mode, onChange: onChangeHandler, onAfterExec: onAfterExecHandler, extraCompleters: completers, errors: errors }">
   </div>
 <!-- /ko -->
 
@@ -59,22 +59,20 @@ ${ commonheader(_("Notebooks"), "spark", user, "60px") | n,unicode }
 <script src="${ static('desktop/ext/js/knockout-sortable.min.js') }" type="text/javascript" charset="utf-8"></script>
 <script src="${ static('desktop/ext/js/knockout-deferred-updates.min.js') }" type="text/javascript" charset="utf-8"></script>
 <script src="${ static('desktop/js/ko.hue-bindings.js') }" type="text/javascript" charset="utf-8"></script>
-<script src="${ static('beeswax/js/autocomplete.utils.js') }" type="text/javascript" charset="utf-8"></script>
+
+<script src="${ static('spark/js/ace.autocomplete.js') }" type="text/javascript" charset="utf-8"></script>
 
 <script>
 
-  var HIVE_AUTOCOMPLETE_BASE_URL = "/beeswax/api/autocomplete/";
-  var HIVE_AUTOCOMPLETE_FAILS_QUIETLY_ON = [500]; // error codes from beeswax/views.py - autocomplete
-  var HIVE_AUTOCOMPLETE_USER = "hue";
-  var HIVE_AUTOCOMPLETE_APP = "beeswax";
 
-  var STATS_PROBLEMS = "${ _('There was a problem loading the stats.') }";
+  var AceAutocomplete = new Autocomplete({
+   autocompleteBaseURL: "/beeswax/api/autocomplete/",
+   autocompleteApp: "beeswax",
+   autocompleteUser: "hue",
+   autocompleteFailsQuietlyOn: [500] // error codes from beeswax/views.py - autocomplete
+  });
 
-  var HIVE_AUTOCOMPLETE_GLOBAL_CALLBACK = function (data) {
-    if (data != null && data.error && typeof resetNavigator != "undefined") {
-      resetNavigator();
-    }
-  };
+  var DEFAULT_DB = "default";
 
   function newCompleter(items) {
     return {
@@ -84,44 +82,138 @@ ${ commonheader(_("Notebooks"), "spark", user, "60px") | n,unicode }
     }
   }
 
-  function textInputHandler(event, editor, valueAccessor) {
-
-  }
+  function fieldsAutocomplete(editor, valueAccessor) {
+    try {
+      var _before = editor.getTextBeforeCursor(";");
+      var _after = editor.getTextAfterCursor(";");
+      var _statement = _before + _after;
+      var _foundTable = "";
+      if (_before.substr(-1) == "."){ // gets the table alias
+        _foundTable = _before.split(" ").pop().slice(0, -1);
+      }
+      else { // gets the standard table
+        var _from = _statement.toUpperCase().indexOf("FROM");
+        if (_from > -1) {
+          var _match = _statement.toUpperCase().substring(_from).match(/ ON| LIMIT| WHERE| GROUP| SORT| ORDER BY|;/);
+          var _to = _statement.length;
+          if (_match) {
+            _to = _match.index;
+          }
+          var _found = _statement.substr(_from, _to).replace(/(\r\n|\n|\r)/gm, "").replace(/from/gi, "").replace(/join/gi, ",").split(",");
+        }
 
-  function changeEditor(event, editor, valueAccessor) {
-    // if it's pig and before it's LOAD ' we disable the autocomplete and show a filechooser btn
-    if (editor.session.getMode().$id = "ace/mode/pig") {
+        for (var i = 0; i < _found.length; i++) {
+          if ($.trim(_found[i]) != "" && _foundTable == "") {
+            _foundTable = $.trim(_found[i]).split(" ")[0];
+          }
+        }
+      }
 
+      if (_foundTable != "") {
+          editor.showSpinner();
+          // fill up with fields
+          AceAutocomplete.getTableColumns(DEFAULT_DB, _foundTable, _statement, function(data){
+            var _fieldNames = data.split(" ");
+            var _fields = [];
+            _fieldNames.forEach(function(fld){
+              _fields.push({value: fld, score: (fld == "*") ? 1001: 1000, meta: "column"});
+            });
+            valueAccessor().extraCompleters([newCompleter(_fields)]);
+            editor.hideSpinner();
+          });
+      }
     }
-    else {
-
-      if (event.data.text == "."){
-      editor.showSpinner();
-      // fill up with fields
-      hac_getTableColumns("default", "sample_07", editor.getValue(), function(data){
-        var _fieldNames = data.split(" ");
-        var _fields = [];
-        _fieldNames.forEach(function(fld){
-          _fields.push({value: fld, score: 1000, meta: "column"});
-        });
-        valueAccessor().extraCompleters([newCompleter(_fields)]);
-        editor.hideSpinner();
-      })
+    catch (e) {
     }
-    else {
-      editor.showSpinner();
-      valueAccessor().extraCompleters([]);
-      hac_getTables("default", function(data){
-        var _tableNames = data.split(" ");
-        var _tables = [];
-        _tableNames.forEach(function(tbl){
-          _tables.push({value: tbl, score: 1000, meta: "table"});
+  }
+
+  function onAfterExecHandler(e, editor, valueAccessor) {
+    editor.session.getMode().$id = valueAccessor().mode(); // forces the id again because of Ace command internals
+    if ((editor.session.getMode().$id == "ace/mode/hivesql" || editor.session.getMode().$id == "ace/mode/impalasql") && e.args == "."){
+      fieldsAutocomplete(editor, valueAccessor);
+    }
+        // if it's pig and before it's LOAD ' we disable the autocomplete and show a filechooser btn
+    if (editor.session.getMode().$id = "ace/mode/pig" && e.args) {
+      var _textBefore = editor.getTextBeforeCursor();
+      if ((e.args == "'" && _textBefore.toUpperCase().indexOf("LOAD ") > -1 && _textBefore.toUpperCase().indexOf("LOAD ") == _textBefore.toUpperCase().length - 5)
+          || _textBefore.toUpperCase().indexOf("LOAD '") > -1 && _textBefore.toUpperCase().indexOf("LOAD '") == _textBefore.toUpperCase().length - 6){
+        editor.disableAutocomplete();
+        var _btn = editor.showFileButton();
+        _btn.on("click", function(ie){
+          ie.preventDefault();
+          if ($(".filechooser-content").data("spinner") == null){
+            $(".filechooser-content").data("spinner", $(".filechooser-content").html());
+          }
+          else {
+            $(".filechooser-content").html($(".filechooser-content").data("spinner"));
+          }
+          $(".filechooser-content").jHueFileChooser({
+            onFileChoose: function (filePath) {
+              editor.session.insert(editor.getCursorPosition(), filePath + "'");
+              editor.hideFileButton();
+              editor.enableAutocomplete();
+              $(".filechooser").hide();
+            },
+            selectFolder: false,
+            createFolder: false
+          });
+          $(".filechooser").css({ "top": $(ie.currentTarget).position().top, "left": $(ie.currentTarget).position().left}).show();
         });
-        valueAccessor().extraCompleters([newCompleter(_tables)]);
-        editor.hideSpinner();
-      });
+      }
+      else {
+        editor.hideFileButton();
+        editor.enableAutocomplete();
+      }
+      if (e.args != "'" && _textBefore.toUpperCase().indexOf("LOAD '") > -1 && _textBefore.toUpperCase().indexOf("LOAD '") == _textBefore.toUpperCase().length - 6) {
+        editor.hideFileButton();
+        editor.enableAutocomplete();
+      }
     }
 
+  }
+
+
+  function onChangeHandler(event, editor, valueAccessor) {
+    valueAccessor().extraCompleters([]);
+    editor.session.getMode().$id = valueAccessor().mode();
+
+    var _before = editor.getTextBeforeCursor(";");
+    var _beforeU = _before.toUpperCase();
+    var _after = editor.getTextAfterCursor(";");
+    var _afterU = _after.toUpperCase();
+    if (editor.session.getMode().$id == "ace/mode/hivesql" || editor.session.getMode().$id == "ace/mode/impalasql") {
+      if ($.trim(_before).substr(-1) != ".") {
+        if ((_beforeU.indexOf(" FROM ") > -1 || _beforeU.indexOf(" TABLE ") > -1 || _beforeU.indexOf(" STATS ") > -1) && _beforeU.indexOf(" ON ") == -1 && _beforeU.indexOf(" ORDER BY ") == -1 && _beforeU.indexOf(" WHERE ") == -1 ||
+            _beforeU.indexOf("REFRESH") > -1 || _beforeU.indexOf("METADATA") > -1 || _beforeU.indexOf("DESCRIBE") > -1) {
+          editor.showSpinner();
+          valueAccessor().extraCompleters([]);
+          AceAutocomplete.getTables(DEFAULT_DB, function (data) {
+            var _tableNames = data.split(" ");
+            var _tables = [];
+            _tableNames.forEach(function (tbl) {
+              _tables.push({value: tbl, score: 1000, meta: "table"});
+            });
+            valueAccessor().extraCompleters([newCompleter(_tables)]);
+            editor.hideSpinner();
+          });
+        }
+        if (_beforeU.indexOf("SELECT ") > -1 && _beforeU.indexOf(" FROM ") == -1) { //  && !CodeMirror.fromDot
+          if (_afterU.indexOf("FROM ") > -1) {
+            fieldsAutocomplete(editor, valueAccessor);
+          }
+          else {
+            console.log("table magic")
+          }
+        }
+        else {
+          if ((_beforeU.indexOf("WHERE") > -1 || _beforeU.indexOf("ORDER BY") > -1) && _beforeU.match(/ ON| LIMIT| GROUP| SORT/) == null) {
+            fieldsAutocomplete(editor, valueAccessor);
+          }
+          else {
+            console.log("do other stuff")
+          }
+        }
+      }
     }
   }
 
@@ -143,9 +235,9 @@ ${ commonheader(_("Notebooks"), "spark", user, "60px") | n,unicode }
 ##    var snippy2 = new Snip("ace/mode/python");
 ##    snippy2.snippet("def nano:");
 ##    viewModel.editors.push(snippy2);
-##    var snippy3 = new Snip("ace/mode/hivesql");
-##    snippy3.snippet("SELECT * FROM sample_07");
-##    viewModel.editors.push(snippy3);
+    var snippy3 = new Snip("ace/mode/hivesql");
+    snippy3.snippet("SELECT * FROM sample_07 sa, web_logs w");
+    viewModel.editors.push(snippy3);
     var snippy = new Snip("ace/mode/pig");
     snippy.snippet("A = LOAD ''");
     viewModel.editors.push(snippy);

+ 3 - 43
desktop/core/src/desktop/static/desktop/js/ko.hue-bindings.js

@@ -1515,58 +1515,18 @@ ko.bindingHandlers.aceEditor = {
         _onChange(e, editor, valueAccessor);
       });
 
-      editor.container.addEventListener("keydown", function(e){
-        // if it's pig and before it's LOAD ' we disable the autocomplete and show a filechooser btn
-        if (editor.session.getMode().$id = "ace/mode/pig" && e.keyCode) {
-          var _textBefore = editor.getTextBeforeCursor();
-          if ((e.keyCode == 222 && _textBefore.toUpperCase().indexOf("LOAD ") > -1 && _textBefore.toUpperCase().indexOf("LOAD ") == _textBefore.toUpperCase().length - 5)
-              || _textBefore.toUpperCase().indexOf("LOAD '") > -1 && _textBefore.toUpperCase().indexOf("LOAD '") == _textBefore.toUpperCase().length - 6){
-            editor.disableAutocomplete();
-            var _btn = editor.showFileButton();
-            _btn.on("click", function(ie){
-              ie.preventDefault();
-              if ($(".filechooser-content").data("spinner") == null){
-                $(".filechooser-content").data("spinner", $(".filechooser-content").html());
-              }
-              else {
-                $(".filechooser-content").html($(".filechooser-content").data("spinner"));
-              }
-              $(".filechooser-content").jHueFileChooser({
-                onFileChoose: function (filePath) {
-                  editor.session.insert(editor.getCursorPosition(), filePath);
-                  editor.hideFileButton();
-                  editor.enableAutocomplete();
-                  $(".filechooser").hide();
-                },
-                selectFolder: false,
-                createFolder: false
-              });
-              $(".filechooser").css({ "top": $(ie.currentTarget).position().top, "left": $(ie.currentTarget).position().left}).show();
-            });
-          }
-          else {
-            editor.hideFileButton();
-            editor.enableAutocomplete();
-          }
-          if (e.keyCode != 222 && _textBefore.toUpperCase().indexOf("LOAD '") > -1 && _textBefore.toUpperCase().indexOf("LOAD '") == _textBefore.toUpperCase().length - 6) {
-            editor.hideFileButton();
-            editor.enableAutocomplete();
-          }
-        }
-      }, true);
-
       editor.commands.on("afterExec", function(e) {
         _onAfterExec(e, editor, valueAccessor);
       });
 
       editor.$blockScrolling = Infinity
       element.originalCompleters = editor.completers;
-      _options.ace(editor);
+      _options.aceInstance(editor);
     },
     update: function (element, valueAccessor) {
       var _options = ko.unwrap(valueAccessor());
-      if (_options.ace()) {
-        var _editor = _options.ace();
+      if (_options.aceInstance()) {
+        var _editor = _options.aceInstance();
         _editor.completers = element.originalCompleters.slice();
         if (_options.extraCompleters().length > 0) {
           _options.extraCompleters().forEach(function (complete) {