Browse Source

feat:issueless events. Prepare data structures for issueless events (#13822)

This PR prepares the ground for skipping issue creation if the
event has no fingerprint by removing some assumption that the group
should always exists.
Filippo Pacifici 5 years ago
parent
commit
618bb0cc6f

+ 16 - 0
src/sentry/api/bases/event.py

@@ -0,0 +1,16 @@
+from __future__ import absolute_import
+
+from sentry.api.bases.project import ProjectPermission
+
+
+class EventPermission(ProjectPermission):
+    scope_map = {
+        'GET': ['event:read', 'event:write', 'event:admin'],
+        'POST': ['event:write', 'event:admin'],
+        'PUT': ['event:write', 'event:admin'],
+        'DELETE': ['event:admin'],
+    }
+
+    def has_object_permission(self, request, view, event):
+        return super(EventPermission, self).has_object_permission(
+            request, view, event.project)

+ 3 - 3
src/sentry/api/endpoints/event_details.py

@@ -3,14 +3,14 @@ from __future__ import absolute_import
 from rest_framework.response import Response
 from rest_framework.response import Response
 
 
 from sentry.api.base import Endpoint
 from sentry.api.base import Endpoint
-from sentry.api.bases.group import GroupPermission
+from sentry.api.bases.event import EventPermission
 from sentry.api.exceptions import ResourceDoesNotExist
 from sentry.api.exceptions import ResourceDoesNotExist
 from sentry.api.serializers import DetailedEventSerializer, serialize
 from sentry.api.serializers import DetailedEventSerializer, serialize
 from sentry.models import Event
 from sentry.models import Event
 
 
 
 
 class EventDetailsEndpoint(Endpoint):
 class EventDetailsEndpoint(Endpoint):
-    permission_classes = (GroupPermission, )
+    permission_classes = (EventPermission, )
 
 
     def get(self, request, event_id):
     def get(self, request, event_id):
         """
         """
@@ -27,7 +27,7 @@ class EventDetailsEndpoint(Endpoint):
         if event is None:
         if event is None:
             raise ResourceDoesNotExist
             raise ResourceDoesNotExist
 
 
-        self.check_object_permissions(request, event.group)
+        self.check_object_permissions(request, event)
 
 
         Event.objects.bind_nodes([event], 'data')
         Event.objects.bind_nodes([event], 'data')
 
 

+ 2 - 2
src/sentry/api/serializers/models/event.py

@@ -247,7 +247,7 @@ class EventSerializer(Serializer):
 
 
         d = {
         d = {
             'id': six.text_type(obj.id),
             'id': six.text_type(obj.id),
-            'groupID': six.text_type(obj.group_id),
+            'groupID': six.text_type(obj.group_id) if obj.group_id else None,
             'eventID': six.text_type(obj.event_id),
             'eventID': six.text_type(obj.event_id),
             'projectID': six.text_type(obj.project_id),
             'projectID': six.text_type(obj.project_id),
             'size': obj.size,
             'size': obj.size,
@@ -359,7 +359,7 @@ class SimpleEventSerializer(EventSerializer):
         return {
         return {
             'id': six.text_type(obj.id),
             'id': six.text_type(obj.id),
             'event.type': six.text_type(obj.type),
             'event.type': six.text_type(obj.type),
-            'groupID': six.text_type(obj.group_id),
+            'groupID': six.text_type(obj.group_id) if obj.group_id else None,
             'eventID': six.text_type(obj.event_id),
             'eventID': six.text_type(obj.event_id),
             'projectID': six.text_type(obj.project_id),
             'projectID': six.text_type(obj.project_id),
             # XXX for 'message' this doesn't do the proper resolution of logentry
             # XXX for 'message' this doesn't do the proper resolution of logentry

+ 5 - 4
src/sentry/event_manager.py

@@ -725,7 +725,7 @@ class EventManager(object):
                     extra={
                     extra={
                         'event_uuid': event_id,
                         'event_uuid': event_id,
                         'project_id': project.id,
                         'project_id': project.id,
-                        'group_id': group.id,
+                        'group_id': group.id if group else None,
                         'model': EventMapping.__name__,
                         'model': EventMapping.__name__,
                     }
                     }
                 )
                 )
@@ -833,7 +833,7 @@ class EventManager(object):
                     extra={
                     extra={
                         'event_uuid': event_id,
                         'event_uuid': event_id,
                         'project_id': project.id,
                         'project_id': project.id,
-                        'group_id': group.id,
+                        'group_id': group.id if group else None,
                         'model': Event.__name__,
                         'model': Event.__name__,
                     }
                     }
                 )
                 )
@@ -842,7 +842,7 @@ class EventManager(object):
             tagstore.delay_index_event_tags(
             tagstore.delay_index_event_tags(
                 organization_id=project.organization_id,
                 organization_id=project.organization_id,
                 project_id=project.id,
                 project_id=project.id,
-                group_id=group.id,
+                group_id=group.id if group else None,
                 environment_id=environment.id,
                 environment_id=environment.id,
                 event_id=event.id,
                 event_id=event.id,
                 tags=event.tags,
                 tags=event.tags,
@@ -885,7 +885,8 @@ class EventManager(object):
         if not raw:
         if not raw:
             if not project.first_event:
             if not project.first_event:
                 project.update(first_event=date)
                 project.update(first_event=date)
-                first_event_received.send_robust(project=project, group=group, sender=Project)
+                first_event_received.send_robust(
+                    project=project, event=event, sender=Project)
 
 
         eventstream.insert(
         eventstream.insert(
             group=group,
             group=group,

+ 17 - 7
src/sentry/models/event.py

@@ -89,6 +89,8 @@ class EventCommon(object):
     @property
     @property
     def group(self):
     def group(self):
         from sentry.models import Group
         from sentry.models import Group
+        if not self.group_id:
+            return None
         if not hasattr(self, '_group_cache'):
         if not hasattr(self, '_group_cache'):
             self._group_cache = Group.objects.get(id=self.group_id)
             self._group_cache = Group.objects.get(id=self.group_id)
         return self._group_cache
         return self._group_cache
@@ -223,7 +225,9 @@ class EventCommon(object):
     @property
     @property
     def culprit(self):
     def culprit(self):
         # For a while events did not save the culprit
         # For a while events did not save the culprit
-        return self.data.get('culprit') or self.group.culprit
+        if self.group_id:
+            return self.data.get('culprit') or self.group.culprit
+        return self.data.get('culprit')
 
 
     @property
     @property
     def location(self):
     def location(self):
@@ -353,7 +357,7 @@ class EventCommon(object):
 
 
         # for a long time culprit was not persisted.  In those cases put
         # for a long time culprit was not persisted.  In those cases put
         # the culprit in from the group.
         # the culprit in from the group.
-        if data.get('culprit') is None:
+        if data.get('culprit') is None and self.group_id:
             data['culprit'] = self.group.culprit
             data['culprit'] = self.group.culprit
 
 
         # Override title and location with dynamically generated data
         # Override title and location with dynamically generated data
@@ -370,12 +374,18 @@ class EventCommon(object):
     def level(self):
     def level(self):
         # we might want to move to this:
         # we might want to move to this:
         # return LOG_LEVELS_MAP.get(self.get_level_display()) or self.group.level
         # return LOG_LEVELS_MAP.get(self.get_level_display()) or self.group.level
-        return self.group.level
+        if self.group:
+            return self.group.level
+        else:
+            return None
 
 
     def get_level_display(self):
     def get_level_display(self):
         # we might want to move to this:
         # we might want to move to this:
         # return self.get_tag('level') or self.group.get_level_display()
         # return self.get_tag('level') or self.group.get_level_display()
-        return self.group.get_level_display()
+        if self.group:
+            return self.group.get_level_display()
+        else:
+            return None
 
 
     # deprecated accessors
     # deprecated accessors
 
 
@@ -630,7 +640,7 @@ class SnubaEvent(EventCommon):
             conditions=conditions,
             conditions=conditions,
             filter_keys={
             filter_keys={
                 'project_id': [self.project_id],
                 'project_id': [self.project_id],
-                'issue': [self.group_id],
+                'issue': [self.group_id] if self.group_id else [],
             },
             },
             orderby=['timestamp', 'event_id'],
             orderby=['timestamp', 'event_id'],
             limit=1,
             limit=1,
@@ -660,7 +670,7 @@ class SnubaEvent(EventCommon):
             conditions=conditions,
             conditions=conditions,
             filter_keys={
             filter_keys={
                 'project_id': [self.project_id],
                 'project_id': [self.project_id],
-                'issue': [self.group_id],
+                'issue': [self.group_id] if self.group_id else [],
             },
             },
             orderby=['-timestamp', '-event_id'],
             orderby=['-timestamp', '-event_id'],
             limit=1,
             limit=1,
@@ -778,7 +788,7 @@ class EventSubjectTemplateData(object):
             return self.event.project.get_full_name()
             return self.event.project.get_full_name()
         elif name == 'projectID':
         elif name == 'projectID':
             return self.event.project.slug
             return self.event.project.slug
-        elif name == 'shortID':
+        elif name == 'shortID' and self.event.group_id:
             return self.event.group.qualified_short_id
             return self.event.group.qualified_short_id
         elif name == 'orgID':
         elif name == 'orgID':
             return self.event.organization.slug
             return self.event.organization.slug

+ 1 - 1
src/sentry/receivers/features.py

@@ -50,7 +50,7 @@ DEFAULT_TAGS = frozenset(
 
 
 # First Event
 # First Event
 @first_event_received.connect(weak=False)
 @first_event_received.connect(weak=False)
-def record_first_event(project, group, **kwargs):
+def record_first_event(project, **kwargs):
     FeatureAdoption.objects.record(
     FeatureAdoption.objects.record(
         organization_id=project.organization_id, feature_slug="first_event", complete=True
         organization_id=project.organization_id, feature_slug="first_event", complete=True
     )
     )

+ 7 - 7
src/sentry/receivers/onboarding.py

@@ -93,7 +93,7 @@ def record_raven_installed(project, user, **kwargs):
 
 
 
 
 @first_event_received.connect(weak=False)
 @first_event_received.connect(weak=False)
-def record_first_event(project, group, **kwargs):
+def record_first_event(project, event, **kwargs):
     """
     """
     Requires up to 2 database calls, but should only run with the first event in
     Requires up to 2 database calls, but should only run with the first event in
     any project, so performance should not be a huge bottleneck.
     any project, so performance should not be a huge bottleneck.
@@ -111,7 +111,7 @@ def record_first_event(project, group, **kwargs):
             'project_id': project.id,
             'project_id': project.id,
             'date_completed': project.first_event,
             'date_completed': project.first_event,
             'data': {
             'data': {
-                'platform': group.platform
+                'platform': event.platform
             },
             },
         }
         }
     )
     )
@@ -124,7 +124,7 @@ def record_first_event(project, group, **kwargs):
             user_id=user.id,
             user_id=user.id,
             organization_id=project.organization_id,
             organization_id=project.organization_id,
             project_id=project.id,
             project_id=project.id,
-            platform=group.platform,
+            platform=event.platform,
         )
         )
         return
         return
 
 
