class SnubaSearchTest(SnubaTestCase): def setUp(self): super(SnubaSearchTest, self).setUp() self.backend = SnubaSearchBackend() self.environments = {} base_datetime = (datetime.utcnow() - timedelta(days=7)).replace(tzinfo=pytz.utc) self.group1 = self.create_group( project=self.project, checksum='a' * 32, message='foo', times_seen=5, status=GroupStatus.UNRESOLVED, last_seen=base_datetime, first_seen=base_datetime - timedelta(days=31), score=ScoreClause.calculate( times_seen=5, last_seen=base_datetime, ), ) self.event1 = self.create_event( event_id='a' * 32, group=self.group1, datetime=base_datetime - timedelta(days=31), message='group1', stacktrace={ 'frames': [{ 'module': 'group1' }]}, tags={ 'server': 'example.com', 'environment': 'production', } ) self.event3 = self.create_event( event_id='c' * 32, group=self.group1, datetime=base_datetime, message='group1', stacktrace={ 'frames': [{ 'module': 'group1' }]}, tags={ 'server': 'example.com', 'environment': 'production', } ) self.group2 = self.create_group( project=self.project, checksum='b' * 32, message='bar', times_seen=10, status=GroupStatus.RESOLVED, last_seen=base_datetime - timedelta(days=30), first_seen=base_datetime - timedelta(days=30), score=ScoreClause.calculate( times_seen=10, last_seen=base_datetime - timedelta(days=30), ), ) self.event2 = self.create_event( event_id='b' * 32, group=self.group2, datetime=base_datetime - timedelta(days=30), message='group2', stacktrace={ 'frames': [{ 'module': 'group2' }]}, tags={ 'server': 'example.com', 'environment': 'staging', 'url': 'http://example.com', } ) GroupBookmark.objects.create( user=self.user, group=self.group2, project=self.group2.project, ) GroupAssignee.objects.create( user=self.user, group=self.group2, project=self.group2.project, ) GroupSubscription.objects.create( user=self.user, group=self.group1, project=self.group1.project, is_active=True, ) GroupSubscription.objects.create( user=self.user, group=self.group2, project=self.group2.project, is_active=False, ) def create_event(self, *args, **kwargs): event = super(SnubaSearchTest, self).create_event(*args, **kwargs) data = event.data.data tags = dict(data.get('tags', [])) if tags['environment'] not in self.environments: self.environments[tags['environment']] = Environment.get_or_create( event.project, tags['environment'], ) return event def test_query(self): results = self.backend.query(self.project, query='foo') assert set(results) == set([self.group1]) results = self.backend.query(self.project, query='bar') assert set(results) == set([self.group2]) def test_query_with_environment(self): results = self.backend.query( self.project, environment=self.environments['production'], query='foo') assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['production'], query='bar') assert set(results) == set([]) results = self.backend.query( self.project, environment=self.environments['staging'], query='bar') assert set(results) == set([self.group2]) def test_sort(self): results = self.backend.query(self.project, sort_by='date') assert list(results) == [self.group1, self.group2] results = self.backend.query(self.project, sort_by='new') assert list(results) == [self.group2, self.group1] results = self.backend.query(self.project, sort_by='freq') assert list(results) == [self.group1, self.group2] results = self.backend.query(self.project, sort_by='priority') assert list(results) == [self.group1, self.group2] def test_sort_with_environment(self): for dt in [ self.group1.first_seen + timedelta(days=1), self.group1.first_seen + timedelta(days=2), self.group1.last_seen + timedelta(days=1)]: self.create_event( group=self.group2, datetime=dt, message='group2', stacktrace={ 'frames': [{ 'module': 'group2' }]}, tags={'environment': 'production'} ) results = self.backend.query( self.project, environment=self.environments['production'], sort_by='date', ) assert list(results) == [self.group2, self.group1] results = self.backend.query( self.project, environment=self.environments['production'], sort_by='new', ) assert list(results) == [self.group2, self.group1] results = self.backend.query( self.project, environment=self.environments['production'], sort_by='freq', ) assert list(results) == [self.group2, self.group1] results = self.backend.query( self.project, environment=self.environments['production'], sort_by='priority', ) assert list(results) == [self.group2, self.group1] def test_status(self): results = self.backend.query(self.project, status=GroupStatus.UNRESOLVED) assert set(results) == set([self.group1]) results = self.backend.query(self.project, status=GroupStatus.RESOLVED) assert set(results) == set([self.group2]) def test_status_with_environment(self): results = self.backend.query( self.project, environment=self.environments['production'], status=GroupStatus.UNRESOLVED) assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['staging'], status=GroupStatus.RESOLVED) assert set(results) == set([self.group2]) results = self.backend.query( self.project, environment=self.environments['production'], status=GroupStatus.RESOLVED) assert set(results) == set([]) def test_tags(self): results = self.backend.query( self.project, tags={'environment': 'staging'}) assert set(results) == set([self.group2]) results = self.backend.query( self.project, tags={'environment': 'example.com'}) assert set(results) == set([]) results = self.backend.query( self.project, tags={'environment': ANY}) assert set(results) == set([self.group2, self.group1]) results = self.backend.query( self.project, tags={'environment': 'staging', 'server': 'example.com'}) assert set(results) == set([self.group2]) results = self.backend.query( self.project, tags={'environment': 'staging', 'server': ANY}) assert set(results) == set([self.group2]) results = self.backend.query( self.project, tags={'environment': 'staging', 'server': 'bar.example.com'}) assert set(results) == set([]) def test_tags_with_environment(self): results = self.backend.query( self.project, environment=self.environments['production'], tags={'server': 'example.com'}) assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['staging'], tags={'server': 'example.com'}) assert set(results) == set([self.group2]) results = self.backend.query( self.project, environment=self.environments['staging'], tags={'server': ANY}) assert set(results) == set([self.group2]) results = self.backend.query( self.project, environment=self.environments['production'], tags={'url': 'http://example.com'}) assert set(results) == set([]) results = self.backend.query( self.project, environment=self.environments['staging'], tags={'url': 'http://example.com'}) assert set(results) == set([self.group2]) results = self.backend.query( self.project, environment=self.environments['staging'], tags={'server': 'bar.example.com'}) assert set(results) == set([]) def test_bookmarked_by(self): results = self.backend.query(self.project, bookmarked_by=self.user) assert set(results) == set([self.group2]) def test_bookmarked_by_with_environment(self): results = self.backend.query( self.project, environment=self.environments['staging'], bookmarked_by=self.user) assert set(results) == set([self.group2]) results = self.backend.query( self.project, environment=self.environments['production'], bookmarked_by=self.user) assert set(results) == set([]) def test_project(self): results = self.backend.query(self.create_project(name='other')) assert set(results) == set([]) def test_pagination(self): results = self.backend.query(self.project, limit=1, sort_by='date') assert set(results) == set([self.group1]) results = self.backend.query(self.project, cursor=results.next, limit=1, sort_by='date') assert set(results) == set([self.group2]) results = self.backend.query(self.project, cursor=results.next, limit=1, sort_by='date') assert set(results) == set([]) def test_pagination_with_environment(self): for dt in [ self.group1.first_seen + timedelta(days=1), self.group1.first_seen + timedelta(days=2), self.group1.last_seen + timedelta(days=1)]: self.create_event( group=self.group2, datetime=dt, message='group2', stacktrace={ 'frames': [{ 'module': 'group2' }]}, tags={'environment': 'production'} ) results = self.backend.query( self.project, environment=self.environments['production'], sort_by='date', limit=1, count_hits=True, ) assert list(results) == [self.group2] assert results.hits == 2 results = self.backend.query( self.project, environment=self.environments['production'], sort_by='date', limit=1, cursor=results.next, count_hits=True, ) assert list(results) == [self.group1] assert results.hits == 2 results = self.backend.query( self.project, environment=self.environments['production'], sort_by='date', limit=1, cursor=results.next, count_hits=True, ) assert list(results) == [] assert results.hits == 2 def test_age_filter(self): results = self.backend.query( self.project, age_from=self.group2.first_seen, age_from_inclusive=True, ) assert set(results) == set([self.group2]) results = self.backend.query( self.project, age_to=self.group1.first_seen + timedelta(minutes=1), age_to_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, age_from=self.group1.first_seen, age_from_inclusive=True, age_to=self.group1.first_seen + timedelta(minutes=1), age_to_inclusive=True, ) assert set(results) == set([self.group1]) def test_age_filter_with_environment(self): results = self.backend.query( self.project, environment=self.environments['production'], age_from=self.group1.first_seen, age_from_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['production'], age_to=self.group1.first_seen, age_to_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['production'], age_from=self.group1.first_seen, age_from_inclusive=False, ) assert set(results) == set([]) self.create_event( group=self.group1, datetime=self.group1.first_seen + timedelta(days=1), message='group1', stacktrace={ 'frames': [{ 'module': 'group1' }]}, tags={ 'environment': 'development', } ) results = self.backend.query( self.project, environment=self.environments['production'], age_from=self.group1.first_seen, age_from_inclusive=False, ) assert set(results) == set([]) results = self.backend.query( self.project, environment=self.environments['development'], age_from=self.group1.first_seen, age_from_inclusive=False, ) assert set(results) == set([self.group1]) def test_times_seen_filter(self): results = self.backend.query( self.project, times_seen=2, ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, times_seen_lower=2, ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, times_seen_upper=1, ) assert set(results) == set([self.group2]) def test_last_seen_filter(self): results = self.backend.query( self.project, last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, last_seen_to=self.group2.last_seen + timedelta(minutes=1), last_seen_to_inclusive=True, ) assert set(results) == set([self.group2]) results = self.backend.query( self.project, last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, last_seen_to=self.group1.last_seen + timedelta(minutes=1), last_seen_to_inclusive=True, ) assert set(results) == set([self.group1]) def test_last_seen_filter_with_environment(self): results = self.backend.query( self.project, environment=self.environments['production'], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['production'], last_seen_to=self.group1.last_seen, last_seen_to_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['production'], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=False, ) assert set(results) == set([]) self.create_event( group=self.group1, datetime=self.group1.last_seen + timedelta(days=1), message='group1', stacktrace={ 'frames': [{ 'module': 'group1' }]}, tags={ 'environment': 'development', } ) results = self.backend.query( self.project, environment=self.environments['production'], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=False, ) assert set(results) == set([]) results = self.backend.query( self.project, environment=self.environments['development'], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=False, ) assert set(results) == set([self.group1]) def test_date_filter(self): results = self.backend.query( self.project, date_from=self.event2.datetime, ) assert set(results) == set([self.group1, self.group2]) results = self.backend.query( self.project, date_to=self.event1.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, date_from=self.event1.datetime, date_to=self.event2.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group1, self.group2]) @pytest.mark.xfail( not settings.SENTRY_TAGSTORE.startswith('sentry.tagstore.v2'), reason='unsupported on legacy backend due to insufficient index', ) def test_date_filter_with_environment(self): results = self.backend.query( self.project, environment=self.environments['production'], date_from=self.event2.datetime, ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['production'], date_to=self.event1.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['staging'], date_from=self.event1.datetime, date_to=self.event2.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group2]) def test_unassigned(self): results = self.backend.query(self.project, unassigned=True) assert set(results) == set([self.group1]) results = self.backend.query(self.project, unassigned=False) assert set(results) == set([self.group2]) def test_unassigned_with_environment(self): results = self.backend.query( self.project, environment=self.environments['production'], unassigned=True) assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['staging'], unassigned=False) assert set(results) == set([self.group2]) results = self.backend.query( self.project, environment=self.environments['production'], unassigned=False) assert set(results) == set([]) def test_assigned_to(self): results = self.backend.query(self.project, assigned_to=self.user) assert set(results) == set([self.group2]) # test team assignee ga = GroupAssignee.objects.get( user=self.user, group=self.group2, project=self.group2.project, ) ga.update(team=self.team, user=None) assert GroupAssignee.objects.get(id=ga.id).user is None results = self.backend.query(self.project, assigned_to=self.user) assert set(results) == set([self.group2]) # test when there should be no results other_user = self.create_user() results = self.backend.query(self.project, assigned_to=other_user) assert set(results) == set([]) owner = self.create_user() self.create_member( organization=self.project.organization, user=owner, role='owner', teams=[], ) # test that owners don't see results for all teams results = self.backend.query(self.project, assigned_to=owner) assert set(results) == set([]) def test_assigned_to_with_environment(self): results = self.backend.query( self.project, environment=self.environments['staging'], assigned_to=self.user) assert set(results) == set([self.group2]) results = self.backend.query( self.project, environment=self.environments['production'], assigned_to=self.user) assert set(results) == set([]) def test_subscribed_by(self): results = self.backend.query( self.group1.project, subscribed_by=self.user, ) assert set(results) == set([self.group1]) def test_subscribed_by_with_environment(self): results = self.backend.query( self.group1.project, environment=self.environments['production'], subscribed_by=self.user, ) assert set(results) == set([self.group1]) results = self.backend.query( self.group1.project, environment=self.environments['staging'], subscribed_by=self.user, ) assert set(results) == set([]) def test_parse_release_latest(self): with pytest.raises(Release.DoesNotExist): # no releases exist period environment = None result = get_latest_release(self.project, environment) old = Release.objects.create( organization_id=self.project.organization_id, version='old' ) old.add_project(self.project) new_date = old.date_added + timedelta(minutes=1) new = Release.objects.create( version='new-but-in-environment', organization_id=self.project.organization_id, date_released=new_date, ) new.add_project(self.project) ReleaseEnvironment.get_or_create( project=self.project, release=new, environment=self.environment, datetime=new_date, ) ReleaseProjectEnvironment.get_or_create( project=self.project, release=new, environment=self.environment, datetime=new_date, ) newest = Release.objects.create( version='newest-overall', organization_id=self.project.organization_id, date_released=old.date_added + timedelta(minutes=5), ) newest.add_project(self.project) # latest overall (no environment filter) environment = None result = get_latest_release(self.project, environment) assert result == newest.version # latest in environment environment = self.environment result = get_latest_release(self.project, environment) assert result == new.version with pytest.raises(Release.DoesNotExist): # environment with no releases environment = self.create_environment() result = get_latest_release(self.project, environment) assert result == new.version @mock.patch('sentry.utils.snuba.query') def test_optimized_aggregates(self, query_mock): query_mock.return_value = {} def Any(cls): class Any(object): def __eq__(self, other): return isinstance(other, cls) return Any() common_args = { 'start': Any(datetime), 'end': Any(datetime), 'filter_keys': { 'project_id': [self.project.id], 'primary_hash': [u'513772ee53011ad9f4dc374b2d34d0e9'] }, 'groupby': ['primary_hash'], 'conditions': [], 'limit': Any(int), } self.backend.query(self.project, query='foo') assert query_mock.call_args == mock.call( orderby='-last_seen', aggregations=[['max', 'timestamp', 'last_seen']], having=[], **common_args ) self.backend.query(self.project, query='foo', sort_by='date', last_seen_from=timezone.now()) assert query_mock.call_args == mock.call( orderby='-last_seen', aggregations=[['max', 'timestamp', 'last_seen']], having=[('last_seen', '>=', Any(int))], **common_args ) self.backend.query(self.project, query='foo', sort_by='priority') assert query_mock.call_args == mock.call( orderby='-priority', aggregations=[ ['toUInt32(log(times_seen) * 600) + toUInt32(last_seen)', '', 'priority'], ['count()', '', 'times_seen'], ['max', 'timestamp', 'last_seen'] ], having=[], **common_args ) self.backend.query(self.project, query='foo', sort_by='freq', times_seen=5) assert query_mock.call_args == mock.call( orderby='-times_seen', aggregations=[['count()', '', 'times_seen']], having=[('times_seen', '=', 5)], **common_args ) self.backend.query(self.project, query='foo', sort_by='new', age_from=timezone.now()) assert query_mock.call_args == mock.call( orderby='-first_seen', aggregations=[['min', 'timestamp', 'first_seen']], having=[('first_seen', '>=', Any(int))], **common_args )
class SnubaSearchTest(SnubaTestCase): def setUp(self): super(SnubaSearchTest, self).setUp() self.backend = SnubaSearchBackend() self.environments = {} self.base_datetime = (datetime.utcnow() - timedelta(days=7)).replace(tzinfo=pytz.utc) self.group1 = self.create_group( project=self.project, checksum='a' * 32, message='foo', times_seen=5, status=GroupStatus.UNRESOLVED, last_seen=self.base_datetime, first_seen=self.base_datetime - timedelta(days=31), ) self.event1 = self.create_event( event_id='a' * 32, group=self.group1, datetime=self.base_datetime - timedelta(days=31), message='group1', stacktrace={'frames': [{ 'module': 'group1' }]}, tags={ 'server': 'example.com', 'environment': 'production', }) self.event3 = self.create_event( event_id='c' * 32, group=self.group1, datetime=self.base_datetime, message='group1', stacktrace={'frames': [{ 'module': 'group1' }]}, tags={ 'server': 'example.com', 'environment': 'production', }) self.group2 = self.create_group( project=self.project, checksum='b' * 32, message='bar', times_seen=10, status=GroupStatus.RESOLVED, last_seen=self.base_datetime - timedelta(days=30), first_seen=self.base_datetime - timedelta(days=30), ) self.event2 = self.create_event( event_id='b' * 32, group=self.group2, datetime=self.base_datetime - timedelta(days=30), message='group2', stacktrace={'frames': [{ 'module': 'group2' }]}, tags={ 'server': 'example.com', 'environment': 'staging', 'url': 'http://example.com', }) GroupBookmark.objects.create( user=self.user, group=self.group2, project=self.group2.project, ) GroupAssignee.objects.create( user=self.user, group=self.group2, project=self.group2.project, ) GroupSubscription.objects.create( user=self.user, group=self.group1, project=self.group1.project, is_active=True, ) GroupSubscription.objects.create( user=self.user, group=self.group2, project=self.group2.project, is_active=False, ) def set_up_multi_project(self): self.project2 = self.create_project( organization=self.project.organization) self.group_p2 = self.create_group( project=self.project2, checksum='a' * 32, message='foo', times_seen=6, status=GroupStatus.UNRESOLVED, last_seen=self.base_datetime - timedelta(days=1), first_seen=self.base_datetime - timedelta(days=31), ) self.event_p2 = self.create_event( event_id='a' * 32, group=self.group_p2, datetime=self.base_datetime - timedelta(days=31), message='group1', stacktrace={'frames': [{ 'module': 'group_p2' }]}, tags={ 'server': 'example.com', 'environment': 'production', }) def create_event(self, *args, **kwargs): event = super(SnubaSearchTest, self).create_event(*args, **kwargs) data = event.data.data tags = dict(data.get('tags', [])) if tags['environment'] not in self.environments: self.environments[tags['environment']] = Environment.get_or_create( event.project, tags['environment'], ) return event def test_query(self): results = self.backend.query([self.project], query='foo') assert set(results) == set([self.group1]) results = self.backend.query([self.project], query='bar') assert set(results) == set([self.group2]) def test_query_multi_project(self): self.set_up_multi_project() results = self.backend.query([self.project, self.project2], query='foo') assert set(results) == set([self.group1, self.group_p2]) def test_query_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], query='foo') assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], query='bar') assert set(results) == set([]) results = self.backend.query( [self.project], environments=[self.environments['staging']], query='bar') assert set(results) == set([self.group2]) def test_multi_environments(self): self.set_up_multi_project() results = self.backend.query([self.project, self.project2], environments=[ self.environments['production'], self.environments['staging'] ]) assert set(results) == set([self.group1, self.group2, self.group_p2]) def test_query_with_environment_multi_project(self): self.set_up_multi_project() results = self.backend.query( [self.project, self.project2], environments=[self.environments['production']], query='foo') assert set(results) == set([self.group1, self.group_p2]) results = self.backend.query( [self.project, self.project2], environments=[self.environments['production']], query='bar') assert set(results) == set([]) def test_sort(self): results = self.backend.query([self.project], sort_by='date') assert list(results) == [self.group1, self.group2] results = self.backend.query([self.project], sort_by='new') assert list(results) == [self.group2, self.group1] results = self.backend.query([self.project], sort_by='freq') assert list(results) == [self.group1, self.group2] results = self.backend.query([self.project], sort_by='priority') assert list(results) == [self.group1, self.group2] def test_sort_multi_project(self): self.set_up_multi_project() results = self.backend.query([self.project, self.project2], sort_by='date') assert list(results) == [self.group1, self.group_p2, self.group2] results = self.backend.query([self.project, self.project2], sort_by='new') assert list(results) == [self.group2, self.group_p2, self.group1] results = self.backend.query([self.project, self.project2], sort_by='freq') assert list(results) == [self.group1, self.group_p2, self.group2] results = self.backend.query([self.project, self.project2], sort_by='priority') assert list(results) == [self.group1, self.group2, self.group_p2] def test_sort_with_environment(self): for dt in [ self.group1.first_seen + timedelta(days=1), self.group1.first_seen + timedelta(days=2), self.group1.last_seen + timedelta(days=1) ]: self.create_event(group=self.group2, datetime=dt, message='group2', stacktrace={'frames': [{ 'module': 'group2' }]}, tags={'environment': 'production'}) results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='date', ) assert list(results) == [self.group2, self.group1] results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='new', ) assert list(results) == [self.group2, self.group1] results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='freq', ) assert list(results) == [self.group2, self.group1] results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='priority', ) assert list(results) == [self.group2, self.group1] def test_status(self): results = self.backend.query([self.project], status=GroupStatus.UNRESOLVED) assert set(results) == set([self.group1]) results = self.backend.query([self.project], status=GroupStatus.RESOLVED) assert set(results) == set([self.group2]) def test_status_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], status=GroupStatus.UNRESOLVED) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['staging']], status=GroupStatus.RESOLVED) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['production']], status=GroupStatus.RESOLVED) assert set(results) == set([]) def test_tags(self): results = self.backend.query([self.project], tags={'environment': 'staging'}) assert set(results) == set([self.group2]) results = self.backend.query([self.project], tags={'environment': 'example.com'}) assert set(results) == set([]) results = self.backend.query([self.project], tags={'environment': ANY}) assert set(results) == set([self.group2, self.group1]) results = self.backend.query([self.project], tags={ 'environment': 'staging', 'server': 'example.com' }) assert set(results) == set([self.group2]) results = self.backend.query([self.project], tags={ 'environment': 'staging', 'server': ANY }) assert set(results) == set([self.group2]) results = self.backend.query([self.project], tags={ 'environment': 'staging', 'server': 'bar.example.com' }) assert set(results) == set([]) def test_tags_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], tags={'server': 'example.com'}) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['staging']], tags={'server': 'example.com'}) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['staging']], tags={'server': ANY}) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['production']], tags={'url': 'http://example.com'}) assert set(results) == set([]) results = self.backend.query( [self.project], environments=[self.environments['staging']], tags={'url': 'http://example.com'}) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['staging']], tags={'server': 'bar.example.com'}) assert set(results) == set([]) def test_bookmarked_by(self): results = self.backend.query([self.project], bookmarked_by=self.user) assert set(results) == set([self.group2]) def test_bookmarked_by_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['staging']], bookmarked_by=self.user) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['production']], bookmarked_by=self.user) assert set(results) == set([]) def test_project(self): results = self.backend.query([self.create_project(name='other')]) assert set(results) == set([]) def test_pagination(self): for options_set in [{ 'snuba.search.min-pre-snuba-candidates': None }, { 'snuba.search.min-pre-snuba-candidates': 500 }]: with self.options(options_set): results = self.backend.query([self.project], limit=1, sort_by='date') assert set(results) == set([self.group1]) assert not results.prev.has_results assert results.next.has_results results = self.backend.query([self.project], cursor=results.next, limit=1, sort_by='date') assert set(results) == set([self.group2]) assert results.prev.has_results assert not results.next.has_results # note: previous cursor results = self.backend.query([self.project], cursor=results.prev, limit=1, sort_by='date') assert set(results) == set([self.group1]) assert results.prev.has_results assert results.next.has_results # note: previous cursor, paging too far into 0 results results = self.backend.query([self.project], cursor=results.prev, limit=1, sort_by='date') assert set(results) == set([]) assert not results.prev.has_results assert results.next.has_results results = self.backend.query([self.project], cursor=results.next, limit=1, sort_by='date') assert set(results) == set([self.group1]) assert results.prev.has_results assert results.next.has_results results = self.backend.query([self.project], cursor=results.next, limit=1, sort_by='date') assert set(results) == set([self.group2]) assert results.prev.has_results assert not results.next.has_results results = self.backend.query([self.project], cursor=results.next, limit=1, sort_by='date') assert set(results) == set([]) assert results.prev.has_results assert not results.next.has_results def test_pagination_with_environment(self): for dt in [ self.group1.first_seen + timedelta(days=1), self.group1.first_seen + timedelta(days=2), self.group1.last_seen + timedelta(days=1) ]: self.create_event(group=self.group2, datetime=dt, message='group2', stacktrace={'frames': [{ 'module': 'group2' }]}, tags={'environment': 'production'}) results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='date', limit=1, count_hits=True, ) assert list(results) == [self.group2] assert results.hits == 2 results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='date', limit=1, cursor=results.next, count_hits=True, ) assert list(results) == [self.group1] assert results.hits == 1 # TODO this is actually wrong because of the cursor results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='date', limit=1, cursor=results.next, count_hits=True, ) assert list(results) == [] assert results.hits == 1 # TODO this is actually wrong because of the cursor def test_age_filter(self): results = self.backend.query( [self.project], age_from=self.group2.first_seen, age_from_inclusive=True, ) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], age_to=self.group1.first_seen + timedelta(minutes=1), age_to_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], age_from=self.group1.first_seen, age_from_inclusive=True, age_to=self.group1.first_seen + timedelta(minutes=1), age_to_inclusive=True, ) assert set(results) == set([self.group1]) def test_age_filter_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], age_from=self.group1.first_seen, age_from_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], age_to=self.group1.first_seen, age_to_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], age_from=self.group1.first_seen, age_from_inclusive=False, ) assert set(results) == set([]) self.create_event(group=self.group1, datetime=self.group1.first_seen + timedelta(days=1), message='group1', stacktrace={'frames': [{ 'module': 'group1' }]}, tags={ 'environment': 'development', }) results = self.backend.query( [self.project], environments=[self.environments['production']], age_from=self.group1.first_seen, age_from_inclusive=False, ) assert set(results) == set([]) results = self.backend.query( [self.project], environments=[self.environments['development']], age_from=self.group1.first_seen, age_from_inclusive=False, ) assert set(results) == set([self.group1]) def test_times_seen_filter(self): results = self.backend.query( [self.project], times_seen=2, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], times_seen_lower=2, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], times_seen_upper=1, ) assert set(results) == set([self.group2]) def test_last_seen_filter(self): results = self.backend.query( [self.project], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], last_seen_to=self.group2.last_seen + timedelta(minutes=1), last_seen_to_inclusive=True, ) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, last_seen_to=self.group1.last_seen + timedelta(minutes=1), last_seen_to_inclusive=True, ) assert set(results) == set([self.group1]) def test_last_seen_filter_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], last_seen_to=self.group1.last_seen, last_seen_to_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=False, ) assert set(results) == set([]) self.create_event(group=self.group1, datetime=self.group1.last_seen + timedelta(days=1), message='group1', stacktrace={'frames': [{ 'module': 'group1' }]}, tags={ 'environment': 'development', }) self.group1.update(last_seen=self.group1.last_seen + timedelta(days=1)) results = self.backend.query( [self.project], environments=[self.environments['production']], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=False, ) assert set(results) == set([]) results = self.backend.query( [self.project], date_to=self.group1.last_seen + timedelta(days=1), environments=[self.environments['development']], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=False, ) assert set(results) == set() results = self.backend.query( [self.project], date_to=self.group1.last_seen + timedelta(days=1), environments=[self.environments['development']], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, ) assert set(results) == set([self.group1]) def test_date_filter(self): results = self.backend.query( [self.project], date_from=self.event2.datetime, ) assert set(results) == set([self.group1, self.group2]) results = self.backend.query( [self.project], date_to=self.event1.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], date_from=self.event1.datetime, date_to=self.event2.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group1, self.group2]) @pytest.mark.xfail( not settings.SENTRY_TAGSTORE.startswith('sentry.tagstore.v2'), reason='unsupported on legacy backend due to insufficient index', ) def test_date_filter_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], date_from=self.event2.datetime, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], date_to=self.event1.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['staging']], date_from=self.event1.datetime, date_to=self.event2.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group2]) def test_unassigned(self): results = self.backend.query([self.project], unassigned=True) assert set(results) == set([self.group1]) results = self.backend.query([self.project], unassigned=False) assert set(results) == set([self.group2]) def test_unassigned_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], unassigned=True) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['staging']], unassigned=False) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['production']], unassigned=False) assert set(results) == set([]) def test_assigned_to(self): results = self.backend.query([self.project], assigned_to=self.user) assert set(results) == set([self.group2]) # test team assignee ga = GroupAssignee.objects.get( user=self.user, group=self.group2, project=self.group2.project, ) ga.update(team=self.team, user=None) assert GroupAssignee.objects.get(id=ga.id).user is None results = self.backend.query([self.project], assigned_to=self.user) assert set(results) == set([self.group2]) # test when there should be no results other_user = self.create_user() results = self.backend.query([self.project], assigned_to=other_user) assert set(results) == set([]) owner = self.create_user() self.create_member( organization=self.project.organization, user=owner, role='owner', teams=[], ) # test that owners don't see results for all teams results = self.backend.query([self.project], assigned_to=owner) assert set(results) == set([]) def test_assigned_to_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['staging']], assigned_to=self.user) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['production']], assigned_to=self.user) assert set(results) == set([]) def test_subscribed_by(self): results = self.backend.query( [self.group1.project], subscribed_by=self.user, ) assert set(results) == set([self.group1]) def test_subscribed_by_with_environment(self): results = self.backend.query( [self.group1.project], environments=[self.environments['production']], subscribed_by=self.user, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.group1.project], environments=[self.environments['staging']], subscribed_by=self.user, ) assert set(results) == set([]) def test_parse_release_latest(self): with pytest.raises(Release.DoesNotExist): # no releases exist period environment = None result = get_latest_release([self.project], environment) old = Release.objects.create( organization_id=self.project.organization_id, version='old') old.add_project(self.project) new_date = old.date_added + timedelta(minutes=1) new = Release.objects.create( version='new-but-in-environment', organization_id=self.project.organization_id, date_released=new_date, ) new.add_project(self.project) ReleaseEnvironment.get_or_create( project=self.project, release=new, environment=self.environment, datetime=new_date, ) ReleaseProjectEnvironment.get_or_create( project=self.project, release=new, environment=self.environment, datetime=new_date, ) newest = Release.objects.create( version='newest-overall', organization_id=self.project.organization_id, date_released=old.date_added + timedelta(minutes=5), ) newest.add_project(self.project) # latest overall (no environment filter) environment = None result = get_latest_release([self.project], environment) assert result == newest.version # latest in environment environment = self.environment result = get_latest_release([self.project], [environment]) assert result == new.version with pytest.raises(Release.DoesNotExist): # environment with no releases environment = self.create_environment() result = get_latest_release([self.project], [environment]) assert result == new.version @mock.patch('sentry.utils.snuba.raw_query') def test_snuba_not_called_optimization(self, query_mock): assert self.backend.query([self.project], query='foo').results == [self.group1] assert not query_mock.called assert self.backend.query([self.project], query='foo', sort_by='date', last_seen_from=timezone.now()).results == [] assert query_mock.called @mock.patch('sentry.utils.snuba.raw_query') def test_optimized_aggregates(self, query_mock): # TODO this test is annoyingly fragile and breaks in hard-to-see ways # any time anything about the snuba query changes query_mock.return_value = {'data': [], 'totals': {'total': 0}} def Any(cls): class Any(object): def __eq__(self, other): return isinstance(other, cls) return Any() DEFAULT_LIMIT = 100 chunk_growth = options.get('snuba.search.chunk-growth-rate') limit = int(DEFAULT_LIMIT * chunk_growth) common_args = { 'start': Any(datetime), 'end': Any(datetime), 'filter_keys': { 'project_id': [self.project.id], 'issue': [self.group1.id] }, 'referrer': 'search', 'groupby': ['issue'], 'conditions': [], 'selected_columns': [], 'limit': limit, 'offset': 0, 'totals': True, 'turbo': False, 'sample': 1, } self.backend.query([self.project], query='foo') assert not query_mock.called self.backend.query([self.project], query='foo', sort_by='date', last_seen_from=timezone.now()) assert query_mock.call_args == mock.call( orderby=['-last_seen', 'issue'], aggregations=[['uniq', 'issue', 'total'], ['toUInt64(max(timestamp)) * 1000', '', 'last_seen']], having=[('last_seen', '>=', Any(int))], **common_args) self.backend.query([self.project], query='foo', sort_by='priority') assert query_mock.call_args == mock.call( orderby=['-priority', 'issue'], aggregations=[[ '(toUInt64(log(times_seen) * 600)) + last_seen', '', 'priority' ], ['count()', '', 'times_seen'], ['uniq', 'issue', 'total'], ['toUInt64(max(timestamp)) * 1000', '', 'last_seen']], having=[], **common_args) self.backend.query([self.project], query='foo', sort_by='freq', times_seen=5) assert query_mock.call_args == mock.call( orderby=['-times_seen', 'issue'], aggregations=[ ['count()', '', 'times_seen'], ['uniq', 'issue', 'total'], ], having=[('times_seen', '=', 5)], **common_args) self.backend.query([self.project], query='foo', sort_by='new', age_from=timezone.now()) assert query_mock.call_args == mock.call( orderby=['-first_seen', 'issue'], aggregations=[ ['toUInt64(min(timestamp)) * 1000', '', 'first_seen'], ['uniq', 'issue', 'total'], ], having=[('first_seen', '>=', Any(int))], **common_args) def test_pre_and_post_filtering(self): prev_max_pre = options.get('snuba.search.max-pre-snuba-candidates') options.set('snuba.search.max-pre-snuba-candidates', 1) try: # normal queries work as expected results = self.backend.query([self.project], query='foo') assert set(results) == set([self.group1]) results = self.backend.query([self.project], query='bar') assert set(results) == set([self.group2]) # no candidate matches in Sentry, immediately return empty paginator results = self.backend.query([self.project], query='NO MATCHES IN SENTRY') assert set(results) == set() # too many candidates, skip pre-filter, requires >1 postfilter queries results = self.backend.query([self.project]) assert set(results) == set([self.group1, self.group2]) finally: options.set('snuba.search.max-pre-snuba-candidates', prev_max_pre) def test_optimizer_enabled(self): prev_optimizer_enabled = options.get( 'snuba.search.pre-snuba-candidates-optimizer') options.set('snuba.search.pre-snuba-candidates-optimizer', True) try: results = self.backend.query( [self.project], environments=[self.environments['production']], tags={'server': 'example.com'}) assert set(results) == set([self.group1]) finally: options.set('snuba.search.pre-snuba-candidates-optimizer', prev_optimizer_enabled) def test_search_out_of_range(self): results = self.backend.query( [self.project], date_from=datetime(2000, 1, 1, 0, 0, 0, tzinfo=pytz.utc), date_to=datetime(2000, 1, 1, 1, 0, 0, tzinfo=pytz.utc), ) assert set(results) == set([]) def test_hits_estimate(self): # 400 Groups/Events # Every 3rd one is Unresolved # Evey 2nd one has tag match=1 for i in range(400): group = self.create_group( project=self.project, checksum=md5('group {}'.format(i)).hexdigest(), message='group {}'.format(i), times_seen=5, status=GroupStatus.UNRESOLVED if i % 3 == 0 else GroupStatus.RESOLVED, last_seen=self.base_datetime, first_seen=self.base_datetime - timedelta(days=31), ) self.create_event( event_id=md5('event {}'.format(i)).hexdigest(), group=group, datetime=self.base_datetime - timedelta(days=31), message='group {} event'.format(i), stacktrace={'frames': [{ 'module': 'module {}'.format(i) }]}, tags={ 'match': '{}'.format(i % 2), 'environment': 'production', }) # Sample should estimate there are roughly 66 overall matching groups # based on a random sample of 100 (or $sample_size) of the total 200 # snuba matches, of which 33% should pass the postgres filter. with self.options({ # Too small to pass all django candidates down to snuba 'snuba.search.max-pre-snuba-candidates': 5, 'snuba.search.hits-sample-size': 50 }): first_results = self.backend.query( [self.project], status=GroupStatus.UNRESOLVED, tags={'match': '1'}, limit=10, count_hits=True, ) # Deliberately do not assert that the value is within some margin # of error, as this will fail tests at some rate corresponding to # our confidence interval. assert first_results.hits > 10 # When searching for the same tags, we should get the same set of # hits as the sampling is based on the hash of the query. second_results = self.backend.query( [self.project], status=GroupStatus.UNRESOLVED, tags={'match': '1'}, limit=10, count_hits=True, ) assert first_results.results == second_results.results # When using a different search, we should get a different sample # but still should have some hits. third_results = self.backend.query( [self.project], status=GroupStatus.UNRESOLVED, tags={'match': '0'}, limit=10, count_hits=True, ) assert third_results.hits > 10 assert third_results.results != second_results.results
class SnubaSearchTest(SnubaTestCase): def setUp(self): super(SnubaSearchTest, self).setUp() self.backend = SnubaSearchBackend() self.environments = {} self.base_datetime = (datetime.utcnow() - timedelta(days=7)).replace(tzinfo=pytz.utc) self.group1 = self.create_group( project=self.project, checksum='a' * 32, message='foo', times_seen=5, status=GroupStatus.UNRESOLVED, last_seen=self.base_datetime, first_seen=self.base_datetime - timedelta(days=31), ) self.event1 = self.create_event( event_id='a' * 32, group=self.group1, datetime=self.base_datetime - timedelta(days=31), message='group1', stacktrace={ 'frames': [{ 'module': 'group1' }]}, tags={ 'server': 'example.com', 'environment': 'production', } ) self.event3 = self.create_event( event_id='c' * 32, group=self.group1, datetime=self.base_datetime, message='group1', stacktrace={ 'frames': [{ 'module': 'group1' }]}, tags={ 'server': 'example.com', 'environment': 'production', } ) self.group2 = self.create_group( project=self.project, checksum='b' * 32, message='bar', times_seen=10, status=GroupStatus.RESOLVED, last_seen=self.base_datetime - timedelta(days=30), first_seen=self.base_datetime - timedelta(days=30), ) self.event2 = self.create_event( event_id='b' * 32, group=self.group2, datetime=self.base_datetime - timedelta(days=30), message='group2', stacktrace={ 'frames': [{ 'module': 'group2' }]}, tags={ 'server': 'example.com', 'environment': 'staging', 'url': 'http://example.com', } ) GroupBookmark.objects.create( user=self.user, group=self.group2, project=self.group2.project, ) GroupAssignee.objects.create( user=self.user, group=self.group2, project=self.group2.project, ) GroupSubscription.objects.create( user=self.user, group=self.group1, project=self.group1.project, is_active=True, ) GroupSubscription.objects.create( user=self.user, group=self.group2, project=self.group2.project, is_active=False, ) def set_up_multi_project(self): self.project2 = self.create_project(organization=self.project.organization) self.group_p2 = self.create_group( project=self.project2, checksum='a' * 32, message='foo', times_seen=6, status=GroupStatus.UNRESOLVED, last_seen=self.base_datetime - timedelta(days=1), first_seen=self.base_datetime - timedelta(days=31), ) self.event_p2 = self.create_event( event_id='a' * 32, group=self.group_p2, datetime=self.base_datetime - timedelta(days=31), message='group1', stacktrace={ 'frames': [{ 'module': 'group_p2' }]}, tags={ 'server': 'example.com', 'environment': 'production', } ) def create_event(self, *args, **kwargs): event = super(SnubaSearchTest, self).create_event(*args, **kwargs) data = event.data.data tags = dict(data.get('tags', [])) if tags['environment'] not in self.environments: self.environments[tags['environment']] = Environment.get_or_create( event.project, tags['environment'], ) return event def test_query(self): results = self.backend.query([self.project], query='foo') assert set(results) == set([self.group1]) results = self.backend.query([self.project], query='bar') assert set(results) == set([self.group2]) def test_query_multi_project(self): self.set_up_multi_project() results = self.backend.query([self.project, self.project2], query='foo') assert set(results) == set([self.group1, self.group_p2]) def test_query_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], query='foo') assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], query='bar') assert set(results) == set([]) results = self.backend.query( [self.project], environments=[self.environments['staging']], query='bar') assert set(results) == set([self.group2]) def test_multi_environments(self): self.set_up_multi_project() results = self.backend.query( [self.project, self.project2], environments=[ self.environments['production'], self.environments['staging'] ]) assert set(results) == set([self.group1, self.group2, self.group_p2]) def test_query_with_environment_multi_project(self): self.set_up_multi_project() results = self.backend.query( [self.project, self.project2], environments=[self.environments['production']], query='foo') assert set(results) == set([self.group1, self.group_p2]) results = self.backend.query( [self.project, self.project2], environments=[self.environments['production']], query='bar') assert set(results) == set([]) def test_sort(self): results = self.backend.query([self.project], sort_by='date') assert list(results) == [self.group1, self.group2] results = self.backend.query([self.project], sort_by='new') assert list(results) == [self.group2, self.group1] results = self.backend.query([self.project], sort_by='freq') assert list(results) == [self.group1, self.group2] results = self.backend.query([self.project], sort_by='priority') assert list(results) == [self.group1, self.group2] def test_sort_multi_project(self): self.set_up_multi_project() results = self.backend.query([self.project, self.project2], sort_by='date') assert list(results) == [self.group1, self.group_p2, self.group2] results = self.backend.query([self.project, self.project2], sort_by='new') assert list(results) == [self.group2, self.group_p2, self.group1] results = self.backend.query([self.project, self.project2], sort_by='freq') assert list(results) == [self.group1, self.group_p2, self.group2] results = self.backend.query([self.project, self.project2], sort_by='priority') assert list(results) == [self.group1, self.group2, self.group_p2] def test_sort_with_environment(self): for dt in [ self.group1.first_seen + timedelta(days=1), self.group1.first_seen + timedelta(days=2), self.group1.last_seen + timedelta(days=1)]: self.create_event( group=self.group2, datetime=dt, message='group2', stacktrace={ 'frames': [{ 'module': 'group2' }]}, tags={'environment': 'production'} ) results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='date', ) assert list(results) == [self.group2, self.group1] results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='new', ) assert list(results) == [self.group2, self.group1] results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='freq', ) assert list(results) == [self.group2, self.group1] results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='priority', ) assert list(results) == [self.group2, self.group1] def test_status(self): results = self.backend.query([self.project], status=GroupStatus.UNRESOLVED) assert set(results) == set([self.group1]) results = self.backend.query([self.project], status=GroupStatus.RESOLVED) assert set(results) == set([self.group2]) def test_status_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], status=GroupStatus.UNRESOLVED) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['staging']], status=GroupStatus.RESOLVED) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['production']], status=GroupStatus.RESOLVED) assert set(results) == set([]) def test_tags(self): results = self.backend.query( [self.project], tags={'environment': 'staging'}) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], tags={'environment': 'example.com'}) assert set(results) == set([]) results = self.backend.query( [self.project], tags={'environment': ANY}) assert set(results) == set([self.group2, self.group1]) results = self.backend.query( [self.project], tags={'environment': 'staging', 'server': 'example.com'}) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], tags={'environment': 'staging', 'server': ANY}) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], tags={'environment': 'staging', 'server': 'bar.example.com'}) assert set(results) == set([]) def test_tags_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], tags={'server': 'example.com'}) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['staging']], tags={'server': 'example.com'}) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['staging']], tags={'server': ANY}) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['production']], tags={'url': 'http://example.com'}) assert set(results) == set([]) results = self.backend.query( [self.project], environments=[self.environments['staging']], tags={'url': 'http://example.com'}) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['staging']], tags={'server': 'bar.example.com'}) assert set(results) == set([]) def test_bookmarked_by(self): results = self.backend.query([self.project], bookmarked_by=self.user) assert set(results) == set([self.group2]) def test_bookmarked_by_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['staging']], bookmarked_by=self.user) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['production']], bookmarked_by=self.user) assert set(results) == set([]) def test_project(self): results = self.backend.query([self.create_project(name='other')]) assert set(results) == set([]) def test_pagination(self): for options_set in [ {'snuba.search.min-pre-snuba-candidates': None}, {'snuba.search.min-pre-snuba-candidates': 500} ]: with self.options(options_set): results = self.backend.query([self.project], limit=1, sort_by='date') assert set(results) == set([self.group1]) assert not results.prev.has_results assert results.next.has_results results = self.backend.query( [self.project], cursor=results.next, limit=1, sort_by='date') assert set(results) == set([self.group2]) assert results.prev.has_results assert not results.next.has_results # note: previous cursor results = self.backend.query( [self.project], cursor=results.prev, limit=1, sort_by='date') assert set(results) == set([self.group1]) assert results.prev.has_results assert results.next.has_results # note: previous cursor, paging too far into 0 results results = self.backend.query( [self.project], cursor=results.prev, limit=1, sort_by='date') assert set(results) == set([]) assert not results.prev.has_results assert results.next.has_results results = self.backend.query( [self.project], cursor=results.next, limit=1, sort_by='date') assert set(results) == set([self.group1]) assert results.prev.has_results assert results.next.has_results results = self.backend.query( [self.project], cursor=results.next, limit=1, sort_by='date') assert set(results) == set([self.group2]) assert results.prev.has_results assert not results.next.has_results results = self.backend.query( [self.project], cursor=results.next, limit=1, sort_by='date') assert set(results) == set([]) assert results.prev.has_results assert not results.next.has_results def test_pagination_with_environment(self): for dt in [ self.group1.first_seen + timedelta(days=1), self.group1.first_seen + timedelta(days=2), self.group1.last_seen + timedelta(days=1)]: self.create_event( group=self.group2, datetime=dt, message='group2', stacktrace={ 'frames': [{ 'module': 'group2' }]}, tags={'environment': 'production'} ) results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='date', limit=1, count_hits=True, ) assert list(results) == [self.group2] assert results.hits is None # NOQA results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='date', limit=1, cursor=results.next, count_hits=True, ) assert list(results) == [self.group1] assert results.hits is None # NOQA results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='date', limit=1, cursor=results.next, count_hits=True, ) assert list(results) == [] assert results.hits is None # NOQA def test_age_filter(self): results = self.backend.query( [self.project], age_from=self.group2.first_seen, age_from_inclusive=True, ) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], age_to=self.group1.first_seen + timedelta(minutes=1), age_to_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], age_from=self.group1.first_seen, age_from_inclusive=True, age_to=self.group1.first_seen + timedelta(minutes=1), age_to_inclusive=True, ) assert set(results) == set([self.group1]) def test_age_filter_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], age_from=self.group1.first_seen, age_from_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], age_to=self.group1.first_seen, age_to_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], age_from=self.group1.first_seen, age_from_inclusive=False, ) assert set(results) == set([]) self.create_event( group=self.group1, datetime=self.group1.first_seen + timedelta(days=1), message='group1', stacktrace={ 'frames': [{ 'module': 'group1' }]}, tags={ 'environment': 'development', } ) results = self.backend.query( [self.project], environments=[self.environments['production']], age_from=self.group1.first_seen, age_from_inclusive=False, ) assert set(results) == set([]) results = self.backend.query( [self.project], environments=[self.environments['development']], age_from=self.group1.first_seen, age_from_inclusive=False, ) assert set(results) == set([self.group1]) def test_times_seen_filter(self): results = self.backend.query( [self.project], times_seen=2, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], times_seen_lower=2, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], times_seen_upper=1, ) assert set(results) == set([self.group2]) def test_last_seen_filter(self): results = self.backend.query( [self.project], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], last_seen_to=self.group2.last_seen + timedelta(minutes=1), last_seen_to_inclusive=True, ) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, last_seen_to=self.group1.last_seen + timedelta(minutes=1), last_seen_to_inclusive=True, ) assert set(results) == set([self.group1]) def test_last_seen_filter_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], last_seen_to=self.group1.last_seen, last_seen_to_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=False, ) assert set(results) == set([]) self.create_event( group=self.group1, datetime=self.group1.last_seen + timedelta(days=1), message='group1', stacktrace={ 'frames': [{ 'module': 'group1' }]}, tags={ 'environment': 'development', } ) self.group1.update(last_seen=self.group1.last_seen + timedelta(days=1)) results = self.backend.query( [self.project], environments=[self.environments['production']], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=False, ) assert set(results) == set([]) results = self.backend.query( [self.project], date_to=self.group1.last_seen + timedelta(days=1), environments=[self.environments['development']], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=False, ) assert set(results) == set() results = self.backend.query( [self.project], date_to=self.group1.last_seen + timedelta(days=1), environments=[self.environments['development']], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, ) assert set(results) == set([self.group1]) def test_date_filter(self): results = self.backend.query( [self.project], date_from=self.event2.datetime, ) assert set(results) == set([self.group1, self.group2]) results = self.backend.query( [self.project], date_to=self.event1.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], date_from=self.event1.datetime, date_to=self.event2.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group1, self.group2]) @pytest.mark.xfail( not settings.SENTRY_TAGSTORE.startswith('sentry.tagstore.v2'), reason='unsupported on legacy backend due to insufficient index', ) def test_date_filter_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], date_from=self.event2.datetime, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], date_to=self.event1.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['staging']], date_from=self.event1.datetime, date_to=self.event2.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group2]) def test_unassigned(self): results = self.backend.query([self.project], unassigned=True) assert set(results) == set([self.group1]) results = self.backend.query([self.project], unassigned=False) assert set(results) == set([self.group2]) def test_unassigned_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], unassigned=True) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['staging']], unassigned=False) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['production']], unassigned=False) assert set(results) == set([]) def test_assigned_to(self): results = self.backend.query([self.project], assigned_to=self.user) assert set(results) == set([self.group2]) # test team assignee ga = GroupAssignee.objects.get( user=self.user, group=self.group2, project=self.group2.project, ) ga.update(team=self.team, user=None) assert GroupAssignee.objects.get(id=ga.id).user is None results = self.backend.query([self.project], assigned_to=self.user) assert set(results) == set([self.group2]) # test when there should be no results other_user = self.create_user() results = self.backend.query([self.project], assigned_to=other_user) assert set(results) == set([]) owner = self.create_user() self.create_member( organization=self.project.organization, user=owner, role='owner', teams=[], ) # test that owners don't see results for all teams results = self.backend.query([self.project], assigned_to=owner) assert set(results) == set([]) def test_assigned_to_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['staging']], assigned_to=self.user) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['production']], assigned_to=self.user) assert set(results) == set([]) def test_subscribed_by(self): results = self.backend.query( [self.group1.project], subscribed_by=self.user, ) assert set(results) == set([self.group1]) def test_subscribed_by_with_environment(self): results = self.backend.query( [self.group1.project], environments=[self.environments['production']], subscribed_by=self.user, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.group1.project], environments=[self.environments['staging']], subscribed_by=self.user, ) assert set(results) == set([]) def test_parse_release_latest(self): with pytest.raises(Release.DoesNotExist): # no releases exist period environment = None result = get_latest_release([self.project], environment) old = Release.objects.create( organization_id=self.project.organization_id, version='old' ) old.add_project(self.project) new_date = old.date_added + timedelta(minutes=1) new = Release.objects.create( version='new-but-in-environment', organization_id=self.project.organization_id, date_released=new_date, ) new.add_project(self.project) ReleaseEnvironment.get_or_create( project=self.project, release=new, environment=self.environment, datetime=new_date, ) ReleaseProjectEnvironment.get_or_create( project=self.project, release=new, environment=self.environment, datetime=new_date, ) newest = Release.objects.create( version='newest-overall', organization_id=self.project.organization_id, date_released=old.date_added + timedelta(minutes=5), ) newest.add_project(self.project) # latest overall (no environment filter) environment = None result = get_latest_release([self.project], environment) assert result == newest.version # latest in environment environment = self.environment result = get_latest_release([self.project], [environment]) assert result == new.version with pytest.raises(Release.DoesNotExist): # environment with no releases environment = self.create_environment() result = get_latest_release([self.project], [environment]) assert result == new.version @mock.patch('sentry.utils.snuba.query') def test_snuba_not_called_optimization(self, query_mock): assert self.backend.query([self.project], query='foo').results == [self.group1] assert not query_mock.called assert self.backend.query( [self.project], query='foo', sort_by='date', last_seen_from=timezone.now() ).results == [] assert query_mock.called @mock.patch('sentry.utils.snuba.query') def test_optimized_aggregates(self, query_mock): query_mock.return_value = {} def Any(cls): class Any(object): def __eq__(self, other): return isinstance(other, cls) return Any() DEFAULT_LIMIT = 100 chunk_growth = options.get('snuba.search.chunk-growth-rate') limit = (DEFAULT_LIMIT * chunk_growth) + 1 common_args = { 'start': Any(datetime), 'end': Any(datetime), 'filter_keys': { 'project_id': [self.project.id], 'issue': [self.group1.id] }, 'referrer': 'search', 'groupby': ['issue'], 'conditions': [], 'limit': limit, 'offset': 0, } self.backend.query([self.project], query='foo') assert not query_mock.called self.backend.query([self.project], query='foo', sort_by='date', last_seen_from=timezone.now()) assert query_mock.call_args == mock.call( orderby=['-last_seen', 'issue'], aggregations=[['toUInt64(max(timestamp)) * 1000', '', 'last_seen']], having=[('last_seen', '>=', Any(int))], **common_args ) self.backend.query([self.project], query='foo', sort_by='priority') assert query_mock.call_args == mock.call( orderby=['-priority', 'issue'], aggregations=[ ['(toUInt64(log(times_seen) * 600)) + last_seen', '', 'priority'], ['count()', '', 'times_seen'], ['toUInt64(max(timestamp)) * 1000', '', 'last_seen'] ], having=[], **common_args ) self.backend.query([self.project], query='foo', sort_by='freq', times_seen=5) assert query_mock.call_args == mock.call( orderby=['-times_seen', 'issue'], aggregations=[['count()', '', 'times_seen']], having=[('times_seen', '=', 5)], **common_args ) self.backend.query([self.project], query='foo', sort_by='new', age_from=timezone.now()) assert query_mock.call_args == mock.call( orderby=['-first_seen', 'issue'], aggregations=[['toUInt64(min(timestamp)) * 1000', '', 'first_seen']], having=[('first_seen', '>=', Any(int))], **common_args ) def test_pre_and_post_filtering(self): prev_max_pre = options.get('snuba.search.max-pre-snuba-candidates') options.set('snuba.search.max-pre-snuba-candidates', 1) try: # normal queries work as expected results = self.backend.query([self.project], query='foo') assert set(results) == set([self.group1]) results = self.backend.query([self.project], query='bar') assert set(results) == set([self.group2]) # no candidate matches in Sentry, immediately return empty paginator results = self.backend.query([self.project], query='NO MATCHES IN SENTRY') assert set(results) == set() # too many candidates, skip pre-filter, requires >1 postfilter queries results = self.backend.query([self.project]) assert set(results) == set([self.group1, self.group2]) finally: options.set('snuba.search.max-pre-snuba-candidates', prev_max_pre) def test_optimizer_enabled(self): prev_optimizer_enabled = options.get('snuba.search.pre-snuba-candidates-optimizer') options.set('snuba.search.pre-snuba-candidates-optimizer', True) try: results = self.backend.query( [self.project], environments=[self.environments['production']], tags={'server': 'example.com'}) assert set(results) == set([self.group1]) finally: options.set('snuba.search.pre-snuba-candidates-optimizer', prev_optimizer_enabled) def test_search_out_of_range(self): results = self.backend.query( [self.project], date_from=datetime(2000, 1, 1, 0, 0, 0, tzinfo=pytz.utc), date_to=datetime(2000, 1, 1, 1, 0, 0, tzinfo=pytz.utc), ) assert set(results) == set([])
class SnubaSearchTest(SnubaTestCase): def setUp(self): super(SnubaSearchTest, self).setUp() self.backend = SnubaSearchBackend() self.environments = {} self.base_datetime = (datetime.utcnow() - timedelta(days=7)).replace(tzinfo=pytz.utc) self.group1 = self.create_group( project=self.project, checksum='a' * 32, message='foo', times_seen=5, status=GroupStatus.UNRESOLVED, last_seen=self.base_datetime, first_seen=self.base_datetime - timedelta(days=31), ) self.event1 = self.create_event( event_id='a' * 32, group=self.group1, datetime=self.base_datetime - timedelta(days=31), message='group1', stacktrace={'frames': [{ 'module': 'group1' }]}, tags={ 'server': 'example.com', 'environment': 'production', }) self.event3 = self.create_event( event_id='c' * 32, group=self.group1, datetime=self.base_datetime, message='group1', stacktrace={'frames': [{ 'module': 'group1' }]}, tags={ 'server': 'example.com', 'environment': 'production', }) self.group2 = self.create_group( project=self.project, checksum='b' * 32, message='bar', times_seen=10, status=GroupStatus.RESOLVED, last_seen=self.base_datetime - timedelta(days=30), first_seen=self.base_datetime - timedelta(days=30), ) self.event2 = self.create_event( event_id='b' * 32, group=self.group2, datetime=self.base_datetime - timedelta(days=30), message='group2', stacktrace={'frames': [{ 'module': 'group2' }]}, tags={ 'server': 'example.com', 'environment': 'staging', 'url': 'http://example.com', }) GroupBookmark.objects.create( user=self.user, group=self.group2, project=self.group2.project, ) GroupAssignee.objects.create( user=self.user, group=self.group2, project=self.group2.project, ) GroupSubscription.objects.create( user=self.user, group=self.group1, project=self.group1.project, is_active=True, ) GroupSubscription.objects.create( user=self.user, group=self.group2, project=self.group2.project, is_active=False, ) def set_up_multi_project(self): self.project2 = self.create_project( organization=self.project.organization) self.group_p2 = self.create_group( project=self.project2, checksum='a' * 32, message='foo', times_seen=6, status=GroupStatus.UNRESOLVED, last_seen=self.base_datetime - timedelta(days=1), first_seen=self.base_datetime - timedelta(days=31), ) self.event_p2 = self.create_event( event_id='a' * 32, group=self.group_p2, datetime=self.base_datetime - timedelta(days=31), message='group1', stacktrace={'frames': [{ 'module': 'group_p2' }]}, tags={ 'server': 'example.com', 'environment': 'production', }) def create_event(self, *args, **kwargs): event = super(SnubaSearchTest, self).create_event(*args, **kwargs) data = event.data.data tags = dict(data.get('tags', [])) if tags['environment'] not in self.environments: self.environments[tags['environment']] = Environment.get_or_create( event.project, tags['environment'], ) return event def test_query(self): results = self.backend.query([self.project], query='foo') assert set(results) == set([self.group1]) results = self.backend.query([self.project], query='bar') assert set(results) == set([self.group2]) def test_query_multi_project(self): self.set_up_multi_project() results = self.backend.query([self.project, self.project2], query='foo') assert set(results) == set([self.group1, self.group_p2]) def test_query_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], query='foo') assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], query='bar') assert set(results) == set([]) results = self.backend.query( [self.project], environments=[self.environments['staging']], query='bar') assert set(results) == set([self.group2]) def test_multi_environments(self): self.set_up_multi_project() results = self.backend.query([self.project, self.project2], environments=[ self.environments['production'], self.environments['staging'] ]) assert set(results) == set([self.group1, self.group2, self.group_p2]) def test_query_with_environment_multi_project(self): self.set_up_multi_project() results = self.backend.query( [self.project, self.project2], environments=[self.environments['production']], query='foo') assert set(results) == set([self.group1, self.group_p2]) results = self.backend.query( [self.project, self.project2], environments=[self.environments['production']], query='bar') assert set(results) == set([]) def test_sort(self): results = self.backend.query([self.project], sort_by='date') assert list(results) == [self.group1, self.group2] results = self.backend.query([self.project], sort_by='new') assert list(results) == [self.group2, self.group1] results = self.backend.query([self.project], sort_by='freq') assert list(results) == [self.group1, self.group2] results = self.backend.query([self.project], sort_by='priority') assert list(results) == [self.group1, self.group2] def test_sort_multi_project(self): self.set_up_multi_project() results = self.backend.query([self.project, self.project2], sort_by='date') assert list(results) == [self.group1, self.group_p2, self.group2] results = self.backend.query([self.project, self.project2], sort_by='new') assert list(results) == [self.group2, self.group_p2, self.group1] results = self.backend.query([self.project, self.project2], sort_by='freq') assert list(results) == [self.group1, self.group_p2, self.group2] results = self.backend.query([self.project, self.project2], sort_by='priority') assert list(results) == [self.group1, self.group2, self.group_p2] def test_sort_with_environment(self): for dt in [ self.group1.first_seen + timedelta(days=1), self.group1.first_seen + timedelta(days=2), self.group1.last_seen + timedelta(days=1) ]: self.create_event(group=self.group2, datetime=dt, message='group2', stacktrace={'frames': [{ 'module': 'group2' }]}, tags={'environment': 'production'}) results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='date', ) assert list(results) == [self.group2, self.group1] results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='new', ) assert list(results) == [self.group2, self.group1] results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='freq', ) assert list(results) == [self.group2, self.group1] results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='priority', ) assert list(results) == [self.group2, self.group1] def test_status(self): results = self.backend.query([self.project], status=GroupStatus.UNRESOLVED) assert set(results) == set([self.group1]) results = self.backend.query([self.project], status=GroupStatus.RESOLVED) assert set(results) == set([self.group2]) def test_status_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], status=GroupStatus.UNRESOLVED) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['staging']], status=GroupStatus.RESOLVED) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['production']], status=GroupStatus.RESOLVED) assert set(results) == set([]) def test_tags(self): results = self.backend.query([self.project], tags={'environment': 'staging'}) assert set(results) == set([self.group2]) results = self.backend.query([self.project], tags={'environment': 'example.com'}) assert set(results) == set([]) results = self.backend.query([self.project], tags={'environment': ANY}) assert set(results) == set([self.group2, self.group1]) results = self.backend.query([self.project], tags={ 'environment': 'staging', 'server': 'example.com' }) assert set(results) == set([self.group2]) results = self.backend.query([self.project], tags={ 'environment': 'staging', 'server': ANY }) assert set(results) == set([self.group2]) results = self.backend.query([self.project], tags={ 'environment': 'staging', 'server': 'bar.example.com' }) assert set(results) == set([]) def test_tags_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], tags={'server': 'example.com'}) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['staging']], tags={'server': 'example.com'}) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['staging']], tags={'server': ANY}) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['production']], tags={'url': 'http://example.com'}) assert set(results) == set([]) results = self.backend.query( [self.project], environments=[self.environments['staging']], tags={'url': 'http://example.com'}) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['staging']], tags={'server': 'bar.example.com'}) assert set(results) == set([]) def test_bookmarked_by(self): results = self.backend.query([self.project], bookmarked_by=self.user) assert set(results) == set([self.group2]) def test_bookmarked_by_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['staging']], bookmarked_by=self.user) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['production']], bookmarked_by=self.user) assert set(results) == set([]) def test_project(self): results = self.backend.query([self.create_project(name='other')]) assert set(results) == set([]) def test_pagination(self): for options_set in [{ 'snuba.search.min-pre-snuba-candidates': None }, { 'snuba.search.min-pre-snuba-candidates': 500 }]: with self.options(options_set): results = self.backend.query([self.project], limit=1, sort_by='date') assert set(results) == set([self.group1]) assert not results.prev.has_results assert results.next.has_results results = self.backend.query([self.project], cursor=results.next, limit=1, sort_by='date') assert set(results) == set([self.group2]) assert results.prev.has_results assert not results.next.has_results # note: previous cursor results = self.backend.query([self.project], cursor=results.prev, limit=1, sort_by='date') assert set(results) == set([self.group1]) assert results.prev.has_results assert results.next.has_results # note: previous cursor, paging too far into 0 results results = self.backend.query([self.project], cursor=results.prev, limit=1, sort_by='date') assert set(results) == set([]) assert not results.prev.has_results assert results.next.has_results results = self.backend.query([self.project], cursor=results.next, limit=1, sort_by='date') assert set(results) == set([self.group1]) assert results.prev.has_results assert results.next.has_results results = self.backend.query([self.project], cursor=results.next, limit=1, sort_by='date') assert set(results) == set([self.group2]) assert results.prev.has_results assert not results.next.has_results results = self.backend.query([self.project], cursor=results.next, limit=1, sort_by='date') assert set(results) == set([]) assert results.prev.has_results assert not results.next.has_results def test_pagination_with_environment(self): for dt in [ self.group1.first_seen + timedelta(days=1), self.group1.first_seen + timedelta(days=2), self.group1.last_seen + timedelta(days=1) ]: self.create_event(group=self.group2, datetime=dt, message='group2', stacktrace={'frames': [{ 'module': 'group2' }]}, tags={'environment': 'production'}) results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='date', limit=1, count_hits=True, ) assert list(results) == [self.group2] assert results.hits is None # NOQA results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='date', limit=1, cursor=results.next, count_hits=True, ) assert list(results) == [self.group1] assert results.hits is None # NOQA results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='date', limit=1, cursor=results.next, count_hits=True, ) assert list(results) == [] assert results.hits is None # NOQA def test_age_filter(self): results = self.backend.query( [self.project], age_from=self.group2.first_seen, age_from_inclusive=True, ) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], age_to=self.group1.first_seen + timedelta(minutes=1), age_to_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], age_from=self.group1.first_seen, age_from_inclusive=True, age_to=self.group1.first_seen + timedelta(minutes=1), age_to_inclusive=True, ) assert set(results) == set([self.group1]) def test_age_filter_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], age_from=self.group1.first_seen, age_from_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], age_to=self.group1.first_seen, age_to_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], age_from=self.group1.first_seen, age_from_inclusive=False, ) assert set(results) == set([]) self.create_event(group=self.group1, datetime=self.group1.first_seen + timedelta(days=1), message='group1', stacktrace={'frames': [{ 'module': 'group1' }]}, tags={ 'environment': 'development', }) results = self.backend.query( [self.project], environments=[self.environments['production']], age_from=self.group1.first_seen, age_from_inclusive=False, ) assert set(results) == set([]) results = self.backend.query( [self.project], environments=[self.environments['development']], age_from=self.group1.first_seen, age_from_inclusive=False, ) assert set(results) == set([self.group1]) def test_times_seen_filter(self): results = self.backend.query( [self.project], times_seen=2, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], times_seen_lower=2, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], times_seen_upper=1, ) assert set(results) == set([self.group2]) def test_last_seen_filter(self): results = self.backend.query( [self.project], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], last_seen_to=self.group2.last_seen + timedelta(minutes=1), last_seen_to_inclusive=True, ) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, last_seen_to=self.group1.last_seen + timedelta(minutes=1), last_seen_to_inclusive=True, ) assert set(results) == set([self.group1]) def test_last_seen_filter_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], last_seen_to=self.group1.last_seen, last_seen_to_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=False, ) assert set(results) == set([]) self.create_event(group=self.group1, datetime=self.group1.last_seen + timedelta(days=1), message='group1', stacktrace={'frames': [{ 'module': 'group1' }]}, tags={ 'environment': 'development', }) self.group1.update(last_seen=self.group1.last_seen + timedelta(days=1)) results = self.backend.query( [self.project], environments=[self.environments['production']], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=False, ) assert set(results) == set([]) results = self.backend.query( [self.project], date_to=self.group1.last_seen + timedelta(days=1), environments=[self.environments['development']], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=False, ) assert set(results) == set() results = self.backend.query( [self.project], date_to=self.group1.last_seen + timedelta(days=1), environments=[self.environments['development']], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, ) assert set(results) == set([self.group1]) def test_date_filter(self): results = self.backend.query( [self.project], date_from=self.event2.datetime, ) assert set(results) == set([self.group1, self.group2]) results = self.backend.query( [self.project], date_to=self.event1.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], date_from=self.event1.datetime, date_to=self.event2.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group1, self.group2]) @pytest.mark.xfail( not settings.SENTRY_TAGSTORE.startswith('sentry.tagstore.v2'), reason='unsupported on legacy backend due to insufficient index', ) def test_date_filter_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], date_from=self.event2.datetime, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], date_to=self.event1.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['staging']], date_from=self.event1.datetime, date_to=self.event2.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group2]) def test_unassigned(self): results = self.backend.query([self.project], unassigned=True) assert set(results) == set([self.group1]) results = self.backend.query([self.project], unassigned=False) assert set(results) == set([self.group2]) def test_unassigned_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], unassigned=True) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['staging']], unassigned=False) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['production']], unassigned=False) assert set(results) == set([]) def test_assigned_to(self): results = self.backend.query([self.project], assigned_to=self.user) assert set(results) == set([self.group2]) # test team assignee ga = GroupAssignee.objects.get( user=self.user, group=self.group2, project=self.group2.project, ) ga.update(team=self.team, user=None) assert GroupAssignee.objects.get(id=ga.id).user is None results = self.backend.query([self.project], assigned_to=self.user) assert set(results) == set([self.group2]) # test when there should be no results other_user = self.create_user() results = self.backend.query([self.project], assigned_to=other_user) assert set(results) == set([]) owner = self.create_user() self.create_member( organization=self.project.organization, user=owner, role='owner', teams=[], ) # test that owners don't see results for all teams results = self.backend.query([self.project], assigned_to=owner) assert set(results) == set([]) def test_assigned_to_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['staging']], assigned_to=self.user) assert set(results) == set([self.group2]) results = self.backend.query( [self.project], environments=[self.environments['production']], assigned_to=self.user) assert set(results) == set([]) def test_subscribed_by(self): results = self.backend.query( [self.group1.project], subscribed_by=self.user, ) assert set(results) == set([self.group1]) def test_subscribed_by_with_environment(self): results = self.backend.query( [self.group1.project], environments=[self.environments['production']], subscribed_by=self.user, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.group1.project], environments=[self.environments['staging']], subscribed_by=self.user, ) assert set(results) == set([]) def test_parse_release_latest(self): with pytest.raises(Release.DoesNotExist): # no releases exist period environment = None result = get_latest_release([self.project], environment) old = Release.objects.create( organization_id=self.project.organization_id, version='old') old.add_project(self.project) new_date = old.date_added + timedelta(minutes=1) new = Release.objects.create( version='new-but-in-environment', organization_id=self.project.organization_id, date_released=new_date, ) new.add_project(self.project) ReleaseEnvironment.get_or_create( project=self.project, release=new, environment=self.environment, datetime=new_date, ) ReleaseProjectEnvironment.get_or_create( project=self.project, release=new, environment=self.environment, datetime=new_date, ) newest = Release.objects.create( version='newest-overall', organization_id=self.project.organization_id, date_released=old.date_added + timedelta(minutes=5), ) newest.add_project(self.project) # latest overall (no environment filter) environment = None result = get_latest_release([self.project], environment) assert result == newest.version # latest in environment environment = self.environment result = get_latest_release([self.project], [environment]) assert result == new.version with pytest.raises(Release.DoesNotExist): # environment with no releases environment = self.create_environment() result = get_latest_release([self.project], [environment]) assert result == new.version @mock.patch('sentry.utils.snuba.query') def test_snuba_not_called_optimization(self, query_mock): assert self.backend.query([self.project], query='foo').results == [self.group1] assert not query_mock.called assert self.backend.query([self.project], query='foo', sort_by='date', last_seen_from=timezone.now()).results == [] assert query_mock.called @mock.patch('sentry.utils.snuba.query') def test_optimized_aggregates(self, query_mock): query_mock.return_value = {} def Any(cls): class Any(object): def __eq__(self, other): return isinstance(other, cls) return Any() DEFAULT_LIMIT = 100 chunk_growth = options.get('snuba.search.chunk-growth-rate') limit = (DEFAULT_LIMIT * chunk_growth) + 1 common_args = { 'start': Any(datetime), 'end': Any(datetime), 'filter_keys': { 'project_id': [self.project.id], 'issue': [self.group1.id] }, 'referrer': 'search', 'groupby': ['issue'], 'conditions': [], 'limit': limit, 'offset': 0, } self.backend.query([self.project], query='foo') assert not query_mock.called self.backend.query([self.project], query='foo', sort_by='date', last_seen_from=timezone.now()) assert query_mock.call_args == mock.call( orderby=['-last_seen', 'issue'], aggregations=[['toUInt64(max(timestamp)) * 1000', '', 'last_seen']], having=[('last_seen', '>=', Any(int))], **common_args) self.backend.query([self.project], query='foo', sort_by='priority') assert query_mock.call_args == mock.call( orderby=['-priority', 'issue'], aggregations=[[ '(toUInt64(log(times_seen) * 600)) + last_seen', '', 'priority' ], ['count()', '', 'times_seen'], ['toUInt64(max(timestamp)) * 1000', '', 'last_seen']], having=[], **common_args) self.backend.query([self.project], query='foo', sort_by='freq', times_seen=5) assert query_mock.call_args == mock.call( orderby=['-times_seen', 'issue'], aggregations=[['count()', '', 'times_seen']], having=[('times_seen', '=', 5)], **common_args) self.backend.query([self.project], query='foo', sort_by='new', age_from=timezone.now()) assert query_mock.call_args == mock.call( orderby=['-first_seen', 'issue'], aggregations=[[ 'toUInt64(min(timestamp)) * 1000', '', 'first_seen' ]], having=[('first_seen', '>=', Any(int))], **common_args) def test_pre_and_post_filtering(self): prev_max_pre = options.get('snuba.search.max-pre-snuba-candidates') options.set('snuba.search.max-pre-snuba-candidates', 1) try: # normal queries work as expected results = self.backend.query([self.project], query='foo') assert set(results) == set([self.group1]) results = self.backend.query([self.project], query='bar') assert set(results) == set([self.group2]) # no candidate matches in Sentry, immediately return empty paginator results = self.backend.query([self.project], query='NO MATCHES IN SENTRY') assert set(results) == set() # too many candidates, skip pre-filter, requires >1 postfilter queries results = self.backend.query([self.project]) assert set(results) == set([self.group1, self.group2]) finally: options.set('snuba.search.max-pre-snuba-candidates', prev_max_pre) def test_optimizer_enabled(self): prev_optimizer_enabled = options.get( 'snuba.search.pre-snuba-candidates-optimizer') options.set('snuba.search.pre-snuba-candidates-optimizer', True) try: results = self.backend.query( [self.project], environments=[self.environments['production']], tags={'server': 'example.com'}) assert set(results) == set([self.group1]) finally: options.set('snuba.search.pre-snuba-candidates-optimizer', prev_optimizer_enabled) def test_search_out_of_range(self): results = self.backend.query( [self.project], date_from=datetime(2000, 1, 1, 0, 0, 0, tzinfo=pytz.utc), date_to=datetime(2000, 1, 1, 1, 0, 0, tzinfo=pytz.utc), ) assert set(results) == set([])
class SnubaSearchTest(SnubaTestCase): def setUp(self): super(SnubaSearchTest, self).setUp() self.backend = SnubaSearchBackend() self.environments = {} base_datetime = (datetime.utcnow() - timedelta(days=7)).replace(tzinfo=pytz.utc) self.group1 = self.create_group( project=self.project, checksum='a' * 32, message='foo', times_seen=5, status=GroupStatus.UNRESOLVED, last_seen=base_datetime, first_seen=base_datetime - timedelta(days=31), score=ScoreClause.calculate( times_seen=5, last_seen=base_datetime, ), ) self.event1 = self.create_event( event_id='a' * 32, group=self.group1, datetime=base_datetime - timedelta(days=31), message='group1', stacktrace={ 'frames': [{ 'module': 'group1' }]}, tags={ 'server': 'example.com', 'environment': 'production', } ) self.event3 = self.create_event( event_id='c' * 32, group=self.group1, datetime=base_datetime, message='group1', stacktrace={ 'frames': [{ 'module': 'group1' }]}, tags={ 'server': 'example.com', 'environment': 'production', } ) self.group2 = self.create_group( project=self.project, checksum='b' * 32, message='bar', times_seen=10, status=GroupStatus.RESOLVED, last_seen=base_datetime - timedelta(days=30), first_seen=base_datetime - timedelta(days=30), score=ScoreClause.calculate( times_seen=10, last_seen=base_datetime - timedelta(days=30), ), ) self.event2 = self.create_event( event_id='b' * 32, group=self.group2, datetime=base_datetime - timedelta(days=30), message='group2', stacktrace={ 'frames': [{ 'module': 'group2' }]}, tags={ 'server': 'example.com', 'environment': 'staging', 'url': 'http://example.com', } ) GroupBookmark.objects.create( user=self.user, group=self.group2, project=self.group2.project, ) GroupAssignee.objects.create( user=self.user, group=self.group2, project=self.group2.project, ) GroupSubscription.objects.create( user=self.user, group=self.group1, project=self.group1.project, is_active=True, ) GroupSubscription.objects.create( user=self.user, group=self.group2, project=self.group2.project, is_active=False, ) def create_event(self, *args, **kwargs): event = super(SnubaSearchTest, self).create_event(*args, **kwargs) data = event.data.data tags = dict(data.get('tags', [])) if tags['environment'] not in self.environments: self.environments[tags['environment']] = Environment.get_or_create( event.project, tags['environment'], ) return event def test_query(self): results = self.backend.query(self.project, query='foo') assert set(results) == set([self.group1]) results = self.backend.query(self.project, query='bar') assert set(results) == set([self.group2]) def test_query_with_environment(self): results = self.backend.query( self.project, environment=self.environments['production'], query='foo') assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['production'], query='bar') assert set(results) == set([]) results = self.backend.query( self.project, environment=self.environments['staging'], query='bar') assert set(results) == set([self.group2]) def test_sort(self): results = self.backend.query(self.project, sort_by='date') assert list(results) == [self.group1, self.group2] results = self.backend.query(self.project, sort_by='new') assert list(results) == [self.group2, self.group1] results = self.backend.query(self.project, sort_by='freq') assert list(results) == [self.group1, self.group2] results = self.backend.query(self.project, sort_by='priority') assert list(results) == [self.group1, self.group2] def test_sort_with_environment(self): for dt in [ self.group1.first_seen + timedelta(days=1), self.group1.first_seen + timedelta(days=2), self.group1.last_seen + timedelta(days=1)]: self.create_event( group=self.group2, datetime=dt, message='group2', stacktrace={ 'frames': [{ 'module': 'group2' }]}, tags={'environment': 'production'} ) results = self.backend.query( self.project, environment=self.environments['production'], sort_by='date', ) assert list(results) == [self.group2, self.group1] results = self.backend.query( self.project, environment=self.environments['production'], sort_by='new', ) assert list(results) == [self.group2, self.group1] results = self.backend.query( self.project, environment=self.environments['production'], sort_by='freq', ) assert list(results) == [self.group2, self.group1] results = self.backend.query( self.project, environment=self.environments['production'], sort_by='priority', ) assert list(results) == [self.group2, self.group1] def test_status(self): results = self.backend.query(self.project, status=GroupStatus.UNRESOLVED) assert set(results) == set([self.group1]) results = self.backend.query(self.project, status=GroupStatus.RESOLVED) assert set(results) == set([self.group2]) def test_status_with_environment(self): results = self.backend.query( self.project, environment=self.environments['production'], status=GroupStatus.UNRESOLVED) assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['staging'], status=GroupStatus.RESOLVED) assert set(results) == set([self.group2]) results = self.backend.query( self.project, environment=self.environments['production'], status=GroupStatus.RESOLVED) assert set(results) == set([]) def test_tags(self): results = self.backend.query( self.project, tags={'environment': 'staging'}) assert set(results) == set([self.group2]) results = self.backend.query( self.project, tags={'environment': 'example.com'}) assert set(results) == set([]) results = self.backend.query( self.project, tags={'environment': ANY}) assert set(results) == set([self.group2, self.group1]) results = self.backend.query( self.project, tags={'environment': 'staging', 'server': 'example.com'}) assert set(results) == set([self.group2]) results = self.backend.query( self.project, tags={'environment': 'staging', 'server': ANY}) assert set(results) == set([self.group2]) results = self.backend.query( self.project, tags={'environment': 'staging', 'server': 'bar.example.com'}) assert set(results) == set([]) def test_tags_with_environment(self): results = self.backend.query( self.project, environment=self.environments['production'], tags={'server': 'example.com'}) assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['staging'], tags={'server': 'example.com'}) assert set(results) == set([self.group2]) results = self.backend.query( self.project, environment=self.environments['staging'], tags={'server': ANY}) assert set(results) == set([self.group2]) results = self.backend.query( self.project, environment=self.environments['production'], tags={'url': 'http://example.com'}) assert set(results) == set([]) results = self.backend.query( self.project, environment=self.environments['staging'], tags={'url': 'http://example.com'}) assert set(results) == set([self.group2]) results = self.backend.query( self.project, environment=self.environments['staging'], tags={'server': 'bar.example.com'}) assert set(results) == set([]) def test_bookmarked_by(self): results = self.backend.query(self.project, bookmarked_by=self.user) assert set(results) == set([self.group2]) def test_bookmarked_by_with_environment(self): results = self.backend.query( self.project, environment=self.environments['staging'], bookmarked_by=self.user) assert set(results) == set([self.group2]) results = self.backend.query( self.project, environment=self.environments['production'], bookmarked_by=self.user) assert set(results) == set([]) def test_project(self): results = self.backend.query(self.create_project(name='other')) assert set(results) == set([]) def test_pagination(self): results = self.backend.query(self.project, limit=1, sort_by='date') assert set(results) == set([self.group1]) results = self.backend.query(self.project, cursor=results.next, limit=1, sort_by='date') assert set(results) == set([self.group2]) results = self.backend.query(self.project, cursor=results.next, limit=1, sort_by='date') assert set(results) == set([]) def test_pagination_with_environment(self): for dt in [ self.group1.first_seen + timedelta(days=1), self.group1.first_seen + timedelta(days=2), self.group1.last_seen + timedelta(days=1)]: self.create_event( group=self.group2, datetime=dt, message='group2', stacktrace={ 'frames': [{ 'module': 'group2' }]}, tags={'environment': 'production'} ) results = self.backend.query( self.project, environment=self.environments['production'], sort_by='date', limit=1, count_hits=True, ) assert list(results) == [self.group2] assert results.hits == 2 results = self.backend.query( self.project, environment=self.environments['production'], sort_by='date', limit=1, cursor=results.next, count_hits=True, ) assert list(results) == [self.group1] assert results.hits == 2 results = self.backend.query( self.project, environment=self.environments['production'], sort_by='date', limit=1, cursor=results.next, count_hits=True, ) assert list(results) == [] assert results.hits == 2 def test_age_filter(self): results = self.backend.query( self.project, age_from=self.group2.first_seen, age_from_inclusive=True, ) assert set(results) == set([self.group2]) results = self.backend.query( self.project, age_to=self.group1.first_seen + timedelta(minutes=1), age_to_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, age_from=self.group1.first_seen, age_from_inclusive=True, age_to=self.group1.first_seen + timedelta(minutes=1), age_to_inclusive=True, ) assert set(results) == set([self.group1]) def test_age_filter_with_environment(self): results = self.backend.query( self.project, environment=self.environments['production'], age_from=self.group1.first_seen, age_from_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['production'], age_to=self.group1.first_seen, age_to_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['production'], age_from=self.group1.first_seen, age_from_inclusive=False, ) assert set(results) == set([]) self.create_event( group=self.group1, datetime=self.group1.first_seen + timedelta(days=1), message='group1', stacktrace={ 'frames': [{ 'module': 'group1' }]}, tags={ 'environment': 'development', } ) results = self.backend.query( self.project, environment=self.environments['production'], age_from=self.group1.first_seen, age_from_inclusive=False, ) assert set(results) == set([]) results = self.backend.query( self.project, environment=self.environments['development'], age_from=self.group1.first_seen, age_from_inclusive=False, ) assert set(results) == set([self.group1]) def test_times_seen_filter(self): results = self.backend.query( self.project, times_seen=2, ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, times_seen_lower=2, ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, times_seen_upper=1, ) assert set(results) == set([self.group2]) def test_last_seen_filter(self): results = self.backend.query( self.project, last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, last_seen_to=self.group2.last_seen + timedelta(minutes=1), last_seen_to_inclusive=True, ) assert set(results) == set([self.group2]) results = self.backend.query( self.project, last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, last_seen_to=self.group1.last_seen + timedelta(minutes=1), last_seen_to_inclusive=True, ) assert set(results) == set([self.group1]) def test_last_seen_filter_with_environment(self): results = self.backend.query( self.project, environment=self.environments['production'], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['production'], last_seen_to=self.group1.last_seen, last_seen_to_inclusive=True, ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['production'], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=False, ) assert set(results) == set([]) self.create_event( group=self.group1, datetime=self.group1.last_seen + timedelta(days=1), message='group1', stacktrace={ 'frames': [{ 'module': 'group1' }]}, tags={ 'environment': 'development', } ) results = self.backend.query( self.project, environment=self.environments['production'], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=False, ) assert set(results) == set([]) results = self.backend.query( self.project, environment=self.environments['development'], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=False, ) assert set(results) == set([self.group1]) def test_date_filter(self): results = self.backend.query( self.project, date_from=self.event2.datetime, ) assert set(results) == set([self.group1, self.group2]) results = self.backend.query( self.project, date_to=self.event1.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, date_from=self.event1.datetime, date_to=self.event2.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group1, self.group2]) @pytest.mark.xfail( not settings.SENTRY_TAGSTORE.startswith('sentry.tagstore.v2'), reason='unsupported on legacy backend due to insufficient index', ) def test_date_filter_with_environment(self): results = self.backend.query( self.project, environment=self.environments['production'], date_from=self.event2.datetime, ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['production'], date_to=self.event1.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['staging'], date_from=self.event1.datetime, date_to=self.event2.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group2]) def test_unassigned(self): results = self.backend.query(self.project, unassigned=True) assert set(results) == set([self.group1]) results = self.backend.query(self.project, unassigned=False) assert set(results) == set([self.group2]) def test_unassigned_with_environment(self): results = self.backend.query( self.project, environment=self.environments['production'], unassigned=True) assert set(results) == set([self.group1]) results = self.backend.query( self.project, environment=self.environments['staging'], unassigned=False) assert set(results) == set([self.group2]) results = self.backend.query( self.project, environment=self.environments['production'], unassigned=False) assert set(results) == set([]) def test_assigned_to(self): results = self.backend.query(self.project, assigned_to=self.user) assert set(results) == set([self.group2]) # test team assignee ga = GroupAssignee.objects.get( user=self.user, group=self.group2, project=self.group2.project, ) ga.update(team=self.team, user=None) assert GroupAssignee.objects.get(id=ga.id).user is None results = self.backend.query(self.project, assigned_to=self.user) assert set(results) == set([self.group2]) # test when there should be no results other_user = self.create_user() results = self.backend.query(self.project, assigned_to=other_user) assert set(results) == set([]) owner = self.create_user() self.create_member( organization=self.project.organization, user=owner, role='owner', teams=[], ) # test that owners don't see results for all teams results = self.backend.query(self.project, assigned_to=owner) assert set(results) == set([]) def test_assigned_to_with_environment(self): results = self.backend.query( self.project, environment=self.environments['staging'], assigned_to=self.user) assert set(results) == set([self.group2]) results = self.backend.query( self.project, environment=self.environments['production'], assigned_to=self.user) assert set(results) == set([]) def test_subscribed_by(self): results = self.backend.query( self.group1.project, subscribed_by=self.user, ) assert set(results) == set([self.group1]) def test_subscribed_by_with_environment(self): results = self.backend.query( self.group1.project, environment=self.environments['production'], subscribed_by=self.user, ) assert set(results) == set([self.group1]) results = self.backend.query( self.group1.project, environment=self.environments['staging'], subscribed_by=self.user, ) assert set(results) == set([]) def test_parse_release_latest(self): with pytest.raises(Release.DoesNotExist): # no releases exist period environment = None result = get_latest_release(self.project, environment) old = Release.objects.create( organization_id=self.project.organization_id, version='old' ) old.add_project(self.project) new_date = old.date_added + timedelta(minutes=1) new = Release.objects.create( version='new-but-in-environment', organization_id=self.project.organization_id, date_released=new_date, ) new.add_project(self.project) ReleaseEnvironment.get_or_create( project=self.project, release=new, environment=self.environment, datetime=new_date, ) ReleaseProjectEnvironment.get_or_create( project=self.project, release=new, environment=self.environment, datetime=new_date, ) newest = Release.objects.create( version='newest-overall', organization_id=self.project.organization_id, date_released=old.date_added + timedelta(minutes=5), ) newest.add_project(self.project) # latest overall (no environment filter) environment = None result = get_latest_release(self.project, environment) assert result == newest.version # latest in environment environment = self.environment result = get_latest_release(self.project, environment) assert result == new.version with pytest.raises(Release.DoesNotExist): # environment with no releases environment = self.create_environment() result = get_latest_release(self.project, environment) assert result == new.version @mock.patch('sentry.utils.snuba.query') def test_optimized_aggregates(self, query_mock): query_mock.return_value = {} def Any(cls): class Any(object): def __eq__(self, other): return isinstance(other, cls) return Any() common_args = { 'start': Any(datetime), 'end': Any(datetime), 'filter_keys': { 'project_id': [self.project.id], 'primary_hash': [u'513772ee53011ad9f4dc374b2d34d0e9'] }, 'referrer': 'search', 'groupby': ['primary_hash'], 'conditions': [], } self.backend.query(self.project, query='foo') assert query_mock.call_args == mock.call( orderby='-last_seen', aggregations=[['max', 'timestamp', 'last_seen']], having=[], **common_args ) self.backend.query(self.project, query='foo', sort_by='date', last_seen_from=timezone.now()) assert query_mock.call_args == mock.call( orderby='-last_seen', aggregations=[['max', 'timestamp', 'last_seen']], having=[('last_seen', '>=', Any(int))], **common_args ) self.backend.query(self.project, query='foo', sort_by='priority') assert query_mock.call_args == mock.call( orderby='-priority', aggregations=[ ['toUInt32(log(times_seen) * 600) + toUInt32(last_seen)', '', 'priority'], ['count()', '', 'times_seen'], ['max', 'timestamp', 'last_seen'] ], having=[], **common_args ) self.backend.query(self.project, query='foo', sort_by='freq', times_seen=5) assert query_mock.call_args == mock.call( orderby='-times_seen', aggregations=[['count()', '', 'times_seen']], having=[('times_seen', '=', 5)], **common_args ) self.backend.query(self.project, query='foo', sort_by='new', age_from=timezone.now()) assert query_mock.call_args == mock.call( orderby='-first_seen', aggregations=[['min', 'timestamp', 'first_seen']], having=[('first_seen', '>=', Any(int))], **common_args ) def test_pre_and_post_filtering(self): from sentry.search.snuba import backend as snuba_search prev_max_pre = snuba_search.MAX_PRE_SNUBA_CANDIDATES prev_max_post = snuba_search.MAX_POST_SNUBA_CHUNK snuba_search.MAX_PRE_SNUBA_CANDIDATES = 1 snuba_search.MAX_POST_SNUBA_CHUNK = 1 try: # normal queries work as expected results = self.backend.query(self.project, query='foo') assert set(results) == set([self.group1]) results = self.backend.query(self.project, query='bar') assert set(results) == set([self.group2]) # no candidate matches in Sentry, immediately return empty paginator results = self.backend.query(self.project, query='NO MATCHES IN SENTRY') assert set(results) == set() # too many candidates, skip pre-filter, requires >1 postfilter queries results = self.backend.query(self.project) assert set(results) == set([self.group1, self.group2]) finally: snuba_search.MAX_PRE_SNUBA_CANDIDATES = prev_max_pre snuba_search.MAX_POST_SNUBA_CHUNK = prev_max_post
class SnubaSearchTest(SnubaTestCase): def setUp(self): super(SnubaSearchTest, self).setUp() self.backend = SnubaSearchBackend() self.base_datetime = (datetime.utcnow() - timedelta(days=3)).replace(tzinfo=pytz.utc) self.event1 = self.store_event( data={ 'fingerprint': ['put-me-in-group1'], 'event_id': 'a' * 32, 'message': 'foo', 'environment': 'production', 'tags': { 'server': 'example.com', }, 'timestamp': (self.base_datetime - timedelta(days=21)).isoformat()[:19], 'stacktrace': { 'frames': [{ 'module': 'group1' }] }, }, project_id=self.project.id, ) self.event3 = self.store_event( data={ 'fingerprint': ['put-me-in-group1'], 'event_id': 'c' * 32, 'message': 'group1', 'environment': 'production', 'tags': { 'server': 'example.com', }, 'timestamp': self.base_datetime.isoformat()[:19], 'stacktrace': { 'frames': [{ 'module': 'group1' }] }, }, project_id=self.project.id, ) self.group1 = Group.objects.get(id=self.event1.group.id) assert self.group1.id == self.event3.group.id assert self.group1.first_seen == self.event1.datetime assert self.group1.last_seen == self.event3.datetime self.group1.times_seen = 5 self.group1.status = GroupStatus.UNRESOLVED self.group1.save() self.event2 = self.store_event( data={ 'fingerprint': ['put-me-in-group2'], 'event_id': 'b' * 32, 'timestamp': (self.base_datetime - timedelta(days=20)).isoformat()[:19], 'message': 'bar', 'stacktrace': { 'frames': [{ 'module': 'group2' }] }, 'environment': 'staging', 'tags': { 'server': 'example.com', 'url': 'http://example.com', } }, project_id=self.project.id ) self.group2 = Group.objects.get(id=self.event2.group.id) assert self.group2.first_seen == self.group2.last_seen == self.event2.datetime self.group2.status = GroupStatus.RESOLVED self.group2.times_seen = 10 self.group2.save() GroupBookmark.objects.create( user=self.user, group=self.group2, project=self.group2.project, ) GroupAssignee.objects.create( user=self.user, group=self.group2, project=self.group2.project, ) GroupSubscription.objects.create( user=self.user, group=self.group1, project=self.group1.project, is_active=True, ) GroupSubscription.objects.create( user=self.user, group=self.group2, project=self.group2.project, is_active=False, ) self.environments = { 'production': self.event1.get_environment(), 'staging': self.event2.get_environment(), } def set_up_multi_project(self): self.project2 = self.create_project(organization=self.project.organization) self.event_p2 = self.store_event( data={ 'event_id': 'a' * 32, 'fingerprint': ['put-me-in-groupP2'], 'timestamp': (self.base_datetime - timedelta(days=21)).isoformat()[:19], 'message': 'foo', 'stacktrace': { 'frames': [{ 'module': 'group_p2' }] }, 'tags': { 'server': 'example.com', }, 'environment': 'production', }, project_id=self.project2.id ) self.group_p2 = Group.objects.get(id=self.event_p2.group.id) self.group_p2.times_seen = 6 self.group_p2.last_seen = self.base_datetime - timedelta(days=1) self.group_p2.save() def build_search_filter(self, query, projects=None, user=None, environments=None): user = user if user is not None else self.user projects = projects if projects is not None else [self.project] return convert_query_values(parse_search_query(query), projects, user, environments) def make_query(self, projects=None, search_filter_query=None, environments=None, **kwargs): search_filters = [] projects = projects if projects is not None else [self.project] if search_filter_query is not None: search_filters = self.build_search_filter( search_filter_query, projects, environments=environments, ) return self.backend.query( projects, search_filters=search_filters, environments=environments, **kwargs ) def test_query(self): results = self.make_query(search_filter_query='foo', query='foo') assert set(results) == set([self.group1]) results = self.make_query(search_filter_query='bar', query='bar') assert set(results) == set([self.group2]) def test_query_multi_project(self): self.set_up_multi_project() results = self.make_query( [self.project, self.project2], search_filter_query='foo', query='foo', ) assert set(results) == set([self.group1, self.group_p2]) def test_query_with_environment(self): results = self.make_query( environments=[self.environments['production']], search_filter_query='foo', query='foo', ) assert set(results) == set([self.group1]) results = self.make_query( environments=[self.environments['production']], search_filter_query='bar', query='bar', ) assert set(results) == set([]) results = self.make_query( environments=[self.environments['staging']], search_filter_query='bar', query='bar', ) assert set(results) == set([self.group2]) def test_multi_environments(self): self.set_up_multi_project() results = self.make_query( [self.project, self.project2], environments=[ self.environments['production'], self.environments['staging'], ]) assert set(results) == set([self.group1, self.group2, self.group_p2]) def test_query_with_environment_multi_project(self): self.set_up_multi_project() results = self.make_query( [self.project, self.project2], environments=[self.environments['production']], search_filter_query='foo', query='foo', ) assert set(results) == set([self.group1, self.group_p2]) results = self.make_query( [self.project, self.project2], environments=[self.environments['production']], search_filter_query='bar', query='bar', ) assert set(results) == set([]) def test_sort(self): results = self.make_query(sort_by='date') assert list(results) == [self.group1, self.group2] results = self.make_query(sort_by='new') assert list(results) == [self.group2, self.group1] results = self.make_query(sort_by='freq') assert list(results) == [self.group1, self.group2] results = self.make_query(sort_by='priority') assert list(results) == [self.group1, self.group2] def test_sort_multi_project(self): self.set_up_multi_project() results = self.make_query([self.project, self.project2], sort_by='date') assert list(results) == [self.group1, self.group_p2, self.group2] results = self.make_query([self.project, self.project2], sort_by='new') assert list(results) == [self.group2, self.group_p2, self.group1] results = self.make_query([self.project, self.project2], sort_by='freq') assert list(results) == [self.group1, self.group_p2, self.group2] results = self.make_query([self.project, self.project2], sort_by='priority') assert list(results) == [self.group1, self.group2, self.group_p2] def test_sort_with_environment(self): for dt in [ self.group1.first_seen + timedelta(days=1), self.group1.first_seen + timedelta(days=2), self.group1.last_seen + timedelta(days=1)]: self.store_event( data={ 'fingerprint': ['put-me-in-group2'], 'timestamp': dt.isoformat()[:19], 'stacktrace': { 'frames': [{ 'module': 'group2' }] }, 'environment': 'production', 'message': 'group2' }, project_id=self.project.id ) results = self.make_query( environments=[self.environments['production']], sort_by='date', ) assert list(results) == [self.group2, self.group1] results = self.make_query( environments=[self.environments['production']], sort_by='new', ) assert list(results) == [self.group2, self.group1] results = self.make_query( environments=[self.environments['production']], sort_by='freq', ) assert list(results) == [self.group2, self.group1] results = self.make_query( environments=[self.environments['production']], sort_by='priority', ) assert list(results) == [self.group2, self.group1] def test_status(self): results = self.make_query( search_filter_query='is:unresolved', status=GroupStatus.UNRESOLVED, ) assert set(results) == set([self.group1]) results = self.make_query( search_filter_query='is:resolved', status=GroupStatus.RESOLVED, ) assert set(results) == set([self.group2]) def test_status_with_environment(self): results = self.make_query( environments=[self.environments['production']], search_filter_query='is:unresolved', status=GroupStatus.UNRESOLVED, ) assert set(results) == set([self.group1]) results = self.make_query( environments=[self.environments['staging']], search_filter_query='is:resolved', status=GroupStatus.RESOLVED, ) assert set(results) == set([self.group2]) results = self.make_query( environments=[self.environments['production']], status=GroupStatus.RESOLVED, search_filter_query='is:resolved', ) assert set(results) == set([]) def test_tags(self): results = self.make_query( search_filter_query='environment:staging', tags={'environment': 'staging'}, ) assert set(results) == set([self.group2]) results = self.make_query( search_filter_query='environment:example.com', tags={'environment': 'example.com'}, ) assert set(results) == set([]) results = self.make_query( search_filter_query='has:environment', tags={'environment': ANY}, ) assert set(results) == set([self.group2, self.group1]) results = self.make_query( search_filter_query='environment:staging server:example.com', tags={'environment': 'staging', 'server': 'example.com'}, ) assert set(results) == set([self.group2]) results = self.make_query( search_filter_query='url:"http://example.com"', tags={'url': 'http://example.com'}, ) assert set(results) == set([self.group2]) results = self.make_query( search_filter_query='environment:staging has:server', tags={'environment': 'staging', 'server': ANY}, ) assert set(results) == set([self.group2]) results = self.make_query( search_filter_query='environment:staging server:bar.example.com', tags={'environment': 'staging', 'server': 'bar.example.com'}, ) assert set(results) == set([]) def test_tags_with_environment(self): results = self.make_query( environments=[self.environments['production']], search_filter_query='server:example.com', tags={'server': 'example.com'}, ) assert set(results) == set([self.group1]) results = self.make_query( environments=[self.environments['staging']], search_filter_query='server:example.com', tags={'server': 'example.com'}, ) assert set(results) == set([self.group2]) results = self.make_query( environments=[self.environments['staging']], search_filter_query='has:server', tags={'server': ANY}, ) assert set(results) == set([self.group2]) results = self.make_query( environments=[self.environments['production']], search_filter_query='url:"http://example.com"', tags={'url': 'http://example.com'}) assert set(results) == set([]) results = self.make_query( environments=[self.environments['staging']], search_filter_query='url:"http://example.com"', tags={'url': 'http://example.com'}, ) assert set(results) == set([self.group2]) results = self.make_query( environments=[self.environments['staging']], search_filter_query='server:bar.example.com', tags={'server': 'bar.example.com'}, ) assert set(results) == set([]) def test_bookmarked_by(self): results = self.make_query( bookmarked_by=self.user, search_filter_query='bookmarks:%s' % self.user.username, ) assert set(results) == set([self.group2]) def test_bookmarked_by_with_environment(self): results = self.make_query( environments=[self.environments['staging']], bookmarked_by=self.user, search_filter_query='bookmarks:%s' % self.user.username, ) assert set(results) == set([self.group2]) results = self.make_query( environments=[self.environments['production']], bookmarked_by=self.user, search_filter_query='bookmarks:%s' % self.user.username, ) assert set(results) == set([]) def test_project(self): results = self.make_query([self.create_project(name='other')]) assert set(results) == set([]) def test_pagination(self): for options_set in [ {'snuba.search.min-pre-snuba-candidates': None}, {'snuba.search.min-pre-snuba-candidates': 500} ]: with self.options(options_set): results = self.backend.query([self.project], limit=1, sort_by='date') assert set(results) == set([self.group1]) assert not results.prev.has_results assert results.next.has_results results = self.backend.query( [self.project], cursor=results.next, limit=1, sort_by='date') assert set(results) == set([self.group2]) assert results.prev.has_results assert not results.next.has_results # note: previous cursor results = self.backend.query( [self.project], cursor=results.prev, limit=1, sort_by='date') assert set(results) == set([self.group1]) assert results.prev.has_results assert results.next.has_results # note: previous cursor, paging too far into 0 results results = self.backend.query( [self.project], cursor=results.prev, limit=1, sort_by='date') assert set(results) == set([]) assert not results.prev.has_results assert results.next.has_results results = self.backend.query( [self.project], cursor=results.next, limit=1, sort_by='date') assert set(results) == set([self.group1]) assert results.prev.has_results assert results.next.has_results results = self.backend.query( [self.project], cursor=results.next, limit=1, sort_by='date') assert set(results) == set([self.group2]) assert results.prev.has_results assert not results.next.has_results results = self.backend.query( [self.project], cursor=results.next, limit=1, sort_by='date') assert set(results) == set([]) assert results.prev.has_results assert not results.next.has_results def test_pagination_with_environment(self): for dt in [ self.group1.first_seen + timedelta(days=1), self.group1.first_seen + timedelta(days=2), self.group1.last_seen + timedelta(days=1)]: self.store_event( data={ 'fingerprint': ['put-me-in-group2'], 'timestamp': dt.isoformat()[:19], 'environment': 'production', 'message': 'group2', 'stacktrace': { 'frames': [{ 'module': 'group2' }] }, }, project_id=self.project.id ) results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='date', limit=1, count_hits=True, ) assert list(results) == [self.group2] assert results.hits == 2 results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='date', limit=1, cursor=results.next, count_hits=True, ) assert list(results) == [self.group1] assert results.hits == 2 results = self.backend.query( [self.project], environments=[self.environments['production']], sort_by='date', limit=1, cursor=results.next, count_hits=True, ) assert list(results) == [] assert results.hits == 2 def test_active_at_filter(self): results = self.make_query( active_at_from=self.group2.active_at, active_at_inclusive=True, search_filter_query='activeSince:>=%s' % date_to_query_format(self.group2.active_at), ) assert set(results) == set([self.group2]) results = self.make_query( active_at_to=self.group1.active_at + timedelta(minutes=1), active_at_inclusive=True, search_filter_query='activeSince:<=%s' % date_to_query_format( self.group1.active_at + timedelta(minutes=1), ), ) assert set(results) == set([self.group1]) results = self.make_query( active_at_from=self.group1.active_at, active_at_from_inclusive=True, active_at_to=self.group1.active_at + timedelta(minutes=1), active_at_to_inclusive=True, search_filter_query='activeSince:>=%s activeSince:<=%s' % ( date_to_query_format(self.group1.active_at), date_to_query_format(self.group1.active_at + timedelta(minutes=1)), ) ) assert set(results) == set([self.group1]) def test_age_filter(self): results = self.make_query( age_from=self.group2.first_seen, age_from_inclusive=True, search_filter_query='firstSeen:>=%s' % date_to_query_format(self.group2.first_seen), ) assert set(results) == set([self.group2]) results = self.make_query( age_to=self.group1.first_seen + timedelta(minutes=1), age_to_inclusive=True, search_filter_query='firstSeen:<=%s' % date_to_query_format( self.group1.first_seen + timedelta(minutes=1), ), ) assert set(results) == set([self.group1]) results = self.make_query( age_from=self.group1.first_seen, age_from_inclusive=True, age_to=self.group1.first_seen + timedelta(minutes=1), age_to_inclusive=True, search_filter_query='firstSeen:>=%s firstSeen:<=%s' % ( date_to_query_format(self.group1.first_seen), date_to_query_format(self.group1.first_seen + timedelta(minutes=1)), ) ) assert set(results) == set([self.group1]) def test_age_filter_with_environment(self): results = self.make_query( environments=[self.environments['production']], age_from=self.group1.first_seen, age_from_inclusive=True, search_filter_query='firstSeen:>=%s' % date_to_query_format(self.group1.first_seen), ) assert set(results) == set([self.group1]) results = self.make_query( environments=[self.environments['production']], age_to=self.group1.first_seen, age_to_inclusive=True, search_filter_query='firstSeen:<=%s' % date_to_query_format(self.group1.first_seen), ) assert set(results) == set([self.group1]) results = self.make_query( environments=[self.environments['production']], age_from=self.group1.first_seen, age_from_inclusive=False, search_filter_query='firstSeen:>%s' % date_to_query_format(self.group1.first_seen), ) assert set(results) == set([]) self.store_event( data={ 'fingerprint': ['put-me-in-group1'], 'timestamp': (self.group1.first_seen + timedelta(days=1)).isoformat()[:19], 'message': 'group1', 'stacktrace': { 'frames': [{ 'module': 'group1' }] }, 'environment': 'development' }, project_id=self.project.id ) results = self.make_query( environments=[self.environments['production']], age_from=self.group1.first_seen, age_from_inclusive=False, search_filter_query='firstSeen:>%s' % date_to_query_format(self.group1.first_seen), ) assert set(results) == set([]) results = self.make_query( environments=[Environment.objects.get(name='development')], age_from=self.group1.first_seen, age_from_inclusive=False, search_filter_query='firstSeen:>%s' % date_to_query_format(self.group1.first_seen), ) assert set(results) == set([self.group1]) def test_times_seen_filter(self): results = self.make_query( [self.project], times_seen=2, search_filter_query='times_seen:2', ) assert set(results) == set([self.group1]) results = self.make_query( [self.project], times_seen_lower=2, search_filter_query='times_seen:>=2', ) assert set(results) == set([self.group1]) results = self.make_query( [self.project], times_seen_upper=1, search_filter_query='times_seen:<=1', ) assert set(results) == set([self.group2]) def test_last_seen_filter(self): results = self.make_query( last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, search_filter_query='lastSeen:>=%s' % date_to_query_format(self.group1.last_seen), ) assert set(results) == set([self.group1]) results = self.make_query( last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, last_seen_to=self.group1.last_seen + timedelta(minutes=1), last_seen_to_inclusive=True, search_filter_query='lastSeen:>=%s lastSeen:<=%s' % ( date_to_query_format(self.group1.last_seen), date_to_query_format(self.group1.last_seen + timedelta(minutes=1)), ) ) assert set(results) == set([self.group1]) def test_last_seen_filter_with_environment(self): results = self.make_query( environments=[self.environments['production']], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, search_filter_query='lastSeen:>=%s' % date_to_query_format(self.group1.last_seen), ) assert set(results) == set([self.group1]) results = self.make_query( environments=[self.environments['production']], last_seen_to=self.group1.last_seen, last_seen_to_inclusive=True, search_filter_query='lastSeen:<=%s' % date_to_query_format(self.group1.last_seen), ) assert set(results) == set([self.group1]) results = self.make_query( environments=[self.environments['production']], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=False, search_filter_query='lastSeen:>%s' % date_to_query_format(self.group1.last_seen), ) assert set(results) == set([]) self.store_event( data={ 'fingerprint': ['put-me-in-group1'], 'timestamp': (self.group1.last_seen + timedelta(days=1)).isoformat()[:19], 'message': 'group1', 'stacktrace': { 'frames': [{ 'module': 'group1' }] }, 'environment': 'development', }, project_id=self.project.id ) self.group1.update(last_seen=self.group1.last_seen + timedelta(days=1)) results = self.make_query( environments=[self.environments['production']], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=False, search_filter_query='lastSeen:>%s' % date_to_query_format(self.group1.last_seen), ) assert set(results) == set([]) results = self.make_query( environments=[Environment.objects.get(name='development')], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=False, search_filter_query='lastSeen:>%s' % date_to_query_format(self.group1.last_seen), ) assert set(results) == set() results = self.backend.query( [self.project], date_to=self.group1.last_seen + timedelta(days=1), environments=[Environment.objects.get(name='development')], last_seen_from=self.group1.last_seen, last_seen_from_inclusive=True, search_filter_query='lastSeen:>=%s' % date_to_query_format(self.group1.last_seen), ) assert set(results) == set([self.group1]) def test_date_filter(self): results = self.make_query( date_from=self.event2.datetime, search_filter_query='timestamp:>=%s' % date_to_query_format(self.event2.datetime), ) assert set(results) == set([self.group1, self.group2]) results = self.make_query( date_to=self.event1.datetime + timedelta(minutes=1), search_filter_query='timestamp:<=%s' % date_to_query_format( self.event1.datetime + timedelta(minutes=1), ), ) assert set(results) == set([self.group1]) results = self.make_query( date_from=self.event1.datetime, date_to=self.event2.datetime + timedelta(minutes=1), search_filter_query='timestamp:>=%s timestamp:<=%s' % ( date_to_query_format(self.event1.datetime), date_to_query_format(self.event2.datetime + timedelta(minutes=1)), ) ) assert set(results) == set([self.group1, self.group2]) @pytest.mark.xfail( not settings.SENTRY_TAGSTORE.startswith('sentry.tagstore.v2'), reason='unsupported on legacy backend due to insufficient index', ) def test_date_filter_with_environment(self): results = self.backend.query( [self.project], environments=[self.environments['production']], date_from=self.event2.datetime, ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['production']], date_to=self.event1.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group1]) results = self.backend.query( [self.project], environments=[self.environments['staging']], date_from=self.event1.datetime, date_to=self.event2.datetime + timedelta(minutes=1), ) assert set(results) == set([self.group2]) def test_unassigned(self): results = self.make_query( unassigned=True, search_filter_query='is:unassigned', ) assert set(results) == set([self.group1]) results = self.make_query( unassigned=False, search_filter_query='is:assigned', ) assert set(results) == set([self.group2]) def test_unassigned_with_environment(self): results = self.make_query( environments=[self.environments['production']], unassigned=True, search_filter_query='is:unassigned', ) assert set(results) == set([self.group1]) results = self.make_query( environments=[self.environments['staging']], unassigned=False, search_filter_query='is:assigned', ) assert set(results) == set([self.group2]) results = self.make_query( environments=[self.environments['production']], unassigned=False, search_filter_query='is:assigned', ) assert set(results) == set([]) def test_assigned_to(self): results = self.make_query( assigned_to=self.user, search_filter_query='assigned:%s' % self.user.username, ) assert set(results) == set([self.group2]) # test team assignee ga = GroupAssignee.objects.get( user=self.user, group=self.group2, project=self.group2.project, ) ga.update(team=self.team, user=None) assert GroupAssignee.objects.get(id=ga.id).user is None results = self.make_query( assigned_to=self.user, search_filter_query='assigned:%s' % self.user.username, ) assert set(results) == set([self.group2]) # test when there should be no results other_user = self.create_user() results = self.make_query( assigned_to=other_user, search_filter_query='assigned:%s' % other_user.username ) assert set(results) == set([]) owner = self.create_user() self.create_member( organization=self.project.organization, user=owner, role='owner', teams=[], ) # test that owners don't see results for all teams results = self.make_query( assigned_to=owner, search_filter_query='assigned:%s' % owner.username ) assert set(results) == set([]) def test_assigned_to_with_environment(self): results = self.make_query( environments=[self.environments['staging']], assigned_to=self.user, search_filter_query='assigned:%s' % self.user.username ) assert set(results) == set([self.group2]) results = self.make_query( environments=[self.environments['production']], assigned_to=self.user, search_filter_query='assigned:%s' % self.user.username ) assert set(results) == set([]) def test_subscribed_by(self): results = self.make_query( [self.group1.project], subscribed_by=self.user, search_filter_query='subscribed:%s' % self.user.username ) assert set(results) == set([self.group1]) def test_subscribed_by_with_environment(self): results = self.make_query( [self.group1.project], environments=[self.environments['production']], subscribed_by=self.user, search_filter_query='subscribed:%s' % self.user.username ) assert set(results) == set([self.group1]) results = self.make_query( [self.group1.project], environments=[self.environments['staging']], subscribed_by=self.user, search_filter_query='subscribed:%s' % self.user.username ) assert set(results) == set([]) @mock.patch('sentry.utils.snuba.raw_query') def test_snuba_not_called_optimization(self, query_mock): assert self.make_query(query='foo', search_filter_query='foo').results == [self.group1] assert not query_mock.called assert self.make_query( search_filter_query='last_seen:>%s foo' % date_to_query_format(timezone.now()), query='foo', sort_by='date', last_seen_from=timezone.now(), ).results == [] assert query_mock.called @mock.patch('sentry.utils.snuba.raw_query') def test_optimized_aggregates(self, query_mock): # TODO this test is annoyingly fragile and breaks in hard-to-see ways # any time anything about the snuba query changes query_mock.return_value = {'data': [], 'totals': {'total': 0}} def Any(cls): class Any(object): def __eq__(self, other): return isinstance(other, cls) return Any() DEFAULT_LIMIT = 100 chunk_growth = options.get('snuba.search.chunk-growth-rate') limit = int(DEFAULT_LIMIT * chunk_growth) common_args = { 'start': Any(datetime), 'end': Any(datetime), 'filter_keys': { 'project_id': [self.project.id], 'issue': [self.group1.id] }, 'referrer': 'search', 'groupby': ['issue'], 'conditions': [[['positionCaseInsensitive', ['message', "'foo'"]], '!=', 0]], 'selected_columns': [], 'limit': limit, 'offset': 0, 'totals': True, 'turbo': False, 'sample': 1, } self.make_query(query='foo', search_filter_query='foo') assert not query_mock.called self.make_query( search_filter_query='last_seen:>=%s foo' % date_to_query_format(timezone.now()), query='foo', last_seen_from=timezone.now(), sort_by='date', ) assert query_mock.call_args == mock.call( orderby=['-last_seen', 'issue'], aggregations=[ ['uniq', 'issue', 'total'], ['toUInt64(max(timestamp)) * 1000', '', 'last_seen'] ], having=[['last_seen', '>=', Any(int)]], **common_args ) self.make_query( search_filter_query='foo', query='foo', sort_by='priority', ) assert query_mock.call_args == mock.call( orderby=['-priority', 'issue'], aggregations=[ ['(toUInt64(log(times_seen) * 600)) + last_seen', '', 'priority'], ['count()', '', 'times_seen'], ['uniq', 'issue', 'total'], ['toUInt64(max(timestamp)) * 1000', '', 'last_seen'] ], having=[], **common_args ) self.make_query( search_filter_query='times_seen:5 foo', query='foo', times_seen=5, sort_by='freq', ) assert query_mock.call_args == mock.call( orderby=['-times_seen', 'issue'], aggregations=[ ['count()', '', 'times_seen'], ['uniq', 'issue', 'total'], ], having=[['times_seen', '=', 5]], **common_args ) self.make_query( search_filter_query='age:>=%s foo' % date_to_query_format(timezone.now()), query='foo', age_from=timezone.now(), sort_by='new', ) assert query_mock.call_args == mock.call( orderby=['-first_seen', 'issue'], aggregations=[ ['toUInt64(min(timestamp)) * 1000', '', 'first_seen'], ['uniq', 'issue', 'total'], ], having=[['first_seen', '>=', Any(int)]], **common_args ) def test_pre_and_post_filtering(self): prev_max_pre = options.get('snuba.search.max-pre-snuba-candidates') options.set('snuba.search.max-pre-snuba-candidates', 1) try: # normal queries work as expected results = self.make_query(query='foo', search_filter_query='foo') assert set(results) == set([self.group1]) results = self.make_query(query='bar', search_filter_query='bar') assert set(results) == set([self.group2]) # no candidate matches in Sentry, immediately return empty paginator results = self.make_query( search_filter_query='NO MATCHES IN SENTRY', query='NO MATCHES IN SENTRY', ) assert set(results) == set() # too many candidates, skip pre-filter, requires >1 postfilter queries results = self.make_query() assert set(results) == set([self.group1, self.group2]) finally: options.set('snuba.search.max-pre-snuba-candidates', prev_max_pre) def test_optimizer_enabled(self): prev_optimizer_enabled = options.get('snuba.search.pre-snuba-candidates-optimizer') options.set('snuba.search.pre-snuba-candidates-optimizer', True) try: results = self.make_query( search_filter_query='server:example.com', environments=[self.environments['production']], tags={'server': 'example.com'}) assert set(results) == set([self.group1]) finally: options.set('snuba.search.pre-snuba-candidates-optimizer', prev_optimizer_enabled) def test_search_out_of_range(self): the_date = datetime(2000, 1, 1, 0, 0, 0, tzinfo=pytz.utc) results = self.make_query( search_filter_query='event.timestamp:>%s event.timestamp:<%s' % (the_date, the_date), date_from=the_date, date_to=the_date, ) assert set(results) == set([]) def test_hits_estimate(self): # 400 Groups/Events # Every 3rd one is Unresolved # Evey 2nd one has tag match=1 for i in range(400): event = self.store_event( data={ 'event_id': md5('event {}'.format(i)).hexdigest(), 'fingerprint': ['put-me-in-group{}'.format(i)], 'timestamp': (self.base_datetime - timedelta(days=21)).isoformat()[:19], 'message': 'group {} event'.format(i), 'stacktrace': { 'frames': [{ 'module': 'module {}'.format(i) }] }, 'tags': { 'match': '{}'.format(i % 2), }, 'environment': 'production', }, project_id=self.project.id ) group = event.group group.times_seen = 5 group.status = GroupStatus.UNRESOLVED if i % 3 == 0 else GroupStatus.RESOLVED group.save() # Sample should estimate there are roughly 66 overall matching groups # based on a random sample of 100 (or $sample_size) of the total 200 # snuba matches, of which 33% should pass the postgres filter. with self.options({ # Too small to pass all django candidates down to snuba 'snuba.search.max-pre-snuba-candidates': 5, 'snuba.search.hits-sample-size': 50}): first_results = self.make_query( search_filter_query='is:unresolved match:1', status=GroupStatus.UNRESOLVED, tags={'match': '1'}, limit=10, count_hits=True, ) # Deliberately do not assert that the value is within some margin # of error, as this will fail tests at some rate corresponding to # our confidence interval. assert first_results.hits > 10 # When searching for the same tags, we should get the same set of # hits as the sampling is based on the hash of the query. second_results = self.make_query( search_filter_query='is:unresolved match:1', status=GroupStatus.UNRESOLVED, tags={'match': '1'}, limit=10, count_hits=True, ) assert first_results.results == second_results.results # When using a different search, we should get a different sample # but still should have some hits. third_results = self.make_query( search_filter_query='is:unresolved match:0', status=GroupStatus.UNRESOLVED, tags={'match': '0'}, limit=10, count_hits=True, ) assert third_results.hits > 10 assert third_results.results != second_results.results def test_first_release(self): results = self.make_query( first_release='fake', search_filter_query='first_release:%s' % 'fake', ) assert set(results) == set([]) release = self.create_release(self.project) results = self.make_query( first_release=release.version, search_filter_query='first_release:%s' % release.version, ) assert set(results) == set([]) self.group1.first_release = release self.group1.save() results = self.make_query( first_release=release.version, search_filter_query='first_release:%s' % release.version, ) assert set(results) == set([self.group1]) def test_first_release_environments(self): results = self.make_query( first_release='fake', environments=[self.environments['production']], search_filter_query='first_release:%s' % 'fake', ) assert set(results) == set([]) release = self.create_release(self.project) group_env = GroupEnvironment.get_or_create( group_id=self.group1.id, environment_id=self.environments['production'].id, )[0] results = self.make_query( first_release=release.version, environments=[self.environments['production']], search_filter_query='first_release:%s' % release.version, ) assert set(results) == set([]) group_env.first_release = release group_env.save() results = self.make_query( first_release=release.version, environments=[self.environments['production']], search_filter_query='first_release:%s' % release.version, ) assert set(results) == set([self.group1]) def test_query_enclosed_in_quotes(self): results = self.make_query(search_filter_query='"foo"', query='"foo"') assert set(results) == set([self.group1]) results = self.make_query(search_filter_query='"bar"', query='"bar"') assert set(results) == set([self.group2]) @xfail_if_not_postgres('Wildcard searching only supported in Postgres') def test_wildcard(self): escaped_event = self.store_event( data={ 'fingerprint': ['hello-there'], 'event_id': 'f' * 32, 'message': 'somet[hing]', 'environment': 'production', 'tags': { 'server': 'example.com', }, 'timestamp': self.base_datetime.isoformat()[:19], 'stacktrace': { 'frames': [{ 'module': 'group1' }] }, }, project_id=self.project.id, ) # Note: Adding in `environment:production` so that we make sure we query # in both snuba and postgres results = self.make_query( search_filter_query='environment:production so*t', ) assert set(results) == set([escaped_event.group]) # Make sure it's case insensitive results = self.make_query( search_filter_query='environment:production SO*t', ) assert set(results) == set([escaped_event.group]) results = self.make_query( search_filter_query='environment:production so*zz', ) assert set(results) == set() results = self.make_query( search_filter_query='environment:production [hing]', ) assert set(results) == set([escaped_event.group]) results = self.make_query( search_filter_query='environment:production s*]', ) assert set(results) == set([escaped_event.group])