浏览代码

[spark] Add link on jobs page to open them with their result

Fix non ASCII char in a template
Fix for Impala test
Consolidate menu bar
Romain Rigaux 12 年之前
父节点
当前提交
718c3bbe3a

+ 34 - 10
apps/beeswax/src/beeswax/templates/layout.mako

@@ -28,6 +28,37 @@ def is_selected(section, matcher):
 %>
 
 <%def name="menubar(section='')">
+  % if app_name == 'spark':
+  ## Duplication from spark common.mako!
+  <div class="navbar navbar-inverse navbar-fixed-top">
+    <div class="navbar-inner">
+      <div class="container-fluid">
+        <div class="nav-collapse">
+          <ul class="nav">
+            <li class="currentApp">
+              <a href="/spark">
+                <img src="/spark/static/art/icon_spark_24.png" />
+                ${ _('Spark Editor') }
+              </a>
+            </li>
+            <li class="${is_selected(section, 'query')}"><a href="${ url('spark:editor') }">${_('Query Editor')}</a></li>
+            ##<li class="${is_selected(section, 'my queries')}"><a href="${ url(app_name + ':my_queries') }">${_('My Queries')}</a></li>
+            <li class="${is_selected(section, 'queries')}"><a href="${ url('spark:list_designs') }">${_('Queries')}</a></li>
+            <li class="${is_selected(section, 'history')}"><a href="${ url('spark:list_query_history') }">${_('History')}</a></li>
+            <li class="currentApp">
+              <a href="/spark">
+                ${ _('Browser') }
+              </a>
+            </li>
+            <li class="${is_selected(section, 'jobs')}"><a href="${ url('spark:list_jobs') }">${_('Jobs')}</a></li>
+            <li class="${is_selected(section, 'contexts')}"><a href="${ url('spark:list_contexts') }">${_('Contexts')}</a></li>
+            <li class="${is_selected(section, 'jars')}"><a href="${ url('spark:list_jars') }">${_('Jars')}</a></li>
+          </ul>
+        </div>
+      </div>
+    </div>
+  </div>
+  % else:  
   <div class="navbar navbar-inverse navbar-fixed-top">
       <div class="navbar-inner">
         <div class="container-fluid">
@@ -37,25 +68,17 @@ def is_selected(section, matcher):
                 <a href="/${app_name}">
                 % if app_name == 'impala':
                   <img src="/impala/static/art/icon_impala_24.png" />
-                  ${ current_app.nice_name }
+                  Impala
                 % elif app_name == 'rdbms':
                   <img src="/rdbms/static/art/icon_rdbms_24.png" />
                   DB Query
-                % elif app_name == 'spark':
-                  <img src="/spark/static/art/icon_spark_24.png" />
-                  Spark Editor                  
                 % else:
                   <img src="/beeswax/static/art/icon_beeswax_24.png" />
                   Hive Editor
                 % endif
                 </a>
               </li>
-              <li class="${is_selected(section, 'query')}"><a href="${ url(app_name + ':execute_query') }">${_('Query Editor')}</a></li>
-              % if app_name == 'spark':
-                <li class="class=${is_selected(section, 'jobs')}"><a href="${ url('spark:list_jobs') }">${_('Jobs')}</a></li>
-                <li class="class=${is_selected(section, 'contexts')}"><a href="${ url('spark:list_contexts') }">${_('Contexts')}</a></li>
-                <li class="class=${is_selected(section, 'jars')}"><a href="${ url('spark:list_jars') }">${_('Jars')}</a></li>
-              % endif              
+              <li class="${is_selected(section, 'query')}"><a href="${ url(app_name + ':execute_query') }">${_('Query Editor')}</a></li>            
               <li class="${is_selected(section, 'my queries')}"><a href="${ url(app_name + ':my_queries') }">${_('My Queries')}</a></li>
               <li class="${is_selected(section, 'saved queries')}"><a href="${ url(app_name + ':list_designs') }">${_('Saved Queries')}</a></li>
               <li class="${is_selected(section, 'history')}"><a href="${ url(app_name + ':list_query_history') }">${_('History')}</a></li>
