瀏覽代碼

HUE-1842 [beeswax] Error info above query box instead of the popup

Make error messages appear on bottom half of the screen if
the error occurs on submission.
Abraham Elmahrek 12 年之前
父節點
當前提交
63558b1663
共有 3 個文件被更改,包括 89 次插入36 次删除
  1. 23 15
      apps/beeswax/src/beeswax/api.py
  2. 43 12
      apps/beeswax/src/beeswax/templates/execute.mako
  3. 23 9
      apps/beeswax/static/js/beeswax.vm.js

+ 23 - 15
apps/beeswax/src/beeswax/api.py

@@ -53,13 +53,19 @@ def error_handler(view_fn):
     except Exception, e:
       if not hasattr(e, 'message') or not e.message:
         message = _("Unknown exception.")
+        response = {
+          'status': -1,
+          'error': str(e),
+          'message': message,
+        }
       else:
         message = force_unicode(e.message, strings_only=True, errors='replace')
-      response = {
-        'error': str(e),
-        'message': message,
-      }
-      return HttpResponse(json.dumps(response), mimetype="application/json", status=500)
+        response = {
+          'status': 1,
+          'error': str(e),
+          'message': message,
+        }
+      return HttpResponse(json.dumps(response), mimetype="application/json", status=200)
   return decorator
 
 
@@ -139,15 +145,6 @@ def watch_query_refresh_json(request, id):
   handle, state = _get_query_handle_and_state(query_history)
   query_history.save_state(state)
 
-  # Show popup message if error, should be better in error tab instead and merged into the result response below
-  if query_history.is_failure():
-    res = db.get_operation_status(handle)
-    if hasattr(res, 'errorMessage') and res.errorMessage:
-      message = res.errorMessage
-    else:
-      message = ''
-    raise QueryServerException(Exception('Bad status for request %s:\n%s' % (id, res)), message=message)
-
   # Multi query if more statements
   try:
     if not query_history.is_finished() and query_history.is_success() and not query_history.has_results:
@@ -169,11 +166,22 @@ def watch_query_refresh_json(request, id):
     'log': log,
     'jobs': jobs,
     'jobUrls': job_urls,
-    'isSuccess': query_history.is_finished() or (query_history.is_success() and query_history.has_results),
+    'isSuccess': query_history.is_finished() or (query_history.is_success() and query_history.has_results and query_history.is_failure()),
     'isFailure': query_history.is_failure(),
     'id': id
   }
 
+  # Show popup message if error, should be better in error tab instead and merged into the result response below
+  if query_history.is_failure():
+    res = db.get_operation_status(handle)
+    if hasattr(res, 'errorMessage') and res.errorMessage:
+      result['message'] = res.errorMessage
+    else:
+      result['message'] = ''
+    
+    result['error'] = 'Bad status for request %s:\n%s' % (id, res)
+    result['status'] = 1
+
   return HttpResponse(json.dumps(result), mimetype="application/json")
 
 

+ 43 - 12
apps/beeswax/src/beeswax/templates/execute.mako

@@ -96,11 +96,10 @@ ${layout.menubar(section='query')}
             <a data-bind="click: function() { $root.addFileResources('','') }" class="btn btn-mini paramAdd">${_('Add')}</a>
           </div>
         </li>
-        <li class="nav-header" title="${_("User-Defined Functions")}"
+        <li class="nav-header 
           % if app_name == 'impala':
             hide
-          % endif
-          ">
+          % endif" title="${_("User-Defined Functions")}">
           ${_('UDFs')}
         </li>
         <li class="white paramContainer
@@ -223,6 +222,14 @@ ${layout.menubar(section='query')}
             </div>
           </div>
 
+          <div data-bind="css: {'hide': query.watch.errors().length == 0}" class="alert alert-error">
+            <p><strong>${_('Your query has the following error(s):')}</strong></p>
+
+            <div data-bind="foreach: query.watch.errors">
+              <p data-bind="text: $data" class="queryErrorMessage"></p>
+            </div>
+          </div>
+
           <textarea class="hide" tabindex="1" name="query" id="queryField"></textarea>
 
           <div class="actions">
@@ -296,6 +303,7 @@ ${layout.menubar(section='query')}
           </table>
         </div>
         <div class="tab-pane" id="results">
+
           <div data-bind="css: {'hide': $root.query.results.rows().length == 0}" class="hide">
             <table class="table table-striped table-condensed resultTable" cellpadding="0" cellspacing="0" data-tablescroller-enforce-height="true">
               <thead>
@@ -318,6 +326,7 @@ ${layout.menubar(section='query')}
             </div>
           </div>
         </div>
+
          <div class="tab-pane" id="chart">
           <div style="text-align: center">
           <form class="form-inline">
