浏览代码

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 12 年之前
父节点
当前提交
1cd4b2d
共有 3 个文件被更改,包括 144 次插入58 次删除
  1. 6 1
      apps/beeswax/src/beeswax/api.py
  2. 28 21
      apps/beeswax/src/beeswax/templates/execute.mako
  3. 110 36
      apps/beeswax/static/js/beeswax.vm.js

+ 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());