Browse Source

ref(notifications): modify legacy option code (#60437)

Before we did the changes for notification v2, we had a bunch of
complicated mapping logic to map old options to `NotificationSetting`
rows. It was very complicated and hard to understand. Now that we have
migrated weekly reports to the new notification system and removed some
complexity in the UI, we can remove a bunch of code. None of these
endpoints are documented or used by APIs (purely used by the UI).

For `UserNotificationDetailsEndpoint`, now it only handles the legacy
options of `personalActivityNotifications` and `selfAssignOnResolve`
which are not true notification settings. In the future, we probably
should handle them differently but for now, I'm making the endpoint a
lot more simple by ripping out the code that tries to set
`NotificationSetting` rows.

I also renamed `UserNotificationFineTuningEndpoint` to
`UserNotificationEmailEndpoint` and ripped out all the non-email
functionality. No more dependency on `NotificationSetting` at all.

Note, for some reason a bunch of tests broke that relied on a
mock`sentry.notifications.notify.notify`. I think it had do with this
[change](https://github.com/getsentry/sentry/pull/60437/files#diff-3bd5835dd3fcc008d9aed0de1929d1374f90d772551b46d4ef17480b0f5165f7R423)
which I had to make because of some circular import problem that got
exposed. Turns out we didn't need the mock at all so I just ripped it
out.
Stephen Cefali 1 year ago
parent
commit
8299cd3510

+ 0 - 1
pyproject.toml

@@ -258,7 +258,6 @@ module = [
     "sentry.api.endpoints.user_identity_config",
     "sentry.api.endpoints.user_index",
     "sentry.api.endpoints.user_notification_details",
-    "sentry.api.endpoints.user_notification_fine_tuning",
     "sentry.api.endpoints.user_permission_details",
     "sentry.api.endpoints.user_permissions",
     "sentry.api.endpoints.user_permissions_config",

+ 22 - 49
src/sentry/api/endpoints/user_notification_details.py

@@ -7,18 +7,22 @@ from rest_framework.response import Response
 from sentry.api.api_publish_status import ApiPublishStatus
 from sentry.api.base import control_silo_endpoint
 from sentry.api.bases.user import UserEndpoint
-from sentry.api.fields.empty_integer import EmptyIntegerField
 from sentry.api.serializers import Serializer, serialize
-from sentry.models.notificationsetting import NotificationSetting
 from sentry.models.options.user_option import UserOption
-from sentry.notifications.types import NotificationScopeType, UserOptionsSettingsKey
-from sentry.notifications.utils.legacy_mappings import (
-    USER_OPTION_SETTINGS,
-    get_option_value_from_int,
-    get_type_from_user_option_settings_key,
-    map_notification_settings_to_legacy,
-)
-from sentry.types.integrations import ExternalProviders
+from sentry.notifications.types import UserOptionsSettingsKey
+
+USER_OPTION_SETTINGS = {
+    UserOptionsSettingsKey.SELF_ACTIVITY: {
+        "key": "self_notifications",
+        "default": "0",
+        "type": bool,
+    },
+    UserOptionsSettingsKey.SELF_ASSIGN: {
+        "key": "self_assign_issue",
+        "default": "0",
+        "type": bool,
+    },
+}
 
 
 class UserNotificationsSerializer(Serializer):
@@ -28,19 +32,6 @@ class UserNotificationsSerializer(Serializer):
         ).select_related("user")
         keys_to_user_option_objects = {user_option.key: user_option for user_option in user_options}
 
-        notification_settings = NotificationSetting.objects._filter(
-            ExternalProviders.EMAIL,
-            scope_type=NotificationScopeType.USER,
-            user_ids=[user.id for user in item_list],
-        )
-        notification_settings_as_user_options = map_notification_settings_to_legacy(
-            notification_settings, user_mapping={user.id: user for user in item_list}
-        )
-
-        # Override deprecated UserOption rows with NotificationSettings.
-        for user_option in notification_settings_as_user_options:
-            keys_to_user_option_objects[user_option.key] = user_option
-
         results = defaultdict(list)
         for user_option in keys_to_user_option_objects.values():
             results[user_option.user].append(user_option)
@@ -57,21 +48,13 @@ class UserNotificationsSerializer(Serializer):
             elif uo["type"] == int:
                 data[key.value] = int(val)
 
-        data["weeklyReports"] = True  # This cannot be overridden
-
         return data
 
 
+# only expose legacy options
 class UserNotificationDetailsSerializer(serializers.Serializer):
-    deployNotifications = EmptyIntegerField(
-        required=False, min_value=2, max_value=4, allow_null=True
-    )
     personalActivityNotifications = serializers.BooleanField(required=False)
     selfAssignOnResolve = serializers.BooleanField(required=False)
-    subscribeByDefault = serializers.BooleanField(required=False)
-    workflowNotifications = EmptyIntegerField(
-        required=False, min_value=0, max_value=2, allow_null=True
-    )
 
 
 @control_silo_endpoint
@@ -100,22 +83,12 @@ class UserNotificationDetailsEndpoint(UserEndpoint):
                     status=status.HTTP_400_BAD_REQUEST,
                 )
 
-            type = get_type_from_user_option_settings_key(key)
-            if type:
-                NotificationSetting.objects.update_settings(
-                    ExternalProviders.EMAIL,
-                    type,
-                    get_option_value_from_int(type, int(value)),
-                    user_id=user.id,
-                )
-            else:
-                # Legacy user options which does not include weekly report
-                user_option, _ = UserOption.objects.get_or_create(
-                    key=USER_OPTION_SETTINGS[key]["key"],
-                    user=user,
-                    project_id=None,
-                    organization_id=None,
-                )
-                user_option.update(value=str(int(value)))
+            user_option, _ = UserOption.objects.get_or_create(
+                key=USER_OPTION_SETTINGS[key]["key"],
+                user=user,
+                project_id=None,
+                organization_id=None,
+            )
+            user_option.update(value=str(int(value)))
 
         return self.get(request, user)

+ 66 - 0
src/sentry/api/endpoints/user_notification_email.py

@@ -0,0 +1,66 @@
+from django.db import router, transaction
+from rest_framework import status
+from rest_framework.request import Request
+from rest_framework.response import Response
+
+from sentry.api.api_owners import ApiOwner
+from sentry.api.api_publish_status import ApiPublishStatus
+from sentry.api.base import control_silo_endpoint
+from sentry.api.bases.user import UserEndpoint
+from sentry.models.options.user_option import UserOption
+from sentry.models.useremail import UserEmail
+
+INVALID_EMAIL_MSG = (
+    "Invalid email value(s) provided. Email values must be verified emails for the given user."
+)
+
+
+@control_silo_endpoint
+class UserNotificationEmailEndpoint(UserEndpoint):
+    publish_status = {
+        "GET": ApiPublishStatus.PRIVATE,
+        "PUT": ApiPublishStatus.PRIVATE,
+    }
+    owner = ApiOwner.ISSUES
+
+    def get(self, request: Request, user) -> Response:
+        """
+        Fetches the user's email notification settings.
+        Returns a dictionary where the keys are the IDs of the projects
+        and the values are the email addresses to be used for notifications for that project.
+        """
+        email_options = UserOption.objects.filter(
+            key="mail:email", user=user, project_id__isnull=False
+        ).select_related("user")
+
+        return self.respond({str(option.project_id): option.value for option in email_options})
+
+    def put(self, request: Request, user) -> Response:
+        """
+        Updates the user's email notification settings.
+        The request data should be a dictionary where the keys are the IDs of the projects
+        and the values are the email addresses to be used for notifications for that project.
+        All email addresses must be verified and belong to the user.
+        If any email address is not verified or does not belong to the user, a 400 error is returned.
+        If the update is successful, a 204 status code is returned.
+        """
+        data = request.data
+
+        # Make sure target emails exist and are verified
+        emails_to_check = set(data.values())
+        emails = UserEmail.objects.filter(user=user, email__in=emails_to_check, is_verified=True)
+
+        # TODO(mgaeta): Is there a better way to check this?
+        if len(emails) != len(emails_to_check):
+            return Response({"detail": INVALID_EMAIL_MSG}, status=status.HTTP_400_BAD_REQUEST)
+
+        with transaction.atomic(using=router.db_for_write(UserOption)):
+            for id, value in data.items():
+                user_option, CREATED = UserOption.objects.get_or_create(
+                    user=user,
+                    key="mail:email",
+                    project_id=int(id),
+                )
+                user_option.update(value=str(value))
+
+        return Response(status=status.HTTP_204_NO_CONTENT)

+ 0 - 208
src/sentry/api/endpoints/user_notification_fine_tuning.py

@@ -1,208 +0,0 @@
-from typing import Any, Mapping
-
-from django.db import router, transaction
-from rest_framework import status
-from rest_framework.request import Request
-from rest_framework.response import Response
-
-from sentry.api.api_publish_status import ApiPublishStatus
-from sentry.api.base import control_silo_endpoint
-from sentry.api.bases.user import UserEndpoint
-from sentry.api.serializers import serialize
-from sentry.api.serializers.models import UserNotificationsSerializer
-from sentry.models.notificationsetting import NotificationSetting
-from sentry.models.notificationsettingoption import NotificationSettingOption
-from sentry.models.options.user_option import UserOption
-from sentry.models.useremail import UserEmail
-from sentry.notifications.types import (
-    FineTuningAPIKey,
-    NotificationScopeEnum,
-    NotificationSettingEnum,
-    NotificationSettingsOptionEnum,
-)
-from sentry.notifications.utils.legacy_mappings import (
-    get_option_value_from_int,
-    get_type_from_fine_tuning_key,
-)
-from sentry.services.hybrid_cloud.user.service import user_service
-from sentry.types.integrations import ExternalProviders
-
-INVALID_EMAIL_MSG = (
-    "Invalid email value(s) provided. Email values must be verified emails for the given user."
-)
-INVALID_USER_MSG = (
-    "User does not belong to at least one of the requested organizations (org_id: %s)."
-)
-
-
-@control_silo_endpoint
-class UserNotificationFineTuningEndpoint(UserEndpoint):
-    publish_status = {
-        "GET": ApiPublishStatus.UNKNOWN,
-        "PUT": ApiPublishStatus.UNKNOWN,
-    }
-
-    def get(self, request: Request, user, notification_type) -> Response:
-        try:
-            notification_type = FineTuningAPIKey(notification_type)
-        except ValueError:
-            return Response(
-                {"detail": "Unknown notification type: %s." % notification_type},
-                status=status.HTTP_404_NOT_FOUND,
-            )
-
-        notifications = UserNotificationsSerializer()
-        return Response(
-            serialize(
-                user,
-                request.user,
-                notifications,
-                notification_type=notification_type,
-            )
-        )
-
-    def put(self, request: Request, user, notification_type) -> Response:
-        """
-        Update user notification options
-        ````````````````````````````````
-
-        Updates user's notification options on a per project or organization
-        basis. Expected payload is a map/dict whose key is a project or org id
-        and value varies depending on `notification_type`.
-
-        For `alerts`, `workflow`, `email` it expects a key of projectId
-        For `deploy` and `reports` it expects a key of organizationId
-
-        For `alerts`, `workflow`, `deploy`, it expects a value of:
-            - "-1" = for "default" value (i.e. delete the option)
-            - "0"  = disabled
-            - "1"  = enabled
-        For `reports` it is only a boolean.
-        For `email` it is a verified email (string).
-
-        :auth required:
-        :pparam string notification_type:  One of:  alerts, workflow, reports, deploy, email
-        :param map: Expects a map of id -> value (enabled or email)
-        """
-        try:
-            notification_type = FineTuningAPIKey(notification_type)
-        except ValueError:
-            return Response(
-                {"detail": "Unknown notification type: %s." % notification_type},
-                status=status.HTTP_404_NOT_FOUND,
-            )
-
-        if notification_type == FineTuningAPIKey.REPORTS:
-            return self._handle_put_reports(user, request.data)
-
-        # Validate that all of the IDs are integers.
-        try:
-            for k in request.data.keys():
-                int(k)
-        except ValueError:
-            return Response(
-                {"detail": "Invalid id value provided. Id values should be integers."},
-                status=status.HTTP_400_BAD_REQUEST,
-            )
-
-        # Make sure that the IDs we are going to update are a subset of the
-        # user's list of organizations or projects.
-
-        if notification_type == FineTuningAPIKey.EMAIL:
-            return self._handle_put_emails(user, request.data)
-
-        return self._handle_put_notification_settings(user, notification_type, request.data)
-
-    @staticmethod
-    def _handle_put_reports(user, data):
-        user_option, _ = UserOption.objects.get_or_create(
-            user=user,
-            key="reports:disabled-organizations",
-        )
-
-        value = set(user_option.value or [])
-
-        # The set of IDs of the organizations of which the user is a member.
-        org_ids = {o.id for o in user_service.get_organizations(user_id=user.id, only_visible=True)}
-        for org_id, enabled in data.items():
-            org_id = int(org_id)
-            # We want "0" to be falsey
-            enabled = int(enabled)
-
-            # make sure user is in org
-            if org_id not in org_ids:
-                return Response(
-                    {"detail": INVALID_USER_MSG % org_id}, status=status.HTTP_403_FORBIDDEN
-                )
-
-            # The list contains organization IDs that should have reports
-            # DISABLED. If enabled, we need to check if org_id exists in list
-            # (because by default they will have reports enabled.)
-            if enabled and org_id in value:
-                value.remove(org_id)
-            elif not enabled:
-                value.add(org_id)
-
-            NotificationSettingOption.objects.create_or_update(
-                scope_type=NotificationScopeEnum.ORGANIZATION.value,
-                scope_identifier=org_id,
-                user_id=user.id,
-                type=NotificationSettingEnum.REPORTS.value,
-                values={
-                    "value": NotificationSettingsOptionEnum.ALWAYS.value
-                    if enabled
-                    else NotificationSettingsOptionEnum.NEVER.value,
-                },
-            )
-
-        user_option.update(value=list(value))
-        return Response(status=status.HTTP_204_NO_CONTENT)
-
-    @staticmethod
-    def _handle_put_emails(user, data):
-        # Make sure target emails exist and are verified
-        emails_to_check = set(data.values())
-        emails = UserEmail.objects.filter(user=user, email__in=emails_to_check, is_verified=True)
-
-        # TODO(mgaeta): Is there a better way to check this?
-        if len(emails) != len(emails_to_check):
-            return Response({"detail": INVALID_EMAIL_MSG}, status=status.HTTP_400_BAD_REQUEST)
-
-        with transaction.atomic(using=router.db_for_write(UserOption)):
-            for id, value in data.items():
-                user_option, CREATED = UserOption.objects.get_or_create(
-                    user=user,
-                    key="mail:email",
-                    project_id=int(id),
-                )
-                user_option.update(value=str(value))
-
-        return Response(status=status.HTTP_204_NO_CONTENT)
-
-    @staticmethod
-    def _handle_put_notification_settings(
-        user, notification_type: FineTuningAPIKey, data: Mapping[str, Any]
-    ):
-        with transaction.atomic(using=router.db_for_write(NotificationSetting)):
-            for setting_obj_id_str, value_str in data.items():
-                setting_obj_id_int = int(setting_obj_id_str)
-
-                # This partitioning always does the same thing because notification_type stays constant.
-                project_option, organization_option = (
-                    (None, setting_obj_id_int)
-                    if notification_type == FineTuningAPIKey.DEPLOY
-                    else (setting_obj_id_int, None)
-                )
-
-                type = get_type_from_fine_tuning_key(notification_type)
-                value_int = int(value_str)
-                NotificationSetting.objects.update_settings(
-                    ExternalProviders.EMAIL,
-                    type,
-                    get_option_value_from_int(type, value_int),
-                    user_id=user.id,
-                    project=project_option,
-                    organization=organization_option,
-                )
-
-        return Response(status=status.HTTP_204_NO_CONTENT)

+ 0 - 1
src/sentry/api/serializers/models/__init__.py

@@ -82,7 +82,6 @@ from .tagvalue import *  # noqa: F401,F403
 from .team import *  # noqa: F401,F403
 from .user import *  # noqa: F401,F403
 from .user_identity_config import *  # noqa: F401,F403
-from .user_notifications import *  # noqa: F401,F403
 from .user_social_auth import *  # noqa: F401,F403
 from .useremail import *  # noqa: F401,F403
 from .userip import *  # noqa: F401,F403

+ 0 - 65
src/sentry/api/serializers/models/user_notifications.py

@@ -1,65 +0,0 @@
-from collections import defaultdict
-from typing import Iterable
-
-from sentry.api.serializers import Serializer
-from sentry.models.notificationsetting import NotificationSetting
-from sentry.models.options.user_option import UserOption
-from sentry.notifications.types import FineTuningAPIKey, NotificationScopeType
-from sentry.notifications.utils.legacy_mappings import (
-    get_type_from_fine_tuning_key,
-    map_notification_settings_to_legacy,
-)
-from sentry.types.integrations import ExternalProviders
-
-
-def handle_legacy(notification_type: FineTuningAPIKey, users: Iterable) -> Iterable:
-    """For EMAIL and REPORTS, check UserOptions."""
-    filter_args = {}
-    if notification_type == FineTuningAPIKey.EMAIL:
-        filter_args["project_id__isnull"] = False
-
-    key = {
-        FineTuningAPIKey.EMAIL: "mail:email",
-        FineTuningAPIKey.REPORTS: "reports:disabled-organizations",
-    }.get(notification_type)
-
-    return UserOption.objects.filter(key=key, user__in=users, **filter_args).select_related("user")
-
-
-class UserNotificationsSerializer(Serializer):
-    def get_attrs(self, item_list, user, **kwargs):
-        notification_type = kwargs["notification_type"]
-        type = get_type_from_fine_tuning_key(notification_type)
-        if not type:
-            data = handle_legacy(notification_type, item_list)
-        else:
-            user_mapping = {user.id: user for user in item_list}
-            notifications_settings = NotificationSetting.objects._filter(
-                ExternalProviders.EMAIL,
-                get_type_from_fine_tuning_key(notification_type),
-                user_ids=list(user_mapping.keys()),
-            ).exclude(scope_type=NotificationScopeType.USER.value)
-            data = map_notification_settings_to_legacy(notifications_settings, user_mapping)
-
-        results = defaultdict(list)
-        for uo in data:
-            results[uo.user].append(uo)
-
-        return results
-
-    def serialize(self, obj, attrs, user, **kwargs):
-        notification_type = kwargs["notification_type"]
-        data = {}
-
-        for uo in attrs:
-            if notification_type == FineTuningAPIKey.REPORTS:
-                # UserOption for key=reports:disabled-organizations saves a list of orgIds
-                # that should not receive reports
-                # This UserOption should have both project + organization = None
-                for org_id in uo.value:
-                    data[org_id] = "0"
-            elif uo.project_id is not None:
-                data[uo.project_id] = str(uo.value)
-            elif uo.organization_id is not None:
-                data[uo.organization_id] = str(uo.value)
-        return data

+ 4 - 4
src/sentry/api/urls.py

@@ -583,7 +583,7 @@ from .endpoints.user_identity_details import UserIdentityDetailsEndpoint
 from .endpoints.user_index import UserIndexEndpoint
 from .endpoints.user_ips import UserIPsEndpoint
 from .endpoints.user_notification_details import UserNotificationDetailsEndpoint
-from .endpoints.user_notification_fine_tuning import UserNotificationFineTuningEndpoint
+from .endpoints.user_notification_email import UserNotificationEmailEndpoint
 from .endpoints.user_notification_settings_options import UserNotificationSettingsOptionsEndpoint
 from .endpoints.user_notification_settings_options_detail import (
     UserNotificationSettingsOptionsDetailEndpoint,
@@ -876,9 +876,9 @@ USER_URLS = [
         name="sentry-api-0-user-notifications",
     ),
     re_path(
-        r"^(?P<user_id>[^\/]+)/notifications/(?P<notification_type>[^\/]+)/$",
-        UserNotificationFineTuningEndpoint.as_view(),
-        name="sentry-api-0-user-notifications-fine-tuning",
+        r"^(?P<user_id>[^\/]+)/notifications/email/$",
+        UserNotificationEmailEndpoint.as_view(),
+        name="sentry-api-0-user-notifications-email",
     ),
     re_path(
         r"^(?P<user_id>[^\/]+)/notification-options/$",

+ 0 - 7
src/sentry/notifications/types.py

@@ -205,15 +205,8 @@ class FineTuningAPIKey(Enum):
 
 
 class UserOptionsSettingsKey(Enum):
-    DEPLOY = "deployNotifications"
     SELF_ACTIVITY = "personalActivityNotifications"
     SELF_ASSIGN = "selfAssignOnResolve"
-    SUBSCRIBE_BY_DEFAULT = "subscribeByDefault"
-    WORKFLOW = "workflowNotifications"
-    ACTIVE_RELEASE = "activeReleaseNotifications"
-    APPROVAL = "approvalNotifications"
-    QUOTA = "quotaNotifications"
-    SPIKE_PROTECTION = "spikeProtectionNotifications"
 
 
 VALID_VALUES_FOR_KEY = {

+ 2 - 1
src/sentry/notifications/utils/__init__.py

@@ -47,7 +47,6 @@ from sentry.models.release import Release
 from sentry.models.releasecommit import ReleaseCommit
 from sentry.models.repository import Repository
 from sentry.models.rule import Rule
-from sentry.notifications.notify import notify
 from sentry.services.hybrid_cloud.integration import integration_service
 from sentry.services.hybrid_cloud.user import RpcUser
 from sentry.services.hybrid_cloud.util import region_silo_function
@@ -421,6 +420,8 @@ def get_notification_group_title(
 
 
 def send_activity_notification(notification: ActivityNotification | UserReportNotification) -> None:
+    from sentry.notifications.notify import notify
+
     participants_by_provider = notification.get_participants_with_group_subscription_reason()
     if participants_by_provider.is_empty():
         return

+ 0 - 224
src/sentry/notifications/utils/legacy_mappings.py

@@ -1,224 +0,0 @@
-from __future__ import annotations
-
-from collections import namedtuple
-from typing import Any, Iterable, Mapping, Optional, Union
-
-from sentry.models.user import User
-from sentry.notifications.types import (
-    FineTuningAPIKey,
-    NotificationScopeType,
-    NotificationSettingOptionValues,
-    NotificationSettingTypes,
-    UserOptionsSettingsKey,
-)
-from sentry.services.hybrid_cloud.user.model import RpcUser
-
-LegacyUserOptionClone = namedtuple(
-    "LegacyUserOptionClone",
-    [
-        "user",
-        "project_id",
-        "organization_id",
-        "key",
-        "value",
-    ],
-)
-
-USER_OPTION_SETTINGS = {
-    UserOptionsSettingsKey.DEPLOY: {
-        "key": "deploy-emails",
-        "default": "3",
-        "type": int,
-    },
-    UserOptionsSettingsKey.SELF_ACTIVITY: {
-        "key": "self_notifications",
-        "default": "0",
-        "type": bool,
-    },
-    UserOptionsSettingsKey.SELF_ASSIGN: {
-        "key": "self_assign_issue",
-        "default": "0",
-        "type": bool,
-    },
-    UserOptionsSettingsKey.SUBSCRIBE_BY_DEFAULT: {
-        "key": "subscribe_by_default",
-        "default": "1",
-        "type": bool,
-    },
-    UserOptionsSettingsKey.WORKFLOW: {
-        "key": "workflow:notifications",
-        "default": "1",
-        "type": int,
-    },
-}
-
-KEYS_TO_LEGACY_KEYS = {
-    NotificationSettingTypes.DEPLOY: "deploy-emails",
-    NotificationSettingTypes.ISSUE_ALERTS: "mail:alert",
-    NotificationSettingTypes.WORKFLOW: "workflow:notifications",
-}
-
-
-KEY_VALUE_TO_LEGACY_VALUE = {
-    NotificationSettingTypes.DEPLOY: {
-        NotificationSettingOptionValues.ALWAYS: 2,
-        NotificationSettingOptionValues.COMMITTED_ONLY: 3,
-        NotificationSettingOptionValues.NEVER: 4,
-    },
-    NotificationSettingTypes.ISSUE_ALERTS: {
-        NotificationSettingOptionValues.ALWAYS: 1,
-        NotificationSettingOptionValues.NEVER: 0,
-    },
-    NotificationSettingTypes.WORKFLOW: {
-        NotificationSettingOptionValues.ALWAYS: 0,
-        NotificationSettingOptionValues.SUBSCRIBE_ONLY: 1,
-        NotificationSettingOptionValues.NEVER: 2,
-    },
-}
-
-LEGACY_VALUE_TO_KEY = {
-    NotificationSettingTypes.DEPLOY: {
-        -1: NotificationSettingOptionValues.DEFAULT,
-        2: NotificationSettingOptionValues.ALWAYS,
-        3: NotificationSettingOptionValues.COMMITTED_ONLY,
-        4: NotificationSettingOptionValues.NEVER,
-    },
-    NotificationSettingTypes.ISSUE_ALERTS: {
-        -1: NotificationSettingOptionValues.DEFAULT,
-        0: NotificationSettingOptionValues.NEVER,
-        1: NotificationSettingOptionValues.ALWAYS,
-    },
-    NotificationSettingTypes.WORKFLOW: {
-        -1: NotificationSettingOptionValues.DEFAULT,
-        0: NotificationSettingOptionValues.ALWAYS,
-        1: NotificationSettingOptionValues.SUBSCRIBE_ONLY,
-        2: NotificationSettingOptionValues.NEVER,
-    },
-}
-
-
-def get_legacy_key(type: NotificationSettingTypes, scope_type: NotificationScopeType) -> str | None:
-    """Temporary mapping from new enum types to legacy strings."""
-    if scope_type == NotificationScopeType.USER and type == NotificationSettingTypes.ISSUE_ALERTS:
-        return "subscribe_by_default"
-
-    return KEYS_TO_LEGACY_KEYS.get(type)
-
-
-def get_legacy_value(type: NotificationSettingTypes, value: NotificationSettingOptionValues) -> str:
-    """
-    Temporary mapping from new enum types to legacy strings. Each type has a separate mapping.
-    """
-
-    return str(KEY_VALUE_TO_LEGACY_VALUE.get(type, {}).get(value))
-
-
-def get_option_value_from_boolean(value: bool) -> NotificationSettingOptionValues:
-    if value:
-        return NotificationSettingOptionValues.ALWAYS
-    else:
-        return NotificationSettingOptionValues.NEVER
-
-
-def get_option_value_from_int(
-    type: NotificationSettingTypes, value: int
-) -> NotificationSettingOptionValues | None:
-    return LEGACY_VALUE_TO_KEY.get(type, {}).get(value)
-
-
-def get_type_from_fine_tuning_key(key: FineTuningAPIKey) -> NotificationSettingTypes | None:
-    return {
-        FineTuningAPIKey.ALERTS: NotificationSettingTypes.ISSUE_ALERTS,
-        FineTuningAPIKey.DEPLOY: NotificationSettingTypes.DEPLOY,
-        FineTuningAPIKey.WORKFLOW: NotificationSettingTypes.WORKFLOW,
-    }.get(key)
-
-
-def get_type_from_user_option_settings_key(
-    key: UserOptionsSettingsKey,
-) -> NotificationSettingTypes | None:
-    return {
-        UserOptionsSettingsKey.DEPLOY: NotificationSettingTypes.DEPLOY,
-        UserOptionsSettingsKey.WORKFLOW: NotificationSettingTypes.WORKFLOW,
-        UserOptionsSettingsKey.SUBSCRIBE_BY_DEFAULT: NotificationSettingTypes.ISSUE_ALERTS,
-    }.get(key)
-
-
-def get_key_from_legacy(key: str) -> NotificationSettingTypes | None:
-    return {
-        "deploy-emails": NotificationSettingTypes.DEPLOY,
-        "mail:alert": NotificationSettingTypes.ISSUE_ALERTS,
-        "subscribe_by_default": NotificationSettingTypes.ISSUE_ALERTS,
-        "workflow:notifications": NotificationSettingTypes.WORKFLOW,
-    }.get(key)
-
-
-def get_key_value_from_legacy(
-    key: str, value: Any
-) -> tuple[NotificationSettingTypes | None, NotificationSettingOptionValues | None]:
-    type = get_key_from_legacy(key)
-    if type not in LEGACY_VALUE_TO_KEY:
-        return None, None
-    option_value = LEGACY_VALUE_TO_KEY.get(type, {}).get(int(value))
-
-    return type, option_value
-
-
-def get_legacy_object(
-    notification_setting: Any,
-    user_mapping: Optional[Mapping[int, Union[User, RpcUser]]] = None,
-) -> Any:
-    type = NotificationSettingTypes(notification_setting.type)
-    value = NotificationSettingOptionValues(notification_setting.value)
-    scope_type = NotificationScopeType(notification_setting.scope_type)
-    key = get_legacy_key(type, scope_type)
-
-    data = {
-        "key": key,
-        "value": get_legacy_value(type, value),
-        "user": user_mapping.get(notification_setting.user_id) if user_mapping else None,
-        "project_id": None,
-        "organization_id": None,
-    }
-
-    if scope_type == NotificationScopeType.PROJECT:
-        data["project_id"] = notification_setting.scope_identifier
-    if scope_type == NotificationScopeType.ORGANIZATION:
-        data["organization_id"] = notification_setting.scope_identifier
-
-    return LegacyUserOptionClone(**data)
-
-
-def map_notification_settings_to_legacy(
-    notification_settings: Iterable[Any],
-    user_mapping: Mapping[int, Union[User, RpcUser]],
-) -> list[Any]:
-    """A hack for legacy serializers. Pretend a list of NotificationSettings is a list of UserOptions."""
-    return [
-        get_legacy_object(notification_setting, user_mapping)
-        for notification_setting in notification_settings
-    ]
-
-
-def get_parent_mappings(
-    notification_settings: Iterable[Any],
-) -> tuple[Mapping[int, Any], Mapping[int, Any]]:
-    """Prefetch a list of Project or Organization objects for the Serializer."""
-    from sentry.models.organization import Organization
-    from sentry.models.project import Project
-
-    project_ids = []
-    organization_ids = []
-    for notification_setting in notification_settings:
-        if notification_setting.scope_type == NotificationScopeType.PROJECT.value:
-            project_ids.append(notification_setting.scope_identifier)
-        if notification_setting.scope_type == NotificationScopeType.ORGANIZATION.value:
-            organization_ids.append(notification_setting.scope_identifier)
-
-    projects = Project.objects.filter(id__in=project_ids)
-    organizations = Organization.objects.filter(id__in=organization_ids)
-
-    project_mapping = {project.id: project for project in projects}
-    organization_mapping = {organization.id: organization for organization in organizations}
-
-    return project_mapping, organization_mapping

Some files were not shown because too many files changed in this diff