@@ -64,6 +87,7 @@ def is_selected(section, matcher):
         </div>
       </div>
   </div>
+  % endif
 </%def>
 
 <%def name="metastore_menubar()">

+ 84 - 90
apps/spark/src/spark/templates/editor.mako

@@ -35,7 +35,7 @@ ${ common.navbar('editor') }
             <a class="uploadAppModalBtn" href="javascript:void(0);">
               ${ _("None, create one?") }
             </a>
-            <!-- /ko -->
+            <!-- /ko -->
             <a data-bind="if: $root.appName" data-toggle="dropdown" href="javascript:void(0);">
               <strong data-bind="text: $root.appName().nice_name"></strong>
               <i class="fa fa-caret-down"></i>
@@ -76,8 +76,8 @@ ${ common.navbar('editor') }
           </div>
         </li>
         <span class="pull-right">
-		  <button type="button" class="btn btn-primary uploadAppModalBtn">${ _('Upload app') }</button>
-		  <button type="button" class="btn btn-primary createContextModalBtn">${ _('Create context') }</button>
+      <button type="button" class="btn btn-primary uploadAppModalBtn">${ _('Upload app') }</button>
+      <button type="button" class="btn btn-primary createContextModalBtn">${ _('Create context') }</button>
         </span>
       </ul>
     </div>
@@ -88,7 +88,7 @@ ${ common.navbar('editor') }
       <div class="card card-small">
         <div style="margin-bottom: 30px">
           <h1 class="card-heading simple">
-            ${ _('Script Editor') }
+            ${ _('Parameters') }
             % if can_edit_name:
               :
               <a href="javascript:void(0);"
@@ -131,7 +131,6 @@ ${ common.navbar('editor') }
                 <button data-bind="click: tryExecuteQuery" type="button" id="executeQuery" class="btn btn-primary" tabindex="2">${_('Execute')}</button>
                 <button data-bind="click: trySaveQuery, css: {'hide': !$root.query.id() || $root.query.id() == -1}" type="button" class="btn hide">${_('Save')}</button>
                 <button data-bind="click: trySaveAsQuery" type="button" class="btn">${_('Save as...')}</button>
-                <button data-bind="click: tryExplainQuery" type="button" id="explainQuery" class="btn">${_('Explain')}</button>
                 &nbsp; ${_('or create a')} &nbsp;<a type="button" class="btn" href="${ url('spark:editor') }">${_('New query')}</a>
                 <br /><br />
             </div>
@@ -141,6 +140,7 @@ ${ common.navbar('editor') }
         </div>
       </div>
     </div>
+
     <div data-bind="css: {'hide': rows().length == 0}" class="hide">
       <div class="card card-small scrollable">
         <table class="table table-striped table-condensed resultTable" cellpadding="0" cellspacing="0" data-tablescroller-min-height-disable="true" data-tablescroller-enforce-height="true">
@@ -160,16 +160,27 @@ ${ common.navbar('editor') }
           <div class="span10 offset1 center empty-wrapper">
             <i class="fa fa-frown-o"></i>
             <h1>${_('The server returned no results.')}</h1>
-            <br />
+            <br/>
           </div>
         </div>
       </div>
     </div>
+
+    <div id="wait-info" class="hide">
+      <div class="card card-small scrollable">
+        <div class="row-fluid">
+          <div class="span10 offset1 center" style="padding: 30px">
+            <!--[if !IE]><!--><i class="fa fa-spinner fa-spin" style="font-size: 60px; color: #DDD"></i><!--<![endif]-->
+            <!--[if IE]><img src="/static/art/spinner.gif" /><![endif]-->
+          </div>
+        </div>
+      </div>
+    </div>
+
   </div>
+
   <div class="span2" id="navigator">
       <div class="card card-small">
