Skip to content

Commit 624fb34

Browse files
authored
fix(seer): Set viewer context for missing issue-assignment paths (#127873)
This PR fixes only the issue-assignment paths that can reach the first-assignment Seer summary without ViewerContext. There is no higher up place that we could assign ViewerContext where we have a valid User and we would need to default to System which is incorrect
1 parent 0586ea0 commit 624fb34

6 files changed

Lines changed: 123 additions & 9 deletions

File tree

‎src/sentry/issues/ingest.py‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from sentry.utils import json, metrics, redis
3737
from sentry.utils.strings import truncatechars
3838
from sentry.utils.tag_normalization import normalized_sdk_tag_from_event
39+
from sentry.viewer_context import ActorType, ViewerContext, viewer_context_scope
3940
from sentry.workflow_engine.models import IncidentGroupOpenPeriod
4041
from sentry.workflow_engine.processors.detector import (
4142
associate_new_group_with_detector,
@@ -302,7 +303,16 @@ def save_issue_from_occurrence(
302303
try:
303304
# Since this calls hybrid cloud it has to be run outside the transaction
304305
assignee = occurrence.assignee.resolve()
305-
with action_context_scope(source=ActionSource.SYSTEM, actor=SYSTEM_ACTOR):
306+
with (
307+
viewer_context_scope(
308+
ViewerContext(
309+
organization_id=project.organization_id,
310+
project_id=project.id,
311+
actor_type=ActorType.SYSTEM,
312+
)
313+
),
314+
action_context_scope(source=ActionSource.SYSTEM, actor=SYSTEM_ACTOR),
315+
):
306316
GroupAssignee.objects.assign(group, assignee, create_only=True)
307317
except Exception:
308318
logger.exception("Failed to process assignment for occurrence")

‎src/sentry/models/groupassignee.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import contextvars
34
import logging
45
from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple, TypedDict
56

@@ -197,8 +198,10 @@ def assign(
197198
affected = True
198199

199200
if affected:
201+
assignment_context = contextvars.copy_context()
200202
transaction.on_commit(
201-
lambda: issue_assigned.send_robust(
203+
lambda: assignment_context.run(
204+
issue_assigned.send_robust,
202205
project=group.project,
203206
group=group,
204207
user=acting_user,

‎src/sentry/receivers/releases.py‎

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
from sentry.users.services.user import RpcUser
4646
from sentry.users.services.user_option import get_option_from_list, user_option_service
4747
from sentry.utils.cache import cache
48+
from sentry.viewer_context import ActorType, ViewerContext, viewer_context_scope
4849

4950
logger = logging.getLogger(__name__)
5051

@@ -219,8 +220,19 @@ def resolved_in_commit(instance: Commit, created, **kwargs):
219220

220221
if acting_user:
221222
if self_assign_issue == "1" and not group.assignee_set.exists():
222-
with action_context_scope(
223-
source=ActionSource.SYSTEM, actor=GroupActionActor.user(acting_user.id)
223+
with (
224+
viewer_context_scope(
225+
ViewerContext(
226+
organization_id=group.project.organization_id,
227+
project_id=group.project_id,
228+
user_id=acting_user.id,
229+
actor_type=ActorType.USER,
230+
)
231+
),
232+
action_context_scope(
233+
source=ActionSource.SYSTEM,
234+
actor=GroupActionActor.user(acting_user.id),
235+
),
224236
):
225237
GroupAssignee.objects.assign(
226238
group=group, assigned_to=acting_user, acting_user=acting_user
@@ -315,8 +327,19 @@ def resolved_in_pull_request(instance: PullRequest, created, **kwargs):
315327
linked_id=instance.id,
316328
)
317329
if acting_user:
318-
with action_context_scope(
319-
source=ActionSource.SYSTEM, actor=GroupActionActor.user(acting_user.id)
330+
with (
331+
viewer_context_scope(
332+
ViewerContext(
333+
organization_id=group.project.organization_id,
334+
project_id=group.project_id,
335+
user_id=acting_user.id,
336+
actor_type=ActorType.USER,
337+
)
338+
),
339+
action_context_scope(
340+
source=ActionSource.SYSTEM,
341+
actor=GroupActionActor.user(acting_user.id),
342+
),
320343
):
321344
GroupAssignee.objects.assign(
322345
group=group, assigned_to=acting_user, acting_user=acting_user

‎tests/sentry/issues/test_ingest.py‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from dataclasses import dataclass
33
from datetime import UTC, datetime, timedelta
44
from hashlib import md5
5+
from typing import Any
56
from unittest import mock
67
from unittest.mock import patch
78

@@ -50,6 +51,7 @@
5051
from sentry.utils import json
5152
from sentry.utils.samples import load_data
5253
from sentry.utils.snuba import raw_query
54+
from sentry.viewer_context import ActorType, ViewerContext, get_viewer_context
5355
from sentry.workflow_engine.models import DataCondition, DataConditionGroup, DataPacket
5456
from sentry.workflow_engine.models.alertrule_detector import AlertRuleDetector
5557
from sentry.workflow_engine.models.detector_group import DetectorGroup
@@ -161,11 +163,29 @@ def test_new_group_with_priority(self) -> None:
161163
def test_new_group_with_user_assignee(self) -> None:
162164
event = self.store_event(data={}, project_id=self.project.id)
163165
occurrence = self.build_occurrence(event_id=event.event_id, assignee=f"user:{self.user.id}")
164-
_, group_info = save_issue_occurrence(occurrence.to_dict(), event)
166+
captured_contexts: list[ViewerContext | None] = []
167+
assign = GroupAssignee.objects.assign
168+
169+
def capture_context(*args: Any, **kwargs: Any) -> Any:
170+
captured_contexts.append(get_viewer_context())
171+
return assign(*args, **kwargs)
172+
173+
with patch(
174+
"sentry.issues.ingest.GroupAssignee.objects.assign", side_effect=capture_context
175+
):
176+
_, group_info = save_issue_occurrence(occurrence.to_dict(), event)
177+
165178
assert group_info is not None
166179
assert group_info.group.priority == PriorityLevel.LOW
167180
assignee = GroupAssignee.objects.get(group=group_info.group)
168181
assert assignee.user_id == self.user.id
182+
assert captured_contexts == [
183+
ViewerContext(
184+
organization_id=self.organization.id,
185+
project_id=self.project.id,
186+
actor_type=ActorType.SYSTEM,
187+
)
188+
]
169189

170190
def test_new_group_with_team_assignee(self) -> None:
171191
event = self.store_event(data={}, project_id=self.project.id)

‎tests/sentry/models/test_groupassignee.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,36 @@
1515
from sentry.testutils.skips import requires_snuba
1616
from sentry.types.activity import ActivityType
1717
from sentry.users.services.user.service import user_service
18+
from sentry.viewer_context import ActorType as ViewerActorType
19+
from sentry.viewer_context import ViewerContext, get_viewer_context, viewer_context_scope
1820

1921
pytestmark = requires_snuba
2022

2123

2224
class GroupAssigneeTestCase(TestCase):
25+
def test_assign_preserves_viewer_context_until_on_commit(self) -> None:
26+
expected_context = ViewerContext(
27+
organization_id=self.organization.id,
28+
project_id=self.project.id,
29+
user_id=self.user.id,
30+
actor_type=ViewerActorType.USER,
31+
)
32+
captured_contexts: list[ViewerContext | None] = []
33+
34+
with mock.patch("sentry.models.groupassignee.issue_assigned.send_robust") as send_robust:
35+
send_robust.side_effect = lambda **kwargs: captured_contexts.append(
36+
get_viewer_context()
37+
)
38+
with self.capture_on_commit_callbacks() as callbacks:
39+
with viewer_context_scope(expected_context):
40+
GroupAssignee.objects.assign(self.group, self.user)
41+
42+
assert get_viewer_context() is None
43+
for callback in callbacks:
44+
callback()
45+
46+
assert captured_contexts == [expected_context]
47+
2348
def test_constraints(self) -> None:
2449
# Can't both be assigned
2550
with pytest.raises(AssertionError):

‎tests/sentry/receivers/test_releases.py‎

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from sentry.types.activity import ActivityType
4141
from sentry.users.models.user_option import UserOption
4242
from sentry.users.models.useremail import UserEmail
43+
from sentry.viewer_context import ActorType, ViewerContext, get_viewer_context
4344

4445

4546
class InvalidateReleaseCacheTest(TestCase):
@@ -326,7 +327,15 @@ def test_matching_author_with_assignment(self) -> None:
326327
)
327328
author.preload_users()
328329

329-
with self.assertLogs("sentry.issues.action_log", level="INFO") as logs:
330+
captured_contexts: list[ViewerContext | None] = []
331+
with (
332+
self.assertLogs("sentry.issues.action_log", level="INFO") as logs,
333+
patch("sentry.models.groupassignee.issue_assigned.send_robust") as send_robust,
334+
self.capture_on_commit_callbacks(execute=True),
335+
):
336+
send_robust.side_effect = lambda **kwargs: captured_contexts.append(
337+
get_viewer_context()
338+
)
330339
commit = Commit.objects.create(
331340
key=sha1(uuid4().hex.encode("utf-8")).hexdigest(),
332341
organization_id=group.organization.id,
@@ -338,6 +347,14 @@ def test_matching_author_with_assignment(self) -> None:
338347
self.assertLinkedFromCommitDeferred(group, commit)
339348

340349
assert GroupAssignee.objects.filter(group=group, user_id=user.id).exists()
350+
assert captured_contexts == [
351+
ViewerContext(
352+
organization_id=group.organization.id,
353+
project_id=group.project_id,
354+
user_id=user.id,
355+
actor_type=ActorType.USER,
356+
)
357+
]
341358

342359
# The self-assign is attributed to the commit author, not logged as a system action.
343360
assign_records = [r for r in logs.records if r.__dict__.get("action") == "assign"]
@@ -452,7 +469,15 @@ def test_matching_external_actor_sets_activity_user(self) -> None:
452469
)
453470
author = self._create_pull_request_author("newdev", group.organization.id)
454471

455-
pull_request = self._create_resolving_pull_request(group, repo, author)
472+
captured_contexts: list[ViewerContext | None] = []
473+
with (
474+
patch("sentry.models.groupassignee.issue_assigned.send_robust") as send_robust,
475+
self.capture_on_commit_callbacks(execute=True),
476+
):
477+
send_robust.side_effect = lambda **kwargs: captured_contexts.append(
478+
get_viewer_context()
479+
)
480+
pull_request = self._create_resolving_pull_request(group, repo, author)
456481

457482
activity = Activity.objects.get(
458483
group=group,
@@ -461,6 +486,14 @@ def test_matching_external_actor_sets_activity_user(self) -> None:
461486
assert activity.user_id == user.id
462487
assert activity.data == {"pull_request": pull_request.id}
463488
assert GroupAssignee.objects.filter(group=group, user_id=user.id).exists()
489+
assert captured_contexts == [
490+
ViewerContext(
491+
organization_id=group.organization.id,
492+
project_id=group.project_id,
493+
user_id=user.id,
494+
actor_type=ActorType.USER,
495+
)
496+
]
464497

465498
@receivers_raise_on_send()
466499
def test_author_from_different_organization_does_not_set_activity_user(self) -> None:

0 commit comments

Comments
 (0)