Skip to content

Commit a675f8c

Browse files
authored
fix(replay): post replay views with timestamp = finished_at (#69079)
1 parent 80f9de5 commit a675f8c

File tree

4 files changed

+48
-6
lines changed

4 files changed

+48
-6
lines changed

src/sentry/replays/endpoints/project_replay_viewed_by.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import uuid
2+
from datetime import datetime
23
from typing import Any, TypedDict
34

45
from drf_spectacular.utils import extend_schema
@@ -17,6 +18,7 @@
1718
from sentry.models.project import Project
1819
from sentry.replays.query import query_replay_viewed_by_ids
1920
from sentry.replays.usecases.events import publish_replay_event, viewed_event
21+
from sentry.replays.usecases.query import execute_query, make_full_aggregation_query
2022
from sentry.services.hybrid_cloud.user.serial import serialize_generic_user
2123
from sentry.services.hybrid_cloud.user.service import user_service
2224

@@ -103,9 +105,33 @@ def post(self, request: Request, project: Project, replay_id: str) -> Response:
103105
except ValueError:
104106
return Response(status=404)
105107

106-
message = viewed_event(project.id, replay_id, request.user.id)
107-
publish_replay_event(message, is_async=False)
108+
# make a query to avoid overwriting the `finished_at` column
109+
filter_params = self.get_filter_params(request, project, date_filter_optional=False)
110+
finished_at_response = execute_query(
111+
query=make_full_aggregation_query(
112+
fields=["finished_at"],
113+
replay_ids=[replay_id],
114+
project_ids=[project.id],
115+
period_start=filter_params["start"],
116+
period_end=filter_params["end"],
117+
request_user_id=request.user.id,
118+
),
119+
tenant_id={"organization_id": project.organization.id} if project.organization else {},
120+
referrer="replays.endpoints.viewed_by_post",
121+
)["data"]
122+
if not finished_at_response:
123+
return Response(status=404)
108124

125+
finished_at = finished_at_response[0]["finished_at"]
126+
finished_at_ts = datetime.fromisoformat(finished_at).timestamp()
127+
128+
message = viewed_event(
129+
project.id,
130+
replay_id,
131+
request.user.id,
132+
finished_at_ts,
133+
)
134+
publish_replay_event(message, is_async=False)
109135
return Response(status=204)
110136

111137

src/sentry/replays/usecases/events.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ def archive_event(project_id: int, replay_id: str) -> str:
2828
)
2929

3030

31-
def viewed_event(project_id: int, replay_id: str, viewed_by_id: int) -> str:
31+
def viewed_event(project_id: int, replay_id: str, viewed_by_id: int, timestamp: float) -> str:
3232
"""Create a "replay_viewed" message."""
3333
return _replay_event(
3434
project_id=project_id,
3535
replay_id=replay_id,
3636
event={
3737
"type": "replay_viewed",
38-
"timestamp": time.time(),
38+
"timestamp": timestamp,
3939
"viewed_by_id": viewed_by_id,
4040
},
4141
)

tests/sentry/replays/test_project_replay_viewed_by.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,23 @@ def test_get_replay_viewed_by_feature_flag_disabled(self):
132132
@patch("sentry.replays.endpoints.project_replay_viewed_by.publish_replay_event")
133133
def test_post_replay_viewed_by(self, publish_replay_event):
134134
with self.feature(REPLAYS_FEATURES):
135+
finished_at_dt = datetime.datetime.now() - datetime.timedelta(seconds=20)
136+
self.store_replays(mock_replay(finished_at_dt, self.project.id, self.replay_id))
137+
135138
response = self.client.post(self.url, data="")
136139
assert response.status_code == 204
137140
assert publish_replay_event.called
138141

139142
replay_event = json.loads(publish_replay_event.call_args[0][0])
140143
payload = json.loads(bytes(replay_event["payload"]))
141144
assert payload["type"] == "replay_viewed"
145+
assert payload["viewed_by_id"] == self.user.id
146+
assert isinstance(payload["timestamp"], float)
147+
148+
# time should match the last replay segment with second-level precision
149+
assert int(payload["timestamp"]) == int(finished_at_dt.timestamp())
150+
151+
def test_post_replay_viewed_by_not_exist(self):
152+
with self.feature(REPLAYS_FEATURES):
153+
response = self.client.post(self.url, data="")
154+
assert response.status_code == 404

tests/sentry/replays/unit/usecases/test_events.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import time
2+
13
from sentry.replays.usecases.events import archive_event, viewed_event
24
from sentry.utils import json
35

@@ -28,7 +30,8 @@ def test_archive_event():
2830

2931
def test_viewed_event():
3032
"""Test "replay_viewed" event generator."""
31-
event = viewed_event(1, "2", 3)
33+
ts = time.time()
34+
event = viewed_event(1, "2", 3, ts)
3235

3336
parsed_event = json.loads(event)
3437
assert parsed_event["type"] == "replay_event"
@@ -40,4 +43,4 @@ def test_viewed_event():
4043
payload = json.loads(bytes(parsed_event["payload"]))
4144
assert payload["type"] == "replay_viewed"
4245
assert payload["viewed_by_id"] == 3
43-
assert isinstance(payload["timestamp"], float)
46+
assert payload["timestamp"] == ts

0 commit comments

Comments
 (0)