Skip to content

Commit 95d5389

Browse files
leeandherandrewshie-sentry
authored andcommitted
chore(sentry-apps): Allow Sentry App tokens to appear on group activity payload (#93078)
Requires #93612 In the event a token issued from a sentry app is used to create an activity, we attribute it to the proxy user instead of a sentry app. <img width="512" alt="image" src="https://github.com/user-attachments/assets/de959330-bc0a-4476-803e-3c33e28d0764" /> To get the sentry app to appear (and minimize API/RPC calls) I added a new rpc method that will return the sentry app, and its avatars (everything the FE will need) to properly render the source of the activity. Thinking about this more, I would rather read `is_sentry_app` off of the user property, so I will modify the RPC method to do that instead of using all user_ids.
1 parent 080c0f3 commit 95d5389

File tree

2 files changed

+74
-0
lines changed

2 files changed

+74
-0
lines changed

src/sentry/api/serializers/models/activity.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
from sentry.models.commit import Commit
55
from sentry.models.group import Group
66
from sentry.models.pullrequest import PullRequest
7+
from sentry.sentry_apps.api.serializers.sentry_app_avatar import SentryAppAvatarSerializer
8+
from sentry.sentry_apps.services.app import app_service
9+
from sentry.sentry_apps.services.app.model import RpcSentryApp
710
from sentry.types.activity import ActivityType
811
from sentry.users.services.user.serial import serialize_generic_user
912
from sentry.users.services.user.service import user_service
@@ -26,6 +29,22 @@ def get_attrs(self, item_list, user, **kwargs):
2629
)
2730
users = {u["id"]: u for u in user_list}
2831

32+
# If an activity is created by the proxy user of a Sentry App, attach it to the payload
33+
sentry_apps_list: list[RpcSentryApp] = []
34+
if user_ids:
35+
sentry_apps_list = app_service.get_sentry_apps_by_proxy_users(proxy_user_ids=user_ids)
36+
# Minimal Sentry App serialization to keep the payload minimal
37+
sentry_apps = {
38+
str(app.proxy_user_id): {
39+
"id": str(app.id),
40+
"name": app.name,
41+
"slug": app.slug,
42+
"avatars": serialize(app.avatars, user, serializer=SentryAppAvatarSerializer()),
43+
}
44+
for app in sentry_apps_list
45+
if app.proxy_user_id
46+
}
47+
2948
commit_ids = {
3049
i.data["commit"]
3150
for i in item_list
@@ -85,6 +104,7 @@ def get_attrs(self, item_list, user, **kwargs):
85104
return {
86105
item: {
87106
"user": users.get(str(item.user_id)) if item.user_id else None,
107+
"sentry_app": sentry_apps.get(str(item.user_id)) if item.user_id else None,
88108
"source": (
89109
groups.get(item.data["source_id"])
90110
if item.type == ActivityType.UNMERGE_DESTINATION.value
@@ -121,6 +141,7 @@ def serialize(self, obj, attrs, user, **kwargs):
121141
return {
122142
"id": str(obj.id),
123143
"user": attrs["user"],
144+
"sentry_app": attrs["sentry_app"],
124145
"type": obj.get_type_display(),
125146
"data": data,
126147
"dateCreated": obj.datetime,

tests/sentry/api/serializers/test_activity.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
from sentry.models.commit import Commit
44
from sentry.models.group import GroupStatus
55
from sentry.models.pullrequest import PullRequest
6+
from sentry.silo.base import SiloMode
67
from sentry.testutils.cases import TestCase
8+
from sentry.testutils.silo import assume_test_silo_mode
79
from sentry.types.activity import ActivityType
10+
from sentry.users.models.user import User
811

912

1013
class GroupActivityTestCase(TestCase):
@@ -150,3 +153,53 @@ def test_collapse_group_stats_in_activity_with_option(self):
150153
serialized = serialize(act)
151154

152155
assert "firstSeen" not in serialized["data"]["source"]
156+
157+
def test_get_activities_for_group_proxy_user(self):
158+
project = self.create_project(name="test_activities_group")
159+
group = self.create_group(project)
160+
user = self.create_user()
161+
data = serialize(
162+
Activity.objects.create_group_activity(
163+
group=group,
164+
type=ActivityType.NOTE,
165+
data={"text": "A human sent this message"},
166+
user=user,
167+
)
168+
)
169+
# Regular users, have a new empty key
170+
assert data["user"]["name"] == user.username
171+
assert data["sentry_app"] is None
172+
173+
sentry_app = self.create_sentry_app(name="test_sentry_app")
174+
default_avatar = self.create_sentry_app_avatar(sentry_app=sentry_app)
175+
upload_avatar = self.create_sentry_app_avatar(sentry_app=sentry_app)
176+
with assume_test_silo_mode(SiloMode.CONTROL):
177+
proxy_user = User.objects.get(id=sentry_app.proxy_user_id)
178+
upload_avatar.avatar_type = 1 # an upload
179+
upload_avatar.color = True # a logo
180+
upload_avatar.save()
181+
182+
data = serialize(
183+
Activity.objects.create_group_activity(
184+
group=group,
185+
type=ActivityType.NOTE,
186+
data={"text": "My app sent this message"},
187+
user=proxy_user,
188+
)
189+
)
190+
assert data["user"]["name"] == proxy_user.username
191+
assert data["sentry_app"]["name"] == sentry_app.name
192+
assert {
193+
"avatarType": "default",
194+
"avatarUuid": default_avatar.ident,
195+
"avatarUrl": f"http://testserver/sentry-app-avatar/{default_avatar.ident}/",
196+
"color": False,
197+
"photoType": "icon",
198+
} in data["sentry_app"]["avatars"]
199+
assert {
200+
"avatarType": "upload",
201+
"avatarUuid": upload_avatar.ident,
202+
"avatarUrl": f"http://testserver/sentry-app-avatar/{upload_avatar.ident}/",
203+
"color": True,
204+
"photoType": "logo",
205+
} in data["sentry_app"]["avatars"]

0 commit comments

Comments
 (0)