diff --git a/src/sentry/apidocs/examples/replay_examples.py b/src/sentry/apidocs/examples/replay_examples.py index 7dc96ad4888b9e..f3950639384a70 100644 --- a/src/sentry/apidocs/examples/replay_examples.py +++ b/src/sentry/apidocs/examples/replay_examples.py @@ -200,6 +200,71 @@ class ReplayExamples: ) ] + GET_REPLAY_DELETION_JOBS = [ + OpenApiExample( + "List replay deletion jobs", + value={ + "data": [ + { + "id": 1, + "dateCreated": "2024-01-01T00:00:00Z", + "dateUpdated": "2024-01-01T00:05:00Z", + "rangeStart": "2023-12-01T00:00:00Z", + "rangeEnd": "2024-01-01T00:00:00Z", + "environments": ["production"], + "status": "pending", + "query": "user.email:test@example.com", + "countDeleted": 0, + } + ] + }, + status_codes=["200"], + response_only=True, + ) + ] + + CREATE_REPLAY_DELETION_JOB = [ + OpenApiExample( + "Create a replay deletion job", + value={ + "data": { + "id": 1, + "dateCreated": "2024-01-01T00:00:00Z", + "dateUpdated": "2024-01-01T00:05:00Z", + "rangeStart": "2023-12-01T00:00:00Z", + "rangeEnd": "2024-01-01T00:00:00Z", + "environments": ["production"], + "status": "pending", + "query": "user.email:test@example.com", + "countDeleted": 0, + } + }, + status_codes=["201"], + response_only=True, + ) + ] + + GET_REPLAY_DELETION_JOB = [ + OpenApiExample( + "Get a replay deletion job", + value={ + "data": { + "id": 1, + "dateCreated": "2024-01-01T00:00:00Z", + "dateUpdated": "2024-01-01T00:05:00Z", + "rangeStart": "2023-12-01T00:00:00Z", + "rangeEnd": "2024-01-01T00:00:00Z", + "environments": ["production"], + "status": "pending", + "query": "user.email:test@example.com", + "countDeleted": 0, + } + }, + status_codes=["200"], + response_only=True, + ) + ] + GET_REPLAY_VIEWED_BY = [ OpenApiExample( "Get list of users who have viewed a replay", diff --git a/src/sentry/apidocs/parameters.py b/src/sentry/apidocs/parameters.py index bebff27aef1a10..2cbb90a209ba93 100644 --- a/src/sentry/apidocs/parameters.py +++ b/src/sentry/apidocs/parameters.py @@ -927,6 +927,14 @@ class ReplayParams: description="""The ID of the segment you'd like to retrieve.""", ) + JOB_ID = OpenApiParameter( + name="job_id", + location="path", + required=True, + type=OpenApiTypes.INT, + description="""The ID of the replay deletion job you'd like to retrieve.""", + ) + class NotificationParams: TRIGGER_TYPE = OpenApiParameter( diff --git a/src/sentry/replays/endpoints/project_replay_jobs_delete.py b/src/sentry/replays/endpoints/project_replay_jobs_delete.py index 18239a97f386ed..9326af6caf8991 100644 --- a/src/sentry/replays/endpoints/project_replay_jobs_delete.py +++ b/src/sentry/replays/endpoints/project_replay_jobs_delete.py @@ -1,3 +1,8 @@ +from __future__ import annotations + +from typing import TypedDict + +from drf_spectacular.utils import extend_schema from rest_framework import serializers from rest_framework.request import Request from rest_framework.response import Response @@ -10,12 +15,36 @@ from sentry.api.exceptions import ResourceDoesNotExist from sentry.api.paginator import OffsetPaginator from sentry.api.serializers import Serializer, serialize +from sentry.apidocs.constants import RESPONSE_BAD_REQUEST, RESPONSE_FORBIDDEN, RESPONSE_NOT_FOUND +from sentry.apidocs.examples.replay_examples import ReplayExamples +from sentry.apidocs.parameters import GlobalParams, ReplayParams +from sentry.apidocs.utils import inline_sentry_response_serializer from sentry.replays.endpoints.project_replay_endpoint import ProjectReplayEndpoint from sentry.replays.models import ReplayDeletionJobModel from sentry.replays.permissions import has_replay_permission from sentry.replays.tasks import run_bulk_replay_delete_job +class ReplayDeletionJobResponseData(TypedDict): + id: int + dateCreated: str + dateUpdated: str + rangeStart: str + rangeEnd: str + environments: list[str] + status: str + query: str + countDeleted: int + + +class ReplayDeletionJobListResponse(TypedDict): + data: list[ReplayDeletionJobResponseData] + + +class ReplayDeletionJobDetailResponse(TypedDict): + data: ReplayDeletionJobResponseData + + class ReplayDeletionJobPermission(ProjectPermission): scope_map = { "GET": ["project:write", "project:admin"], @@ -57,14 +86,29 @@ class ReplayDeletionJobCreateSerializer(serializers.Serializer): @cell_silo_endpoint +@extend_schema(tags=["Replays"]) class ProjectReplayDeletionJobsIndexEndpoint(ProjectEndpoint): owner = ApiOwner.DATA_BROWSING publish_status = { - "GET": ApiPublishStatus.PRIVATE, - "POST": ApiPublishStatus.PRIVATE, + "GET": ApiPublishStatus.PUBLIC, + "POST": ApiPublishStatus.PUBLIC, } permission_classes = (ReplayDeletionJobPermission,) + @extend_schema( + operation_id="List Replay Deletion Jobs", + parameters=[ + GlobalParams.ORG_ID_OR_SLUG, + GlobalParams.PROJECT_ID_OR_SLUG, + ], + responses={ + 200: inline_sentry_response_serializer( + "ListReplayDeletionJobs", ReplayDeletionJobListResponse + ), + 403: RESPONSE_FORBIDDEN, + }, + examples=ReplayExamples.GET_REPLAY_DELETION_JOBS, + ) def get(self, request: Request, project) -> Response: """ Retrieve a collection of replay delete jobs. @@ -86,6 +130,22 @@ def get(self, request: Request, project) -> Response: paginator_cls=OffsetPaginator, ) + @extend_schema( + operation_id="Create a Replay Deletion Job", + parameters=[ + GlobalParams.ORG_ID_OR_SLUG, + GlobalParams.PROJECT_ID_OR_SLUG, + ], + request=ReplayDeletionJobCreateSerializer, + responses={ + 201: inline_sentry_response_serializer( + "CreateReplayDeletionJob", ReplayDeletionJobDetailResponse + ), + 400: RESPONSE_BAD_REQUEST, + 403: RESPONSE_FORBIDDEN, + }, + examples=ReplayExamples.CREATE_REPLAY_DELETION_JOB, + ) def post(self, request: Request, project) -> Response: """ Create a new replay deletion job. @@ -132,12 +192,29 @@ def post(self, request: Request, project) -> Response: @cell_silo_endpoint +@extend_schema(tags=["Replays"]) class ProjectReplayDeletionJobDetailEndpoint(ProjectReplayEndpoint): publish_status = { - "GET": ApiPublishStatus.PRIVATE, + "GET": ApiPublishStatus.PUBLIC, } permission_classes = (ReplayDeletionJobPermission,) + @extend_schema( + operation_id="Get a Replay Deletion Job", + parameters=[ + GlobalParams.ORG_ID_OR_SLUG, + GlobalParams.PROJECT_ID_OR_SLUG, + ReplayParams.JOB_ID, + ], + responses={ + 200: inline_sentry_response_serializer( + "GetReplayDeletionJob", ReplayDeletionJobDetailResponse + ), + 403: RESPONSE_FORBIDDEN, + 404: RESPONSE_NOT_FOUND, + }, + examples=ReplayExamples.GET_REPLAY_DELETION_JOB, + ) def get(self, request: Request, project, job_id: int) -> Response: """ Fetch a replay delete job instance.