Răsfoiți Sursa

HUE-7 [metastore] Integrate new create table from a file in metastore

Romain Rigaux 9 ani în urmă
părinte
comite
dc96714

+ 8 - 0
apps/metastore/src/metastore/conf.py

@@ -18,3 +18,11 @@
 from django.utils.translation import ugettext_lazy as _
 
 from desktop.lib.conf import Config
+
+
+ENABLE_NEW_CREATE_TABLE = Config(
+  key="enable_new_create_table",
+  help=_("Flag to turn on the new version of the create table wizard."),
+  type=bool,
+  default=False
+)

+ 13 - 3
apps/metastore/src/metastore/templates/metastore.mako

@@ -19,14 +19,17 @@ from django.utils.translation import ugettext as _
 
 from desktop import conf
 from desktop.conf import USE_NEW_EDITOR
-from beeswax.conf import LIST_PARTITIONS_LIMIT
 from desktop.lib.i18n import smart_unicode
 from desktop.views import commonheader, commonfooter, _ko
+from beeswax.conf import LIST_PARTITIONS_LIMIT
+
+from metastore.conf import ENABLE_NEW_CREATE_TABLE
 %>
 
 <%namespace name="actionbar" file="actionbar.mako" />
 <%namespace name="assist" file="/assist.mako" />
 <%namespace name="components" file="components.mako" />
+
 % if not is_embeddable:
 ${ commonheader(_("Metastore"), app_name, user, request) | n,unicode }
 
@@ -558,8 +561,14 @@ ${ components.menubar() }
   <div class="inline-block pull-right">
     <a class="inactive-action" href="javascript:void(0)" data-bind="tooltip: { placement: 'bottom', delay: 750 }, click: function () { huePubSub.publish('assist.db.refresh', { sourceType: 'hive' }); }" title="${_('Refresh')}"><i class="pointer fa fa-refresh" data-bind="css: { 'fa-spin blue' : $root.reloading }"></i></a>
     % if has_write_access:
-    <a class="inactive-action margin-left-10" data-bind="tooltip: { placement: 'bottom', delay: 750 }, attr: { 'href': '/beeswax/create/import_wizard/' + database().name }" title="${_('Create a new table from a file')}"><span class="fa-stack fa-fw" style="width: 1.28571429em"><i class="fa fa-file-o fa-stack-1x"></i><i class="fa fa-plus-circle fa-stack-1x" style="font-size: 14px; margin-left: 5px; margin-top: 6px;"></i></span></a>
-    <a class="inactive-action margin-left-10" data-bind="tooltip: { placement: 'bottom', delay: 750 }, attr: { 'href': '/beeswax/create/create_table/' + database().name }" title="${_('Create a new table manually')}"><i class="fa fa-plus"></i></a>
+      % if is_embeddable:
+        <a class="inactive-action margin-left-10" data-bind="tooltip: { placement: 'bottom', delay: 750 }, click: function () { onePageViewModel.currentApp('importer') }" title="${_('Create a new table')}"><i class="fa fa-plus"></i></a>
+      % elif ENABLE_NEW_CREATE_TABLE.get():
+        <a class="inactive-action margin-left-10" data-bind="tooltip: { placement: 'bottom', delay: 750 }, attr: { 'href': '${ url('indexer:importer_prefill', source_type='table', target_type='table') }' + database().name }" title="${_('Create a new table')}"><i class="fa fa-plus"></i></a>
+      % else:
+        <a class="inactive-action margin-left-10" data-bind="tooltip: { placement: 'bottom', delay: 750 }, attr: { 'href': '/beeswax/create/import_wizard/' + database().name }" title="${_('Create a new table from a file')}"><span class="fa-stack fa-fw" style="width: 1.28571429em"><i class="fa fa-file-o fa-stack-1x"></i><i class="fa fa-plus-circle fa-stack-1x" style="font-size: 14px; margin-left: 5px; margin-top: 6px;"></i></span></a>
+        <a class="inactive-action margin-left-10" data-bind="tooltip: { placement: 'bottom', delay: 750 }, attr: { 'href': '/beeswax/create/create_table/' + database().name }" title="${_('Create a new table manually')}"><i class="fa fa-plus"></i></a>
+      % endif
     % endif
   </div>
 </script>
@@ -1300,6 +1309,7 @@ ${ components.menubar() }
   })();
 </script>
 </span>
+
 % if not is_embeddable:
 ${ commonfooter(request, messages) | n,unicode }
 % endif

+ 10 - 1
desktop/conf.dist/hue.ini

@@ -919,6 +919,15 @@
     ## validate=true
 
 
