Explorar o código

HUE-1875 [beeswax] Friendler UX when missing query property

Hard coded empty query error message.
Better messages sent by execute api if there is an error with the query.
Abraham Elmahrek %!s(int64=12) %!d(string=hai) anos
pai
achega
1cd4b2d

+ 6 - 1
apps/beeswax/src/beeswax/api.py

@@ -270,7 +270,12 @@ def execute(request, design_id=None):
         return execute_directly(request, query, design, query_server)
     else:
       response['message'] = _('There was an error with your query.')
-      response['errors'] = query_form.query.errors
+      response['errors'] = {
+        'query': [query_form.query.errors],
+        'settings': query_form.settings.errors,
+        'file_resources': query_form.file_resources.errors,
+        'functions': query_form.functions.errors,
+      }
   except RuntimeError, e:
     response['message']= str(e)
 

+ 28 - 21
apps/beeswax/src/beeswax/templates/execute.mako

@@ -37,18 +37,18 @@ ${layout.menubar(section='query')}
         </li>
         <li class="nav-header">${_('settings')}</li>
         <li class="white paramContainer">
-          <!-- ko foreach: design.settings -->
+          <!-- ko foreach: design.settings.values -->
           <div class="param">
             <div class="remove">
               <button data-bind="click: $root.removeSetting.bind(this, $index())" type="button" class="btn btn-mini settingsDelete" title="${_('Delete this setting')}">x
               </button>
             </div>
-            <div class="control-group">
+            <div data-bind="css: {'error': $root.getSettingKeyErrors($index()).length > 0}" class="control-group">
               <label>${_('Key')}</label>
               <input data-bind="value: key" type="text" class="settingsField span8" autocomplete="off" placeholder="mapred.reduce.tasks"/>
             </div>
 
-            <div class="control-group">
+            <div data-bind="css: {'error': $root.getSettingValueErrors($index()).length > 0}" class="control-group">
               <label>${_('Value')}</label>
               <input data-bind="value: value" type="text" class="settingValuesField span8" placeholder="1"/>
             </div>
@@ -71,12 +71,12 @@ ${layout.menubar(section='query')}
              hide
           % endif
           ">
-          <!-- ko foreach: design.fileResources -->
+          <!-- ko foreach: design.fileResources.values -->
           <div class="param">
             <div class="remove">
-              <button data-bind="click: $root.removeFileResources.bind(this, $index())" type="button" class="btn btn-mini" title="${_('Delete this setting')}">&times;</button>
+              <button data-bind="click: $root.removeFileResource.bind(this, $index())" type="button" class="btn btn-mini" title="${_('Delete this setting')}">&times;</button>
             </div>
-            <div class="control-group">
+            <div data-bind="css: {'error': $root.getFileResourceTypeErrors($index()).length > 0}" class="control-group">
               <label>${_('Type')}</label>
               <select data-bind="value: type" class="input-small">
                 <option value="JAR">${_('jar')}</option>
@@ -85,7 +85,7 @@ ${layout.menubar(section='query')}
               </select>
             </div>
 
-            <div class="control-group">
+            <div data-bind="css: {'error': $root.getFileResourcePathErrors($index()).length > 0}" class="control-group">
               <label>${_('Path')}</label>
               <input data-bind="value: path" type="text" class="filesField span7 pathChooser" placeholder="/user/foo/udf.jar"/>
             </div>
@@ -93,7 +93,7 @@ ${layout.menubar(section='query')}
           <!-- /ko -->
 
           <div class="control-group">
-            <a data-bind="click: function() { $root.addFileResources('','') }" class="btn btn-mini paramAdd">${_('Add')}</a>
+            <a data-bind="click: function() { $root.addFileResource('','') }" class="btn btn-mini paramAdd">${_('Add')}</a>
           </div>
         </li>
         <li title="${ _('User-Defined Functions') }" class="nav-header
@@ -108,17 +108,17 @@ ${layout.menubar(section='query')}
             hide
           % endif
           ">
-          <!-- ko foreach: design.functions -->
+          <!-- ko foreach: design.functions.values -->
           <div class="param">
             <div class="remove">
               <button data-bind="click: $root.removeFunction.bind(this, $index())" type="button" class="btn btn-mini settingsDelete" title="${_('Delete this setting')}">&times;</button>
             </div>
-            <div class="control-group">
+            <div data-bind="css: {'error': $root.getFunctionNameErrors($index()).length > 0}" class="control-group">
               <label>${_('Name')}</label>
               <input data-bind="value: name" type="text" class="functionsField span8" autocomplete="off" placeholder="myFunction"/>
             </div>
 
