123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081 |
- from functools import reduce
- from rest_framework.request import Request
- from rest_framework.response import Response
- from sentry.api.base import EnvironmentMixin
- from sentry.api.bases import OrganizationMemberEndpoint
- from sentry.api.paginator import DateTimePaginator
- from sentry.api.serializers import OrganizationActivitySerializer, serialize
- from sentry.models import Activity, OrganizationMemberTeam, Project
- from sentry.types.activity import ActivityType
- class OrganizationActivityEndpoint(OrganizationMemberEndpoint, EnvironmentMixin):
- def get(self, request: Request, organization, member) -> Response:
- # There is an activity record created for both sides of the unmerge
- # operation, so we only need to include one of them here to avoid
- # showing the same entry twice.
- base_qs = Activity.objects.exclude(type=ActivityType.UNMERGE_SOURCE.value).values_list(
- "id", flat=True
- )
- # To make this query efficient, we have to hammer it into a weird format. This table is
- # extremely large and if we are making a query across many projects, in a lot of cases
- # Postgres decides that the best query plan is to iterate backwards on the datetime index.
- # This means it does something close to a table scan to get the results it wants - this can
- # be the case even for orgs with less than a page of activity rows, and often results in
- # queries that take > 30s, which get killed by stomper.
- # To convince Postgres to use the index on `(project_id, datetime)`, it basically needs to
- # see queries that filter on a single project id and then order by datetime. So we replicate
- # the query for every project and UNION ALL them together to get the candidate set of rows.
- # Then we sort these and return the final result. Convoluted, but it improves the query a
- # lot.
- # To make this work well with pagination, we have to also apply the pagination queries to
- # the subqueries.
- cursor = self.get_cursor_from_request(request)
- paginator = DateTimePaginator(base_qs, order_by="-datetime")
- if cursor is not None and cursor.value:
- cursor_value = paginator.value_from_cursor(cursor)
- else:
- cursor_value = 0
- base_qs = paginator.build_queryset(cursor_value, False)
- project_ids = list(
- Project.objects.filter(
- organization=organization,
- teams__in=OrganizationMemberTeam.objects.filter(organizationmember=member).values(
- "team"
- ),
- ).values_list("id", flat=True)
- )
- union_qs = Activity.objects.none()
- if project_ids:
- union_qs = reduce(
- lambda qs1, qs2: qs1.union(qs2, all=True),
- [
- base_qs.filter(project_id=project)[: paginator.max_limit]
- for project in project_ids
- ],
- )
- # We do `select_related` here to make the unions less heavy. This way we only join these
- # table for the rows we actually want.
- queryset = Activity.objects.filter(id__in=union_qs[: paginator.max_limit]).select_related(
- "project", "group", "user"
- )
- return self.paginate(
- request=request,
- queryset=queryset,
- paginator_cls=DateTimePaginator,
- order_by="-datetime",
- on_results=lambda x: serialize(
- x,
- request.user,
- OrganizationActivitySerializer(
- environment_func=self._get_environment_func(request, organization.id)
- ),
- ),
- )
|