|
@@ -1,3 +1,5 @@
|
|
|
+from collections.abc import Callable
|
|
|
+
|
|
|
from drf_spectacular.utils import extend_schema
|
|
|
from rest_framework.exceptions import ParseError
|
|
|
from rest_framework.request import Request
|
|
@@ -9,7 +11,6 @@ from sentry.api.api_publish_status import ApiPublishStatus
|
|
|
from sentry.api.base import region_silo_endpoint
|
|
|
from sentry.api.bases.organization import NoProjects, OrganizationEndpoint
|
|
|
from sentry.api.event_search import parse_search_query
|
|
|
-from sentry.api.paginator import GenericOffsetPaginator
|
|
|
from sentry.apidocs.constants import RESPONSE_BAD_REQUEST, RESPONSE_FORBIDDEN
|
|
|
from sentry.apidocs.examples.replay_examples import ReplayExamples
|
|
|
from sentry.apidocs.parameters import GlobalParams
|
|
@@ -17,9 +18,10 @@ from sentry.apidocs.utils import inline_sentry_response_serializer
|
|
|
from sentry.exceptions import InvalidSearchQuery
|
|
|
from sentry.models.organization import Organization
|
|
|
from sentry.replays.post_process import ReplayDetailsResponse, process_raw_response
|
|
|
-from sentry.replays.query import query_replays_collection, replay_url_parser_config
|
|
|
+from sentry.replays.query import query_replays_collection_raw, replay_url_parser_config
|
|
|
from sentry.replays.usecases.errors import handled_snuba_exceptions
|
|
|
from sentry.replays.validators import ReplayValidator
|
|
|
+from sentry.utils.cursors import Cursor, CursorResult
|
|
|
|
|
|
|
|
|
@region_silo_endpoint
|
|
@@ -76,7 +78,7 @@ class OrganizationReplayIndexEndpoint(OrganizationEndpoint):
|
|
|
if key not in filter_params:
|
|
|
filter_params[key] = value
|
|
|
|
|
|
- def data_fn(offset, limit):
|
|
|
+ def data_fn(offset: int, limit: int):
|
|
|
try:
|
|
|
search_filters = parse_search_query(
|
|
|
request.query_params.get("query", ""), config=replay_url_parser_config
|
|
@@ -84,7 +86,7 @@ class OrganizationReplayIndexEndpoint(OrganizationEndpoint):
|
|
|
except InvalidSearchQuery as e:
|
|
|
raise ParseError(str(e))
|
|
|
|
|
|
- return query_replays_collection(
|
|
|
+ return query_replays_collection_raw(
|
|
|
project_ids=filter_params["project_id"],
|
|
|
start=filter_params["start"],
|
|
|
end=filter_params["end"],
|
|
@@ -100,7 +102,7 @@ class OrganizationReplayIndexEndpoint(OrganizationEndpoint):
|
|
|
|
|
|
return self.paginate(
|
|
|
request=request,
|
|
|
- paginator=GenericOffsetPaginator(data_fn=data_fn),
|
|
|
+ paginator=ReplayPaginator(data_fn=data_fn),
|
|
|
on_results=lambda results: {
|
|
|
"data": process_raw_response(
|
|
|
results,
|
|
@@ -108,3 +110,21 @@ class OrganizationReplayIndexEndpoint(OrganizationEndpoint):
|
|
|
)
|
|
|
},
|
|
|
)
|
|
|
+
|
|
|
+
|
|
|
+class ReplayPaginator:
|
|
|
+ """Defers all pagination decision making to the implementation."""
|
|
|
+
|
|
|
+ def __init__(self, data_fn: Callable[[int, int], tuple[list, bool]]) -> None:
|
|
|
+ self.data_fn = data_fn
|
|
|
+
|
|
|
+ def get_result(self, limit: int, cursor=None):
|
|
|
+ assert limit > 0
|
|
|
+ offset = int(cursor.offset) if cursor is not None else 0
|
|
|
+ data, has_more = self.data_fn(offset, limit + 1)
|
|
|
+
|
|
|
+ return CursorResult(
|
|
|
+ data,
|
|
|
+ prev=Cursor(0, max(0, offset - limit), True, offset > 0),
|
|
|
+ next=Cursor(0, max(0, offset + limit), False, has_more),
|
|
|
+ )
|