-        <a href="#" title="${_('Double click on a table name or field to insert it in the editor')}" rel="tooltip" data-placement="left" class="pull-right" style="margin:10px;margin-left: 0"><i class="fa fa-question-circle"></i></a>
-        <a id="refreshNavigator" href="#" title="${_('Manually refresh the table list')}" rel="tooltip" data-placement="left" class="pull-right" style="margin:10px"><i class="fa fa-refresh"></i></a>
         <h1 class="card-heading simple"><i class="fa fa-compass"></i> ${_('History')}</h1>
         <div class="card-body">
           <p>
@@ -219,19 +230,19 @@ ${ common.navbar('editor') }
     <h3>${_('Upload application')}</h3>
   </div>
   <div class="modal-body">
-	    ${ _('One class of the jar should implement SparkJob.') }
-	    <div class="control-group">
-	      <label class="control-label">${ _("Local jar file") }</label>
-	      <div class="controls">
-	        <input type="file" name="jar_file" id="jar_file">
-	      </div>
-	    </div>
-        <div class="control-group">
-          <label class="control-label">${ _("App name") }</label>
-          <div class="controls">
-            <input type="text" name="app_name" id="app_name">
-          </div>
-        </div>
+    ${ _('One class of the jar should implement SparkJob.') }
+    <div class="control-group">
+      <label class="control-label">${ _("Local jar file") }</label>
+      <div class="controls">
+        <input type="file" name="jar_file" id="jar_file">
+      </div>
+    </div>
+    <div class="control-group">
+      <label class="control-label">${ _("App name") }</label>
+      <div class="controls">
+        <input type="text" name="app_name" id="app_name">
+      </div>
+    </div>
   </div>
   <div class="modal-footer">
     <button class="btn" data-dismiss="modal">${_('Cancel')}</button>
@@ -247,26 +258,26 @@ ${ common.navbar('editor') }
     <h3>${_('Create context')}</h3>
   </div>
   <div class="modal-body">
-      <form class="form-horizontal" id="createContextForm">
-        <div class="control-group">
-          <label class="control-label">${ _("Name") }</label>
-          <div class="controls">
-            <input type="text" name="name">
-          </div>
+    <form class="form-horizontal" id="createContextForm">
+      <div class="control-group">
+        <label class="control-label">${ _("Name") }</label>
+        <div class="controls">
+          <input type="text" name="name">
         </div>
-        <div class="control-group">
-          <label class="control-label">${ _("Num cpu cores") }</label>
-          <div class="controls">
-            <input type="text" name="numCores"value="1">
-          </div>
+      </div>
+      <div class="control-group">
+        <label class="control-label">${ _("Num cpu cores") }</label>
+        <div class="controls">
+          <input type="text" name="numCores"value="1">
         </div>
-        <div class="control-group">
-          <label class="control-label">${ _("Memory per node") }</label>
-          <div class="controls">
-            <input type="text" name="memPerNode" value="512m">
-          </div>
+      </div>
+      <div class="control-group">
+        <label class="control-label">${ _("Memory per node") }</label>
+        <div class="controls">
+          <input type="text" name="memPerNode" value="512m">
         </div>
-      </form>
+      </div>
+    </form>
   </div>
   <div class="modal-footer">
     <button class="btn" data-dismiss="modal">${_('Cancel')}</button>
@@ -341,9 +352,9 @@ ${ common.navbar('editor') }
     margin-bottom: 20px;
   }
 
