Przeglądaj źródła

[impala] Initial commit

Beeswax becomes a lib that provides a reusable query editor. The lib is still in the 'apps'
directory for historical reason and Hive UI is the default option of this lib (so Beeswax itself
does not change).
The new Impala app is basded on this Beeswax lib and contains two ini properties.

Done:
Convert Beeswax urls to 'named' urls
Make each namespace of URL plugable and changed all the views/templates. The name of the current
app is available trought a new context processor adding 'app_name' (e.g. 'beeswax' or 'impala').
In each app: History, Saved Queries, My Queries are only showing query from one type (Beeswax or Impala).
Beeswax ini files are reverted to 2.0 release (only one Beeswaxd server can be specified).
Impala app custom settings inherits Beeswax ones.
Hide non supported Impala options.

Until Impala support DDL:
DDL queries are still sent to Beeswaxd (query from query editor are always sent to Impala tough).
beeswax.py/design.py queries are always of Beeswax type (e.g. 0 or null).
Romain Rigaux 13 lat temu
rodzic
commit
e299da3
46 zmienionych plików z 489 dodań i 402 usunięć
  1. 4 2
      apps/Makefile
  2. 1 30
      apps/beeswax/src/beeswax/conf.py
  3. 13 9
      apps/beeswax/src/beeswax/create_table.py
  4. 1 1
      apps/beeswax/src/beeswax/design.py
  5. 15 36
      apps/beeswax/src/beeswax/forms.py
  6. 1 4
      apps/beeswax/src/beeswax/management/commands/beeswax_server.py
  7. 3 2
      apps/beeswax/src/beeswax/models.py
  8. 24 33
      apps/beeswax/src/beeswax/server/dbms.py
  9. 2 0
      apps/beeswax/src/beeswax/settings.py
  10. 5 3
      apps/beeswax/src/beeswax/templates/choose_delimiter.mako
  11. 7 5
      apps/beeswax/src/beeswax/templates/choose_file.mako
  12. 6 20
      apps/beeswax/src/beeswax/templates/configuration.mako
  13. 13 11
      apps/beeswax/src/beeswax/templates/create_table_index.mako
  14. 6 4
      apps/beeswax/src/beeswax/templates/create_table_manually.mako
  15. 5 3
      apps/beeswax/src/beeswax/templates/define_columns.mako
  16. 1 1
      apps/beeswax/src/beeswax/templates/describe_partitions.mako
  17. 6 6
      apps/beeswax/src/beeswax/templates/describe_table.mako
  18. 28 26
      apps/beeswax/src/beeswax/templates/execute.mako
  19. 8 1
      apps/beeswax/src/beeswax/templates/explain.mako
  20. 12 11
      apps/beeswax/src/beeswax/templates/index.mako
  21. 6 6
      apps/beeswax/src/beeswax/templates/layout.mako
  22. 8 6
      apps/beeswax/src/beeswax/templates/list_designs.mako
  23. 5 5
      apps/beeswax/src/beeswax/templates/list_history.mako
  24. 1 1
      apps/beeswax/src/beeswax/templates/load_table.mako
  25. 10 10
      apps/beeswax/src/beeswax/templates/my_queries.mako
  26. 4 7
      apps/beeswax/src/beeswax/templates/parameterization.mako
  27. 1 1
      apps/beeswax/src/beeswax/templates/save_results.mako
  28. 11 8
      apps/beeswax/src/beeswax/templates/show_tables.mako
  29. 3 3
      apps/beeswax/src/beeswax/templates/util.mako
  30. 4 4
      apps/beeswax/src/beeswax/templates/watch_results.mako
  31. 4 4
      apps/beeswax/src/beeswax/templates/watch_wait.mako
  32. 7 8
      apps/beeswax/src/beeswax/test_base.py
  33. 9 4
      apps/beeswax/src/beeswax/tests.py
  34. 30 31
      apps/beeswax/src/beeswax/urls.py
  35. 60 60
      apps/beeswax/src/beeswax/views.py
  36. 24 0
      apps/impala/Makefile
  37. 2 0
      apps/impala/babel.cfg
  38. 20 0
      apps/impala/hueversion.py
  39. 29 0
      apps/impala/setup.py
  40. 3 0
      apps/impala/static/help/index.html
  41. 13 16
      desktop/conf.dist/hue.ini
  42. 14 16
      desktop/conf/pseudo-distributed.ini.tmpl
  43. 42 0
      desktop/core/src/desktop/context_processors.py
  44. 13 1
      desktop/core/src/desktop/settings.py
  45. 4 2
      desktop/core/src/desktop/templates/error.mako
  46. 1 1
      desktop/core/src/desktop/urls.py

+ 4 - 2
apps/Makefile

@@ -39,7 +39,8 @@ APPS := about \
   oozie \
   proxy \
   shell \
-  useradmin
+  useradmin \
+  impala
 
 ################################################
 # Install all applications into the Desktop environment
@@ -107,7 +108,8 @@ I18N_APPS := about \
   jobsub \
   shell \
   useradmin \
-  oozie
+  oozie \
+  impala
 
 COMPILE_LOCALE_TARGETS := $(I18N_APPS:%=.recursive-compile-locales/%)
 compile-locales: $(COMPILE_LOCALE_TARGETS)

+ 1 - 30
apps/beeswax/src/beeswax/conf.py

@@ -18,7 +18,7 @@ import os.path
 
 from django.utils.translation import ugettext_lazy as _
 
