Browse Source

HUE-1176 [jb] Skeleton of Revamp v2

Romain Rigaux 9 years ago
parent
commit
99c5e95

+ 85 - 0
apps/jobbrowser/src/jobbrowser/api2.py

@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+# 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.
+
+import json
+import logging
+
+from django.utils.translation import ugettext as _
+from desktop.lib.i18n import smart_unicode
+from desktop.lib.django_util import JsonResponse
+
+from jobbrowser.job_api import YarnApi
+
+
+LOG = logging.getLogger(__name__)
+
+
+def get_api(user):
+  pass
+
+
+def api_error_handler(func):
+  def decorator(*args, **kwargs):
+    response = {}
+
+    try:
+      return func(*args, **kwargs)
+    except Exception, e:
+      LOG.exception('Error running %s' % func)
+      response['status'] = -1
+      response['message'] = smart_unicode(e)
+    finally:
+      if response:
+        return JsonResponse(response)
+
+  return decorator
+
+
+@api_error_handler
+def jobs(request):
+  response = {'status': -1}
+
+  search = json.loads(request.POST.get('search', '{}'))
+
+  response['apps'] = YarnApi(request.user).apps()
+  response['status'] = 0
+
+  return JsonResponse(response)
+
+
+@api_error_handler
+def job(request): return {}
+
+
+@api_error_handler
+def kill(request): return {}
+
+@api_error_handler
+def progress(request): return {'progress': 0}
+
+
+@api_error_handler
+def tasks(request): return []
+
+
+@api_error_handler
+def logs(request): return {'stderr': '', 'stdout': ''}
+
+
+@api_error_handler
+def profile(request): return {}
+

+ 7 - 0
apps/jobbrowser/src/jobbrowser/conf.py

@@ -37,4 +37,11 @@ LOG_OFFSET = Config(
   default=-1000000,
   type=int,
   help=_('Offset in bytes where a negative offset will fetch the last N bytes for the given log file (default 1MB).')
+)
+
+ENABLE_V2 = Config(
+    key="enable_v2",
+    help=_("Show the version 2 of app which unifies all the past browsers into one."),
+    type=coerce_bool,
+    default=False
 )

+ 96 - 0
apps/jobbrowser/src/jobbrowser/job_api.py

@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+# 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.
+
+import logging
+
+from django.utils.translation import ugettext as _
+from jobbrowser.api import YarnApi as NativeYarnApi
+
+
+LOG = logging.getLogger(__name__)
+
+
+
+def get_api(user):
+  pass
+
+
+
+class Api():
+
+  def __init__(self, user):
+    self.user = user
+
+  def apps(self): return []
+
+  def app(self): return {}
+
+  def kill(self): return {}
+
+  def progress(self): return {'progress': 0}
+
+  def tasks(self): return []
+
+  def logs(self): return {'stderr': '', 'stdout': ''}
+
+  def profile(self): return {}
+
+
+# Job
+
+class YarnApi(Api):
+
+  def apps(self):
+    jobs = NativeYarnApi(self.user).get_jobs(self.user, username=self.user.username, state='all', text='')
+    return [{'id': app.jobId, 'status': app.status} for app in jobs]
+
+
+class MapReduce2Api(Api):
+  pass
+
+class MapReduceHistoryServerApi(Api):
+  pass
+
+
+class SparkApi(Api):
+  pass
+
+class SparkHistoryServerApi(Api):
+  pass
+
+
+class ImpalaApi(Api):
+  pass
+
+
+# Batch
+
+class BatchApi(Api):
+  pass
+
+# Schedule
+
+class ScheduleApi(Api):
+  pass
+
+
+# History
+
+class HueHistoryApi(Api):
+
+  def apps(self): return []
+

+ 0 - 3
apps/jobbrowser/src/jobbrowser/settings.py

@@ -14,9 +14,6 @@
 # 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.
-#
-# Django settings for jobbrowser project.
-#
 
 DJANGO_APPS = ['jobbrowser']
 NICE_NAME = "Job Browser"

+ 162 - 0
apps/jobbrowser/src/jobbrowser/templates/apps.mako

