Переглянути джерело

HUE-7062 [search] Add custom autocomplete parser for solr formulas

Johan Ahlen 8 роки тому
батько
коміт
1f26e9ecef

+ 5 - 0
Gruntfile.js

@@ -119,6 +119,11 @@ module.exports = function(grunt) {
         files: {
           'desktop/core/src/desktop/static/desktop/js/autocomplete/jison/globalSearchParser.js': ['desktop/core/src/desktop/static/desktop/js/autocomplete/jison/globalSearchParser.js']
         }
+      },
+      solrExpressionParser: {
+        files: {
+          'desktop/core/src/desktop/static/desktop/js/autocomplete/jison/solrExpressionParser.js': ['desktop/core/src/desktop/static/desktop/js/autocomplete/jison/solrExpressionParser.js']
+        }
       }
     }
   });

+ 228 - 0
desktop/core/src/desktop/static/desktop/js/autocomplete/jison/solrExpressionParser.jison

@@ -0,0 +1,228 @@
+// 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.
+
+%lex
+%options case-insensitive
+%%
+
+\s                                         { /* skip whitespace */ }
+'--'.*                                     { /* skip comments */ }
+[/][*][^*]*[*]+([^/*][^*]*[*]+)*[/]        { /* skip comments */ }
+
+'\u2020'                                   { parser.yy.cursorFound = yylloc; return 'CURSOR'; }
+
+[0-9]+(?:[,.][0-9]+)?                      { return 'NUMBER'; }
+
+'-'                                        { return '-'; }
+'*'                                        { return 'OPERATOR'; }
+'+'                                        { return 'OPERATOR'; }
+'/'                                        { return 'OPERATOR'; }
+
+[a-z]+\s*\(                                {
+                                             yy.lexer.unput('(');
+                                             parser.addFunctionLocation({
+                                               first_line: yylloc.first_line,
+                                               first_column: yylloc.first_column,
+                                               last_line: yylloc.first_line,
+                                               last_column: yylloc.first_column + yytext.trim().length
+                                             }, yytext.trim());
+                                             return 'FUNCTION';
+                                           }
+
+','                                        { return ','; }
+'('                                        { return '('; }
+')'                                        { return ')'; }
+
+<<EOF>>                                    { return 'EOF'; }
+
+[^\s\u2020()]+                             { parser.addFieldLocation(yylloc, yytext); return 'IDENTIFIER'; }
+
+/lex
+
+%left '-' 'OPERATOR'
+
+%start SolrExpressionAutocomplete
+
+%%
+
+SolrExpressionAutocomplete
+ : SolrExpression 'EOF'
+   {
+     return {};
+   }
+ | SolrExpression_EDIT 'EOF'
+   {
+     return $1
+   }
+ | 'CURSOR' 'EOF'
+   {
+     return { suggestFunctions: true, suggestFields: true }
+   }
+ ;
+
+SolrExpression
+ : NonParenthesizedSolrExpression
+ | '(' NonParenthesizedSolrExpression ')'
+ ;
+
+SolrExpression_EDIT
+ : NonParenthesizedSolrExpression_EDIT
+ | '(' NonParenthesizedSolrExpression_EDIT RightParenthesisOrError   --> $2
+ ;
+
+NonParenthesizedSolrExpression
+ : 'NUMBER'
+ | 'IDENTIFIER'
+ | 'FUNCTION' '(' ArgumentList ')'
+ | SolrExpression 'OPERATOR' SolrExpression
+ | SolrExpression '-' SolrExpression
+ | '-' SolrExpression
+ ;
+
+NonParenthesizedSolrExpression_EDIT
+ : 'NUMBER' 'CURSOR'                                                 --> { suggestOperators: true }
+ | 'IDENTIFIER' 'CURSOR'                                             --> { suggestOperators: true }
+ | 'CURSOR' 'NUMBER'                                                 --> { suggestFunctions: true, suggestFields: true }
+ | 'CURSOR' 'IDENTIFIER'                                             --> { suggestFunctions: true, suggestFields: true }
+ ;
+
+NonParenthesizedSolrExpression_EDIT
+ : 'FUNCTION' '(' 'CURSOR' RightParenthesisOrError                   --> { suggestFunctions: true, suggestFields: true }
+ | 'FUNCTION' '(' ArgumentList_EDIT RightParenthesisOrError          --> $3
+ | 'FUNCTION' '(' ArgumentList ')' 'CURSOR'                          --> { suggestOperators: true }
+ ;
+
+NonParenthesizedSolrExpression_EDIT
+ : SolrExpression 'OPERATOR' 'CURSOR'                                --> { suggestFunctions: true, suggestFields: true }
+ | 'CURSOR' 'OPERATOR' SolrExpression                                --> { suggestFunctions: true, suggestFields: true }
+ | SolrExpression_EDIT 'OPERATOR' SolrExpression                     --> $1
+ | SolrExpression 'OPERATOR' SolrExpression_EDIT                     --> $3
+ ;
+
+NonParenthesizedSolrExpression_EDIT
+ : SolrExpression '-' 'CURSOR'                                       --> { suggestFunctions: true, suggestFields: true }
+ | 'CURSOR' '-' SolrExpression                                       --> { suggestFunctions: true, suggestFields: true }
+ | SolrExpression_EDIT '-' SolrExpression                            --> $1
+ | SolrExpression '-' SolrExpression_EDIT                            --> $3
+ ;
+
+NonParenthesizedSolrExpression_EDIT
+ : '-' 'CURSOR'                                                      --> { suggestFunctions: true, suggestFields: true }
+ | '-' SolrExpression_EDIT                                           --> $2
+ ;
+
+ArgumentList
+ : SolrExpression
+ | ArgumentList ',' SolrExpression
+ ;
+
+ArgumentList_EDIT
+ : SolrExpression_EDIT
+ | ArgumentList ',' SolrExpression_EDIT                              --> $3
+ | SolrExpression_EDIT ',' ArgumentList
+ | ArgumentList ',' SolrExpression_EDIT ',' ArgumentList             --> $3
+ ;
+
+
+RightParenthesisOrError
+ : ')'
+ | error
+ ;
+
+%%
+
+parser.yy.parseError = function () { return false; }
+
+parser.identifyPartials = function (beforeCursor, afterCursor) {
+  var beforeMatch = beforeCursor.match(/[^()-*+/,\s]*$/);
+  var afterMatch = afterCursor.match(/^[^()-*+/,\s]*/);
+  return {left: beforeMatch ? beforeMatch[0].length : 0, right: afterMatch ? afterMatch[0].length : 0};
+};
+
+var adjustLocationForCursor = function (location) {
+  // columns are 0-based and lines not, so add 1 to cols
+  var newLocation = {
+    first_line: location.first_line,
+    last_line: location.last_line,
+    first_column: location.first_column + 1,
+    last_column: location.last_column + 1
+  };
+  if (parser.yy.cursorFound) {
+    if (parser.yy.cursorFound.first_line === newLocation.first_line && parser.yy.cursorFound.last_column <= newLocation.first_column) {
+      var additionalSpace = parser.yy.partialLengths.left + parser.yy.partialLengths.right;
+      additionalSpace -= parser.yy.partialCursor ? 1 : 3; // For some reason the normal cursor eats 3 positions.
+      newLocation.first_column = newLocation.first_column + additionalSpace;
+      newLocation.last_column = newLocation.last_column + additionalSpace;
+    }
+  }
+  return newLocation;
+};
+
+parser.addFunctionLocation = function (location, name) {
+  parser.yy.locations.push({ type: 'function', name: name, location: adjustLocationForCursor(location) });
+}
+
+parser.addFieldLocation = function (location, name) {
+  parser.yy.locations.push({ type: 'field', name: name, location: adjustLocationForCursor(location) });
+}
+
+parser.parseSolrExpression = function (beforeCursor, afterCursor, debug) {
+  parser.yy.cursorFound = false;
+  parser.yy.locations = [];
+
+  beforeCursor = beforeCursor.replace(/\r\n|\n\r/gm, '\n');
+  afterCursor = afterCursor.replace(/\r\n|\n\r/gm, '\n');
+
+  parser.yy.partialLengths = parser.identifyPartials(beforeCursor, afterCursor);
+
+  if (parser.yy.partialLengths.left > 0) {
+    beforeCursor = beforeCursor.substring(0, beforeCursor.length - parser.yy.partialLengths.left);
+  }
+
+  if (parser.yy.partialLengths.right > 0) {
+    afterCursor = afterCursor.substring(parser.yy.partialLengths.right);
+  }
+
+  var result;
+  try {
+    result = parser.parse(beforeCursor + '\u2020' + afterCursor);
+  } catch (err) {
+    // Workaround for too many missing parentheses (it's the only error we handle in the parser)
+    if (err && err.toString().indexOf('Parsing halted while starting to recover from another error') !== -1) {
+      var leftCount = (beforeCursor.match(/\(/g) || []).length;
+      var rightCount = (beforeCursor.match(/\)/g) || []).length;
+      var parenthesisPad = '';
+      while (rightCount < leftCount) {
+        parenthesisPad += ')';
+        rightCount++;
+      }
+      try {
+        result = parser.parse(beforeCursor + '\u2020' + parenthesisPad);
+      } catch (err) {
+        return {}
+      }
+    } else {
+      if (debug) {
+        console.log(beforeCursor + '\u2020' + afterCursor);
+        console.log(err);
+        console.error(err.stack);
+      }
+      return {}
+    }
+  }
+  result.locations = parser.yy.locations;
+  return result;
+};

Різницю між файлами не показано, бо вона завелика
+ 15 - 0
desktop/core/src/desktop/static/desktop/js/autocomplete/solrExpressionParser.js


+ 84 - 0
desktop/core/src/desktop/static/desktop/spec/autocomplete/solrExpressionParserSpec.js

@@ -0,0 +1,84 @@
+// 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.
+(function () {
+  describe('solrExpressionParser.js', function () {
+
+    var testParser = function (beforeCursor, afterCursor, expectedResult) {
+      var result = solrExpressionParser.parseSolrExpression(beforeCursor, afterCursor, true);
+      if (!expectedResult.locations) {
+        delete result.locations;
+      }
+      expect(result).toEqual(expectedResult);
+    };
+
+    it('should suggest functions and fields for "|"', function () {
+      testParser('', '', {
+        suggestFunctions: true,
+        suggestFields: true,
+        locations: []
+      });
+    });
+
+    it('should suggest functions and fields for "min(|"', function () {
+      testParser('min(', '', {
+        suggestFunctions: true,
+        suggestFields: true,
+        locations: [
+          { type: 'function', name: 'min', location: { first_line: 1, last_line: 1, first_column: 1, last_column: 4 }}
+        ]
+      });
+    });
+
+    it('should suggest functions and fields for "min(boo + |"', function () {
+      testParser('min(boo + ', '', {
+        suggestFunctions: true,
+        suggestFields: true
+      });
+    });
+
+    it('should suggest functions and fields for "min(boo + | + baa)"', function () {
+      testParser('min(boo + ', ' + baa)', {
+        suggestFunctions: true,
+        suggestFields: true
+      });
+    });
+
+    it('should suggest functions and fields for "min(1- max(|"', function () {
+      testParser('min(1- max(', '', {
+        suggestFunctions: true,
+        suggestFields: true
+      });
+    });
+
+    it('should suggest operators for "min(boo + 1) - 4 + mul(10, baa)|"', function () {
+      testParser('min(boo + 1) - 4 + mul(10, baa)', '', {
+        suggestOperators: true,
+        locations: [
+          { type: 'function', name: 'min', location: { first_line: 1, last_line: 1, first_column: 1, last_column: 4 } },
+          { type: 'field', name: 'boo', location: { first_line: 1, last_line: 1, first_column: 5, last_column: 8 } },
+          { type: 'function', name: 'mul', location: { first_line: 1, last_line: 1, first_column: 20, last_column: 23 } },
+          { type: 'field', name: 'baa', location: { first_line: 1, last_line: 1, first_column: 28, last_column: 31 } }
+        ]
+      });
+    });
+
+    it('should suggest operators for "min(boo |"', function () {
+      testParser('min(boo ', '', {
+        suggestOperators: true
+      });
+    });
+  });
+})();

+ 3 - 0
desktop/core/src/desktop/templates/jasmineRunner.html

@@ -134,6 +134,9 @@
   <script type="text/javascript" src="../static/desktop/js/autocomplete/globalSearchParser.js"></script>
   <script type="text/javascript" src="../static/desktop/spec/autocomplete/globalSearchParserSpec.js"></script>
 
+  <script type="text/javascript" src="../static/desktop/js/autocomplete/solrExpressionParser.js"></script>
+  <script type="text/javascript" src="../static/desktop/spec/autocomplete/solrExpressionParserSpec.js"></script>
+
   <script type="text/javascript" charset="utf-8">
     if (/PhantomJS/.test(window.navigator.userAgent)) {
       console.log("Running Jasmine tests with JUnitXmlReporter and TerminalReporter");

+ 7 - 0
tools/jison/hue-jison.sh

@@ -60,5 +60,12 @@ grunt uglify:globalSearchParser
 cat license.txt globalSearchParser.js > ../globalSearchParser.js
 rm globalSearchParser.js
 
+# === SOLR Expression parser ===
+echo "Creating SOLR Expression parser..."
+jison solrExpressionParser.jison
+grunt uglify:solrExpressionParser
+cat license.txt solrExpressionParser.js > ../solrExpressionParser.js
+rm solrExpressionParser.js
+
 popd
 echo "Done!"

Деякі файли не було показано, через те що забагато файлів було змінено