Browse Source

[spark] Add download result data

Remove history tab for now
Move query name to top bar
Rename Jars to Applications
Romain Rigaux 12 năm trước cách đây
mục cha
commit
b9eeb26131

+ 2 - 2
apps/beeswax/src/beeswax/templates/layout.mako

@@ -47,12 +47,12 @@ def is_selected(section, matcher):
             ##<li class="${is_selected(section, 'history')}"><a href="${ url('spark:list_query_history') }">${_('History')}</a></li>
             <li class="currentApp">
               <a href="/spark">
-                ${ _('Browser') }
+                ${ _('Dashboard') }
               </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>
+            <li class="${is_selected(section, 'applications')}"><a href="${ url('spark:list_applications') }">${_('Applications')}</a></li>
           </ul>
         </div>
       </div>

+ 7 - 5
apps/spark/src/spark/api.py

@@ -15,7 +15,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import datetime
 import json
 import logging
 
@@ -159,11 +158,14 @@ def execute(request, design_id=None):
             context=None if form.cleaned_data['autoContext'] else form.cleaned_data['context'],
             sync=False
         )
-        response['status'] = 0
-        response['results'] = results
-        #response['design'] = design.id
+
+        if results['status'] == 'STARTED':
+          response['status'] = 0
+          response['results'] = results
+        else:
+          response['message'] = str(results[1]['result'])
+        response['design'] = design.id
       except Exception, e:
-        response['status'] = -1
         response['message'] = str(e)
 
     else:

+ 1 - 1
apps/spark/src/spark/design.py

@@ -88,7 +88,7 @@ class SparkDesign(object):
           'VERSION': SERIALIZATION_VERSION,
           'query': {
               'type': 3, 'appName': '', 'classPath': 'spark.jobserver.WordCountExample', 'autoContext': True, 'context': '',
-              'params': json.dumps([{'name': 'aa', 'value': 'rr'}])
+              'params': json.dumps([])
           }
       }
 

+ 70 - 2
apps/spark/src/spark/templates/common.mako

@@ -24,6 +24,8 @@ def is_selected(section, matcher):
     return ""
 %>
 
+## Nav bar is also duplicated in beeswax layout.mako.
+
 <%def name="navbar(section='editor')">
   <div class="navbar navbar-inverse navbar-fixed-top">
     <div class="navbar-inner">
@@ -42,15 +44,81 @@ def is_selected(section, matcher):
             ##<li class="${is_selected(section, 'history')}"><a href="${ url('spark:list_query_history') }">${_('History')}</a></li>
             <li class="currentApp">
               <a href="/spark">
-                ${ _('Browser') }
+                ${ _('Dashboard') }
               </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>
+            <li class="${is_selected(section, 'applications')}"><a href="${ url('spark:list_applications') }">${_('Applications')}</a></li>
           </ul>
         </div>
       </div>
     </div>
   </div>
 </%def>
+
+<%def name="createContextModal()">
+<div id="createContextModal" class="modal hide fade">
+  <div class="modal-header">
+    <a href="#" class="close" data-dismiss="modal">&times;</a>
+    <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>
+      </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">${ _("Memory per node") }</label>
+        <div class="controls">
+          <input type="text" name="memPerNode" value="512m">
+        </div>
+      </div>
+    </form>
+  </div>
+  <div class="modal-footer">
+    <button class="btn" data-dismiss="modal">${_('Cancel')}</button>
+    <button data-bind="click: createContext" class="btn btn-primary">${_('Create')}</button>
+  </div>
+</div>
+</%def>
+
+
+<%def name="uploadAppModal()">
+<div id="uploadAppModal" class="modal hide fade">
+  <form class="form-horizontal" id="uploadAppForm" action="${ url('spark:upload_app') }" method="POST" enctype="multipart/form-data">
+  <div class="modal-header">
+    <a href="#" class="close" data-dismiss="modal">&times;</a>
+    <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>
+  </div>
+  <div class="modal-footer">
+    <button class="btn" data-dismiss="modal">${_('Cancel')}</button>
+    <input type="submit" class="btn btn-primary" value="${_('Upload')}"/>
+  </div>
+  </form>
+</div>
+</%def>