@@ -138,8 +138,8 @@ def record_first_event(project, group, **kwargs):
 
 
     # Only counts if it's a new project and platform
     # Only counts if it's a new project and platform
     if oot.project_id != project.id and oot.data.get(
     if oot.project_id != project.id and oot.data.get(
-        'platform', group.platform
-    ) != group.platform:
+        'platform', event.platform
+    ) != event.platform:
         rows_affected, created = OrganizationOnboardingTask.objects.create_or_update(
         rows_affected, created = OrganizationOnboardingTask.objects.create_or_update(
             organization_id=project.organization_id,
             organization_id=project.organization_id,
             task=OnboardingTask.SECOND_PLATFORM,
             task=OnboardingTask.SECOND_PLATFORM,
@@ -149,7 +149,7 @@ def record_first_event(project, group, **kwargs):
                 'project_id': project.id,
                 'project_id': project.id,
                 'date_completed': project.first_event,
                 'date_completed': project.first_event,
                 'data': {
                 'data': {
-                    'platform': group.platform
+                    'platform': event.platform
                 },
                 },
             }
             }
         )
         )
@@ -159,7 +159,7 @@ def record_first_event(project, group, **kwargs):
                 user_id=user.id,
                 user_id=user.id,
                 organization_id=project.organization_id,
                 organization_id=project.organization_id,
                 project_id=project.id,
                 project_id=project.id,
