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/')