소스 검색

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

Johan Ahlen 8 년 전
부모
커밋
2341c19ed7

+ 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">

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.