Эх сурвалжийг харах

HUE-1641 [jb] Improve UX of non started job yet

Does not PopupException anymore when clicking on:
 - started job that is not assigned
 - view log link

Manage missing attributes.
Romain Rigaux 12 жил өмнө
parent
commit
64414f7

+ 14 - 1
apps/jobbrowser/src/jobbrowser/api.py

@@ -234,14 +234,20 @@ class YarnApi(JobBrowserApi):
       jobid = jobid.replace('job', 'application')
       job = self.resource_manager_api.app(jobid)['app']
 
-      # MR id
+      if job['state'] == 'ACCEPTED':
+        raise ApplicationNotRunning(jobid, job)
+
+      # MR id, assume 'applicationType': 'MAPREDUCE'
       jobid = jobid.replace('application', 'job')
+
       if job['state'] in ('NEW', 'SUBMITTED', 'ACCEPTED', 'RUNNING'):
         json = self.mapreduce_api.job(self.user, jobid)
         job = YarnJob(self.mapreduce_api, json['job'])
       else:
         json = self.history_server_api.job(self.user, jobid)
         job = YarnJob(self.history_server_api, json['job'])
+    except ApplicationNotRunning, e:
+      raise e
     except Exception, e:
       raise PopupException('Job %s could not be found: %s' % (jobid, e), detail=e)
 
@@ -257,3 +263,10 @@ class YarnApi(JobBrowserApi):
   def get_tracker(self, node_manager_http_address, container_id):
     api = node_manager_api.get_resource_manager_api('http://' + node_manager_http_address)
     return Container(api.container(container_id))
+
+
+class ApplicationNotRunning(Exception):
+
+  def __init__(self, application_id, job):
+    self.application_id = application_id
+    self.job = job

+ 77 - 0
apps/jobbrowser/src/jobbrowser/templates/job_not_assigned.mako

@@ -0,0 +1,77 @@
+## Licensed to Cloudera, Inc. under one
+## or more contributor license agreements.  See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership.  Cloudera, Inc. licenses this file
+## to you under the Apache License, Version 2.0 (the
+## "License"); you may not use this file except in compliance
+## with the License.  You may obtain a copy of the License at
+##
+##     http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+
+<%namespace name="comps" file="jobbrowser_components.mako" />
+
+<%!
+  from desktop.views import commonheader, commonfooter
+
+  from django.template.defaultfilters import urlencode
+  from django.utils.translation import ugettext as _
+%>
+
+${ commonheader(_('Job'), "jobbrowser", user) | n,unicode }
+${ comps.menubar() }
+
+<link href="/jobbrowser/static/css/jobbrowser.css" rel="stylesheet">
+
+<div class="container-fluid">
+  <div class="row-fluid">
+    <div class="span2">
+      <div class="sidebar-nav" style="padding-top: 0">
+        <ul class="nav nav-list">
+          <li class="nav-header">${_('Job ID')}</li>
+          <li class="white hellipsify">${ jobid }</li>
+        </ul>
+      </div>
+    </div>
+    <div class="span10">
+      <div class="card card-small">
+        <h1 class="card-heading simple"></h1>
+          <div class="card-body">
+            <p>
+
+             ${ _('The application might not be running yet or there is no Node Manager or Container available.') }
+
+             <!--[if !IE]><!--><i class="icon-spinner icon-spin loader-main"></i><!--<![endif]-->
+             <!--[if IE]><img src="/hbase/static/art/loader.gif" /><![endif]-->
+            </p>
+      </div>
+    </div>
+  </div>
+</div>
+
+<script type="text/javascript" charset="utf-8">
+  $(document).ready(function () {
+
+    function checkStatus() {
+      $.getJSON("${ url('jobbrowser.views.job_not_assigned', jobid=jobid, path=path) }?format=json", function (data) {
+        if (data.status == 1) {
+          window.setTimeout(checkStatus, 1000);
+        } else if (data.status == 0) {
+          window.location.replace("${ path }");
+        } else {
+          // info js popup
+          window.setTimeout(checkStatus, 1000);
+        }
+      });
+    }
+
+    checkStatus();
+  });
+</script>
+
+${ commonfooter(messages) | n,unicode }

+ 9 - 0
apps/jobbrowser/src/jobbrowser/tests.py

@@ -400,6 +400,15 @@ class TestMapReduce2NoHadoop:
     response = self.c.get('/jobbrowser/jobs/job_1356251510842_0009')
     assert_equal(response.context['job'].jobId, 'job_1356251510842_0009')
 
