Ver Fonte

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 há 12 anos atrás
pai
commit
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)
         return execute_directly(request, query, design, query_server)
     else:
     else:
       response['message'] = _('There was an error with your query.')
       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:
   except RuntimeError, e:
     response['message']= str(e)
     response['message']= str(e)
 
 

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

@@ -37,18 +37,18 @@ ${layout.menubar(section='query')}
         </li>
         </li>
         <li class="nav-header">${_('settings')}</li>
         <li class="nav-header">${_('settings')}</li>
         <li class="white paramContainer">
         <li class="white paramContainer">
-          <!-- ko foreach: design.settings -->
+          <!-- ko foreach: design.settings.values -->
           <div class="param">
           <div class="param">
             <div class="remove">
             <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 data-bind="click: $root.removeSetting.bind(this, $index())" type="button" class="btn btn-mini settingsDelete" title="${_('Delete this setting')}">x
               </button>
               </button>
             </div>
             </div>
-            <div class="control-group">
+            <div data-bind="css: {'error': $root.getSettingKeyErrors($index()).length > 0}" class="control-group">
               <label>${_('Key')}</label>
               <label>${_('Key')}</label>
               <input data-bind="value: key" type="text" class="settingsField span8" autocomplete="off" placeholder="mapred.reduce.tasks"/>
               <input data-bind="value: key" type="text" class="settingsField span8" autocomplete="off" placeholder="mapred.reduce.tasks"/>
             </div>
             </div>
 
 
-            <div class="control-group">
+            <div data-bind="css: {'error': $root.getSettingValueErrors($index()).length > 0}" class="control-group">
               <label>${_('Value')}</label>
               <label>${_('Value')}</label>
               <input data-bind="value: value" type="text" class="settingValuesField span8" placeholder="1"/>
               <input data-bind="value: value" type="text" class="settingValuesField span8" placeholder="1"/>
             </div>
             </div>
@@ -71,12 +71,12 @@ ${layout.menubar(section='query')}
              hide
              hide
           % endif
           % endif
           ">
           ">
-          <!-- ko foreach: design.fileResources -->
+          <!-- ko foreach: design.fileResources.values -->
           <div class="param">
           <div class="param">
             <div class="remove">
             <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>
-            <div class="control-group">
+            <div data-bind="css: {'error': $root.getFileResourceTypeErrors($index()).length > 0}" class="control-group">
               <label>${_('Type')}</label>
               <label>${_('Type')}</label>
               <select data-bind="value: type" class="input-small">
               <select data-bind="value: type" class="input-small">
                 <option value="JAR">${_('jar')}</option>
                 <option value="JAR">${_('jar')}</option>
@@ -85,7 +85,7 @@ ${layout.menubar(section='query')}
               </select>
               </select>
             </div>
             </div>
 
 
-            <div class="control-group">
+            <div data-bind="css: {'error': $root.getFileResourcePathErrors($index()).length > 0}" class="control-group">
               <label>${_('Path')}</label>
               <label>${_('Path')}</label>
               <input data-bind="value: path" type="text" class="filesField span7 pathChooser" placeholder="/user/foo/udf.jar"/>
               <input data-bind="value: path" type="text" class="filesField span7 pathChooser" placeholder="/user/foo/udf.jar"/>
             </div>
             </div>
@@ -93,7 +93,7 @@ ${layout.menubar(section='query')}
           <!-- /ko -->
           <!-- /ko -->
 
 
           <div class="control-group">
           <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>
           </div>
         </li>
         </li>
         <li title="${ _('User-Defined Functions') }" class="nav-header
         <li title="${ _('User-Defined Functions') }" class="nav-header
@@ -108,17 +108,17 @@ ${layout.menubar(section='query')}
             hide
             hide
           % endif
           % endif
           ">
           ">
-          <!-- ko foreach: design.functions -->
+          <!-- ko foreach: design.functions.values -->
           <div class="param">
           <div class="param">
             <div class="remove">
             <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>
               <button data-bind="click: $root.removeFunction.bind(this, $index())" type="button" class="btn btn-mini settingsDelete" title="${_('Delete this setting')}">&times;</button>
             </div>
             </div>
