diff --git a/caravel/assets/javascripts/explore.js b/caravel/assets/javascripts/explore.js index c1fda16560fff..f9033d0532fee 100644 --- a/caravel/assets/javascripts/explore.js +++ b/caravel/assets/javascripts/explore.js @@ -294,9 +294,8 @@ function initExploreView() { function set_filters() { ["flt", "having"].forEach(function (prefix) { for (var i = 1; i < 10; i++) { - var eq = px.getParam(prefix + "_eq_" + i); var col = px.getParam(prefix + "_col_" + i); - if (eq !== '' && col !== '') { + if (col !== '') { add_filter(i, prefix); } } diff --git a/caravel/forms.py b/caravel/forms.py index eb050b1f36f62..6d77c821d3410 100644 --- a/caravel/forms.py +++ b/caravel/forms.py @@ -1009,12 +1009,14 @@ def add_to_form(attrs): field_css_classes['granularity'] = ['form-control', 'select2_freeform'] field_css_classes['druid_time_origin'] = ['form-control', 'select2_freeform'] filter_choices = self.choicify(['in', 'not in', 'regex']) - having_op_choices = self.choicify(['>', '<', '==']) + having_op_choices = self.choicify( + ['==', '!=', '>', '<', '>=', '<=']) filter_prefixes += ['having'] add_to_form(('since', 'until')) + # filter_cols defaults to ''. Filters with blank col will be ignored filter_cols = self.choicify( - viz.datasource.filterable_column_names or ['']) + ([''] + viz.datasource.filterable_column_names) or ['']) having_cols = filter_cols + viz.datasource.metrics_combo for field_prefix in filter_prefixes: is_having_filter = field_prefix == 'having' diff --git a/caravel/models.py b/caravel/models.py index e359044faddef..7c113f0d9abb8 100644 --- a/caravel/models.py +++ b/caravel/models.py @@ -28,7 +28,7 @@ from pydruid.client import PyDruid from pydruid.utils.filters import Dimension, Filter from pydruid.utils.postaggregator import Postaggregator -from pydruid.utils.having import Having, Aggregation +from pydruid.utils.having import Aggregation from six import string_types from sqlalchemy import ( Column, Integer, String, ForeignKey, Text, Boolean, DateTime, Date, @@ -1261,7 +1261,7 @@ def recursive_get_fields(_conf): if filters: qry['filter'] = filters - having_filters = self.get_having_filters(extras.get('having')) + having_filters = self.get_having_filters(extras.get('having_druid')) if having_filters: qry['having'] = having_filters @@ -1377,26 +1377,37 @@ def get_filters(raw_filters): filters = cond return filters + def _get_having_obj(self, col, op, eq): + cond = None + if op == '==': + if col in self.column_names: + cond = DimSelector(dimension=col, value=eq) + else: + cond = Aggregation(col) == eq + elif op == '>': + cond = Aggregation(col) > eq + elif op == '<': + cond = Aggregation(col) < eq + + return cond + def get_having_filters(self, raw_filters): filters = None + reversed_op_map = { + '!=': '==', + '>=': '<', + '<=': '>' + } + for col, op, eq in raw_filters: cond = None - if op == '==': - if col in self.column_names: - cond = DimSelector(dimension=col, value=eq) - else: - cond = Aggregation(col) == eq - elif op == '!=': - cond = ~(Aggregation(col) == eq) - elif op == '>': - cond = Aggregation(col) > eq - elif op == '<': - cond = Aggregation(col) < eq + if op in ['==', '>', '<']: + cond = self._get_having_obj(col, op, eq) + elif op in reversed_op_map: + cond = ~self._get_having_obj(col, reversed_op_map[op], eq) + if filters: - filters = Filter(type="and", fields=[ - Having.build_having(cond), - Having.build_having(filters) - ]) + filters = filters & cond else: filters = cond return filters diff --git a/caravel/templates/caravel/explore.html b/caravel/templates/caravel/explore.html index 7c63f83a7f98c..40c52fb2ffcbc 100644 --- a/caravel/templates/caravel/explore.html +++ b/caravel/templates/caravel/explore.html @@ -147,7 +147,7 @@ {{ _("Filters") }} + title="{{_("Filters are defined using comma delimited strings as in 'US,FR,Other'")}}. {{_("Leave the value field empty to filter empty strings or nulls")}}"> [-]