@@ -0,0 +1,162 @@
+## 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.
+<%
+  from desktop.views import commonheader, commonfooter
+  from django.utils.translation import ugettext as _
+%>
+
+<%namespace name="require" file="/require.mako" />
+
+${ commonheader("Job Browser", "jobbrowser", user) | n,unicode }
+
+${ require.config() }
+
+
+<div class="navbar navbar-inverse navbar-fixed-top nokids">
+    <div class="navbar-inner">
+      <div class="container-fluid">
+        <div class="nav-collapse">
+          <ul class="nav">
+            <li class="currentApp">
+              <a href="/${app_name}">
+                <img src="${ static('jobbrowser/art/icon_jobbrowser_48.png') }" class="app-icon"/>
+                ${ _('Job Browser') }
+              </a>
+            </li>
+          </ul>
+          % if not hiveserver2_impersonation_enabled:
+            <div class="pull-right alert alert-warning" style="margin-top: 4px">${ _("Hive jobs are running as the 'hive' user") }</div>
+          % endif
+        </div>
+      </div>
+    </div>
+</div>
+
+
+<div class="container-fluid">
+
+  ${_('Username')} <input id="userFilter" type="text" class="input-medium search-query" placeholder="${_('Search for username')}" value="${ user_filter or '' }">
+  &nbsp;&nbsp;${_('Text')} <input id="textFilter" type="text" class="input-xlarge search-query" placeholder="${_('Search for id, name, status...')}" value="${ text_filter or '' }">
+
+  <span class="btn-group">
+    <a class="btn btn-status" data-value="completed">${ _('Jobs') }</a>
+    <a class="btn btn-status" data-value="running">${ _('Batches') }</a>
+    <a class="btn btn-status" data-value="killed">${ _('Schedules') }</a>
+  </span>
+
+  <div class="card card-small">
+
+  <table id="jobsTable" class="datatables table table-condensed">
+    <thead>
+    <tr>
+      <th>${_('Logs')}</th>
+      <th>${_('Id')}</th>
+      <th>${_('Name')}</th>
+      <th>${_('Type')}</th>
+      <th>${_('Status')}</th>
+      <th>${_('User')}</th>
+      <th>${_('Cluster')}</th>
+      <th>${_('Progress')}</th>
+      <th>${_('Duration')}</th>
+      <th>${_('Submitted')}</th>
+    </tr>
+    </thead>
+    <tbody data-bind="foreach: apps">
+      <tr>
+        <td></td>
+        <td data-bind="text: id"></td>
+        <td data-bind="text: name"></td>
+        <td data-bind="text: type"></td>
+        <td data-bind="text: status"></td>
+        <td data-bind="text: user"></td>
+        <td data-bind="text: cluster"></td>
+        <td data-bind="text: progress"></td>
+        <td data-bind="text: duration"></td>
+        <td data-bind="text: submitted"></td>
+      </tr>
+    </tbody>
+  </table>
+    </div>
+</div>
+
+
+<script type="text/javascript" charset="utf-8">
+  require([
+    "knockout",
+    "ko.charts",
+    "desktop/js/apiHelper",
+    "notebook/js/notebook.ko",
+    "knockout-mapping",
+    "knockout-sortable",
+    "ko.editable",
+    "ko.hue-bindings"
+  ], function (ko, charts, ApiHelper, EditorViewModel) {
+
+    var Job = function (vm, job) {
+      var self = this;
+
+      self.id = ko.observable(typeof job.id != "undefined" && job.id != null ? job.id : null);
+      self.name = ko.observable(typeof job.name != "undefined" && job.name != null ? job.name : null);
+      self.type = ko.observable(typeof job.type != "undefined" && job.type != null ? job.type : null);
+      self.status = ko.observable(typeof job.status != "undefined" && job.status != null ? job.status : null);
+      self.user = ko.observable(typeof job.user != "undefined" && job.user != null ? job.user : null);
+      self.cluster = ko.observable(typeof job.cluster != "undefined" && job.cluster != null ? job.cluster : null);
+      self.progress = ko.observable(typeof job.progress != "undefined" && job.progress != null ? job.progress : null);
+      self.duration = ko.observable(typeof job.duration != "undefined" && job.duration != null ? job.duration : null);
+      self.submitted = ko.observable(typeof job.submitted != "undefined" && job.submitted != null ? job.submitted : null);
+    };
+
+    var JobBrowserViewModel = function (options) {
+      var self = this;
+
+      self.apps = ko.observableArray();
+      self.loadingApps = ko.observable(false);
+
+      self.fetchJobs = function (callback) {
+        self.loadingApps(true);
+        $.get("/jobbrowser/api/jobs", {
+        }, function(data) {
+          var apps = [];
+          if (data && data.apps){
+            data.apps.forEach(function(job){
+              apps.push(new Job(self, job));
+            });
+          }
+          self.apps(apps);
+        }).always(function(){
+          self.loadingApps(false);
+        });
+      };
+    };
+
+    var viewModel;
+
+    $(document).ready(function () {
+      var options = {
+        user: '${ user.username }',
+        i18n: {
+          errorLoadingDatabases: "${ _('There was a problem loading the databases') }",
+        }
+      }
+      viewModel = new JobBrowserViewModel(options);
+      ko.applyBindings(viewModel);
+
+      viewModel.fetchJobs();
+    });
+  });
+</script>
+
+${ commonfooter(request, messages) | n,unicode }

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