-            <div class="control-group">
+            <div data-bind="css: {'error': $root.getFunctionClassNameErrors($index()).length > 0}" class="control-group">
               <label>${_('Class name')}</label>
               <input data-bind="value: class_name" type="text" class="classNamesField span8" placeholder="com.acme.example"/>
             </div>
@@ -215,12 +215,19 @@ ${layout.menubar(section='query')}
       <div class="tab-content">
         <div id="queryPane">
 
-          <div data-bind="css: {'hide': design.errors().length == 0}" class="hide alert alert-error">
+          <div data-bind="css: {'hide': design.query.errors().length == 0}" class="hide alert alert-error">
+            <!-- ko if: design.query.errors().length > 0 -->
+            <p><strong>${_('Please provide a query')}</strong></p>
+            <!-- /ko -->
+            <!-- ko if: design.query.errors().length == 0 -->
             <p><strong>${_('Your query has the following error(s):')}</strong></p>
 
-            <div data-bind="foreach: design.errors">
-              <p data-bind="text: $data" class="queryErrorMessage"></p>
+            <div>
+              <div data-bind="foreach: design.errors">
+                <p data-bind="text: $data" class="queryErrorMessage"></p>
+              </div>
             </div>
+            <!-- /ko -->
           </div>
 
           <div data-bind="css: {'hide': design.watch.errors().length == 0}" class="alert alert-error">
