示例#1
0
    def test_get_project_releases_by_stability(self):
        # Add an extra session with a different `distinct_id` so that sorting by users
        # is stable
        self.store_session({
            "session_id": "5e910c1a-6941-460e-9843-24103fb6a63c",
            "distinct_id": "39887d89-13b2-4c84-8c23-5d13d2102665",
            "status": "ok",
            "seq": 0,
            "release": self.session_release,
            "environment": "prod",
            "retention_days": 90,
            "org_id": self.project.organization_id,
            "project_id": self.project.id,
            "duration": None,
            "errors": 0,
            "started": self.session_started,
            "received": self.received,
        })

        for scope in "sessions", "users":
            data = get_project_releases_by_stability([self.project.id],
                                                     offset=0,
                                                     limit=100,
                                                     scope=scope,
                                                     stats_period="24h")

            assert data == [
                (self.project.id, self.session_release),
                (self.project.id, self.session_crashed_release),
            ]
示例#2
0
    def test_get_project_releases_by_stability(self):
        for scope in "sessions", "users":
            data = get_project_releases_by_stability(
                [self.project.id], offset=0, limit=100, scope=scope, stats_period="24h",
            )

            assert data == [
                (self.project.id, self.session_release),
                (self.project.id, self.session_crashed_release),
            ]
示例#3
0
 def test_get_project_releases_by_stability_for_crash_free_sort(self):
     """
     Test that ensures that using crash free rate sort options, returns a list of ASC releases
     according to the chosen crash_free sort option
     """
     for scope in "crash_free_sessions", "crash_free_users":
         data = get_project_releases_by_stability(
             [self.project.id], offset=0, limit=100, scope=scope, stats_period="24h"
         )
         assert data == [
             (self.project.id, self.session_crashed_release),
             (self.project.id, self.session_release),
         ]
示例#4
0
    def test_get_project_releases_by_stability_for_releases_with_users_data(
            self):
        """
        Test that ensures if releases contain no users data, then those releases should not be
        returned on `users` and `crash_free_users` sorts
        """
        self.store_session({
            "session_id": "bd1521fc-d27c-11eb-b8bc-0242ac130003",
            "status": "ok",
            "seq": 0,
            "release": "release-with-no-users",
            "environment": "prod",
            "retention_days": 90,
            "org_id": self.project.organization_id,
            "project_id": self.project.id,
            "duration": None,
            "errors": 0,
            "started": self.session_started,
            "received": self.received,
        })
        data = get_project_releases_by_stability([self.project.id],
                                                 offset=0,
                                                 limit=100,
                                                 scope="users",
                                                 stats_period="24h")
        assert set(data) == {
            (self.project.id, self.session_release),
            (self.project.id, self.session_crashed_release),
        }

        data = get_project_releases_by_stability([self.project.id],
                                                 offset=0,
                                                 limit=100,
                                                 scope="crash_free_users",
                                                 stats_period="24h")
        assert set(data) == {
            (self.project.id, self.session_crashed_release),
            (self.project.id, self.session_release),
        }