-                platform=group.platform,
+                platform=event.platform,
             )
             )
 
 
 
 

+ 1 - 1
src/sentry/signals.py

@@ -66,7 +66,7 @@ event_saved = BetterSignal(providing_args=["project"])
 # Organization Onboarding Signals
 # Organization Onboarding Signals
 project_created = BetterSignal(providing_args=["project", "user"])
 project_created = BetterSignal(providing_args=["project", "user"])
 first_event_pending = BetterSignal(providing_args=["project", "user"])
 first_event_pending = BetterSignal(providing_args=["project", "user"])
-first_event_received = BetterSignal(providing_args=["project", "group"])
+first_event_received = BetterSignal(providing_args=["project", "event"])
 member_invited = BetterSignal(providing_args=["member", "user"])
 member_invited = BetterSignal(providing_args=["member", "user"])
 member_joined = BetterSignal(providing_args=["member", "organization"])
 member_joined = BetterSignal(providing_args=["member", "organization"])
 issue_tracker_used = BetterSignal(providing_args=["plugin", "project", "user"])
 issue_tracker_used = BetterSignal(providing_args=["plugin", "project", "user"])

+ 3 - 0
src/sentry/similarity/features.py

@@ -102,6 +102,9 @@ class FeatureSet(object):
 
 
         items = []
         items = []
         for event in events:
         for event in events:
