Browse Source

HUE-1937 [beeswax] Clarify behavior of a multi query with error

When a multi query as an error, the user can modify the query and
click next to try to re-execute it without re-executing the queries
before.
The frontend was changed to transmit the full query each time between
two next statement. We also don't increase the statement number in
case of failure before.
We could also update the query between two statements but are not
doing it.

Fix in Oozie:
A PREP workflow should have the kill option
A coordinator workflow can become non in rare cases
Romain Rigaux 11 years ago
parent
commit
e5c5e60184

+ 2 - 2
apps/beeswax/src/beeswax/api.py

@@ -157,7 +157,7 @@ def watch_query_refresh_json(request, id):
   # Go to next statement if asked to continue or when a statement with no dataset finished.
   # Go to next statement if asked to continue or when a statement with no dataset finished.
   try:
   try:
     if request.POST.get('next') or (not query_history.is_finished() and query_history.is_success() and not query_history.has_results):
     if request.POST.get('next') or (not query_history.is_finished() and query_history.is_success() and not query_history.has_results):
-      query_history = db.execute_next_statement(query_history)
+      query_history = db.execute_next_statement(query_history, request.POST.get('query-query'))
       handle, state = _get_query_handle_and_state(query_history)
       handle, state = _get_query_handle_and_state(query_history)
   except QueryServerException, ex:
   except QueryServerException, ex:
     raise ex
     raise ex
@@ -192,7 +192,7 @@ def watch_query_refresh_json(request, id):
       result['message'] = res.errorMessage
       result['message'] = res.errorMessage
     else:
     else:
       result['message'] = _('Bad status for request %s:\n%s') % (id, res)
       result['message'] = _('Bad status for request %s:\n%s') % (id, res)
-    result['status'] = 1
+    result['status'] = -1
 
 
   return HttpResponse(json.dumps(result), mimetype="application/json")
   return HttpResponse(json.dumps(result), mimetype="application/json")
 
 

+ 1 - 1
apps/beeswax/src/beeswax/conf.py

