Skip to content

Commit 3c913e1

Browse files
committed
fix(crons): allow team and user names to be used in checkin payload
1 parent 77f9b12 commit 3c913e1

File tree

2 files changed

+71
-4
lines changed

2 files changed

+71
-4
lines changed

src/sentry/types/actor.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,19 +165,28 @@ def from_identifier(cls, id: None) -> None: ...
165165
@classmethod
166166
def from_identifier(cls, id: int | str) -> "Actor": ...
167167

168+
@overload
169+
@classmethod
170+
def from_identifier(cls, id: int | str, organization_id: int) -> "Actor": ...
171+
168172
@classmethod
169-
def from_identifier(cls, id: str | int | None) -> "Actor | None":
173+
def from_identifier(
174+
cls, id: str | int | None, organization_id: int | None = None
175+
) -> "Actor | None":
170176
"""
171177
Parse an actor identifier into an Actor
172178
173179
Forms `id` can take:
174180
1231 -> look up User by id
175181
"1231" -> look up User by id
176182
"user:1231" -> look up User by id
183+
"user:maiseythedog" -> look up user by username
177184
"team:1231" -> look up Team by id
185+
"team:team-name" -> look up Team by name (must provide organization_id)
178186
"maiseythedog" -> look up User by username
179187
"[email protected]" -> look up User by primary email
180188
"""
189+
from sentry.models.team import Team
181190
from sentry.users.services.user.service import user_service
182191

183192
if not id:
@@ -192,10 +201,25 @@ def from_identifier(cls, id: str | int | None) -> "Actor | None":
192201
return cls(id=int(id), actor_type=ActorType.USER)
193202

194203
if id.startswith("user:"):
195-
return cls(id=int(id[5:]), actor_type=ActorType.USER)
204+
remainder = id[5:]
205+
if remainder.isdigit():
206+
return cls(id=int(remainder), actor_type=ActorType.USER)
207+
# pass this on to get to the user lookup below
208+
id = remainder
196209

197210
if id.startswith("team:"):
198-
return cls(id=int(id[5:]), actor_type=ActorType.TEAM)
211+
remainder = id[5:]
212+
if remainder.isdigit():
213+
return cls(id=int(remainder), actor_type=ActorType.TEAM)
214+
215+
if organization_id is not None:
216+
try:
217+
team = Team.objects.get(name=remainder, organization_id=organization_id)
218+
return cls(id=team.id, actor_type=ActorType.TEAM)
219+
except Team.DoesNotExist:
220+
pass
221+
222+
raise cls.InvalidActor(f"Unable to resolve team name: {remainder}")
199223

200224
try:
201225
user = user_service.get_by_username(username=id)[0]
@@ -280,7 +304,7 @@ def parse_and_validate_actor(actor_identifier: str | None, organization_id: int)
280304
return None
281305

282306
try:
283-
actor = Actor.from_identifier(actor_identifier)
307+
actor = Actor.from_identifier(actor_identifier, organization_id)
284308
except Exception:
285309
raise serializers.ValidationError(
286310
"Could not parse actor. Format should be `type:id` where type is `team` or `user`."

tests/sentry/monitors/consumers/test_monitor_consumer.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,49 @@ def test_monitor_upsert_empty_timezone(self):
940940
assert monitor is not None
941941
assert "timezone" not in monitor.config
942942

943+
def test_team_name_as_owner(self):
944+
monitor = self._create_monitor(slug="my-monitor", owner_user_id=self.user.id)
945+
self.send_checkin(
946+
"my-monitor",
947+
monitor_config={
948+
"schedule": {"type": "crontab", "value": "13 * * * *"},
949+
"owner": f"team:{self.team.name}",
950+
},
951+
)
952+
checkin = MonitorCheckIn.objects.get(guid=self.guid)
953+
assert checkin.status == CheckInStatus.OK
954+
955+
monitor_environment = MonitorEnvironment.objects.get(id=checkin.monitor_environment.id)
956+
assert monitor_environment.status == MonitorStatus.OK
957+
monitor.refresh_from_db()
958+
assert monitor.owner_user_id is None
959+
assert monitor.owner_team_id == self.team.id
960+
961+
def test_user_name_as_owner(self):
962+
named_user = self.create_user(
963+
"admin2@localhost",
964+
username="test_user",
965+
is_superuser=True,
966+
is_staff=True,
967+
is_sentry_app=False,
968+
)
969+
monitor = self._create_monitor(slug="my-monitor", owner_user_id=named_user.id)
970+
971+
self.send_checkin(
972+
"my-monitor",
973+
monitor_config={
974+
"schedule": {"type": "crontab", "value": "13 * * * *"},
975+
"owner": f"user:{named_user.username}",
976+
},
977+
)
978+
checkin = MonitorCheckIn.objects.get(guid=self.guid)
979+
assert checkin.status == CheckInStatus.OK
980+
981+
monitor_environment = MonitorEnvironment.objects.get(id=checkin.monitor_environment.id)
982+
assert monitor_environment.status == MonitorStatus.OK
983+
assert monitor.owner_user_id == named_user.id
984+
assert monitor.owner_team_id is None
985+
943986
def test_monitor_upsert_invalid_slug(self):
944987
self.send_checkin(
945988
"some/slug@with-weird|stuff",

0 commit comments

Comments
 (0)