+###########################################################################
+# Settings to configure Metastore
+###########################################################################
+
+[metastore]
+  # Flag to turn on the new version of the create table wizard.
+  ## enable_new_create_table=false
+
+
 ###########################################################################
 # Settings to configure Impala
 ###########################################################################
@@ -1287,7 +1296,7 @@
       # Secure connections are the default, but this can be explicitly overridden:
       ## is_secure=true
 
-      # The default calling format uses https://<bucket-name>.s3.amazonaws.com but 
+      # The default calling format uses https://<bucket-name>.s3.amazonaws.com but
       # this may not make sense if DNS is not configured in this way for custom endpoints.
       # e.g. Use boto.s3.connection.OrdinaryCallingFormat for https://s3.amazonaws.com/<bucket-name>
       ## calling_format=boto.s3.connection.S3Connection.DefaultCallingFormat

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

@@ -926,6 +926,15 @@
     ## validate=true
 
 
+###########################################################################
+# Settings to configure Metastore
+###########################################################################
+
+[metastore]
+  # Flag to turn on the new version of the create table wizard.
+  ## enable_new_create_table=false
+
+
 ###########################################################################
 # Settings to configure Impala
 ###########################################################################

+ 7 - 2
desktop/libs/indexer/src/indexer/api3.py

@@ -169,10 +169,15 @@ def _create_table(request, source, destination):
 
 
 def _create_table_from_a_file(request, source, destination):
-  table_name = final_table_name = destination['name']
+  if '.' in destination['name']:
+    database, table_name = destination['name'].split('.', 1)
+  else:
+    database = 'default'
+    table_name = destination['name']
+  final_table_name = table_name
+
   table_format = destination['tableFormat']
 
-  database = destination['database']
   columns = destination['columns']
   partition_columns = destination['partitionColumns']
 

+ 28 - 18
desktop/libs/indexer/src/indexer/templates/importer.mako

@@ -271,11 +271,11 @@ ${ assist.assistPanel() }
           <!-- ko if: createWizard.source.show -->
           <h3 class="card-heading simple">${_('Format')}</h3>
           <div class="card-body">
-              <label>${_('File Type')} <select data-bind="options: $root.createWizard.fileTypes, optionsText: 'description', value: $root.createWizard.fileType"></select></label>
+            <label data-bind="visible: ! createWizard.prefill.source_type">${_('File Type')} <select data-bind="options: $root.createWizard.fileTypes, optionsText: 'description', value: $root.createWizard.fileType"></select></label>
 
-              <span data-bind="with: createWizard.source.format, visible: createWizard.source.show">
-                <!-- ko template: {name: 'format-settings'} --> <!-- /ko -->
-              </span>
+            <span data-bind="with: createWizard.source.format, visible: createWizard.source.show">
+              <!-- ko template: {name: 'format-settings'} --> <!-- /ko -->
+            </span>
           </div>
 
           <h3 class="card-heading simple">${_('Preview')}</h3>
@@ -320,12 +320,21 @@ ${ assist.assistPanel() }
 
       <!-- ko with: createWizard.destination -->
       <div class="control-group">
-        <label for="collectionType" class="control-label"><div>${ _('Type') }</div>
+        <label for="collectionType" class="control-label" data-bind="visible: ! $parent.createWizard.prefill.target_type"><div>${ _('Type') }</div>
           <select id="collectionType" data-bind="options: ouputFormats, value: ouputFormat, optionsValue: 'value', optionsText: 'name'"></select>
         </label>
 
         <label for="collectionName" class="control-label"><div>${ _('Name') }</div>
-          <input type="text" class="form-control input-xlarge" id="collectionName" data-bind="value: name, valueUpdate: 'afterkeydown'" placeholder="${ _('Name') }">
+          <!-- ko if: ouputFormat() != 'table' -->
+            <input type="text" class="form-control input-xlarge" id="collectionName" data-bind="value: name, valueUpdate: 'afterkeydown'" placeholder="${ _('Name') }">
+          <!-- /ko -->
+
+          <!-- ko if: ouputFormat() == 'table' -->
+            <label for="path" class="control-label">
+              <input type="text" data-bind="value: name, hivechooser: name, skipColumns: true, valueUpdate: 'afterkeydown'" placeholder="${ _('Name') }">
+            </label>
+          <!-- /ko -->
+
           <span class="help-inline muted" data-bind="visible: $root.createWizard.isNameAvailable()">
             ${ _('Create a new ') } <span data-bind="text: ouputFormat"></span>
           </span>
@@ -351,11 +360,6 @@ ${ assist.assistPanel() }
             </label>
           </div>
 