+ 88 - 142
apps/spark/src/spark/templates/editor.mako

@@ -33,7 +33,7 @@ ${ common.navbar('editor') }
             ${_('App name')}&nbsp;
             <!-- ko if: $root.appNames().length == 0 -->
             <a class="uploadAppModalBtn" href="javascript:void(0);">
-              ${ _("None, create one?") }
+              ${ _("Missing, add one?") }
             </a>
             <!-- /ko -->
             <a data-bind="if: $root.appName" data-toggle="dropdown" href="javascript:void(0);">
@@ -48,21 +48,21 @@ ${ common.navbar('editor') }
         <li>&nbsp;&nbsp;&nbsp;&nbsp;</li>
         <li>
             ${_('Class path')}&nbsp;
-            <input type="text" data-bind="value: $root.classPath" class="input-xlarge"></input>
+            <input type="text" data-bind="value: $root.classPath" class="input-xlarge" placeholder="spark.jobserver.WordCountExample"></input>
         </li>
         <li>&nbsp;&nbsp;&nbsp;&nbsp;</li>
         <li>
           <div style="display: inline" class="dropdown">
             ${_('Context')}&nbsp;
             <input type="checkbox" data-bind="checked: $root.autoContext" />
-            <span data-bind="visible: $root.autoContext()">
+            <span data-bind="visible: $root.autoContext">
               ${ _('auto') }
             </span>
 
             <span data-bind="visible: ! $root.autoContext()">
               <!-- ko if: $root.contexts().length == 0 -->
               <a class="createContextModalBtn" href="javascript:void(0);">
-                ${ _("None, create one?") }
+                ${ _("Create one?") }
               </a>
               <!-- /ko -->
               <a data-bind="if: $root.context" data-toggle="dropdown" href="javascript:void(0);">
@@ -75,45 +75,42 @@ ${ common.navbar('editor') }
             </span>
           </div>
         </li>
+        % if can_edit_name:
+        <li style="padding-left:50px">
+          ${ _("Script") }
+          <a href="javascript:void(0);"
+             id="query-name"
+             data-type="text"
+             data-name="name"
+             data-value="${design.name}"
+             data-original-title="${ _('Script name') }"
+             data-placement="right">
+          </a>
+          <a href="javascript:void(0);"
+             id="query-description"
+             data-type="textarea"
+             data-name="description"
+             data-value="${design.desc}"
+             data-original-title="${ _('Script description') }"
+             data-placement="right">
+          </a>
+         </p>
+        </li>
+        %endif
+
         <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>
   </div>
+
   <div class="row-fluid">
-    <div class="span10">
+    <div class="span12">
     <div id="query">
       <div class="card card-small">
-        <div style="margin-bottom: 30px">
-          <h1 class="card-heading simple">
-            % if can_edit_name:
-              <a href="javascript:void(0);"
-                 id="query-name"
-                 data-type="text"
-                 data-name="name"
-                 data-value="${design.name}"
-                 data-original-title="${ _('Script name') }"
-                 data-placement="right">
-              </a>
-              :
-            %endif
-            ${ _('Parameters') }
-          </h1>
-          % if can_edit_name:
-            <p style="margin-left: 20px">
-              <a href="javascript:void(0);"
-                 id="query-description"
-                 data-type="textarea"
-                 data-name="description"
-                 data-value="${design.desc}"
-                 data-original-title="${ _('Query description') }"
-                 data-placement="right">
-              </a>
-            </p>
-          % endif
-        </div>
+
         <div class="card-body">
           <div class="tab-content">
             <div id="queryPane">
@@ -125,38 +122,51 @@ ${ common.navbar('editor') }
                 </div>
               </div>
 