+  def job_not_assigned(self):
+    response = self.c.get('/jobbrowser/jobs/job_1356251510842_0009/job_not_assigned//my_url')
+    assert_equal(response.context['jobid'], 'job_1356251510842_0009')
+    assert_equal(response.context['path'], '/my_url')
+
+    response = self.c.get('/jobbrowser/jobs/job_1356251510842_0009/job_not_assigned//my_url?format=json')
+    result = json.loads(response.content)
+    assert_equal(result['status'], 0)
+
 
 class MockResourceManagerApi:
   APPS = {

+ 1 - 0
apps/jobbrowser/src/jobbrowser/urls.py

@@ -37,6 +37,7 @@ urlpatterns = patterns('jobbrowser.views',
   # MR2 specific
   url(r'^jobs/(?P<job>\w+)/job_attempt_logs/(?P<attempt_index>\d+)$', 'job_attempt_logs', name='job_attempt_logs'),
   url(r'^jobs/(?P<job>\w+)/job_attempt_logs_json/(?P<attempt_index>\d+)/(?P<name>\w+)?/(?P<offset>\d+)?$', 'job_attempt_logs_json', name='job_attempt_logs_json'),
+  url(r'^jobs/(?P<jobid>\w+)/job_not_assigned/(?P<path>.+)$','job_not_assigned', name='job_not_assigned'),
 
   # Unused
   url(r'^jobs/(?P<job>\w+)/setpriority$', 'set_job_priority', name='set_job_priority'),

+ 24 - 4
apps/jobbrowser/src/jobbrowser/views.py

@@ -37,7 +37,7 @@ from desktop.views import register_status_bar_view
 from hadoop.api.jobtracker.ttypes import ThriftJobPriority, TaskTrackerNotFoundException, ThriftJobState
 
 from jobbrowser import conf
-from jobbrowser.api import get_api
+from jobbrowser.api import get_api, ApplicationNotRunning
 from jobbrowser.models import Job, JobLinkage, Tracker, Cluster
 
 import urllib2
@@ -46,14 +46,17 @@ import urllib2
 def check_job_permission(view_func):
   """
   Ensure that the user has access to the job.
-  Assumes that the wrapped function takes a 'jobid' param.
+  Assumes that the wrapped function takes a 'jobid' param named 'job'.
   """
   def decorate(request, *args, **kwargs):
     jobid = kwargs['job']
     try:
       job = get_api(request.user, request.jt).get_job(jobid=jobid)
+    except ApplicationNotRunning, e:
+      # reverse() seems broken, using request.path but beware, it discards GET and POST info
+      return job_not_assigned(request, jobid, request.path)
     except Exception, e:
-      raise PopupException(_('Could not find job %s. The job might not be running yet.') % jobid, detail=e)
+      raise PopupException(_('Could not find job %s.') % jobid, detail=e)
     if not conf.SHARE_JOBS.get() and not request.user.is_superuser \
       and job.user != request.user.username:
       raise PopupException(_("You don't have permission to access job %(id)s.") % {'id': jobid})
@@ -62,6 +65,23 @@ def check_job_permission(view_func):
   return wraps(view_func)(decorate)
 
 
+def job_not_assigned(request, jobid, path):
+  if request.GET.get('format') == 'json':
+    result = {'status': -1, 'message': ''}
+
+    try:
+      get_api(request.user, request.jt).get_job(jobid=jobid)
+      result['status'] = 0
+    except ApplicationNotRunning, e:
+      result['status'] = 1
+    except Exception, e:
+      result['message'] = _('Error polling job %s: e') % (jobid, e)
+
+    return HttpResponse(encode_json_for_js(result), mimetype="application/json")
+  else:
+    return render('job_not_assigned.mako', request, {'jobid': jobid, 'path': path})
+
+
 def jobs(request):
   user = request.GET.get('user', request.user.username)
   state = request.GET.get('state')
@@ -242,7 +262,7 @@ def job_single_logs(request, job):
   if failed_tasks:
     task = failed_tasks[0]
   else:
-    recent_tasks = job.filter_tasks(task_states=('running', 'succeeded',), task_types=('map', 'reduce',))
+    recent_tasks = job.filter_tasks(task_states=('running', 'succeeded', 'scheduled'), task_types=('map', 'reduce',))
     recent_tasks.sort(cmp_exec_time, reverse=True)
     if recent_tasks:
       task = recent_tasks[0]

+ 2 - 0
apps/jobbrowser/src/jobbrowser/yarn_models.py

@@ -193,6 +193,8 @@ class Attempt:
     setattr(self, 'mapFinishTimeFormatted', None)
     if not hasattr(self, 'diagnostics'):
       self.diagnostics = ''
+    if not hasattr(self, 'assignedContainerId'):
+      setattr(self, 'assignedContainerId', '')
 
   @property
   def counters(self):