-            <div class="control-group">
+            <div data-bind="css: {'error': $root.getFunctionNameErrors($index()).length > 0}" class="control-group">
               <label>${_('Name')}</label>
               <label>${_('Name')}</label>
               <input data-bind="value: name" type="text" class="functionsField span8" autocomplete="off" placeholder="myFunction"/>
               <input data-bind="value: name" type="text" class="functionsField span8" autocomplete="off" placeholder="myFunction"/>
             </div>
             </div>
 
 
-            <div class="control-group">
+            <div data-bind="css: {'error': $root.getFunctionClassNameErrors($index()).length > 0}" class="control-group">
               <label>${_('Class name')}</label>
               <label>${_('Class name')}</label>
               <input data-bind="value: class_name" type="text" class="classNamesField span8" placeholder="com.acme.example"/>
               <input data-bind="value: class_name" type="text" class="classNamesField span8" placeholder="com.acme.example"/>
             </div>
             </div>
@@ -215,12 +215,19 @@ ${layout.menubar(section='query')}
       <div class="tab-content">
       <div class="tab-content">
         <div id="queryPane">
         <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>
             <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>
             </div>
+            <!-- /ko -->
           </div>
           </div>
 
 
           <div data-bind="css: {'hide': design.watch.errors().length == 0}" class="alert alert-error">
           <div data-bind="css: {'hide': design.watch.errors().length == 0}" class="alert alert-error">
