Browse Source

HUE-3313 [editor] Save result as a table

Romain Rigaux 9 years ago
parent
commit
4beef78

+ 12 - 9
desktop/core/src/desktop/templates/ko_components.mako

@@ -548,7 +548,7 @@ from desktop.views import _ko
             <div class="control-group">
               <div class="controls">
                 <label class="radio">
-                  <input data-bind="checked: saveTarget() == 'hdfs-file'" type="radio" name="save-results-type">
+                  <input data-bind="checked: saveTarget" type="radio" name="save-results-type" value="hdfs-file">
                   &nbsp;${ _('In an HDFS file') }
                 </label>
                 <span data-bind="visible: saveTarget() == 'hdfs-file'">
@@ -559,6 +559,15 @@ from desktop.views import _ko
                   ${ _('Overwrite') }
                 </label>
               </div>
+              <div class="controls">
+                <label class="radio">
+                  <input data-bind="checked: saveTarget" type="radio" name="save-results-type" value="hive-table">
+                  &nbsp;${ _('In a new table') }
+                </label>
+                <span data-bind="visible: saveTarget() == 'hive-table'">
+                  <input data-bind="value: savePath" type="text" name="target_table" class="input-xlarge" placeholder="${_('Table name or <database>.<table>')}">
+                </span>
+              </div>
             </div>
           </fieldset>
         </form>
@@ -605,7 +614,7 @@ from desktop.views import _ko
         self.saveResults = function() {
           var self = this;
 
-           $.post("${ url('notebook:export_result') }", {
+          $.post("${ url('notebook:export_result') }", {
             notebook: ko.mapping.toJSON(self.notebook.getContext()),
             snippet: ko.mapping.toJSON(self.snippet.getContext()),
             format: ko.mapping.toJSON(self.saveTarget()),
@@ -614,13 +623,7 @@ from desktop.views import _ko
           },
           function(data) {
             if (data.status == 0) {
-              if (data.id) {
-                // Not used yet
-                self.design.watch.url(data.watch_url);
-                self.watchQueryLoop(redirect_fn);
-              } else {
-                window.location.href = data.success_url;
-              }
+              window.location.href = data.watch_url;
             } else {
               $(document).trigger('error', data.message);
             }

+ 10 - 4
desktop/libs/notebook/src/notebook/api.py

@@ -18,6 +18,7 @@
 import json
 import logging
 
+from django.core.urlresolvers import reverse
 from django.forms import ValidationError
 from django.http import HttpResponseBadRequest, HttpResponseRedirect
 from django.utils.translation import ugettext as _
@@ -459,18 +460,23 @@ def export_result(request):
   # Passed by check_document_access_permission but unused by APIs
   notebook = json.loads(request.POST.get('notebook', '{}'))
   snippet = json.loads(request.POST.get('snippet', '{}'))
-  format = json.loads(request.POST.get('format', 'hdfs-file'))
+  data_format = json.loads(request.POST.get('format', 'hdfs-file'))
   destination = json.loads(request.POST.get('destination', ''))
   overwrite = json.loads(request.POST.get('overwrite', False))
 
-  if format == 'hdfs-file':
+  api = get_api(request, snippet)
+
+  if data_format == 'hdfs-file':
     if overwrite and request.fs.exists(destination):
       if request.fs.isfile(destination):
         request.fs.do_as_user(request.user.username, request.fs.rmtree, destination)
       else:
         raise ValidationError(_("The target path is a directory"))
-
-    response['success_url'] = get_api(request, snippet).export_data_as_csv_file(snippet, destination, overwrite)
+    response['watch_url'] = api.export_data_as_csv_file(snippet, destination, overwrite)
+    response['status'] = 0
+  elif data_format == 'hive-table':
+    notebook_id = notebook['id'] or request.GET.get('editor', request.GET.get('notebook'))
+    response['watch_url'] = reverse('notebook:execute_and_watch') + '?action=save_as_table&notebook=' + str(notebook_id) + '&snippet=0&destination=' + destination
     response['status'] = 0
 
   return JsonResponse(response)

+ 2 - 0
desktop/libs/notebook/src/notebook/connectors/base.py

@@ -157,3 +157,5 @@ class Api(object):
     return []
 
   def export_data_as_hdfs_file(self, snippet, target_file, overwrite): raise NotImplementedError()
+
+  def export_data_as_table(self, snippet, destination): raise NotImplementedError()

+ 22 - 0
desktop/libs/notebook/src/notebook/connectors/hiveserver2.py

@@ -343,3 +343,25 @@ class HS2Api(Api):
     upload(target_file, handle, self.request.user, db, self.request.fs)
 
     return '/filebrowser/view=%s' % target_file
+
+
+  def export_data_as_table(self, snippet, destination):
+    db = self._get_db(snippet)
+
+    response = self._get_current_statement(db, snippet)
+
+    statement = response.pop('statement')
+
+    if not statement.strip().lower().startswith('select'):
+      raise Exception(_('Only SELECT statements can be saved. Provided statement: %(query)s') % {'query': statement})
+
+    database = snippet.get('database') or 'default'
+    table = destination
+
+    if '.' in table:
+      database, table = table.split('.', 1)
+
+    hql = 'CREATE TABLE `%s`.`%s` AS %s' % (database, table, statement)
+    success_url = reverse('metastore:describe_table', kwargs={'database': database, 'table': table})
+
+    return hql, success_url

+ 1 - 1
desktop/libs/notebook/src/notebook/decorators.py

@@ -37,7 +37,7 @@ LOG = logging.getLogger(__name__)
 def check_document_access_permission():
   def inner(view_func):
     def decorate(request, *args, **kwargs):
-      notebook_id = request.GET.get('notebook')
+      notebook_id = request.GET.get('notebook', request.GET.get('editor'))
       if not notebook_id:
         notebook_id = json.loads(request.POST.get('notebook', '{}')).get('id')
 

+ 4 - 0
desktop/libs/notebook/src/notebook/static/notebook/js/notebook.ko.js

@@ -695,6 +695,9 @@
                 }, 1000);
               }
             }
+            if (vm.successUrl()) {
+              window.location.href = vm.successUrl();
+            }
           }
           else if (self.status() == 'success') {
             self.progress(99);
@@ -1217,6 +1220,7 @@
     self.selectedNotebook = ko.observable();
     self.combinedContent = ko.observable();
     self.isPlayerMode = ko.observable(false);
+    self.successUrl = ko.observable(options.success_url);
 
     self.sqlSourceTypes = [];
 

+ 2 - 2
desktop/libs/notebook/src/notebook/templates/editor_components.mako

@@ -228,7 +228,7 @@ ${ require.config() }
               % elif editor_type == 'pig':
                 <img src="${ static('pig/art/icon_pig_48.png') }" class="app-icon" />
                 Pig
-              % elif editor_type == 'beeswax':
+              % elif editor_type in ('beeswax', 'hive'):
                 <img src="${ static('beeswax/art/icon_beeswax_48.png') }" class="app-icon" />
                 Hive
               % else:
@@ -387,7 +387,7 @@ ${ require.config() }
         }">
       </div>
       %if hasattr(caller, "addSnippetHTML"):