-          ${_('Database')}
-          <label class="checkbox">
-            <input type="text" data-bind="value: database">
-          </label>
-
           <label class="checkbox">
             <input type="checkbox" data-bind="checked: importData, disable: ! useDefaultLocation() && $parent.createWizard.source.path() == nonDefaultLocation();"> ${_('Import data')}
           </label>
@@ -837,7 +841,7 @@ ${ assist.assistPanel() }
       init();
     }
 
-    var Source = function (vm) {
+    var Source = function (vm, wizard) {
       var self = this;
 
       self.name = ko.observable('');
@@ -856,8 +860,14 @@ ${ assist.assistPanel() }
           {'value': 'text', 'name': 'Copy paste text'},
           {'value': 'query', 'name': 'SQL Query'},
           {'value': 'dbms', 'name': 'DBMS'},
-          {'value': 'manual', 'name': 'Manual with no input'},
+          {'value': 'manual', 'name': 'Manually'},
       ]);
+      if (wizard.prefill.source_type) {
+        self.inputFormats([
+            {'value': 'file', 'name': 'File'},
+            {'value': 'manual', 'name': 'Manually'},
+        ]);
+      }
 
       // File
       self.path = ko.observable('');
@@ -931,7 +941,7 @@ ${ assist.assistPanel() }
           }
         } else if (self.inputFormat() == 'table') {
           if (self.table().split('.', 2).length == 2) {
-            name = self.tableName();
+            name = self.table();
           }
         } else if (self.inputFormat() == 'query') {
           if (self.query()) {
@@ -942,7 +952,8 @@ ${ assist.assistPanel() }
         return name.replace(' ', '_');
       });
       self.defaultName.subscribe(function(newVal) {
-        vm.createWizard.destination.name(newVal);
+        var prefix = wizard.prefill.target_path ? wizard.prefill.target_path() + '.' : '';
+        vm.createWizard.destination.name(prefix + newVal);
       });
     };
 
@@ -963,7 +974,6 @@ ${ assist.assistPanel() }
       self.columns = ko.observableArray();
 
       // Table
-      self.database = ko.observable('default');
       self.tableFormat = ko.observable('text');
       self.tableFormats = ko.observableArray([
           {'value': 'text', 'name': 'Text'},
@@ -1010,12 +1020,12 @@ ${ assist.assistPanel() }
       self.fieldTypes = ${fields_json | n}.solr;
       self.hiveFieldTypes = ${fields_json | n}.hive;
       self.fileTypes = ${file_types_json | n};
-
+      self.prefill = ko.mapping.fromJS(${prefill | n});
 
       self.show = ko.observable(true);
       self.showCreate = ko.observable(false);
 
-      self.source = new Source(vm);
+      self.source = new Source(vm, self);
       self.destination = new Destination(vm);
 
       self.customDelimiters = ko.observable([

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

@@ -27,6 +27,7 @@ urlpatterns = patterns('indexer.views',
   # V3
   url(r'^indexer/$', 'indexer', name='indexer'),
   url(r'^importer/$', 'importer', name='importer'),
+  url(r'^importer/prefill/(?P<source_type>[^/]+)/(?P<target_type>[^/]+)/(?P<target_path>[^/]+)?$', 'importer_prefill', name='importer_prefill'),
 )
 
 urlpatterns += patterns('indexer.api',

+ 19 - 1
desktop/libs/indexer/src/indexer/views.py

@@ -72,6 +72,23 @@ HIVE_TYPES = HIVE_PRIMITIVE_TYPES + ("array", "map", "struct")
 
 
 def importer(request):
+  prefill = {
+  }
+  
+  return _importer(request, prefill)
+
+
+def importer_prefill(request, source_type, target_type, target_path=None):
+  prefill = {
+    'source_type': source_type,
+    'target_type': target_type,
+    'target_path': target_path
+  }
+  
+  return _importer(request, prefill)
+
+
+def _importer(request, prefill):
   searcher = IndexController(request.user)
   indexes = searcher.get_indexes()
 
@@ -84,7 +101,8 @@ def importer(request):
       'fields_json' : json.dumps({'solr': [field.name for field in FIELD_TYPES], 'hive': HIVE_TYPES}),
       'operators_json' : json.dumps([operator.to_dict() for operator in OPERATORS]),
       'file_types_json' : json.dumps([format_.format_info() for format_ in get_file_indexable_format_types()]),
-      'default_field_type' : json.dumps(Field().to_dict())
+      'default_field_type' : json.dumps(Field().to_dict()),
+      'prefill' : json.dumps(prefill)
   })