@@ -1079,8 +1088,11 @@ $(document).ready(function () {
     if (codeMirror.getValue() == queryPlaceholder) {
       codeMirror.setValue("");
     }
-    if (errorWidget) {
-      errorWidget.clear();
+    if (errorWidgets) {
+      $.each(errorWidgets, function(index, errorWidget) {
+        errorWidget.clear();
+      });
+      errorWidgets = [];
     }
     $("#validationResults").empty();
   });
@@ -1432,15 +1444,25 @@ $(document).on('explain.query', cleanResultsTable);
 $(document).on('fetched.results', resultsTable);
 
 var selectedLine = -1;
-var errorWidget = null;
+var errorWidgets = [];
 $(document).on('error.query', function () {
-  if ($(".queryErrorMessage").length > 0) {
-    var err = $(".queryErrorMessage").text().toLowerCase();
+  $.each(errorWidgets, function(index, el) {
+    $(el).remove();
+    errorWidgets = [];
+  });
+
+  $.each($(".queryErrorMessage"), function(index, el) {
+    var err = $(el).text().toLowerCase();
     var firstPos = err.indexOf("line");
     if (firstPos > -1) {
       selectedLine = $.trim(err.substring(err.indexOf(" ", firstPos), err.indexOf(":", firstPos))) * 1;
-      errorWidget = codeMirror.addLineWidget(selectedLine - 1, $("<div>").addClass("editorError").html("<i class='fa fa-exclamation-circle'></i> " + err)[0], {coverGutter: true, noHScroll: true})
+      errorWidgets.push(codeMirror.addLineWidget(selectedLine - 1, $("<div>").addClass("editorError").html("<i class='fa fa-exclamation-circle'></i> " + err)[0], {coverGutter: true, noHScroll: true}));
     }
+    $(el).hide();
+  });
+
+  if ($(".queryErrorMessage:hidden").length == $(".queryErrorMessage").length) {
+    $(".queryErrorMessage").parent().parent().hide();
   }
 });
 
@@ -1725,7 +1747,7 @@ $(document).ready(function () {
       showSection('explain-parameter-selection');
     },
     'query/logs': function () {
-      if (viewModel.query.watch.logs().length == 0) {
+      if (viewModel.query.watch.logs().length == 0 && viewModel.query.watch.errors().length == 0) {
         routie('query');
       }
       codeMirror.setSize("99%", 100);
@@ -1735,7 +1757,7 @@ $(document).ready(function () {
       clickHard('.resultsContainer .nav-tabs a[href="#log"]');
     },
     'query/results': function () {
-      if (viewModel.query.results.empty()) {
+      if (viewModel.query.id() == -1 && viewModel.query.results.empty()) {
         routie('query');
       } else {
         codeMirror.setSize("99%", 100);
@@ -1770,12 +1792,21 @@ $(document).ready(function () {
   $(document).on('explained.query', function () {
     routie('query/explanation');
   });
-  $(document).on('watched.query', function () {
+  $(document).on('watched.query', function (e, data) {
+    if (data.status && data.status && data.status != 0) {
+      viewModel.query.watch.errors.push(data.error || data.message);
+    }
+    routie('query/logs');
+  });
+  $(document).on('error_watch.query', function () {
     routie('query/logs');
   });
   $(document).on('fetched.results', function () {
     routie('query/results');
   });
+  $(document).on('execute.query', function() {
+    routie('query');
+  });
 });
 
 // @TODO: Make file resources list automatically generated.

+ 23 - 9
apps/beeswax/static/js/beeswax.vm.js

@@ -46,6 +46,7 @@ function BeeswaxViewModel(server, query_id) {
     'watch': {
       'logs': [],
       'url': null,
+      'errors': []
     },
     'isRunning': false
   };
@@ -80,7 +81,9 @@ function BeeswaxViewModel(server, query_id) {
       }
     },
     'write': function(value) {
-      self.selectedDatabase(self.databases.indexOf(value));
+      if (value) {
+        self.selectedDatabase(self.databases.indexOf(value));
+      }
     },
     'deferEvaluation': true
   });
@@ -344,6 +347,7 @@ function BeeswaxViewModel(server, query_id) {
     $(document).trigger('execute.query', data);
     self.query.explain(false);
     self.query.isRunning(true);
+    self.query.errors.removeAll();
 
     var data = {
       'query-query': self.query.query(),
@@ -367,6 +371,7 @@ function BeeswaxViewModel(server, query_id) {
           self.watchQueryLoop();
         } else {
           self.query.errors.push(data.message);
+          self.query.isRunning(false);
           $(document).trigger('error.query');
         }
         $(document).trigger('executed.query', data);
@@ -411,28 +416,37 @@ function BeeswaxViewModel(server, query_id) {
   self.watchQueryLoop = function(fn) {
     var TIMEOUT = 1000;
     var timer = null;
-    var executed_once = false;
 
     self.query.watch.logs.removeAll();
+    self.query.watch.errors.removeAll();
     self.query.results.rows.removeAll();
     self.query.results.columns.removeAll();
 
     var _fn = function() {
       $(document).one('watched.query', function(e, data) {
-        if (executed_once && (data.isSuccess || data.isFailure)) {
+        if (data.isSuccess || data.isFailure) {
           clearTimeout(timer);
-          $(document).trigger('stop_watch.query');
-          if (fn) {
-            fn(data);
-          } else {
-            self.fetchResults();
+          self.query.isRunning(false);
+
+          if (data.log) {
+            self.query.watch.logs.push(data.log);
+            // scroll logs
+          }
+          if (!data.isFailure) {
+            $(document).trigger('stop_watch.query');
+
+            if (fn) {
+              fn(data);
+            } else {
+              self.fetchResults();
+            }
           }
         } else {
-          executed_once = true;
           if (data.log) {
             self.query.watch.logs.push(data.log);
             // scroll logs
           }
+          
           timer = setTimeout(_fn, TIMEOUT);
         }
       });