diff --git a/caravel/data/energy.json.gz b/caravel/data/energy.json.gz index b2f47a148..624d71db6 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 c0752ff74..b8b550612 100644 --- a/caravel/models.py +++ b/caravel/models.py @@ -3,6 +3,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals +import re import functools import json @@ -54,6 +55,7 @@ from caravel.utils import flasher, MetricPermException, DimSelector config = app.config QueryResult = namedtuple('namedtuple', ['df', 'query', 'duration']) +FillterPattern = re.compile(r'''((?:[^,"']|"[^"]*"|'[^']*')+)''') class JavascriptPostAggregator(Postaggregator): @@ -839,7 +841,8 @@ class SqlaTable(Model, Queryable, AuditMixinNullable): 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 @@ class DruidDatasource(Model, AuditMixinNullable, Queryable): 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 d12b97c15..be87414c9 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 20899f0df..61f3d5d75 100755 --- a/caravel/viz.py +++ b/caravel/viz.py @@ -214,9 +214,12 @@ class BaseViz(object): 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 cefa2d852..1898dfcaa 100644 --- a/tests/core_tests.py +++ b/tests/core_tests.py @@ -308,6 +308,16 @@ class CoreTests(CaravelTestCase): 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/')