Esempio n. 1
0
    def test_environment(self):
        group = self.group

        environment = Environment.get_or_create(group.project, "production")

        from sentry.api.serializers.models.group import tsdb

        with mock.patch("sentry.api.serializers.models.group.tsdb.get_range",
                        side_effect=tsdb.get_range) as get_range:
            serialize(
                [group],
                serializer=StreamGroupSerializer(
                    environment_func=lambda: environment, stats_period="14d"),
            )
            assert get_range.call_count == 1
            for args, kwargs in get_range.call_args_list:
                assert kwargs["environment_ids"] == [environment.id]

        def get_invalid_environment():
            raise Environment.DoesNotExist()

        with mock.patch("sentry.api.serializers.models.group.tsdb.make_series",
                        side_effect=tsdb.make_series) as make_series:
            serialize(
                [group],
                serializer=StreamGroupSerializer(
                    environment_func=get_invalid_environment,
                    stats_period="14d"),
            )
            assert make_series.call_count == 1
Esempio n. 2
0
    def get(self, request, project, version):
        """
        List issues to be resolved in a particular release
        ````````````````````````

        Retrieve a list of issues to be resolved in a given release.

        :pparam string organization_slug: the slug of the organization the
                                          release belongs to.
        :pparam string project_slug: the slug of the project associated with the release.
        :pparam string version: the version identifier of the release.
        :auth: required
        """
        try:
            release = Release.objects.get(version=version,
                                          organization=project.organization)
        except Release.DoesNotExist:
            raise ResourceDoesNotExist

        groups = Group.objects.filter(
            project=project,
            id__in=GroupCommitResolution.objects.filter(
                commit_id__in=ReleaseCommit.objects.filter(
                    release=release, ).values_list(
                        'commit_id', flat=True), ).values_list('group_id',
                                                               flat=True),
        )

        context = serialize(list(groups), request.user,
                            StreamGroupSerializer(stats_period=None))
        return Response(context)
    def get(self, request, organization):
        email = request.GET.get('email')

        if email is None:
            return Response(status=400)

        limit = request.GET.get('limit', 100)

        # limit to only teams user has opted into
        project_ids = list(
            Project.objects.filter(
                team__in=OrganizationMemberTeam.objects.filter(
                    organizationmember__user=request.user,
                    organizationmember__organization=organization,
                    is_active=True,
                ).values('team'),
            ).values_list('id', flat=True)[:1000]
        )

        event_users = EventUser.objects.filter(
            email=email,
            project_id__in=project_ids,
        )[:1000]

        project_ids = list(set([e.project_id for e in event_users]))

        group_ids = tagstore.get_group_ids_for_users(project_ids, event_users, limit=limit)

        groups = Group.objects.filter(
            id__in=group_ids,
        ).order_by('-last_seen')[:limit]

        context = serialize(list(groups), request.user, StreamGroupSerializer(stats_period=None))

        return Response(context)
Esempio n. 4
0
def get_serialized_and_stats(
        group: Group, stats_period: str) -> tuple[Mapping[str, Any], int]:
    result = serialize(
        group,
        None,
        StreamGroupSerializer(stats_period=stats_period),
    )
    stats = reduce(
        lambda x, y: x + y[1],
        result["stats"][stats_period],
        0,
    )

    return result, stats
Esempio n. 5
0
    def get(self, request, organization):
        email = request.GET.get('email')

        if email is None:
            return Response(status=400)

        limit = request.GET.get('limit', 100)

        # limit to only teams user has opted into
        member = OrganizationMember.objects.get(user=request.user,
                                                organization=organization)
        teams = Team.objects.filter(
            id__in=OrganizationMemberTeam.objects.filter(
                organizationmember=member,
                is_active=True,
            ).values('team'))

        projects = Project.objects.filter(team__in=list(teams), )

        event_users = EventUser.objects.filter(
            email=email, project_id__in=[p.id for p in projects])[:1000]

        projects = list(set([e.project_id for e in event_users]))

        tag_values = [eu.tag_value for eu in event_users]
        tags = GroupTagValue.objects.filter(key='sentry:user',
                                            value__in=tag_values,
                                            project_id__in=projects)

        group_ids = tags.values_list('group_id', flat=True)

        groups = Group.objects.filter(
            id__in=group_ids,
            project_id__in=projects).order_by('-last_seen')[:limit]

        context = serialize(list(groups), request.user,
                            StreamGroupSerializer(stats_period=None))

        response = Response(context)

        return response