-				<div class="control-group">
-				  <div class="controls">
-				      <table class="table-condensed designTable">
-				        <thead>
-				          <tr>
-				            <th>${ _('Name') }</th>
-				            <th>${ _('Value') }</th>
-				            <th/>
-				          </tr>
-				        </thead>
-				        <tbody data-bind="foreach: query.params">
-				          <tr>
-				            <td><input type="text" class="span6 required propKey" data-bind="value: name" /></td>
-				            <td><input type="text" class="span6 required pathChooserKo" data-bind="fileChooser: $data, value: value" /></td>
-				            <td>
-				              <a class="btn" href="#" data-bind="click: $root.removeParam">${ _('Delete') }</a>
-				            </td>
-				          </tr>
-				        </tbody>
-				      </table>
-				      <button class="btn" data-bind="click: $root.addParam">${ _('Add') }</button>
-				  </div>
-				</div>
-
+      <div class="control-group">
+        <label class="control-label" style="padding-right:50px">${ _('Parameters') }</label>
+        <div class="controls">
+            <table class="table-condensed">
+              <thead data-bind="visible: query.params().length > 0">
+                <tr>
+                  <th>${ _('Name') }</th>
+                  <th>${ _('Value') }</th>
+                  <th/>
+                </tr>
+              </thead>
+              <tbody data-bind="foreach: query.params">
+                <tr>
+                  <td><input type="text" class="span6 required propKey" data-bind="value: name" /></td>
+                  <td><input type="text" class="span6 required pathChooserKo" data-bind="fileChooser: $data, value: value" /></td>
+                  <td>
+                    <a class="btn" href="#" data-bind="click: $root.removeParam">${ _('Delete') }</a>
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+            <button class="btn" data-bind="click: $root.addParam">${ _('Add') }</button>
+          </div>
+         </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div class="row-fluid">
+    <div class="span12">
+    <div class="card card-small">
               <div class="actions">