-.CodeMirror.cm-s-default {
-   height:150px;
-}
+  .CodeMirror.cm-s-default {
+    height:100px;
+  }
 
   .editorError {
     color: #B94A48;
@@ -420,18 +431,10 @@ ${ common.navbar('editor') }
   var spark_AUTOCOMPLETE_GLOBAL_CALLBACK = $.noop;
 
   $(document).ready(function(){
-
-    var queryPlaceholder = "${_('Example: SELECT * FROM tablename, or press CTRL + space')}";
-
     $("*[rel=tooltip]").tooltip({
       placement: 'bottom'
     });
 
-    var queryEditor = $("#queryField")[0];
-
-    var selectedLine = -1;
-    var errorWidget = null;
-
     $("#help").popover({
       'title': "${_('Did you know?')}",
       'content': $("#help-content").html(),
@@ -439,8 +442,17 @@ ${ common.navbar('editor') }
       'html': true
     });
 
+    // Knockout
+    viewModel = new sparkViewModel();
+    viewModel.fetchAppNames();
+    viewModel.fetchContexts();
+    % if job_id:
+      viewModel.openQuery("${ job_id }");
+    % endif
+    ko.applyBindings(viewModel);
   });
 
+
   function modal(el) {
     var el = $(el);
     return function() {
@@ -462,12 +474,6 @@ ${ common.navbar('editor') }
     viewModel.executeQuery();
   }
 
-  function tryExplainQuery() {
-    var query = getHighlightedQuery() || codeMirror.getValue();
-    viewModel.query.query(query);
-    viewModel.explainQuery();
-  }
-
   function trySaveQuery() {
     var query = getHighlightedQuery() || codeMirror.getValue();
     viewModel.query.query(query);
@@ -503,21 +509,8 @@ ${ common.navbar('editor') }
     }
   }
 
-  function checkLastDatabase(server, database) {
-    var key = "huesparkLastDatabase-" + server;
-    if (database != $.totalStorage(key)) {
-      $.totalStorage(key, database);
-    }
-  }
-
-  function getLastDatabase(server) {
-    var key = "huesparkLastDatabase-" + server;
-    return $.totalStorage(key);
-  }
-
-    var queryEditor = $("#queryField")[0];
-
-    var AUTOCOMPLETE_SET = CodeMirror.sqlHint;
+  var queryEditor = $("#queryField")[0];
+  var AUTOCOMPLETE_SET = CodeMirror.sqlHint;
 
   codeMirror = CodeMirror(function (elt) {
       queryEditor.parentNode.replaceChild(elt, queryEditor);
@@ -543,17 +536,17 @@ ${ common.navbar('editor') }
             var _table = _partial.substring(_partial.lastIndexOf(" ") + 1, _partial.length - 1);
             if (codeMirror.getValue().toUpperCase().indexOf("FROM") > -1) {
               rdbms_getTableColumns(viewModel.server().name(), viewModel.database(), _table, codeMirror.getValue(),
-	              function (columns) {
-	                var _cols = columns.split(" ");
-	                for (var col in _cols) {
-	                  _cols[col] = "." + _cols[col];
-	                }
-	                CodeMirror.catalogFields = _cols.join(" ");
-	                CodeMirror.fromDot = true;
-	                window.setTimeout(function () {
-	                  codeMirror.execCommand("autocomplete");
-	                }, 100);  // timeout for IE8
-	              });
+                function (columns) {
+                  var _cols = columns.split(" ");
+                  for (var col in _cols) {
+                    _cols[col] = "." + _cols[col];
+                  }
+                  CodeMirror.catalogFields = _cols.join(" ");
+                  CodeMirror.fromDot = true;
+                  window.setTimeout(function () {
+                    codeMirror.execCommand("autocomplete");
+                  }, 100);  // timeout for IE8
+                });
             }
           }
         }
@@ -561,13 +554,6 @@ ${ common.navbar('editor') }
     });
 
 
