浏览代码

HUE-6908 [jb] Basic Livy session page

Romain Rigaux 8 年之前
父节点
当前提交
59570c1

+ 5 - 0
apps/jobbrowser/src/jobbrowser/apis/base_api.py

@@ -31,6 +31,7 @@ LOG = logging.getLogger(__name__)
 def get_api(user, interface):
   from jobbrowser.apis.bundle_api import BundleApi
   from jobbrowser.apis.data_eng_api import DataEngClusterApi, DataEngJobApi
+  from jobbrowser.apis.livy_api import LivySessionsApi, LivyJobApi
   from jobbrowser.apis.job_api import JobApi
   from jobbrowser.apis.schedule_api import ScheduleApi
   from jobbrowser.apis.workflow_api import WorkflowApi
@@ -47,6 +48,10 @@ def get_api(user, interface):
     return DataEngClusterApi(user)
   elif interface == 'dataeng-jobs':
     return DataEngJobApi(user)
+  elif interface == 'livy-sessions':
+    return LivySessionsApi(user)
+  elif interface == 'livy-job':
+    return LivyJobApi(user)
   elif interface == 'slas':
     return Api(user)
   else:

+ 40 - 15
apps/jobbrowser/src/jobbrowser/apis/livy_api.py

@@ -17,8 +17,6 @@
 
 import logging
 
-from datetime import datetime,  timedelta
-
 from django.utils.translation import ugettext as _
 
 from spark.job_server_api import get_api
@@ -34,27 +32,46 @@ class LivySessionsApi(Api):
   def apps(self, filters):
     api = get_api(self.user)
 
-    jobs = api.list_clusters()
+    jobs = api.get_sessions()
 
     return {
       'apps': [{
-        'id': app['crn'],
-        'name': '%(clusterName)s' % app,
-        'status': app['status'],
-        'apiStatus': self._api_status(app['status']),
-        'type': '%(serviceType)s %(workersGroupSize)s %(instanceType)s %(cdhVersion)s' % app,
-        'user': app['clusterName'].split('-', 1)[0],
+        'id': 'livy-%(id)s' % app,
+        'name': '%(kind)s %(id)s' % app,
+        'status': app['state'],
+        'apiStatus': self._api_status(app['state']),
+        'type': 'livy-session',
+        'user': app['owner'],
         'progress': 100,
         'queue': 'group',
         'duration': 1,
-        'submitted': app['creationDate']
-      } for app in jobs['clusters']],
-      'total': len(jobs)
+        'submitted': ''
+      } for app in jobs['sessions']],
+      'total': jobs['total']
     }
 
 
   def app(self, appid):
-    return {}
+    appid = appid.rsplit('-')[-1]
+    api = get_api(self.user)
+
+    job = api.get_session(appid)
+
+    return {
+      'id': 'livy-%(id)s' % job,
+      'name': '%(kind)s %(id)s' % job,
+      'status': job['state'],
+      'apiStatus': self._api_status(job['state']),
+      'type': 'livy-session',
+      'user': job['owner'],
+      'progress': 100,
+      'queue': 'group',
+      'duration': 1,
+      'submitted': '',
+      'properties': {
+        'statements': []
+      }
+    }
 
 
   def action(self, appid, action):
@@ -65,8 +82,16 @@ class LivySessionsApi(Api):
     return {'logs': ''}
 
 
-  def profile(self, appid, app_type, app_property):
-    return {}
+  def profile(self, appid, app_type, app_property, app_filters):
+    appid = appid.rsplit('-')[-1]
+
+    if app_property == 'properties':
+      api = get_api(self.user)
+
+      return api.get_statements(appid)
+    else:
+      return {}
+
 
   def _api_status(self, status):
     if status in ['CREATING', 'CREATED', 'TERMINATING']:

+ 91 - 0
apps/jobbrowser/src/jobbrowser/templates/job_browser.mako

@@ -249,6 +249,10 @@ ${ commonheader("Job Browser", "jobbrowser", user, request) | n,unicode }
               <div data-bind="template: { name: 'dataeng-job-page${ SUFFIX }' }"></div>
             <!-- /ko -->
 
+            <!-- ko if: mainType() == 'livy-sessions' -->
+              <div class="jb-panel" data-bind="template: { name: 'livy-session-page${ SUFFIX }' }"></div>
+            <!-- /ko -->
+
           <!-- /ko -->
           <!-- /ko -->
 
@@ -840,6 +844,85 @@ ${ commonheader("Job Browser", "jobbrowser", user, request) | n,unicode }
 </script>
 
 