-                <button data-bind="click: tryExecuteQuery" type="button" id="executeQuery" class="btn btn-primary" tabindex="2">${_('Execute')}</button>
+                <button data-bind="click: tryExecuteQuery, enable: $root.appNames().length > 0 && $root.classPath()"
+                    type="button" id="executeQuery" class="btn btn-primary disable-feedback"
+                    tabindex="2" data-loading-text="${ _("Executing...") }">
+                  ${_('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>
                 &nbsp; ${_('or create a')} &nbsp;<a type="button" class="btn" href="${ url('spark:editor') }">${_('New query')}</a>
-                <br /><br />
-            </div>
-
+                <span class="pull-right">
+                  <a type="button" class="btn" data-bind="visible: rows().length != 0, attr: {'href': '${ url('spark:download_result') }' + $root.query.jobId()}">${_('Download')}</a>
+                </span>
+              </div>
+             </div>
             </div>
           </div>
         </div>
@@ -201,19 +211,6 @@ ${ common.navbar('editor') }
 
   </div>
 
-  <div class="span2" id="navigator">
-      <div class="card card-small">
-        <h1 class="card-heading simple"><i class="fa fa-compass"></i> ${_('History')}</h1>
-        <div class="card-body">
-          <p>
-            <input id="navigatorSearch" type="text" placeholder="${ _('Table name...') }" style="width:90%"/>
-            <span id="navigatorNoTables">${_('The selected database has no tables.')}</span>
-            <ul id="navigatorTables" class="unstyled"></ul>
-          </p>
-        </div>
-      </div>
-  </div>
-
   </div>
 </div>
 
@@ -245,67 +242,8 @@ ${ common.navbar('editor') }
   </div>
 </div>
 
-<div id="uploadAppModal" class="modal hide fade">
-  <form class="form-horizontal" id="uploadAppForm" action="${ url('spark:upload_app') }" method="POST" enctype="multipart/form-data">
-  <div class="modal-header">
-    <a href="#" class="close" data-dismiss="modal">&times;</a>
-    <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>
-  </div>
-  <div class="modal-footer">
-    <button class="btn" data-dismiss="modal">${_('Cancel')}</button>
-    <input type="submit" class="btn btn-primary" value="${_('Upload')}"/>
-    ##<button data-bind="click: $('#uploadAppForm').submit()" class="btn btn-primary">${_('Upload')}</button>
-  </div>
-  </form>
-</div>
-
-<div id="createContextModal" class="modal hide fade">
-  <div class="modal-header">
-    <a href="#" class="close" data-dismiss="modal">&times;</a>
-    <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>
-      </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">${ _("Memory per node") }</label>
-        <div class="controls">
-          <input type="text" name="memPerNode" value="512m">
-        </div>
-      </div>
-    </form>
-  </div>
-  <div class="modal-footer">
-    <button class="btn" data-dismiss="modal">${_('Cancel')}</button>
-    <button data-bind="click: createContext" class="btn btn-primary">${_('Create')}</button>
-  </div>
-</div>
+${ common.uploadAppModal() }
+${ common.createContextModal() }
 
 
 <style type="text/css">
@@ -483,19 +421,17 @@ ${ common.navbar('editor') }
   $('.uploadAppModalBtn').click(function(){
     $('#uploadAppModal').modal('show');
   });
+
   $('.createContextModalBtn').click(function(){
     $('#createContextModal').modal('show');
   });
 
   function modalSaveAsQuery() {
-    if (viewModel.appName() && viewModel.query.name()) {
+    if (viewModel.query.name()) {
       viewModel.query.id(-1);
       viewModel.saveQuery();
       $('#saveas-query-name').removeClass('error');
       $('#saveAsQueryModal').modal('hide');
-    } else if (viewModel.query.name()) {
-      $.jHueNotify.error("${_('No application name provided.')}");
-      $('#saveAsQueryModal').modal('hide');
     } else {
       $('#saveas-query-name').addClass('error');
     }
@@ -514,6 +450,14 @@ ${ common.navbar('editor') }
     emptytext: "${ _('Query name') }"
   });
 
+  $("#query-description").editable({
+    success: function(response, newValue) {
+      viewModel.query.description(newValue);
+    },
+    emptytext: "${ _('Empty description') }"
+  });
+
+
   // Events and datatables
   $(document).on('saved.query', function() {
     $.jHueNotify.info("${_('Query saved successfully!')}")
@@ -542,7 +486,7 @@ ${ common.navbar('editor') }
         "bPaginate": false,
         "bLengthChange": false,
         "bInfo": false,
-        "aaSorting":[],
+        "aaSorting": [],
         "oLanguage": {
           "sEmptyTable": "${_('No data available')}",
           "sZeroRecords": "${_('No matching records')}"
@@ -577,10 +521,12 @@ ${ common.navbar('editor') }
 
   $(document).on('execute.query', function() {
     $('#wait-info').show();
+    $("#executeQuery").button("loading");
     cleanResultsTable();
   });
   $(document).on('executed.query', function() {
     $('#wait-info').hide();
+    $("#executeQuery").button("reset");
     resultsTable();
   });
   $(document).on('created.context', function() {

+ 22 - 13
apps/spark/src/spark/templates/list_jars.mako → apps/spark/src/spark/templates/list_applications.mako

@@ -20,13 +20,17 @@
 
 <%namespace name="common" file="common.mako" />
 
-${ commonheader(_('Query'), app_name, user) | n,unicode }
+${ commonheader(_('Applications'), app_name, user) | n,unicode }
 
-${ common.navbar('jars') }
+${ common.navbar('applications') }
 
 <div class="container-fluid">
   <div class="card card-small">
-    <h1 class="card-heading simple">${_('Jars')}</h1>
+    <h1 class="card-heading simple">${_('Applications')}</h1>
+
+    <button type="button" class="btn uploadAppModalBtn">
+      <i class="fa fa-plus-circle"> ${ _('Upload app') }</i>
+    </button>
 
     <table class="table table-condensed datatables">
     <thead>
@@ -36,7 +40,7 @@ ${ common.navbar('jars') }
       </tr>
     </thead>
     <tbody>
-      % for name, ts in jars.iteritems():
+      % for name, ts in applications.iteritems():
       <tr>
         <td>${ name }</td>
         <td>${ ts }</td>
@@ -70,12 +74,16 @@ ${ common.navbar('jars') }
   </form>
 </div>
 
+
+${ common.uploadAppModal() }
+
+
 <script src="/static/ext/js/knockout-min.js" type="text/javascript" charset="utf-8"></script>
 
 <script type="text/javascript" charset="utf-8">
   $(document).ready(function () {
     var viewModel = {
-        availableSavedQueries : ko.observableArray(${ jars | n,unicode }),
+        availableSavedQueries : ko.observableArray(${ applications_json | n,unicode }),
         chosenSavedQueries : ko.observableArray([])
     };
 
@@ -83,18 +91,15 @@ ${ common.navbar('jars') }
 
     var savedQueries = $(".datatables").dataTable({
       "sDom":"<'row'r>t<'row'<'span8'i><''p>>",
-      "bPaginate":false,
-      "bLengthChange":false,
-      "bInfo":false,
+      "bPaginate": false,
+      "bLengthChange": false,
+      "bInfo": false,
       "aaSorting":[
-        [4, 'desc']
+        [1, 'desc']
       ],
       "aoColumns":[
         null,
-        null,
-        null,
-        null,
-        { "sSortDataType":"dom-sort-value", "sType":"numeric" }
+        null
       ],
       "oLanguage":{
         "sEmptyTable":"${_('No data available')}",
@@ -107,6 +112,10 @@ ${ common.navbar('jars') }
       savedQueries.fnFilter($(this).val());
     });
 
+    $('.uploadAppModalBtn').click(function(){
+      $('#uploadAppModal').modal('show');
+    });
+
     $("a[data-row-selector='true']").jHueRowSelector();
   });
 </script>

+ 24 - 5
apps/spark/src/spark/templates/list_contexts.mako

@@ -28,9 +28,14 @@ ${ common.navbar('contexts') }
   <div class="card card-small">
     <h1 class="card-heading simple">${_('Contexts')}</h1>
 
-    <a href="#" id="deleteContextBtn" title="${_('Delete forever')}" class="btn"><i class="fa fa-bolt"></i> ${_('Delete')}</a>
-
-    <table class="table table-condensed datatables">
+    <button type="button" class="btn createContextModalBtn">
+      <i class="fa fa-plus-circle"> ${ _('Create context') }</i>
+    </button>
+    <a href="#" id="deleteContextBtn" title="${_('Delete forever')}" class="btn">
+      <i class="fa fa-bolt"></i> ${ _('Delete') }
+    </a>
+
+    <table class="table table-condensed datatables" id="contextTable">
     <thead>
       <th width="1%"><div class="hueCheckbox selectAll fa" data-selectables="savedCheck"></div></th>
       <th>${ _('Name') }</th>
@@ -72,16 +77,27 @@ ${ common.navbar('contexts') }
   </form>
 </div>
 
+
+${ common.createContextModal() }
+
+
 <script src="/static/ext/js/knockout-min.js" type="text/javascript" charset="utf-8"></script>
+<script src="/static/ext/js/knockout.mapping-2.3.2.js" type="text/javascript" charset="utf-8"></script>
+
+<script src="/spark/static/js/spark.vm.js"></script>
 
 <script type="text/javascript" charset="utf-8">
-  $(document).ready(function () {
+
+  $(document).ready(function() {
     var viewModel = {
         availableSavedQueries : ko.observableArray(${ contexts_json | n,unicode }),
         chosenSavedQueries : ko.observableArray([])
     };
 
-    ko.applyBindings(viewModel);
+    ko.applyBindings(viewModel, $('#deleteContext')[0]);
+
+    //var sparkViewModel = new sparkViewModel();
+    //ko.applyBindings(sparkViewModel, $('#createContextModal')[0]);
 
     var savedQueries = $(".datatables").dataTable({
       "sDom":"<'row'r>t<'row'<'span8'i><''p>>",
@@ -173,6 +189,9 @@ ${ common.navbar('contexts') }
       deleteQueries();
     });
 
+    $('.createContextModalBtn').click(function(){
+      $('#createContextModal').modal('show');
+    });
 
     $("a[data-row-selector='true']").jHueRowSelector();
   });

+ 1 - 1
apps/spark/src/spark/templates/list_jobs.mako

@@ -20,7 +20,7 @@
 
 <%namespace name="common" file="common.mako" />
 
-${ commonheader(_('Query'), app_name, user) | n,unicode }
+${ commonheader(_('Jobs'), app_name, user) | n,unicode }
 
 ${ common.navbar('jobs') }
 

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

@@ -27,8 +27,9 @@ urlpatterns = patterns('spark.views',
   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'),
-  url(r'^list_jars', 'list_jars', name='list_jars'),
+  url(r'^list_applications', 'list_applications', name='list_applications'),
   url(r'^upload_app$', 'upload_app', name='upload_app'),
+  url(r'^download_result/(?P<job_id>.+)?$', 'download_result', name='download_result'),
 )
 
 # APIs

+ 19 - 4
apps/spark/src/spark/views.py

@@ -89,11 +89,13 @@ def delete_contexts(request):
     return render('confirm.mako', request, {'url': request.path, 'title': _('Delete context(s)?')})
 
 
-def list_jars(request):
+def list_applications(request):
   api = get_api(request.user)
+  applications = api.jars()
 
-  return render('list_jars.mako', request, {
-    'jars': api.jars()
+  return render('list_applications.mako', request, {
+    'applications': applications,
+    'applications_json': json.dumps([applications])
   })
 
 
@@ -119,4 +121,17 @@ def upload_app(request):
   else:
     response['results'] = form.errors
 
-  return redirect(reverse('spark:index'))
+  return redirect(request.META['HTTP_REFERER'])
+
+
+def download_result(request, job_id):
+  api = get_api(request.user)
+  result = api.job(job_id)
+
+  mimetype = 'application/json'
+  gen = json.dumps(result['result'])
+
+  resp = HttpResponse(gen, mimetype=mimetype)
+  resp['Content-Disposition'] = 'attachment; filename=query_result.%s' % format
+
+  return resp

+ 0 - 181
apps/spark/static/js/autocomplete.utils.js

@@ -1,181 +0,0 @@
-// 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.
-
-function spark_jsoncalls(options) {
-  if (typeof spark_AUTOCOMPLETE_BASE_URL != "undefined") {
-    if (options.database === null) {
-      $.getJSON(spark_AUTOCOMPLETE_BASE_URL, options.onDataReceived);
-    }
-    if (options.database) {
-      if (options.table) {
-        $.getJSON(spark_AUTOCOMPLETE_BASE_URL + "/servers/" + options.server + "/databases/" + options.database + "/tables/" + options.table + "/columns", options.onDataReceived);
-      }
-      else {
-        $.getJSON(spark_AUTOCOMPLETE_BASE_URL + "/servers/" + options.server + "/databases/" + options.database + "/tables", options.onDataReceived);
-      }
-    }
-  }
-  else {
-    try {
-      console.error("You need to specify a spark_AUTOCOMPLETE_BASE_URL to use the autocomplete");
-    }
-    catch (e) {
-    }
-  }
-}
-
-function spark_hasExpired(timestamp){
-  var TIME_TO_LIVE_IN_MILLIS = 600000; // 10 minutes
-  return (new Date()).getTime() - timestamp > TIME_TO_LIVE_IN_MILLIS;
-}
-
-function spark_getTableAliases(textScanned) {
-  var _aliases = {};
-  var _val = textScanned; //codeMirror.getValue();
-  var _from = _val.toUpperCase().indexOf("FROM ");
-  if (_from > -1) {
-    var _match = _val.toUpperCase().substring(_from).match(/ON|WHERE|GROUP|SORT|ORDER/);
-    var _to = _val.length;
-    if (_match) {
-      _to = _match.index;
-    }
-    var _found = _val.substr(_from, _to).replace(/(\r\n|\n|\r)/gm, "").replace(/from/gi, "").replace(/join/gi, ",").split(",");
-    for (var i = 0; i < _found.length; i++) {
-      var _tablealias = $.trim(_found[i]).split(" ");
-      if (_tablealias.length > 1) {
-        _aliases[_tablealias[1]] = _tablealias[0];
-      }
-    }
-  }
-  return _aliases;
-}
-
-function spark_tableHasAlias(tableName, textScanned) {
-  var _aliases = spark_getTableAliases(textScanned);
-  for (var alias in _aliases) {
-    if (_aliases[alias] == tableName) {
-      return true;
-    }
-  }
-  return false;
-}
-
-function spark_getTableColumns(serverName, databaseName, tableName, textScanned, callback) {
-  if (tableName.indexOf("(") > -1) {
-    tableName = tableName.substr(tableName.indexOf("(") + 1);
-  }
-
-  var _aliases = spark_getTableAliases(textScanned);
-  if (_aliases[tableName]) {
-    tableName = _aliases[tableName];
-  }
-
-  if ($.totalStorage('spark_columns_' + serverName + '_' + databaseName + '_' + tableName) && $.totalStorage('spark_extended_columns_' + serverName + '_' + databaseName + '_' + tableName)) {
-    callback($.totalStorage('spark_columns_' + serverName + '_' + databaseName + '_' + tableName), $.totalStorage('spark_extended_columns_' + serverName + '_' + databaseName + '_' + tableName));
-    if ($.totalStorage('spark_timestamp_columns_' + serverName + '_' + databaseName + '_' + tableName) === null || spark_hasExpired($.totalStorage('spark_timestamp_columns_' + serverName + '_' + databaseName + '_' + tableName))){
-      spark_jsoncalls({
-        server: serverName,
-        database: databaseName,
-        table: tableName,
-        onDataReceived: function (data) {
-          if (typeof spark_AUTOCOMPLETE_GLOBAL_CALLBACK == "function") {
-            spark_AUTOCOMPLETE_GLOBAL_CALLBACK(data);
-          }
-          if (data.error) {
-            if (typeof spark_AUTOCOMPLETE_FAILS_SILENTLY_ON == "undefined" || data.code === null || spark_AUTOCOMPLETE_FAILS_SILENTLY_ON.indexOf(data.code) == -1){
-              $(document).trigger('error', data.error);
-            }
-          }
-          else {
-            $.totalStorage('spark_columns_' + serverName + '_' + databaseName + '_' + tableName, (data.columns ? "* " + data.columns.join(" ") : "*"));
-            $.totalStorage('spark_extended_columns_' + serverName + '_' + databaseName + '_' + tableName, (data.extended_columns ? data.extended_columns : []));
-            $.totalStorage('spark_timestamp_columns_' + serverName + '_' + databaseName + '_' + tableName, (new Date()).getTime());
-          }
-        }
-      });
-    }
-  }
-  else {
-    spark_jsoncalls({
-      server: serverName,
-      database: databaseName,
-      table: tableName,
-      onDataReceived: function (data) {
-        if (typeof spark_AUTOCOMPLETE_GLOBAL_CALLBACK == "function") {
-          spark_AUTOCOMPLETE_GLOBAL_CALLBACK(data);
-        }
-        if (data.error) {
-          if (typeof spark_AUTOCOMPLETE_FAILS_SILENTLY_ON == "undefined" || data.code === null || spark_AUTOCOMPLETE_FAILS_SILENTLY_ON.indexOf(data.code) == -1){
-            $(document).trigger('error', data.error);
-          }
-        }
-        else {
-          $.totalStorage('spark_columns_' + serverName + '_' + databaseName + '_' + tableName, (data.columns ? "* " + data.columns.join(" ") : "*"));
-          $.totalStorage('spark_extended_columns_' + serverName + '_' + databaseName + '_' + tableName, (data.extended_columns ? data.extended_columns : []));
-          $.totalStorage('spark_timestamp_columns_' + serverName + '_' + databaseName + '_' + tableName, (new Date()).getTime());
-          callback($.totalStorage('spark_columns_' + serverName + '_' + databaseName + '_' + tableName), $.totalStorage('spark_extended_columns_' + serverName + '_' + databaseName + '_' + tableName));
-        }
-      }
-    });
-  }
-}
-
-function spark_getTables(serverName, databaseName, callback) {
-  if ($.totalStorage('spark_tables_' + serverName + '_' + databaseName)) {
-    callback($.totalStorage('spark_tables_' + serverName + '_' + databaseName));
-    if ($.totalStorage('spark_timestamp_tables_' + serverName + '_' + databaseName) === null || spark_hasExpired($.totalStorage('spark_timestamp_tables_' + serverName + '_' + databaseName))){
-      spark_jsoncalls({
-        server: serverName,
-        database: databaseName,
-        onDataReceived: function (data) {
-          if (typeof spark_AUTOCOMPLETE_GLOBAL_CALLBACK == "function") {
-            spark_AUTOCOMPLETE_GLOBAL_CALLBACK(data);
-          }
-          if (data.error) {
-            if (typeof spark_AUTOCOMPLETE_FAILS_SILENTLY_ON == "undefined" || data.code === null || spark_AUTOCOMPLETE_FAILS_SILENTLY_ON.indexOf(data.code) == -1){
-              $(document).trigger('error', data.error);
-            }
-          }
-          else {
-            $.totalStorage('spark_tables_' + serverName + '_' + databaseName, data.tables.join(" "));
-            $.totalStorage('spark_timestamp_tables_' + serverName + '_' + databaseName, (new Date()).getTime());
-          }
-        }
-      });
-    }
-  }
-  else {
-    spark_jsoncalls({
-      server: serverName,
-      database: databaseName,
-      onDataReceived: function (data) {
-        if (typeof spark_AUTOCOMPLETE_GLOBAL_CALLBACK == "function") {
-          spark_AUTOCOMPLETE_GLOBAL_CALLBACK(data);
-        }
-        if (data.error) {
-          if (typeof spark_AUTOCOMPLETE_FAILS_SILENTLY_ON == "undefined" || data.code === null || spark_AUTOCOMPLETE_FAILS_SILENTLY_ON.indexOf(data.code) == -1){
-            $(document).trigger('error', data.error);
-          }
-        }
-        else {
-          $.totalStorage('spark_tables_' + serverName + '_' + databaseName, data.tables.join(" "));
-          $.totalStorage('spark_timestamp_tables_' + serverName + '_' + databaseName, (new Date()).getTime());
-          callback($.totalStorage('spark_tables_' + serverName + '_' + databaseName));
-        }
-      }
-    });
-  }
-}

+ 5 - 5
apps/spark/static/js/spark.vm.js

@@ -77,6 +77,7 @@ function sparkViewModel() {
 
   self.updateResults = function(results) {
     self.rows.removeAll();
+    // todo if results is not a list or map
     var newRows = [];
     $.each(results, function(key, value) {
       newRows.push([key, value]);
@@ -210,7 +211,7 @@ function sparkViewModel() {
     data.appName = self.appName().name;
     data.classPath = self.classPath();
     data.autoContext = self.autoContext();
-    data.context = self.context().name;
+    data.context = self.context() ? self.context().name : '';
     data.params = JSON.stringify(self.query.params());
     var request = {
       url: '/spark/api/execute',
@@ -245,9 +246,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);
         }
@@ -301,7 +301,7 @@ function sparkViewModel() {
       success: function(result) {
         self.query.errors.removeAll();
         if (result.status == 'OK') {
-          self.contexts().push(createDropdownItem(result.name));
+          self.contexts.push(createDropdownItem(result.name));
           self.context(result.name);
           self.autoContext(false);
           $(document).trigger('created.context', data);