-  // Knockout
-  viewModel = new sparkViewModel();
-  viewModel.fetchAppNames();
-  viewModel.fetchContexts();
-  ko.applyBindings(viewModel);
-
-
   // Editables
   $("#query-name").editable({
     validate: function (value) {
@@ -587,6 +573,7 @@ ${ common.navbar('editor') }
   });
 
   var dataTable = null;
+
   function cleanResultsTable() {
     if (dataTable) {
       dataTable.fnClearTable();
@@ -608,6 +595,7 @@ ${ common.navbar('editor') }
         "bPaginate": false,
         "bLengthChange": false,
         "bInfo": false,
+        "aaSorting":[],
         "oLanguage": {
           "sEmptyTable": "${_('No data available')}",
           "sZeroRecords": "${_('No matching records')}"
@@ -639,9 +627,15 @@ ${ common.navbar('editor') }
       $(".resultTable").width($(".resultTable").parent().width());
     }
   }
-  $(document).on('execute.query', cleanResultsTable);
-  $(document).on('executed.query', resultsTable);
 
+  $(document).on('execute.query', function() {
+    $('#wait-info').show();
+    cleanResultsTable();
+  });
+  $(document).on('executed.query', function() {
+    $('#wait-info').hide();
+    resultsTable();
+  });
   $(document).on('created.context', function() {
     $('#createContextModal').modal('hide');
   });

+ 36 - 26
apps/spark/src/spark/templates/list_jobs.mako

@@ -29,30 +29,40 @@ ${ common.navbar('jobs') }
     <h1 class="card-heading simple">${_('Jobs')}</h1>
 
     <table class="table table-condensed datatables">
-    <thead>
-      <tr>
-        <th>${_('Status')}</th>
-        <th>${_('Job id')}</th>
-        <th>${_('Class path')}</th>
-        <th>${_('Context')}</th>
-        <th>${_('Start time')}</th>
-        <th>${_('Duration')}</th>
-      </tr>
-    </thead>
-    <tbody>
-      % for job in jobs:
-      <tr>
-        <td>${ job.get('status') }</td>
-        <td>${ job.get('jobId') }</td>
-        <td>${ job.get('classPath') }</td>
-        <td>${ job.get('context') }</td>
-        <td>${ job.get('startTime') }</td>
-        <td>${ job.get('duration') }</td>
-      </tr>
-      % endfor
-
-    </tbody>
-  </table>
+     <thead>
+        <tr>
+          <th>${_('Status')}</th>
+          <th>${_('Job id')}</th>
+          <th>${_('Class path')}</th>
+          <th>${_('Context')}</th>
+          <th>${_('Start time')}</th>
+          <th>${_('Duration')}</th>
+        </tr>
+      </thead>
+      <tbody>
+        % for job in jobs:
+        <tr>
+          <td>${ job.get('status') }</td>
+          <td>
+            <%
+              can_view = job.get('status') == 'FINISHED'
+            %>
+            % if can_view:
+              <a href="${ url('spark:view_job', job.get('jobId')) }" data-row-selector="true">
+            % endif
+            ${ job.get('jobId') }
+            % if can_view:
+              </a>
+            % endif
+          </td>
+          <td>${ job.get('classPath') }</td>
+          <td>${ job.get('context') }</td>
+          <td>${ job.get('startTime') }</td>
+          <td>${ job.get('duration') }</td>
+        </tr>
+        % endfor
+      </tbody>
+    </table>
     <div class="card-body">
       <p>
         ## ${ comps.pagination(page) }
@@ -83,8 +93,8 @@ ${ common.navbar('jobs') }
 <script type="text/javascript" charset="utf-8">
   $(document).ready(function () {
     var viewModel = {
-        availableSavedQueries : ko.observableArray(${ jobs_json | n,unicode }),
-        chosenSavedQueries : ko.observableArray([])
+      availableSavedQueries : ko.observableArray(${ jobs_json | n,unicode }),
+      chosenSavedQueries : ko.observableArray([])
     };
 
     ko.applyBindings(viewModel);

+ 3 - 2
apps/spark/src/spark/urls.py

@@ -21,8 +21,9 @@ from django.conf.urls.defaults import patterns, url
 # Views
 urlpatterns = patterns('spark.views',
   url(r'^$', 'editor', name='index'),
-  url(r'^editor/(?P<design_id>\d+)?$', 'editor', name='editor'),
-  url(r'^editor/(?P<design_id>\d+)?$', 'editor', name='execute_query'),
+  url(r'^editor/(?P<design_id>.+)?$', 'editor', name='editor'),
+  url(r'^editor/(?P<design_id>.+)?$', 'editor', name='execute_query'), # For Beeswax
+  url(r'^editor/(?P<design_id>.+)?$', 'editor', name='view_job'), # For browser
   url(r'^list_jobs', 'list_jobs', name='list_jobs'),
   url(r'^list_contexts', 'list_contexts', name='list_contexts'),
   url(r'^delete_contexts', 'delete_contexts', name='delete_contexts'),

+ 5 - 0
apps/spark/src/spark/views.py

@@ -40,6 +40,10 @@ LOG = logging.getLogger(__name__)
 
 
 def editor(request, design_id=None):
+  if design_id is not None and not design_id.isdigit():
+    job_id, design_id = design_id, None
+  else:
+    job_id = None
   action = request.path
   app_name = get_app_name(request)
   query_type = beeswax_models.SavedQuery.TYPES_MAPPING[app_name]
@@ -49,6 +53,7 @@ def editor(request, design_id=None):
     'action': action,
     'design': design,
     'can_edit_name': design.id and not design.is_auto,
+    'job_id': job_id,
   })
 
 

+ 15 - 24
apps/spark/static/js/spark.vm.js

@@ -150,22 +150,6 @@ function sparkViewModel() {
     }
   };
 
-  self.fetchQuery = function(id) {
-    var _id = id || self.query.id();
-    if (_id && _id != -1) {
-      var request = {
-        url: '/spark/api/query/' + _id + '/get',
-        dataType: 'json',
-        type: 'GET',
-        success: function(data) {
-          self.updateQuery(data.design);
-        },
-        error: error_fn
-      };
-      $.ajax(request);
-    }
-  };
-
   self.saveQuery = function() {
     var self = this;
     if (self.query.query() && self.query.name()) {
@@ -204,6 +188,7 @@ function sparkViewModel() {
       type: 'POST',
       success: function(data) {
         self.query.errors.removeAll();
+        self.rows.removeAll();
         if (data.status == 0) {
           $(document).trigger('execute.query', data);
           self.query.id(data.design);
@@ -221,8 +206,8 @@ function sparkViewModel() {
   };
 
   self.checkQueryStatus = function() {
-	var timerId = 0;
-	
+  var timerId = 0;
+
     var request = {
       url: '/spark/api/job/' + self.query.jobId(),
       dataType: 'json',
@@ -230,8 +215,8 @@ function sparkViewModel() {
       success: function(data) {
         // Script finished
         if (data.results.status == 'OK' || data.results.status == 'ERROR') {
-    	  clearInterval(timerId);
-    	  self.updateResults(data.results.result);
+        clearInterval(timerId);
+        self.updateResults(data.results.result);
 
           self.resultsEmpty($.isEmptyObject(data.results.result));
           $(document).trigger('executed.query', data);
@@ -245,6 +230,12 @@ function sparkViewModel() {
     }, 1000);
   };
 
+  self.openQuery = function(jobId) {
+    self.query.jobId(jobId);
+    $(document).trigger('execute.query');
+    self.checkQueryStatus();
+  };
+
   self.fetchAppNames = function() {
     var request = {
       url: '/spark/api/jars',
@@ -280,10 +271,10 @@ function sparkViewModel() {
       success: function(result) {
         self.query.errors.removeAll();
         if (result.status == 'OK') {
-      	  self.contexts().push(createDropdownItem(result.name));
-      	  self.context(result.name);
-      	  self.autoContext(false);
-      	  $(document).trigger('created.context', data);
+          self.contexts().push(createDropdownItem(result.name));
+          self.context(result.name);
+          self.autoContext(false);
+          $(document).trigger('created.context', data);
         } else {
           $(document).trigger('error', result.result);
         }