+            # TODO: how we index events?
+            if not event.group_id:
+                continue
             for label, features in self.extract(event).items():
             for label, features in self.extract(event).items():
                 if scope is None:
                 if scope is None:
                     scope = self.__get_scope(event.project)
                     scope = self.__get_scope(event.project)

+ 8 - 7
src/sentry/testutils/factories.py

@@ -447,11 +447,11 @@ class Factories(object):
         return useremail
         return useremail
 
 
     @staticmethod
     @staticmethod
-    def create_event(group, event_id=None, normalize=True, **kwargs):
+    def create_event(group=None, project=None, event_id=None, normalize=True, **kwargs):
         # XXX: Do not use this method for new tests! Prefer `store_event`.
         # XXX: Do not use this method for new tests! Prefer `store_event`.
         if event_id is None:
         if event_id is None:
             event_id = uuid4().hex
             event_id = uuid4().hex
-        kwargs.setdefault('project', group.project)
+        kwargs.setdefault('project', project if project else group.project)
         kwargs.setdefault('data', copy.deepcopy(DEFAULT_EVENT_DATA))
         kwargs.setdefault('data', copy.deepcopy(DEFAULT_EVENT_DATA))
         kwargs.setdefault('platform', kwargs['data'].get('platform', 'python'))
         kwargs.setdefault('platform', kwargs['data'].get('platform', 'python'))
         kwargs.setdefault('message', kwargs['data'].get('message', 'message'))
         kwargs.setdefault('message', kwargs['data'].get('message', 'message'))
@@ -497,11 +497,12 @@ class Factories(object):
         )
         )
 
 
         event = Event(event_id=event_id, group=group, **kwargs)
         event = Event(event_id=event_id, group=group, **kwargs)
-        EventMapping.objects.create(
-            project_id=event.project.id,
-            event_id=event_id,
-            group=group,
-        )
+        if group:
+            EventMapping.objects.create(
+                project_id=event.project.id,
+                event_id=event_id,
+                group=group,
+            )
         # emulate EventManager refs
         # emulate EventManager refs
         event.data.bind_ref(event)
         event.data.bind_ref(event)
         event.save()
         event.save()

Some files were not shown because too many files changed in this diff