@@ -76,7 +76,7 @@ CLOSE_QUERIES = Config(
   help=_t("Hue will try to close the Hive query when the user leaves the editor page. "
   help=_t("Hue will try to close the Hive query when the user leaves the editor page. "
           "This will free all the query resources in HiveServer2, but also make its results inaccessible."),
           "This will free all the query resources in HiveServer2, but also make its results inaccessible."),
   type=coerce_bool,
   type=coerce_bool,
-  default=True
+  default=False
 )
 )
 
 
 SSL = ConfigSection(
 SSL = ConfigSection(

+ 4 - 0
apps/beeswax/src/beeswax/design.py

@@ -87,6 +87,10 @@ class HQLdesign(object):
   def hql_query(self):
   def hql_query(self):
     return self._data_dict['query']['query']
     return self._data_dict['query']['query']
 
 
+  @hql_query.setter
+  def hql_query(self, query):
+    self._data_dict['query']['query'] = query
+
   @property
   @property
   def query(self):
   def query(self):
     return self._data_dict['query'].copy()
     return self._data_dict['query'].copy()

+ 7 - 0
apps/beeswax/src/beeswax/models.py

@@ -128,6 +128,13 @@ class QueryHistory(models.Model):
     else:
     else:
       return self.query
       return self.query
 
 
+  def refresh_design(self, hql_query):
+    # Refresh only HQL query part
+    query = self.design.get_design()
+    query.hql_query = hql_query
+    self.design.data = query.dumps()
+    self.query = hql_query
+ 
   def is_finished(self):
   def is_finished(self):
     is_statement_finished = not self.is_running()
     is_statement_finished = not self.is_running()
 
 

+ 9 - 2
apps/beeswax/src/beeswax/server/dbms.py

@@ -338,11 +338,18 @@ class HiveServer2Dbms(object):
     return None
     return None
 
 
 
 
-  def execute_next_statement(self, query_history):
-    query_history.statement_number += 1
+  def execute_next_statement(self, query_history, hql_query):
+    if query_history.is_success():
+      # We need to go to the next statement only if the previous one passed
+      query_history.statement_number += 1
+    else:
+      # We need to update the query in case it was fixed
+      query_history.refresh_design(hql_query)
+
     query_history.last_state = QueryHistory.STATE.submitted.index
     query_history.last_state = QueryHistory.STATE.submitted.index
     query_history.save()
     query_history.save()
     query = query_history.design.get_design()
     query = query_history.design.get_design()
+
     return self.execute_and_watch(query, query_history=query_history)
     return self.execute_and_watch(query, query_history=query_history)
 
 
 
 

+ 10 - 1
apps/beeswax/src/beeswax/templates/execute.mako

@@ -244,7 +244,7 @@ ${layout.menubar(section='query')}
             <button data-bind="click: tryExecuteQuery, visible: $root.canExecute, enable: $root.queryEditorBlank" type="button" id="executeQuery" class="btn btn-primary disable-feedback" tabindex="2">${_('Execute')}</button>
             <button data-bind="click: tryExecuteQuery, visible: $root.canExecute, enable: $root.queryEditorBlank" type="button" id="executeQuery" class="btn btn-primary disable-feedback" tabindex="2">${_('Execute')}</button>
             <button data-bind="click: tryCancelQuery, visible: $root.design.isRunning()" class="btn btn-danger" data-loading-text="${ _('Canceling...') }" rel="tooltip" data-original-title="${ _('Cancel the query') }">${ _('Cancel') }</button>
             <button data-bind="click: tryCancelQuery, visible: $root.design.isRunning()" class="btn btn-danger" data-loading-text="${ _('Canceling...') }" rel="tooltip" data-original-title="${ _('Cancel the query') }">${ _('Cancel') }</button>
 
 
-            <button data-bind="click: executeNextStatement, visible: !$root.design.isFinished()" type="button" class="btn btn-primary disable-feedback" tabindex="2">${_('Next')}</button>
+            <button data-bind="click: tryExecuteNextStatement, visible: !$root.design.isFinished()" type="button" class="btn btn-primary disable-feedback" tabindex="2">${_('Next')}</button>
 
 
             <button data-bind="click: trySaveDesign, css: {'hide': !$root.design.id() || $root.design.id() == -1}" type="button" class="btn hide">${_('Save')}</button>
             <button data-bind="click: trySaveDesign, css: {'hide': !$root.design.id() || $root.design.id() == -1}" type="button" class="btn hide">${_('Save')}</button>
             <button data-bind="click: saveAsModal" type="button" class="btn">${_('Save as...')}</button>
             <button data-bind="click: saveAsModal" type="button" class="btn">${_('Save as...')}</button>
@@ -1640,6 +1640,15 @@ function tryExecuteQuery() {
   logGA('query/execute');
   logGA('query/execute');
 }
 }
 
 
+function tryExecuteNextStatement() {
+  var query = getHighlightedQuery() || codeMirror.getValue();
+  viewModel.design.query.value(query);
+
+  viewModel.executeNextStatement();
+
+  logGA('query/execute_next');
+}
+
 function tryExecuteParameterizedQuery() {
 function tryExecuteParameterizedQuery() {
   $(".tooltip").remove();
   $(".tooltip").remove();
   viewModel.executeQuery();
   viewModel.executeQuery();

+ 56 - 0
apps/beeswax/src/beeswax/tests.py

@@ -496,6 +496,62 @@ for x in sys.stdin:
     content = fetch_query_result_data(self.client, resp)
     content = fetch_query_result_data(self.client, resp)
     assert_true(content.get('is_finished'), content)
     assert_true(content.get('is_finished'), content)
 
 
+
+  def test_multiple_statements_with_next_button(self):
+    hql = """
+      show tables;
+      select * from test
+    """
+
+    resp = _make_query(self.client, hql)
+
+    # First statement
+    content = json.loads(resp.content)
+    watch_url = content['watch_url']
+    assert_equal('show tables', content.get('statement'), content)
+
+    resp = wait_for_query_to_finish(self.client, resp, max=30.0)
+    content = fetch_query_result_data(self.client, resp)
+    assert_true([u'test'] in content.get('results'), content)
+
+    # Next statement
+    resp = self.client.post(watch_url, {'next': True, 'query-query': hql})
+    content = json.loads(resp.content)
+    assert_equal('select * from test', content.get('statement'), content)
+
+    resp = wait_for_query_to_finish(self.client, resp, max=30.0)
+    content = fetch_query_result_data(self.client, resp)
+    assert_true([0, u'0x0'] in content.get('results'), content)
+
+  def test_multiple_statements_with_error(self):
+    hql = """
+      show tables;
+      select * from
+    """
+
+    resp = _make_query(self.client, hql)
+
+    content = json.loads(resp.content)
+    watch_url = content['watch_url']
+    assert_equal('show tables', content.get('statement'), content)
+
+    resp = wait_for_query_to_finish(self.client, resp, max=30.0)
+
+    resp = self.client.post(watch_url, {'next': True, 'query-query': hql})
+    content = json.loads(resp.content)
+    assert_true('Error while compiling statement' in content.get('message'), content)
+
+    hql = """
+      show tables;
+      select * from test
+    """
+
+    # Retry where we were with the statement fixed
+    resp = self.client.post(watch_url, {'next': True, 'query-query': hql})
+    content = json.loads(resp.content)
+    assert_equal('select * from test', content.get('statement'), content)
+
+
   def test_parallel_queries(self):
   def test_parallel_queries(self):
     """
     """
     Test that we can issue two queries to the BeeswaxServer in parallel.
     Test that we can issue two queries to the BeeswaxServer in parallel.

+ 2 - 1
apps/beeswax/static/js/beeswax.vm.js

@@ -508,7 +508,8 @@ function BeeswaxViewModel(server) {
     self.resetErrors();
     self.resetErrors();
 
 
     var data = {
     var data = {
-      'next': true
+      'next': true,
+      'query-query': self.design.query.value(),
     };
     };
     var request = {
     var request = {
       url: self.design.watch.url(),
       url: self.design.watch.url(),

+ 2 - 2
apps/oozie/src/oozie/templates/dashboard/list_oozie_workflow.mako

@@ -453,7 +453,7 @@ ${ layout.menubar(section='workflows', dashboard=True) }
 
 
     function zoom(){
     function zoom(){
       $("#graph").css("zoom", CURRENT_ZOOM);
       $("#graph").css("zoom", CURRENT_ZOOM);
-      $("#graph").css("-moz-transform", "scale("+CURRENT_ZOOM+")");
+      $("#graph").css("-moz-transform", "scale(" + CURRENT_ZOOM + ")");
     }
     }
 
 
     $("*[rel=tooltip]").tooltip();
     $("*[rel=tooltip]").tooltip();
@@ -604,7 +604,7 @@ ${ layout.menubar(section='workflows', dashboard=True) }
           $("#suspend-btn").hide();
           $("#suspend-btn").hide();
         }
         }
 
 
-        if (data.id && data.status != "RUNNING" && data.status != "SUSPENDED"){
+        if (data.id && data.status != "RUNNING" && data.status != "SUSPENDED" && data.status != "PREP"){
           $("#kill-btn").hide();
           $("#kill-btn").hide();
           $("#rerun-btn").show();
           $("#rerun-btn").show();
           $.jHueTitleUpdater.reset();
           $.jHueTitleUpdater.reset();

+ 1 - 1
apps/oozie/src/oozie/views/dashboard.py

@@ -184,7 +184,7 @@ def list_oozie_workflow(request, job_id, coordinator_job_id=None, bundle_job_id=
   hue_coord = history and history.get_coordinator() or History.get_coordinator_from_config(oozie_workflow.conf_dict)
   hue_coord = history and history.get_coordinator() or History.get_coordinator_from_config(oozie_workflow.conf_dict)
   hue_workflow = (hue_coord and hue_coord.workflow) or (history and history.get_workflow()) or History.get_workflow_from_config(oozie_workflow.conf_dict)
   hue_workflow = (hue_coord and hue_coord.workflow) or (history and history.get_workflow()) or History.get_workflow_from_config(oozie_workflow.conf_dict)
 
 
-  if hue_coord: Job.objects.can_read_or_exception(request, hue_coord.workflow.id)
+  if hue_coord and hue_coord.workflow: Job.objects.can_read_or_exception(request, hue_coord.workflow.id)
   if hue_workflow: Job.objects.can_read_or_exception(request, hue_workflow.id)
   if hue_workflow: Job.objects.can_read_or_exception(request, hue_workflow.id)
 
 
   parameters = oozie_workflow.conf_dict.copy()
   parameters = oozie_workflow.conf_dict.copy()