-        ${caller.addSnippetHTML()}
+        ${ caller.addSnippetHTML() }
       %endif
     </div>
   </div>

+ 1 - 0
desktop/libs/notebook/src/notebook/urls.py

@@ -42,6 +42,7 @@ urlpatterns = patterns('notebook.views',
 
   url(r'^editor/?$', 'editor', name='editor'),
   url(r'^browse/(?P<database>\w+)/(?P<table>\w+)/?$', 'browse', name='browse'),
+  url(r'^execute_and_watch/?$', 'execute_and_watch', name='execute_and_watch'),
 )
 
 # APIs

+ 31 - 1
desktop/libs/notebook/src/notebook/views.py

@@ -22,6 +22,7 @@ from django.db.models import Q
 from django.utils.translation import ugettext as _
 
 from desktop.lib.django_util import render, JsonResponse
+from desktop.lib.exceptions_renderable import PopupException
 from desktop.lib.json_utils import JSONEncoderForHTML
 from desktop.models import Document2, Document
 
@@ -117,6 +118,36 @@ def browse(request, database, table):
   })
 
 
+@check_document_access_permission()
+def execute_and_watch(request):
+  notebook_id = request.GET.get('editor', request.GET.get('notebook'))
+  snippet_id = int(request.GET['snippet'])
+  action = request.GET['action']
+  destination = request.GET['destination']
+
+  notebook = Notebook(document=Document2.objects.get(id=notebook_id))
+  snippet = notebook.get_data()['snippets'][snippet_id]
+  editor_type = snippet['type']
+
+  api = get_api(request, snippet)
+
+  if action == 'save_as_table':
+    sql, success_url = api.export_data_as_table(snippet, destination)
+    editor = make_notebook(name='Execute and watch', editor_type=editor_type, statement=sql, status='ready-execute')
+  else:
+    raise PopupException(_('Action %s is unknown') % action)
+
+  return render('editor.mako', request, {
+      'notebooks_json': json.dumps([editor.get_data()]),
+      'options_json': json.dumps({
+          'languages': [{"name": "%s SQL" % editor_type.title(), "type": editor_type}],
+          'mode': 'editor',
+          'success_url': success_url
+      }),
+      'editor_type': editor_type,
+  })
+
+
 @check_document_modify_permission()
 def delete(request):
   notebooks = json.loads(request.POST.get('notebooks', '[]'))
@@ -171,4 +202,3 @@ def install_examples(request):
     response['message'] = _('A POST request is required.')
 
   return JsonResponse(response)
-