Skip to content

Commit 6c97277

Browse files
committed
Revert "Revert "feat(events-v2): Add an organization event details endpoint (#13553)" (#13616)"
This reverts commit e2a11ff.
1 parent e2a11ff commit 6c97277

File tree

4 files changed

+418
-0
lines changed

4 files changed

+418
-0
lines changed

src/sentry/api/bases/organization_events.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22

33
from copy import deepcopy
44
from rest_framework.exceptions import PermissionDenied
5+
import six
6+
from enum import Enum
57

68
from sentry import features
79
from sentry.api.bases import OrganizationEndpoint, OrganizationEventsError
810
from sentry.api.event_search import get_snuba_query_args, InvalidSearchQuery
911
from sentry.models.project import Project
12+
from sentry.utils import snuba
1013

1114
# We support 4 "special fields" on the v2 events API which perform some
1215
# additional calculations over aggregated event data
@@ -28,6 +31,11 @@
2831
ALLOWED_GROUPINGS = frozenset(('issue.id', 'project.id'))
2932

3033

34+
class Direction(Enum):
35+
NEXT = 0
36+
PREV = 1
37+
38+
3139
class OrganizationEventsEndpointBase(OrganizationEndpoint):
3240

3341
def get_snuba_query_args(self, request, organization):
@@ -137,3 +145,55 @@ def get_snuba_query_args_v2(self, request, organization, params):
137145
raise OrganizationEventsError(
138146
'Boolean search operator OR and AND not allowed in this search.')
139147
return snuba_args
148+
149+
def next_event_id(self, *args):
150+
"""
151+
Returns the next event ID if there is a subsequent event matching the
152+
conditions provided
153+
"""
154+
return self._get_next_or_prev_id(Direction.NEXT, *args)
155+
156+
def prev_event_id(self, *args):
157+
"""
158+
Returns the previous event ID if there is a previous event matching the
159+
conditions provided
160+
"""
161+
return self._get_next_or_prev_id(Direction.PREV, *args)
162+
163+
def _get_next_or_prev_id(self, direction, request, organization, snuba_args, event):
164+
if (direction == Direction.NEXT):
165+
time_condition = [
166+
['timestamp', '>=', event.timestamp],
167+
[['timestamp', '>', event.timestamp], ['event_id', '>', event.event_id]]
168+
]
169+
orderby = ['timestamp', 'event_id']
170+
start = max(event.datetime, snuba_args['start'])
171+
end = snuba_args['end']
172+
173+
else:
174+
time_condition = [
175+
['timestamp', '<=', event.timestamp],
176+
[['timestamp', '<', event.timestamp], ['event_id', '<', event.event_id]]
177+
]
178+
orderby = ['-timestamp', '-event_id']
179+
start = snuba_args['start']
180+
end = min(event.datetime, snuba_args['end'])
181+
182+
conditions = snuba_args['conditions'][:]
183+
conditions.extend(time_condition)
184+
185+
result = snuba.raw_query(
186+
start=start,
187+
end=end,
188+
selected_columns=['event_id'],
189+
conditions=conditions,
190+
filter_keys=snuba_args['filter_keys'],
191+
orderby=orderby,
192+
limit=1,
193+
referrer='api.organization-events.next-or-prev-id',
194+
)
195+
196+
if 'error' in result or len(result['data']) == 0:
197+
return None
198+
199+
return six.text_type(result['data'][0]['event_id'])
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
from __future__ import absolute_import
2+
3+
from rest_framework.response import Response
4+
import six
5+
from enum import Enum
6+
7+
from sentry.api.bases import OrganizationEventsEndpointBase, OrganizationEventsError, NoProjects
8+
from sentry import features
9+
from sentry.models import SnubaEvent
10+
from sentry.models.project import Project
11+
from sentry.api.serializers import serialize
12+
from sentry.utils.snuba import raw_query
13+
14+
15+
class EventOrdering(Enum):
16+
LATEST = 0
17+
OLDEST = 1
18+
19+
20+
class OrganizationEventDetailsEndpoint(OrganizationEventsEndpointBase):
21+
def get(self, request, organization, project_slug, event_id):
22+
if not features.has('organizations:events-v2', organization, actor=request.user):
23+
return Response(status=404)
24+
25+
try:
26+
params = self.get_filter_params(request, organization)
27+
snuba_args = self.get_snuba_query_args_v2(request, organization, params)
28+
except OrganizationEventsError as exc:
29+
return Response({'detail': exc.message}, status=400)
30+
except NoProjects:
31+
return Response(status=404)
32+
33+
try:
34+
project = Project.objects.get(
35+
slug=project_slug,
36+
organization_id=organization.id
37+
)
38+
except Project.DoesNotExist:
39+
return Response(status=404)
40+
41+
# We return the requested event if we find a match regardless of whether
42+
# it occurred within the range specified
43+
event = SnubaEvent.objects.from_event_id(event_id, project.id)
44+
45+
if event is None:
46+
return Response({'detail': 'Event not found'}, status=404)
47+
48+
data = serialize(event)
49+
50+
data['nextEventID'] = self.next_event_id(request, organization, snuba_args, event)
51+
data['previousEventID'] = self.prev_event_id(request, organization, snuba_args, event)
52+
data['projectSlug'] = project_slug
53+
54+
return Response(data)
55+
56+
57+
class OrganizationEventsLatestOrOldest(OrganizationEventsEndpointBase):
58+
def get(self, latest_or_oldest, request, organization):
59+
if not features.has('organizations:events-v2', organization, actor=request.user):
60+
return Response(status=404)
61+
62+
try:
63+
params = self.get_filter_params(request, organization)
64+
snuba_args = self.get_snuba_query_args_v2(request, organization, params)
65+
except OrganizationEventsError as exc:
66+
return Response({'detail': exc.message}, status=400)
67+
except NoProjects:
68+
return Response(status=404)
69+
70+
if latest_or_oldest == EventOrdering.LATEST:
71+
orderby = ['-timestamp', '-event_id']
72+
else:
73+
orderby = ['timestamp', 'event_id']
74+
75+
result = raw_query(
76+
start=snuba_args['start'],
77+
end=snuba_args['end'],
78+
selected_columns=SnubaEvent.selected_columns,
79+
conditions=snuba_args['conditions'],
80+
filter_keys=snuba_args['filter_keys'],
81+
orderby=orderby,
82+
limit=2,
83+
referrer='api.organization-event-details-latest-or-oldest',
84+
)
85+
86+
if 'error' in result or len(result['data']) == 0:
87+
return Response({'detail': 'Event not found'}, status=404)
88+
89+
try:
90+
project_id = result['data'][0]['project_id']
91+
project_slug = Project.objects.get(
92+
organization=organization, id=project_id).slug
93+
except Project.DoesNotExist:
94+
project_slug = None
95+
96+
data = serialize(SnubaEvent(result['data'][0]))
97+
data['previousEventID'] = None
98+
data['nextEventID'] = None
99+
data['projectSlug'] = project_slug
100+
101+
if latest_or_oldest == EventOrdering.LATEST and len(result['data']) == 2:
102+
data['previousEventID'] = six.text_type(result['data'][1]['event_id'])
103+
104+
if latest_or_oldest == EventOrdering.OLDEST and len(result['data']) == 2:
105+
data['nextEventID'] = six.text_type(result['data'][1]['event_id'])
106+
107+
return Response(data)
108+
109+
110+
class OrganizationEventDetailsLatestEndpoint(OrganizationEventsLatestOrOldest):
111+
def get(self, request, organization):
112+
return super(OrganizationEventDetailsLatestEndpoint, self).get(
113+
EventOrdering.LATEST, request, organization)
114+
115+
116+
class OrganizationEventDetailsOldestEndpoint(OrganizationEventsLatestOrOldest):
117+
def get(self, request, organization):
118+
return super(OrganizationEventDetailsOldestEndpoint, self).get(
119+
EventOrdering.OLDEST, request, organization)

src/sentry/api/urls.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
from .endpoints.organization_discover_saved_queries import OrganizationDiscoverSavedQueriesEndpoint
7373
from .endpoints.organization_discover_saved_query_detail import OrganizationDiscoverSavedQueryDetailEndpoint
7474
from .endpoints.organization_events import OrganizationEventsEndpoint, OrganizationEventsMetaEndpoint, OrganizationEventsStatsEndpoint, OrganizationEventsHeatmapEndpoint
75+
from .endpoints.organization_event_details import OrganizationEventDetailsEndpoint, OrganizationEventDetailsLatestEndpoint, OrganizationEventDetailsOldestEndpoint
7576
from .endpoints.organization_group_index import OrganizationGroupIndexEndpoint
7677
from .endpoints.organization_dashboard_details import OrganizationDashboardDetailsEndpoint
7778
from .endpoints.organization_dashboard_widget_details import OrganizationDashboardWidgetDetailsEndpoint
@@ -605,6 +606,21 @@
605606
OrganizationEventsEndpoint.as_view(),
606607
name='sentry-api-0-organization-events'
607608
),
609+
url(
610+
r'^organizations/(?P<organization_slug>[^\/]+)/events/(?P<project_slug>[^\/]+):(?P<event_id>(?:\d+|[A-Fa-f0-9]{32}))/$',
611+
OrganizationEventDetailsEndpoint.as_view(),
612+
name='sentry-api-0-organization-event-details'
613+
),
614+
url(
615+
r'^organizations/(?P<organization_slug>[^\/]+)/events/latest/$',
616+
OrganizationEventDetailsLatestEndpoint.as_view(),
617+
name='sentry-api-0-organization-event-details-latest'
618+
),
619+
url(
620+
r'^organizations/(?P<organization_slug>[^\/]+)/events/oldest/$',
621+
OrganizationEventDetailsOldestEndpoint.as_view(),
622+
name='sentry-api-0-organization-event-details-oldest'
623+
),
608624
url(
609625
r'^organizations/(?P<organization_slug>[^\/]+)/events-stats/$',
610626
OrganizationEventsStatsEndpoint.as_view(),

0 commit comments

Comments
 (0)