示例#5
0
    def get(self, request, organization):
        """
        List an Organization's Releases
        ```````````````````````````````
        Return a list of releases for a given organization.

        :pparam string organization_slug: the organization short name
        :qparam string query: this parameter can be used to create a
                              "starts with" filter for the version.
        """
        query = request.GET.get("query")
        with_health = request.GET.get("health") == "1"
        flatten = request.GET.get("flatten") == "1"
        sort = request.GET.get("sort") or "date"
        health_stat = request.GET.get("healthStat") or "sessions"
        summary_stats_period = request.GET.get("summaryStatsPeriod") or "14d"
        health_stats_period = request.GET.get("healthStatsPeriod") or (
            "24h" if with_health else "")
        if summary_stats_period not in STATS_PERIODS:
            raise ParseError(detail=get_stats_period_detail(
                "summaryStatsPeriod", STATS_PERIODS))
        if health_stats_period and health_stats_period not in STATS_PERIODS:
            raise ParseError(detail=get_stats_period_detail(
                "healthStatsPeriod", STATS_PERIODS))
        if health_stat not in ("sessions", "users"):
            raise ParseError(detail="invalid healthStat")

        paginator_cls = OffsetPaginator
        paginator_kwargs = {}

        try:
            filter_params = self.get_filter_params(request,
                                                   organization,
                                                   date_filter_optional=True)
        except NoProjects:
            return Response([])

        # This should get us all the projects into postgres that have received
        # health data in the last 24 hours.  If health data is not requested
        # we don't upsert releases.
        if with_health:
            debounce_update_release_health_data(organization,
                                                filter_params["project_id"])

        queryset = (Release.objects.filter(
            organization=organization).select_related("owner").annotate(
                date=Coalesce("date_released", "date_added"), ))

        queryset = add_environment_to_queryset(queryset, filter_params)

        if query:
            query_q = Q(version__icontains=query)

            suffix_match = _release_suffix.match(query)
            if suffix_match is not None:
                query_q |= Q(version__icontains="%s+%s" %
                             suffix_match.groups())

            queryset = queryset.filter(query_q)

        select_extra = {}

        queryset = queryset.distinct()
        if flatten:
            select_extra[
                "_for_project_id"] = "sentry_release_project.project_id"

        if sort == "date":
            queryset = queryset.filter(
                projects__id__in=filter_params["project_id"]).order_by("-date")
            paginator_kwargs["order_by"] = "-date"
        elif sort in (
                "crash_free_sessions",
                "crash_free_users",
                "sessions",
                "users",
                "sessions_24h",
                "users_24h",
        ):
            if not flatten:
                return Response(
                    {
                        "detail":
                        "sorting by crash statistics requires flattening (flatten=1)"
                    },
                    status=400,
                )
            paginator_cls = MergingOffsetPaginator
            paginator_kwargs.update(
                data_load_func=lambda offset, limit:
                get_project_releases_by_stability(
                    project_ids=filter_params["project_id"],
                    environments=filter_params.get("environment"),
                    scope=sort,
                    offset=offset,
                    stats_period=summary_stats_period,
                    limit=limit,
                ),
                apply_to_queryset=lambda queryset, rows: queryset.filter(
                    projects__id__in=list(x[0] for x in rows),
                    version__in=list(x[1] for x in rows)),
                key_from_model=lambda x: (x._for_project_id, x.version),
            )
        else:
            return Response({"detail": "invalid sort"}, status=400)

        queryset = queryset.extra(select=select_extra)
        queryset = add_date_filter_to_queryset(queryset, filter_params)

        return self.paginate(
            request=request,
            queryset=queryset,
            paginator_cls=paginator_cls,
            on_results=lambda x: serialize(
                x,
                request.user,
                with_health_data=with_health,
                health_stat=health_stat,
                health_stats_period=health_stats_period,
                summary_stats_period=summary_stats_period,
                environments=filter_params.get("environment") or None,
            ),
            **paginator_kwargs)