@@ -1521,7 +1528,7 @@ $(document).on('error.query', function () {
 // Save
 // Save
 function trySaveDesign() {
 function trySaveDesign() {
   var query = getHighlightedQuery() || codeMirror.getValue();
   var query = getHighlightedQuery() || codeMirror.getValue();
-  viewModel.design.query(query);
+  viewModel.design.query.value(query);
   if (viewModel.design.id() && viewModel.design.id() != -1) {
   if (viewModel.design.id() && viewModel.design.id() != -1) {
     viewModel.saveDesign();
     viewModel.saveDesign();
   }
   }
@@ -1529,12 +1536,12 @@ function trySaveDesign() {
 
 
 function saveAsModal() {
 function saveAsModal() {
   var query = getHighlightedQuery() || codeMirror.getValue();
   var query = getHighlightedQuery() || codeMirror.getValue();
-  viewModel.design.query(query);
+  viewModel.design.query.value(query);
   $('#saveAs').modal('show');
   $('#saveAs').modal('show');
 }
 }
 
 
 function trySaveAsDesign() {
 function trySaveAsDesign() {
-  if (viewModel.design.query() && viewModel.design.name()) {
+  if (viewModel.design.query.value() && viewModel.design.name()) {
     viewModel.design.id(-1);
     viewModel.design.id(-1);
     viewModel.saveDesign();
     viewModel.saveDesign();
     $('#saveas-query-name').removeClass('error');
     $('#saveas-query-name').removeClass('error');
@@ -1564,7 +1571,7 @@ $(document).on('saved.results', function() {
 function tryExecuteQuery() {
 function tryExecuteQuery() {
   $(".tooltip").remove();
   $(".tooltip").remove();
   var query = getHighlightedQuery() || codeMirror.getValue();
   var query = getHighlightedQuery() || codeMirror.getValue();
-  viewModel.design.query(query);
+  viewModel.design.query.value(query);
   if ($(".dataTables_wrapper").length > 0) { // forces results to be up
   if ($(".dataTables_wrapper").length > 0) { // forces results to be up
     $(".dataTables_wrapper").scrollTop(0);
     $(".dataTables_wrapper").scrollTop(0);
   }
   }
@@ -1586,7 +1593,7 @@ function tryExecuteParameterizedQuery() {
 function tryExplainQuery() {
 function tryExplainQuery() {
   $(".tooltip").remove();
   $(".tooltip").remove();
   var query = getHighlightedQuery() || codeMirror.getValue();
   var query = getHighlightedQuery() || codeMirror.getValue();
-  viewModel.design.query(query);
+  viewModel.design.query.value(query);
   viewModel.explainQuery();
   viewModel.explainQuery();
 }
 }
 
 
@@ -1945,12 +1952,12 @@ viewModel = new BeeswaxViewModel("${app_name}");
 % endif
 % endif
 if (viewModel.design.id() > 0 || viewModel.design.history.id() > 0) {
 if (viewModel.design.id() > 0 || viewModel.design.history.id() > 0) {
   // Code mirror and ko.
   // Code mirror and ko.
-  var codeMirrorSubscription = viewModel.design.query.subscribe(function(value) {
+  var codeMirrorSubscription = viewModel.design.query.value.subscribe(function(value) {
     codeMirror.setValue(value);
     codeMirror.setValue(value);
     codeMirrorSubscription.dispose();
     codeMirrorSubscription.dispose();
   });
   });
 }
 }
-viewModel.design.fileResources.subscribe(function() {
+viewModel.design.fileResources.values.subscribe(function() {
   // File chooser button for file resources.
   // File chooser button for file resources.
   $(".pathChooser:not(:has(~ button))").after(getFileBrowseButton($(".pathChooser:not(:has(~ button))")));
   $(".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 = {
   var DESIGN_DEFAULTS = {
     'id': -1,
     'id': -1,
-    'query': '',
+    'query': {
+      'value': '',
+      'errors': []
+    },
     'name': null,
     'name': null,
     'description': null,
     'description': null,
-    'settings': [],
-    'fileResources': [],
-    'functions': [],
+    'settings': {
+      'values': [],
+      'errors': []
+    },
+    'fileResources': {
+      'values': [],
+      'errors': []
+    },
+    'functions': {
+      'values': [],
+      'errors': []
+    },
     'parameters': [],
     'parameters': [],
     'isParameterized': true,
     'isParameterized': true,
     'email': false,
     'email': false,
@@ -109,12 +121,31 @@ function BeeswaxViewModel(server) {
     ko.mapping.fromJS(QUERY_DEFAULTS, self.design);
     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.updateDatabases = function(databases) {
     self.databases(databases);
     self.databases(databases);
   };
   };
 
 
   self.updateDesign = function(design) {
   self.updateDesign = function(design) {
-    self.design.query(design.query);
+    self.design.query.value(design.query);
     self.design.id(design.id);
     self.design.id(design.id);
     self.design.name(design.name);
     self.design.name(design.name);
     self.design.description(design.desc);
     self.design.description(design.desc);
@@ -122,9 +153,9 @@ function BeeswaxViewModel(server) {
     self.design.isParameterized(design.is_parameterized);
     self.design.isParameterized(design.is_parameterized);
     self.design.email(design.email_notify);
     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) {
     $.each(design.settings, function(index, setting) {
       self.addSetting(setting.key, setting.value);
       self.addSetting(setting.key, setting.value);
@@ -177,34 +208,46 @@ function BeeswaxViewModel(server) {
       'value': ko.observable(value)
       'value': ko.observable(value)
     };
     };
     obj.key.subscribe(function() {
     obj.key.subscribe(function() {
-      self.design.settings.valueHasMutated();
+      self.design.settings.values.valueHasMutated();
     });
     });
     obj.value.subscribe(function() {
     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.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 = {
     var obj = {
       'type': ko.observable(type),
       'type': ko.observable(type),
       'path': ko.observable(path)
       'path': ko.observable(path)
     };
     };
     obj.type.subscribe(function() {
     obj.type.subscribe(function() {
-      self.design.fileResources.valueHasMutated();
+      self.design.fileResources.values.valueHasMutated();
     });
     });
     obj.path.subscribe(function() {
     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) {
   self.addFunction = function(name, class_name) {
@@ -213,18 +256,50 @@ function BeeswaxViewModel(server) {
       'class_name': ko.observable(class_name)
       'class_name': ko.observable(class_name)
     };
     };
     obj.name.subscribe(function() {
     obj.name.subscribe(function() {
-      self.design.functions.valueHasMutated();
+      self.design.functions.values.valueHasMutated();
     });
     });
     obj.class_name.subscribe(function() {
     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.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) {
   function getMultiFormData(prefix, arr, members) {
     var data = {};
     var data = {};
     var index = 0;
     var index = 0;
@@ -240,15 +315,15 @@ function BeeswaxViewModel(server) {
   }
   }
 
 
   self.getSettingsFormData = function() {
   self.getSettingsFormData = function() {
-    return getMultiFormData('settings', self.design.settings(), ['key', 'value']);
+    return getMultiFormData('settings', self.design.settings.values(), ['key', 'value']);
   };
   };
 
 
   self.getFileResourcesFormData = function() {
   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() {
   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() {
   self.getParametersFormData = function() {
@@ -326,7 +401,7 @@ function BeeswaxViewModel(server) {
     $(document).trigger('fetch.parameters');
     $(document).trigger('fetch.parameters');
 
 
     var data = {
     var data = {
-      'query-query': self.design.query(),
+      'query-query': self.design.query.value(),
       'query-database': self.database()
       'query-database': self.database()
     };
     };
     $.extend(data, self.getSettingsFormData());
     $.extend(data, self.getSettingsFormData());
@@ -353,10 +428,10 @@ function BeeswaxViewModel(server) {
     self.design.explain(true);
     self.design.explain(true);
     self.design.isRunning(true);
     self.design.isRunning(true);
     self.design.isFinished(true);
     self.design.isFinished(true);
-    self.design.errors.removeAll();
+    self.resetErrors();
 
 
     var data = {
     var data = {
-      'query-query': self.design.query(),
+      'query-query': self.design.query.value(),
       'query-database': self.database()
       'query-database': self.database()
     };
     };
     $.extend(data, self.getSettingsFormData());
     $.extend(data, self.getSettingsFormData());
@@ -369,14 +444,13 @@ function BeeswaxViewModel(server) {
       dataType: 'json',
       dataType: 'json',
       type: 'POST',
       type: 'POST',
       success: function(data) {
       success: function(data) {
-        self.design.errors.removeAll();
         if (data.status == 0) {
         if (data.status == 0) {
           self.design.watch.logs.removeAll();
           self.design.watch.logs.removeAll();
           self.design.results.rows.removeAll();
           self.design.results.rows.removeAll();
           self.design.results.columns.removeAll();
           self.design.results.columns.removeAll();
           self.design.results.explanation(data.explanation);
           self.design.results.explanation(data.explanation);
         } else {
         } else {
-          self.design.errors.push(data.message);
+          self.setErrors(data.message);
           $(document).trigger('error.query');
           $(document).trigger('error.query');
         }
         }
         $(document).trigger('explained.query', data);
         $(document).trigger('explained.query', data);
@@ -393,10 +467,10 @@ function BeeswaxViewModel(server) {
     self.design.explain(false);
     self.design.explain(false);
     self.design.isRunning(true);
     self.design.isRunning(true);
     self.design.isFinished(true);
     self.design.isFinished(true);
-    self.design.errors.removeAll();
+    self.resetErrors();
 
 
     var data = {
     var data = {
-      'query-query': self.design.query(),
+      'query-query': self.design.query.value(),
       'query-database': self.database()
       'query-database': self.database()
     };
     };
     $.extend(data, self.getSettingsFormData());
     $.extend(data, self.getSettingsFormData());
@@ -421,7 +495,7 @@ function BeeswaxViewModel(server) {
           self.design.history.id(data.id);
           self.design.history.id(data.id);
           self.watchQueryLoop();
           self.watchQueryLoop();
         } else {
         } else {
-          self.design.errors.push(data.message);
+          self.setErrors(data.message, data.errors);
           self.design.isRunning(false);
           self.design.isRunning(false);
           $(document).trigger('error.query');
           $(document).trigger('error.query');
         }
         }
@@ -464,7 +538,7 @@ function BeeswaxViewModel(server) {
 
 
   self.watchQuery = function() {
   self.watchQuery = function() {
     var data = {
     var data = {
-      'query-query': self.design.query(),
+      'query-query': self.design.query.value(),
       'query-database': self.database()
       'query-database': self.database()
     };
     };
     $.extend(data, self.getSettingsFormData());
     $.extend(data, self.getSettingsFormData());
@@ -572,9 +646,9 @@ function BeeswaxViewModel(server) {
 
 
   self.saveDesign = function() {
   self.saveDesign = function() {
     var self = this;
     var self = this;
-    if (self.design.query() && self.design.name()) {
+    if (self.design.query.value() && self.design.name()) {
       var data = {
       var data = {
-        'query-query': self.design.query(),
+        'query-query': self.design.query.value(),
         'query-database': self.database()
         'query-database': self.database()
       };
       };
       $.extend(data, self.getSettingsFormData());
       $.extend(data, self.getSettingsFormData());