Skip to content

Commit 6db38a2

Browse files
authored
feat(replays): switch to replay_id column from tag (#68950)
We want to have the replay_id column be queried on instead of the tag that we previously had, as we no longer are populating the tag for non javascript platforms. Depends on getsentry/snuba#5777.
1 parent f69663c commit 6db38a2

File tree

8 files changed

+62
-83
lines changed

8 files changed

+62
-83
lines changed

src/sentry/models/group.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ class EventOrdering(Enum):
213213
LATEST = ["-timestamp", "-event_id"]
214214
OLDEST = ["timestamp", "event_id"]
215215
MOST_HELPFUL = [
216-
"-replayId",
216+
"-replay.id",
217217
"-profile.id",
218218
"num_processing_errors",
219219
"-trace.sampled",

src/sentry/replays/endpoints/organization_replay_count.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,6 @@
2424
from sentry.snuba.dataset import Dataset
2525
from sentry.types.ratelimit import RateLimit, RateLimitCategory
2626

27-
MAX_REPLAY_COUNT = 51
28-
MAX_VALS_PROVIDED = {
29-
"issue.id": 25,
30-
"transaction": 25,
31-
"replay_id": 100,
32-
}
33-
34-
FILTER_HAS_A_REPLAY = "AND !replayId:''"
35-
3627

3728
class ReplayDataSourceValidator(serializers.Serializer):
3829
data_source = serializers.ChoiceField(

src/sentry/replays/usecases/replay_counts.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
from sentry.api.event_search import ParenExpression, SearchFilter, parse_search_query
1010
from sentry.models.group import Group
1111
from sentry.replays.query import query_replays_count
12-
from sentry.search.events.builder import QueryBuilder
13-
from sentry.search.events.types import QueryBuilderConfig, SnubaParams
12+
from sentry.search.events.types import SnubaParams
13+
from sentry.snuba import discover, issue_platform
1414
from sentry.snuba.dataset import Dataset
1515

1616
MAX_REPLAY_COUNT = 51
@@ -20,7 +20,7 @@
2020
"replay_id": 100,
2121
}
2222

23-
FILTER_HAS_A_REPLAY = " AND !replayId:''"
23+
FILTER_HAS_A_REPLAY = ' AND !replay.id:""'
2424

2525

2626
def get_replay_counts(snuba_params: SnubaParams, query, return_ids, data_source) -> dict[str, Any]:
@@ -30,6 +30,7 @@ def get_replay_counts(snuba_params: SnubaParams, query, return_ids, data_source)
3030
- If the identifier is 'replay_id', the returned count is always 1. Use this to check the existence of replay_ids
3131
- Set the flag 'return_ids' to get the replay_ids (32 char hex strings) for each identifier
3232
"""
33+
3334
if snuba_params.start is None or snuba_params.end is None or snuba_params.organization is None:
3435
raise ValueError("Must provide start and end")
3536

@@ -62,6 +63,12 @@ def _get_replay_id_mappings(
6263
If select_column is replay_id, return an identity map of replay_id -> [replay_id].
6364
The keys of the returned dict are UUIDs, represented as 32 char hex strings (all '-'s stripped)
6465
"""
66+
67+
if data_source == Dataset.Discover:
68+
search_query_func = discover.query
69+
elif data_source == Dataset.IssuePlatform:
70+
search_query_func = issue_platform.query # type: ignore[assignment]
71+
6572
select_column, column_value = _get_select_column(query)
6673
query = query + FILTER_HAS_A_REPLAY if data_source == Dataset.Discover else query
6774

@@ -95,27 +102,21 @@ def _get_replay_id_mappings(
95102
projects=[group.project for group in groups],
96103
)
97104

98-
builder = QueryBuilder(
99-
dataset=data_source,
105+
results = search_query_func(
100106
params={},
101107
snuba_params=snuba_params,
102-
selected_columns=["group_uniq_array(100,replayId)", select_column],
108+
selected_columns=["group_uniq_array(100,replay.id)", select_column],
103109
query=query,
104110
limit=25,
105111
offset=0,
106-
config=QueryBuilderConfig(
107-
functions_acl=["group_uniq_array"],
108-
),
109-
)
110-
111-
discover_results = builder.run_query(
112-
referrer="api.organization-issue-replay-count", use_cache=True
112+
functions_acl=["group_uniq_array"],
113+
referrer="api.organization-issue-replay-count",
113114
)
114115

115116
replay_id_to_issue_map = defaultdict(list)
116117

117-
for row in discover_results["data"]:
118-
for replay_id in row["group_uniq_array_100_replayId"]:
118+
for row in results["data"]:
119+
for replay_id in row["group_uniq_array_100_replay_id"]:
119120
# When no replay exists these strings are provided in their empty
120121
# state rather than null. This can cause downstream problems so
121122
# we filter them out.

src/sentry/search/events/builder/discover.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -307,13 +307,11 @@ def resolve_column_name(self, col: str) -> str:
307307
# TODO when utils/snuba.py becomes typed don't need this extra annotation
308308
column_resolver: Callable[[str], str] = resolve_column(self.dataset)
309309
column_name = column_resolver(col)
310-
311310
# If the original column was passed in as tag[X], then there won't be a conflict
312311
# and there's no need to prefix the tag
313312
if not col.startswith("tags[") and column_name.startswith("tags["):
314313
self.prefixed_to_tag_map[f"tags_{col}"] = col
315314
self.tag_to_prefixed_map[col] = f"tags_{col}"
316-
317315
return column_name
318316

319317
def resolve_query(
@@ -1091,7 +1089,6 @@ def aliased_column(self, name: str) -> SelectType:
10911089
:param name: The unresolved sentry name.
10921090
:param alias: The expected alias in the result.
10931091
"""
1094-
10951092
# TODO: This method should use an aliased column from the SDK once
10961093
# that is available to skip these hacks that we currently have to
10971094
# do aliasing.
@@ -1578,14 +1575,16 @@ def default_filter_converter(
15781575
raise InvalidSearchQuery(INVALID_SPAN_ID.format(name))
15791576

15801577
# Validate event ids, trace ids, and profile ids are uuids
1581-
if name in {"id", "trace", "profile.id"}:
1578+
if name in {"id", "trace", "profile.id", "replay.id"}:
15821579
if search_filter.value.is_wildcard():
15831580
raise InvalidSearchQuery(WILDCARD_NOT_ALLOWED.format(name))
15841581
elif not search_filter.value.is_event_id():
15851582
if name == "trace":
15861583
label = "Filter Trace ID"
15871584
elif name == "profile.id":
15881585
label = "Filter Profile ID"
1586+
elif name == "replay.id":
1587+
label = "Filter Replay ID"
15891588
else:
15901589
label = "Filter ID"
15911590
raise InvalidSearchQuery(INVALID_ID_DETAILS.format(label))

src/sentry/snuba/events.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -742,8 +742,20 @@ class Columns(Enum):
742742
REPLAY_ID = Column(
743743
group_name=None,
744744
event_name="replay_id",
745-
transaction_name=None,
746-
discover_name=None,
745+
transaction_name="replay_id",
746+
discover_name="replay_id",
747+
issue_platform_name="replay_id",
748+
alias="replay.id",
749+
)
750+
# We used to set the replay_id as a tag on error events as
751+
# replayId. We allow this query for backwards compatibility,
752+
# but in the future shouldn't be displayed in the UI anywhere
753+
# as a suggested column.
754+
REPLAY_ID_DEPRECATED = Column(
755+
group_name=None,
756+
event_name="replay_id",
757+
transaction_name="replay_id",
758+
discover_name="replay_id",
747759
issue_platform_name="replay_id",
748760
alias="replayId",
749761
)

src/sentry/utils/snuba.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,13 @@ def log_snuba_info(content):
144144
"category": "sentry_tags[category]",
145145
"span.category": "sentry_tags[category]",
146146
"span.status_code": "sentry_tags[status_code]",
147+
"replay_id": "sentry_tags[replay_id]",
148+
"replay.id": "sentry_tags[replay_id]",
147149
"resource.render_blocking_status": "sentry_tags[resource.render_blocking_status]",
148150
"http.response_content_length": "sentry_tags[http.response_content_length]",
149151
"http.decoded_response_content_length": "sentry_tags[http.decoded_response_content_length]",
150152
"http.response_transfer_size": "sentry_tags[http.response_transfer_size]",
151153
"app_start_type": "sentry_tags[app_start_type]",
152-
"replay.id": "sentry_tags[replay_id]",
153154
"browser.name": "sentry_tags[browser.name]",
154155
"origin.transaction": "sentry_tags[transaction]",
155156
"is_transaction": "is_segment",
@@ -1225,6 +1226,7 @@ def _resolve_column(col):
12251226

12261227
# Some dataset specific logic:
12271228
if dataset == Dataset.Discover:
1229+
12281230
if isinstance(col, (list, tuple)) or col in ("project_id", "group_id"):
12291231
return col
12301232
elif (
@@ -1249,7 +1251,6 @@ def _resolve_column(col):
12491251
span_op_breakdown_name = get_span_op_breakdown_name(col)
12501252
if "span_op_breakdowns_key" in DATASETS[dataset] and span_op_breakdown_name:
12511253
return f"span_op_breakdowns[{span_op_breakdown_name}]"
1252-
12531254
return f"tags[{col}]"
12541255

12551256
return _resolve_column

tests/sentry/replays/test_organization_replay_count.py

Lines changed: 19 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ class OrganizationReplayCountEndpointTest(
3030
def setUp(self):
3131
super().setUp()
3232
self.project.update(flags=F("flags").bitor(Project.flags.has_replays))
33-
self.min_ago = before_now(minutes=1)
33+
self.min_ago = before_now(minutes=2)
3434
self.login_as(user=self.user)
3535
self.url = reverse(
3636
"sentry-api-0-organization-replay-count",
3737
kwargs={"organization_slug": self.project.organization.slug},
3838
)
3939
self.features = {"organizations:session-replay": True}
4040

41-
def test_simple(self):
41+
def test_simple_b(self):
4242
event_id_a = "a" * 32
4343
event_id_b = "b" * 32
4444
replay1_id = uuid.uuid4().hex
@@ -70,7 +70,7 @@ def test_simple(self):
7070
data={
7171
"event_id": event_id_a,
7272
"timestamp": iso_format(self.min_ago),
73-
"tags": {"replayId": replay1_id},
73+
"contexts": {"replay": {"replay_id": replay1_id}},
7474
"fingerprint": ["group-1"],
7575
},
7676
project_id=self.project.id,
@@ -79,7 +79,7 @@ def test_simple(self):
7979
data={
8080
"event_id": uuid.uuid4().hex,
8181
"timestamp": iso_format(self.min_ago),
82-
"tags": {"replayId": replay2_id},
82+
"contexts": {"replay": {"replay_id": replay2_id}},
8383
"fingerprint": ["group-1"],
8484
},
8585
project_id=self.project.id,
@@ -88,7 +88,9 @@ def test_simple(self):
8888
data={
8989
"event_id": event_id_b,
9090
"timestamp": iso_format(self.min_ago),
91-
"tags": {"replayId": "z" * 32}, # a replay id that doesn't exist
91+
"contexts": {
92+
"replay": {"replay_id": uuid.uuid4().hex}
93+
}, # a replay id that doesn't exist
9294
"fingerprint": ["group-1"],
9395
},
9496
project_id=self.project.id,
@@ -97,14 +99,15 @@ def test_simple(self):
9799
data={
98100
"event_id": event_id_b,
99101
"timestamp": iso_format(self.min_ago),
100-
"tags": {"replayId": replay3_id},
102+
"contexts": {"replay": {"replay_id": replay3_id}},
101103
"fingerprint": ["group-2"],
102104
},
103105
project_id=self.project.id,
104106
)
105107

106108
query = {"query": f"(issue.id:[{event_a.group.id}, {event_c.group.id}] or abc)"}
107109
with self.feature(self.features):
110+
108111
response = self.client.get(self.url, query, format="json")
109112

110113
expected = {
@@ -146,7 +149,7 @@ def test_simple_return_ids(self):
146149
data={
147150
"event_id": event_id_a,
148151
"timestamp": iso_format(self.min_ago),
149-
"tags": {"replayId": replay1_id},
152+
"contexts": {"replay": {"replay_id": replay1_id}},
150153
"fingerprint": ["group-1"],
151154
},
152155
project_id=self.project.id,
@@ -155,7 +158,7 @@ def test_simple_return_ids(self):
155158
data={
156159
"event_id": uuid.uuid4().hex,
157160
"timestamp": iso_format(self.min_ago),
158-
"tags": {"replayId": replay2_id},
161+
"contexts": {"replay": {"replay_id": replay2_id}},
159162
"fingerprint": ["group-1"],
160163
},
161164
project_id=self.project.id,
@@ -164,7 +167,7 @@ def test_simple_return_ids(self):
164167
data={
165168
"event_id": event_id_b,
166169
"timestamp": iso_format(self.min_ago),
167-
"tags": {"replayId": "z" * 32}, # a replay id that doesn't exist
170+
"contexts": {"replay": {"replay_id": uuid.uuid4().hex}},
168171
"fingerprint": ["group-1"],
169172
},
170173
project_id=self.project.id,
@@ -173,7 +176,7 @@ def test_simple_return_ids(self):
173176
data={
174177
"event_id": event_id_b,
175178
"timestamp": iso_format(self.min_ago),
176-
"tags": {"replayId": replay3_id},
179+
"contexts": {"replay": {"replay_id": replay3_id}},
177180
"fingerprint": ["group-2"],
178181
},
179182
project_id=self.project.id,
@@ -315,7 +318,7 @@ def test_one_replay_multiple_issues(self):
315318
data={
316319
"event_id": event_id_a,
317320
"timestamp": iso_format(self.min_ago),
318-
"tags": {"replayId": replay1_id},
321+
"contexts": {"replay": {"replay_id": replay1_id}},
319322
"fingerprint": ["group-1"],
320323
},
321324
project_id=self.project.id,
@@ -324,7 +327,7 @@ def test_one_replay_multiple_issues(self):
324327
data={
325328
"event_id": event_id_b,
326329
"timestamp": iso_format(self.min_ago),
327-
"tags": {"replayId": replay1_id},
330+
"contexts": {"replay": {"replay_id": replay1_id}},
328331
"fingerprint": ["group-2"],
329332
},
330333
project_id=self.project.id,
@@ -357,7 +360,7 @@ def test_one_replay_same_issue_twice(self):
357360
data={
358361
"event_id": event_id_a,
359362
"timestamp": iso_format(self.min_ago),
360-
"tags": {"replayId": replay1_id},
363+
"contexts": {"replay": {"replay_id": replay1_id}},
361364
"fingerprint": ["group-1"],
362365
},
363366
project_id=self.project.id,
@@ -366,7 +369,7 @@ def test_one_replay_same_issue_twice(self):
366369
data={
367370
"event_id": event_id_b,
368371
"timestamp": iso_format(self.min_ago),
369-
"tags": {"replayId": replay1_id},
372+
"contexts": {"replay": {"replay_id": replay1_id}},
370373
"fingerprint": ["group-1"],
371374
},
372375
project_id=self.project.id,
@@ -397,7 +400,7 @@ def test_simple_transaction(self):
397400
data={
398401
"event_id": event_id_a,
399402
"timestamp": iso_format(self.min_ago),
400-
"tags": {"replayId": replay1_id},
403+
"contexts": {"replay": {"replay_id": replay1_id}},
401404
"transaction": "t-1",
402405
},
403406
project_id=self.project.id,
@@ -413,36 +416,6 @@ def test_simple_transaction(self):
413416
assert response.status_code == 200, response.content
414417
assert response.data == expected
415418

416-
def test_max_51(self):
417-
replay_ids = [uuid.uuid4().hex for _ in range(100)]
418-
for replay_id in replay_ids:
419-
self.store_replays(
420-
mock_replay(
421-
datetime.datetime.now() - datetime.timedelta(seconds=22),
422-
self.project.id,
423-
replay_id,
424-
)
425-
)
426-
event_a = self.store_event(
427-
data={
428-
"event_id": uuid.uuid4().hex,
429-
"timestamp": iso_format(self.min_ago),
430-
"tags": {"replayId": replay_id},
431-
"fingerprint": ["group-1"],
432-
},
433-
project_id=self.project.id,
434-
)
435-
436-
query = {"query": f"issue.id:[{event_a.group.id}]"}
437-
with self.feature(self.features):
438-
response = self.client.get(self.url, query, format="json")
439-
440-
expected = {
441-
event_a.group.id: 51,
442-
}
443-
assert response.status_code == 200, response.content
444-
assert response.data == expected
445-
446419
def test_invalid_params_need_one_issue_id(self):
447420
query = {"query": ""}
448421
with self.feature(self.features):
@@ -580,7 +553,7 @@ def test_cross_organization_lookups(self):
580553
data={
581554
"event_id": event_id_a,
582555
"timestamp": iso_format(self.min_ago),
583-
"tags": {"replayId": replay1_id},
556+
"contexts": {"replay": {"replay_id": replay1_id}},
584557
"fingerprint": ["group-1"],
585558
},
586559
project_id=self.project.id,
@@ -607,7 +580,6 @@ def test_cross_organization_lookups(self):
607580
data={
608581
"event_id": event_id_b,
609582
"timestamp": iso_format(self.min_ago),
610-
"tags": {"replayId": replay2_id},
611583
"fingerprint": ["group-2"],
612584
},
613585
project_id=project.id,

0 commit comments

Comments
 (0)