def get(self, request, project): """ List a Project's Issues ``````````````````````` Return a list of issues (groups) bound to a project. All parameters are supplied as query string parameters. A default query of ``is:unresolved`` is applied. To return results with other statuses send an new query value (i.e. ``?query=`` for all results). The ``statsPeriod`` parameter can be used to select the timeline stats which should be present. Possible values are: '' (disable), '24h', '14d' :qparam string statsPeriod: an optional stat period (can be one of ``"24h"``, ``"14d"``, and ``""``). :qparam bool shortIdLookup: if this is set to true then short IDs are looked up by this function as well. This can cause the return value of the function to return an event issue of a different project which is why this is an opt-in. Set to `1` to enable. :qparam querystring query: an optional Sentry structured search query. If not provided an implied ``"is:unresolved"`` is assumed.) :pparam string organization_slug: the slug of the organization the issues belong to. :pparam string project_slug: the slug of the project the issues belong to. :auth: required """ stats_period = request.GET.get('statsPeriod') if stats_period not in (None, '', '24h', '14d'): return Response({"detail": ERR_INVALID_STATS_PERIOD}, status=400) elif stats_period is None: # default stats_period = '24h' elif stats_period == '': # disable stats stats_period = None serializer = functools.partial( StreamGroupSerializer, environment_func=self._get_environment_func(request, project.organization_id), stats_period=stats_period, ) query = request.GET.get('query', '').strip() if query: matching_group = None matching_event = None if len(query) == 32: # check to see if we've got an event ID try: matching_group = Group.objects.from_event_id(project, query) except Group.DoesNotExist: pass else: try: matching_event = Event.objects.get( event_id=query, project_id=project.id) except Event.DoesNotExist: pass else: Event.objects.bind_nodes([matching_event], 'data') elif matching_group is None: matching_group = get_by_short_id( project.organization_id, request.GET.get('shortIdLookup'), query, ) if matching_group is not None and matching_group.project_id != project.id: matching_group = None if matching_group is not None: matching_event_environment = None try: matching_event_environment = matching_event.get_environment().name if matching_event else None except Environment.DoesNotExist: pass response = Response( serialize( [matching_group], request.user, serializer( matching_event_id=getattr(matching_event, 'id', None), matching_event_environment=matching_event_environment, ) ) ) response['X-Sentry-Direct-Hit'] = '1' return response try: cursor_result, query_kwargs = self._search(request, project, {'count_hits': True}) except ValidationError as exc: return Response({'detail': six.text_type(exc)}, status=400) results = list(cursor_result) context = serialize(results, request.user, serializer()) # HACK: remove auto resolved entries if query_kwargs.get('status') == GroupStatus.UNRESOLVED: context = [r for r in context if r['status'] == 'unresolved'] response = Response(context) self.add_cursor_headers(request, response, cursor_result) if results and query not in SAVED_SEARCH_QUERIES: advanced_search.send(project=project, sender=request.user) analytics.record('project_issue.searched', user_id=request.user.id, organization_id=project.organization_id, project_id=project.id, query=query) return response
def get(self, request, project): """ List a Project's Issues ``````````````````````` Return a list of issues (groups) bound to a project. All parameters are supplied as query string parameters. A default query of ``is:unresolved`` is applied. To return results with other statuses send an new query value (i.e. ``?query=`` for all results). The ``statsPeriod`` parameter can be used to select the timeline stats which should be present. Possible values are: '' (disable), '24h', '14d' :qparam string statsPeriod: an optional stat period (can be one of ``"24h"``, ``"14d"``, and ``""``). :qparam bool shortIdLookup: if this is set to true then short IDs are looked up by this function as well. This can cause the return value of the function to return an event issue of a different project which is why this is an opt-in. Set to `1` to enable. :qparam querystring query: an optional Sentry structured search query. If not provided an implied ``"is:unresolved"`` is assumed.) :pparam string organization_slug: the slug of the organization the issues belong to. :pparam string project_slug: the slug of the project the issues belong to. :auth: required """ stats_period = request.GET.get('statsPeriod') if stats_period not in (None, '', '24h', '14d'): return Response({"detail": ERR_INVALID_STATS_PERIOD}, status=400) elif stats_period is None: # default stats_period = '24h' elif stats_period == '': # disable stats stats_period = None query = request.GET.get('query', '').strip() if query: matching_group = None matching_event = None if len(query) == 32: # check to see if we've got an event ID try: mapping = EventMapping.objects.get( project_id=project.id, event_id=query, ) except EventMapping.DoesNotExist: pass else: matching_group = Group.objects.get(id=mapping.group_id) try: matching_event = Event.objects.get(event_id=query, project_id=project.id) except Event.DoesNotExist: pass # If the query looks like a short id, we want to provide some # information about where that is. Note that this can return # results for another project. The UI deals with this. elif request.GET.get('shortIdLookup') == '1' and \ looks_like_short_id(query): try: matching_group = Group.objects.by_qualified_short_id( project.organization_id, query) except Group.DoesNotExist: matching_group = None if matching_group is not None: response = Response(serialize( [matching_group], request.user, StreamGroupSerializer( stats_period=stats_period, matching_event_id=getattr(matching_event, 'id', None) ) )) response['X-Sentry-Direct-Hit'] = '1' return response try: query_kwargs = self._build_query_params_from_request(request, project) except ValidationError as exc: return Response({'detail': six.text_type(exc)}, status=400) cursor_result = search.query(**query_kwargs) results = list(cursor_result) context = serialize( results, request.user, StreamGroupSerializer( stats_period=stats_period ) ) # HACK: remove auto resolved entries if query_kwargs.get('status') == GroupStatus.UNRESOLVED: context = [ r for r in context if r['status'] == 'unresolved' ] response = Response(context) response['Link'] = ', '.join([ self.build_cursor_link(request, 'previous', cursor_result.prev), self.build_cursor_link(request, 'next', cursor_result.next), ]) if results and query not in SAVED_SEARCH_QUERIES: advanced_search.send(project=project, sender=request.user) return response
def get(self, request, project): """ List a Project's Issues ``````````````````````` Return a list of issues (groups) bound to a project. All parameters are supplied as query string parameters. A default query of ``is:unresolved`` is applied. To return results with other statuses send an new query value (i.e. ``?query=`` for all results). The ``statsPeriod`` parameter can be used to select the timeline stats which should be present. Possible values are: ``""`` (disable), ``"24h"``, ``"14d"`` :qparam string statsPeriod: an optional stat period (can be one of ``"24h"``, ``"14d"``, and ``""``). :qparam bool shortIdLookup: if this is set to true then short IDs are looked up by this function as well. This can cause the return value of the function to return an event issue of a different project which is why this is an opt-in. Set to `1` to enable. :qparam querystring query: an optional Sentry structured search query. If not provided an implied ``"is:unresolved"`` is assumed.) :qparam string environment: this restricts the issues to ones containing events from this environment :pparam string organization_slug: the slug of the organization the issues belong to. :pparam string project_slug: the slug of the project the issues belong to. :auth: required """ stats_period = request.GET.get("statsPeriod") if stats_period not in (None, "", "24h", "14d"): return Response({"detail": ERR_INVALID_STATS_PERIOD}, status=400) elif stats_period is None: # default stats_period = "24h" elif stats_period == "": # disable stats stats_period = None serializer = functools.partial( StreamGroupSerializer, environment_func=self._get_environment_func( request, project.organization_id), stats_period=stats_period, ) query = request.GET.get("query", "").strip() if query: matching_group = None matching_event = None event_id = normalize_event_id(query) if event_id: # check to see if we've got an event ID try: matching_group = Group.objects.from_event_id( project, event_id) except Group.DoesNotExist: pass else: matching_event = eventstore.get_event_by_id( project.id, event_id) elif matching_group is None: matching_group = get_by_short_id( project.organization_id, request.GET.get("shortIdLookup"), query) if matching_group is not None and matching_group.project_id != project.id: matching_group = None if matching_group is not None: matching_event_environment = None try: matching_event_environment = ( matching_event.get_environment().name if matching_event else None) except Environment.DoesNotExist: pass response = Response( serialize( [matching_group], request.user, serializer( matching_event_id=getattr(matching_event, "event_id", None), matching_event_environment= matching_event_environment, ), )) response["X-Sentry-Direct-Hit"] = "1" return response try: cursor_result, query_kwargs = prep_search(self, request, project, {"count_hits": True}) except ValidationError as exc: return Response({"detail": str(exc)}, status=400) results = list(cursor_result) context = serialize(results, request.user, serializer()) # HACK: remove auto resolved entries # TODO: We should try to integrate this into the search backend, since # this can cause us to arbitrarily return fewer results than requested. status = [ search_filter for search_filter in query_kwargs.get("search_filters", []) if search_filter.key.name == "status" ] if status and (GroupStatus.UNRESOLVED in status[0].value.raw_value): status_labels = { QUERY_STATUS_LOOKUP[s] for s in status[0].value.raw_value } context = [ r for r in context if "status" not in r or r["status"] in status_labels ] response = Response(context) self.add_cursor_headers(request, response, cursor_result) if results and query: advanced_search.send(project=project, sender=request.user) analytics.record( "project_issue.searched", user_id=request.user.id, organization_id=project.organization_id, project_id=project.id, query=query, ) return response
def test_advanced_search(self): advanced_search.send(project=self.project, sender=type(self.project)) feature_complete = FeatureAdoption.objects.get_by_slug( organization=self.organization, slug="advanced_search") assert feature_complete
def test_advanced_search(self): advanced_search.send(project=self.project, sender=type(self.project)) feature_complete = FeatureAdoption.objects.get_by_slug( organization=self.organization, slug="advanced_search" ) assert feature_complete