-from desktop.lib.conf import Config, UnspecifiedConfigSection, ConfigSection, coerce_bool
+from desktop.lib.conf import Config, coerce_bool
 
 
 SERVER_INTERFACE = Config(
@@ -27,41 +27,12 @@ SERVER_INTERFACE = Config(
   default="beeswax")
 
 
-QUERY_SERVERS = UnspecifiedConfigSection(
-  "query_servers",
-  help=_("One entry for each Query Server that can execute some queries."),
-  each=ConfigSection(
-    help=_("Information about a single Query Server"),
-    members=dict(
-      SERVER_HOST = Config(
-        key="server_host",
-        help=_("Host where the Query Server Thrift daemon is running."),
-        private=True,
-        default="localhost"),
-      SERVER_PORT = Config(
-        key="server_port",
-        help=_("Configure the port the Query Server Thrift server."),
-        default=8002,
-        type=int),
-      SUPPORT_DDL = Config(
-        key='support_ddl',
-        default=True,
-        type=coerce_bool,
-        help=_('If DDL queries are supported (e.g. DROP can be sent directly to this server).'))
-    )
-  )
-)
-
-# Deprecated! To remove in Hue 3
-# Multiple sections are now available in QUERY_SERVERS
 BEESWAX_SERVER_HOST = Config(
   key="beeswax_server_host",
   help=_("Host where Beeswax server Thrift daemon is running."),
   private=True,
   default="localhost")
 
-# Deprecated! To remove in Hue 3
-# Multiple sections are now available in QUERY_SERVERS
 BEESWAX_SERVER_PORT = Config(
   key="beeswax_server_port",
   help=_("Configure the port the Beeswax Thrift server runs on."),

+ 13 - 9
apps/beeswax/src/beeswax/create_table.py

@@ -22,9 +22,10 @@ Views & controls for creating tables
 import logging
 import gzip
 
-from django.core import urlresolvers
+from django.core.urlresolvers import reverse
 from django.utils.translation import ugettext as _
 
+from desktop.context_processors import get_app_name
 from desktop.lib import django_mako, i18n
 from desktop.lib.django_util import render
 from desktop.lib.exceptions import PopupException
@@ -37,7 +38,7 @@ from beeswax.forms import CreateTableForm, ColumnTypeFormSet,\
   PartitionTypeFormSet, CreateByImportFileForm, CreateByImportDelimForm,\
   TERMINATOR_CHOICES
 from beeswax.server import dbms
-from beeswax.views import describe_table, execute_directly
+from beeswax.views import execute_directly
 
 
 LOG = logging.getLogger(__name__)
@@ -110,6 +111,7 @@ def import_wizard(request):
     - Does not work with binary data.
   """
   encoding = i18n.get_site_encoding()
+  app_name = get_app_name(request)
 
   if request.method == 'POST':
     # Have a while loop to allow an easy way to break
@@ -193,7 +195,7 @@ def import_wizard(request):
 
       if do_s2_auto_delim or do_s2_user_delim or cancel_s3_column_def:
         return render('choose_delimiter.mako', request, dict(
-          action=urlresolvers.reverse(import_wizard),
+          action=reverse(app_name + ':import_wizard'),
           delim_readable=DELIMITER_READABLE.get(s2_delim_form['delimiter'].data[0], s2_delim_form['delimiter'].data[1]),
           initial=delim_is_auto,
           file_form=s1_file_form,
@@ -216,7 +218,7 @@ def import_wizard(request):
             ))
           s3_col_formset = ColumnTypeFormSet(prefix='cols', initial=columns)
         return render('define_columns.mako', request, dict(
-          action=urlresolvers.reverse(import_wizard),
+          action=reverse(app_name + ':import_wizard'),
           file_form=s1_file_form,
           delim_form=s2_delim_form,
           column_formset=s3_col_formset,
@@ -248,7 +250,7 @@ def import_wizard(request):
     s1_file_form = CreateByImportFileForm()
 
   return render('choose_file.mako', request, dict(
-    action=urlresolvers.reverse(import_wizard),
+    action=reverse(app_name + ':import_wizard'),
     file_form=s1_file_form,
   ))
 
@@ -257,14 +259,15 @@ def _submit_create_and_load(request, create_hql, table_name, path, do_load):
   """
   Submit the table creation, and setup the load to happen (if ``do_load``).
   """
-  on_success_params = { }
+  on_success_params = {}
+  app_name = get_app_name(request)
 
   if do_load:
     on_success_params['table'] = table_name
     on_success_params['path'] = path
-    on_success_url = urlresolvers.reverse(load_after_create)
+    on_success_url = reverse(app_name + ':load_after_create')
   else:
-    on_success_url = urlresolvers.reverse(describe_table, kwargs={'table': table_name})
+    on_success_url = reverse(app_name + ':describe_table', kwargs={'table': table_name})
 
   query = hql_query(create_hql)
   return execute_directly(request, query,
@@ -454,6 +457,7 @@ def load_after_create(request):
   LOG.debug("Auto loading data from %s into table %s" % (path, tablename))
   hql = "LOAD DATA INPATH '%s' INTO TABLE `%s`" % (path, tablename)
   query = hql_query(hql)
-  on_success_url = urlresolvers.reverse(describe_table, kwargs={'table': tablename})
+  app_name = get_app_name(request)
+  on_success_url = reverse(app_name + ':describe_table', kwargs={'table': tablename})
 
   return execute_directly(request, query, on_success_url=on_success_url)

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

@@ -36,7 +36,7 @@ SERIALIZATION_VERSION = '0.4.0'
 
 
 def hql_query(hql):
-  data_dict = simplejson.loads('{"query": {"email_notify": null, "query": null, "type": null, "is_parameterized": null}, '
+  data_dict = simplejson.loads('{"query": {"email_notify": null, "query": null, "type": 0, "is_parameterized": true}, '
                                '"functions": [], "VERSION": "0.4.0", "file_resources": [], "settings": []}')
   if not (isinstance(hql, str) or isinstance(hql, unicode)):
     raise Exception('Requires a SQL text query of type <str>, <unicode> and not %s' % type(hql))

+ 15 - 36
apps/beeswax/src/beeswax/forms.py

@@ -18,17 +18,17 @@
 from django import forms
 from django.utils.translation import ugettext as _, ugettext_lazy as _t
 
+import hadoop
 import hive_metastore
 
 from desktop.lib.django_forms import simple_formset_factory, DependencyAwareForm
 from desktop.lib.django_forms import ChoiceOrOtherField, MultiForm, SubmitButton
-
 from filebrowser.forms import PathField
 
-from beeswax import common, conf, models
+from beeswax import common
 from beeswax.design import _strip_trailing_semicolon
-import hadoop
 from beeswax.server.dbms import NoSuchObjectException
+from beeswax.models import SavedQuery
 
 
 class QueryForm(MultiForm):
@@ -38,28 +38,14 @@ class QueryForm(MultiForm):
       settings=SettingFormSet,
       file_resources=FileResourceFormSet,
       functions=FunctionFormSet,
-      saveform=SaveForm,
-      query_servers=QueryServerForm)
-
-
-class QueryServerForm(forms.Form):
-  server = forms.CharField(max_length=128, help_text=_t('Host:port of the query server.'))
-
-  def __init__(self, *args, **kwargs):
-    super(QueryServerForm, self).__init__(*args, **kwargs)
-
-    if conf.QUERY_SERVERS:
-      servers = ((server, server) for server in conf.QUERY_SERVERS.keys())
-    else:
-      servers = (('default', 'default'),)
-    self.fields['server'].widget = forms.Select(choices=servers)
+      saveform=SaveForm)
 
 
 class SaveForm(forms.Form):
   """Used for saving query design."""
   name = forms.CharField(required=False,
                         max_length=64,
-                        initial=models.SavedQuery.DEFAULT_NEW_DESIGN_NAME,
+                        initial=SavedQuery.DEFAULT_NEW_DESIGN_NAME,
                         help_text=_t('Change the name to save as a new design.'))
   desc = forms.CharField(required=False, max_length=1024, label=_t("Description"))
   save = forms.BooleanField(widget=SubmitButton, required=False)
@@ -73,15 +59,14 @@ class SaveForm(forms.Form):
     self.fields['saveas'].widget.label = _('Save As')
 
   def clean_name(self):
-    name = self.cleaned_data.get('name', '').strip()
-    return name
+    return self.cleaned_data.get('name', '').strip()
 
   def clean(self):
     if self.errors:
       return
     save = self.cleaned_data.get('save')
     name = self.cleaned_data.get('name')
-    if save and len(name) == 0:
+    if save and not name:
       # Bother with name iff we're saving
       raise forms.ValidationError(_('Please enter a name'))
     return self.cleaned_data
@@ -132,9 +117,10 @@ class SaveResultsForm(DependencyAwareForm):
 class HQLForm(forms.Form):
   query = forms.CharField(label=_t("Query Editor"),
                           required=True,
-                          widget=forms.Textarea(attrs={'class':'beeswax_query'}))
+                          widget=forms.Textarea(attrs={'class': 'beeswax_query'}))
   is_parameterized = forms.BooleanField(required=False, initial=True)
   email_notify = forms.BooleanField(required=False, initial=False)
+  type = forms.IntegerField(required=False, initial=0)
 
   def clean_query(self):
     return _strip_trailing_semicolon(self.cleaned_data['query'])
@@ -155,7 +141,7 @@ class FileResourceForm(forms.Form):
       ("FILE", ("file")),
     ], help_text=_t("Resources to upload with your Hive job." +
        "  Use 'jar' for UDFs.  Use 'file' and 'archive' for "
-       "files to be copied and made locally available duirng MAP/TRANSFORM. " +
+       "files to be copied and made locally available during MAP/TRANSFORM. " +
        "Paths are on HDFS.")
   )
 
@@ -232,8 +218,7 @@ class CreateTableForm(DependencyAwareForm):
   ]
 
   # External?
-  use_default_location = forms.BooleanField(required=False, initial=True,
-    label=_t("Use default location"))
+  use_default_location = forms.BooleanField(required=False, initial=True, label=_t("Use default location"))
   external_location = forms.CharField(required=False, help_text=_t("Path to HDFS directory or file of table data."))
 
   dependencies += [
@@ -297,22 +282,16 @@ class CreateByImportDelimForm(forms.Form):
   def clean(self):
     # ChoiceOrOtherField doesn't work with required=True
     delimiter = self.cleaned_data.get('delimiter')
-    if not delimiter:
-      raise forms.ValidationError(_('Delimiter value is required.'))
-    _clean_terminator(delimiter)
-    return self.cleaned_data
-
-  def old(self):
     if delimiter.isdigit():
       try:
         chr(int(delimiter))
         return int(delimiter)
       except ValueError:
         raise forms.ValidationError(_('Delimiter value must be smaller than 256.'))
-    val = delimiter.decode('string_escape')
-    if len(val) != 1:
-      raise forms.ValidationError(_('Delimiter must be exactly one character.'))
-    return ord(val)
+    if not delimiter:
+      raise forms.ValidationError(_('Delimiter value is required.'))
+    _clean_terminator(delimiter)
+    return self.cleaned_data
 
 
 # Note, struct is not currently supported.  (Because it's recursive, for example.)

+ 1 - 4
apps/beeswax/src/beeswax/management/commands/beeswax_server.py

@@ -60,10 +60,7 @@ class Command(NoArgsCommand):
     if dt_host == '0.0.0.0':
       dt_host = '127.0.0.1'
 
-    if 'beeswax_server_port' in beeswax.conf.QUERY_SERVERS['bind_to']:
-      server_port = beeswax.conf.QUERY_SERVERS['default'].SERVER_PORT.get()
-    else:
-      server_port = beeswax.conf.BEESWAX_SERVER_PORT.get()
+    server_port = beeswax.conf.BEESWAX_SERVER_PORT.get()
 
     args = [
       os.path.basename(bin),

+ 3 - 2
apps/beeswax/src/beeswax/models.py

@@ -97,7 +97,7 @@ class QueryHistory(models.Model):
       return HiveServerQueryHistory.objects.get(id=id)
 
 
-  def get_query_server(self):
+  def get_query_server_config(self):
     return dict(zip(['server_name', 'server_host', 'server_port', 'server_type'],
                     [self.server_name, self.server_host, self.server_port, self.server_type]))
 
@@ -216,7 +216,8 @@ class SavedQuery(models.Model):
   """
   DEFAULT_NEW_DESIGN_NAME = _('My saved query')
   AUTO_DESIGN_SUFFIX = _(' (new)')
-  TYPES = (HQL, REPORT) = range(2) # REPORT is unused
+  TYPES = (HQL, IMPALA) = range(2)
+  TYPES_MAPPING = {'beeswax': HQL, 'hql': HQL, 'impala': IMPALA}
 
   type = models.IntegerField(null=False)
   owner = models.ForeignKey(User, db_index=True)

+ 24 - 33
apps/beeswax/src/beeswax/server/dbms.py

@@ -26,7 +26,9 @@ from filebrowser.views import location_to_url
 
 from beeswaxd.ttypes import BeeswaxException
 
-from beeswax import conf
+from beeswax.conf import BEESWAX_SERVER_HOST, BEESWAX_SERVER_PORT,\
+  BROWSE_PARTITIONED_TABLE_LIMIT
+from impala.conf import SERVER_HOST, SERVER_PORT
 from beeswax.design import hql_query
 from beeswax.models import QueryHistory, HIVE_SERVER2
 from beeswax.conf import SERVER_INTERFACE
@@ -41,7 +43,7 @@ def get(user, query_server=None):
   from beeswax.server.beeswax_lib import BeeswaxClient
 
   if query_server is None:
-    query_server = get_query_server(support_ddl=True)
+    query_server = get_query_server_config(requires_ddl=True)
 
   if SERVER_INTERFACE.get() == HIVE_SERVER2:
     return Dbms(HiveServerClientCompatible(HiveServerClient(query_server, user)), QueryHistory.SERVER_TYPE[1][0])
@@ -50,33 +52,22 @@ def get(user, query_server=None):
 
 
 
-def get_query_server(name='default', support_ddl=False):
-  if 'beeswax_server_host' in conf.QUERY_SERVERS['bind_to'] or 'beeswax_server_port' in conf.QUERY_SERVERS['bind_to']:
-    return {
-          'server_name': 'default',
-          'server_host': conf.BEESWAX_SERVER_HOST.get(),
-          'server_port': conf.BEESWAX_SERVER_PORT.get(),
-          'support_ddl': True,
+def get_query_server_config(name='beeswax', requires_ddl=False):
+  if name == 'impala' and not requires_ddl:
+    query_server = {
+        'server_name': 'impala',
+        'server_host': SERVER_HOST.get(),
+        'server_port': SERVER_PORT.get(),
+        'support_ddl': False,
+    }
+  else:
+    query_server = {
+        'server_name': 'beeswax',
+        'server_host': BEESWAX_SERVER_HOST.get(),
+        'server_port': BEESWAX_SERVER_PORT.get(),
+        'support_ddl': True,
       }
 
-  servers = conf.QUERY_SERVERS
-
-  if support_ddl:
-    if not servers[name].SUPPORT_DDL.get():
-      for key in servers.keys():
-        if servers[key].SUPPORT_DDL.get():
-          name = key
-          break
-
-  config = servers[name]
-
-  query_server = {
-      'server_name': name,
-      'server_host': config.SERVER_HOST.get(),
-      'server_port': config.SERVER_PORT.get(),
-      'support_ddl': config.SUPPORT_DDL.get(),
-  }
-
   return query_server
 
 
@@ -128,7 +119,7 @@ class Dbms:
   def get_sample(self, table):
     """No samples if it's a view (HUE-526)"""
     if not table.is_view:
-      limit = min(100, conf.BROWSE_PARTITIONED_TABLE_LIMIT.get())
+      limit = min(100, BROWSE_PARTITIONED_TABLE_LIMIT.get())
       hql = "SELECT * FROM `%s` LIMIT %s" % (table.name, limit)
       query = hql_query(hql)
       handle = self.execute_and_wait(query, timeout_sec=5.0)
@@ -201,7 +192,7 @@ class Dbms:
     except BeeswaxException, ex: # TODO HS2
       LOG.exception(ex)
       # Kind of expected (hql compile/syntax error, etc.)
-      if ex.handle:
+      if hasattr(ex, 'handle') and ex.handle:
         query_history.server_id = ex.handle.id
         query_history.log_context = ex.handle.log_context
       query_history.save_state(QueryHistory.STATE.failed)
@@ -229,8 +220,8 @@ class Dbms:
 
 
   def get_partitions(self, db_name, table, max_parts=None):
-    if max_parts is None or max_parts > conf.BROWSE_PARTITIONED_TABLE_LIMIT.get():
-      max_parts = conf.BROWSE_PARTITIONED_TABLE_LIMIT.get()
+    if max_parts is None or max_parts > BROWSE_PARTITIONED_TABLE_LIMIT.get():
+      max_parts = BROWSE_PARTITIONED_TABLE_LIMIT.get()
 
     return self.client.get_partitions(db_name, table.name, max_parts)
 
@@ -254,7 +245,7 @@ class Dbms:
   def _get_browse_limit_clause(self, table):
     """Get the limit clause when browsing a partitioned table"""
     if table.partition_keys:
-      limit = conf.BROWSE_PARTITIONED_TABLE_LIMIT.get()
+      limit = BROWSE_PARTITIONED_TABLE_LIMIT.get()
       if limit > 0:
         return "LIMIT %d" % (limit,)
     return ""
@@ -284,7 +275,7 @@ def expand_exception(exc, db):
     log = db.get_log(exc)
   except:
     # Always show something, even if server has died on the job.
-    log = _("Could not retrieve log.")
+    log = _("Could not retrieve logs.")
 
   if not exc.message:
     error_message = _("Unknown exception.")

+ 2 - 0
apps/beeswax/src/beeswax/settings.py

@@ -18,3 +18,5 @@ NICE_NAME = "Beeswax (Hive UI)"
 REQUIRES_HADOOP = True
 ICON = "/beeswax/static/art/icon_beeswax_24.png"
 MENU_INDEX = 10
+
+IS_URL_NAMESPACED = True

+ 5 - 3
apps/beeswax/src/beeswax/templates/choose_delimiter.mako

@@ -17,10 +17,12 @@
 from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
 %>
+
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="util" file="util.mako" />
-${commonheader(_('Create table from file'), "beeswax", user, "100px")}
+
+${commonheader(_('Create table from file'), app_name, user, '100px')}
 ${layout.menubar(section='tables')}
 
 <div class="container-fluid">
@@ -30,8 +32,8 @@ ${layout.menubar(section='tables')}
             <div class="well sidebar-nav">
                 <ul class="nav nav-list">
                     <li class="nav-header">${_('Actions')}</li>
-                    <li><a href="${ url('beeswax.create_table.import_wizard')}">${_('Create a new table from a file')}</a></li>
-                    <li><a href="${ url('beeswax.create_table.create_table')}">${_('Create a new table manually')}</a></li>
+                    <li><a href="${ url(app_name + ':import_wizard')}">${_('Create a new table from a file')}</a></li>
+                    <li><a href="${ url(app_name + ':create_table')}">${_('Create a new table manually')}</a></li>
                 </ul>
             </div>
         </div>

+ 7 - 5
apps/beeswax/src/beeswax/templates/choose_file.mako

@@ -17,9 +17,11 @@
 from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
 %>
+
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
-${commonheader(_('Create table from file'), "beeswax", user, "100px")}
+
+${commonheader(_('Create table from file'), app_name, user, '100px')}
 ${layout.menubar(section='tables')}
 
 
@@ -30,14 +32,14 @@ ${layout.menubar(section='tables')}
             <div class="well sidebar-nav">
                 <ul class="nav nav-list">
                     <li class="nav-header">${_('Actions')}</li>
-                    <li><a href="${ url('beeswax.create_table.import_wizard')}">${_('Create a new table from a file')}</a></li>
-                    <li><a href="${ url('beeswax.create_table.create_table')}">${_('Create a new table manually')}</a></li>
+                    <li><a href="${ url(app_name + ':import_wizard')}">${_('Create a new table from a file')}</a></li>
+                    <li><a href="${ url(app_name + ':create_table')}">${_('Create a new table manually')}</a></li>
                 </ul>
             </div>
         </div>
         <div class="span9">
             <ul class="nav nav-pills">
-                <li class="active"><a href="${ url('beeswax.create_table.import_wizard') }">${_('Step 1: Choose File')}</a></li>
+                <li class="active"><a href="${ url(app_name + ':import_wizard') }">${_('Step 1: Choose File')}</a></li>
                 <li><a id="step2" href="#">${_('Step 2: Choose Delimiter')}</a></li>
                 <li><a href="#">${_('Step 3: Define Columns')}</a></li>
             </ul>
@@ -73,7 +75,7 @@ ${layout.menubar(section='tables')}
                         <div class="controls">
                             ${comps.field(file_form["path"],
                             placeholder="/user/user_name/data_dir",
-                            klass="pathChooser",
+                            klass="pathChooser input-xxlarge",
                             file_chooser=True,
                             show_errors=False
                             )}

+ 6 - 20
apps/beeswax/src/beeswax/templates/configuration.mako

@@ -17,24 +17,21 @@
 from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
 %>
+
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="util" file="util.mako" />
 
-${commonheader(_('Configuration Variables'), "beeswax", user, "100px")}
+${commonheader(_('Configuration Variables'), app_name, user, '100px')}
 ${layout.menubar(section='configuration')}
 
 <div class="container-fluid">
 	<h1>${_('Configuration Variables')}</h1>
 	<div class="well">
 		<form class="form-search" method="POST">
-		  <span>
-		    ${server_form['server']}
-		    <button type="submit" class="btn btn-primary">${_('Look')}</button>
-		   </span>
-		   <span class="pull-right">
-              ${_('Filter:')} <input type="text" id="filterInput" class="input-xlarge search-query" placeholder="${_('Search for key, value, etc...')}">
-		      <a href="#" id="clearFilterBtn" class="btn">${_('Clear')}</a>
-		   </span>
+		    <span class="pull-left">
+                ${_('Filter:')} <input type="text" id="filterInput" class="input-xlarge search-query" placeholder="${_('Search for key, value, etc...')}">
+		        <a href="#" id="clearFilterBtn" class="btn">${_('Clear')}</a>
+		    </span>
 		</form>
 	</div>
 	<table class="table table-striped table-condensed datatables">
@@ -55,9 +52,6 @@ ${layout.menubar(section='configuration')}
 	</table>
 </div>
 
-
-<script src="/static/ext/js/jquery/plugins/jquery.cookie.js"></script>
-
 <script type="text/javascript" charset="utf-8">
 	$(document).ready(function(){
 		$(".datatables").dataTable({
@@ -85,14 +79,6 @@ ${layout.menubar(section='configuration')}
 	            $(value).show();
 	        });
 	    });
-
-        $("#id_server").change(function(){
-            $.cookie("hueBeeswaxLastQueryServer", $(this).val(), {expires: 90});
-        });
-
-        if ($.cookie("hueBeeswaxLastQueryServer") != null) {
-            $("#id_server").val($.cookie("hueBeeswaxLastQueryServer"));
-        }
 	});
 </script>
 

+ 13 - 11
apps/beeswax/src/beeswax/templates/create_table_index.mako

@@ -17,20 +17,22 @@
 from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
 %>
+
 <%namespace name="layout" file="layout.mako" />
-${commonheader(_('Create Table'), "beeswax", user, "100px")}
+
+${commonheader(_('Create Table'), app_name, user, '100px')}
+
 <div class="container-fluid">
-  <div class="toolbar">
-  </div>
+  <div class="toolbar"></div>
 
-<div id="create_table" class="view">
-  <div class="bw-create_table_choice">
-    <h1>${_('How Do You Want to Create Your Table?')}</h1>
-    <ul class="creation_choices">
-       <li class="clearfix"><a href="${ url('beeswax.create_table.import_wizard')}">${_('Create From File')}</a></li>
-       <li class="clearfix"><a href="${ url('beeswax.create_table.create_table')}">${_('Create Manually')}</a></li>
-    </ul>
+  <div id="create_table" class="view">
+    <div class="bw-create_table_choice">
+      <h1>${_('How Do You Want to Create Your Table?')}</h1>
+      <ul class="creation_choices">
+         <li class="clearfix"><a href="${ url(app_name + ':import_wizard')}">${_('Create From File')}</a></li>
+         <li class="clearfix"><a href="${ url(app_name + ':create_table')}">${_('Create Manually')}</a></li>
+      </ul>
+    </div>
   </div>
 </div>
-</div>
 ${commonfooter(messages)}

+ 6 - 4
apps/beeswax/src/beeswax/templates/create_table_manually.mako

@@ -17,9 +17,11 @@
     from desktop.views import commonheader, commonfooter
     from django.utils.translation import ugettext as _
 %>
+
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="layout" file="layout.mako" />
-${commonheader(_("Create table manually"), "beeswax", user, "100px")}
+
+${commonheader(_("Create table manually"), app_name, user, '100px')}
 ${layout.menubar(section='tables')}
 
 <div class="container-fluid">
@@ -29,8 +31,8 @@ ${layout.menubar(section='tables')}
     <div class="well sidebar-nav">
         <ul class="nav nav-list">
             <li class="nav-header">${_('Actions')}</li>
-            <li><a href="${ url('beeswax.create_table.import_wizard')}">${_('Create a new table from a file')}</a></li>
-            <li><a href="${ url('beeswax.create_table.create_table')}">${_('Create a new table manually')}</a></li>
+            <li><a href="${ url(app_name + ':import_wizard')}">${_('Create a new table from a file')}</a></li>
+            <li><a href="${ url(app_name + ':create_table')}">${_('Create a new table manually')}</a></li>
         </ul>
     </div>
 </div>
@@ -257,7 +259,7 @@ ${layout.menubar(section='tables')}
               <div class="controls">
                   ${comps.field(table_form["external_location"],
                   placeholder="/user/user_name/data_dir",
-                  klass="pathChooser",
+                  klass="pathChooser input-xxlarge",
                   file_chooser=True,
                   show_errors=False
                   )}

+ 5 - 3
apps/beeswax/src/beeswax/templates/define_columns.mako

@@ -17,10 +17,12 @@
 from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
 %>
+
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="util" file="util.mako" />
-${commonheader(_('Create table from file'), "beeswax", user, "100px")}
+
+${commonheader(_('Create table from file'), app_name, user, '100px')}
 ${layout.menubar(section='tables')}
 
 <div class="container-fluid">
@@ -30,8 +32,8 @@ ${layout.menubar(section='tables')}
             <div class="well sidebar-nav">
                 <ul class="nav nav-list">
                     <li class="nav-header">${_('Actions')}</li>
-                    <li><a href="${ url('beeswax.create_table.import_wizard')}">${_('Create a new table from a file')}</a></li>
-                    <li><a href="${ url('beeswax.create_table.create_table')}">${_('Create a new table manually')}</a></li>
+                    <li><a href="${ url(app_name + ':import_wizard')}">${_('Create a new table from a file')}</a></li>
+                    <li><a href="${ url(app_name + ':create_table')}">${_('Create a new table manually')}</a></li>
                 </ul>
             </div>
         </div>

+ 1 - 1
apps/beeswax/src/beeswax/templates/describe_partitions.mako

@@ -21,7 +21,7 @@
 
 <%namespace name="layout" file="layout.mako" />
 
-${commonheader(_('Beeswax Table Partitions: %(tableName)s') % dict(tableName=table.name), "beeswax", user, "100px")}
+${commonheader(_('Beeswax Table Partitions: %(tableName)s') % dict(tableName=table.name), app_name, user, '100px')}
 ${layout.menubar(section='tables')}
 
 <div class="container-fluid">

+ 6 - 6
apps/beeswax/src/beeswax/templates/describe_table.mako

@@ -27,7 +27,7 @@ from django.utils.translation import ugettext as _
   else:
     view_or_table_noun = _("Table")
 %>
-${commonheader(_("%s Metadata: %s") % (view_or_table_noun, table.name), "beeswax", user, "100px")}
+${commonheader(_("%s Metadata: %s") % (view_or_table_noun, table.name), app_name, user, '100px')}
 ${layout.menubar(section='tables')}
 
 <%def name="column_table(cols)">
@@ -60,7 +60,7 @@ ${layout.menubar(section='tables')}
                 <ul class="nav nav-list">
                     <li class="nav-header">${_('Actions')}</li>
                     <li><a href="#importData" data-toggle="modal">${_('Import Data')}</a></li>
-                    <li><a href="${ url("beeswax.views.read_table", table=table.name) }">${_('Browse Data')}</a></li>
+                    <li><a href="${ url("beeswax:read_table", table=table.name) }">${_('Browse Data')}</a></li>
                     <li><a href="#dropTable" data-toggle="modal">${_('Drop')} ${view_or_table_noun}</a></li>
                     <li><a href="${ table.hdfs_link }" rel="${ table.path_location }">${_('View File Location')}</a></li>
                 </ul>
@@ -89,7 +89,7 @@ ${layout.menubar(section='tables')}
                 % if table.partition_keys:
                   <div class="tab-pane" id="partitionColumns">
                     ${column_table(table.partition_keys)}
-                    <a href="${ url("beeswax.views.describe_partitions", table=table.name) }">${_('Show Partitions')}</a>
+                    <a href="${ url("beeswax:describe_partitions", table=table.name) }">${_('Show Partitions')}</a>
                   </div>
                 % endif
 
@@ -131,7 +131,7 @@ ${layout.menubar(section='tables')}
 
 
 <div id="dropTable" class="modal hide fade">
-    <form id="dropTableForm" method="POST" action="${ url("beeswax.views.drop_table", table=table.name) }">
+    <form id="dropTableForm" method="POST" action="${ url("beeswax:drop_table", table=table.name) }">
     <div class="modal-header">
         <a href="#" class="close" data-dismiss="modal">&times;</a>
         <h3>${_('Drop Table')}</h3>
@@ -150,7 +150,7 @@ ${layout.menubar(section='tables')}
 
 
 <div id="importData" class="modal hide fade">
-    <form method="POST" action="${ url("beeswax.views.load_table", table=table.name) }" class="form-horizontal">
+    <form method="POST" action="${ url("beeswax:load_table", table=table.name) }" class="form-horizontal">
         <div class="modal-header">
             <a href="#" class="close" data-dismiss="modal">&times;</a>
             <h3>${_('Import data')}</h3>
@@ -246,7 +246,7 @@ ${layout.menubar(section='tables')}
        "bFilter":false
      });
 
