'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'

@attach_scenarios([list_project_issues_scenario])
def get(self, request, project):

    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):
        stats_period = '24h'
    elif (stats_period == ''):
        stats_period = None
    query = request.GET.get('query', '').strip()
    if query:
        matching_group = None
        matching_event = None
        if (len(query) == 32):
            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
        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)
    count_hits = features.has('projects:stream-hit-counts', project=project, actor=request.user)
    cursor_result = search.query(count_hits=count_hits, **query_kwargs)
    results = list(cursor_result)
    context = serialize(results, request.user, StreamGroupSerializer(stats_period=stats_period))
    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)
    return response