+<script type="text/html" id="livy-session-page${ SUFFIX }">
+
+  <div class="row-fluid">
+    <div data-bind="css: {'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <li class="nav-header">${ _('Id') }</li>
+          <li class="break-word"><span data-bind="text: id"></span></li>
+          <!-- ko if: doc_url -->
+          <li class="nav-header">${ _('Document') }</li>
+          <li>
+            <a data-bind="hueLink: doc_url" href="javascript: void(0);" title="${ _('Open in editor') }">
+              <span data-bind="text: name"></span>
+            </a>
+          </li>
+          <!-- /ko -->
+          <!-- ko ifnot: doc_url -->
+          <li class="nav-header">${ _('Name') }</li>
+          <li><span data-bind="text: name"></span></li>
+          <!-- /ko -->
+          <li class="nav-header">${ _('Status') }</li>
+          <li><span data-bind="text: status"></span></li>
+          <li class="nav-header">${ _('User') }</li>
+          <li><span data-bind="text: user"></span></li>
+          <li class="nav-header">${ _('Progress') }</li>
+          <li><span data-bind="text: progress"></span>%</li>
+          <li>
+            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-danger': apiStatus() === 'FAILED', 'progress-warning': isRunning(), 'progress-success': apiStatus() === 'SUCCEEDED' }">
+              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+            </div>
+          </li>
+          <li class="nav-header">${ _('Duration') }</li>
+          <li><span data-bind="text: duration().toHHMMSS()"></span></li>
+          <li class="nav-header">${ _('Submitted') }</li>
+          <li><span data-bind="moment: {data: submitted, format: 'LLL'}"></span></li>
+        </ul>
+      </div>
+    </div>
+    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
+
+      <ul class="nav nav-pills margin-top-20">
+        <li>
+          <a href="#livy-session-page-statements${ SUFFIX }" data-bind="click: function(){ fetchProfile('properties'); $('a[href=\'#livy-session-page-statements${ SUFFIX }\']').tab('show'); }">
+            ${ _('Properties') }</a>
+        </li>
+      </ul>
+
+      <div class="clearfix"></div>
+
+      <div class="tab-content">
+        <div class="tab-pane active" id="livy-session-page-statements${ SUFFIX }">
+          <table id="actionsTable" class="datatables table table-condensed">
+            <thead>
+            <tr>
+              <th>${_('Id')}</th>
+              <th>${_('State')}</th>
+              <th>${_('Output')}</th>
+            </tr>
+            </thead>
+            <tbody data-bind="foreach: properties['statements']">
+              <tr data-bind="click: function() {  $root.job().id(id); $root.job().fetchJob(); }" class="pointer">
+                <td>
+                  <a data-bind="hueLink: '/jobbrowser/jobs/' + id(), clickBubble: false">
+                    <i class="fa fa-tasks"></i>
+                  </a>
+                </td>
+                <td data-bind="text: state"></td>
+                <td data-bind="text: output"></td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+
+      </div>
+    </div>
+  </div>
+</script>
+
+
 <script type="text/html" id="job-actions${ SUFFIX }">
   <div class="btn-group">
     <!-- ko if: hasResume -->
@@ -1665,6 +1748,9 @@ ${ commonheader("Job Browser", "jobbrowser", user, request) | n,unicode }
         else if (/[a-z0-9]{8}\-[a-z0-9]{4}\-[a-z0-9]{4}\-[a-z0-9]{4}\-[a-z0-9]{12}/.test(self.id())) {
           interface = 'dataeng-jobs';
         }
+        else if (/livy-[0-9]+/.test(self.id())) {
+          interface = 'livy-sessions';
+        }
 
         interface = vm.isValidInterface(interface);
         vm.interface(interface);
@@ -2183,6 +2269,9 @@ ${ commonheader("Job Browser", "jobbrowser", user, request) | n,unicode }
         var schedulerInterfaceCondition = function () {
           return self.appConfig() && self.appConfig()['scheduler'] && self.appConfig()['scheduler']['interpreters'].length > 0;
         }
+        var livyInterfaceCondition = function () {
+          return self.appConfig() && self.appConfig()['editor'] && self.appConfig()['editor']['interpreter_names'].indexOf('pyspark') != -1;
+        }
 
         var interfaces = [
           {'interface': 'jobs', 'label': '${ _ko('Jobs') }', 'condition': jobsInterfaceCondition},
@@ -2192,6 +2281,7 @@ ${ commonheader("Job Browser", "jobbrowser", user, request) | n,unicode }
           {'interface': 'bundles', 'label': '${ _ko('Bundles') }', 'condition': schedulerInterfaceCondition},
           {'interface': 'slas', 'label': '${ _ko('SLAs') }', 'condition': schedulerInterfaceCondition},
           {'interface': 'dataeng-clusters', 'label': '${ _ko('Clusters') }', 'condition': dataEngInterfaceCondition},
+          {'interface': 'livy-sessions', 'label': '${ _ko('Livy') }', 'condition': livyInterfaceCondition},
         ];
 
         return interfaces.filter(function (i) {
@@ -2343,6 +2433,7 @@ ${ commonheader("Job Browser", "jobbrowser", user, request) | n,unicode }
           case 'bundles':
           case 'dataeng-clusters':
           case 'dataeng-jobs':
+          case 'livy-sessions':
             self.selectInterface(h);
             break;
           default:

+ 3 - 0
apps/spark/src/spark/job_server_api.py

@@ -110,6 +110,9 @@ class JobServerApi(object):
   def get_session(self, uuid):
     return self._root.get('sessions/%s' % uuid)
 
+  def get_statements(self, uuid):
+    return self._root.get('sessions/%s/statements' % uuid)
+
   def submit_statement(self, uuid, statement):
     data = {'code': statement}
     return self._root.post('sessions/%s/statements' % uuid, data=json.dumps(data), contenttype=_JSON_CONTENT_TYPE)