def test_quoted_raw_search_anywhere(self): assert parse_search_query('"hello there" user.email:[email protected] "general kenobi"') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello there'), ), SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ), SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='general kenobi'), ), ] assert parse_search_query(' " hello " ') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value=' hello '), ), ] assert parse_search_query(' " he\\"llo " ') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value=' he"llo '), ), ]
def test_parse_search_query(self): # test with raw search query at the end assert parse_search_query('user.email:[email protected] release:1.2.1 hello') == [ SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ), SearchFilter( key=SearchKey(name='release'), operator="=", value=SearchValue(raw_value='1.2.1'), ), SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello'), ) ] # if the search query starts with the raw query, assume the whole thing is a raw string assert parse_search_query('hello user.email:[email protected] release:1.2.1') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello user.email:[email protected] release:1.2.1'), ), ]
def test_rel_time_filter(self): now = timezone.now() with freeze_time(now): assert parse_search_query('first_seen:+7d') == [ SearchFilter( key=SearchKey(name='first_seen'), operator="<=", value=SearchValue( raw_value=now - timedelta(days=7), ), ), ] assert parse_search_query('first_seen:-2w') == [ SearchFilter( key=SearchKey(name='first_seen'), operator=">=", value=SearchValue( raw_value=now - timedelta(days=14), ), ), ] assert parse_search_query('random:-2w') == [ SearchFilter( key=SearchKey(name='random'), operator="=", value=SearchValue('-2w'), ), ]
def test_multiple_quotes(self): assert parse_search_query('device.family:"" browser.name:"Chrome"') == [ SearchFilter( key=SearchKey(name='device.family'), operator='=', value=SearchValue(raw_value=''), ), SearchFilter( key=SearchKey(name='browser.name'), operator='=', value=SearchValue(raw_value='Chrome'), ), ] assert parse_search_query('device.family:"\\"" browser.name:"Chrome"') == [ SearchFilter( key=SearchKey(name='device.family'), operator='=', value=SearchValue(raw_value='"'), ), SearchFilter( key=SearchKey(name='browser.name'), operator='=', value=SearchValue(raw_value='Chrome'), ), ]
def test_simple(self): assert parse_search_query( 'user.email:[email protected] OR user.email:[email protected]' ) == [SearchBoolean(left_term=self.term1, operator="OR", right_term=self.term2)] assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected]' ) == [SearchBoolean(left_term=self.term1, operator="AND", right_term=self.term2)]
def test_parse_search_query_timestamp(self): # test date format assert parse_search_query('timestamp>2015-05-18') == [ SearchFilter( key=SearchKey(name='timestamp'), operator=">", value=SearchValue( raw_value=datetime.datetime( 2015, 5, 18, 0, 0, tzinfo=timezone.utc), ), ), ] # test date time format assert parse_search_query('timestamp>2015-05-18T10:15:01') == [ SearchFilter( key=SearchKey(name='timestamp'), operator=">", value=SearchValue( raw_value=datetime.datetime( 2015, 5, 18, 10, 15, 1, tzinfo=timezone.utc), ), ), ] # test date time format w microseconds assert parse_search_query('timestamp>2015-05-18T10:15:01.103') == [ SearchFilter( key=SearchKey(name='timestamp'), operator=">", value=SearchValue( raw_value=datetime.datetime( 2015, 5, 18, 10, 15, 1, 103000, tzinfo=timezone.utc), ), ), ]
def test_invalid_date_formats(self): invalid_queries = [ 'first_seen:hello', 'first_seen:123', 'first_seen:2018-01-01T00:01ZZ' ] for invalid_query in invalid_queries: with self.assertRaises( InvalidSearchQuery, expected_regex='Invalid format for numeric search', ): parse_search_query(invalid_query)
def test_quoted_val(self): assert parse_search_query('release:"a release"') == [ SearchFilter( key=SearchKey(name='release'), operator='=', value=SearchValue(raw_value='a release'), ), ] assert parse_search_query('!release:"a release"') == [ SearchFilter( key=SearchKey(name='release'), operator='!=', value=SearchValue('a release'), ), ]
def test_quoted_key(self): assert parse_search_query('"hi:there":value') == [ SearchFilter( key=SearchKey(name='hi:there'), operator='=', value=SearchValue(raw_value='value'), ), ] assert parse_search_query('!"hi:there":value') == [ SearchFilter( key=SearchKey(name='hi:there'), operator='!=', value=SearchValue(raw_value='value'), ), ]
def test_escaped_quotes(self): assert parse_search_query('release:"a\\"thing\\""') == [ SearchFilter( key=SearchKey(name='release'), operator='=', value=SearchValue(raw_value='a"thing"') ), ] assert parse_search_query('release:"a\\"\\"release"') == [ SearchFilter( key=SearchKey(name='release'), operator='=', value=SearchValue(raw_value='a""release') ), ]
def test_empty_filter_value(self): assert parse_search_query('device.family:""') == [ SearchFilter( key=SearchKey(name='device.family'), operator='=', value=SearchValue(raw_value=''), ), ]
def test_tab_within_quote(self): assert parse_search_query('release:"a\trelease"') == [ SearchFilter( key=SearchKey(name='release'), operator='=', value=SearchValue(raw_value='a\trelease') ), ]
def test_sooo_many_quotes(self): assert parse_search_query('device.family:"\\"\\"\\"\\"\\"\\"\\"\\"\\"\\""') == [ SearchFilter( key=SearchKey(name='device.family'), operator='=', value=SearchValue(raw_value='""""""""""'), ), ]
def test_numeric_filter(self): # Numeric format should still return a string if field isn't whitelisted assert parse_search_query('random_field:>500') == [ SearchFilter( key=SearchKey(name='random_field'), operator="=", value=SearchValue(raw_value='>500'), ), ]
def test_not_has_tag(self): # unquoted key assert parse_search_query('!has:release') == [ SearchFilter( key=SearchKey(name='release'), operator='=', value=SearchValue(''), ), ] # quoted key assert parse_search_query('!has:"hi:there"') == [ SearchFilter( key=SearchKey(name='hi:there'), operator='=', value=SearchValue(''), ), ]
def test_other_dates(self): # test date format with other name assert parse_search_query('first_seen>2015-05-18') == [ SearchFilter( key=SearchKey(name='first_seen'), operator=">", value=SearchValue( raw_value=datetime.datetime( 2015, 5, 18, 0, 0, tzinfo=timezone.utc, ), ), ), ] # test colon format assert parse_search_query('first_seen:>2015-05-18') == [ SearchFilter( key=SearchKey(name='first_seen'), operator=">", value=SearchValue( raw_value=datetime.datetime( 2015, 5, 18, 0, 0, tzinfo=timezone.utc, ), ), ), ] assert parse_search_query('random:>2015-05-18') == [ SearchFilter( key=SearchKey(name='random'), operator="=", value=SearchValue('>2015-05-18'), ), ]
def test_specific_time_filter(self): assert parse_search_query('first_seen:2018-01-01') == [ SearchFilter( key=SearchKey(name='first_seen'), operator=">=", value=SearchValue( raw_value=datetime.datetime(2018, 1, 1, tzinfo=timezone.utc), ), ), SearchFilter( key=SearchKey(name='first_seen'), operator="<", value=SearchValue( raw_value=datetime.datetime(2018, 1, 2, tzinfo=timezone.utc), ), ), ] assert parse_search_query('first_seen:2018-01-01T05:06:07') == [ SearchFilter( key=SearchKey(name='first_seen'), operator=">=", value=SearchValue( raw_value=datetime.datetime(2018, 1, 1, 5, 1, 7, tzinfo=timezone.utc), ), ), SearchFilter( key=SearchKey(name='first_seen'), operator="<", value=SearchValue( raw_value=datetime.datetime(2018, 1, 1, 5, 12, 7, tzinfo=timezone.utc), ), ), ] assert parse_search_query('random:2018-01-01T05:06:07') == [ SearchFilter( key=SearchKey(name='random'), operator="=", value=SearchValue(raw_value='2018-01-01T05:06:07'), ), ]
def test_has_tag(self): # unquoted key assert parse_search_query('has:release') == [ SearchFilter( key=SearchKey(name='release'), operator='!=', value=SearchValue(raw_value=''), ), ] # quoted key assert parse_search_query('has:"hi:there"') == [ SearchFilter( key=SearchKey(name='hi:there'), operator='!=', value=SearchValue(raw_value=''), ), ] # malformed key with self.assertRaises(InvalidSearchQuery): parse_search_query('has:"hi there"')
def test_custom_tag(self): assert parse_search_query('fruit:apple release:1.2.1') == [ SearchFilter( key=SearchKey(name='fruit'), operator='=', value=SearchValue(raw_value='apple'), ), SearchFilter( key=SearchKey(name='release'), operator='=', value=SearchValue(raw_value='1.2.1'), ), ]
def test_order_of_operations(self): assert parse_search_query( 'user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected]' ) == [SearchBoolean( left_term=self.term1, operator='OR', right_term=SearchBoolean( left_term=self.term2, operator='AND', right_term=self.term3 ) )] assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected]' ) == [SearchBoolean( left_term=SearchBoolean( left_term=self.term1, operator='AND', right_term=self.term2, ), operator='OR', right_term=self.term3 )]
def test_tab_outside_quote(self): # tab outside quote assert parse_search_query('release:a\trelease') == [ SearchFilter( key=SearchKey(name='release'), operator='=', value=SearchValue(raw_value='a'), ), SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='\trelease') ), ]
def get(self, request, organization): try: params = self.get_filter_params(request, organization) except OrganizationEventsError as e: return Response({"detail": six.text_type(e)}, status=400) except NoProjects: return Response([]) possible_keys = ["transaction"] lookup_keys = {key: request.query_params.get(key) for key in possible_keys} if not any(lookup_keys.values()): return Response( { "detail": "Must provide one of {} in order to find related events".format( possible_keys ) }, status=400, ) try: projects = self.get_projects(request, organization) query_kwargs = build_query_params_from_request( request, organization, projects, params.get("environment") ) query_kwargs["limit"] = 5 try: # Need to escape quotes in case some "joker" has a transaction with quotes transaction_name = UNESCAPED_QUOTE_RE.sub('\\"', lookup_keys["transaction"]) parsed_terms = parse_search_query('transaction:"{}"'.format(transaction_name)) except ParseError: return Response({"detail": "Invalid transaction search"}, status=400) if query_kwargs.get("search_filters"): query_kwargs["search_filters"].extend(parsed_terms) else: query_kwargs["search_filters"] = parsed_terms results = search.query(**query_kwargs) except discover.InvalidSearchQuery as err: raise ParseError(detail=six.text_type(err)) context = serialize( list(results), request.user, GroupSerializer(environment_func=self._get_environment_func(request, organization.id)), ) return Response(context)
def test_simple(self): # test with raw search query at the end assert parse_search_query('user.email:[email protected] release:1.2.1 hello') == [ SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ), SearchFilter( key=SearchKey(name='release'), operator="=", value=SearchValue(raw_value='1.2.1'), ), SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello'), ) ] assert parse_search_query('hello user.email:[email protected] release:1.2.1') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello'), ), SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ), SearchFilter( key=SearchKey(name='release'), operator="=", value=SearchValue(raw_value='1.2.1'), ), ]
def test_basic_fallthrough(self): # These should all fall through to basic equal searches, even though they # look like numeric, date, etc. queries = [ ('random:<hello', self._build_search_filter('random', '=', '<hello')), ('random:<512.1.0', self._build_search_filter('random', '=', '<512.1.0')), ('random:2018-01-01', self._build_search_filter('random', '=', '2018-01-01')), ('random:+7d', self._build_search_filter('random', '=', '+7d')), ('random:>2018-01-01', self._build_search_filter('random', '=', '>2018-01-01')), ('random:2018-01-01', self._build_search_filter('random', '=', '2018-01-01')), ('random:hello', self._build_search_filter('random', '=', 'hello')), ('random:123', self._build_search_filter('random', '=', '123')), ] for query, expected in queries: assert parse_search_query(query) == [expected]
def test_duration_op_breakdowns_filter(self): assert parse_search_query("spans.browser:1.5s") == [ SearchFilter( key=SearchKey(name="spans.browser"), operator="=", value=SearchValue(raw_value=1500), ) ] assert parse_search_query("spans.browser:>1.5s") == [ SearchFilter( key=SearchKey(name="spans.browser"), operator=">", value=SearchValue(raw_value=1500), ) ] assert parse_search_query("spans.browser:<1.5s") == [ SearchFilter( key=SearchKey(name="spans.browser"), operator="<", value=SearchValue(raw_value=1500), ) ]
def test_duration_aggregate_measurements_filter(self): assert parse_search_query("percentile(measurements.fp, 0.5):3.3s") == [ SearchFilter( key=SearchKey(name="percentile(measurements.fp, 0.5)"), operator="=", value=SearchValue(raw_value=3300), ) ] assert parse_search_query("percentile(measurements.fp, 0.5):>3.3s") == [ SearchFilter( key=SearchKey(name="percentile(measurements.fp, 0.5)"), operator=">", value=SearchValue(raw_value=3300), ) ] assert parse_search_query("percentile(measurements.fp, 0.5):<3.3s") == [ SearchFilter( key=SearchKey(name="percentile(measurements.fp, 0.5)"), operator="<", value=SearchValue(raw_value=3300), ) ]
def test_duration_aggregate_op_breakdowns_filter(self): assert parse_search_query("percentile(spans.browser, 0.5):3.3s") == [ SearchFilter( key=SearchKey(name="percentile(spans.browser, 0.5)"), operator="=", value=SearchValue(raw_value=3300), ) ] assert parse_search_query("percentile(spans.browser, 0.5):>3.3s") == [ SearchFilter( key=SearchKey(name="percentile(spans.browser, 0.5)"), operator=">", value=SearchValue(raw_value=3300), ) ] assert parse_search_query("percentile(spans.browser, 0.5):<3.3s") == [ SearchFilter( key=SearchKey(name="percentile(spans.browser, 0.5)"), operator="<", value=SearchValue(raw_value=3300), ) ]
def test_rel_time_filter(self): now = timezone.now() with freeze_time(now): assert parse_search_query('first_seen:+7d') == [ SearchFilter( key=SearchKey(name='first_seen'), operator="<=", value=SearchValue(raw_value=now - timedelta(days=7), ), ), ] assert parse_search_query('first_seen:-2w') == [ SearchFilter( key=SearchKey(name='first_seen'), operator=">=", value=SearchValue(raw_value=now - timedelta(days=14), ), ), ] assert parse_search_query('random:-2w') == [ SearchFilter( key=SearchKey(name='random'), operator="=", value=SearchValue('-2w'), ), ]
def test_duration_measurements_filter(self): assert parse_search_query("measurements.fp:1.5s") == [ SearchFilter( key=SearchKey(name="measurements.fp"), operator="=", value=SearchValue(raw_value=1500), ) ] assert parse_search_query("measurements.fp:>1.5s") == [ SearchFilter( key=SearchKey(name="measurements.fp"), operator=">", value=SearchValue(raw_value=1500), ) ] assert parse_search_query("measurements.fp:<1.5s") == [ SearchFilter( key=SearchKey(name="measurements.fp"), operator="<", value=SearchValue(raw_value=1500), ) ]
def test_empty_spaces_stripped_correctly(self): assert parse_search_query( "event.type:transaction transaction:/organizations/:orgId/discover/results/" ) == [ SearchFilter( key=SearchKey(name="event.type"), operator="=", value=SearchValue(raw_value="transaction"), ), SearchFilter( key=SearchKey(name="transaction"), operator="=", value=SearchValue(raw_value="/organizations/:orgId/discover/results/"), ), ]
def test_multiple_quotes(self): assert parse_search_query( 'device.family:"" browser.name:"Chrome"') == [ SearchFilter(key=SearchKey(name="device.family"), operator="=", value=SearchValue(raw_value="")), SearchFilter( key=SearchKey(name="browser.name"), operator="=", value=SearchValue(raw_value="Chrome"), ), ] assert parse_search_query( 'device.family:"\\"" browser.name:"Chrome"') == [ SearchFilter(key=SearchKey(name="device.family"), operator="=", value=SearchValue(raw_value='"')), SearchFilter( key=SearchKey(name="browser.name"), operator="=", value=SearchValue(raw_value="Chrome"), ), ]
def test_numeric_filter(self): # test numeric format assert parse_search_query('some_number:>500') == [ SearchFilter( key=SearchKey(name='some_number'), operator=">", value=SearchValue(raw_value=500), ), ] assert parse_search_query('some_number:<500') == [ SearchFilter( key=SearchKey(name='some_number'), operator="<", value=SearchValue(raw_value=500), ), ] # Non numeric shouldn't match assert parse_search_query('some_number:<hello') == [ SearchFilter( key=SearchKey(name='some_number'), operator="=", value=SearchValue(raw_value="<hello"), ), ]
def test_basic_fallthrough(self): # These should all fall through to basic equal searches, even though they # look like numeric, date, etc. queries = [ ("random:<hello", self._build_search_filter("random", "=", "<hello")), ("random:<512.1.0", self._build_search_filter("random", "=", "<512.1.0")), ("random:2018-01-01", self._build_search_filter("random", "=", "2018-01-01")), ("random:+7d", self._build_search_filter("random", "=", "+7d")), ("random:>2018-01-01", self._build_search_filter("random", "=", ">2018-01-01")), ("random:2018-01-01", self._build_search_filter("random", "=", "2018-01-01")), ("random:hello", self._build_search_filter("random", "=", "hello")), ("random:123", self._build_search_filter("random", "=", "123")), ] for query, expected in queries: assert parse_search_query(query) == [expected]
def test_key_remapping(self): config = SearchConfig(key_mappings={"target_value": ["someValue", "legacy-value"]}) assert parse_search_query( "someValue:123 legacy-value:456 normal_value:hello", config=config ) == [ SearchFilter( key=SearchKey(name="target_value"), operator="=", value=SearchValue("123") ), SearchFilter( key=SearchKey(name="target_value"), operator="=", value=SearchValue("456") ), SearchFilter( key=SearchKey(name="normal_value"), operator="=", value=SearchValue("hello") ), ]
def test_custom_explicit_tag(self): assert parse_search_query("tags[fruit]:apple release:1.2.1 tags[project_id]:123") == [ SearchFilter( key=SearchKey(name="tags[fruit]"), operator="=", value=SearchValue(raw_value="apple"), ), SearchFilter( key=SearchKey(name="release"), operator="=", value=SearchValue(raw_value="1.2.1") ), SearchFilter( key=SearchKey(name="tags[project_id]"), operator="=", value=SearchValue(raw_value="123"), ), ]
def test_timestamp_rollup(self): assert parse_search_query( "timestamp.to_hour:2018-01-01T05:06:07+00:00") == [ SearchFilter( key=SearchKey(name="timestamp.to_hour"), operator=">=", value=SearchValue(raw_value=datetime.datetime( 2018, 1, 1, 5, 1, 7, tzinfo=timezone.utc)), ), SearchFilter( key=SearchKey(name="timestamp.to_hour"), operator="<", value=SearchValue(raw_value=datetime.datetime( 2018, 1, 1, 5, 12, 7, tzinfo=timezone.utc)), ), ]
def test_malformed_groups(self): error_text = "Rule 'search' matched in its entirety, but it didn't consume all the text. The non-matching portion of the text begins with" with pytest.raises(IncompleteParseError) as error: parse_search_query( '(user.email:[email protected] OR user.email:[email protected]') assert six.text_type(error.value) == '%s %s' % ( error_text, "'(user.email:foo@exam' (line 1, column 1).") with pytest.raises(IncompleteParseError) as error: parse_search_query( '((user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected])' ) assert six.text_type(error.value) == '%s %s' % ( error_text, "'((user.email:foo@exa' (line 1, column 1).") with pytest.raises(IncompleteParseError) as error: parse_search_query( 'user.email:[email protected] OR user.email:[email protected])') assert six.text_type( error.value) == '%s %s' % (error_text, "')' (line 1, column 57).") with pytest.raises(IncompleteParseError) as error: parse_search_query( '(user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected]))' ) assert six.text_type( error.value) == '%s %s' % (error_text, "')' (line 1, column 91).")
def test_multiple_statements(self): assert parse_search_query( 'user.email:[email protected] OR user.email:[email protected] OR user.email:[email protected]' ) == [ SearchBoolean(left_term=self.term1, operator='OR', right_term=SearchBoolean(left_term=self.term2, operator='OR', right_term=self.term3)) ] assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected]' ) == [ SearchBoolean(left_term=self.term1, operator='AND', right_term=SearchBoolean(left_term=self.term2, operator='AND', right_term=self.term3)) ] # longer even number of terms assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected]' ) == [ SearchBoolean(left_term=SearchBoolean(left_term=self.term1, operator='AND', right_term=self.term2), operator='OR', right_term=SearchBoolean(left_term=self.term3, operator='AND', right_term=self.term4)) ] # longer odd number of terms assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected]' ) == [ SearchBoolean(left_term=SearchBoolean(left_term=self.term1, operator='AND', right_term=self.term2), operator='OR', right_term=SearchBoolean(left_term=self.term3, operator='AND', right_term=SearchBoolean( left_term=self.term4, operator='AND', right_term=self.term5))) ] # absurdly long assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected]' ) == [ SearchBoolean( left_term=SearchBoolean(left_term=self.term1, operator='AND', right_term=self.term2), operator='OR', right_term=SearchBoolean( left_term=SearchBoolean(left_term=self.term3, operator='AND', right_term=SearchBoolean( left_term=self.term4, operator='AND', right_term=self.term5)), operator='OR', right_term=SearchBoolean( left_term=SearchBoolean(left_term=self.term1, operator='AND', right_term=self.term2), operator='OR', right_term=SearchBoolean(left_term=self.term3, operator='AND', right_term=SearchBoolean( left_term=self.term4, operator='AND', right_term=self.term5))))) ]
def test_single_term(self): assert parse_search_query('user.email:[email protected]') == [self.term1]
def test_raw_search_anywhere(self): assert parse_search_query( 'hello what user.email:[email protected] where release:1.2.1 when' ) == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello what'), ), SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ), SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='where'), ), SearchFilter( key=SearchKey(name='release'), operator="=", value=SearchValue(raw_value='1.2.1'), ), SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='when'), ), ] assert parse_search_query('hello') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello'), ), ] assert parse_search_query(' hello ') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello'), ), ] assert parse_search_query(' hello there') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello there'), ), ] assert parse_search_query(' hello there:bye') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello'), ), SearchFilter( key=SearchKey(name='there'), operator='=', value=SearchValue(raw_value='bye'), ), ]
def test_empty_string(self): # Empty quotations become a dropped term assert parse_search_query('') == []
def get(self, request, organization): try: # events-meta is still used by events v1 which doesn't require global views params = self.get_snuba_params(request, organization, check_global_views=False) except NoProjects: return Response([]) with sentry_sdk.start_span(op="discover.endpoint", description="find_lookup_keys") as span: possible_keys = ["transaction"] lookup_keys = { key: request.query_params.get(key) for key in possible_keys } if not any(lookup_keys.values()): return Response( { "detail": f"Must provide one of {possible_keys} in order to find related events" }, status=400, ) with self.handle_query_errors(): with sentry_sdk.start_span(op="discover.endpoint", description="filter_creation"): projects = self.get_projects(request, organization) query_kwargs = build_query_params_from_request( request, organization, projects, params.get("environment")) query_kwargs["limit"] = 5 try: # Need to escape quotes in case some "joker" has a transaction with quotes transaction_name = UNESCAPED_QUOTE_RE.sub( '\\"', lookup_keys["transaction"]) parsed_terms = parse_search_query( f'transaction:"{transaction_name}"') except ParseError: return Response({"detail": "Invalid transaction search"}, status=400) if query_kwargs.get("search_filters"): query_kwargs["search_filters"].extend(parsed_terms) else: query_kwargs["search_filters"] = parsed_terms with sentry_sdk.start_span(op="discover.endpoint", description="issue_search"): results = search.query(**query_kwargs) with sentry_sdk.start_span(op="discover.endpoint", description="serialize_results") as span: results = list(results) span.set_data("result_length", len(results)) context = serialize( results, request.user, GroupSerializer(environment_func=self._get_environment_func( request, organization.id)), ) return Response(context)
def test_invalid_aggregate_column_with_duration_filter(self): with self.assertRaises(InvalidSearchQuery, regex="not a duration column"): parse_search_query("avg(stack.colno):>500s")
def test_quotes_filtered_on_raw(self): # Enclose the full raw query? Strip it. assert parse_search_query('thinger:unknown "what is this?"') == [ SearchFilter( key=SearchKey(name='thinger'), operator='=', value=SearchValue(raw_value='unknown'), ), SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='what is this?'), ), ] # Enclose the full query? Strip it and the whole query is raw. assert parse_search_query('"thinger:unknown what is this?"') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='thinger:unknown what is this?'), ), ] # Allow a single quotation at end assert parse_search_query('end"') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='end"'), ), ] # Allow a single quotation at beginning assert parse_search_query('"beginning') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='"beginning'), ), ] # Allow a single quotation assert parse_search_query('"') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='"'), ), ] # Empty quotations become a dropped term assert parse_search_query('""') == [] # Allow a search for space assert parse_search_query('" "') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value=' '), ), ] # Strip in a balanced manner assert parse_search_query('""woof"') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='woof"'), ), ] # Don't try this at home kids assert parse_search_query('"""""""""') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='"'), ), ]
def test_invalid_aggregate_duration_filter(self): with self.assertRaises(InvalidSearchQuery, expected_regex="not a valid duration value"): parse_search_query("avg(transaction.duration):>..500s")
def test_newline_outside_quote(self): with self.assertRaises(IncompleteParseError): parse_search_query('release:a\nrelease')
def test_raw_search_anywhere(self): assert parse_search_query('hello what user.email:[email protected] where release:1.2.1 when') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello what'), ), SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ), SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='where'), ), SearchFilter( key=SearchKey(name='release'), operator="=", value=SearchValue(raw_value='1.2.1'), ), SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='when'), ), ] assert parse_search_query('hello') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello'), ), ] assert parse_search_query(' hello ') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello'), ), ] assert parse_search_query(' hello there') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello there'), ), ] assert parse_search_query(' hello there:bye') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello'), ), SearchFilter( key=SearchKey(name='there'), operator='=', value=SearchValue(raw_value='bye'), ), ]
def test_is_query_unsupported(self): with self.assertRaises(InvalidSearchQuery): parse_search_query('is:unassigned')
def test_invalid_aggregate_percentage_filter(self): with self.assertRaises(InvalidSearchQuery, expected_regex="not a valid percentage value"): parse_search_query("percentage(transaction.duration, transaction.duration):>..500%")
def test_multiple_statements(self): assert parse_search_query( 'user.email:[email protected] OR user.email:[email protected] OR user.email:[email protected]' ) == [SearchBoolean( left_term=self.term1, operator='OR', right_term=SearchBoolean( left_term=self.term2, operator='OR', right_term=self.term3 ) )] assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected]' ) == [SearchBoolean( left_term=self.term1, operator='AND', right_term=SearchBoolean( left_term=self.term2, operator='AND', right_term=self.term3 ) )] term4 = SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ) # longer even number of terms assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected]' ) == [SearchBoolean( left_term=SearchBoolean( left_term=self.term1, operator='AND', right_term=self.term2 ), operator='OR', right_term=SearchBoolean( left_term=self.term3, operator='AND', right_term=term4 ) )] term5 = SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ) # longer odd number of terms assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected]' ) == [ SearchBoolean( left_term=SearchBoolean( left_term=self.term1, operator='AND', right_term=self.term2 ), operator='OR', right_term=SearchBoolean( left_term=self.term3, operator='AND', right_term=SearchBoolean( left_term=term4, operator='AND', right_term=term5 ) ) )] # absurdly long assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected]' ) == [SearchBoolean( left_term=SearchBoolean( left_term=self.term1, operator='AND', right_term=self.term2), operator='OR', right_term=SearchBoolean( left_term=SearchBoolean( left_term=self.term3, operator='AND', right_term=SearchBoolean( left_term=term4, operator='AND', right_term=term5)), operator='OR', right_term=SearchBoolean( left_term=SearchBoolean( left_term=self.term1, operator='AND', right_term=self.term2), operator='OR', right_term=SearchBoolean( left_term=self.term3, operator='AND', right_term=SearchBoolean( left_term=term4, operator='AND', right_term=term5 ) ) ) ) )]
def test_tab_within_quote(self): assert parse_search_query('release:"a\trelease"') == [ SearchFilter(key=SearchKey(name='release'), operator='=', value=SearchValue(raw_value='a\trelease')), ]
def test_quotes_filtered_on_raw(self): # Enclose the full raw query? Strip it. assert parse_search_query('thinger:unknown "what is this?"') == [ SearchFilter( key=SearchKey(name='thinger'), operator='=', value=SearchValue(raw_value='unknown'), ), SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='what is this?'), ), ] # Enclose the full query? Strip it and the whole query is raw. assert parse_search_query('"thinger:unknown what is this?"') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='thinger:unknown what is this?'), ), ] # Allow a single quotation at end assert parse_search_query('end"') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='end"'), ), ] # Allow a single quotation at beginning assert parse_search_query('"beginning') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='"beginning'), ), ] # Allow a single quotation assert parse_search_query('"') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='"'), ), ] # Empty quotations become a dropped term assert parse_search_query('""') == [] # Allow a search for space assert parse_search_query('" "') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value=' '), ), ] # Strip in a balanced manner assert parse_search_query('""woof"') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='"woof'), ), ] # Don't try this at home kids assert parse_search_query('"""""""""') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='"'), ), ]