diff --git a/caravel/data/energy.json.gz b/caravel/data/energy.json.gz index b2f47a148b4f1..624d71db683a9 100644 Binary files a/caravel/data/energy.json.gz and b/caravel/data/energy.json.gz differ diff --git a/caravel/models.py b/caravel/models.py index c0752ff7442f2..b8b550612dd30 100644 --- a/caravel/models.py +++ b/caravel/models.py @@ -3,6 +3,7 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals +import re import functools import json @@ -54,6 +55,7 @@ config = app.config QueryResult = namedtuple('namedtuple', ['df', 'query', 'duration']) +FillterPattern = re.compile(r'''((?:[^,"']|"[^"]*"|'[^']*')+)''') class JavascriptPostAggregator(Postaggregator): @@ -839,7 +841,8 @@ def query( # sqla for col, op, eq in filter: col_obj = cols[col] if op in ('in', 'not in'): - values = eq.split(",") + splitted = FillterPattern.split(eq)[1::2] + values = [types.replace("'", '').strip() for types in splitted] cond = col_obj.sqla_col.in_(values) if op == 'not in': cond = ~cond @@ -1597,9 +1600,11 @@ def get_filters(raw_filters): cond = ~(Dimension(col) == eq) elif op in ('in', 'not in'): fields = [] - splitted = eq.split(',') - if len(splitted) > 1: - for s in eq.split(','): + # Distinguish quoted values with regular value types + splitted = FillterPattern.split(eq)[1::2] + values = [types.replace("'", '') for types in splitted] + if len(values) > 1: + for s in values: s = s.strip() fields.append(Dimension(col) == s) cond = Filter(type="or", fields=fields) diff --git a/caravel/templates/caravel/explore.html b/caravel/templates/caravel/explore.html index d12b97c153661..be87414c96f12 100644 --- a/caravel/templates/caravel/explore.html +++ b/caravel/templates/caravel/explore.html @@ -106,8 +106,11 @@
{{ _("Filters") }} + data-placement="right" + title="{{_("Filters are defined using comma delimited strings as in ")}}. + {{_("Leave the value field empty to filter empty strings or nulls")}}. + {{_("For filters with comma in values, wrap them in single quotes, + as in ")}}">
diff --git a/caravel/viz.py b/caravel/viz.py index 20899f0dfd3c6..61f3d5d75065f 100755 --- a/caravel/viz.py +++ b/caravel/viz.py @@ -214,9 +214,12 @@ def query_filters(self, is_having_filter=False): extra_filters = json.loads(extra_filters) for slice_filters in extra_filters.values(): for col, vals in slice_filters.items(): - if col and vals: - if col in self.datasource.filterable_column_names: - filters += [(col, 'in', ",".join(vals))] + if not (col and vals): + continue + elif col in self.datasource.filterable_column_names: + # Quote values with comma to avoid conflict + vals = ["'%s'" % x if "," in x else x for x in vals] + filters += [(col, 'in', ",".join(vals))] return filters def query_obj(self): diff --git a/tests/core_tests.py b/tests/core_tests.py index cefa2d852d57c..1898dfcaa2911 100644 --- a/tests/core_tests.py +++ b/tests/core_tests.py @@ -308,6 +308,16 @@ def test_filter_druid_datasource(self): assert 'datasource_for_gamma' in resp.data.decode('utf-8') assert 'datasource_not_for_gamma' not in resp.data.decode('utf-8') + def test_add_filter(self, username='admin'): + # navigate to energy_usage slice with "Electricity,heat" in filter values + data = ( + "/caravel/explore/table/1/?viz_type=table&groupby=source&metric=count&flt_col_1=source&flt_op_1=in&flt_eq_1=%27Electricity%2Cheat%27" + "&userid=1&datasource_name=energy_usage&datasource_id=1&datasource_type=tablerdo_save=saveas") + resp = self.client.get( + data, + follow_redirects=True) + assert ("source" in resp.data.decode('utf-8')) + def test_gamma(self): self.login(username='gamma') resp = self.client.get('/slicemodelview/list/')