Esempio n. 6
0
    def get(self, request, project):
        """
        List a Project's Aggregates
        ```````````````````````````

        Return a list of aggregates bound to a project.  All parameters are
        supplied as query string parameters.

        A default query of ``is:resolved`` 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 querystring query: an optional Sentry structured search
                                   query.  If not provided an implied
                                   ``"is:resolved"`` is assumed.)
        :pparam string organization_slug: the slug of the organization the
                                          groups belong to.
        :pparam string project_slug: the slug of the project the groups
                                     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')
        if query and 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)
                return Response(
                    serialize(
                        [matching_group], request.user,
                        StreamGroupSerializer(stats_period=stats_period)))

        try:
            query_kwargs = self._build_query_params_from_request(
                request, project)
        except ValidationError as exc:
            return Response({'detail': unicode(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),
        ])

        return response
Esempio n. 7
0
    def get(self, request, project):
        """
        List a Project's Aggregates
        ```````````````````````````

        Return a list of aggregates bound to a project.  All parameters are
        supplied as query string parameters.

        A default query of ``is:resolved`` 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 querystring query: an optional Sentry structured search
                                   query.  If not provided an implied
                                   ``"is:resolved"`` is assumed.)
        :pparam string organization_slug: the slug of the organization the
                                          groups belong to.
        :pparam string project_slug: the slug of the project the groups
                                     belong to.
        :auth: required
        """
        query_kwargs = {
            'project': 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:
            # default
            stats_period = '24h'
        elif stats_period == '':
            # disable stats
            stats_period = None

        if request.GET.get('status'):
            try:
                query_kwargs['status'] = STATUS_CHOICES[request.GET['status']]
            except KeyError:
                return Response('{"detail": "invalid status"}', status=400)

        if request.user.is_authenticated() and request.GET.get('bookmarks'):
            query_kwargs['bookmarked_by'] = request.user

        if request.user.is_authenticated() and request.GET.get('assigned'):
            query_kwargs['assigned_to'] = request.user

        sort_by = request.GET.get('sort')
        if sort_by is None:
            sort_by = DEFAULT_SORT_OPTION

        query_kwargs['sort_by'] = sort_by

        tags = {}
        for tag_key in TagKey.objects.all_keys(project):
            if request.GET.get(tag_key):
                tags[tag_key] = request.GET[tag_key]
        if tags:
            query_kwargs['tags'] = tags

        # TODO: dates should include timestamps
        date_from = request.GET.get('since')
        date_to = request.GET.get('until')
        date_filter = request.GET.get('date_filter')

        limit = request.GET.get('limit')
        if limit:
            try:
                query_kwargs['limit'] = int(limit)
            except ValueError:
                return Response('{"detail": "invalid limit"}', status=400)

        if date_from:
            date_from = self._parse_date(date_from)

        if date_to:
            date_to = self._parse_date(date_to)

        query_kwargs['date_from'] = date_from
        query_kwargs['date_to'] = date_to
        if date_filter:
            query_kwargs['date_filter'] = date_filter

        # TODO: proper pagination support
        cursor = request.GET.get('cursor')
        if cursor:
            query_kwargs['cursor'] = Cursor.from_string(cursor)

        query = request.GET.get('query', 'is:unresolved').strip()
        if len(query) == 32:
            # check to see if we've got an event ID
            try:
                matching_event = EventMapping.objects.filter(
                    project=project,
                    event_id=query,
                ).select_related('group')[0]
            except IndexError:
                pass
            else:
                return Response(
                    serialize(
                        [matching_event.group], request.user,
                        StreamGroupSerializer(stats_period=stats_period)))

        if query is not None:
            query_kwargs.update(parse_query(project, query, request.user))

        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),
        ])

        return response
Esempio n. 8
0
    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
Esempio n. 9
0
    def get(self, request, project):
        """
        List a project's aggregates

        Return a list of aggregates bound to a project.

            {method} {path}

        A default query of 'is:resolved' is applied. To return results with
        other statuses send an new query value (i.e. ?query= for all results).

        Any standard Sentry structured search query can be passed via the
        ``query`` parameter.
        """
        query_kwargs = {
            'project': project,
        }

        if request.GET.get('status'):
            try:
                query_kwargs['status'] = STATUS_CHOICES[request.GET['status']]
            except KeyError:
                return Response('{"detail": "invalid status"}', status=400)

        if request.user.is_authenticated() and request.GET.get('bookmarks'):
            query_kwargs['bookmarked_by'] = request.user

        if request.user.is_authenticated() and request.GET.get('assigned'):
            query_kwargs['assigned_to'] = request.user

        sort_by = request.GET.get('sort')
        if sort_by is None:
            sort_by = DEFAULT_SORT_OPTION

        query_kwargs['sort_by'] = sort_by

        tags = {}
        for tag_key in TagKey.objects.all_keys(project):
            if request.GET.get(tag_key):
                tags[tag_key] = request.GET[tag_key]
        if tags:
            query_kwargs['tags'] = tags

        # TODO: dates should include timestamps
        date_from = request.GET.get('since')
        time_from = request.GET.get('until')
        date_filter = request.GET.get('date_filter')

        date_to = request.GET.get('dt')
        time_to = request.GET.get('tt')
        limit = request.GET.get('limit')
        if limit:
            try:
                query_kwargs['limit'] = int(limit)
            except ValueError:
                return Response('{"detail": "invalid limit"}', status=400)

        today = timezone.now()
        # date format is Y-m-d
        if any(x is not None
               for x in [date_from, time_from, date_to, time_to]):
            date_from, date_to = parse_date(date_from, time_from), parse_date(
                date_to, time_to)
        else:
            date_from = today - timedelta(days=5)
            date_to = None

        query_kwargs['date_from'] = date_from
        query_kwargs['date_to'] = date_to
        if date_filter:
            query_kwargs['date_filter'] = date_filter

        # TODO: proper pagination support
        cursor = request.GET.get('cursor')
        if cursor:
            query_kwargs['cursor'] = Cursor.from_string(cursor)

        query = request.GET.get('query', 'is:unresolved')
        if query is not None:
            query_kwargs.update(parse_query(query, request.user))

        cursor_result = search.query(**query_kwargs)

        context = list(cursor_result)

        response = Response(
            serialize(context, request.user, StreamGroupSerializer()))
        response['Link'] = ', '.join([
            self.build_cursor_link(request, 'previous', cursor_result.prev),
            self.build_cursor_link(request, 'next', cursor_result.next),
        ])

        return response