|
@@ -6,16 +6,25 @@ from datetime import timedelta
|
|
from django.db.models import Q
|
|
from django.db.models import Q
|
|
from django.utils import timezone
|
|
from django.utils import timezone
|
|
from rest_framework.response import Response
|
|
from rest_framework.response import Response
|
|
|
|
+from functools32 import partial
|
|
|
|
|
|
-from sentry import quotas, tagstore
|
|
|
|
|
|
+
|
|
|
|
+from sentry import options, quotas, tagstore
|
|
from sentry.api.base import DocSection, EnvironmentMixin
|
|
from sentry.api.base import DocSection, EnvironmentMixin
|
|
from sentry.api.bases import GroupEndpoint
|
|
from sentry.api.bases import GroupEndpoint
|
|
|
|
+from sentry.api.serializers.models.event import SnubaEvent
|
|
from sentry.api.serializers import serialize
|
|
from sentry.api.serializers import serialize
|
|
-from sentry.api.paginator import DateTimePaginator
|
|
|
|
|
|
+from sentry.api.paginator import DateTimePaginator, GenericOffsetPaginator
|
|
from sentry.models import Environment, Event, Group
|
|
from sentry.models import Environment, Event, Group
|
|
from sentry.search.utils import parse_query
|
|
from sentry.search.utils import parse_query
|
|
-from sentry.utils.apidocs import scenario, attach_scenarios
|
|
|
|
from sentry.search.utils import InvalidQuery
|
|
from sentry.search.utils import InvalidQuery
|
|
|
|
+from sentry.utils.apidocs import scenario, attach_scenarios
|
|
|
|
+from sentry.utils.validators import is_event_id
|
|
|
|
+from sentry.utils.snuba import raw_query
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+class NoResults(Exception):
|
|
|
|
+ pass
|
|
|
|
|
|
|
|
|
|
@scenario('ListAvailableSamples')
|
|
@scenario('ListAvailableSamples')
|
|
@@ -39,62 +48,66 @@ class GroupEventsEndpoint(GroupEndpoint, EnvironmentMixin):
|
|
:auth: required
|
|
:auth: required
|
|
"""
|
|
"""
|
|
|
|
|
|
- def respond(queryset):
|
|
|
|
- return self.paginate(
|
|
|
|
- request=request,
|
|
|
|
- queryset=queryset,
|
|
|
|
- order_by='-datetime',
|
|
|
|
- on_results=lambda x: serialize(x, request.user),
|
|
|
|
- paginator_cls=DateTimePaginator,
|
|
|
|
- )
|
|
|
|
-
|
|
|
|
- events = Event.objects.filter(group_id=group.id)
|
|
|
|
-
|
|
|
|
try:
|
|
try:
|
|
- environment = self._get_environment_from_request(
|
|
|
|
- request,
|
|
|
|
- group.project.organization_id,
|
|
|
|
- )
|
|
|
|
- except Environment.DoesNotExist:
|
|
|
|
- return respond(events.none())
|
|
|
|
-
|
|
|
|
- raw_query = request.GET.get('query')
|
|
|
|
-
|
|
|
|
- if raw_query:
|
|
|
|
- try:
|
|
|
|
- query_kwargs = parse_query([group.project], raw_query, request.user)
|
|
|
|
- except InvalidQuery as exc:
|
|
|
|
- return Response({'detail': six.text_type(exc)}, status=400)
|
|
|
|
|
|
+ environment = self._get_environment(request, group)
|
|
|
|
+ query, tags = self._get_search_query_and_tags(request, group, environment)
|
|
|
|
+ except InvalidQuery as exc:
|
|
|
|
+ return Response({'detail': six.text_type(exc)}, status=400)
|
|
|
|
+ except NoResults:
|
|
|
|
+ return Response([])
|
|
|
|
+
|
|
|
|
+ use_snuba = options.get('snuba.events-queries.enabled')
|
|
|
|
+ backend = self._get_events_snuba if use_snuba else self._get_events_legacy
|
|
|
|
+ return backend(request, group, environment, query, tags)
|
|
|
|
+
|
|
|
|
+ def _get_events_snuba(self, request, group, environment, query, tags):
|
|
|
|
+ conditions = []
|
|
|
|
+ if query:
|
|
|
|
+ msg_substr = ['positionCaseInsensitive', ['message', "'%s'" % (query,)]]
|
|
|
|
+ message_condition = [msg_substr, '!=', 0]
|
|
|
|
+ if is_event_id(query):
|
|
|
|
+ or_condition = [message_condition, ['event_id', '=', query]]
|
|
|
|
+ conditions.append(or_condition)
|
|
else:
|
|
else:
|
|
- query = query_kwargs.pop('query', None)
|
|
|
|
- tags = query_kwargs.pop('tags', {})
|
|
|
|
- else:
|
|
|
|
- query = None
|
|
|
|
- tags = {}
|
|
|
|
|
|
+ conditions.append(message_condition)
|
|
|
|
|
|
- if environment is not None:
|
|
|
|
- if 'environment' in tags and tags['environment'] != environment.name:
|
|
|
|
- # An event can only be associated with a single
|
|
|
|
- # environment, so if the environment associated with
|
|
|
|
- # the request is different than the environment
|
|
|
|
- # provided as a tag lookup, the query cannot contain
|
|
|
|
- # any valid results.
|
|
|
|
- return respond(events.none())
|
|
|
|
- else:
|
|
|
|
- tags['environment'] = environment.name
|
|
|
|
|
|
+ if tags:
|
|
|
|
+ conditions.extend([[u'tags[{}]'.format(k), '=', v] for (k, v) in tags.items()])
|
|
|
|
+
|
|
|
|
+ now = timezone.now()
|
|
|
|
+ data_fn = partial(
|
|
|
|
+ # extract 'data' from raw_query result
|
|
|
|
+ lambda *args, **kwargs: raw_query(*args, **kwargs)['data'],
|
|
|
|
+ start=now - timedelta(days=90),
|
|
|
|
+ end=now,
|
|
|
|
+ conditions=conditions,
|
|
|
|
+ filter_keys={
|
|
|
|
+ 'project_id': [group.project_id],
|
|
|
|
+ 'issue': [group.id]
|
|
|
|
+ },
|
|
|
|
+ selected_columns=SnubaEvent.selected_columns + ['tags.key', 'tags.value'],
|
|
|
|
+ orderby='-timestamp',
|
|
|
|
+ referrer='api.group-events',
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ return self.paginate(
|
|
|
|
+ request=request,
|
|
|
|
+ on_results=lambda results: serialize(
|
|
|
|
+ [SnubaEvent(row) for row in results], request.user),
|
|
|
|
+ paginator=GenericOffsetPaginator(data_fn=data_fn)
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ def _get_events_legacy(self, request, group, environment, query, tags):
|
|
|
|
+ events = Event.objects.filter(group_id=group.id)
|
|
|
|
|
|
if query:
|
|
if query:
|
|
q = Q(message__icontains=query)
|
|
q = Q(message__icontains=query)
|
|
|
|
|
|
- if len(query) == 32:
|
|
|
|
|
|
+ if is_event_id(query):
|
|
q |= Q(event_id__exact=query)
|
|
q |= Q(event_id__exact=query)
|
|
|
|
|
|
events = events.filter(q)
|
|
events = events.filter(q)
|
|
|
|
|
|
- # TODO currently snuba can be used to get this filter of event_ids matching
|
|
|
|
- # the search tags, which is then used to further filter a postgres QuerySet
|
|
|
|
- # Ideally we would just use snuba to completely replace the fetching of the
|
|
|
|
- # events.
|
|
|
|
if tags:
|
|
if tags:
|
|
event_filter = tagstore.get_group_event_filter(
|
|
event_filter = tagstore.get_group_event_filter(
|
|
group.project_id,
|
|
group.project_id,
|
|
@@ -104,7 +117,7 @@ class GroupEventsEndpoint(GroupEndpoint, EnvironmentMixin):
|
|
)
|
|
)
|
|
|
|
|
|
if not event_filter:
|
|
if not event_filter:
|
|
- return respond(events.none())
|
|
|
|
|
|
+ return Response([])
|
|
|
|
|
|
events = events.filter(**event_filter)
|
|
events = events.filter(**event_filter)
|
|
|
|
|
|
@@ -115,4 +128,43 @@ class GroupEventsEndpoint(GroupEndpoint, EnvironmentMixin):
|
|
datetime__gte=timezone.now() - timedelta(days=retention)
|
|
datetime__gte=timezone.now() - timedelta(days=retention)
|
|
)
|
|
)
|
|
|
|
|
|
- return respond(events)
|
|
|
|
|
|
+ return self.paginate(
|
|
|
|
+ request=request,
|
|
|
|
+ queryset=events,
|
|
|
|
+ order_by='-datetime',
|
|
|
|
+ on_results=lambda x: serialize(x, request.user),
|
|
|
|
+ paginator_cls=DateTimePaginator,
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ def _get_environment(self, request, group):
|
|
|
|
+ try:
|
|
|
|
+ return self._get_environment_from_request(
|
|
|
|
+ request,
|
|
|
|
+ group.project.organization_id,
|
|
|
|
+ )
|
|
|
|
+ except Environment.DoesNotExist:
|
|
|
|
+ raise NoResults
|
|
|
|
+
|
|
|
|
+ def _get_search_query_and_tags(self, request, group, environment=None):
|
|
|
|
+ raw_query = request.GET.get('query')
|
|
|
|
+
|
|
|
|
+ if raw_query:
|
|
|
|
+ query_kwargs = parse_query([group.project], raw_query, request.user)
|
|
|
|
+ query = query_kwargs.pop('query', None)
|
|
|
|
+ tags = query_kwargs.pop('tags', {})
|
|
|
|
+ else:
|
|
|
|
+ query = None
|
|
|
|
+ tags = {}
|
|
|
|
+
|
|
|
|
+ if environment is not None:
|
|
|
|
+ if 'environment' in tags and tags['environment'] != environment.name:
|
|
|
|
+ # An event can only be associated with a single
|
|
|
|
+ # environment, so if the environment associated with
|
|
|
|
+ # the request is different than the environment
|
|
|
|
+ # provided as a tag lookup, the query cannot contain
|
|
|
|
+ # any valid results.
|
|
|
|
+ raise NoResults
|
|
|
|
+ else:
|
|
|
|
+ tags['environment'] = environment.name
|
|
|
|
+
|
|
|
|
+ return query, tags
|