示例#6
0
    def get(self, request, organization):
        """
        List an Organization's Releases
        ```````````````````````````````
        Return a list of releases for a given organization.

        :pparam string organization_slug: the organization short name
        :qparam string query: this parameter can be used to create a
                              "starts with" filter for the version.
        """
        query = request.GET.get("query")
        with_health = request.GET.get("health") == "1"
        flatten = request.GET.get("flatten") == "1"
        sort = request.GET.get("sort") or "date"
        summary_stats_period = request.GET.get("summaryStatsPeriod") or "14d"
        health_stats_period = request.GET.get("healthStatsPeriod") or (
            "24h" if with_health else "")
        if summary_stats_period not in STATS_PERIODS:
            raise ParseError(detail=get_stats_period_detail(
                "summaryStatsPeriod", STATS_PERIODS))
        if health_stats_period and health_stats_period not in STATS_PERIODS:
            raise ParseError(detail=get_stats_period_detail(
                "healthStatsPeriod", STATS_PERIODS))

        paginator_cls = OffsetPaginator
        paginator_kwargs = {}

        try:
            filter_params = self.get_filter_params(request,
                                                   organization,
                                                   date_filter_optional=True)
        except NoProjects:
            return Response([])
        except OrganizationEventsError as e:
            return Response({"detail": six.text_type(e)}, status=400)

        # This should get us all the projects into postgres that have received
        # health data in the last 24 hours.  If health data is not requested
        # we don't upsert releases.
        if with_health:
            debounce_update_release_health_data(organization,
                                                filter_params["project_id"])

        queryset = Release.objects.filter(
            organization=organization).select_related("owner")

        if "environment" in filter_params:
            queryset = queryset.filter(
                releaseprojectenvironment__environment__name__in=filter_params[
                    "environment"],
                releaseprojectenvironment__project_id__in=filter_params[
                    "project_id"],
            )

        if query:
            queryset = queryset.filter(version__istartswith=query)

        select_extra = {}
        sort_query = None

        if flatten:
            select_extra[
                "_for_project_id"] = "sentry_release_project.project_id"
        else:
            queryset = queryset.distinct()

        if sort == "date":
            sort_query = "COALESCE(sentry_release.date_released, sentry_release.date_added)"
        elif sort in ("crash_free_sessions", "crash_free_users", "sessions",
                      "users"):
            if not flatten:
                return Response(
                    {
                        "detail":
                        "sorting by crash statistics requires flattening (flatten=1)"
                    },
                    status=400,
                )
            paginator_cls = MergingOffsetPaginator
            paginator_kwargs.update(
                data_load_func=lambda offset, limit:
                get_project_releases_by_stability(
                    project_ids=filter_params["project_id"],
                    environments=filter_params.get("environment"),
                    scope=sort,
                    offset=offset,
                    stats_period=summary_stats_period,
                    limit=limit,
                ),
                apply_to_queryset=lambda queryset, rows: queryset.filter(
                    projects__id__in=list(x[0] for x in rows),
                    version__in=list(x[1] for x in rows)),
                key_from_model=lambda x: (x._for_project_id, x.version),
            )
        else:
            return Response({"detail": "invalid sort"}, status=400)

        if sort_query is not None:
            queryset = queryset.filter(
                projects__id__in=filter_params["project_id"])
            select_extra["sort"] = sort_query
            paginator_kwargs["order_by"] = "-sort"

        queryset = queryset.extra(select=select_extra)
        if filter_params["start"] and filter_params["end"]:
            queryset = queryset.extra(
                where=[
                    "COALESCE(sentry_release.date_released, sentry_release.date_added) BETWEEN %s and %s"
                ],
                params=[filter_params["start"], filter_params["end"]],
            )

        return self.paginate(request=request,
                             queryset=queryset,
                             paginator_cls=paginator_cls,
                             on_results=lambda x: serialize(
                                 x,
                                 request.user,
                                 with_health_data=with_health,
                                 health_stats_period=health_stats_period,
                                 summary_stats_period=summary_stats_period,
                             ),
                             **paginator_kwargs)
