Переглянути джерело

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

Romain Rigaux 13 роки тому
батько
коміт
726d380b4e

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

@@ -39,7 +39,7 @@ def query_form():
 
 
 class SaveForm(forms.Form):
-  """Used for saving query design and report design."""
+  """Used for saving query design."""
   name = forms.CharField(required=False,
                         max_length=64,
                         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:
       * name: design name
       * desc: design description
-      * type: REPORT/HQL design type
+      * type: HQL design type
       * data: the json design data
 """
 
@@ -84,7 +84,7 @@ class Command(NoArgsCommand):
     try:
       user = self._install_user()
       self._install_tables(user)
-      self._install_reports(user)
+      self._install_queries(user)
       self._set_installed()
       LOG.info('Beeswax examples installed')
     except Exception, ex:
@@ -132,7 +132,7 @@ class Command(NoArgsCommand):
       table.install(django_user)
     LOG.info('Successfully created sample tables with data')
 
-  def _install_reports(self, django_user):
+  def _install_queries(self, django_user):
     """
     Install design designs.
     """

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

@@ -116,14 +116,14 @@ class QueryHistory(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'
   probably mean a SavedQuery.
   """
   DEFAULT_NEW_DESIGN_NAME = _('My saved query')
   AUTO_DESIGN_SUFFIX = _(' (new)')
-  TYPES = (HQL, REPORT) = range(2)
+  TYPES = (HQL, REPORT) = range(2) # REPORT is unused
 
   type = models.IntegerField(null=False)
   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="layout" file="layout.mako" />
+
 ${commonheader(_('Beeswax'), "beeswax", "100px")}
 ${layout.menubar(section='tables')}
+
 <div class="container-fluid">
     <div class="row-fluid">
         <div class="span3">

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

@@ -47,11 +47,7 @@ ${layout.menubar(section='saved queries')}
                 <tr>
                 <td>
                     % 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:
                     ${design.name}
                     % endif
@@ -63,11 +59,7 @@ ${layout.menubar(section='saved queries')}
                 </td>
                     <td>${design.owner.username}</td>
                 <td>
-                    % if design.type == models.SavedQuery.REPORT:
-                        ${_('Report')}
-                    % else:
-                        ${_('Query')}
-                    % endif
+                    ${_('Query')}
                 </td>
                 <td data-sort-value="${time.mktime(design.mtime.timetuple())}">${ timesince(design.mtime) } ${_('ago')}</td>
                 <td>
@@ -76,23 +68,15 @@ ${layout.menubar(section='saved queries')}
                         Options
                         <span class="caret"></span>
                     </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="${ 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>
-                </ul>
+                    </ul>
                 </div>
-
-
                 </td>
                 </tr>
             % 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)">
   % if design:
     % 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
     % if design.is_auto:
       [ ${_('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:
                         <tr>
                           <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>
                             % if design.desc:
@@ -79,28 +75,20 @@ ${layout.menubar(section='my queries')}
                             % endif
                           </td>
                           <td>
-                            % if design.type == models.SavedQuery.REPORT:
-                              ${_('Report')}
-                            % else:
-                              ${_('Query')}
-                            % endif
+                            ${_('Query')}
                           </td>
                           <td data-sort-value="${time.mktime(design.mtime.timetuple())}">${ timesince(design.mtime) } ${_('ago')}</td>
                           <td>
                             <div class="btn-group">
                                 <a href="#" data-toggle="dropdown" class="btn dropdown-toggle">
-                                    ${_('Options')}
-                                    <span class="caret"></span>
+                                  ${_('Options')}
+                                  <span class="caret"></span>
                                 </a>
                                 <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>
                             </div>
                           </td>
@@ -139,7 +127,6 @@ ${layout.menubar(section='my queries')}
                     %>
                     <tr>
                       <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>
                         <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('text=whatever')
     do_view('type=hql')
-    do_view('type=report')
     do_view('sort=date')
     do_view('sort=-date')
     do_view('sort=name')
@@ -1087,7 +1086,6 @@ def test_history_page():
   do_view('auto_query=0')
   do_view('auto_query=1')
   do_view('type=hql')
-  do_view('type=report')
   do_view('sort=date')
   do_view('sort=-date')
   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'^clone_design/(?P<design_id>\d+)$', 'views.clone_design'),
   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'^results/(?P<id>\d+)/(?P<first_row>\d+)$', 'views.view_results'),
   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.design
-import beeswax.report
 import beeswax.management.commands.beeswax_install_examples
 from beeswaxd import BeeswaxService
 from beeswaxd.ttypes import QueryHandle, BeeswaxException, QueryNotFoundException
@@ -488,12 +487,6 @@ def expand_exception(exc):
   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):
   """
   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:
     design_cls = beeswax.design.HQLdesign
-  elif type == models.SavedQuery.REPORT:
-    design_cls = beeswax.report.ReportDesign
   else:
     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:
     page=<n>    - Controls pagination. Defaults to 1.
     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:
                     "date", "name", "desc", and "type" (design type)
                   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
 
-  VALID_TYPES = ('report', 'hql')               # Design types
+  VALID_TYPES = ('hql')               # Design types
   SORT_ATTR_TRANSLATION = dict(
     date='mtime',
     name='name',
@@ -1247,7 +1238,7 @@ def _list_query_history(user, querydict, page_size, prefix=""):
   """
   DEFAULT_SORT = ('-', 'date')                  # Descending date
 
-  VALID_TYPES = ('report', 'hql')               # Design types
+  VALID_TYPES = ('hql')               # Design types
   SORT_ATTR_TRANSLATION = dict(
     date='submission_date',
     state='last_state',
@@ -1282,8 +1273,6 @@ def _list_query_history(user, querydict, page_size, prefix=""):
     else:
       if d_type == 'hql':
         d_type = models.SavedQuery.HQL
-      else:
-        d_type = models.SavedQuery.REPORT
       db_queryset = db_queryset.filter(design__type=d_type)
 
   # Ordering