|
@@ -1,7 +1,5 @@
|
|
|
from __future__ import annotations
|
|
|
|
|
|
-from collections import defaultdict, namedtuple
|
|
|
-from collections.abc import Sequence
|
|
|
from typing import TYPE_CHECKING, overload
|
|
|
|
|
|
import sentry_sdk
|
|
@@ -9,7 +7,6 @@ from django.conf import settings
|
|
|
from django.db import IntegrityError, models, router, transaction
|
|
|
from django.db.models.signals import post_save
|
|
|
from django.forms import model_to_dict
|
|
|
-from rest_framework import serializers
|
|
|
|
|
|
from sentry.backup.dependencies import ImportKind, PrimaryKeyMap
|
|
|
from sentry.backup.helpers import ImportFlags
|
|
@@ -20,6 +17,7 @@ from sentry.db.models.fields.hybrid_cloud_foreign_key import HybridCloudForeignK
|
|
|
from sentry.models.outbox import OutboxCategory, OutboxScope, RegionOutbox, outbox_context
|
|
|
from sentry.services.hybrid_cloud.user import RpcUser
|
|
|
from sentry.services.hybrid_cloud.user.service import user_service
|
|
|
+from sentry.utils.actor import ActorTuple
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
from sentry.models.team import Team
|
|
@@ -78,32 +76,6 @@ def fetch_actors_by_actor_ids(
|
|
|
raise ValueError(f"Cls {cls} is not a valid actor type.")
|
|
|
|
|
|
|
|
|
-@overload
|
|
|
-def fetch_actor_by_id(cls: type[User], id: int) -> RpcUser:
|
|
|
- ...
|
|
|
-
|
|
|
-
|
|
|
-@overload
|
|
|
-def fetch_actor_by_id(cls: type[Team], id: int) -> Team:
|
|
|
- ...
|
|
|
-
|
|
|
-
|
|
|
-def fetch_actor_by_id(cls: type[User] | type[Team], id: int) -> Team | RpcUser:
|
|
|
- from sentry.models.team import Team
|
|
|
- from sentry.models.user import User
|
|
|
-
|
|
|
- if cls is Team:
|
|
|
- return Team.objects.get(id=id)
|
|
|
-
|
|
|
- elif cls is User:
|
|
|
- user = user_service.get_user(id)
|
|
|
- if user is None:
|
|
|
- raise User.DoesNotExist()
|
|
|
- return user
|
|
|
- else:
|
|
|
- raise ValueError(f"Cls {cls} is not a valid actor type.")
|
|
|
-
|
|
|
-
|
|
|
def actor_type_to_string(type: int) -> str | None:
|
|
|
# `type` will be 0 or 1 and we want to get "team" or "user"
|
|
|
for k, v in ACTOR_TYPES.items():
|
|
@@ -233,134 +205,6 @@ def get_actor_for_team(team: int | Team) -> Actor:
|
|
|
return actor
|
|
|
|
|
|
|
|
|
-class ActorTuple(namedtuple("Actor", "id type")):
|
|
|
- """
|
|
|
- This is an artifact from before we had the Actor model.
|
|
|
- We want to eventually drop this model and merge functionality with Actor
|
|
|
- This should happen more easily if we move GroupAssignee, GroupOwner, etc. to use the Actor model.
|
|
|
- """
|
|
|
-
|
|
|
- @property
|
|
|
- def identifier(self):
|
|
|
- return f"{self.type.__name__.lower()}:{self.id}"
|
|
|
-
|
|
|
- @overload
|
|
|
- @classmethod
|
|
|
- def from_actor_identifier(cls, actor_identifier: None) -> None:
|
|
|
- ...
|
|
|
-
|
|
|
- @overload
|
|
|
- @classmethod
|
|
|
- def from_actor_identifier(cls, actor_identifier: int | str) -> ActorTuple:
|
|
|
- ...
|
|
|
-
|
|
|
- @classmethod
|
|
|
- def from_actor_identifier(cls, actor_identifier: int | str | None) -> ActorTuple | None:
|
|
|
- from sentry.models.team import Team
|
|
|
- from sentry.models.user import User
|
|
|
-
|
|
|
- """
|
|
|
- Returns an Actor tuple corresponding to a User or Team associated with
|
|
|
- the given identifier.
|
|
|
-
|
|
|
- Forms `actor_identifier` can take:
|
|
|
- 1231 -> look up User by id
|
|
|
- "1231" -> look up User by id
|
|
|
- "user:1231" -> look up User by id
|
|
|
- "team:1231" -> look up Team by id
|
|
|
- "maiseythedog" -> look up User by username
|
|
|
- "maisey@dogsrule.com" -> look up User by primary email
|
|
|
- """
|
|
|
-
|
|
|
- if actor_identifier is None:
|
|
|
- return None
|
|
|
-
|
|
|
- # If we have an integer, fall back to assuming it's a User
|
|
|
- if isinstance(actor_identifier, int):
|
|
|
- return cls(actor_identifier, User)
|
|
|
-
|
|
|
- # If the actor_identifier is a simple integer as a string,
|
|
|
- # we're also a User
|
|
|
- if actor_identifier.isdigit():
|
|
|
- return cls(int(actor_identifier), User)
|
|
|
-
|
|
|
- if actor_identifier.startswith("user:"):
|
|
|
- return cls(int(actor_identifier[5:]), User)
|
|
|
-
|
|
|
- if actor_identifier.startswith("team:"):
|
|
|
- return cls(int(actor_identifier[5:]), Team)
|
|
|
-
|
|
|
- try:
|
|
|
- user = user_service.get_by_username(username=actor_identifier)[0]
|
|
|
- return cls(user.id, User)
|
|
|
- except IndexError as e:
|
|
|
- raise serializers.ValidationError(f"Unable to resolve actor identifier: {e}")
|
|
|
-
|
|
|
- @classmethod
|
|
|
- def from_id(cls, user_id: int | None, team_id: int | None) -> ActorTuple | None:
|
|
|
- from sentry.models.team import Team
|
|
|
- from sentry.models.user import User
|
|
|
-
|
|
|
- if user_id and team_id:
|
|
|
- raise ValueError("user_id and team_id may not both be specified")
|
|
|
- if user_id and not team_id:
|
|
|
- return cls(user_id, User)
|
|
|
- if team_id and not user_id:
|
|
|
- return cls(team_id, Team)
|
|
|
-
|
|
|
- return None
|
|
|
-
|
|
|
- @classmethod
|
|
|
- def from_ids(cls, user_ids: Sequence[int], team_ids: Sequence[int]) -> Sequence[ActorTuple]:
|
|
|
- from sentry.models.team import Team
|
|
|
- from sentry.models.user import User
|
|
|
-
|
|
|
- return [
|
|
|
- *[cls(user_id, User) for user_id in user_ids],
|
|
|
- *[cls(team_id, Team) for team_id in team_ids],
|
|
|
- ]
|
|
|
-
|
|
|
- def resolve(self) -> Team | RpcUser:
|
|
|
- return fetch_actor_by_id(self.type, self.id)
|
|
|
-
|
|
|
- def resolve_to_actor(self) -> Actor:
|
|
|
- from sentry.models.user import User
|
|
|
-
|
|
|
- obj = self.resolve()
|
|
|
- if isinstance(obj, (User, RpcUser)):
|
|
|
- return get_actor_for_user(obj)
|
|
|
- # Team case. Teams have actors generated as a post_save signal
|
|
|
- return Actor.objects.get(id=obj.actor_id)
|
|
|
-
|
|
|
- @classmethod
|
|
|
- def resolve_many(cls, actors: Sequence[ActorTuple]) -> Sequence[Team | RpcUser]:
|
|
|
- """
|
|
|
- Resolve multiple actors at the same time. Returns the result in the same order
|
|
|
- as the input, minus any actors we couldn't resolve.
|
|
|
- :param actors:
|
|
|
- :return:
|
|
|
- """
|
|
|
- from sentry.models.user import User
|
|
|
-
|
|
|
- if not actors:
|
|
|
- return []
|
|
|
-
|
|
|
- actors_by_type = defaultdict(list)
|
|
|
- for actor in actors:
|
|
|
- actors_by_type[actor.type].append(actor)
|
|
|
-
|
|
|
- results = {}
|
|
|
- for model_class, _actors in actors_by_type.items():
|
|
|
- if model_class == User:
|
|
|
- for instance in user_service.get_many(filter={"user_ids": [a.id for a in _actors]}):
|
|
|
- results[(model_class, instance.id)] = instance
|
|
|
- else:
|
|
|
- for instance in model_class.objects.filter(id__in=[a.id for a in _actors]):
|
|
|
- results[(model_class, instance.id)] = instance
|
|
|
-
|
|
|
- return list(filter(None, [results.get((actor.type, actor.id)) for actor in actors]))
|
|
|
-
|
|
|
-
|
|
|
def handle_team_post_save(instance, **kwargs):
|
|
|
# we want to create an actor if we don't have one
|
|
|
if not instance.actor_id:
|