Skip to content

Commit 58a5e3a

Browse files
committed
fix(investigations): Resolve who applied a request on read
Look the applier up with the usual user serializer, so names and avatars stay current and no user data is copied into the request, and list the newest report's requests first.
1 parent 921bb43 commit 58a5e3a

3 files changed

Lines changed: 39 additions & 32 deletions

File tree

‎src/sentry/investigations/endpoints/serializers/action_request.py‎

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
InvestigationActionRequestAnchorType,
1313
InvestigationBlock,
1414
)
15-
from sentry.users.api.serializers.user import SerializedAvatarFields
15+
from sentry.users.api.serializers.user import UserSerializerResponse
1616
from sentry.users.models.user import User
1717
from sentry.users.services.user.model import RpcUser
18+
from sentry.users.services.user.serial import serialize_generic_user
19+
from sentry.users.services.user.service import user_service
1820

1921

2022
class InvestigationActionRequestAnchorResponse(TypedDict):
@@ -27,15 +29,6 @@ class InvestigationActionRequestAnchorResponse(TypedDict):
2729
blockId: str | None
2830

2931

30-
class InvestigationActionRequestActorResponse(TypedDict):
31-
id: str
32-
name: str
33-
username: str
34-
email: str
35-
avatarUrl: str
36-
avatar: SerializedAvatarFields
37-
38-
3932
class InvestigationActionRequestSerializerResponse(TypedDict):
4033
"""
4134
A step Seer suggests. `primary` marks the recommended next step after a run, and `anchor`
@@ -55,7 +48,7 @@ class InvestigationActionRequestSerializerResponse(TypedDict):
5548
state: str
5649
reason: str | None
5750
action: dict[str, Any] | None
58-
appliedBy: InvestigationActionRequestActorResponse | None
51+
appliedBy: UserSerializerResponse | None
5952
appliedDetails: dict[str, Any] | None
6053
dateApplied: datetime | None
6154
error: dict[str, Any] | None
@@ -94,13 +87,25 @@ def get_attrs(
9487
**kwargs: Any,
9588
) -> MutableMapping[InvestigationActionRequest, dict[str, Any]]:
9689
anchored_blocks = self._anchored_block_ids(item_list)
90+
applier_ids = {item.applied_by_id for item in item_list if item.applied_by_id is not None}
91+
appliers = (
92+
{
93+
applier["id"]: applier
94+
for applier in user_service.serialize_many(
95+
filter={"user_ids": list(applier_ids)}, as_user=serialize_generic_user(user)
96+
)
97+
}
98+
if applier_ids
99+
else {}
100+
)
97101
return {
98102
item: {
99103
"block_id": (
100104
anchored_blocks.get((item.investigation_id, item.anchor_id or ""))
101105
if item.anchor_type == InvestigationActionRequestAnchorType.BLOCK
102106
else None
103-
)
107+
),
108+
"applied_by": appliers.get(str(item.applied_by_id)),
104109
}
105110
for item in item_list
106111
}
@@ -129,14 +134,8 @@ def serialize(
129134
"state": obj.state,
130135
"reason": obj.reason,
131136
"action": obj.action_payload,
132-
"appliedBy": (
133-
(obj.applied_details or {}).get("actor") if obj.applied_by_id is not None else None
134-
),
135-
"appliedDetails": (
136-
{key: value for key, value in obj.applied_details.items() if key != "actor"}
137-
if obj.applied_details is not None
138-
else None
139-
),
137+
"appliedBy": attrs["applied_by"],
138+
"appliedDetails": obj.applied_details,
140139
"dateApplied": obj.date_applied,
141140
"error": obj.error,
142141
"dateCreated": obj.date_added,

‎src/sentry/investigations/endpoints/serializers/investigation.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ class InvestigationDetailsSerializerResponse(InvestigationSerializerResponse):
117117
"""
118118
Full notebook with ordered active blocks, parameter definitions and saved values, project
119119
IDs, and source context. source.snapshot is present only for source-backed investigations.
120-
actionRequests has Seer's live action requests, with the primary one first.
120+
actionRequests has Seer's action requests that aren't superseded, newest report first, with
121+
that report's primary at the top.
121122
"""
122123

123124
template: InvestigationTemplateSerializerResponse | None
@@ -242,7 +243,7 @@ def _action_requests_by_investigation(
242243
action_requests = list(
243244
InvestigationActionRequest.objects.filter(investigation__in=item_list)
244245
.exclude(state=InvestigationActionRequestState.SUPERSEDED)
245-
.order_by("-primary", "-report_revision", "id")
246+
.order_by("-report_revision", "-primary", "id")
246247
)
247248
serialized = serialize(
248249
action_requests,

‎tests/sentry/investigations/endpoints/serializers/test_investigation.py‎

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -374,26 +374,32 @@ def test_serializes_requests_without_a_known_action(self) -> None:
374374
{"kind": "seer_task", "prompt": "Add spans"},
375375
]
376376

377-
def test_orders_the_primary_first_and_omits_superseded_requests(self) -> None:
377+
def test_orders_the_newest_report_first_and_omits_superseded_requests(self) -> None:
378+
old_primary = self.add_request(
379+
"old-primary",
380+
report_revision=0,
381+
primary=True,
382+
state=InvestigationActionRequestState.WITHDRAWN,
383+
)
378384
older = self.add_request("older", report_revision=0)
379385
self.add_request("superseded", state=InvestigationActionRequestState.SUPERSEDED)
380-
second = self.add_request("second", report_revision=1)
381-
first = self.add_request("first", report_revision=0, primary=True)
386+
newer = self.add_request("newer", report_revision=1)
387+
primary = self.add_request("primary", report_revision=1, primary=True)
382388

383389
assert [row["id"] for row in self.serialize_requests()] == [
384-
str(first.id),
385-
str(second.id),
390+
str(primary.id),
391+
str(newer.id),
392+
str(old_primary.id),
386393
str(older.id),
387394
]
388395

389396
def test_serializes_who_applied_it_failures_and_withdrawals(self) -> None:
390397
now = timezone.now()
391-
actor = {"id": str(self.user.id), "name": "Ada Lovelace"}
392398
self.add_request(
393399
state=InvestigationActionRequestState.APPLIED,
394400
applied_by_id=self.user.id,
395401
date_applied=now,
396-
applied_details={"provider": "datadog", "rerun": None, "actor": actor},
402+
applied_details={"provider": "datadog", "rerun": None},
397403
)
398404
self.add_request(
399405
"failed",
@@ -408,7 +414,8 @@ def test_serializes_who_applied_it_failures_and_withdrawals(self) -> None:
408414

409415
applied, failed, withdrawn = self.serialize_requests()
410416

411-
assert applied["appliedBy"] == actor
417+
assert applied["appliedBy"] is not None
418+
assert applied["appliedBy"]["id"] == str(self.user.id)
412419
assert applied["appliedDetails"] == {"provider": "datadog", "rerun": None}
413420
assert applied["dateApplied"] == now
414421
assert failed["state"] == "failed"
@@ -418,11 +425,11 @@ def test_serializes_who_applied_it_failures_and_withdrawals(self) -> None:
418425
}
419426
assert (withdrawn["state"], withdrawn["reason"]) == ("withdrawn", "The spikes are real.")
420427

421-
def test_hides_the_stored_applier_once_the_user_is_gone(self) -> None:
428+
def test_shows_no_applier_once_the_user_is_gone(self) -> None:
422429
self.add_request(
423430
state=InvestigationActionRequestState.APPLIED,
424431
applied_by_id=None,
425-
applied_details={"provider": "datadog", "rerun": None, "actor": {"id": "1"}},
432+
applied_details={"provider": "datadog", "rerun": None},
426433
)
427434

428435
(applied,) = self.serialize_requests()

0 commit comments

Comments
 (0)