diff --git a/src/sentry/api/event_search.py b/src/sentry/api/event_search.py index d9651cc8c39b6a..d4f6a72ac8da7d 100644 --- a/src/sentry/api/event_search.py +++ b/src/sentry/api/event_search.py @@ -484,7 +484,7 @@ def parse_search_query(query): raise InvalidSearchQuery( '%s %s' % ( u'Parse error: %r (column %d).' % (e.expr.name, e.column()), - 'This is commonly caused by unmatched-parentheses.', + 'This is commonly caused by unmatched-parentheses. Enclose any text in double quotes.', ) ) return SearchVisitor().visit(tree) diff --git a/src/sentry/api/issue_search.py b/src/sentry/api/issue_search.py index 26511cd9ad6038..7d076e19bf5be0 100644 --- a/src/sentry/api/issue_search.py +++ b/src/sentry/api/issue_search.py @@ -1,6 +1,7 @@ from __future__ import absolute_import from django.utils.functional import cached_property +from parsimonious.exceptions import IncompleteParseError from sentry.api.event_search import ( event_search_grammar, @@ -73,7 +74,15 @@ def visit_boolean_operator(self, node, children): def parse_search_query(query): - tree = event_search_grammar.parse(query) + try: + tree = event_search_grammar.parse(query) + except IncompleteParseError as e: + raise InvalidSearchQuery( + '%s %s' % ( + u'Parse error: %r (column %d).' % (e.expr.name, e.column()), + 'This is commonly caused by unmatched-parentheses. Enclose any text in double quotes.', + ) + ) return IssueSearchVisitor().visit(tree) diff --git a/tests/sentry/api/test_event_search.py b/tests/sentry/api/test_event_search.py index 1e8d1369c0e240..b41209c8da0296 100644 --- a/tests/sentry/api/test_event_search.py +++ b/tests/sentry/api/test_event_search.py @@ -923,25 +923,25 @@ def test_malformed_groups(self): '(user.email:foo@example.com OR user.email:bar@example.com' ) assert six.text_type( - error.value) == "Parse error: 'search' (column 1). This is commonly caused by unmatched-parentheses." + error.value) == "Parse error: 'search' (column 1). This is commonly caused by unmatched-parentheses. Enclose any text in double quotes." with pytest.raises(InvalidSearchQuery) as error: parse_search_query( '((user.email:foo@example.com OR user.email:bar@example.com AND user.email:bar@example.com)' ) assert six.text_type( - error.value) == "Parse error: 'search' (column 1). This is commonly caused by unmatched-parentheses." + error.value) == "Parse error: 'search' (column 1). This is commonly caused by unmatched-parentheses. Enclose any text in double quotes." with pytest.raises(InvalidSearchQuery) as error: parse_search_query( 'user.email:foo@example.com OR user.email:bar@example.com)' ) assert six.text_type( - error.value) == "Parse error: 'search' (column 57). This is commonly caused by unmatched-parentheses." + error.value) == "Parse error: 'search' (column 57). This is commonly caused by unmatched-parentheses. Enclose any text in double quotes." with pytest.raises(InvalidSearchQuery) as error: parse_search_query( '(user.email:foo@example.com OR user.email:bar@example.com AND user.email:bar@example.com))' ) assert six.text_type( - error.value) == "Parse error: 'search' (column 91). This is commonly caused by unmatched-parentheses." + error.value) == "Parse error: 'search' (column 91). This is commonly caused by unmatched-parentheses. Enclose any text in double quotes." def test_grouping_without_boolean_terms(self): with pytest.raises(InvalidSearchQuery) as error: @@ -954,7 +954,7 @@ def test_grouping_without_boolean_terms(self): raw_value='undefined is not an object (evaluating "function.name")'), )] assert six.text_type( - error.value) == "Parse error: 'search' (column 28). This is commonly caused by unmatched-parentheses." + error.value) == "Parse error: 'search' (column 28). This is commonly caused by unmatched-parentheses. Enclose any text in double quotes." class GetSnubaQueryArgsTest(TestCase): diff --git a/tests/snuba/api/endpoints/test_organization_events.py b/tests/snuba/api/endpoints/test_organization_events.py index a3dcb3f4734bb0..5bcbd9689b4321 100644 --- a/tests/snuba/api/endpoints/test_organization_events.py +++ b/tests/snuba/api/endpoints/test_organization_events.py @@ -155,7 +155,7 @@ def test_invalid_search_terms(self): response = self.client.get(url, {'query': 'hi \n there'}, format='json') assert response.status_code == 400, response.content - assert response.data['detail'] == "Parse error: 'search' (column 4). This is commonly caused by unmatched-parentheses." + assert response.data['detail'] == "Parse error: 'search' (column 4). This is commonly caused by unmatched-parentheses. Enclose any text in double quotes." def test_project_filtering(self): user = self.create_user(is_staff=False, is_superuser=False) diff --git a/tests/snuba/api/endpoints/test_organization_events_v2.py b/tests/snuba/api/endpoints/test_organization_events_v2.py index 4b9595e5aac31b..07c2de41f92f42 100644 --- a/tests/snuba/api/endpoints/test_organization_events_v2.py +++ b/tests/snuba/api/endpoints/test_organization_events_v2.py @@ -82,7 +82,7 @@ def test_invalid_search_terms(self): response = self.client.get(self.url, {'query': 'hi \n there'}, format='json') assert response.status_code == 400, response.content - assert response.data['detail'] == "Parse error: 'search' (column 4). This is commonly caused by unmatched-parentheses." + assert response.data['detail'] == "Parse error: 'search' (column 4). This is commonly caused by unmatched-parentheses. Enclose any text in double quotes." def test_raw_data(self): self.login_as(user=self.user)