@@ -17,6 +17,7 @@
 
 from django.conf.urls import patterns, url
 
+
 urlpatterns = patterns('jobbrowser.views',
   # "Default"
   url(r'^$', 'jobs'),
@@ -47,3 +48,12 @@ urlpatterns = patterns('jobbrowser.views',
   url(r'^jobbrowser$', 'jobbrowser', name='jobbrowser'),
   url(r'^dock_jobs/$', 'dock_jobs', name='dock_jobs'),
 )
+
+# V2
+urlpatterns += patterns('jobbrowser.views',
+  url(r'apps$', 'apps', name='apps'),
+)
+
+urlpatterns += patterns('jobbrowser.api2',
+  url(r'api/jobs', 'jobs', name='jobs'),
+)

+ 6 - 0
apps/jobbrowser/src/jobbrowser/views.py

@@ -94,6 +94,12 @@ def check_job_permission(view_func):
   return wraps(view_func)(decorate)
 
 
+def apps(request):
+  return render('apps.mako', request, {
+    'hiveserver2_impersonation_enabled': hiveserver2_impersonation_enabled()
+  })
+
+
 def job_not_assigned(request, jobid, path):
   if request.GET.get('format') == 'json':
     result = {'status': -1, 'message': ''}

+ 3 - 0
desktop/conf.dist/hue.ini

@@ -1150,6 +1150,9 @@
   # Offset in bytes where a negative offset will fetch the last N bytes for the given log file (default 1MB).
   ## log_offset=-1000000
 
+  # Show the version 2 of app which unifies all the past browsers into one.
+  ## enable_v2=false
+
 
 ###########################################################################
 # Settings to configure Sentry / Security App.

+ 3 - 0
desktop/conf/pseudo-distributed.ini.tmpl

@@ -1154,6 +1154,9 @@
   # Offset in bytes where a negative offset will fetch the last N bytes for the given log file (default 1MB).
   ## log_offset=-1000000
 
+  # Show the version 2 of app which unifies all the past browsers into one.
+  ## enable_v2=false
+
 
 ###########################################################################
 # Settings to configure Sentry / Security App.

+ 9 - 2
desktop/core/src/desktop/templates/common_header.mako

@@ -458,8 +458,15 @@ if USE_NEW_EDITOR.get():
       </li>
     % endif
     % if 'jobbrowser' in apps:
-    <li class="hide1380"><a title="${_('Manage jobs')}" rel="navigator-tooltip" href="/${apps['jobbrowser'].display_name}"><i class="fa fa-list-alt"></i>&nbsp;${_('Job Browser')}&nbsp;<span id="jobBrowserCount" class="badge badge-warning hide" style="padding-top:0;padding-bottom: 0"></span></a></li>
-    <li class="hideMoreThan1380"><a title="${_('Job Browser')}" rel="navigator-tooltip" href="/${apps['jobbrowser'].display_name}"><i class="fa fa-list-alt"></i></a></li>
+      <li class="hide1380"><a title="${_('Manage jobs')}" rel="navigator-tooltip" href="/${apps['jobbrowser'].display_name}"><i class="fa fa-list-alt"></i>&nbsp;${_('Job Browser')}&nbsp;<span id="jobBrowserCount" class="badge badge-warning hide" style="padding-top:0;padding-bottom: 0"></span></a></li>
+      <li class="hideMoreThan1380"><a title="${_('Job Browser')}" rel="navigator-tooltip" href="/${apps['jobbrowser'].display_name}"><i class="fa fa-list-alt"></i></a></li>
+      <% from jobbrowser.conf import ENABLE_V2 %>
+      % if ENABLE_V2.get():
+        <li class="hide1380"><a title="${_('Manage jobs')}" rel="navigator-tooltip" href="/jobbrowser/apps">
+          <i class="fa fa-list-alt"></i>&nbsp;${_('Job Browser 2')}&nbsp;<span id="jobBrowserCount" class="badge badge-warning hide" style="padding-top:0;padding-bottom: 0"></span></a>
+        </li>
+        <li class="hideMoreThan1380"><a title="${_('Job Browser 2')}" rel="navigator-tooltip" href="/jobbrowser/apps"><i class="fa fa-list-alt"></i></a></li>
+      % endif
     % endif
     <%
       view_profile = user.has_hue_permission(action="access_view:useradmin:edit_user", app="useradmin") or user.is_superuser