Browse Source

feat(crons): Add backend owner filtering (#69135)

Evan Purkhiser 10 months ago
parent
commit
092afb445b

+ 7 - 0
src/sentry/apidocs/parameters.py

@@ -217,6 +217,13 @@ class MonitorParams:
         type=str,
         description="The name of environment for the monitor environment.",
     )
+    OWNER = OpenApiParameter(
+        name="owner",
+        location="query",
+        required=False,
+        type=str,
+        description="The owner of the monitor, in the format `user:id` or `team:id`. May be specified multiple times.",
+    )
 
 
 class EventParams:

+ 28 - 1
src/sentry/monitors/endpoints/organization_monitor_index.py

@@ -18,6 +18,7 @@ from sentry.api.api_publish_status import ApiPublishStatus
 from sentry.api.base import region_silo_endpoint
 from sentry.api.bases import NoProjects
 from sentry.api.bases.organization import OrganizationEndpoint
+from sentry.api.helpers.teams import get_teams
 from sentry.api.paginator import OffsetPaginator
 from sentry.api.serializers import serialize
 from sentry.apidocs.constants import (
@@ -26,7 +27,7 @@ from sentry.apidocs.constants import (
     RESPONSE_NOT_FOUND,
     RESPONSE_UNAUTHORIZED,
 )
-from sentry.apidocs.parameters import GlobalParams, OrganizationParams
+from sentry.apidocs.parameters import GlobalParams, MonitorParams, OrganizationParams
 from sentry.apidocs.utils import inline_sentry_response_serializer
 from sentry.constants import ObjectStatus
 from sentry.db.models.query import in_iexact
@@ -49,6 +50,7 @@ from sentry.monitors.serializers import (
 from sentry.monitors.utils import create_issue_alert_rule, signal_monitor_created
 from sentry.monitors.validators import MonitorBulkEditValidator, MonitorValidator
 from sentry.search.utils import tokenize_query
+from sentry.utils.actor import ActorTuple
 from sentry.utils.outcomes import Outcome
 
 from .base import OrganizationMonitorPermission
@@ -105,6 +107,7 @@ class OrganizationMonitorIndexEndpoint(OrganizationEndpoint):
             GlobalParams.ORG_SLUG,
             OrganizationParams.PROJECT,
             GlobalParams.ENVIRONMENT,
+            MonitorParams.OWNER,
         ],
         responses={
             200: inline_sentry_response_serializer("MonitorList", list[MonitorSerializerResponse]),
@@ -131,6 +134,7 @@ class OrganizationMonitorIndexEndpoint(OrganizationEndpoint):
             ]
         )
         query = request.GET.get("query")
+        owners = request.GET.getlist("owner")
         is_asc = request.GET.get("asc", "1") == "1"
         sort = request.GET.get("sort", "status")
 
@@ -196,6 +200,29 @@ class OrganizationMonitorIndexEndpoint(OrganizationEndpoint):
         if not is_asc:
             sort_fields = [flip_sort_direction(sort_field) for sort_field in sort_fields]
 
+        if owners:
+            owners = set(owners)
+
+            # Remove special 'myteams' from owners, this can't be parsed as an ActorTuple
+            myteams = ["myteams"] if "myteams" in owners else []
+            owners.discard("myteams")
+
+            actors = [ActorTuple.from_actor_identifier(identifier) for identifier in owners]
+
+            user_ids = [actor.id for actor in actors if actor.type == User]
+            team_ids = [actor.id for actor in actors if actor.type == Team]
+
+            teams = get_teams(
+                request,
+                organization,
+                teams=[*team_ids, *myteams],
+            )
+            team_ids = [team.id for team in teams]
+
+            queryset = queryset.filter(
+                Q(owner_user_id__in=user_ids) | Q(owner_team_id__in=team_ids)
+            )
+
         if query:
             tokens = tokenize_query(query)
             for key, value in tokens.items():

+ 37 - 0
tests/sentry/monitors/endpoints/test_organization_monitor_index.py

@@ -209,6 +209,43 @@ class ListOrganizationMonitorsTest(MonitorTestCase):
         )
         self.check_valid_response(response, expected)
 
+    def test_filter_owners(self):
+        user_1 = self.create_user()
+        user_2 = self.create_user()
+        team_1 = self.create_team()
+        team_2 = self.create_team()
+        self.create_team_membership(team_2, user=self.user)
+
+        mon_1 = self._create_monitor(name="A monitor", owner_user_id=user_1.id)
+        mon_2 = self._create_monitor(name="B monitor", owner_user_id=user_2.id)
+        mon_3 = self._create_monitor(name="C monitor", owner_user_id=None, owner_team_id=team_1.id)
+        mon_4 = self._create_monitor(name="C monitor", owner_user_id=None, owner_team_id=team_2.id)
+
+        # Monitor by user
+        response = self.get_success_response(self.organization.slug, owner=[f"user:{user_1.id}"])
+        self.check_valid_response(response, [mon_1])
+
+        # Monitors by users and teams
+        response = self.get_success_response(
+            self.organization.slug,
+            owner=[f"user:{user_1.id}", f"user:{user_2.id}", f"team:{team_1.id}"],
+        )
+        self.check_valid_response(response, [mon_1, mon_2, mon_3])
+
+        # myteams
+        response = self.get_success_response(
+            self.organization.slug,
+            owner=["myteams"],
+        )
+        self.check_valid_response(response, [mon_4])
+
+        # Invalid user ID
+        response = self.get_success_response(
+            self.organization.slug,
+            owner=["user:12345"],
+        )
+        self.check_valid_response(response, [])
+
     def test_all_monitor_environments(self):
         monitor = self._create_monitor()
         monitor_environment = self._create_monitor_environment(