123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410 |
- # -*- coding: utf-8 -*-
- from __future__ import absolute_import
- import mock
- import six
- from datetime import timedelta
- from django.utils import timezone
- from mock import patch
- from sentry.api.serializers import serialize
- from sentry.api.serializers.models.group import GroupSerializerSnuba, StreamGroupSerializerSnuba, snuba_tsdb
- from sentry.models import (
- Group, Environment, GroupEnvironment, GroupLink, GroupResolution, GroupSnooze, GroupStatus,
- GroupSubscription, UserOption, UserOptionValue
- )
- from sentry.testutils import APITestCase, SnubaTestCase
- class GroupSerializerSnubaTest(APITestCase, SnubaTestCase):
- def setUp(self):
- super(GroupSerializerSnubaTest, self).setUp()
- self.min_ago = timezone.now() - timedelta(minutes=1)
- self.day_ago = timezone.now() - timedelta(days=1)
- self.week_ago = timezone.now() - timedelta(days=7)
- def test_permalink(self):
- group = self.create_group(title='Oh no')
- result = serialize(group, self.user, serializer=GroupSerializerSnuba())
- assert 'http://' in result['permalink']
- assert '{}/issues/{}'.format(group.organization.slug, group.id) in result['permalink']
- def test_permalink_outside_org(self):
- outside_user = self.create_user()
- group = self.create_group(title='Oh no')
- result = serialize(group, outside_user, serializer=GroupSerializerSnuba())
- assert result['permalink'] is None
- def test_is_ignored_with_expired_snooze(self):
- now = timezone.now()
- user = self.create_user()
- group = self.create_group(
- status=GroupStatus.IGNORED,
- )
- GroupSnooze.objects.create(
- group=group,
- until=now - timedelta(minutes=1),
- )
- result = serialize(group, user, serializer=GroupSerializerSnuba())
- assert result['status'] == 'unresolved'
- assert result['statusDetails'] == {}
- def test_is_ignored_with_valid_snooze(self):
- now = timezone.now()
- user = self.create_user()
- group = self.create_group(
- status=GroupStatus.IGNORED,
- )
- snooze = GroupSnooze.objects.create(
- group=group,
- until=now + timedelta(minutes=1),
- )
- result = serialize(group, user, serializer=GroupSerializerSnuba())
- assert result['status'] == 'ignored'
- assert result['statusDetails']['ignoreCount'] == snooze.count
- assert result['statusDetails']['ignoreWindow'] == snooze.window
- assert result['statusDetails']['ignoreUserCount'] == snooze.user_count
- assert result['statusDetails']['ignoreUserWindow'] == snooze.user_window
- assert result['statusDetails']['ignoreUntil'] == snooze.until
- assert result['statusDetails']['actor'] is None
- def test_is_ignored_with_valid_snooze_and_actor(self):
- now = timezone.now()
- user = self.create_user()
- group = self.create_group(
- status=GroupStatus.IGNORED,
- )
- GroupSnooze.objects.create(
- group=group,
- until=now + timedelta(minutes=1),
- actor_id=user.id,
- )
- result = serialize(group, user, serializer=GroupSerializerSnuba())
- assert result['status'] == 'ignored'
- assert result['statusDetails']['actor']['id'] == six.text_type(user.id)
- def test_resolved_in_next_release(self):
- release = self.create_release(project=self.project, version='a')
- user = self.create_user()
- group = self.create_group(
- status=GroupStatus.RESOLVED,
- )
- GroupResolution.objects.create(
- group=group,
- release=release,
- type=GroupResolution.Type.in_next_release,
- )
- result = serialize(group, user, serializer=GroupSerializerSnuba())
- assert result['status'] == 'resolved'
- assert result['statusDetails'] == {'inNextRelease': True, 'actor': None}
- def test_resolved_in_release(self):
- release = self.create_release(project=self.project, version='a')
- user = self.create_user()
- group = self.create_group(
- status=GroupStatus.RESOLVED,
- )
- GroupResolution.objects.create(
- group=group,
- release=release,
- type=GroupResolution.Type.in_release,
- )
- result = serialize(group, user, serializer=GroupSerializerSnuba())
- assert result['status'] == 'resolved'
- assert result['statusDetails'] == {'inRelease': 'a', 'actor': None}
- def test_resolved_with_actor(self):
- release = self.create_release(project=self.project, version='a')
- user = self.create_user()
- group = self.create_group(
- status=GroupStatus.RESOLVED,
- )
- GroupResolution.objects.create(
- group=group,
- release=release,
- type=GroupResolution.Type.in_release,
- actor_id=user.id,
- )
- result = serialize(group, user, serializer=GroupSerializerSnuba())
- assert result['status'] == 'resolved'
- assert result['statusDetails']['actor']['id'] == six.text_type(user.id)
- def test_resolved_in_commit(self):
- repo = self.create_repo(project=self.project)
- commit = self.create_commit(repo=repo)
- user = self.create_user()
- group = self.create_group(
- status=GroupStatus.RESOLVED,
- )
- GroupLink.objects.create(
- group_id=group.id,
- project_id=group.project_id,
- linked_id=commit.id,
- linked_type=GroupLink.LinkedType.commit,
- relationship=GroupLink.Relationship.resolves,
- )
- result = serialize(group, user, serializer=GroupSerializerSnuba())
- assert result['status'] == 'resolved'
- assert result['statusDetails']['inCommit']['id'] == commit.key
- @patch('sentry.models.Group.is_over_resolve_age')
- def test_auto_resolved(self, mock_is_over_resolve_age):
- mock_is_over_resolve_age.return_value = True
- user = self.create_user()
- group = self.create_group(
- status=GroupStatus.UNRESOLVED,
- )
- result = serialize(group, user, serializer=GroupSerializerSnuba())
- assert result['status'] == 'resolved'
- assert result['statusDetails'] == {'autoResolved': True}
- def test_subscribed(self):
- user = self.create_user()
- group = self.create_group()
- GroupSubscription.objects.create(
- user=user,
- group=group,
- project=group.project,
- is_active=True,
- )
- result = serialize(group, user, serializer=GroupSerializerSnuba())
- assert result['isSubscribed']
- assert result['subscriptionDetails'] == {
- 'reason': 'unknown',
- }
- def test_explicit_unsubscribed(self):
- user = self.create_user()
- group = self.create_group()
- GroupSubscription.objects.create(
- user=user,
- group=group,
- project=group.project,
- is_active=False,
- )
- result = serialize(group, user, serializer=GroupSerializerSnuba())
- assert not result['isSubscribed']
- assert not result['subscriptionDetails']
- def test_implicit_subscribed(self):
- user = self.create_user()
- group = self.create_group()
- combinations = (
- # ((default, project), (subscribed, details))
- ((UserOptionValue.all_conversations, None), (True, None)),
- ((UserOptionValue.all_conversations, UserOptionValue.all_conversations), (True, None)),
- ((UserOptionValue.all_conversations, UserOptionValue.participating_only), (False, None)),
- ((UserOptionValue.all_conversations, UserOptionValue.no_conversations),
- (False, {'disabled': True})),
- ((None, None), (False, None)),
- ((UserOptionValue.participating_only, None), (False, None)),
- ((UserOptionValue.participating_only, UserOptionValue.all_conversations), (True, None)),
- ((UserOptionValue.participating_only, UserOptionValue.participating_only), (False, None)),
- ((UserOptionValue.participating_only, UserOptionValue.no_conversations),
- (False, {'disabled': True})),
- ((UserOptionValue.no_conversations, None), (False, {'disabled': True})),
- ((UserOptionValue.no_conversations, UserOptionValue.all_conversations), (True, None)),
- ((UserOptionValue.no_conversations, UserOptionValue.participating_only), (False, None)),
- ((UserOptionValue.no_conversations, UserOptionValue.no_conversations),
- (False, {'disabled': True})),
- )
- def maybe_set_value(project, value):
- if value is not None:
- UserOption.objects.set_value(
- user=user,
- project=project,
- key='workflow:notifications',
- value=value,
- )
- else:
- UserOption.objects.unset_value(
- user=user,
- project=project,
- key='workflow:notifications',
- )
- for options, (is_subscribed, subscription_details) in combinations:
- default_value, project_value = options
- UserOption.objects.clear_local_cache()
- maybe_set_value(None, default_value)
- maybe_set_value(group.project, project_value)
- result = serialize(group, user, serializer=GroupSerializerSnuba())
- assert result['isSubscribed'] is is_subscribed
- assert result.get('subscriptionDetails') == subscription_details
- def test_global_no_conversations_overrides_group_subscription(self):
- user = self.create_user()
- group = self.create_group()
- GroupSubscription.objects.create(
- user=user,
- group=group,
- project=group.project,
- is_active=True,
- )
- UserOption.objects.set_value(
- user=user,
- project=None,
- key='workflow:notifications',
- value=UserOptionValue.no_conversations,
- )
- result = serialize(group, user, serializer=GroupSerializerSnuba())
- assert not result['isSubscribed']
- assert result['subscriptionDetails'] == {
- 'disabled': True,
- }
- def test_project_no_conversations_overrides_group_subscription(self):
- user = self.create_user()
- group = self.create_group()
- GroupSubscription.objects.create(
- user=user,
- group=group,
- project=group.project,
- is_active=True,
- )
- UserOption.objects.set_value(
- user=user,
- project=group.project,
- key='workflow:notifications',
- value=UserOptionValue.no_conversations,
- )
- result = serialize(group, user, serializer=GroupSerializerSnuba())
- assert not result['isSubscribed']
- assert result['subscriptionDetails'] == {
- 'disabled': True,
- }
- def test_no_user_unsubscribed(self):
- group = self.create_group()
- result = serialize(group, serializer=GroupSerializerSnuba())
- assert not result['isSubscribed']
- def test_seen_stats(self):
- environment = self.create_environment(project=self.project)
- environment2 = self.create_environment(project=self.project)
- events = []
- for event_id, env, user_id, timestamp in [
- ('a' * 32, environment, 1, self.min_ago.isoformat()[:19]),
- ('b' * 32, environment, 2, self.min_ago.isoformat()[:19]),
- ('c' * 32, environment2, 3, self.week_ago.isoformat()[:19]),
- ]:
- events.append(self.store_event(
- data={
- 'event_id': event_id,
- 'fingerprint': ['put-me-in-group1'],
- 'timestamp': timestamp,
- 'environment': env.name,
- 'user': {'id': user_id},
- },
- project_id=self.project.id
- ))
- # Assert all events are in the same group
- group_id, = set(e.group.id for e in events)
- group = Group.objects.get(id=group_id)
- group.times_seen = 5
- group.first_seen = self.week_ago
- group.save()
- # should use group columns when no environments arg passed
- result = serialize(group, serializer=GroupSerializerSnuba(environment_ids=[]))
- assert result['count'] == '5'
- assert result['lastSeen'] == group.last_seen
- assert result['firstSeen'] == group.first_seen
- # update this to something different to make sure it's being used
- group_env = GroupEnvironment.objects.get(group_id=group_id, environment_id=environment.id)
- group_env.first_seen = self.day_ago - timedelta(days=3)
- group_env.save()
- group_env2 = GroupEnvironment.objects.get(group_id=group_id, environment_id=environment2.id)
- result = serialize(
- group, serializer=GroupSerializerSnuba(
- environment_ids=[environment.id, environment2.id])
- )
- assert result['count'] == '3'
- # result is rounded down to nearest second
- assert result['lastSeen'] == self.min_ago - timedelta(microseconds=self.min_ago.microsecond)
- assert result['firstSeen'] == group_env.first_seen
- assert group_env2.first_seen > group_env.first_seen
- assert result['userCount'] == 3
- # test userCount, count, lastSeen filtering correctly by time
- # firstSeen should still be from GroupEnvironment
- result = serialize(group, serializer=GroupSerializerSnuba(
- environment_ids=[environment.id, environment2.id],
- start=self.week_ago - timedelta(hours=1),
- end=self.week_ago + timedelta(hours=1),
- ))
- assert result['userCount'] == 1
- assert result['lastSeen'] == self.week_ago - \
- timedelta(microseconds=self.week_ago.microsecond)
- assert result['firstSeen'] == group_env.first_seen
- assert result['count'] == '1'
- class StreamGroupSerializerTestCase(APITestCase, SnubaTestCase):
- def test_environment(self):
- group = self.group
- environment = Environment.get_or_create(group.project, 'production')
- with mock.patch(
- 'sentry.api.serializers.models.group.snuba_tsdb.get_range',
- side_effect=snuba_tsdb.get_range) as get_range:
- serialize(
- [group],
- serializer=StreamGroupSerializerSnuba(
- environment_ids=[environment.id],
- stats_period='14d',
- ),
- )
- assert get_range.call_count == 1
- for args, kwargs in get_range.call_args_list:
- assert kwargs['environment_ids'] == [environment.id]
- with mock.patch(
- 'sentry.api.serializers.models.group.snuba_tsdb.get_range',
- side_effect=snuba_tsdb.get_range) as get_range:
- serialize(
- [group],
- serializer=StreamGroupSerializerSnuba(
- environment_ids=None,
- stats_period='14d',
- )
- )
- assert get_range.call_count == 1
- for args, kwargs in get_range.call_args_list:
- assert kwargs['environment_ids'] is None
|