浏览代码

HUE-7062 [dashboard] Convert arithmetic operators to their corresponding functions in the formula editor

Johan Ahlen 8 年之前
父节点
当前提交
2341c19

+ 52 - 15
desktop/core/src/desktop/static/desktop/js/autocomplete/jison/solrExpressionParser.jison

@@ -27,9 +27,9 @@
 [0-9]+(?:[,.][0-9]+)?                      { return 'NUMBER'; }
 
 '-'                                        { return '-'; }
-'*'                                        { return 'OPERATOR'; }
-'+'                                        { return 'OPERATOR'; }
-'/'                                        { return 'OPERATOR'; }
+'+'                                        { return '+'; }
+'*'                                        { return '*'; }
+'/'                                        { return '/'; }
 
 [a-z]+\s*\(                                {
                                              yy.lexer.unput('(');
@@ -52,7 +52,8 @@
 
 /lex
 
-%left '-' 'OPERATOR'
+%left '+' '-'
+%left '*' '/'
 
 %start SolrExpressionAutocomplete
 
@@ -61,7 +62,9 @@
 SolrExpressionAutocomplete
  : SolrExpression 'EOF'
    {
-     return {};
+     return {
+       parsedValue: $1
+     };
    }
  | SolrExpression_EDIT 'EOF'
    {
@@ -75,7 +78,7 @@ SolrExpressionAutocomplete
 
 SolrExpression
  : NonParenthesizedSolrExpression
- | '(' NonParenthesizedSolrExpression ')'
+ | '(' NonParenthesizedSolrExpression ')'  -> $1 + $2 + $3
  ;
 
 SolrExpression_EDIT
@@ -86,10 +89,12 @@ SolrExpression_EDIT
 NonParenthesizedSolrExpression
  : 'NUMBER'
  | 'IDENTIFIER'
- | 'FUNCTION' '(' ArgumentList ')'
- | SolrExpression 'OPERATOR' SolrExpression
- | SolrExpression '-' SolrExpression
- | '-' SolrExpression
+ | 'FUNCTION' '(' ArgumentList ')'    -> $1 + $2 + $3 + $4
+ | SolrExpression '+' SolrExpression  -> 'sum(' + $1 + ',' + $3 + ')'
+ | SolrExpression '-' SolrExpression  -> 'sub(' + $1 + ',' + $3 + ')'
+ | SolrExpression '*' SolrExpression  -> 'mul(' + $1 + ',' + $3 + ')'
+ | SolrExpression '/' SolrExpression  -> 'div(' + $1 + ',' + $3 + ')'
+ | '-' SolrExpression                 -> 'sub(0,' + $2 + ')'
  ;
 
 NonParenthesizedSolrExpression_EDIT
@@ -106,10 +111,10 @@ NonParenthesizedSolrExpression_EDIT
  ;
 
 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
+ : SolrExpression '+' 'CURSOR'                                       --> { suggestFunctions: true, suggestFields: true }
+ | 'CURSOR' '+' SolrExpression                                       --> { suggestFunctions: true, suggestFields: true }
+ | SolrExpression_EDIT '+' SolrExpression                            --> $1
+ | SolrExpression '+' SolrExpression_EDIT                            --> $3
  ;
 
 NonParenthesizedSolrExpression_EDIT
@@ -119,6 +124,20 @@ NonParenthesizedSolrExpression_EDIT
  | SolrExpression '-' 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
+ : 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
@@ -179,7 +198,25 @@ parser.addFieldLocation = function (location, name) {
   parser.yy.locations.push({ type: 'field', name: name, location: adjustLocationForCursor(location) });
 }
 
-parser.parseSolrExpression = function (beforeCursor, afterCursor, debug) {
+parser.parseSolrExpression = function (expression, debug) {
+  parser.yy.cursorFound = false;
+  parser.yy.locations = [];
+  expression = expression.replace(/\r\n|\n\r/gm, '\n');
+
+  var result;
+  try {
+    result = parser.parse(expression);
+  } catch (err) {
+    if (debug) {
+      console.log(beforeCursor + '\u2020' + afterCursor);
+      console.log(err);
+      console.error(err.stack);
+    }
+  }
+  return result || false;
+}
+
+parser.autocompleteSolrExpression = function (beforeCursor, afterCursor, debug) {
   parser.yy.cursorFound = false;
   parser.yy.locations = [];
 

文件差异内容过多而无法显示
+ 0 - 0
desktop/core/src/desktop/static/desktop/js/autocomplete/solrExpressionParser.js


+ 111 - 48
desktop/core/src/desktop/static/desktop/spec/autocomplete/solrExpressionParserSpec.js

@@ -16,67 +16,130 @@
 (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 aggregate functions for "|"', function () {
-      testParser('', '', {
-        suggestAggregateFunctions: true
+    describe('autocomplete', function () {
+      var testAutocomplete = function (beforeCursor, afterCursor, expectedResult) {
+        var result = solrExpressionParser.autocompleteSolrExpression(beforeCursor, afterCursor, true);
+        if (!expectedResult.locations) {
+          delete result.locations;
+        }
+        expect(result).toEqual(expectedResult);
+      };
+
+      it('should suggest aggregate functions for "|"', function () {
+        testAutocomplete('', '', {
+          suggestAggregateFunctions: true
+        });
       });
-    });
 
-    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(|"', function () {
+        testAutocomplete('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 + |"', function () {
+        testAutocomplete('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(boo + | + baa)"', function () {
+        testAutocomplete('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 functions and fields for "min(1- max(|"', function () {
+        testAutocomplete('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 + 1) - 4 + mul(10, baa)|"', function () {
+        testAutocomplete('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
+      it('should suggest operators for "min(boo |"', function () {
+        testAutocomplete('min(boo ', '', {
+          suggestOperators: true
+        });
       });
     });
+
+    describe('parse', function () {
+      var testParse = function (expression, expectedResult) {
+        var result = solrExpressionParser.parseSolrExpression(expression);
+        expect(result).toBeTruthy();
+        expect(result).toEqual(expectedResult);
+      };
+
+      it('should parse "min(boo)"', function () {
+        testParse('min(boo)', {
+          parsedValue: 'min(boo)'
+        });
+      });
+
+      it('should fail parsing "min(boo"', function () {
+        var result = solrExpressionParser.parseSolrExpression('min(boo');
+        expect(result).toBeFalsy();
+      });
+
+      it('should convert + to sum for "min(boo + 1)"', function () {
+        testParse('min(boo + 1)', {
+          parsedValue: 'min(sum(boo,1))'
+        });
+      });
+
+      it('should convert - to sub for "10 - min(boo + 1)"', function () {
+        testParse('10 - min(boo + 1)', {
+          parsedValue: 'sub(10,min(sum(boo,1)))'
+        });
+      });
+
+      it('should convert - to sub for "-min(boo)"', function () {
+        testParse('-min(boo)', {
+          parsedValue: 'sub(0,min(boo))'
+        });
+      });
+
+      it('should convert / to div for "min(boo)/10"', function () {
+        testParse('min(boo)/10', {
+          parsedValue: 'div(min(boo),10)'
+        });
+      });
+
+      it('should convert * to mul for "1*2*3*4"', function () {
+        testParse('1*2*3*4', {
+          parsedValue: 'mul(mul(mul(1,2),3),4)'
+        });
+      });
+
+      it('should handle precedence properly  "1*2+3*4"', function () {
+        testParse('1*2+3*4', {
+          parsedValue: 'sum(mul(1,2),mul(3,4))'
+        });
+      });
+
+      it('should handle precedence properly with parentheses "1*(2+3)*4"', function () {
+        testParse('1*(2+3)*4', {
+          parsedValue: 'mul(mul(1,(sum(2,3))),4)'
+        });
+      });
+    })
   });
 })();

+ 26 - 1
desktop/core/src/desktop/templates/ko_components/ko_simple_ace_editor.mako

@@ -212,6 +212,13 @@ from desktop.views import _ko
             draggable: 'ms()',
             description: 'Returns milliseconds of difference between its arguments. Dates are relative to the Unix or POSIX time epoch, midnight, January 1, 1970 UTC. Arguments may be the name of an indexed TrieDateField, or date math based on a constant date or NOW.\n\n- ms(): Equivalent to ms(NOW), number of milliseconds since the epoch.\n- ms(a): Returns the number of milliseconds since the epoch that the argument represents.\n- ms(a,b) : Returns the number of milliseconds that b occurs before a (that is, a - b)'
           },
+          mul: {
+            returnTypes: ['T'],
+            arguments: [[{type: 'T', multiple: true}]],
+            signature: 'mul(x, y, ...)',
+            draggable: 'mul()',
+            description: 'Returns the product of multiple values or functions, which are specified in a comma-separated list. Same as the product function.'
+          },
           norm: {
             returnTypes: ['T'],
             arguments: [[{type: 'T'}]],
@@ -549,10 +556,14 @@ from desktop.views import _ko
 
         SolrFormulaAutocompleter.prototype.autocomplete = function () {
           var self = this;
-          var parseResult = solrExpressionParser.parseSolrExpression(self.editor.getTextBeforeCursor(), self.editor.getTextAfterCursor());
+          var parseResult = solrExpressionParser.autocompleteSolrExpression(self.editor.getTextBeforeCursor(), self.editor.getTextAfterCursor());
           self.suggestions.update(parseResult);
         };
 
+        SolrFormulaAutocompleter.prototype.parse = function (value) {
+          return solrExpressionParser.parseSolrExpression(value);
+        };
+
         return SolrFormulaAutocompleter;
       })();
 
@@ -564,6 +575,7 @@ from desktop.views import _ko
         var $element = $(element);
         var self = this;
         self.value = params.value;
+        self.parsedValue = params.parsedValue;
         self.ace = ko.observable();
         self.disposeFunctions = [];
 
@@ -665,8 +677,21 @@ from desktop.views import _ko
           });
         }
 
+        var parseThrottle = -1;
         var inputListener = editor.on('input', function () {
           self.value(editor.getValue());
+          if (self.parsedValue && self.autocompleter && self.autocompleter.parse) {
+            window.clearTimeout(parseThrottle);
+            parseThrottle = window.setTimeout(function () {
+              var parseResult = self.autocompleter.parse(editor.getValue());
+              if (parseResult) {
+                self.parsedValue(parseResult.parsedValue)
+              } else {
+                // TODO: What to do when we can't parse?
+                self.parsedValue(editor.getValue());
+              }
+            }, 200);
+          }
         });
 
         self.disposeFunctions.push(function () {

+ 1 - 2
desktop/libs/dashboard/src/dashboard/templates/common_search.mako

@@ -2007,8 +2007,7 @@ ${ dashboard.layout_skeleton(suffix='search') }
       <div class="clearfix"></div>
       <br/>
 
-      <div data-bind="component: { name: 'hue-simple-ace-editor', params: { value: formula, autocomplete: { type: 'solrFormula', support: { fields: $root.collection.template.fieldsAttributes } }, singleLine: true } }, visible: $parent.field() == 'formula'"></div>
-      <input data-bind="value: plain_formula" type="hidden"></input>
+      <div data-bind="component: { name: 'hue-simple-ace-editor', params: { value: plain_formula, parsedValue: formula, autocomplete: { type: 'solrFormula', support: { fields: $root.collection.template.fieldsAttributes } }, singleLine: true } }, visible: $parent.field() == 'formula'"></div>
 
       <!-- ko if: $data.function() != 'field' && $parents[1].widgetType() != 'hit-widget' -->
         <div class="facet-field-cnt">

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