@@ -1521,7 +1528,7 @@ $(document).on('error.query', function () {
 // Save
 function trySaveDesign() {
   var query = getHighlightedQuery() || codeMirror.getValue();
-  viewModel.design.query(query);
+  viewModel.design.query.value(query);
   if (viewModel.design.id() && viewModel.design.id() != -1) {
     viewModel.saveDesign();
   }
@@ -1529,12 +1536,12 @@ function trySaveDesign() {
 
 function saveAsModal() {
   var query = getHighlightedQuery() || codeMirror.getValue();
-  viewModel.design.query(query);
+  viewModel.design.query.value(query);
   $('#saveAs').modal('show');
 }
 
 function trySaveAsDesign() {
-  if (viewModel.design.query() && viewModel.design.name()) {
+  if (viewModel.design.query.value() && viewModel.design.name()) {
     viewModel.design.id(-1);
     viewModel.saveDesign();
     $('#saveas-query-name').removeClass('error');
@@ -1564,7 +1571,7 @@ $(document).on('saved.results', function() {
 function tryExecuteQuery() {
   $(".tooltip").remove();
   var query = getHighlightedQuery() || codeMirror.getValue();
-  viewModel.design.query(query);
+  viewModel.design.query.value(query);
   if ($(".dataTables_wrapper").length > 0) { // forces results to be up
     $(".dataTables_wrapper").scrollTop(0);
   }
@@ -1586,7 +1593,7 @@ function tryExecuteParameterizedQuery() {
 function tryExplainQuery() {
   $(".tooltip").remove();
   var query = getHighlightedQuery() || codeMirror.getValue();
-  viewModel.design.query(query);
+  viewModel.design.query.value(query);
   viewModel.explainQuery();
 }
 
@@ -1945,12 +1952,12 @@ viewModel = new BeeswaxViewModel("${app_name}");
 % endif
 if (viewModel.design.id() > 0 || viewModel.design.history.id() > 0) {
   // Code mirror and ko.
-  var codeMirrorSubscription = viewModel.design.query.subscribe(function(value) {
+  var codeMirrorSubscription = viewModel.design.query.value.subscribe(function(value) {
     codeMirror.setValue(value);
     codeMirrorSubscription.dispose();
   });
 }
-viewModel.design.fileResources.subscribe(function() {
+viewModel.design.fileResources.values.subscribe(function() {
   // File chooser button for file resources.
   $(".pathChooser:not(:has(~ button))").after(getFileBrowseButton($(".pathChooser:not(:has(~ button))")));
 });

+ 110 - 36
apps/beeswax/static/js/beeswax.vm.js

@@ -20,12 +20,24 @@ function BeeswaxViewModel(server) {
 
   var DESIGN_DEFAULTS = {
     'id': -1,
-    'query': '',
+    'query': {
+      'value': '',
+      'errors': []
+    },
     'name': null,
     'description': null,
-    'settings': [],
-    'fileResources': [],
-    'functions': [],
+    'settings': {
+      'values': [],
+      'errors': []
+    },
+    'fileResources': {
+      'values': [],
+      'errors': []
+    },
+    'functions': {
+      'values': [],
+      'errors': []
+    },
     'parameters': [],
     'isParameterized': true,
     'email': false,
@@ -109,12 +121,31 @@ function BeeswaxViewModel(server) {
     ko.mapping.fromJS(QUERY_DEFAULTS, self.design);
   };
 
+  self.resetErrors = function() {
+    self.design.errors.removeAll();
+    self.design.query.errors.removeAll();
+    self.design.settings.errors.removeAll();
+    self.design.fileResources.errors.removeAll();
+    self.design.functions.errors.removeAll();
+  };
+
+  self.setErrors = function(message, errors) {
+    self.resetErrors();
+    self.design.errors.push(message);
+    if (errors) {
+      self.design.query.errors.push.apply(self.design.query.errors, errors.query);
+      self.design.settings.errors.push.apply(self.design.settings.errors, errors.settings);
+      self.design.fileResources.errors.push.apply(self.design.fileResources.errors, errors.file_resources);
+      self.design.functions.errors.push.apply(self.design.functions.errors, errors.functions);
+    }
+  };
+
   self.updateDatabases = function(databases) {
     self.databases(databases);
   };
 
   self.updateDesign = function(design) {
-    self.design.query(design.query);
+    self.design.query.value(design.query);
     self.design.id(design.id);
     self.design.name(design.name);
     self.design.description(design.desc);
@@ -122,9 +153,9 @@ function BeeswaxViewModel(server) {
     self.design.isParameterized(design.is_parameterized);
     self.design.email(design.email_notify);
 
-    self.design.settings.removeAll();
-    self.design.fileResources.removeAll();
-    self.design.functions.removeAll();
+    self.design.settings.values.removeAll();
+    self.design.fileResources.values.removeAll();
+    self.design.functions.values.removeAll();
 
     $.each(design.settings, function(index, setting) {
       self.addSetting(setting.key, setting.value);
@@ -177,34 +208,46 @@ function BeeswaxViewModel(server) {
       'value': ko.observable(value)
     };
     obj.key.subscribe(function() {
-      self.design.settings.valueHasMutated();
+      self.design.settings.values.valueHasMutated();
     });
     obj.value.subscribe(function() {
-      self.design.settings.valueHasMutated();
+      self.design.settings.values.valueHasMutated();
     });
-    self.design.settings.push(obj);
+    self.design.settings.values.push(obj);
   };
 
   self.removeSetting = function(index) {
-    self.design.settings.splice(index, 1);
+    self.design.settings.values.splice(index, 1);
   };
 
-  self.addFileResources = function(type, path) {
+  self.addFileResource = function(type, path) {
     var obj = {
       'type': ko.observable(type),
       'path': ko.observable(path)
     };
     obj.type.subscribe(function() {
-      self.design.fileResources.valueHasMutated();
+      self.design.fileResources.values.valueHasMutated();
     });
     obj.path.subscribe(function() {
-      self.design.fileResources.valueHasMutated();
+      self.design.fileResources.values.valueHasMutated();
     });
-    self.design.fileResources.push(obj);
+    self.design.fileResources.values.push(obj);
+  };
+
+  self.removeFileResource = function(index) {
+    self.design.fileResources.values.splice(index, 1);
+  };
+
+  self.getFileResourceTypeErrors = function(index) {
+    if (self.design.settings.errors() && self.design.settings.errors()[index]) {
+      return self.design.settings.errors()[index];
+    } else {
+      return {};
+    }
   };
 
-  self.removeFileResources = function(index) {
-    self.design.fileResources.splice(index, 1);
+  self.getFileResourcePathErrors = function(index) {
+    
   };
 
   self.addFunction = function(name, class_name) {
@@ -213,18 +256,50 @@ function BeeswaxViewModel(server) {
       'class_name': ko.observable(class_name)
     };
     obj.name.subscribe(function() {
-      self.design.functions.valueHasMutated();
+      self.design.functions.values.valueHasMutated();
     });
     obj.class_name.subscribe(function() {
-      self.design.functions.valueHasMutated();
+      self.design.functions.values.valueHasMutated();
     });
-    self.design.functions.push(obj);
+    self.design.functions.values.push(obj);
   };
 
   self.removeFunction = function(index) {
-    self.design.functions.splice(index, 1);
+    self.design.functions.values.splice(index, 1);
+  };
+
+  self.getFunctionErrors = function(index) {
+    if (self.design.settings.errors() && self.design.settings.errors()[index]) {
+      return self.design.settings.errors()[index];
+    } else {
+      return {};
+    }
+  };
+
+  var advancedParameterErrorHandling = function(advanced_parameter, key) {
+    return function(index) {
+      var errors = self.design[advanced_parameter].errors();
+      if (errors && errors[index]) {
+        if (key in errors[index]) {
+          return errors[index][key];
+        } else {
+          return [];
+        }
+      } else {
+        return [];
+      }
+    };
   };
 
+  self.getSettingKeyErrors = advancedParameterErrorHandling('settings', 'key');
+  self.getSettingValueErrors = advancedParameterErrorHandling('settings', 'value');
+
+  self.getFileResourceTypeErrors = advancedParameterErrorHandling('fileResources', 'type');
+  self.getFileResourcePathErrors = advancedParameterErrorHandling('fileResources', 'path');
+
+  self.getFunctionNameErrors = advancedParameterErrorHandling('functions', 'name');
+  self.getFunctionClassNameErrors = advancedParameterErrorHandling('functions', 'class_name');
+
   function getMultiFormData(prefix, arr, members) {
     var data = {};
     var index = 0;
@@ -240,15 +315,15 @@ function BeeswaxViewModel(server) {
   }
 
   self.getSettingsFormData = function() {
-    return getMultiFormData('settings', self.design.settings(), ['key', 'value']);
+    return getMultiFormData('settings', self.design.settings.values(), ['key', 'value']);
   };
 
   self.getFileResourcesFormData = function() {
-    return getMultiFormData('file_resources', self.design.fileResources(), ['type', 'path']);
+    return getMultiFormData('file_resources', self.design.fileResources.values(), ['type', 'path']);
   };
 
   self.getFunctionsFormData = function() {
-    return getMultiFormData('functions', self.design.functions(), ['name', 'class_name']);
+    return getMultiFormData('functions', self.design.functions.values(), ['name', 'class_name']);
   };
 
   self.getParametersFormData = function() {
@@ -326,7 +401,7 @@ function BeeswaxViewModel(server) {
     $(document).trigger('fetch.parameters');
 
     var data = {
-      'query-query': self.design.query(),
+      'query-query': self.design.query.value(),
       'query-database': self.database()
     };
     $.extend(data, self.getSettingsFormData());
@@ -353,10 +428,10 @@ function BeeswaxViewModel(server) {
     self.design.explain(true);
     self.design.isRunning(true);
     self.design.isFinished(true);
-    self.design.errors.removeAll();
+    self.resetErrors();
 
     var data = {
-      'query-query': self.design.query(),
+      'query-query': self.design.query.value(),
       'query-database': self.database()
     };
     $.extend(data, self.getSettingsFormData());
@@ -369,14 +444,13 @@ function BeeswaxViewModel(server) {
       dataType: 'json',
       type: 'POST',
       success: function(data) {
-        self.design.errors.removeAll();
         if (data.status == 0) {
           self.design.watch.logs.removeAll();
           self.design.results.rows.removeAll();
           self.design.results.columns.removeAll();
           self.design.results.explanation(data.explanation);
         } else {
-          self.design.errors.push(data.message);
+          self.setErrors(data.message);
           $(document).trigger('error.query');
         }
         $(document).trigger('explained.query', data);
@@ -393,10 +467,10 @@ function BeeswaxViewModel(server) {
     self.design.explain(false);
     self.design.isRunning(true);
     self.design.isFinished(true);
-    self.design.errors.removeAll();
+    self.resetErrors();
 
     var data = {
-      'query-query': self.design.query(),
+      'query-query': self.design.query.value(),
       'query-database': self.database()
     };
     $.extend(data, self.getSettingsFormData());
@@ -421,7 +495,7 @@ function BeeswaxViewModel(server) {
           self.design.history.id(data.id);
           self.watchQueryLoop();
         } else {
-          self.design.errors.push(data.message);
+          self.setErrors(data.message, data.errors);
           self.design.isRunning(false);
           $(document).trigger('error.query');
         }
@@ -464,7 +538,7 @@ function BeeswaxViewModel(server) {
 
   self.watchQuery = function() {
     var data = {
-      'query-query': self.design.query(),
+      'query-query': self.design.query.value(),
       'query-database': self.database()
     };
     $.extend(data, self.getSettingsFormData());
@@ -572,9 +646,9 @@ function BeeswaxViewModel(server) {
 
   self.saveDesign = function() {
     var self = this;
-    if (self.design.query() && self.design.name()) {
+    if (self.design.query.value() && self.design.name()) {
       var data = {
-        'query-query': self.design.query(),
+        'query-query': self.design.query.value(),
         'query-database': self.database()
       };
       $.extend(data, self.getSettingsFormData());