示例#7
0
    def get(self, request, organization):
        """
        List an Organization's Releases
        ```````````````````````````````
        Return a list of releases for a given organization.

        :pparam string organization_slug: the organization short name
        :qparam string query: this parameter can be used to create a
                              "starts with" filter for the version.
        """
        query = request.GET.get("query")
        with_health = request.GET.get("health") == "1"
        with_adoption_stages = request.GET.get("adoptionStages") == "1"
        status_filter = request.GET.get("status", "open")
        flatten = request.GET.get("flatten") == "1"
        sort = request.GET.get("sort") or "date"
        health_stat = request.GET.get("healthStat") or "sessions"
        summary_stats_period = request.GET.get("summaryStatsPeriod") or "14d"
        health_stats_period = request.GET.get("healthStatsPeriod") or ("24h" if with_health else "")
        if summary_stats_period not in STATS_PERIODS:
            raise ParseError(detail=get_stats_period_detail("summaryStatsPeriod", STATS_PERIODS))
        if health_stats_period and health_stats_period not in STATS_PERIODS:
            raise ParseError(detail=get_stats_period_detail("healthStatsPeriod", STATS_PERIODS))
        if health_stat not in ("sessions", "users"):
            raise ParseError(detail="invalid healthStat")

        paginator_cls = OffsetPaginator
        paginator_kwargs = {}

        try:
            filter_params = self.get_filter_params(request, organization, date_filter_optional=True)
        except NoProjects:
            return Response([])

        # This should get us all the projects into postgres that have received
        # health data in the last 24 hours.
        debounce_update_release_health_data(organization, filter_params["project_id"])

        queryset = Release.objects.filter(organization=organization)

        if status_filter:
            try:
                status_int = ReleaseStatus.from_string(status_filter)
            except ValueError:
                raise ParseError(detail="invalid value for status")

            if status_int == ReleaseStatus.OPEN:
                queryset = queryset.filter(Q(status=status_int) | Q(status=None))
            else:
                queryset = queryset.filter(status=status_int)

        queryset = queryset.select_related("owner").annotate(date=F("date_added"))

        queryset = add_environment_to_queryset(queryset, filter_params)
        if query:
            try:
                queryset = _filter_releases_by_query(queryset, organization, query, filter_params)
            except InvalidSearchQuery as e:
                return Response(
                    {"detail": str(e)},
                    status=400,
                )

        select_extra = {}

        queryset = queryset.distinct()
        if flatten:
            select_extra["_for_project_id"] = "sentry_release_project.project_id"

        queryset = queryset.filter(projects__id__in=filter_params["project_id"])

        if sort == "date":
            queryset = queryset.order_by("-date")
            paginator_kwargs["order_by"] = "-date"
        elif sort == "build":
            queryset = queryset.filter(build_number__isnull=False).order_by("-build_number")
            paginator_kwargs["order_by"] = "-build_number"
        elif sort == "semver":
            queryset = queryset.annotate_prerelease_column()

            order_by = [F(col).desc(nulls_last=True) for col in Release.SEMVER_COLS]
            # TODO: Adding this extra sort order breaks index usage. Index usage is already broken
            # when we filter by status, so when we fix that we should also consider the best way to
            # make this work as expected.
            order_by.append(F("date_added").desc())
            paginator_kwargs["order_by"] = order_by
        elif sort == "adoption":
            # sort by adoption date (most recently adopted first)
            order_by = F("releaseprojectenvironment__adopted").desc(nulls_last=True)
            queryset = queryset.order_by(order_by)
            paginator_kwargs["order_by"] = order_by
        elif sort in self.SESSION_SORTS:
            if not flatten:
                return Response(
                    {"detail": "sorting by crash statistics requires flattening (flatten=1)"},
                    status=400,
                )

            def qs_load_func(queryset, total_offset, qs_offset, limit):
                # We want to fetch at least total_offset + limit releases to check, to make sure
                # we're not fetching only releases that were on previous pages.
                release_versions = list(
                    queryset.order_by_recent().values_list("version", flat=True)[
                        : total_offset + limit
                    ]
                )
                releases_with_session_data = check_releases_have_health_data(
                    organization.id,
                    filter_params["project_id"],
                    release_versions,
                    filter_params["start"]
                    if filter_params["start"]
                    else datetime.utcnow() - timedelta(days=90),
                    filter_params["end"] if filter_params["end"] else datetime.utcnow(),
                )
                valid_versions = [
                    rv for rv in release_versions if rv not in releases_with_session_data
                ]

                results = list(
                    Release.objects.filter(
                        organization_id=organization.id,
                        version__in=valid_versions,
                    ).order_by_recent()[qs_offset : qs_offset + limit]
                )
                return results

            paginator_cls = MergingOffsetPaginator
            paginator_kwargs.update(
                data_load_func=lambda offset, limit: get_project_releases_by_stability(
                    project_ids=filter_params["project_id"],
                    environments=filter_params.get("environment"),
                    scope=sort,
                    offset=offset,
                    stats_period=summary_stats_period,
                    limit=limit,
                ),
                data_count_func=lambda: get_project_releases_count(
                    organization_id=organization.id,
                    project_ids=filter_params["project_id"],
                    environments=filter_params.get("environment"),
                    scope=sort,
                    stats_period=summary_stats_period,
                ),
                apply_to_queryset=lambda queryset, rows: queryset.filter(
                    version__in=list(x[1] for x in rows)
                ),
                queryset_load_func=qs_load_func,
                key_from_model=lambda x: (x._for_project_id, x.version),
            )
        else:
            return Response({"detail": "invalid sort"}, status=400)

        queryset = queryset.extra(select=select_extra)
        queryset = add_date_filter_to_queryset(queryset, filter_params)

        with_adoption_stages = with_adoption_stages and features.has(
            "organizations:release-adoption-stage", organization, actor=request.user
        )

        return self.paginate(
            request=request,
            queryset=queryset,
            paginator_cls=paginator_cls,
            on_results=lambda x: serialize(
                x,
                request.user,
                with_health_data=with_health,
                with_adoption_stages=with_adoption_stages,
                health_stat=health_stat,
                health_stats_period=health_stats_period,
                summary_stats_period=summary_stats_period,
                environments=filter_params.get("environment") or None,
            ),
            **paginator_kwargs,
        )