-     $.getJSON("${ url("beeswax.views.drop_table", table=table.name) }", function (data) {
+     $.getJSON("${ url("beeswax:drop_table", table=table.name) }", function (data) {
        $("#dropTableMessage").text(data.title);
      });
 

+ 28 - 26
apps/beeswax/src/beeswax/templates/execute.mako

@@ -21,7 +21,6 @@
 
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="layout" file="layout.mako" />
-<%namespace name="util" file="util.mako" />
 
 <%def name="query()">
     <h1>${_('Query Editor')}</h1>
@@ -54,13 +53,15 @@
         <a id="saveQuery" class="btn">${_('Save')}</a>
         % endif
         <a id="saveQueryAs" class="btn">${_('Save as...')}</a>
+        % if app_name != 'impala':
         <a id="explainQuery" class="btn">${_('Explain')}</a>
-        &nbsp; ${_('or create a')} &nbsp;<a class="btn" href="${ url('beeswax.views.execute_query') }">${_('New query')}</a>
+        % endif
+        &nbsp; ${_('or create a')} &nbsp;<a class="btn" href="${ url(app_name + ':execute_query') }">${_('New query')}</a>
     </div>
 </%def>
 
 
-${commonheader(_('Query'), "beeswax", user, "100px")}
+${commonheader(_('Query'), app_name, user, '100px')}
 ${layout.menubar(section='query')}
 
 <div class="container-fluid">
@@ -69,10 +70,6 @@ ${layout.menubar(section='query')}
             <div class="well sidebar-nav">
                 <form id="advancedSettingsForm" action="${action}" method="POST" class="form form-horizontal noPadding">
                     <ul class="nav nav-list">
-                        <li class="nav-header">${_('Query server')}</li>
-                        <li>
-                          ${form.query_servers['server']}
-                        </li>
                         <li class="nav-header">${_('settings')}</li>
                         <li>
                             % for i, f in enumerate(form.settings.forms):
@@ -108,8 +105,18 @@ ${layout.menubar(section='query')}
                                 <a class="btn btn-small" data-form-prefix="settings">${_('Add')}</a>
                             </div>
                         </li>
-                        <li class="nav-header">${_('File Resources')}</li>
-                        <li>
+                        <li class="nav-header
+                        % if app_name == 'impala':
+                            hide
+                        % endif
+                        ">
+                            ${_('File Resources')}
+                        </li>
+                        <li
+                        % if app_name == 'impala':
+                            class="hide"
+                        % endif
+                        >
                             % for i, f in enumerate(form.file_resources.forms):
                             <div class="param">
                                 <div class="remove">
@@ -143,8 +150,18 @@ ${layout.menubar(section='query')}
                                 <a class="btn btn-small" data-form-prefix="file_resources">${_('Add')}</a>
                             </div>
                         </li>
-                        <li class="nav-header">${_('User-defined Functions')}</li>
-                        <li>
+                        <li class="nav-header
+                        % if app_name == 'impala':
+                            hide
+                        % endif
+                        ">
+                            ${_('User-defined Functions')}
+                        </li>
+                        <li
+                        % if app_name == 'impala':
+                            class="hide"
+                        % endif
+                        >
                             % for i, f in enumerate(form.functions.forms):
                                 <div class="param">
                                     <div class="remove">
@@ -328,17 +345,11 @@ ${layout.menubar(section='query')}
 </style>
 
 
-
-<script src="/static/ext/js/jquery/plugins/jquery.cookie.js"></script>
-
 <script type="text/javascript" charset="utf-8">
     $(document).ready(function(){
         $("*[rel=tooltip]").tooltip({
             placement: 'bottom'
         });
-        // hack!!!
-        $("select:not(#id_query_servers-server)").addClass("span8");
-        $("#id_query_servers-server").addClass("span12");
 
         $("a[data-form-prefix]").each(function(){
             var _prefix = $(this).attr("data-form-prefix");
@@ -424,18 +435,9 @@ ${layout.menubar(section='query')}
         });
 
         function checkAndSubmit(){
-            // TODO: client side validation
             $(".query").val($("#queryField").val());
             $("#advancedSettingsForm").submit();
         }
-
-        $("#id_query_servers-server").change(function(){
-            $.cookie("hueBeeswaxLastQueryServer", $(this).val(), {expires: 90});
-        });
-
-        if ($.cookie("hueBeeswaxLastQueryServer") != null) {
-            $("#id_query_servers-server").val($.cookie("hueBeeswaxLastQueryServer"));
-        }
     });
 </script>
 

+ 8 - 1
apps/beeswax/src/beeswax/templates/explain.mako

@@ -17,10 +17,12 @@
 from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
 %>
+
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="util" file="util.mako" />
-${commonheader(_('Query Explanation'), "beeswax", user, "100px")}
+
+${commonheader(_('Query Explanation'), app_name, user, '100px')}
 ${layout.menubar(section='saved queries')}
 
 <div class="container-fluid">
@@ -39,5 +41,10 @@ ${layout.menubar(section='saved queries')}
             <pre>${query.hql_query | h}</pre>
         </div>
     </div>
+
+  <br/>
+  <a class="btn" onclick="history.back()">${ _('Back') }</a>
+
 </div>
+
 ${commonfooter(messages)}

+ 12 - 11
apps/beeswax/src/beeswax/templates/index.mako

@@ -17,10 +17,11 @@
 from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
 %>
+
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="layout" file="layout.mako" />
 
-${commonheader(_('Beeswax'), "beeswax", user, "100px")}
+${commonheader(_('Beeswax'), app_name, user, '100px')}
 ${layout.menubar(section='tables')}
 
 <div class="container-fluid">
@@ -33,22 +34,22 @@ ${layout.menubar(section='tables')}
                     <li><a href="#installSamples" data-toggle="modal">${_('Install Samples')}</a></li>
                     % endif
                     <li class="nav-header">${_('Tables')}</li>
-                    <li><a href="${ url('beeswax.views.show_tables') }">${_('Show Tables')}</a></li>
-                    <li><a href="${ url('beeswax.create_table.create_table') }">${_('Create Table')}</a></li>
+                    <li><a href="${ url(app_name + ':show_tables') }">${_('Show Tables')}</a></li>
+                    <li><a href="${ url(app_name + ':create_table') }">${_('Create Table')}</a></li>
                     <li class="nav-header">${_('Queries')}</li>
-                    <li><a href="${ url('beeswax.views.list_designs') }">${_('Saved Queries')}</a></li>
-                    <li><a href="${ url('beeswax.views.execute_query') }">${_('Execute Query')}</a></li>
-                    <li><a href="${ url('beeswax.views.list_query_history') }">${_('Query History')}</a></li>
+                    <li><a href="${ url(app_name + ':list_designs') }">${_('Saved Queries')}</a></li>
+                    <li><a href="${ url(app_name + ':execute_query') }">${_('Execute Query')}</a></li>
+                    <li><a href="${ url(app_name + ':list_query_history') }">${_('Query History')}</a></li>
                     <li class="nav-header">${_('Configuration')}</li>
-                    <li><a href="${ url('beeswax.views.configuration') }">${_('Configuration')}</a></li>
-                    <li><a href="${ url('beeswax.views.configuration') }?include_hadoop=1">${_('Extended Configuration')}</a></li>
+                    <li><a href="${ url(app_name + ':configuration') }">${_('Configuration')}</a></li>
+                    <li><a href="${ url(app_name + ':configuration') }?include_hadoop=1">${_('Extended Configuration')}</a></li>
                 </ul>
             </div>
         </div>
         <div class="span9">
             <h1>${_('Welcome to Beeswax for Hive')}</h1>
             ${_("To get started with Beeswax you'll first need set up some data:")}
-            <a href="${ url('beeswax.create_table.create_table') }" class='btn'>${_('Import Data')}</a>
+            <a href="${ url(app_name + ':create_table') }" class='btn'>${_('Import Data')}</a>
             ${_("or")} <a href="#installSamples" data-toggle="modal" class='btn'>${_('Install Samples')}</a>
         </div>
     </div>
@@ -71,13 +72,13 @@ ${layout.menubar(section='tables')}
 
 <script type="text/javascript" charset="utf-8">
     $(document).ready(function(){
-        $.getJSON("${ url('beeswax.views.install_examples') }",function(data){
+        $.getJSON("${ url(app_name + ':install_examples') }",function(data){
             $("#installSamplesMessage").text(data.title);
         });
 
         $("#installSamplesBtn").click(function(){
             $.post(
-                "${ url('beeswax.views.install_examples') }",
+                "${ url(app_name + ':install_examples') }",
                 { submit:"Submit" },
                 function(result){
                     if (result.creationSucceeded){

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

@@ -31,12 +31,12 @@ def is_selected(section, matcher):
 <div class="subnav subnav-fixed">
 	<div class="container-fluid">
 		<ul class="nav nav-pills">
-			<li class="${is_selected(section, 'query')}"><a href="/beeswax/execute">${_('Query Editor')}</a></li>
-			<li class="${is_selected(section, 'my queries')}"><a href="/beeswax/my_queries">${_('My Queries')}</a></li>
-			<li class="${is_selected(section, 'saved queries')}"><a href="/beeswax/list_designs">${_('Saved Queries')}</a></li>
-			<li class="${is_selected(section, 'history')}"><a href="/beeswax/query_history">${_('History')}</a></li>
-			<li class="${is_selected(section, 'tables')}"><a href="/beeswax/tables">${_('Tables')}</a></li>
-			<li class="${is_selected(section, 'configuration')}"><a href="/beeswax/configuration">${_('Settings')}</a></li>
+			<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>
+			<li class="${is_selected(section, 'tables')}"><a href="${ url(app_name + ':show_tables') }">${_('Tables')}</a></li>
+			<li class="${is_selected(section, 'configuration')}"><a href="${ url(app_name + ':configuration') }">${_('Settings')}</a></li>
 		</ul>
 	</div>
 </div>

+ 8 - 6
apps/beeswax/src/beeswax/templates/list_designs.mako

@@ -19,9 +19,11 @@
     from desktop.views import commonheader, commonfooter
     from django.utils.translation import ugettext as _
 %>
+
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="layout" file="layout.mako" />
-${commonheader(_('Queries'), "beeswax", user, "100px")}
+
+${commonheader(_('Queries'), app_name, user, '100px')}
 ${layout.menubar(section='saved queries')}
 <div class="container-fluid">
     <h1>${_('Queries')}</h1>
@@ -47,7 +49,7 @@ ${layout.menubar(section='saved queries')}
                 <tr>
                 <td>
                     % if may_edit:
-                        <a href="${ url('beeswax.views.execute_query', design_id=design.id) }" data-row-selector="true">${design.name}</a>
+                        <a href="${ url(app_name + ':execute_query', design_id=design.id) }" data-row-selector="true">${design.name}</a>
                     % else:
                     ${design.name}
                     % endif
@@ -70,11 +72,11 @@ ${layout.menubar(section='saved queries')}
                     </a>
                     <ul class="dropdown-menu">
                     % if may_edit:
-                        <li><a href="${ url('beeswax.views.execute_query', design_id=design.id) }" title="${_('Edit this query.')}" class="contextItem">${_('Edit')}</a></li>
-                        <li><a href="javascript:void(0)" data-confirmation-url="${ url('beeswax.views.delete_design', design_id=design.id) }" title="${_('Delete this query.')}" class="contextItem confirmationModal">${_('Delete')}</a></li>
-                        <li><a href="${ url('beeswax.views.list_query_history') }?design_id=${design.id}" title="${_('View the usage history of this query.')}" class="contextItem">${_('Usage History')}</a></li>
+                        <li><a href="${ url(app_name + ':execute_query', design_id=design.id) }" title="${_('Edit this query.')}" class="contextItem">${_('Edit')}</a></li>
+                        <li><a href="javascript:void(0)" data-confirmation-url="${ url(app_name + ':delete_design', design_id=design.id) }" title="${_('Delete this query.')}" class="contextItem confirmationModal">${_('Delete')}</a></li>
+                        <li><a href="${ url(app_name + ':list_query_history') }?design_id=${design.id}" title="${_('View the usage history of this query.')}" class="contextItem">${_('Usage History')}</a></li>
                     % endif
-                    <li><a href="${ url('beeswax.views.clone_design', design_id=design.id) }" title="${_('Copy this query.')}" class="contextItem">${_('Clone')}</a></li>
+                    <li><a href="${ url(app_name + ':clone_design', design_id=design.id) }" title="${_('Copy this query.')}" class="contextItem">${_('Clone')}</a></li>
                     </ul>
                 </div>
                 </td>

+ 5 - 5
apps/beeswax/src/beeswax/templates/list_history.mako

@@ -17,19 +17,19 @@
 import time
 from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
+from beeswax.views import collapse_whitespace
 %>
 
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
-<%!  from beeswax.views import collapse_whitespace %>
 
-${commonheader(_('Query History'), "beeswax", user, "100px")}
+${commonheader(_('Query History'), app_name, user, '100px')}
 ${layout.menubar(section='history')}
 
 <%def name="show_saved_query(design, history)">
   % if design:
     % if request.user == design.owner:
-      <a href="${ url('beeswax.views.execute_query', design_id=design.id) }">
+      <a href="${ url(app_name + ':execute_query', design_id=design.id) }">
     % endif
     % if design.is_auto:
       [ ${_('Unsaved')} ]
@@ -40,7 +40,7 @@ ${layout.menubar(section='history')}
     </a>
     % else:
     ## TODO (bc/nutron): Shouldn't be able to edit someone else's design. Let user clone instead.
-    <a href="${ url('beeswax.views.clone_design', design_id=design.id) }" title="${_('Copy this query.')}">${_('Clone')}</a>
+    <a href="${ url(app_name + ':clone_design', design_id=design.id) }" title="${_('Copy this query.')}">${_('Clone')}</a>
     % endif
   % else:
     [ ${_('Auto generated action')} ]
@@ -130,7 +130,7 @@ ${layout.menubar(section='history')}
                 <td>${models.QueryHistory.STATE[query.last_state]}</td>
                 <td>
                   % if qcontext and query.last_state != models.QueryHistory.STATE.expired.index:
-                    <a href="${ url('beeswax.views.watch_query', id=query.id) }?context=${qcontext|u}" data-row-selector="true">${_('Results')}</a>
+                    <a href="${ url(app_name + ':watch_query', id=query.id) }?context=${qcontext|u}" data-row-selector="true">${_('Results')}</a>
                   % else:
                     ~
                   % endif

+ 1 - 1
apps/beeswax/src/beeswax/templates/load_table.mako

@@ -19,7 +19,7 @@ from django.utils.translation import ugettext as _
 %>
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="util" file="util.mako" />
-${commonheader(_('Load Data into %(table)s') % dict(table=table), "beeswax", user, "100px")}
+${commonheader(_('Load Data into %(table)s') % dict(table=table), app_name, user, '100px')}
 ${layout.menubar()}
 <div class="container-fluid">
   <h1>${_('Load Data into %(table)s') % dict(table=table)}</h1>

+ 10 - 10
apps/beeswax/src/beeswax/templates/my_queries.mako

@@ -22,7 +22,7 @@ from django.utils.translation import ugettext as _
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%!  from beeswax.views import collapse_whitespace %>
-${commonheader(_('My Queries'), "beeswax", user, "100px")}
+${commonheader(_('My Queries'), app_name, user, '100px')}
 ${layout.menubar(section='my queries')}
 <style>
     .tab-content {
@@ -67,7 +67,7 @@ ${layout.menubar(section='my queries')}
                       % for design in q_page.object_list:
                         <tr>
                           <td>
-                            <a href="${ url('beeswax.views.execute_query', design_id=design.id) }" data-row-selector="true">${design.name}</a>
+                            <a href="${ url(app_name + ':execute_query', design_id=design.id) }" data-row-selector="true">${design.name}</a>
                           </td>
                           <td>
                             % if design.desc:
@@ -85,10 +85,10 @@ ${layout.menubar(section='my queries')}
                                   <span class="caret"></span>
                                 </a>
                                 <ul class="dropdown-menu">
-                                  <li><a href="${ url('beeswax.views.execute_query', design_id=design.id) }" title="${_('Edit this query.')}" class="contextItem">${_('Edit')}</a></li>
-                                  <li><a href="javascript:void(0)" data-confirmation-url="${ url('beeswax.views.delete_design', design_id=design.id) }" title="${_('Delete this query.')}" class="contextItem confirmationModal">${_('Delete')}</a></li>
-                                  <li><a href="${ url('beeswax.views.list_query_history') }?design_id=${design.id}" title="${_('View the usage history of this query.')}" class="contextItem">${_('Usage History')}</a></li>
-                                  <li><a href="${ url('beeswax.views.clone_design', design_id=design.id) }" title="${_('Copy this query.')}" class="contextItem">${_('Clone')}</a></li>
+                                  <li><a href="${ url(app_name + ':execute_query', design_id=design.id) }" title="${_('Edit this query.')}" class="contextItem">${_('Edit')}</a></li>
+                                  <li><a href="javascript:void(0)" data-confirmation-url="${ url(app_name + ':delete_design', design_id=design.id) }" title="${_('Delete this query.')}" class="contextItem confirmationModal">${_('Delete')}</a></li>
+                                  <li><a href="${ url(app_name + ':list_query_history') }?design_id=${design.id}" title="${_('View the usage history of this query.')}" class="contextItem">${_('Usage History')}</a></li>
+                                  <li><a href="${ url(app_name + ':clone_design', design_id=design.id) }" title="${_('Copy this query.')}" class="contextItem">${_('Clone')}</a></li>
                                 </ul>
                             </div>
                           </td>
@@ -97,7 +97,7 @@ ${layout.menubar(section='my queries')}
                       </tbody>
                     </table>
                     % if q_page.number != q_page.num_pages():
-                      <a href="${ url('beeswax.views.list_designs') }?user=${request.user.username|u}" >${_('View all my queries')} &raquo;</a>
+                      <a href="${ url(app_name + ':list_designs') }?user=${request.user.username|u}" >${_('View all my queries')} &raquo;</a>
                     % endif
             </div>
 
@@ -127,7 +127,7 @@ ${layout.menubar(section='my queries')}
                     %>
                     <tr>
                       <td data-sort-value="${time.mktime(query.submission_date.timetuple())}">${query.submission_date.strftime("%x %X")}</td>
-                      <td><a href="${ url('beeswax.views.execute_query', design_id=design.id) }" data-row-selector="true">${design.name}</a></td>
+                      <td><a href="${ url(app_name + ':execute_query', design_id=design.id) }" data-row-selector="true">${design.name}</a></td>
                       <td>
                         <p>
                           % if len(query.query) > 100:
@@ -140,7 +140,7 @@ ${layout.menubar(section='my queries')}
                       <td>${models.QueryHistory.STATE[query.last_state]}</td>
                       <td>
                         % if qcontext and query.last_state != models.QueryHistory.STATE.expired.index:
-                          <a href="${ url('beeswax.views.watch_query', id=query.id) }?context=${qcontext|u}">${_('View')}</a>
+                          <a href="${ url(app_name + ':watch_query', id=query.id) }?context=${qcontext|u}">${_('View')}</a>
                         % else:
                           ~
                         % endif
@@ -150,7 +150,7 @@ ${layout.menubar(section='my queries')}
                   </tbody>
                 </table>
                 % if h_page.number != h_page.num_pages():
-                  <a href="${ url('beeswax.views.list_query_history') }">${_('View my entire query history')} &raquo;</a>
+                  <a href="${ url(app_name + ':list_query_history') }">${_('View my entire query history')} &raquo;</a>
                 % endif
             </div>
     </div>

+ 4 - 7
apps/beeswax/src/beeswax/templates/parameterization.mako

@@ -20,17 +20,17 @@ from django.utils.translation import ugettext as _
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="layout" file="layout.mako" />
 
-${commonheader(_('Parameterize Query'), "beeswax", user, "100px")}
+${commonheader(_('Parameterize Query'), app_name, user, '100px')}
 
 ${layout.menubar()}
 
 <div class="container-fluid">
     <%
         if explain:
-            action = url('beeswax.views.explain_parameterized_query', design.id)
+            action = url(app_name + ':explain_parameterized_query', design.id)
             btn = _("Explain query")
         else:
-            action = url('beeswax.views.execute_parameterized_query', design.id)
+            action = url(app_name + ':execute_parameterized_query', design.id)
             btn = _("Execute query")
     %>
     <form method="POST" action="${action}" class="form-horizontal">
@@ -44,12 +44,9 @@ ${layout.menubar()}
                     </div>
                 </div>
             % endfor
-            <div class="hide">
-              ${query_servers}
-            </div>
             <div class="form-actions">
-                <button type="submit" class="btn btn-primary">${btn}</button>
                 <a class="btn" href="javascript:history.go(-1);">${_('Cancel')}</a>
+                <button type="submit" class="btn btn-primary">${btn}</button>
             </div>
         </fieldset>
     </form>

+ 1 - 1
apps/beeswax/src/beeswax/templates/save_results.mako

@@ -20,7 +20,7 @@ from django.utils.translation import ugettext as _
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="util" file="util.mako" />
-${commonheader(_('Create table from file'), "beeswax", user, "100px")}
+${commonheader(_('Create table from file'), app_name, user, '100px')}
 ${layout.menubar(section='history')}
 <div class="container-fluid">
 % if error_msg:

+ 11 - 8
apps/beeswax/src/beeswax/templates/show_tables.mako

@@ -17,10 +17,13 @@
 from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
 %>
+
 <%namespace name="layout" file="layout.mako" />
-${commonheader(_('Table List'), "beeswax", user, "100px")}
+
+${commonheader(_('Table List'), app_name, user, '100px')}
 ${layout.menubar(section='tables')}
 
+
 <div class="container-fluid">
 	<h1>${_('Table List')}</h1>
 	<div class="row-fluid">
@@ -31,8 +34,8 @@ ${layout.menubar(section='tables')}
 					% if not examples_installed:
 		        	<li><a href="#installSamples" data-toggle="modal">${_('Install samples')}</a></li>
 		      		% endif
-		      		<li><a href="${ url('beeswax.create_table.import_wizard')}">${_('Create a new table from a file')}</a></li>
-					<li><a href="${ url('beeswax.create_table.create_table')}">${_('Create a new table manually')}</a></li>
+		      		<li><a href="${ url(app_name + ':import_wizard') }">${_('Create a new table from a file')}</a></li>
+					<li><a href="${ url(app_name + ':create_table') }">${_('Create a new table manually')}</a></li>
 				</ul>
 			</div>
 		</div>
@@ -48,9 +51,9 @@ ${layout.menubar(section='tables')}
 				% for table in tables:
 					<tr>
 						<td>
-							<a href="${ url("beeswax.views.describe_table", table=table) }" data-row-selector="true">${ table }</a>
+							<a href="${ url(app_name + ':describe_table', table=table) }" data-row-selector="true">${ table }</a>
 						</td>
-						<td><a href="${ url("beeswax.views.read_table", table=table) }" class="btn">${_('Browse Data')}</a></td>
+						<td><a href="${ url(app_name + ':read_table', table=table) }" class="btn">${_('Browse Data')}</a></td>
 					</tr>
 				% endfor
 				</tbody>
@@ -95,17 +98,17 @@ ${layout.menubar(section='tables')}
 		$("a[data-row-selector='true']").jHueRowSelector();
 
 		% if not examples_installed:
-		$.getJSON("${ url('beeswax.views.install_examples') }",function(data){
+		$.getJSON("${ url(app_name + ':install_examples') }",function(data){
 			$("#installSamplesMessage").text(data.title);
 		});
 
         $("#installSamplesBtn").click(function(){
             $.post(
-                "${ url('beeswax.views.install_examples') }",
+                "${ url(app_name + ':install_examples') }",
                 { submit:"Submit" },
                 function(result){
                     if (result.creationSucceeded){
-                        window.location.href = "/beeswax/tables";
+                        window.location.href = "${ url(app_name + ':show_tables') }";
                     }
                     else {
                         var message = "${_('There was an error processing your request:')} " + result.message;

+ 3 - 3
apps/beeswax/src/beeswax/templates/util.mako

@@ -59,13 +59,13 @@ from django.utils.translation import ugettext as _
   % if query_context:
     % if query_context[0] == 'table':
       <% tablename = query_context[1] %>
-      <a href="${ url('beeswax.views.describe_table', tablename) }">${tablename}</a>
+      <a href="${ url(app_name + ':describe_table', tablename) }">${tablename}</a>
     % elif query_context[0] == 'design':
       <% design = query_context[1] %>
       % if design.is_auto:
-		<a href="${ url('beeswax.views.execute_query', design.id)}">${_('Unsaved Query')}</a>
+		<a href="${ url(app_name + ':execute_query', design.id)}">${_('Unsaved Query')}</a>
       % else:
-        <a href="${ url('beeswax.views.execute_query', design.id)}">${design.name}</a>
+        <a href="${ url(app_name + ':execute_query', design.id)}">${design.name}</a>
       % endif
     % else:
       ${_('Query Results')}

+ 4 - 4
apps/beeswax/src/beeswax/templates/watch_results.mako

@@ -22,7 +22,7 @@ from django.utils.translation import ugettext as _
 <%namespace name="util" file="util.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 
-${commonheader(_('Query Results'), "beeswax", user, "100px")}
+${commonheader(_('Query Results'), app_name, user, '100px')}
 ${layout.menubar(section='query')}
 
 <style>
@@ -145,10 +145,10 @@ ${layout.menubar(section='query')}
             <div class="pagination pull-right">
               <ul>
               % if start_row != 0:
-                  <li class="prev"><a title="${_('Beginning of List')}" href="${ url('beeswax.views.view_results', query.id, 0) }${'?context=' + context_param or '' | n}">&larr; ${_('Beginning of List')}</a></li>
+                  <li class="prev"><a title="${_('Beginning of List')}" href="${ url(app_name + ':view_results', query.id, 0) }${'?context=' + context_param or '' | n}">&larr; ${_('Beginning of List')}</a></li>
               % endif
               % if has_more and len(results) == 100:
-                  <li><a title="${_('Next page')}" href="${ url('beeswax.views.view_results', query.id, next_row) }${'?context=' + context_param or '' | n}">${_('Next Page')} &rarr;</a></li>
+                  <li><a title="${_('Next page')}" href="${ url(app_name + ':view_results', query.id, next_row) }${'?context=' + context_param or '' | n}">${_('Next Page')} &rarr;</a></li>
               % endif
               </ul>
             </div>
@@ -185,7 +185,7 @@ ${layout.menubar(section='query')}
 
 %if can_save:
 <div id="saveAs" class="modal hide fade">
-  <form id="saveForm" action="${url('beeswax.views.save_results', query.id) }" method="POST"
+  <form id="saveForm" action="${url(app_name + ':save_results', query.id) }" method="POST"
         class="form form-inline form-padding-fix">
     <div class="modal-header">
       <a href="#" class="close" data-dismiss="modal">&times;</a>

+ 4 - 4
apps/beeswax/src/beeswax/templates/watch_wait.mako

@@ -21,11 +21,11 @@ from django.utils.translation import ugettext as _
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="util" file="util.mako" />
 
-${commonheader(_('Waiting for query...'), "beeswax", user, "100px")}
+${commonheader(_('Waiting for query...'), app_name, user, '100px')}
 ${layout.menubar(section='query')}
 
 ## Required for unit tests
-<!-- <meta http-equiv="refresh" content="3;${url('beeswax.views.watch_query', query.id)}?${fwd_params}" /> -->
+<!-- <meta http-equiv="refresh" content="3;${url(app_name + ':watch_query', query.id)}?${fwd_params}" /> -->
 
 <div class="container-fluid">
 	<h1>${_('Waiting for query...')} ${util.render_query_context(query_context)}</h1>
@@ -71,7 +71,7 @@ ${layout.menubar(section='query')}
 <script>
 
   $(document).ready(function(){
-    var fwdUrl = "${url('beeswax.views.watch_query', query.id)}?${fwd_params}";
+    var fwdUrl = "${url(app_name + ':watch_query', query.id)}?${fwd_params}";
     var labels = {
       MRJOB: "${_('MR Job')}",
       MRJOBS: "${_('MR Jobs')}"
@@ -82,7 +82,7 @@ ${layout.menubar(section='query')}
     var logsAtEnd = true;
 
     function refreshView() {
-      $.getJSON("${url('beeswax.views.watch_query_refresh_json', query.id)}", function (data) {
+      $.getJSON("${url(app_name + ':watch_query_refresh_json', query.id)}", function (data) {
         if (data.isSuccess || data.isFailure) {
           location.href = fwdUrl;
         }

+ 7 - 8
apps/beeswax/src/beeswax/test_base.py

@@ -16,6 +16,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+from django.core.urlresolvers import reverse
 
 """
 Common infrastructure for beeswax tests
@@ -39,7 +40,7 @@ import beeswax.conf
 
 from beeswax.conf import SERVER_INTERFACE
 from beeswax.models import HIVE_SERVER2
-from beeswax.server.dbms import get_query_server
+from beeswax.server.dbms import get_query_server_config
 from beeswax.server import dbms
 
 
@@ -94,9 +95,8 @@ def get_shared_beeswax_server():
     default_xml = file(beeswax.conf.BEESWAX_HIVE_CONF_DIR.get() + "/hive-default.xml.template").read()
 
     finish = (
-      beeswax.conf.QUERY_SERVERS['default'].SERVER_HOST.set_for_testing("localhost"),
-      beeswax.conf.QUERY_SERVERS['default'].SERVER_PORT.set_for_testing(BEESWAXD_TEST_PORT),
-      beeswax.conf.QUERY_SERVERS['default'].SUPPORT_DDL.set_for_testing(True),
+      beeswax.conf.BEESWAX_SERVER_HOST.set_for_testing("localhost"),
+      beeswax.conf.BEESWAX_SERVER_PORT.set_for_testing(BEESWAXD_TEST_PORT),
       beeswax.conf.BEESWAX_META_SERVER_HOST.set_for_testing("localhost"),
       beeswax.conf.BEESWAX_META_SERVER_PORT.set_for_testing(BEESWAXD_TEST_PORT + 1),
       # Use a bogus path to avoid loading the normal hive-site.xml
@@ -132,7 +132,7 @@ def get_shared_beeswax_server():
       sleep = 0.001
       make_logged_in_client()
       user = User.objects.get(username='test')
-      query_server = get_query_server(support_ddl=True)
+      query_server = get_query_server_config(requires_ddl=True)
       db = dbms.get(user, query_server)
 
       while not started and time.time() - start < 20.0:
@@ -201,7 +201,7 @@ def wait_for_query_to_finish(client, response, max=30.0):
 def make_query(client, query, submission_type="Execute",
                udfs=None, settings=None, resources=None,
                wait=False, name=None, desc=None, local=True,
-               is_parameterized=True, max=30.0, server='default', **kwargs):
+               is_parameterized=True, max=30.0, **kwargs):
   """
   Prepares arguments for the execute view.
 
@@ -217,7 +217,6 @@ def make_query(client, query, submission_type="Execute",
   # Prepares arguments for the execute view.
   parameters = {
     'query-query': query,
-    'query_servers-server': server,
     'query-is_parameterized': is_parameterized and "on"
   }
 
@@ -253,7 +252,7 @@ def make_query(client, query, submission_type="Execute",
     parameters["file_resources-%d-_exists" % i] = 'True'
 
   kwargs.setdefault('follow', True)
-  response = client.post("/beeswax/execute", parameters, **kwargs)
+  response = client.post("/beeswax/execute/", parameters, **kwargs)
 
   if wait:
     return wait_for_query_to_finish(client, response, max)

+ 9 - 4
apps/beeswax/src/beeswax/tests.py

@@ -45,7 +45,7 @@ import beeswax.views
 
 from beeswax import conf
 from beeswax.views import collapse_whitespace
-from beeswax.test_base import make_query, wait_for_query_to_finish, verify_history, get_query_server,\
+from beeswax.test_base import make_query, wait_for_query_to_finish, verify_history, get_query_server_config,\
   BEESWAXD_TEST_PORT
 from beeswax.design import hql_query, _strip_trailing_semicolon
 from beeswax.data_export import download
@@ -66,6 +66,7 @@ def _make_query(client, query, submission_type="Execute",
   res = make_query(client, query, submission_type,
                    udfs, settings, resources,
                    wait, name, desc, local, is_parameterized, max, **kwargs)
+
   # Should be in the history if it's submitted.
   if submission_type == 'Execute':
     fragment = collapse_whitespace(smart_str(query[:20]))
@@ -87,7 +88,7 @@ class TestBeeswaxWithHadoop(BeeswaxSampleProvider):
 
   def setUp(self):
     user = User.objects.get(username='test')
-    self.db = dbms.get(user, get_query_server())
+    self.db = dbms.get(user, get_query_server_config())
 
   def _verify_query_state(self, state):
     """
@@ -1074,10 +1075,10 @@ for x in sys.stdin:
 
   def test_select_query_server(self):
     c = make_logged_in_client()
-    _make_query(c, 'SELECT bogus FROM test', server='default') # Improvement: mock another server
+    _make_query(c, 'SELECT bogus FROM test', server='beeswax') # Improvement: mock another server
 
     history = beeswax.models.QueryHistory.objects.latest('id')
-    assert_equal('default', history.server_name)
+    assert_equal('beeswax', history.server_name)
     assert_equal('localhost', history.server_host)
     assert_equal(BEESWAXD_TEST_PORT, history.server_port)
 
@@ -1126,6 +1127,7 @@ def test_history_page():
   def do_view(param):
     resp = client.get('/beeswax/query_history?' + param)
     assert_true(len(resp.context['page'].object_list) >= 0)     # Make the query run
+    return resp
 
   do_view('user=test')
   do_view('user=:all')
@@ -1144,6 +1146,9 @@ def test_history_page():
   do_view('sort=name&user=bogus&design_id=1&auto_query=1')
   do_view('sort=-type&user=:all&type=hql&auto_query=0')
 
+  # Only show Beeswax queries
+  response = do_view('')
+  assert_equal('beeswax', response.context['filter_params']['type'])
 
 def test_strip_trailing_semicolon():
   # Note that there are two queries (both an execute and an explain) scattered

+ 30 - 31
apps/beeswax/src/beeswax/urls.py

@@ -18,41 +18,40 @@
 from django.conf.urls.defaults import patterns, url
 
 urlpatterns = patterns('beeswax.views',
-  url(r'^$', 'index'),
-
-  url(r'^tables$', 'show_tables'),
-  url(r'^table/(?P<table>\w+)$', 'describe_table'),
-  url(r'^table/(?P<table>\w+)/partitions$', 'describe_partitions'),
-  url(r'^table/(?P<table>\w+)/load$', 'load_table'),
-  url(r'^table/(?P<table>\w+)/read$', 'read_table'),
-  url(r'^table/(?P<table>\w+)/drop$', 'drop_table'),
-
-  url(r'^execute$', 'execute_query'),
-  url(r'^execute/(?P<design_id>\d+)$', 'execute_query'),
-  url(r'^explain_parameterized/(?P<design_id>\d+)$', 'explain_parameterized_query'),
-  url(r'^execute_parameterized/(?P<design_id>\d+)$', 'execute_parameterized_query'),
-  url(r'^watch/(?P<id>\d+)$', 'watch_query'),
-  url(r'^watch/json/(?P<id>\d+)$', 'watch_query_refresh_json'),
-  url(r'^results/(?P<id>\d+)/(?P<first_row>\d+)$', 'view_results'),
-  url(r'^download/(?P<id>\d+)/(?P<format>\w+)$', 'download'),
-  url(r'^save_results/(?P<id>\d+)$', 'save_results'),
-
-  url(r'^my_queries$', 'my_queries'),
-  url(r'^list_designs$', 'list_designs'),
-  url(r'^delete_design/(?P<design_id>\d+)$', 'delete_design'),
-  url(r'^clone_design/(?P<design_id>\d+)$', 'clone_design'),
-  url(r'^query_history$', 'list_query_history'),
-
-  url(r'^configuration$', 'configuration'),
-  url(r'^install_examples$', 'install_examples'),
-  url(r'^query_cb/done/(?P<server_id>\S+)$', 'query_done_cb'),
+  url(r'^$', 'index', name='index'),
+
+  url(r'^tables$', 'show_tables', name='show_tables'),
+  url(r'^table/(?P<table>\w+)$', 'describe_table', name='describe_table'),
+  url(r'^table/(?P<table>\w+)/partitions$', 'describe_partitions', name='describe_partitions'),
+  url(r'^table/(?P<table>\w+)/load$', 'load_table', name='load_table'),
+  url(r'^table/(?P<table>\w+)/read$', 'read_table', name='read_table'),
+  url(r'^table/(?P<table>\w+)/drop$', 'drop_table', name='drop_table'),
+
+  url(r'^execute/(?P<design_id>\d+)?$', 'execute_query', name='execute_query'),
+  url(r'^explain_parameterized/(?P<design_id>\d+)$', 'explain_parameterized_query', name='explain_parameterized_query'),
+  url(r'^execute_parameterized/(?P<design_id>\d+)$', 'execute_parameterized_query', name='execute_parameterized_query'),
+  url(r'^watch/(?P<id>\d+)$', 'watch_query', name='watch_query'),
+  url(r'^watch/json/(?P<id>\d+)$', 'watch_query_refresh_json', name='watch_query_refresh_json'),
+  url(r'^results/(?P<id>\d+)/(?P<first_row>\d+)$', 'view_results', name='view_results'),
+  url(r'^download/(?P<id>\d+)/(?P<format>\w+)$', 'download', name='download'),
+  url(r'^save_results/(?P<id>\d+)$', 'save_results', name='save_results'),
+
+  url(r'^my_queries$', 'my_queries', name='my_queries'),
+  url(r'^list_designs$', 'list_designs', name='list_designs'),
+  url(r'^delete_design/(?P<design_id>\d+)$', 'delete_design', name='delete_design'),
+  url(r'^clone_design/(?P<design_id>\d+)$', 'clone_design', name='clone_design'),
+  url(r'^query_history$', 'list_query_history', name='list_query_history'),
+
+  url(r'^configuration$', 'configuration', name='configuration'),
+  url(r'^install_examples$', 'install_examples', name='install_examples'),
+  url(r'^query_cb/done/(?P<server_id>\S+)$', 'query_done_cb', name='query_done_cb'),
 )
 
 urlpatterns += patterns(
   'beeswax.create_table',
 
   url(r'^create$', 'index'),
-  url(r'^create/create_table$', 'create_table'),
-  url(r'^create/import_wizard$', 'import_wizard'),
-  url(r'^create/auto_load$', 'load_after_create'),
+  url(r'^create/create_table$', 'create_table', name='create_table'),
+  url(r'^create/import_wizard$', 'import_wizard', name='import_wizard'),
+  url(r'^create/auto_load$', 'load_after_create', name='load_after_create'),
 )

+ 60 - 60
apps/beeswax/src/beeswax/views.py

@@ -24,13 +24,13 @@ import re
 
 from django import forms
 from django.contrib import messages
-from django.core import urlresolvers
 from django.db.models import Q
 from django.http import HttpResponse, QueryDict
 from django.shortcuts import redirect
 from django.utils.translation import ugettext as _
 from django.core.urlresolvers import reverse
 
+from desktop.context_processors import get_app_name
 from desktop.lib.paginator import Paginator
 from desktop.lib.django_util import copy_query_dict, format_preserving_redirect, render
 from desktop.lib.django_util import login_notrequired, get_desktop_uri_prefix
@@ -45,10 +45,11 @@ import beeswax.design
 import beeswax.management.commands.beeswax_install_examples
 
 from beeswax import common, data_export, models, conf
-from beeswax.forms import QueryServerForm, LoadDataForm, QueryForm
+from beeswax.forms import LoadDataForm, QueryForm
 from beeswax.design import HQLdesign, hql_query
+from beeswax.models import SavedQuery
 from beeswax.server import dbms
-from beeswax.server.dbms import expand_exception, get_query_server
+from beeswax.server.dbms import expand_exception, get_query_server_config
 
 
 LOG = logging.getLogger(__name__)
@@ -90,6 +91,8 @@ def save_design(request, form, type, design, explicit_save):
 
   if type == models.SavedQuery.HQL:
     design_cls = beeswax.design.HQLdesign
+  elif type == models.SavedQuery.IMPALA:
+    design_cls = beeswax.design.HQLdesign
   else:
     raise ValueError(_('Invalid design type %(type)s') % {'type': type})
 
@@ -114,8 +117,11 @@ def save_design(request, form, type, design, explicit_save):
       design.name = models.SavedQuery.DEFAULT_NEW_DESIGN_NAME
     design.is_auto = True
 
+  design.type = type
   design.data = new_data
+
   design.save()
+
   LOG.info('Saved %sdesign "%s" (id %s) for %s' %
            (explicit_save and '' or 'auto ', design.name, design.id, design.owner))
   if explicit_save:
@@ -135,9 +141,9 @@ def delete_design(request, design_id):
 
   if request.method == 'POST':
     design.delete()
-    return redirect(urlresolvers.reverse(list_designs))
+    return redirect(reverse(get_app_name(request) + ':list_designs'))
   else:
-    return render('confirm.html', request, dict(url=request.path, title='Delete design?'))
+    return render('confirm.html', request, dict(url=request.path, title=_('Delete design?')))
 
 
 def clone_design(request, design_id):
@@ -154,7 +160,7 @@ def clone_design(request, design_id):
   copy.save()
   messages.info(request, _('Copied design: %(name)s') % {'name': design.name})
   return format_preserving_redirect(
-      request, urlresolvers.reverse(execute_query, kwargs={'design_id': copy.id}))
+      request, reverse(get_app_name(request) + ':execute_query', kwargs={'design_id': copy.id}))
 
 
 def list_designs(request):
@@ -198,12 +204,15 @@ def my_queries(request):
   is the ``user`` filter, since this view only shows what belongs to the user.
   """
   DEFAULT_PAGE_SIZE = 40
+  app_name= get_app_name(request)
 
   # Extract the history list.
   prefix = 'h-'
   querydict_history = _copy_prefix(prefix, request.GET)
   # Manually limit up the user filter.
   querydict_history[ prefix + 'user' ] = request.user.username
+  querydict_history[ prefix + 'type' ] = app_name
+
   hist_page, hist_filter = _list_query_history(request.user,
                                                querydict_history,
                                                DEFAULT_PAGE_SIZE,
@@ -213,6 +222,8 @@ def my_queries(request):
   querydict_query = _copy_prefix(prefix, request.GET)
   # Manually limit up the user filter.
   querydict_query[ prefix + 'user' ] = request.user.username
+  querydict_query[ prefix + 'type' ] = app_name
+
   query_page, query_filter = _list_designs(querydict_query, DEFAULT_PAGE_SIZE, prefix)
 
   filter_params = hist_filter
@@ -249,6 +260,9 @@ def list_query_history(request):
   if not share_queries:
     querydict_query['user'] = request.user.username
 
+  app_name= get_app_name(request)
+  querydict_query['type'] = app_name
+
   page, filter_params = _list_query_history(request.user, querydict_query, DEFAULT_PAGE_SIZE)
 
   return render('list_history.mako', request, {
@@ -300,7 +314,7 @@ def drop_table(request, table):
   if request.method == 'POST':
     try:
       query_history = db.drop_table(table)
-      url = reverse(watch_query, args=[query_history.id]) + '?on_success_url=' + urlresolvers.reverse(show_tables)
+      url = reverse(get_app_name(request) + ':watch_query', args=[query_history.id]) + '?on_success_url=' + reverse(get_app_name(request) + ':show_tables')
       return redirect(url)
     except BeeswaxException, ex:
       error_message, log = expand_exception(ex, db)
@@ -351,7 +365,7 @@ def load_table(request, table):
         hql += ", ".join(vals)
         hql += ")"
 
-      on_success_url = urlresolvers.reverse(describe_table, kwargs={'table': table})
+      on_success_url = reverse(get_app_name(request) + ':describe_table', kwargs={'table': table})
       return confirm_query(request, hql, on_success_url)
   else:
     form = beeswax.forms.LoadDataForm(table_obj)
@@ -375,7 +389,7 @@ def download(request, id, format):
   assert format in common.DL_FORMATS
 
   query_history = authorized_get_history(request, id, must_exist=True)
-  db = dbms.get(request.user, query_history.get_query_server())
+  db = dbms.get(request.user, query_history.get_query_server_config())
   LOG.debug('Download results for query %s: [ %s ]' % (query_history.server_id, query_history.query))
 
   return data_export.download(query_history.get_handle(), format, db)
@@ -401,7 +415,9 @@ def execute_query(request, design_id=None):
   form = QueryForm()
   action = request.path
   log = None
-  design = safe_get_design(request, models.SavedQuery.HQL, design_id)
+  app_name = get_app_name(request)
+  query_type = SavedQuery.TYPES_MAPPING[app_name]
+  design = safe_get_design(request, query_type, design_id)
   on_success_url = request.REQUEST.get('on_success_url')
 
   if request.method == 'POST':
@@ -421,12 +437,12 @@ def execute_query(request, design_id=None):
 
       if to_submit or to_save or to_saveas or to_explain:
         explicit_save = to_save or to_saveas
-        design = save_design(request, form, models.SavedQuery.HQL, design, explicit_save)
-        action = urlresolvers.reverse(execute_query, kwargs=dict(design_id=design.id))
+        design = save_design(request, form, query_type, design, explicit_save)
+        action = reverse(app_name + ':execute_query', kwargs=dict(design_id=design.id))
 
       if to_explain or to_submit:
         query_str = form.query.cleaned_data["query"]
-        query_server = get_query_server(form.query_servers.cleaned_data["server"])
+        query_server = get_query_server_config(app_name, requires_ddl=False)
 
         # (Optional) Parameterization.
         parameterization = get_parameterization(request, query_str, form, design, to_explain)
@@ -496,7 +512,7 @@ def watch_query(request, id):
   context_param = request.GET.get('context', '')
 
   # GET param: on_success_url. Default to view_results
-  results_url = urlresolvers.reverse(view_results, kwargs={'id': id, 'first_row': 0})
+  results_url = reverse(get_app_name(request) + ':view_results', kwargs={'id': id, 'first_row': 0})
   on_success_url = request.GET.get('on_success_url')
   if not on_success_url:
     on_success_url = results_url
@@ -517,7 +533,7 @@ def watch_query(request, id):
     return format_preserving_redirect(request, results_url, request.GET)
 
   # Still running
-  log = dbms.get(request.user, query_history.get_query_server()).get_log(handle)
+  log = dbms.get(request.user, query_history.get_query_server_config()).get_log(handle)
 
   # Keep waiting
   # - Translate context into something more meaningful (type, data)
@@ -536,13 +552,12 @@ def watch_query_refresh_json(request, id):
   handle, state = _get_query_handle_and_state(query_history)
   query_history.save_state(state)
 
-  log = dbms.get(request.user, query_history.get_query_server()).get_log(handle)
+  log = dbms.get(request.user, query_history.get_query_server_config()).get_log(handle)
 
   jobs = _parse_out_hadoop_jobs(log)
   job_urls = {}
   for job in jobs:
-    job_urls[job] = urlresolvers.reverse('jobbrowser.views.single_job', kwargs=dict(jobid=job))
-
+    job_urls[job] = reverse('jobbrowser.views.single_job', kwargs=dict(jobid=job))
 
   result = {
     'log': log,
@@ -551,6 +566,7 @@ def watch_query_refresh_json(request, id):
     'isSuccess': query_history.is_success(),
     'isFailure': query_history.is_failure()
   }
+
   return HttpResponse(json.dumps(result), mimetype="application/json")
 
 def view_results(request, id, first_row=0):
@@ -575,7 +591,7 @@ def view_results(request, id, first_row=0):
   log = ''
 
   query_history = authorized_get_history(request, id, must_exist=True)
-  db = dbms.get(request.user, query_history.get_query_server())
+  db = dbms.get(request.user, query_history.get_query_server_config())
 
   handle, state = _get_query_handle_and_state(query_history)
   context_param = request.GET.get('context', '')
@@ -615,7 +631,7 @@ def view_results(request, id, first_row=0):
     download_urls = {}
     if downloadable:
       for format in common.DL_FORMATS:
-        download_urls[format] = urlresolvers.reverse(download, kwargs=dict(id=str(id), format=format))
+        download_urls[format] = reverse(get_app_name(request) + ':download', kwargs=dict(id=str(id), format=format))
 
     save_form = beeswax.forms.SaveResultsForm()
     results.start_row = first_row
@@ -657,7 +673,7 @@ def save_results(request, id):
         msg = _('The result of this query is not available yet.')
       raise PopupException(msg)
 
-    db = dbms.get(request.user, query_history.get_query_server())
+    db = dbms.get(request.user, query_history.get_query_server_config())
     form = beeswax.forms.SaveResultsForm(request.POST, db=db)
 
     # Cancel goes back to results
@@ -692,7 +708,7 @@ def save_results(request, id):
           request.fs.rename_star(result_meta.table_dir, target_dir)
           LOG.debug("Moved results from %s to %s" % (result_meta.table_dir, target_dir))
           query_history.save_state(models.QueryHistory.STATE.expired)
-          return redirect(urlresolvers.reverse('filebrowser.views.view', kwargs={'path': target_dir}))
+          return redirect(reverse('filebrowser.views.view', kwargs={'path': target_dir}))
         elif form.cleaned_data['save_target'] == form.SAVE_TYPE_TBL:
           # To new table
           try:
@@ -711,7 +727,7 @@ def save_results(request, id):
   if error_msg:
     error_msg = _('Failed to save results from query: %(error)s') % {'error': error_msg}
   return render('save_results.mako', request, dict(
-    action=urlresolvers.reverse(save_results, kwargs={'id': str(id)}),
+    action=reverse(get_app_name(request) + ':save_results', kwargs={'id': str(id)}),
     form=form,
     error_msg=error_msg,
     log=log,
@@ -723,7 +739,7 @@ def _save_results_ctas(request, query_history, target_table, result_meta):
   Handle saving results as a new table. Returns HTTP response.
   May raise BeeswaxException, IOError.
   """
-  query_server = query_history.get_query_server() # Query server requires DDL support
+  query_server = query_history.get_query_server_config() # Query server requires DDL support
   db = dbms.get(request.user)
 
   # Case 1: The results are straight from an existing table
@@ -731,7 +747,7 @@ def _save_results_ctas(request, query_history, target_table, result_meta):
     hql = 'CREATE TABLE `%s` AS SELECT * FROM %s' % (target_table, result_meta.in_tablename)
     query = hql_query(hql)
     # Display the CTAS running. Could take a long time.
-    return execute_directly(request, query, query_server, on_success_url=urlresolvers.reverse(show_tables))
+    return execute_directly(request, query, query_server, on_success_url=reverse(get_app_name(request) + ':show_tables'))
 
   # Case 2: The results are in some temporary location
   # 1. Create table
@@ -779,7 +795,7 @@ def _save_results_ctas(request, query_history, target_table, result_meta):
     raise ex
 
   # Show tables upon success
-  return format_preserving_redirect(request, urlresolvers.reverse(show_tables))
+  return format_preserving_redirect(request, reverse(get_app_name(request) + ':show_tables'))
 
 
 def confirm_query(request, query, on_success_url=None):
@@ -795,7 +811,7 @@ def confirm_query(request, query, on_success_url=None):
   mform.query.initial = dict(query=query)
   return render('execute.mako', request, {
     'form': mform,
-    'action': urlresolvers.reverse(execute_query),
+    'action': reverse(get_app_name(request) + ':execute_query'),
     'error_message': None,
     'design': None,
     'on_success_url': on_success_url,
@@ -810,18 +826,12 @@ def explain_directly(request, query, design, query_server):
 
 
 def configuration(request):
-  if request.method == 'POST':
-    server_form = QueryServerForm(request.POST)
-    if server_form.is_valid():
-      query_server = get_query_server(server_form.cleaned_data["server"])
-      config_values = dbms.get(request.user, query_server).get_default_configuration(
-                          bool(request.REQUEST.get("include_hadoop", False)))
-  else:
-    server_form = QueryServerForm()
-    config_values = {}
+  app_name = get_app_name(request)
+  query_server = get_query_server_config(app_name)
+  config_values = dbms.get(request.user, query_server).get_default_configuration(
+                      bool(request.REQUEST.get("include_hadoop", False)))
 
-  return render("configuration.mako", request, {'config_values': config_values,
-                                                'server_form': server_form})
+  return render("configuration.mako", request, {'config_values': config_values})
 
 
 """
@@ -881,7 +891,7 @@ def query_done_cb(request, server_id):
 
   link = "%s/#launch=Beeswax:%s" % \
             (get_desktop_uri_prefix(),
-             urlresolvers.reverse(watch_query, kwargs={'id': history.id}))
+             reverse(get_app_name(request) + ':watch_query', kwargs={'id': history.id}))
   body = _("%(subject)s. You may see the results here: %(link)s\n\nQuery:\n%(query)s") % {'subject': subject, 'link': link, 'query': history.query}
   try:
     user.email_user(subject, body)
@@ -960,8 +970,7 @@ def get_parameterization(request, query_str, form, design, is_explain):
       return render("parameterization.mako", request, dict(
         form=parameters_form(prefix="parameterization"),
         design=design,
-        explain=is_explain,
-        query_servers=form.query_servers))
+        explain=is_explain))
   return None
 
 def make_parameterization_form(query_str):
@@ -998,7 +1007,7 @@ def _run_parameterized_query(request, design_id, explain):
   assert query_form.is_valid()
 
   query_str = query_form.query.cleaned_data["query"]
-  query_server = get_query_server(query_form.query_servers.cleaned_data["server"])
+  query_server = get_query_server_config(get_app_name(request))
 
   parameterization_form_cls = make_parameterization_form(query_str)
   if not parameterization_form_cls:
@@ -1019,15 +1028,14 @@ def _run_parameterized_query(request, design_id, explain):
       db = dbms.get(request.user, query_server)
       error_message, log = expand_exception(ex, db)
       return render('execute.mako', request, {
-        'action': urlresolvers.reverse(execute_query),
+        'action': reverse(get_app_name(request) + ':execute_query'),
         'design': design,
         'error_message': error_message,
         'form': query_form,
         'log': log,
       })
   else:
-    return render("parameterization.mako", request, dict(form=parameterization_form, design=design, explain=explain,
-                                                         query_servers=query_form.query_servers))
+    return render("parameterization.mako", request, dict(form=parameterization_form, design=design, explain=explain))
 
 
 def execute_directly(request, query, query_server=None, design=None, tablename=None,
@@ -1067,7 +1075,7 @@ def execute_directly(request, query, query_server=None, design=None, tablename=N
     authorized_get_design(request, design.id)
 
   history_obj = dbms.get(request.user, query_server).execute_query(query, design)
-  watch_url = urlresolvers.reverse(watch_query, kwargs={'id': history_obj.id})
+  watch_url = reverse(get_app_name(request) + ':watch_query', kwargs={'id': history_obj.id})
 
   # Prepare the GET params for the watch_url
   get_dict = QueryDict(None, mutable=True)
@@ -1101,7 +1109,6 @@ def _list_designs(querydict, page_size, prefix="", user=None):
   """
   DEFAULT_SORT = ('-', 'date')                  # Descending date
 
-  VALID_TYPES = ('hql')               # Design types
   SORT_ATTR_TRANSLATION = dict(
     date='mtime',
     name='name',
@@ -1120,14 +1127,10 @@ def _list_designs(querydict, page_size, prefix="", user=None):
   d_type = querydict.get(prefix + 'type')
   if d_type:
     d_type = str(d_type)
-    if d_type not in VALID_TYPES:
+    if d_type not in SavedQuery.TYPES_MAPPING.keys():
       LOG.warn('Bad parameter to list_designs: type=%s' % (d_type,))
     else:
-      if d_type == 'hql':
-        d_type = models.SavedQuery.HQL
-      else:
-        d_type = models.SavedQuery.REPORT
-      db_queryset = db_queryset.filter(type=d_type)
+      db_queryset = db_queryset.filter(type=SavedQuery.TYPES_MAPPING[d_type])
 
   # Text search
   frag = querydict.get(prefix + 'text')
@@ -1183,7 +1186,7 @@ def _get_query_handle_and_state(query_history):
   if handle is None:
     raise PopupException(_("Failed to retrieve query state from the Beeswax Server."))
 
-  state = dbms.get(query_history.owner, query_history.get_query_server()).get_state(handle)
+  state = dbms.get(query_history.owner, query_history.get_query_server_config()).get_state(handle)
 
   if state is None:
     raise PopupException(_("Failed to contact Beeswax Server to check query status."))
@@ -1246,7 +1249,6 @@ def _list_query_history(user, querydict, page_size, prefix=""):
   """
   DEFAULT_SORT = ('-', 'date')                  # Descending date
 
-  VALID_TYPES = ('hql')               # Design types
   SORT_ATTR_TRANSLATION = dict(
     date='submission_date',
     state='last_state',
@@ -1276,12 +1278,10 @@ def _list_query_history(user, querydict, page_size, prefix=""):
   # Design type
   d_type = querydict.get(prefix + 'type')
   if d_type:
-    if d_type not in VALID_TYPES:
+    if d_type not in SavedQuery.TYPES_MAPPING.keys():
       LOG.warn('Bad parameter to list_query_history: type=%s' % (d_type,))
     else:
-      if d_type == 'hql':
-        d_type = models.SavedQuery.HQL
-      db_queryset = db_queryset.filter(design__type=d_type)
+      db_queryset = db_queryset.filter(design__type=SavedQuery.TYPES_MAPPING[d_type])
 
   # Ordering
   sort_key = querydict.get(prefix + 'sort')
@@ -1330,7 +1330,7 @@ def _update_query_state(query_history):
   when the user attempts to view results that have expired.
   """
   if query_history.last_state <= models.QueryHistory.STATE.running.index:
-    state_enum = dbms.get(query_history.owner, query_history.get_query_server()).get_state(query_history.get_handle())
+    state_enum = dbms.get(query_history.owner, query_history.get_query_server_config()).get_state(query_history.get_handle())
     if state_enum is None:
       # Error was logged at the source
       return False

+ 24 - 0
apps/impala/Makefile

@@ -0,0 +1,24 @@
+#
+# 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.
+#
+
+ifeq ($(ROOT),)
+  $(error "Error: Expect the environment variable $$ROOT to point to the Desktop installation")
+endif
+
+APP_NAME = impala
+include $(ROOT)/Makefile.sdk

+ 2 - 0
apps/impala/babel.cfg

@@ -0,0 +1,2 @@
+[python: src/impala/**.py]
+[mako: src/impala/templates/**.mako]

+ 20 - 0
apps/impala/hueversion.py

@@ -0,0 +1,20 @@
+# 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.
+#
+# This file should be the one source of truth for for versions within HUE.
+# It is at least included by each of the default hue app's setup.py.
+
+VERSION="2.1.0"

+ 29 - 0
apps/impala/setup.py

@@ -0,0 +1,29 @@
+# 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 setuptools import setup, find_packages
+from hueversion import VERSION
+
+setup(
+      name = "Impala",
+      version = VERSION,
+      author = "Hue",
+      url = 'http://github.com/cloudera/hue',
+      description = "Real time queries all your Big Data",
+      packages = find_packages('src'),
+      package_dir = {'': 'src'},
+      install_requires = ['setuptools', 'desktop'],
+      entry_points = { 'desktop.sdk.application': 'impala=impala' },
+)

+ 3 - 0
apps/impala/static/help/index.html

@@ -0,0 +1,3 @@
+<html>
+<h1><a name="AboutHue-AboutHue"></a>About impala</h1>
+</html>

+ 13 - 16
desktop/conf.dist/hue.ini

@@ -332,22 +332,18 @@
 
 [beeswax]
 
-  # Deprecated! Will be removed in Hue 3
-  # Multiple sections are now available in query_servers
   # Host where Beeswax internal metastore Thrift daemon is running
   ## beeswax_meta_server_host=localhost
 
-  # Deprecated! Will be removed in Hue 3
-  # Multiple sections are now available in query_servers  
   # Configure the port the internal metastore daemon runs on. Used only if
-  # hive.metastore.local is true. 
+  # hive.metastore.local is true.
   ## beeswax_meta_server_port=8003
 
   # Host where Beeswax internal metastore Thrift daemon is running
   ## beeswax_meta_server_host=localhost
-  
+
   # Configure the port the internal metastore daemon runs on. Used only if
-  # hive.metastore.local is true. 
+  # hive.metastore.local is true.
   ## beeswax_meta_server_port=8003
 
   # Hive home directory
@@ -375,17 +371,18 @@
   # Choices are 'beeswax' (default), 'hiveserver2'.
   ## server_interface=beeswax
 
- # One entry for each Query Server that can execute some SQL queries.
- # This must be in the bottom of the [beewax] section.
- [[query_servers]]
 
-   [[[default]]]
-   # Host where the Query Server Thrift daemon is running
+###########################################################################
+# Settings to configure Impala
+###########################################################################
+
+[impala]
+
+   # Host of the Impala Server
    ## server_host=localhost
-   # Configure the port the Query Server Thrift server 
-   ## server_port=8002
-   # If DDL queries are supported (e.g. DROP can be sent directly to this server)
-   ## support_ddl=True
+
+   # Port of the Impala Server
+   ## server_port=21000
 
 
 ###########################################################################

+ 14 - 16
desktop/conf/pseudo-distributed.ini.tmpl

@@ -335,22 +335,18 @@
 
 [beeswax]
 
-  # Deprecated! Will be removed in Hue 3
-  # Multiple sections are now available in query_servers
   # Host where Beeswax internal metastore Thrift daemon is running
   ## beeswax_meta_server_host=localhost
 
-  # Deprecated! Will be removed in Hue 3
-  # Multiple sections are now available in query_servers  
   # Configure the port the internal metastore daemon runs on. Used only if
-  # hive.metastore.local is true. 
+  # hive.metastore.local is true.
   ## beeswax_meta_server_port=8003
 
   # Host where Beeswax internal metastore Thrift daemon is running
   ## beeswax_meta_server_host=localhost
-  
+
   # Configure the port the internal metastore daemon runs on. Used only if
-  # hive.metastore.local is true. 
+  # hive.metastore.local is true.
   ## beeswax_meta_server_port=8003
 
   # Hive home directory
@@ -378,17 +374,19 @@
   # Choices are 'beeswax' (default), 'hiveserver2'.
   ## server_interface=beeswax
 
- # One entry for each Query Server that can execute some SQL queries.
- # This must be in the bottom of the [beewax] section.
- [[query_servers]]
 
-   [[[default]]]
-   # Host where the Query Server Thrift daemon is running
+###########################################################################
+# Settings to configure Impala
+###########################################################################
+
+[impala]
+
+   # Host of the Impala Server
    ## server_host=localhost
-   # Configure the port the Query Server Thrift server 
-   ## server_port=8002
-   # If DDL queries are supported (e.g. DROP can be sent directly to this server)
-   ## support_ddl=True
+
+   # Port of the Impala Server
+   ## server_port=21000
+
 
 ###########################################################################
 # Settings to configure Job Designer

+ 42 - 0
desktop/core/src/desktop/context_processors.py

@@ -0,0 +1,42 @@
+#!/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 re
+
+
+def app_name(request):
+  """
+  Add the name of the app to the template context.
+  """
+  context = {}
+
+  name = get_app_name(request)
+
+  if name is not None:
+    context.update({'app_name': name})
+
+  return context
+
+
+NAME = re.compile('/([^/]+)')
+
+def get_app_name(request):
+  # No other cleaner way found
+  match = NAME.search(request.path)
+
+  if match is not None:
+    return match.group(1)

+ 13 - 1
desktop/core/src/desktop/settings.py

@@ -148,6 +148,18 @@ INSTALLED_APPS = [
     'desktop'
 ]
 
+# Keep default values up to date
+TEMPLATE_CONTEXT_PROCESSORS = (
+  'django.contrib.auth.context_processors.auth',
+  'django.core.context_processors.debug',
+  'django.core.context_processors.i18n',
+  'django.core.context_processors.media',
+  'django.contrib.messages.context_processors.messages',
+   # Not default
+  'desktop.context_processors.app_name',
+)
+
+
 # Desktop doesn't use an auth profile module, because
 # because it doesn't mesh very well with the notion
 # of having multiple apps.  If your app needs
@@ -255,7 +267,7 @@ if SECRET_KEY == "":
 
 ############################################################
 
-# Necessary for South to not futz with tests.  Fixed in South 0.7.1
+# Necessary for South to not fuzz with tests.  Fixed in South 0.7.1
 SKIP_SOUTH_TESTS = True
 
 # Set up environment variable so Kerberos libraries look at our private

+ 4 - 2
desktop/core/src/desktop/templates/error.mako

@@ -18,7 +18,8 @@ from desktop.views import commonheader, commonfooter
 from desktop.lib.i18n import smart_unicode
 from django.utils.translation import ugettext as _
 %>
-${commonheader(_('Error'), "", user)}
+
+${commonheader(_('Error'), app_name, user)}
 
   <div class="container-fluid">
     <h1>${_('Error!')}</h1>
@@ -29,7 +30,8 @@ ${commonheader(_('Error'), "", user)}
       ${smart_unicode(traceback) | h}
       </textarea>
     %endif
+
+    <a class="btn" onclick="history.back()">${ _('Back') }</a>
   </div>
 
 ${commonfooter(messages)}
-

+ 1 - 1
desktop/core/src/desktop/urls.py

@@ -85,7 +85,7 @@ for app in appmanager.DESKTOP_APPS:
     static_patterns.append(
       static_pattern('%s/static' % app.name, app.static_dir))
 
-# TODO this stuff should probably be moved into a "ui" lib or somesuch so it
+# TODO this stuff should probably be moved into a "ui" lib or such so it
 # is autodiscovered
 def buildpath(d):
   return os.path.join(os.path.dirname(__file__), "..", '..', '..', d)