Skip to content

Commit 029c25a

Browse files
ameliahsuandrewshie-sentry
authored andcommitted
feat(aci): add detector types endpoint (#87401)
adds endpoint to fetch available detector types for the UI
1 parent 5063f4f commit 029c25a

File tree

3 files changed

+147
-0
lines changed

3 files changed

+147
-0
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from drf_spectacular.utils import extend_schema
2+
3+
from sentry.api.api_owners import ApiOwner
4+
from sentry.api.api_publish_status import ApiPublishStatus
5+
from sentry.api.base import region_silo_endpoint
6+
from sentry.api.bases import OrganizationEndpoint
7+
from sentry.api.paginator import OffsetPaginator
8+
from sentry.apidocs.constants import (
9+
RESPONSE_BAD_REQUEST,
10+
RESPONSE_FORBIDDEN,
11+
RESPONSE_NOT_FOUND,
12+
RESPONSE_UNAUTHORIZED,
13+
)
14+
from sentry.apidocs.parameters import GlobalParams
15+
from sentry.apidocs.utils import inline_sentry_response_serializer
16+
from sentry.issues import grouptype
17+
18+
19+
@region_silo_endpoint
20+
class OrganizationDetectorTypeIndexEndpoint(OrganizationEndpoint):
21+
publish_status = {
22+
"GET": ApiPublishStatus.EXPERIMENTAL,
23+
}
24+
owner = ApiOwner.ISSUES
25+
26+
@extend_schema(
27+
operation_id="Fetch Detector Types",
28+
parameters=[
29+
GlobalParams.ORG_ID_OR_SLUG,
30+
],
31+
responses={
32+
201: inline_sentry_response_serializer("ListDetectorTypes", list[str]),
33+
400: RESPONSE_BAD_REQUEST,
34+
401: RESPONSE_UNAUTHORIZED,
35+
403: RESPONSE_FORBIDDEN,
36+
404: RESPONSE_NOT_FOUND,
37+
},
38+
)
39+
def get(self, request, organization):
40+
"""
41+
Returns a list of detector types for a given org
42+
"""
43+
type_slugs = [
44+
gt.slug
45+
for gt in grouptype.registry.get_visible(organization)
46+
if gt.detector_handler is not None
47+
]
48+
type_slugs.sort()
49+
50+
return self.paginate(
51+
request=request,
52+
queryset=type_slugs,
53+
paginator_cls=OffsetPaginator,
54+
)

src/sentry/workflow_engine/endpoints/urls.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from django.urls import re_path
22

33
from .organization_data_condition_index import OrganizationDataConditionIndexEndpoint
4+
from .organization_detector_types import OrganizationDetectorTypeIndexEndpoint
45
from .organization_workflow_details import OrganizationWorkflowDetailsEndpoint
56
from .organization_workflow_index import OrganizationWorkflowIndexEndpoint
67
from .project_detector_details import ProjectDetectorDetailsEndpoint
@@ -46,4 +47,9 @@
4647
OrganizationDataConditionIndexEndpoint.as_view(),
4748
name="sentry-api-0-organization-data-condition-index",
4849
),
50+
re_path(
51+
r"^(?P<organization_id_or_slug>[^\/]+)/detector_types/$",
52+
OrganizationDetectorTypeIndexEndpoint.as_view(),
53+
name="sentry-api-0-organization-detector-type-index",
54+
),
4955
]
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from dataclasses import dataclass
2+
from unittest.mock import patch
3+
4+
from sentry.incidents.grouptype import MetricAlertFire
5+
from sentry.issues.grouptype import (
6+
GroupCategory,
7+
GroupType,
8+
GroupTypeRegistry,
9+
MonitorIncidentType,
10+
PerformanceSlowDBQueryGroupType,
11+
UptimeDomainCheckFailure,
12+
)
13+
from sentry.testutils.cases import APITestCase
14+
from sentry.testutils.silo import region_silo_test
15+
from sentry.workflow_engine.handlers.detector import DetectorEvaluationResult, DetectorHandler
16+
from sentry.workflow_engine.models import DataPacket
17+
from sentry.workflow_engine.types import DetectorGroupKey, DetectorPriorityLevel
18+
19+
20+
@region_silo_test
21+
class OrganizationDetectorTypesAPITestCase(APITestCase):
22+
endpoint = "sentry-api-0-organization-detector-type-index"
23+
24+
def setUp(self):
25+
super().setUp()
26+
self.login_as(user=self.user)
27+
28+
self.registry_patcher = patch(
29+
"sentry.workflow_engine.endpoints.organization_detector_types.grouptype.registry",
30+
new=GroupTypeRegistry(),
31+
)
32+
self.registry_patcher.start()
33+
34+
class MockDetectorHandler(DetectorHandler[dict]):
35+
def evaluate(
36+
self, data_packet: DataPacket[dict]
37+
) -> dict[DetectorGroupKey, DetectorEvaluationResult]:
38+
return {None: DetectorEvaluationResult(None, True, DetectorPriorityLevel.HIGH)}
39+
40+
@dataclass(frozen=True)
41+
class TestMetricGroupType(GroupType):
42+
type_id = 1
43+
slug = MetricAlertFire.slug
44+
description = "Metric alert"
45+
category = GroupCategory.METRIC_ALERT.value
46+
detector_handler = MockDetectorHandler
47+
released = True
48+
49+
@dataclass(frozen=True)
50+
class TestCronsGroupType(GroupType):
51+
type_id = 2
52+
slug = MonitorIncidentType.slug
53+
description = "Crons"
54+
category = GroupCategory.CRON.value
55+
detector_handler = MockDetectorHandler
56+
released = True
57+
58+
@dataclass(frozen=True)
59+
class TestUptimeGroupType(GroupType):
60+
type_id = 3
61+
slug = UptimeDomainCheckFailure.slug
62+
description = "Uptime"
63+
category = GroupCategory.UPTIME.value
64+
detector_handler = MockDetectorHandler
65+
released = True
66+
67+
# Should not be included in the response
68+
@dataclass(frozen=True)
69+
class TestPerformanceGroupType(GroupType):
70+
type_id = 4
71+
slug = PerformanceSlowDBQueryGroupType.slug
72+
description = "Performance"
73+
category = GroupCategory.PERFORMANCE.value
74+
released = True
75+
76+
def tearDown(self):
77+
super().tearDown()
78+
self.registry_patcher.stop()
79+
80+
def test_simple(self):
81+
response = self.get_success_response(self.organization.slug, status_code=200)
82+
assert len(response.data) == 3
83+
assert response.data == [
84+
MetricAlertFire.slug,
85+
MonitorIncidentType.slug,
86+
UptimeDomainCheckFailure.slug,
87+
]

0 commit comments

Comments
 (0)