Repository: getsentry/sentry

Issue:
Add subscription for team in `endpoints/group_notes.py`
A few changes we will need to make here
1) Update `extract_user_ids_from_mentions` to no longer unpack teams into `user_id`
2) Update the call to `bulk_subscribe` to handle the new output of the function above


Discussion:

Resolving patch:
diff --git a/src/sentry/api/endpoints/group_notes.py b/src/sentry/api/endpoints/group_notes.py
--- a/src/sentry/api/endpoints/group_notes.py
+++ b/src/sentry/api/endpoints/group_notes.py
@@ -5,6 +5,7 @@
 from rest_framework.request import Request
 from rest_framework.response import Response
 
+from sentry import features
 from sentry.api.api_publish_status import ApiPublishStatus
 from sentry.api.base import region_silo_endpoint
 from sentry.api.bases.group import GroupEndpoint
@@ -74,11 +75,18 @@ def post(self, request: Request, group) -> Response:
             group=group, user_ids=mentioned_users["users"], reason=GroupSubscriptionReason.mentioned
         )
 
-        GroupSubscription.objects.bulk_subscribe(
-            group=group,
-            user_ids=mentioned_users["team_users"],
-            reason=GroupSubscriptionReason.team_mentioned,
-        )
+        if features.has("organizations:team-workflow-notifications", group.organization):
+            GroupSubscription.objects.bulk_subscribe(
+                group=group,
+                team_ids=mentioned_users["teams"],
+                reason=GroupSubscriptionReason.team_mentioned,
+            )
+        else:
+            GroupSubscription.objects.bulk_subscribe(
+                group=group,
+                user_ids=mentioned_users["team_users"],
+                reason=GroupSubscriptionReason.team_mentioned,
+            )
 
         activity = Activity.objects.create_group_activity(
             group, ActivityType.NOTE, user_id=request.user.id, data=data
diff --git a/src/sentry/api/serializers/rest_framework/mentions.py b/src/sentry/api/serializers/rest_framework/mentions.py
--- a/src/sentry/api/serializers/rest_framework/mentions.py
+++ b/src/sentry/api/serializers/rest_framework/mentions.py
@@ -11,10 +11,10 @@
 def extract_user_ids_from_mentions(organization_id, mentions):
     """
     Extracts user ids from a set of mentions. Mentions should be a list of
-    `ActorTuple` instances. Returns a dictionary with 'users' and 'team_users' keys.
-    'users' is the user ids for all explicitly mentioned users, and 'team_users'
+    `ActorTuple` instances. Returns a dictionary with 'users', 'team_users', and 'teams' keys.
+    'users' is the user ids for all explicitly mentioned users, 'team_users'
     is all user ids from explicitly mentioned teams, excluding any already
-    mentioned users.
+    mentioned users, and 'teams' is the team ids for all explicitly mentioned teams.
     """
     actors: Sequence[RpcUser | Team] = ActorTuple.resolve_many(mentions)
     actor_mentions = separate_resolved_actors(actors)
@@ -33,6 +33,7 @@ def extract_user_ids_from_mentions(organization_id, mentions):
     return {
         "users": {user.id for user in actor_mentions["users"]},
         "team_users": set(mentioned_team_users),
+        "teams": {team.id for team in actor_mentions["teams"]},
     }
 
 


Test patch:
diff --git a/tests/sentry/api/endpoints/test_group_notes.py b/tests/sentry/api/endpoints/test_group_notes.py
--- a/tests/sentry/api/endpoints/test_group_notes.py
+++ b/tests/sentry/api/endpoints/test_group_notes.py
@@ -5,6 +5,7 @@
 from sentry.silo import SiloMode
 from sentry.tasks.merge import merge_groups
 from sentry.testutils.cases import APITestCase
+from sentry.testutils.helpers.features import with_feature
 from sentry.testutils.silo import assume_test_silo_mode, region_silo_test
 from sentry.types.activity import ActivityType
 
@@ -166,7 +167,7 @@ def test_with_mentions(self):
 
         assert response.data == {"mentions": ["Cannot mention a non team member"]}
 
-    def test_with_team_mentions(self):
+    def test_with_team_user_mentions(self):
         user = self.create_user(email="redTeamUser@example.com")
 
         self.org = self.create_organization(name="Gnarly Org", owner=None)
@@ -261,3 +262,73 @@ def test_with_group_link(self):
                 assert activity.user_id == self.user.id
                 assert activity.group == group
                 assert activity.data == {"text": comment, "external_id": "123456789"}
+
+    @with_feature("organizations:team-workflow-notifications")
+    def test_with_team_mentions(self):
+        """
+        This test assures teams can be subscribed via mention, rather than subscribing the individual users on the team.
+        """
+        user = self.create_user(email="grunt@teamgalactic.com")
+
+        self.org = self.create_organization(name="Galactic Org", owner=None)
+        self.team = self.create_team(organization=self.org, name="Team Galactic", members=[user])
+        self.create_member(user=self.user, organization=self.org, role="member", teams=[self.team])
+
+        group = self.group
+
+        self.login_as(user=self.user)
+
+        url = f"/api/0/issues/{group.id}/comments/"
+
+        # mentioning a team in the project returns 201
+        response = self.client.post(
+            url,
+            format="json",
+            data={
+                "text": "hey **team-galactic** check out this bug",
+                "mentions": ["team:%s" % self.team.id],
+            },
+        )
+        assert response.status_code == 201, response.content
+
+        # should subscribe the team and not the user with the team_mentioned reason
+        assert GroupSubscription.objects.filter(
+            group=group, team=self.team, reason=GroupSubscriptionReason.team_mentioned
+        ).exists()
+        assert not GroupSubscription.objects.filter(group=group, user_id=user.id)
+
+    @with_feature("organizations:team-workflow-notifications")
+    def test_with_user_on_team_mentions(self):
+        """
+        This test assures that if a user is mentioned along with their team, they get subscribed both individually and as part of the team.
+        """
+        user = self.create_user(email="maxie@teammagma.com")
+
+        self.org = self.create_organization(name="Emerald Org", owner=None)
+        self.team = self.create_team(organization=self.org, name="Team Magma", members=[user])
+        self.create_member(user=self.user, organization=self.org, role="member", teams=[self.team])
+
+        group = self.group
+
+        self.login_as(user=self.user)
+
+        url = f"/api/0/issues/{group.id}/comments/"
+
+        # mentioning a team and a user in the project returns 201
+        response = self.client.post(
+            url,
+            format="json",
+            data={
+                "text": "look at this **team-magma** **maxie@teammagma.com**",
+                "mentions": ["team:%s" % self.team.id, "%s" % user.id],
+            },
+        )
+        assert response.status_code == 201, response.content
+
+        # should subscribe the team and the user
+        assert GroupSubscription.objects.filter(
+            group=group, team=self.team, reason=GroupSubscriptionReason.team_mentioned
+        ).exists()
+        assert GroupSubscription.objects.filter(
+            group=group, user_id=user.id, reason=GroupSubscriptionReason.mentioned
+        )
diff --git a/tests/sentry/api/serializers/rest_framework/test_mentions.py b/tests/sentry/api/serializers/rest_framework/test_mentions.py
--- a/tests/sentry/api/serializers/rest_framework/test_mentions.py
+++ b/tests/sentry/api/serializers/rest_framework/test_mentions.py
@@ -11,6 +11,7 @@ def test_users(self):
         result = extract_user_ids_from_mentions(self.organization.id, [actor])
         assert result["users"] == {self.user.id}
         assert result["team_users"] == set()
+        assert result["teams"] == set()
 
         other_user = self.create_user()
         result = extract_user_ids_from_mentions(
@@ -18,6 +19,7 @@ def test_users(self):
         )
         assert result["users"] == {self.user.id, other_user.id}
         assert result["team_users"] == set()
+        assert result["teams"] == set()
 
     def test_teams(self):
         member_user = self.create_user()
@@ -32,6 +34,7 @@ def test_teams(self):
         result = extract_user_ids_from_mentions(self.organization.id, [actor])
         assert result["users"] == set()
         assert result["team_users"] == {self.user.id, member_user.id}
+        assert result["teams"] == {self.team.id}
 
         # Explicitly mentioned users shouldn't be included in team_users
         result = extract_user_ids_from_mentions(
@@ -39,3 +42,4 @@ def test_teams(self):
         )
         assert result["users"] == {member_user.id}
         assert result["team_users"] == {self.user.id}
+        assert result["teams"] == {self.team.id}