Przeglądaj źródła

HUE-783 [beeswax] Remove old files from Hue 1

Romain Rigaux 13 lat temu
rodzic
commit
726d380b4e

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

@@ -39,7 +39,7 @@ def query_form():
 
 
 
 
 class SaveForm(forms.Form):
 class SaveForm(forms.Form):
-  """Used for saving query design and report design."""
+  """Used for saving query design."""
   name = forms.CharField(required=False,
   name = forms.CharField(required=False,
                         max_length=64,
                         max_length=64,
                         initial=models.SavedQuery.DEFAULT_NEW_DESIGN_NAME,
                         initial=models.SavedQuery.DEFAULT_NEW_DESIGN_NAME,

+ 3 - 3
apps/beeswax/src/beeswax/management/commands/beeswax_install_examples.py

@@ -28,7 +28,7 @@ Expects 2 files in the beeswax.conf.LOCAL_EXAMPLES_DATA_DIR:
     has key-value pairs:
     has key-value pairs:
       * name: design name
       * name: design name
       * desc: design description
       * desc: design description
-      * type: REPORT/HQL design type
+      * type: HQL design type
       * data: the json design data
       * data: the json design data
 """
 """
 
 
@@ -84,7 +84,7 @@ class Command(NoArgsCommand):
     try:
     try:
       user = self._install_user()
       user = self._install_user()
       self._install_tables(user)
       self._install_tables(user)
-      self._install_reports(user)
+      self._install_queries(user)
       self._set_installed()
       self._set_installed()
       LOG.info('Beeswax examples installed')
       LOG.info('Beeswax examples installed')
     except Exception, ex:
     except Exception, ex:
@@ -132,7 +132,7 @@ class Command(NoArgsCommand):
       table.install(django_user)
       table.install(django_user)
     LOG.info('Successfully created sample tables with data')
     LOG.info('Successfully created sample tables with data')
 
 
-  def _install_reports(self, django_user):
+  def _install_queries(self, django_user):
     """
     """
     Install design designs.
     Install design designs.
     """
     """

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

@@ -116,14 +116,14 @@ class QueryHistory(models.Model):
 
 
 class SavedQuery(models.Model):
 class SavedQuery(models.Model):
   """
   """
-  Stores the query/report that people have save or submitted.
+  Stores the query that people have save or submitted.
 
 
   Note that this used to be called QueryDesign. Any references to 'design'
   Note that this used to be called QueryDesign. Any references to 'design'
   probably mean a SavedQuery.
   probably mean a SavedQuery.
   """
   """
   DEFAULT_NEW_DESIGN_NAME = _('My saved query')
   DEFAULT_NEW_DESIGN_NAME = _('My saved query')
   AUTO_DESIGN_SUFFIX = _(' (new)')
   AUTO_DESIGN_SUFFIX = _(' (new)')
-  TYPES = (HQL, REPORT) = range(2)
+  TYPES = (HQL, REPORT) = range(2) # REPORT is unused
 
 
   type = models.IntegerField(null=False)
   type = models.IntegerField(null=False)
   owner = models.ForeignKey(User, db_index=True)
   owner = models.ForeignKey(User, db_index=True)

+ 0 - 22
apps/beeswax/src/beeswax/report/__init__.py

@@ -1,22 +0,0 @@
-#!/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.
-
-"""
-The Report Generator package
-"""
-from beeswax.report.views import edit_report
-from beeswax.report.design import ReportDesign

+ 0 - 125
apps/beeswax/src/beeswax/report/design.py

@@ -1,125 +0,0 @@
-#!/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.
-
-"""
-The ReportDesign class can (de)serialize a design to/from a QueryDict.
-"""
-
-import logging
-import simplejson
-import django.http
-
-from desktop.lib.django_forms import MultiForm
-
-from beeswax.design import denormalize_form_dict
-from beeswax.design import denormalize_formset_dict
-from beeswax.design import normalize_form_dict
-from beeswax.design import normalize_formset_dict
-from beeswax.design import SERIALIZATION_VERSION
-
-from beeswax.report import report_gen_views
-
-
-LOG = logging.getLogger(__name__)
-
-class ReportDesign(object):
-  """
-  Represents a report design, with methods to perform (de)serialization.
-  """
-  _COLUMN_ATTRS = [ 'display', 'source', 'agg', 'distinct', 'constant', 'table', 'table_alias',
-                    'col', 'col_alias', 'sort', 'sort_order', 'group_order' ]
-  _COND_ATTRS = [ 'l_source', 'l_table', 'l_col', 'l_constant', 'op',
-                  'r_source', 'r_table', 'r_col', 'r_constant' ]
-  _BOOL_ATTRS = [ 'bool' ]
-
-  def __init__(self, form):
-    """Initialize the design from form data. The form may be invalid."""
-    assert isinstance(form, MultiForm)
-    self._data_dict = dict(
-        columns = normalize_formset_dict(form.columns, ReportDesign._COLUMN_ATTRS))
-    self._data_dict['union'] = self._normalize_union_mform(form.union)
-
-
-  def _normalize_union_mform(self, union_mform):
-    """
-    Normalize the subunions in the MultiForm recursively.
-    Returns a data dict.
-    """
-    data_dict = dict(
-        bools = normalize_form_dict(union_mform.bool, ReportDesign._BOOL_ATTRS),
-        conds = normalize_formset_dict(union_mform.conds, ReportDesign._COND_ATTRS))
-
-    subunion_list = [ ]
-    for name, subform in union_mform.get_subforms():
-      if name.startswith(report_gen_views.SUB_UNION_PREFIX):
-        dic = self._normalize_union_mform(subform)
-        subunion_list.append(dic)
-    data_dict['subunions'] = subunion_list
-    return data_dict
-
-
-  def dumps(self):
-    """Returns the serialized form of the design in a string"""
-    dic = self._data_dict.copy()
-    dic['VERSION'] = SERIALIZATION_VERSION
-    return simplejson.dumps(dic)
-
-
-  def get_query_dict(self):
-    """get_query_dict() -> QueryDict"""
-    # We construct the mform to use its structure and prefix. We don't actually bind
-    # data to the forms.
-    mform = report_gen_views.report_form()
-    mform.bind()
-
-    res = django.http.QueryDict('', mutable=True)
-    res.update(denormalize_formset_dict(
-                self._data_dict['columns'], mform.columns, ReportDesign._COLUMN_ATTRS))
-    res.update(self._denormalize_union_mform(self._data_dict['union'], mform.union))
-    return res
-
-
-  def _denormalize_union_mform(self, data_dict, mform):
-    """Returns a QueryDict"""
-    res = django.http.QueryDict('', mutable=True)
-    res.update(denormalize_form_dict(data_dict['bools'], mform.bool, ReportDesign._BOOL_ATTRS))
-    res.update(denormalize_formset_dict(data_dict['conds'], mform.conds, ReportDesign._COND_ATTRS))
-
-    subunion_dict_list = data_dict['subunions']
-    for i, subunion_dict in enumerate(subunion_dict_list):
-      # Make a subform on the fly and denormalize that recursively
-      name = '%s%d' % (report_gen_views.SUB_UNION_PREFIX, i)
-      mform.add_subform(name, report_gen_views.UnionMultiForm)
-      res.update(self._denormalize_union_mform(subunion_dict, getattr(mform, name)))
-
-    res[mform.mgmt.add_prefix('next_form_id')] = str(len(subunion_dict_list))
-    return res
-
-
-  @staticmethod
-  def loads(data):
-    """Returns an HQLdesign from the serialized form"""
-    dic = simplejson.loads(data)
-    if dic['VERSION'] != SERIALIZATION_VERSION:
-      LOG.error('Report design version mismatch. Found %s; expect %s' %
-                (dic['VERSION'], SERIALIZATION_VERSION))
-      return None
-    del dic['VERSION']
-
-    design = ReportDesign.__new__(ReportDesign)
-    design._data_dict = dic
-    return design

+ 0 - 289
apps/beeswax/src/beeswax/report/report_gen.py

@@ -1,289 +0,0 @@
-#!/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.
-
-"""
-The building blocks of a query:
-- A Selection (which despite its name, doesn't have to appear in a SELECT clause) is
-  a column or a constant value, with optional functions applied to it.
-- A QTable is a table used in a query, and can return the columns it has.
-- A BooleanCondition is a boolean/logical operator on one or more Selections, or other
-  BooleanCondition's.
-- A WHERE clause is just a BooleanCondition.
-- A Join is a bunch of tables, plus a BooleanCondition.
-
-  SELECT
-    x.foo AS foo,                       <- ColumnSelection
-    1,                                  <- ConstSelection
-    2 + y.bar,                          <- FunctionSelection of two other Selections
-    LOG10(10)                           <- FunctionSelection of a ConstSelection
-  FROM
-    table_x x JOIN table_y y            <- Two Tables
-    ON
-      x.foo = y.bar                     <- A LogicalUnion with one BooleanCondition
-  WHERE                                 <- A LogicalUnion
-    x.fred = y.bar OR                      ... its lhs is another BooleanCondition
-    ( x.num > SIN(10) AND                  ... its rhs is a LogicalUnion of two BooleanCondition's
-      x.num < 100 )
-"""
-
-import logging
-from beeswax import common
-from beeswax import db_utils
-
-from django.utils.translation import ugettext as _
-
-LOG = logging.getLogger(__name__)
-
-#
-# TODO(bc): Complex type support missing, e.g. array subscript, struct member access.
-# TODO(bc): Missing CLUSTERBY, DISTRIBUTE BY, TRANSFORM
-# TODO(bc): Missing subquery
-#
-
-class QTable(object):
-  def __init__(self, table_name, alias=None):
-    self.name = table_name
-    self.alias = alias or None          # Canonical alias == None, when it has no alias
-    self.columns = None                 # None, or a list of Column's
-
-  def __cmp__(self, obj):
-    return cmp((self.name, self.alias), (obj.name, obj.alias))
-
-  def manifest(self, is_from=False):
-    """If the QTable shows up in a FROM clause, we show 'name alias'. Otherwise just either one"""
-    res = self.name
-    if is_from:
-      if self.alias:
-        res = "%s %s" % (res, self.alias)
-    else:
-      if self.alias:
-        return self.alias
-    return res
-
-  def get_columns(self):
-    """
-    get_columns() -> List of (cached) column names
-    May raise ttypes.NoSuchObjectException if the table name is bogus.
-    """
-    if self.columns is None:
-      ttable = db_utils.meta_client().get_table("default", self.name)
-      self.columns = [ c.name for c in ttable.sd.cols ]
-    return self.columns
-
-
-class _Selection(object):
-  """
-  An abstract selection with distinct and aggregation.
-  """
-  def __init__(self):
-    self._agg = None
-    self.distinct = False
-
-  def set_aggregation(self, agg):
-    if agg not in common.AGGREGATIONS:
-      raise KeyError(_("%(aggregation)s is not a valid aggregation") % {'aggregation': agg})
-    self._agg = agg
-
-  @property
-  def aggregation(self):
-    return self._agg
-
-  def manifest(self, name, alias=None, is_select=False):
-    res = name
-    if is_select:
-      if self.distinct:
-        res = 'DISTINCT ' + res
-      if self.aggregation:
-        res = '%s(%s)' % (self.aggregation, res)
-      if alias:
-        res = '%s AS %s' % (res, alias)
-    return res
-
-
-class ConstSelection(_Selection):
-  """Simple selection of a constant."""
-  def __init__(self, value, alias=None):
-    _Selection.__init__(self)
-    self.value = str(value)
-    try:
-      # Try to be smart and detect numbers vs strings
-      _ = float(value)
-    except ValueError:
-      # Not a number. Need quotes.
-      self.value = '"%s"' % (self.value.replace('"', '\\"'),)
-    self.alias = alias or None
-
-  def manifest(self, is_select=False):
-    return _Selection.manifest(self, self.value, self.alias, is_select)
-
-
-class ColumnSelection(_Selection):
-  """Simple selection of a column from a table."""
-  def __init__(self, table, column, alias=None):
-    _Selection.__init__(self)
-    self.table = table                  # QTable object
-    self.column = column
-    self.alias = alias
-
-  def manifest(self, is_select=False, is_sort=False):
-    if is_sort and self.alias:
-      return self.alias                 # HQL requires 'SORT BY <alias>'
-    name = '%s.%s' % (self.table.manifest(), self.column)
-    return _Selection.manifest(self, name, self.alias, is_select)
-
-
-class FunctionSelection(_Selection):
-  """Application of a function on ColumnSelection(s)."""
-  # TODO(bc)  This is very tedious to get right. Might use FreeFormSelection instead.
-  def __init__(self, fn_name, args, alias):
-    """
-    fn_name is the literal function name, e.g. '+', '!', 'LOG'.
-    args is a list of ColumnSelection(s) and constant(s), to which the function applies in order.
-    E.g.  FunctionSelection('-', [ ColumnSelection(tbl, n), 3 ], 'header')
-          -> SELECT ... tbl.n - 3 AS header ...
-    E.g.  FunctionSelection('LOG10', [ ColumnSelection(tbl, n), ], 'header')
-          -> SELECT ... log10(tbl.n) AS header ...
-    """
-    _Selection.__init__(self)
-    self.fn_name = fn_name
-    self.args = args
-    self.alias = alias
-
-  def manifest(self):
-    return "TODO_FunctionSelection_manifest"
-
-
-class FreeFormSelection(_Selection):
-  """The users can enter whatever the hack they want"""
-  def __init__(self, str, alias=None):
-    _Selection.__init__(self)
-    self.str = str
-    self.alias = alias
-
-  def manifest(self, is_select=False):
-    return _Selection.manifest(self, str, self.alias, is_select)
-
-
-class BooleanCondition(object):
-  """Represents an atomic condition that evaluates into True or False."""
-  def __init__(self, lhs_selection, relation, rhs_selection=None):
-    """
-    For unary relational operators, e.g. "IS NULL", "NOT", rhs may be None.
-    lhs and rhs are _Selection objects.
-    """
-    assert isinstance(lhs_selection, _Selection)
-    assert rhs_selection is None or isinstance(rhs_selection, _Selection)
-    if relation not in common.RELATION_OPS:
-      raise ValueError(_("%(relation)s is not a valid operator") % {'relation': relation})
-    self._lhs = lhs_selection
-    self._rhs = rhs_selection
-    self._relation = relation
-
-  def is_joinable(self):
-    """Whether this can be used as a JOIN condition."""
-    return isinstance(self._rhs, ColumnSelection) and \
-          isinstance(self._lhs, ColumnSelection) and \
-          self._relation == '=' and \
-          self._lhs != self._rhs
-
-  def manifest(self):
-    if self._relation in common.RELATION_OPS_UNARY:
-      if self._relation == 'NOT':
-        return 'NOT %s' % (self._lhs.manifest(),)
-      return '%s %s' % (self._lhs.manifest(), self._relation)
-    return '%s %s %s' % (self._lhs.manifest(), self._relation, self._rhs.manifest())
-
-
-class LogicalUnion(object):
-  """Represents a tree of BooleanCondition objects, AND/OR'ed together."""
-  def __init__(self, union_type):
-    assert union_type in ('AND', 'OR')
-    self.union_type = union_type
-    self.cond_list = [ ]                 # A list of BooleanCondition
-    self.sub_unions = [ ]                # A list of sub-LogicalUnion
-
-  def is_empty(self):
-    return self.size() == 0
-
-  def size(self):
-    return len(self.cond_list) + len(self.sub_unions)
-
-  def add_cond(self, boolean_cond):
-    assert isinstance(boolean_cond, BooleanCondition)
-    self.cond_list.append(boolean_cond)
-
-  def add_subunion(self, union_cond):
-    assert isinstance(union_cond, LogicalUnion)
-    self.sub_unions.append(union_cond)
-
-  def compact(self):
-    """Get rid of unions that only consist of one other union"""
-    for child in self.sub_unions:
-      child.compact()
-    if len(self.cond_list) == 0 and len(self.sub_unions) == 1:
-      copy = self.sub_unions[0]
-      self.union_type = copy.union_type
-      self.cond_list = copy.cond_list
-      self.sub_unions = copy.sub_unions
-
-  def is_joinable(self):
-    return all([ cond.is_joinable() for cond in self.cond_list ]) and \
-            all([ child.is_joinable() for child in self.sub_unions ])
-
-  def split_join_condition(self):
-    """
-    split_join_condition() -> join-able LogicalUnion
-
-    Split this LogicalUnion into two. Return a join-able LogicalUnion, and
-    modifies this object to contain WHERE clause conditions. This is needed because
-    HIVE is not smart enough to optimize the join from the WHERE clause.
-
-    Theoretically, (which we don't do), we should convert self to CNF, then pick
-    out all the join-able subconditions. In practice, this is too hairy.
-    """
-    res = LogicalUnion('AND')
-    if self.union_type == 'OR' and self.size() != 1:
-      # Since we're not in conjunctive form, just return an empty condition.
-      # Note that AND/OR doesn't matter if we're a singleton.
-      return res
-
-    where_cond_list = [ ]
-    for cond in self.cond_list:
-      if cond.is_joinable():
-        res.add_cond(cond)
-      else:
-        where_cond_list.append(cond)
-
-    where_sub_unions = [ ]
-    for child in self.sub_unions:                 # Split sub_unions recursively
-      j_child = child.split_join_condition()
-      if not j_child.is_empty():
-        res.add_subunion(j_child)
-      if not child.is_empty():
-        where_sub_unions.append(child)
-
-    self.cond_list = where_cond_list
-    self.sub_unions = where_sub_unions
-    return res
-
-  def manifest(self, level=0):
-    if self.is_empty():
-      return ''
-    atoms = [ cond.manifest() for cond in self.cond_list ] + \
-            [ child.manifest(level+1) for child in self.sub_unions ]
-    connector = '\n' + '     ' * level + ' %s ' % (self.union_type)
-    return '( ' + connector.join(atoms) + ' )'

+ 0 - 505
apps/beeswax/src/beeswax/report/report_gen_views.py

@@ -1,505 +0,0 @@
-#!/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.
-
-"""
-Views for the Report Generator.
-
-The Report Generator has a root level MultiForm, which contains a
-ReportColumnFormset, and a UnionMultiForm. The ReportColumnFormset is just a
-collection of ReportColumnForm's. The tricky bit is in the UnionMultiForm.
-
-One can think of a UnionMultiForm as a logical statement:
-    AND(x=1, y=2, z=3)
-When a UnionMultiForm is created, it initially only consists of:
-    * "bool"          - a ReportConditionBoolForm; the "AND"
-    * "conds"         - a ReportConditionFormset; the "x=1, y=2, z=3" list
-    * "mgmt"          - a ManagementForm; to allow adding sub-conditions
-
-But a UnionMultiForm may contain sub-conditions (i.e. other UnionMultiForm).
-Using the example above:
-    AND(x=1, y=2, z=3, OR(a=4, b=5))
-When we add the sub-condition OR(a=4, b=5), we need to dynamically extend the
-parent UnionMultiForm to hold a child UnionMultiForm. We name the nth child form
-"sub<n>". From the same example, the parent UnionMultiForm will have:
-    * "bool"          - a ReportConditionBoolForm; the "AND"
-    * "conds"         - a ReportConditionFormset; the "x=1, y=2, z=3" list
-    * "mgmt"          - a ManagementForm; to allow adding sub-conditions
-    * "sub0"          - a UnionMultiForm; the "OR(a=4, b=5)"
-
-When we handle the view, we always first create a initial UnionMultiForm with no
-sub-conditions, because we do not know whether there is any. Then we look at
-the POST data to figure out what sub-UnionMultiForm's we should have (see
-fixup_union()).
-"""
-
-import logging
-
-import beeswax.forms
-
-from django import forms
-
-from beeswax import common
-from beeswax.report import report_gen
-from desktop.lib.django_forms import BaseSimpleFormSet, ManagementForm, MultiForm
-from desktop.lib.django_forms import simple_formset_factory, SubmitButton
-
-from django.utils.translation import ugettext as _t
-
-LOG = logging.getLogger(__name__)
-
-SUB_UNION_PREFIX = 'sub'
-
-def fixup_union(parent_mform, subform_name, data, is_root=False):
-  """
-  Create child union mforms dynamically. Note that the parent_mform may be invalid.
-  The parent_mform is a MultiForm, in which the subform_name is a UnionMultiForm we
-  need to fix.
-
-  This method performs this dynamic construction of the child UnionMultiForm.
-  Note that it applies to existing child forms (i.e. the child form exists and the
-  user is submitting the query), as well as a newly added child (i.e. the user just
-  clicked 'ADD' to add a sub-condition).
-
-  We figure out the number of children from the mgmt form. Note that this is actually
-  a count of the number of children we have ever had. It is a max of the number of
-  children, some of which may have been deleted.
-
-  For each child <i>, our strategy is to test if 'sub<i>' exists in the POST data.
-  If so, we create a sub-UnionMultiForm for that child. And we do it recursively.
-  """
-  global SUB_UNION_PREFIX
-
-  union_mform = getattr(parent_mform, subform_name)
-  mgmt_form = union_mform.mgmt
-  if not mgmt_form.is_valid():
-    raise forms.ValidationError(_t('Missing ManagementForm for conditions'))
-  n_children = mgmt_form.form_counts()
-
-  # This removes our current subform (union_mform) and any children.
-  if mgmt_form.cleaned_data['remove']:
-    parent_mform.remove_subform(subform_name)
-    LOG.debug('Removed subform: %s' % (union_mform,))
-    # If we just removed the root union, add back an empty one.
-    if is_root:
-      parent_mform.add_subform('union', UnionMultiForm, data=None)
-      LOG.debug('Adding root union back')
-    return
-
-  # Note that some of the n_children could be non-existent (i.e. deleted)
-  for i in range(0, n_children):
-    child_name = '%s%d' % (SUB_UNION_PREFIX, i)
-    if not union_mform.has_subform_data(child_name, data):
-      LOG.debug('Skipping over non-existent subform: %s %s' % (union_mform, child_name))
-      continue
-    union_mform.add_subform(child_name, UnionMultiForm, data)
-    LOG.debug('Instantiating subform: %s %s' % (union_mform, child_name))
-    # The child may have grand-children
-    fixup_union(union_mform, child_name, data)
-
-  if mgmt_form.cleaned_data['add']:
-    id = mgmt_form.new_form_id()
-    child_name = '%s%d' % (SUB_UNION_PREFIX, id)
-    union_mform.add_subform(child_name, UnionMultiForm)
-    LOG.debug('Added subform: %s %s' % (union_mform, child_name))
-
-
-def construct_query(mform):
-  """Take a root level MultiForm and return a query string"""
-  columns = mform.columns
-  selection_list = [ ]
-  for form in columns.forms:
-    if form.cleaned_data['display']:
-      selection_list.append(form.selection)
-
-  select_clause_atoms = [ sel.manifest(is_select=True) for sel in selection_list ]
-  select_clause = 'SELECT %s' % (', '.join(select_clause_atoms))
-
-  from_clause_atoms = [ t.manifest(is_from=True) for t in columns.qtable_list ]
-  from_clause = '\nFROM\n  %s' % (' JOIN \n  '.join(from_clause_atoms))
-
-  # where clause
-  table_alias_dict = { }
-  for qt in columns.qtable_list:
-    alias = qt.alias or qt.name
-    table_alias_dict[alias] = qt
-  where_union_cond = _extract_condition(mform.union, table_alias_dict)
-  join_union_cond = where_union_cond.split_join_condition()
-  if where_union_cond.is_empty():
-    where_clause = ''
-  else:
-    where_union_cond.compact()
-    where_clause = '\nWHERE\n' + where_union_cond.manifest()
-
-  # join condition
-  if join_union_cond.is_empty():
-    join_on_clause = ''
-  else:
-    join_union_cond.compact()
-    join_on_clause = '\nON ' + join_union_cond.manifest()
-
-  # group_list is a list of forms that specify grouping
-  group_list = filter(lambda form: form.cleaned_data.get('group_order', ''), columns.forms)
-  if group_list:
-    group_list.sort(lambda f1, f2:
-                      cmp(f1.cleaned_data['group_order'], f2.cleaned_data['group_order']))
-    group_clause_atoms = [ form.selection.manifest() for form in group_list ]
-    group_clause = '\nGROUP BY %s' % (', '.join(group_clause_atoms))
-  else:
-    group_clause = ''
-
-  # sort_list is a list of forms that specify sorting
-  sort_list = filter(lambda form: form.cleaned_data.get('sort', ''), columns.forms)
-  if sort_list:
-    sort_list.sort(lambda f1, f2:
-                      cmp(f1.cleaned_data['sort_order'], f2.cleaned_data['sort_order']))
-    sort_clause_atoms = [ '%s %s' % (form.selection.manifest(is_sort=True),
-                                     form.cleaned_data['sort_hql'])
-                          for form in sort_list ]
-    sort_clause = '\nSORT BY %s' % (', '.join(sort_clause_atoms))
-  else:
-    sort_clause = ''
-
-  return select_clause + from_clause + join_on_clause + where_clause + group_clause + sort_clause
-
-
-def _extract_condition(union_mform, table_alias_dict):
-  """
-  Extract LogicalUnion's from each form in union_mform, and recurse into the child union.
-  Returns a LogicalUnion.
-  """
-  global SUB_UNION_PREFIX
-  if not union_mform.is_valid():
-    assert False, _t('UnionMultiForm is not valid')
-    return None
-
-  op = union_mform.bool.cleaned_data['bool']
-  res = report_gen.LogicalUnion(op)
-  for cond_form in union_mform.conds.forms:
-    res.add_cond(cond_form.get_boolean_condition(table_alias_dict))
-
-  n_children = union_mform.mgmt.form_counts()
-  for i in range(0, n_children):
-    child_name = '%s%d' % (SUB_UNION_PREFIX, i)
-    try:
-      sub_form = getattr(union_mform, child_name)
-      res.add_subunion(_extract_condition(sub_form, table_alias_dict))
-    except AttributeError:
-      LOG.debug('Subform not found: %s %s' % (union_mform, child_name))
-      continue
-
-  return res
-
-
-def _field_source_check(true_source, field_name, field_value, is_from_table):
-  """
-  Some fields come from a table (is_from_table). And they should be specified iff
-  the true_source (what the user selected) says "table". The same holds for constant
-  source. This function verifies that constraint and would raise ValidationError.
-
-  Returns whether this field is required.
-  """
-  if bool(true_source == 'table') ^ bool(is_from_table):
-    if field_value:
-      raise forms.ValidationError(_t('%(field)s value not applicable with %(source)s source') %
-                                  {'field': field_name, 'source': true_source})
-    return False
-  elif not field_value:
-    raise forms.ValidationError(_t('%(field)s value missing') % {'field': field_name})
-  return True
-
-
-###########
-# Columns
-###########
-
-class ReportColumnForm(forms.Form):
-  """
-  A form representing a column in the report.
-  """
-  # If not 'display', then source must be 'table'
-  display = forms.BooleanField(label=_t('Display'), required=False, initial=True)
-
-  # Shown iff 'display'. 'source' is not required, but will be set during clean
-  source = forms.ChoiceField(label=_t('Source'), required=False, initial='table',
-                                choices=common.to_choices(common.SELECTION_SOURCE))
-  # Shown iff 'display'
-  agg = forms.ChoiceField(label=_t('Aggregate'), required=False,
-                                choices=common.to_choices(common.AGGREGATIONS))
-  # Shown iff 'display'
-  distinct = forms.BooleanField(label=_t("Distinct"), required=False)
-
-  # Shown iff 'source' is 'constant'
-  constant = forms.CharField(label=_t('Constant value'), required=False)
-
-  # Shown iff 'source' is 'table'
-  table_alias = common.HiveIdentifierField(label=_t('Table alias'), required=False)
-  # Shown iff 'source' is 'table'
-  col = forms.CharField(label=_t('From column'), required=False)
-  # Shown iff 'display', and 'source' is 'table'
-  col_alias = common.HiveIdentifierField(label=_t('Column alias'), required=False)
-  # Shown iff 'display', and 'source' is 'table'
-  sort = forms.ChoiceField(label=_t('Sort'), required=False,
-                                choices=common.to_choices(common.SORT_OPTIONS))
-  # Shown iff 'sort'
-  sort_order = forms.IntegerField(label=_t('Sort order'), required=False, min_value=1)
-  # Shown iff 'display', and 'source' is 'table'
-  group_order = forms.IntegerField(label=_t('Group order'), required=False, min_value=1)
-
-  def __init__(self, *args, **kwargs):
-    forms.Form.__init__(self, *args, **kwargs)
-    # Shown iff 'source' is 'table'
-    self.fields['table'] = common.HiveTableChoiceField(label=_t('From table'), required=False)
-
-  def _display_check(self):
-    """Reconcile 'display' with 'source'"""
-    src = self.cleaned_data.get('source')
-    if not self.cleaned_data.get('display'):
-      if src and src != 'table':
-        raise forms.ValidationError(_t('Source must be "table" when not displaying column'))
-      self.cleaned_data['source'] = 'table'
-      if self.cleaned_data.get('col_alias'):
-        raise forms.ValidationError(_t('Column alias not applicable when not displaying column'))
-    else:
-      if not src:
-        raise forms.ValidationError(_t('Source value missing'))
-
-
-  def clean_display(self):
-    """Make sure display is set"""
-    return self.cleaned_data.get('display', False)
-
-
-  def clean_sort(self):
-    """Set sort_hql accordingly"""
-    dir = self.cleaned_data.get('sort')
-    if dir == 'ascending':
-      self.cleaned_data['sort_hql'] = 'ASC'
-    elif dir == 'descending':
-      self.cleaned_data['sort_hql'] = 'DESC'
-    elif self.cleaned_data.has_key('sort_hql'):
-      del self.cleaned_data['sort_hql']
-    return dir
-
-
-  def clean(self):
-    self.qtable = None
-    self.selection = None
-
-    self._display_check()
-
-    if self.cleaned_data.get('sort') and not self.cleaned_data['sort_hql']:
-      raise KeyError()
-
-    # Verify that the 'source' field is consistent with the other fields
-    source = self.cleaned_data.get('source')
-    if not source:
-      return None                       # No point since we can't get source
-
-    constant_val = self.cleaned_data.get('constant')
-    _field_source_check(source, _t('Constant'), constant_val, is_from_table=False)
-
-    table_val = self.cleaned_data.get('table')
-    _field_source_check(source, _t('From table'), table_val, is_from_table=True)
-
-    col_val = self.cleaned_data.get('col')
-    _field_source_check(source, _t('From column'), col_val, is_from_table=True)
-
-    if self.cleaned_data.get('sort', '') and not self.cleaned_data.get('sort_order', ''):
-      raise forms.ValidationError(_t('Sort order missing'))
-
-    if table_val:
-      # Column must belong to the table
-      self.qtable = report_gen.QTable(table_val, self.cleaned_data.get('table_alias'))
-      if col_val == '*':
-        if self.cleaned_data.get('col_alias'):
-          raise forms.ValidationError(_t('Alias not applicable for selecting "*"'))
-      elif col_val not in self.qtable.get_columns():
-        raise forms.ValidationError(_t('Invalid column name "%(column)s"') % {'column': col_val})
-      # ColumnSelection object
-      self.selection = report_gen.ColumnSelection(self.qtable,
-                                                  col_val,
-                                                  self.cleaned_data.get('col_alias'))
-    else:
-      # ConstSelection object
-      self.selection = report_gen.ConstSelection(constant_val,
-                                                 self.cleaned_data.get('col_alias'))
-    self.selection.distinct = self.cleaned_data.get('distinct', False)
-    self.selection.set_aggregation(self.cleaned_data.get('agg', ''))
-
-    if self.errors:
-      delattr(self, 'selection')
-    return self.cleaned_data
-
-
-class ReportColumnBaseFormset(BaseSimpleFormSet):
-  def clean(self):
-    self.qtable_list = None
-    if filter(None, [ not f.is_valid() for f in self.forms ]):
-      return
-
-    qt_by_name = { }                    # Dictionary of name -> [ QTable list ]
-    n_display = 0
-    for form in self.forms:
-      if form.cleaned_data['display']:
-        n_display += 1
-
-      # Gather a list of QTables (qtable_list) involved, and check for naming collision
-      if form.cleaned_data['source'] != 'table':
-        continue
-
-      curr = form.qtable
-      qt_list = qt_by_name.get(curr.name, [ ])
-      for qt in qt_list:
-        # Error if a table has alias but another doesn't. (Tables with the same name.)
-        if bool(curr.alias) ^ bool(qt.alias):
-          raise forms.ValidationError(_t('Ambiguous table "%(table)s" without alias') % {'table': qt.name})
-        if curr.alias == qt.alias:
-          # Duplicate. Don't update.
-          break
-      else:
-        qt_list.append(curr)
-        qt_by_name[curr.name] = qt_list
-
-    self.qtable_list = sum([ tbl_list for tbl_list in qt_by_name.values() ], [ ])
-    if not self.qtable_list:
-      raise forms.ValidationError(_t('Not selecting from any table column'))
-    if n_display == 0:
-      raise forms.ValidationError(_t('Not displaying any selection'))
-
-
-ReportColumnFormset = simple_formset_factory(ReportColumnForm,
-                                             formset=ReportColumnBaseFormset,
-                                             initial=(None,))
-
-###########
-# Condition
-###########
-
-class ReportConditionForm(forms.Form):
-  l_source = forms.ChoiceField(label=_t('Source'), initial='table',
-                              choices=common.to_choices(common.SELECTION_SOURCE))
-  l_table = forms.CharField(label=_t('Table name/alias'), required=False)
-  l_col = forms.CharField(label=_t('Column name'), required=False)
-  l_constant = forms.CharField(label=_t('Constant'), required=False)
-  op = forms.ChoiceField(label=_t('Condition'),
-                              choices=common.to_choices(common.RELATION_OPS))
-  r_source = forms.ChoiceField(label=_t('Source'), required=False, initial='table',
-                              choices=common.to_choices(common.SELECTION_SOURCE))
-  r_table = forms.CharField(label=_t('Table name/alias'), required=False)
-  r_col = forms.CharField(label=_t('Column name'), required=False)
-  r_constant = forms.CharField(label=_t('Constant'), required=False)
-
-
-  def clean(self):
-    if self.errors:
-      return
-
-    # Verify unary operators constraints
-    check_right = True
-    op = self.cleaned_data['op']
-    if op in common.RELATION_OPS_UNARY:
-      if self.cleaned_data.get('r_source') or self.cleaned_data.get('r_cond'):
-        raise forms.ValidationError(_t('Operator %(operator)s does not take the right operand') % {'operator': op})
-      check_right = False
-    else:
-      if not self.cleaned_data.get('l_source') or not self.cleaned_data.get('r_source'):
-        raise forms.ValidationError(_t('Operator %(operator)s takes both operands') % {'operator': op})
-
-    # Verify the lhs values match the source
-    l_source = self.cleaned_data['l_source']
-    l_constant = self.cleaned_data.get('l_constant')
-    _field_source_check(l_source, _t('Constant (Left)'), l_constant, is_from_table=False)
-    l_table = self.cleaned_data.get('l_table')
-    _field_source_check(l_source, _t('Table (Left)'), l_table, is_from_table=True)
-    l_col = self.cleaned_data.get('l_col')
-    _field_source_check(l_source, _t('Column (Left)'), l_col, is_from_table=True)
-
-    if check_right:
-      # Verify the rhs values match the source
-      r_source = self.cleaned_data['r_source']
-      r_constant = self.cleaned_data.get('r_constant')
-      _field_source_check(r_source, _t('Constant (Right)'), r_constant, is_from_table=False)
-      r_table = self.cleaned_data.get('r_table')
-      _field_source_check(r_source, _t('Table (Right)'), r_table, is_from_table=True)
-      r_col = self.cleaned_data.get('r_col')
-      _field_source_check(r_source, _t('Column (Right)'), r_col, is_from_table=True)
-    return self.cleaned_data
-
-
-  def get_boolean_condition(self, table_alias_dict):
-    if not self.is_valid():
-      assert False, 'ReportConditionForm is not valid'
-      return None
-
-    op = self.cleaned_data['op']
-    lhs = self._make_selection(table_alias_dict, is_left=True)
-    if op in common.RELATION_OPS_UNARY:
-      return report_gen.BooleanCondition(lhs, op)
-
-    rhs = self._make_selection(table_alias_dict, is_left=False)
-    return report_gen.BooleanCondition(lhs, op, rhs)
-
-
-  def _make_selection(self, table_alias_dict, is_left):
-    if is_left:
-      prefix = 'l_'
-    else:
-      prefix = 'r_'
-
-    source = self.cleaned_data[prefix + 'source']
-    if source == 'table':
-      table = self.cleaned_data[prefix + 'table']
-      col = self.cleaned_data[prefix + 'col']
-      try:
-        return report_gen.ColumnSelection(table_alias_dict[table], col)
-      except KeyError:
-        raise forms.ValidationError(_t('Unknown table "%(table)s" in condition') % {'table': table})
-
-    constant = self.cleaned_data[prefix + 'constant']
-    return report_gen.ConstSelection(constant)
-
-ReportConditionFormset = simple_formset_factory(ReportConditionForm,
-                                                initial=(None,))
-
-
-class ReportConditionBoolForm(forms.Form):
-  bool = forms.ChoiceField(label='And/Or', required=True,
-                           choices=common.to_choices([ 'AND', 'OR' ]))
-
-
-class UnionManagementForm(ManagementForm):
-  def __init__(self, *args, **kwargs):
-    ManagementForm.__init__(self, *args, **kwargs)
-    remove = forms.BooleanField(label=_t('Remove'), widget=SubmitButton, required=False)
-    remove.widget.label = '-'
-    self.fields['remove'] = remove
-
-
-class UnionMultiForm(MultiForm):
-  def __init__(self, *args, **kwargs):
-    MultiForm.__init__(self,
-                       mgmt=UnionManagementForm,
-                       bool=ReportConditionBoolForm,
-                       conds=ReportConditionFormset,
-                       *args, **kwargs)
-
-
-def report_form():
-  """report_form() -> A MultiForm object for report generator"""
-  return MultiForm(columns=ReportColumnFormset,
-                    union=UnionMultiForm,
-                    saveform=beeswax.forms.SaveForm)

+ 0 - 201
apps/beeswax/src/beeswax/report/tests.py

@@ -1,201 +0,0 @@
-#!/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.
-#
-
-"""
-Tests for report generator
-"""
-
-from nose.tools import assert_true, assert_equal
-
-from desktop.lib.django_test_util import assert_equal_mod_whitespace
-from desktop.lib.django_test_util import assert_similar_pages
-from beeswax.test_base import wait_for_query_to_finish, verify_history, BeeswaxSampleProvider
-
-from beeswax.report.report_gen import BooleanCondition, ColumnSelection
-from beeswax.report.report_gen import QTable, ConstSelection, LogicalUnion
-
-import beeswax.models
-
-def test_report_gen_query():
-  """
-  Tests HQL generation backend
-  """
-  # Table manifest
-  table = QTable('test_table')
-  assert_equal_mod_whitespace(table.manifest(is_from=False), 'test_table')
-  assert_equal_mod_whitespace(table.manifest(is_from=True), 'test_table')
-  table.alias = 'foo'
-  assert_equal_mod_whitespace(table.manifest(is_from=False), 'foo')
-  assert_equal_mod_whitespace(table.manifest(is_from=True), 'test_table foo')
-
-  # Column manifest
-  col = ColumnSelection(table, 'col')
-  assert_equal_mod_whitespace(col.manifest(), 'foo.col')
-  assert_equal_mod_whitespace(col.manifest(is_select=True), 'foo.col')
-  assert_equal_mod_whitespace(col.manifest(is_sort=True), 'foo.col')
-  col.alias = 'X'
-  assert_equal_mod_whitespace(col.manifest(), 'foo.col')
-  assert_equal_mod_whitespace(col.manifest(is_select=True), 'foo.col AS X')
-  assert_equal_mod_whitespace(col.manifest(is_sort=True), 'X')
-
-  # Const manifest
-  simple_int = ConstSelection('69')
-  simple_int.alias = 'INT'
-  assert_equal_mod_whitespace(simple_int.manifest(), '69')
-
-  konst = ConstSelection('quote-"')
-  assert_equal_mod_whitespace(konst.manifest(), '"quote-\\""')
-  assert_equal_mod_whitespace(konst.manifest(is_select=True), '"quote-\\""')
-  konst.alias = 'Y'
-  assert_equal_mod_whitespace(konst.manifest(), '"quote-\\""')
-  assert_equal_mod_whitespace(konst.manifest(is_select=True), '"quote-\\"" AS Y')
-
-  # Boolean condition manifest
-  bool_cond = BooleanCondition(col, '<>', konst)
-  assert_true(not bool_cond.is_joinable())
-  assert_equal_mod_whitespace(bool_cond.manifest(), 'foo.col <> "quote-\\""')
-
-  union_root = LogicalUnion('AND')
-  union_root.add_cond(bool_cond)
-  assert_equal_mod_whitespace(union_root.manifest(), '( foo.col <> "quote-\\"" )')
-  union_root.compact()
-  assert_equal_mod_whitespace(union_root.manifest(), '( foo.col <> "quote-\\"" )')
-  union_root.add_cond(BooleanCondition(simple_int, '=', simple_int))
-  assert_equal_mod_whitespace(union_root.manifest(), '( foo.col <> "quote-\\"" AND 69 = 69 )')
-
-  union_sub = LogicalUnion('OR')
-  union_sub.add_cond(BooleanCondition(col, 'IS NULL'))
-  union_root.add_subunion(union_sub)
-  assert_equal(union_root.size(), 3)
-  assert_equal_mod_whitespace(union_root.manifest(),
-                            '( foo.col <> "quote-\\"" AND 69 = 69 AND ( foo.col IS NULL ) )')
-
-  # Test union compaction
-  dumb_union = LogicalUnion('AND')
-  dumb_union.add_subunion(union_root)
-  assert_equal_mod_whitespace(dumb_union.manifest(),
-                            '( ( foo.col <> "quote-\\"" AND 69 = 69 AND ( foo.col IS NULL ) ) )')
-  dumb_union.compact()
-  assert_equal_mod_whitespace(dumb_union.manifest(),
-                            '( foo.col <> "quote-\\"" AND 69 = 69 AND ( foo.col IS NULL ) )')
-
-
-class TestReportGenWithHadoop(BeeswaxSampleProvider):
-  """Tests for report generator that require a running Hadoop"""
-  requires_hadoop = True
-
-  def test_report_gen_view(self):
-    """
-    Test report gen view logic and query generation.
-    It requires Hive because report gen automatically gathers all the table names.
-    """
-    cli = self.client
-
-    resp = cli.get('/beeswax/report_gen')
-    assert_true(resp.status_code, 200)
-
-    # This generates a SELECT * and takes us to the execute page
-    resp = cli.post("/beeswax/report_gen", {
-      'columns-next_form_id':     '1',
-      'columns-0-_exists':       'True',
-      'columns-0-col':           '*',
-      'columns-0-display':       'on',
-      'columns-0-source':        'table',
-      'columns-0-table':         'test',
-      'union.conds-next_form_id': '0',
-      'union.bool-bool':          'AND',
-      'union.mgmt-next_form_id':  '0',
-      'button-advanced':          'Submit',
-    })
-    assert_equal_mod_whitespace(r"SELECT test.* FROM test",
-                                resp.context["form"].query.initial["query"])
-
-    # Add a new column
-    resp = cli.post("/beeswax/report_gen", {
-      'columns-add':                  'True',
-      'columns-next_form_id':         '1',
-      'columns-0-_exists':            'True',
-      'union.bool-bool':              'AND',
-      'union.conds-next_form_id':     '1',
-      'union.conds-0-_exists':        'True',
-      'union.conds-0-op':             '=',
-      'union.mgmt-next_form_id':      '0'
-    })
-    assert_true('columns-1-_exists' in resp.content)
-
-    # Remove a sub form
-    resp = cli.post("/beeswax/report_gen", {
-      'columns-next_form_id':          '1',
-      'columns-0-_exists':             'True',
-      'union.bool-bool':               'AND',
-      'union.conds-next_form_id':      '1',
-      'union.conds-0-_exists':         'True',
-      'union.mgmt-next_form_id':       '1',
-      'union.sub0.bool-bool':          'AND',
-      'union.sub0.conds-next_form_id': '1',
-      'union.sub0.conds-0-_exists':    'True',
-      'union.sub0.mgmt-next_form_id':  '0',
-      'union.sub0.mgmt-remove':        'True'
-    })
-    assert_true('union.sub0' not in resp.content)
-
-    # This generates a SELECT * and directly submits the query
-    resp = cli.post("/beeswax/report_gen", {
-      'columns-next_form_id':     '1',
-      'columns-0-_exists':        'True',
-      'columns-0-col':            '*',
-      'columns-0-display':        'on',
-      'columns-0-source':         'table',
-      'columns-0-table':          'test',
-      'union.conds-next_form_id': '0',
-      'union.bool-bool':          'AND',
-      'union.mgmt-next_form_id':  '0',
-      'button-submit':            'Submit',
-      'saveform-name':            'select star via report',
-      'saveform-save':            'True',
-    }, follow=True)
-    resp = wait_for_query_to_finish(self.client, resp, max=30.0)
-    # Note that it may not return all rows at once. But we expect at least 10.
-    assert_true(len(resp.context['results']) > 10)
-
-    verify_history(cli, fragment='SELECT test.*', design='select star via report')
-
-
-  def test_report_designs(self):
-    """Test report design view and interaction"""
-    cli = self.client
-
-    # Test report design auto-save and loading
-    resp = cli.post("/beeswax/report_gen", {
-      'columns-next_form_id':           '1',
-      'columns-0-_exists':              'True',
-      'union.conds-next_form_id':       '0',
-      'union.mgmt-next_form_id':        '1',
-      'union.sub0.bool-bool':           'AND',
-      'union.sub0.conds-next_form_id':  '1',
-      'union.sub0.conds-0-_exists':     'True',
-      'union.sub0.mgmt-next_form_id':   '0',
-      'button-submit':                  'Submit',
-      'saveform-name':                  'reporter',
-      'saveform-save':                  'True',
-    })
-
-    # The report above is invalid. But it saves and submit at the same time.
-    design = beeswax.models.SavedQuery.objects.filter(name='reporter')[0]
-    resp2 = cli.get('/beeswax/report_gen/%s' % (design.id,))
-    assert_similar_pages(resp.content, resp2.content)

+ 0 - 83
apps/beeswax/src/beeswax/report/views.py

@@ -1,83 +0,0 @@
-#!/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.
-
-from django.core import urlresolvers
-
-from desktop.lib.django_util import render
-
-from beeswaxd.ttypes import BeeswaxException
-import beeswax.report.design
-from beeswax.report import report_gen_views
-
-from beeswax import models
-import beeswax.views
-
-def edit_report(request, design_id=None):
-  """View function for the Report Generator."""
-  action = request.path
-  mform = report_gen_views.report_form()
-  design = beeswax.views.safe_get_design(request, models.SavedQuery.REPORT, design_id)
-  error_message = None
-  log = None
-
-  # Use a loop structure to allow the use of 'break' to get out
-  for _ in range(1):
-    # Always bind to POST data, and update the design accordingly
-    if request.method == 'POST':
-      mform.bind(request.POST)
-      report_gen_views.fixup_union(mform, subform_name='union', data=request.POST, is_root=True)
-
-      to_submit = request.POST.has_key('button-submit')
-      to_advanced = request.POST.has_key('button-advanced')
-      # Always validate the saveform, which will tell us whether it needs explicit saving
-      if not mform.saveform.is_valid():
-        break
-      to_save = mform.saveform.cleaned_data['save']
-      if to_submit or to_advanced or to_save:
-        design = beeswax.views.save_design(
-                            request, mform, models.SavedQuery.REPORT, design, to_save)
-        action = urlresolvers.reverse(beeswax.views.edit_report, kwargs=dict(design_id=design.id))
-
-      # Submit?
-      if (to_advanced or to_submit) and mform.is_valid():
-        query_str = report_gen_views.construct_query(mform)
-        if to_advanced:
-          return beeswax.views.confirm_query(request, query_str)
-        elif to_submit:
-          query_msg = beeswax.views.make_beeswax_query(request, query_str)
-          try:
-            return beeswax.views.execute_directly(request, query_msg, design)
-          except BeeswaxException, ex:
-            error_message, log = beeswax.views.expand_exception(ex)
-      # Fall through if just adding a new column.
-    else:
-      if design.id is not None:
-        data = beeswax.report.design.ReportDesign.loads(design.data).get_query_dict()
-        mform.bind(data)
-        mform.saveform.set_data(design.name, design.desc)
-        report_gen_views.fixup_union(mform, subform_name='union', data=data, is_root=True)
-      else:
-        # New design
-        mform.bind()
-
-  return render('report_gen.mako', request, dict(
-    action=action,
-    design=design,
-    mform=mform,
-    error_message=error_message,
-    log=log,
-  ))

+ 2 - 0
apps/beeswax/src/beeswax/templates/index.mako

@@ -19,8 +19,10 @@ from django.utils.translation import ugettext as _
 %>
 %>
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="layout" file="layout.mako" />
+
 ${commonheader(_('Beeswax'), "beeswax", "100px")}
 ${commonheader(_('Beeswax'), "beeswax", "100px")}
 ${layout.menubar(section='tables')}
 ${layout.menubar(section='tables')}
+
 <div class="container-fluid">
 <div class="container-fluid">
     <div class="row-fluid">
     <div class="row-fluid">
         <div class="span3">
         <div class="span3">

+ 7 - 23
apps/beeswax/src/beeswax/templates/list_designs.mako

@@ -47,11 +47,7 @@ ${layout.menubar(section='saved queries')}
                 <tr>
                 <tr>
                 <td>
                 <td>
                     % if may_edit:
                     % if may_edit:
-                    % if design.type == models.SavedQuery.REPORT:
-                            <a href="${ url('beeswax.views.edit_report', design_id=design.id) }" data-row-selector="true">${design.name}</a>
-                    % else:
-                            <a href="${ url('beeswax.views.execute_query', design_id=design.id) }" data-row-selector="true">${design.name}</a>
-                    % endif
+                        <a href="${ url('beeswax.views.execute_query', design_id=design.id) }" data-row-selector="true">${design.name}</a>
                     % else:
                     % else:
                     ${design.name}
                     ${design.name}
                     % endif
                     % endif
@@ -63,11 +59,7 @@ ${layout.menubar(section='saved queries')}
                 </td>
                 </td>
                     <td>${design.owner.username}</td>
                     <td>${design.owner.username}</td>
                 <td>
                 <td>
-                    % if design.type == models.SavedQuery.REPORT:
-                        ${_('Report')}
-                    % else:
-                        ${_('Query')}
-                    % endif
+                    ${_('Query')}
                 </td>
                 </td>
                 <td data-sort-value="${time.mktime(design.mtime.timetuple())}">${ timesince(design.mtime) } ${_('ago')}</td>
                 <td data-sort-value="${time.mktime(design.mtime.timetuple())}">${ timesince(design.mtime) } ${_('ago')}</td>
                 <td>
                 <td>
@@ -76,23 +68,15 @@ ${layout.menubar(section='saved queries')}
                         Options
                         Options
                         <span class="caret"></span>
                         <span class="caret"></span>
                     </a>
                     </a>
-                <ul class="dropdown-menu">
-
-                % if may_edit:
-                    % if design.type == models.SavedQuery.REPORT:
-                            <li><a href="${ url('beeswax.views.edit_report', design_id=design.id) }" title="${_('Edit this report.')}" class="contextItem">${_('Edit')}</a></li>
-                    % else:
-                            <li><a href="${ url('beeswax.views.execute_query', design_id=design.id) }" title="${_('Edit this query.')}" class="contextItem">${_('Edit')}</a></li>
-                    % endif
+                    <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="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.list_query_history') }?design_id=${design.id}" title="${_('View the usage history of this query.')}" class="contextItem">${_('Usage History')}</a></li>
-
-                % endif
+                    % 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('beeswax.views.clone_design', design_id=design.id) }" title="${_('Copy this query.')}" class="contextItem">${_('Clone')}</a></li>
-                </ul>
+                    </ul>
                 </div>
                 </div>
-
-
                 </td>
                 </td>
                 </tr>
                 </tr>
             % endfor
             % endfor

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

@@ -26,11 +26,7 @@ ${layout.menubar(section='history')}
 <%def name="show_saved_query(design, history)">
 <%def name="show_saved_query(design, history)">
   % if design:
   % if design:
     % if request.user == design.owner:
     % if request.user == design.owner:
-      % if design.type == models.SavedQuery.REPORT:
-        <a href="${ url('beeswax.views.edit_report', design_id=design.id) }">
-      % else:
-        <a href="${ url('beeswax.views.execute_query', design_id=design.id) }">
-      % endif
+      <a href="${ url('beeswax.views.execute_query', design_id=design.id) }">
     % endif
     % endif
     % if design.is_auto:
     % if design.is_auto:
       [ ${_('Unsaved')} ]
       [ ${_('Unsaved')} ]

+ 8 - 21
apps/beeswax/src/beeswax/templates/my_queries.mako

@@ -67,11 +67,7 @@ ${layout.menubar(section='my queries')}
                       % for design in q_page.object_list:
                       % for design in q_page.object_list:
                         <tr>
                         <tr>
                           <td>
                           <td>
-                            % if design.type == models.SavedQuery.REPORT:
-                              <a href="${ url('beeswax.views.edit_report', design_id=design.id) }" data-row-selector="true">${design.name}</a>
-                            % else:
-                              <a href="${ url('beeswax.views.execute_query', design_id=design.id) }" data-row-selector="true">${design.name}</a>
-                            % endif
+                            <a href="${ url('beeswax.views.execute_query', design_id=design.id) }" data-row-selector="true">${design.name}</a>
                           </td>
                           </td>
                           <td>
                           <td>
                             % if design.desc:
                             % if design.desc:
@@ -79,28 +75,20 @@ ${layout.menubar(section='my queries')}
                             % endif
                             % endif
                           </td>
                           </td>
                           <td>
                           <td>
-                            % if design.type == models.SavedQuery.REPORT:
-                              ${_('Report')}
-                            % else:
-                              ${_('Query')}
-                            % endif
+                            ${_('Query')}
                           </td>
                           </td>
                           <td data-sort-value="${time.mktime(design.mtime.timetuple())}">${ timesince(design.mtime) } ${_('ago')}</td>
                           <td data-sort-value="${time.mktime(design.mtime.timetuple())}">${ timesince(design.mtime) } ${_('ago')}</td>
                           <td>
                           <td>
                             <div class="btn-group">
                             <div class="btn-group">
                                 <a href="#" data-toggle="dropdown" class="btn dropdown-toggle">
                                 <a href="#" data-toggle="dropdown" class="btn dropdown-toggle">
-                                    ${_('Options')}
-                                    <span class="caret"></span>
+                                  ${_('Options')}
+                                  <span class="caret"></span>
                                 </a>
                                 </a>
                                 <ul class="dropdown-menu">
                                 <ul class="dropdown-menu">
-                                      % if design.type == models.SavedQuery.REPORT:
-                                        <li><a href="${ url('beeswax.views.edit_report', design_id=design.id) }" title="${_('Edit this report.')}" class="contextItem">${_('Edit')}</a></li>
-                                      % else:
-                                        <li><a href="${ url('beeswax.views.execute_query', design_id=design.id) }" title="${_('Edit this query.')}" class="contextItem">${_('Edit')}</a></li>
-                                      % endif
-                                         <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('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>
                                 </ul>
                                 </ul>
                             </div>
                             </div>
                           </td>
                           </td>
@@ -139,7 +127,6 @@ ${layout.menubar(section='my queries')}
                     %>
                     %>
                     <tr>
                     <tr>
                       <td data-sort-value="${time.mktime(query.submission_date.timetuple())}">${query.submission_date.strftime("%x %X")}</td>
                       <td data-sort-value="${time.mktime(query.submission_date.timetuple())}">${query.submission_date.strftime("%x %X")}</td>
-                      ## TODO (bc): Only showing HQL (not REPORT)
                       <td><a href="${ url('beeswax.views.execute_query', design_id=design.id) }" data-row-selector="true">${design.name}</a></td>
                       <td><a href="${ url('beeswax.views.execute_query', design_id=design.id) }" data-row-selector="true">${design.name}</a></td>
                       <td>
                       <td>
                         <p>
                         <p>

+ 0 - 122
apps/beeswax/src/beeswax/templates/report_gen.mako

@@ -1,122 +0,0 @@
-## Licensed to Cloudera, Inc. under one
-## or more contributor license agreements.  See the NOTICE file
-## distributed with this work for additional information
-## regarding copyright ownership.  Cloudera, Inc. licenses this file
-## to you under the Apache License, Version 2.0 (the
-## "License"); you may not use this file except in compliance
-## with the License.  You may obtain a copy of the License at
-##
-##     http://www.apache.org/licenses/LICENSE-2.0
-##
-## Unless required by applicable law or agreed to in writing, software
-## distributed under the License is distributed on an "AS IS" BASIS,
-## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-## See the License for the specific language governing permissions and
-## limitations under the License.
-<%!
-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(_('Beeswax: Query Constructor'), "beeswax", "100px")}
-${layout.menubar(section='report generator')}
-<div class="container-fluid">
-<h1>${_('Report Generator')}</h1>
-% if design and not design.is_auto and design.name:
-<b>${_('Working on saved query:')} ${design.name}</b>
-% endif
-
-% if error_message:
-${_('Error:')} <b>${error_message}</b>
-% endif
-% if log:
-## The log should probably be in another tab
-<p><a href="#log">${_('View logs')}</a><p/>
-% endif
-
-<form action="${action}" method="POST">
-
-    ## columns management form
-    ${_('Add column:')}
-    ${unicode(mform.columns.management_form) | n}
-
-    ## colums formset errors
-    % for err in mform.columns.non_form_errors():
-      ${util.render_error(err)}
-    % endfor
-
-    ## columns
-    <table>
-    <tr>
-    % for form in mform.columns.forms:
-      <td valign="top" style="border-width:1px; border-style:solid; border-color:black">
-	${util.render_form(form)}
-      </td>
-    % endfor
-    </tr>
-    </table>
-
-    <br/>
-
-    ## conditions
-    <h2>${_('Conditions')}</h2>
-    <%def name="render_conds_formset(formset)">
-      % for form in formset.forms:
-	<table>
-	## formset level errors
-	% for err in form.non_field_errors():
-	  <tr><td colspan="5">
-	  ${util.render_error(err)}
-	  </td></tr>
-	% endfor
-	<tr>
-	% for field in form:
-	  <td>
-	  ${util.render_field(field)}
-	  </td>
-	% endfor
-      </tr></table>
-      % endfor
-      ${unicode(formset.management_form) | n }
-    </%def>
-
-    <%def name="render_union_mform(umform, level)">
-      <div style="margin-left:${level * 30}px;">
-	${unicode(umform.mgmt) | n}
-	${util.render_form(umform.bool)}
-	<div style="border-width:1px; border-style:solid; border-color:black">
-	${render_conds_formset(umform.conds)}
-	</div>
-	% for i in range(0, umform.mgmt.form_counts()):
-	  <%
-	    childname = 'sub%s' % (i,)
-	    childform = getattr(umform, childname)
-	  %>
-	  % if childform:
-	    ${render_union_mform(childform, level + 1)}
-	  % endif
-	% endfor
-      </div>
-    </%def>
-
-    ${render_union_mform(mform.union, 0)}
-
-    <hr/>
-    <input type="submit" name="button-submit" value="${_('Submit')}"/>
-    <input type="submit" name="button-advanced" value="${_('Advanced ...')}"/>
-    <br/>
-
-    ## design info
-    ${util.render_form(mform.saveform)}
-</form>
-
-% if log:
-<br/>
-<a name="log"><h3>${_('Server Log')}</h3></a>
-<pre>
-${log | h}
-</pre>
-% endif
-</div>
-${commonfooter(messages)}

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

@@ -569,7 +569,6 @@ for x in sys.stdin:
     do_view('user=test')
     do_view('user=test')
     do_view('text=whatever')
     do_view('text=whatever')
     do_view('type=hql')
     do_view('type=hql')
-    do_view('type=report')
     do_view('sort=date')
     do_view('sort=date')
     do_view('sort=-date')
     do_view('sort=-date')
     do_view('sort=name')
     do_view('sort=name')
@@ -1087,7 +1086,6 @@ def test_history_page():
   do_view('auto_query=0')
   do_view('auto_query=0')
   do_view('auto_query=1')
   do_view('auto_query=1')
   do_view('type=hql')
   do_view('type=hql')
-  do_view('type=report')
   do_view('sort=date')
   do_view('sort=date')
   do_view('sort=-date')
   do_view('sort=-date')
   do_view('sort=state')
   do_view('sort=state')

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

@@ -38,8 +38,6 @@ urlpatterns = patterns('beeswax',
   url(r'^delete_design/(?P<design_id>\d+)$', 'views.delete_design'),
   url(r'^delete_design/(?P<design_id>\d+)$', 'views.delete_design'),
   url(r'^clone_design/(?P<design_id>\d+)$', 'views.clone_design'),
   url(r'^clone_design/(?P<design_id>\d+)$', 'views.clone_design'),
   url(r'^query_history$', 'views.list_query_history'),
   url(r'^query_history$', 'views.list_query_history'),
-  url(r'^report_gen$', 'views.edit_report'),
-  url(r'^report_gen/(?P<design_id>\d+)$', 'views.edit_report'),
   url(r'^watch/(?P<id>\d+)$', 'views.watch_query'),
   url(r'^watch/(?P<id>\d+)$', 'views.watch_query'),
   url(r'^results/(?P<id>\d+)/(?P<first_row>\d+)$', 'views.view_results'),
   url(r'^results/(?P<id>\d+)/(?P<first_row>\d+)$', 'views.view_results'),
   url(r'^download/(?P<id>\d+)/(?P<format>\w+)$', 'views.download'),
   url(r'^download/(?P<id>\d+)/(?P<format>\w+)$', 'views.download'),

+ 3 - 14
apps/beeswax/src/beeswax/views.py

@@ -40,7 +40,6 @@ from hadoop.fs.exceptions import WebHdfsException
 
 
 import beeswax.forms
 import beeswax.forms
 import beeswax.design
 import beeswax.design
-import beeswax.report
 import beeswax.management.commands.beeswax_install_examples
 import beeswax.management.commands.beeswax_install_examples
 from beeswaxd import BeeswaxService
 from beeswaxd import BeeswaxService
 from beeswaxd.ttypes import QueryHandle, BeeswaxException, QueryNotFoundException
 from beeswaxd.ttypes import QueryHandle, BeeswaxException, QueryNotFoundException
@@ -488,12 +487,6 @@ def expand_exception(exc):
   return error_message, log
   return error_message, log
 
 
 
 
-def edit_report(request, design_id=None):
-  authorized_get_design(request, design_id)
-
-  return beeswax.report.edit_report(request, design_id)
-
-
 def save_design(request, form, type, design, explicit_save):
 def save_design(request, form, type, design, explicit_save):
   """
   """
   save_design(request, form, type, design, explicit_save) -> SavedQuery
   save_design(request, form, type, design, explicit_save) -> SavedQuery
@@ -512,8 +505,6 @@ def save_design(request, form, type, design, explicit_save):
 
 
   if type == models.SavedQuery.HQL:
   if type == models.SavedQuery.HQL:
     design_cls = beeswax.design.HQLdesign
     design_cls = beeswax.design.HQLdesign
-  elif type == models.SavedQuery.REPORT:
-    design_cls = beeswax.report.ReportDesign
   else:
   else:
     raise ValueError(_('Invalid design type %(type)s') % {'type': type})
     raise ValueError(_('Invalid design type %(type)s') % {'type': type})
 
 
@@ -555,7 +546,7 @@ def list_designs(request):
   We get here from /beeswax/list_designs?filterargs, with the options being:
   We get here from /beeswax/list_designs?filterargs, with the options being:
     page=<n>    - Controls pagination. Defaults to 1.
     page=<n>    - Controls pagination. Defaults to 1.
     user=<name> - Show design items belonging to a user. Default to all users.
     user=<name> - Show design items belonging to a user. Default to all users.
-    type=<type> - <type> is "report|hql", for saved query type. Default to show all.
+    type=<type> - <type> is "hql", for saved query type. Default to show all.
     sort=<key>  - Sort by the attribute <key>, which is one of:
     sort=<key>  - Sort by the attribute <key>, which is one of:
                     "date", "name", "desc", and "type" (design type)
                     "date", "name", "desc", and "type" (design type)
                   Accepts the form "-date", which sort in descending order.
                   Accepts the form "-date", which sort in descending order.
@@ -591,7 +582,7 @@ def _list_designs(querydict, page_size, prefix="", user=None):
   """
   """
   DEFAULT_SORT = ('-', 'date')                  # Descending date
   DEFAULT_SORT = ('-', 'date')                  # Descending date
 
 
-  VALID_TYPES = ('report', 'hql')               # Design types
+  VALID_TYPES = ('hql')               # Design types
   SORT_ATTR_TRANSLATION = dict(
   SORT_ATTR_TRANSLATION = dict(
     date='mtime',
     date='mtime',
     name='name',
     name='name',
@@ -1247,7 +1238,7 @@ def _list_query_history(user, querydict, page_size, prefix=""):
   """
   """
   DEFAULT_SORT = ('-', 'date')                  # Descending date
   DEFAULT_SORT = ('-', 'date')                  # Descending date
 
 
-  VALID_TYPES = ('report', 'hql')               # Design types
+  VALID_TYPES = ('hql')               # Design types
   SORT_ATTR_TRANSLATION = dict(
   SORT_ATTR_TRANSLATION = dict(
     date='submission_date',
     date='submission_date',
     state='last_state',
     state='last_state',
@@ -1282,8 +1273,6 @@ def _list_query_history(user, querydict, page_size, prefix=""):
     else:
     else:
       if d_type == 'hql':
       if d_type == 'hql':
         d_type = models.SavedQuery.HQL
         d_type = models.SavedQuery.HQL
-      else:
-        d_type = models.SavedQuery.REPORT
       db_queryset = db_queryset.filter(design__type=d_type)
       db_queryset = db_queryset.filter(design__type=d_type)
 
 
   # Ordering
   # Ordering