Browse Source

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

Johan Ahlen 8 years ago
parent
commit
2341c19

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

@@ -27,9 +27,9 @@
 [0-9]+(?:[,.][0-9]+)?                      { return 'NUMBER'; }
 [0-9]+(?:[,.][0-9]+)?                      { return 'NUMBER'; }
 
 
 '-'                                        { return '-'; }
 '-'                                        { return '-'; }
-'*'                                        { return 'OPERATOR'; }
-'+'                                        { return 'OPERATOR'; }
-'/'                                        { return 'OPERATOR'; }
+'+'                                        { return '+'; }
+'*'                                        { return '*'; }
+'/'                                        { return '/'; }
 
 
 [a-z]+\s*\(                                {
 [a-z]+\s*\(                                {
                                              yy.lexer.unput('(');
                                              yy.lexer.unput('(');
@@ -52,7 +52,8 @@
 
 
 /lex
 /lex
 
 
-%left '-' 'OPERATOR'
+%left '+' '-'
+%left '*' '/'
 
 
 %start SolrExpressionAutocomplete
 %start SolrExpressionAutocomplete
 
 
@@ -61,7 +62,9 @@
 SolrExpressionAutocomplete
 SolrExpressionAutocomplete
  : SolrExpression 'EOF'
  : SolrExpression 'EOF'
    {
    {
-     return {};
+     return {
+       parsedValue: $1
+     };
    }
    }
  | SolrExpression_EDIT 'EOF'
  | SolrExpression_EDIT 'EOF'
    {
    {
@@ -75,7 +78,7 @@ SolrExpressionAutocomplete
 
 
 SolrExpression
 SolrExpression
  : NonParenthesizedSolrExpression
  : NonParenthesizedSolrExpression
- | '(' NonParenthesizedSolrExpression ')'
+ | '(' NonParenthesizedSolrExpression ')'  -> $1 + $2 + $3
  ;
  ;
 
 
 SolrExpression_EDIT
 SolrExpression_EDIT
@@ -86,10 +89,12 @@ SolrExpression_EDIT
 NonParenthesizedSolrExpression
 NonParenthesizedSolrExpression
  : 'NUMBER'
  : 'NUMBER'
  | 'IDENTIFIER'
  | '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
 NonParenthesizedSolrExpression_EDIT
@@ -106,10 +111,10 @@ NonParenthesizedSolrExpression_EDIT
  ;
  ;
 
 
 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
 NonParenthesizedSolrExpression_EDIT
@@ -119,6 +124,20 @@ NonParenthesizedSolrExpression_EDIT
  | SolrExpression '-' SolrExpression_EDIT                            --> $3
  | 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
 NonParenthesizedSolrExpression_EDIT
  : '-' 'CURSOR'                                                      --> { suggestFunctions: true, suggestFields: true }
  : '-' 'CURSOR'                                                      --> { suggestFunctions: true, suggestFields: true }
  | '-' SolrExpression_EDIT                                           --> $2
  | '-' SolrExpression_EDIT                                           --> $2
@@ -179,7 +198,25 @@ parser.addFieldLocation = function (location, name) {
   parser.yy.locations.push({ type: 'field', name: name, location: adjustLocationForCursor(location) });
   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.cursorFound = false;
   parser.yy.locations = [];
   parser.yy.locations = [];
 
 

File diff suppressed because it is too large
+ 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 () {
 (function () {
   describe('solrExpressionParser.js', 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()',
             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)'
             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: {
           norm: {
             returnTypes: ['T'],
             returnTypes: ['T'],
             arguments: [[{type: 'T'}]],
             arguments: [[{type: 'T'}]],
@@ -549,10 +556,14 @@ from desktop.views import _ko
 
 
         SolrFormulaAutocompleter.prototype.autocomplete = function () {
         SolrFormulaAutocompleter.prototype.autocomplete = function () {
           var self = this;
           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);
           self.suggestions.update(parseResult);
         };
         };
 
 
+        SolrFormulaAutocompleter.prototype.parse = function (value) {
+          return solrExpressionParser.parseSolrExpression(value);
+        };
+
         return SolrFormulaAutocompleter;
         return SolrFormulaAutocompleter;
       })();
       })();
 
 
@@ -564,6 +575,7 @@ from desktop.views import _ko
         var $element = $(element);
         var $element = $(element);
         var self = this;
         var self = this;
         self.value = params.value;
         self.value = params.value;
+        self.parsedValue = params.parsedValue;
         self.ace = ko.observable();
         self.ace = ko.observable();
         self.disposeFunctions = [];
         self.disposeFunctions = [];
 
 
@@ -665,8 +677,21 @@ from desktop.views import _ko
           });
           });
         }
         }
 
 
+        var parseThrottle = -1;
         var inputListener = editor.on('input', function () {
         var inputListener = editor.on('input', function () {
           self.value(editor.getValue());
           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 () {
         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>
       <div class="clearfix"></div>
       <br/>
       <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' -->
       <!-- ko if: $data.function() != 'field' && $parents[1].widgetType() != 'hit-widget' -->
         <div class="facet-field-cnt">
         <div class="facet-field-cnt">

Some files were not shown because too many files changed in this diff