diff --git a/src/sentry/services/hybrid_cloud/integration/impl.py b/src/sentry/services/hybrid_cloud/integration/impl.py index 4b7864a10495a6..8d402dc6789805 100644 --- a/src/sentry/services/hybrid_cloud/integration/impl.py +++ b/src/sentry/services/hybrid_cloud/integration/impl.py @@ -26,6 +26,7 @@ from sentry.services.hybrid_cloud.integration.model import ( RpcIntegrationExternalProject, RpcIntegrationIdentityContext, + RpcOrganizationIntegrationContextResult, ) from sentry.services.hybrid_cloud.integration.serial import ( serialize_integration, @@ -235,6 +236,22 @@ def get_organization_contexts( provider: str | None = None, external_id: str | None = None, ) -> tuple[RpcIntegration | None, list[RpcOrganizationIntegration]]: + result = self.get_organization_contexts__tmp( + organization_id=organization_id, + integration_id=integration_id, + provider=provider, + external_id=external_id, + ) + return result.integration, result.installs + + def get_organization_contexts__tmp( + self, + *, + organization_id: int | None = None, + integration_id: int | None = None, + provider: str | None = None, + external_id: str | None = None, + ) -> RpcOrganizationIntegrationContextResult: integration = self.get_integration( organization_id=organization_id, integration_id=integration_id, @@ -242,12 +259,14 @@ def get_organization_contexts( external_id=external_id, ) if not integration: - return (None, []) + return RpcOrganizationIntegrationContextResult(integration=None, installs=[]) organization_integrations = self.get_organization_integrations( integration_id=integration.id, organization_id=organization_id, ) - return (integration, organization_integrations) + return RpcOrganizationIntegrationContextResult( + integration=integration, installs=organization_integrations + ) def update_integrations( self, diff --git a/src/sentry/services/hybrid_cloud/integration/model.py b/src/sentry/services/hybrid_cloud/integration/model.py index 9bc21fa2f240b5..03be9d3d48909e 100644 --- a/src/sentry/services/hybrid_cloud/integration/model.py +++ b/src/sentry/services/hybrid_cloud/integration/model.py @@ -69,6 +69,11 @@ def get_status_display(self) -> str: return "disabled" +class RpcOrganizationIntegrationContextResult(RpcModel): + integration: RpcIntegration | None + installs: list[RpcOrganizationIntegration] + + class RpcIntegrationExternalProject(RpcModel): id: int organization_integration_id: int diff --git a/src/sentry/services/hybrid_cloud/integration/service.py b/src/sentry/services/hybrid_cloud/integration/service.py index 6776e7814d8ec0..273f7074bafbf2 100644 --- a/src/sentry/services/hybrid_cloud/integration/service.py +++ b/src/sentry/services/hybrid_cloud/integration/service.py @@ -11,6 +11,7 @@ from sentry.services.hybrid_cloud.integration.model import ( RpcIntegrationExternalProject, RpcIntegrationIdentityContext, + RpcOrganizationIntegrationContextResult, ) from sentry.services.hybrid_cloud.pagination import RpcPaginationArgs, RpcPaginationResult from sentry.services.hybrid_cloud.rpc import RpcService, rpc_method @@ -143,6 +144,30 @@ def get_organization_context( by either integration_id, or a combination of provider and external_id. """ + def get_organization_context__tmp( + self, + *, + organization_id: int, + integration_id: int | None = None, + provider: str | None = None, + external_id: str | None = None, + ) -> tuple[RpcIntegration | None, RpcOrganizationIntegration | None]: + """ + Returns a tuple of RpcIntegration and RpcOrganizationIntegration. The integration is selected + by either integration_id, or a combination of provider and external_id. + """ + # This is a convencience method for unpacking `get_organization_contexts`. + # Note that it can't be an @rpc_method because it returns a fixed-size tuple. + + contexts = self.get_organization_contexts__tmp( + organization_id=organization_id, + integration_id=integration_id, + provider=provider, + external_id=external_id, + ) + + return contexts.integration, (contexts.installs[0] if contexts.installs else None) + @rpc_method @abstractmethod def get_organization_contexts( @@ -153,9 +178,21 @@ def get_organization_contexts( provider: str | None = None, external_id: str | None = None, ) -> tuple[RpcIntegration | None, list[RpcOrganizationIntegration]]: + pass + + @rpc_method + @abstractmethod + def get_organization_contexts__tmp( + self, + *, + organization_id: int | None = None, + integration_id: int | None = None, + provider: str | None = None, + external_id: str | None = None, + ) -> RpcOrganizationIntegrationContextResult: """ - Returns a tuple of RpcIntegration and RpcOrganizationIntegrations. The integrations are selected - by either integration_id, or a combination of provider and external_id. + The integrations are selected by either integration_id, or a combination of + provider and external_id. """ @rpc_method diff --git a/src/sentry/services/hybrid_cloud/notifications/impl.py b/src/sentry/services/hybrid_cloud/notifications/impl.py index c62adea7af1782..90d538ed6d5c86 100644 --- a/src/sentry/services/hybrid_cloud/notifications/impl.py +++ b/src/sentry/services/hybrid_cloud/notifications/impl.py @@ -14,7 +14,11 @@ NotificationSettingsOptionEnum, ) from sentry.services.hybrid_cloud.actor import ActorType, RpcActor -from sentry.services.hybrid_cloud.notifications import NotificationsService +from sentry.services.hybrid_cloud.notifications import ( + NotificationsService, + RpcGroupSubscriptionStatus, +) +from sentry.services.hybrid_cloud.notifications.serial import serialize_group_subscription_status from sentry.services.hybrid_cloud.user.service import user_service from sentry.types.integrations import EXTERNAL_PROVIDERS, ExternalProviderEnum, ExternalProviders @@ -119,6 +123,21 @@ def get_subscriptions_for_projects( project_ids: list[int], type: NotificationSettingEnum, ) -> Mapping[int, tuple[bool, bool, bool]]: + result = self.get_subscriptions_for_projects__tmp( + user_id=user_id, project_ids=project_ids, type=type + ) + return { + k: (v.is_disabled, v.is_active, v.has_only_inactive_subscriptions) + for (k, v) in result.items() + } + + def get_subscriptions_for_projects__tmp( + self, + *, + user_id: int, + project_ids: list[int], + type: NotificationSettingEnum, + ) -> Mapping[int, RpcGroupSubscriptionStatus]: """ Returns a mapping of project_id to a tuple of (is_disabled, is_active, has_only_inactive_subscriptions) """ @@ -132,7 +151,7 @@ def get_subscriptions_for_projects( type=type, ) return { - project: (s.is_disabled, s.is_active, s.has_only_inactive_subscriptions) + project: serialize_group_subscription_status(s) # TODO(Steve): Simplify API to pass in one project at a time for project, s in controller.get_subscriptions_status_for_projects( user=user, project_ids=project_ids, type=type diff --git a/src/sentry/services/hybrid_cloud/notifications/model.py b/src/sentry/services/hybrid_cloud/notifications/model.py index 2c10ae6cbd4826..e5d6ec61f250ba 100644 --- a/src/sentry/services/hybrid_cloud/notifications/model.py +++ b/src/sentry/services/hybrid_cloud/notifications/model.py @@ -20,3 +20,9 @@ class RpcExternalActor(RpcModel): external_name: str = "" # The unique identifier i.e user ID, channel ID. external_id: str | None = None + + +class RpcGroupSubscriptionStatus(RpcModel): + is_disabled: bool + is_active: bool + has_only_inactive_subscriptions: bool diff --git a/src/sentry/services/hybrid_cloud/notifications/serial.py b/src/sentry/services/hybrid_cloud/notifications/serial.py index b9b9eaf8da86d2..bd34226a95cadb 100644 --- a/src/sentry/services/hybrid_cloud/notifications/serial.py +++ b/src/sentry/services/hybrid_cloud/notifications/serial.py @@ -1,5 +1,6 @@ from sentry.models.integrations.external_actor import ExternalActor -from sentry.services.hybrid_cloud.notifications import RpcExternalActor +from sentry.notifications.types import GroupSubscriptionStatus +from sentry.services.hybrid_cloud.notifications import RpcExternalActor, RpcGroupSubscriptionStatus def serialize_external_actor(actor: ExternalActor) -> RpcExternalActor: @@ -13,3 +14,23 @@ def serialize_external_actor(actor: ExternalActor) -> RpcExternalActor: external_name=actor.external_name, external_id=actor.external_id, ) + + +def serialize_group_subscription_status( + status: GroupSubscriptionStatus, +) -> RpcGroupSubscriptionStatus: + return RpcGroupSubscriptionStatus( + is_disabled=status.is_disabled, + is_active=status.is_active, + has_only_inactive_subscriptions=status.has_only_inactive_subscriptions, + ) + + +def deserialize_group_subscription_status( + status: RpcGroupSubscriptionStatus, +) -> GroupSubscriptionStatus: + return GroupSubscriptionStatus( + is_disabled=status.is_disabled, + is_active=status.is_active, + has_only_inactive_subscriptions=status.has_only_inactive_subscriptions, + ) diff --git a/src/sentry/services/hybrid_cloud/notifications/service.py b/src/sentry/services/hybrid_cloud/notifications/service.py index fb256f0fa5b514..57e532b1d3a098 100644 --- a/src/sentry/services/hybrid_cloud/notifications/service.py +++ b/src/sentry/services/hybrid_cloud/notifications/service.py @@ -11,6 +11,7 @@ NotificationSettingsOptionEnum, ) from sentry.services.hybrid_cloud.actor import ActorType, RpcActor +from sentry.services.hybrid_cloud.notifications import RpcGroupSubscriptionStatus from sentry.services.hybrid_cloud.rpc import RpcService, rpc_method from sentry.silo import SiloMode from sentry.types.integrations import ExternalProviderEnum, ExternalProviders @@ -81,6 +82,17 @@ def get_subscriptions_for_projects( ) -> Mapping[int, tuple[bool, bool, bool]]: pass + @rpc_method + @abstractmethod + def get_subscriptions_for_projects__tmp( + self, + *, + user_id: int, + project_ids: list[int], + type: NotificationSettingEnum, + ) -> Mapping[int, RpcGroupSubscriptionStatus]: + pass + @rpc_method @abstractmethod def get_participants( diff --git a/src/sentry/services/hybrid_cloud/user/impl.py b/src/sentry/services/hybrid_cloud/user/impl.py index 1c28c8943b0ac7..2ad29dece9c22a 100644 --- a/src/sentry/services/hybrid_cloud/user/impl.py +++ b/src/sentry/services/hybrid_cloud/user/impl.py @@ -37,7 +37,11 @@ UserSerializeType, UserUpdateArgs, ) -from sentry.services.hybrid_cloud.user.model import RpcVerifyUserEmail, UserIdEmailArgs +from sentry.services.hybrid_cloud.user.model import ( + RpcUserCreationResult, + RpcVerifyUserEmail, + UserIdEmailArgs, +) from sentry.services.hybrid_cloud.user.serial import serialize_rpc_user, serialize_user_avatar from sentry.services.hybrid_cloud.user.service import UserService from sentry.signals import user_signup @@ -194,10 +198,16 @@ def get_first_superuser(self) -> RpcUser | None: def get_or_create_user_by_email( self, *, email: str, ident: str | None = None, referrer: str | None = None ) -> tuple[RpcUser, bool]: + result = self.get_or_create_user_by_email__tmp(email=email, ident=ident, referrer=referrer) + return result.user, result.was_newly_created + + def get_or_create_user_by_email__tmp( + self, *, email: str, ident: str | None = None, referrer: str | None = None + ) -> RpcUserCreationResult: with transaction.atomic(router.db_for_write(User)): rpc_user = self.get_user_by_email(email=email, ident=ident) if rpc_user: - return (rpc_user, False) + return RpcUserCreationResult(user=rpc_user, was_newly_created=False) # Create User if it doesn't exist user = User.objects.create( @@ -209,7 +219,7 @@ def get_or_create_user_by_email( sender=self, user=user, source="api", referrer=referrer or "unknown" ) user.update(flags=F("flags").bitor(User.flags.newsletter_consent_prompt)) - return (serialize_rpc_user(user), True) + return RpcUserCreationResult(user=serialize_rpc_user(user), was_newly_created=True) def get_user_by_email( self, diff --git a/src/sentry/services/hybrid_cloud/user/model.py b/src/sentry/services/hybrid_cloud/user/model.py index df9038b68cc66b..e5f0186aa1b27c 100644 --- a/src/sentry/services/hybrid_cloud/user/model.py +++ b/src/sentry/services/hybrid_cloud/user/model.py @@ -150,3 +150,8 @@ class UserIdEmailArgs(TypedDict): class RpcVerifyUserEmail(RpcModel): exists: bool = False email: str = "" + + +class RpcUserCreationResult(RpcModel): + user: RpcUser + was_newly_created: bool diff --git a/src/sentry/services/hybrid_cloud/user/service.py b/src/sentry/services/hybrid_cloud/user/service.py index bc34ae2ecfc268..9c3cbf1428c271 100644 --- a/src/sentry/services/hybrid_cloud/user/service.py +++ b/src/sentry/services/hybrid_cloud/user/service.py @@ -17,7 +17,12 @@ UserSerializeType, UserUpdateArgs, ) -from sentry.services.hybrid_cloud.user.model import RpcAvatar, RpcVerifyUserEmail, UserIdEmailArgs +from sentry.services.hybrid_cloud.user.model import ( + RpcAvatar, + RpcUserCreationResult, + RpcVerifyUserEmail, + UserIdEmailArgs, +) from sentry.silo import SiloMode @@ -149,6 +154,17 @@ def get_or_create_user_by_email( ) -> tuple[RpcUser, bool]: pass + @rpc_method + @abstractmethod + def get_or_create_user_by_email__tmp( + self, + *, + email: str, + ident: str | None = None, + referrer: str | None = None, + ) -> RpcUserCreationResult: + pass + @rpc_method @abstractmethod def get_user_by_email(