Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/sentry/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@
OrganizationMonitorEnvironmentDetailsEndpoint,
)
from sentry.monitors.endpoints.organization_monitor_index import OrganizationMonitorIndexEndpoint
from sentry.monitors.endpoints.organization_monitor_index_count import (
OrganizationMonitorIndexCountEndpoint,
)
from sentry.monitors.endpoints.organization_monitor_index_stats import (
OrganizationMonitorIndexStatsEndpoint,
)
Expand Down Expand Up @@ -1816,6 +1819,11 @@ def create_group_urls(name_prefix: str) -> list[URLPattern | URLResolver]:
OrganizationMonitorIndexEndpoint.as_view(),
name="sentry-api-0-organization-monitor-index",
),
re_path(
r"^(?P<organization_id_or_slug>[^\/]+)/monitors-count/$",
OrganizationMonitorIndexCountEndpoint.as_view(),
name="sentry-api-0-organization-monitor-index-count",
),
re_path(
r"^(?P<organization_id_or_slug>[^\/]+)/monitors-stats/$",
OrganizationMonitorIndexStatsEndpoint.as_view(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from django.db.models import Q
from drf_spectacular.utils import extend_schema
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 region_silo_endpoint
from sentry.api.bases import NoProjects
from sentry.api.bases.organization import OrganizationAlertRulePermission, OrganizationEndpoint
from sentry.constants import ObjectStatus
from sentry.models.organization import Organization
from sentry.monitors.models import Monitor
from sentry.utils.auth import AuthenticatedHttpRequest


@region_silo_endpoint
@extend_schema(tags=["Crons"])
class OrganizationMonitorIndexCountEndpoint(OrganizationEndpoint):
publish_status = {
"GET": ApiPublishStatus.PRIVATE,
}
owner = ApiOwner.CRONS
permission_classes = (OrganizationAlertRulePermission,)

def get(self, request: AuthenticatedHttpRequest, organization: Organization) -> Response:
"""
Retrieves the count of cron monitors for an organization.
"""
try:
filter_params = self.get_filter_params(request, organization, date_filter_optional=True)
except NoProjects:
return self.respond([])

queryset = Monitor.objects.filter(
organization_id=organization.id, project_id__in=filter_params["project_id"]
).exclude(
status__in=[
ObjectStatus.PENDING_DELETION,
ObjectStatus.DELETION_IN_PROGRESS,
]
)

environments = filter_params.get("environment_objects")
if environments is not None:
environment_ids = [e.id for e in environments]
# use a distinct() filter as queries spanning multiple tables can include duplicates
queryset = queryset.filter(
Q(monitorenvironment__environment_id__in=environment_ids)
| Q(monitorenvironment=None)
).distinct()

all_monitors_count = queryset.count()
disabled_monitors_count = queryset.filter(status=ObjectStatus.DISABLED).count()
active_monitors_count = all_monitors_count - disabled_monitors_count

return self.respond(
{
"counts": {
"total": all_monitors_count,
"active": active_monitors_count,
"disabled": disabled_monitors_count,
},
}
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from __future__ import annotations

from sentry.constants import ObjectStatus
from sentry.testutils.cases import MonitorTestCase


class OrganizationMonitorsCountTest(MonitorTestCase):
endpoint = "sentry-api-0-organization-monitor-index-count"

def setUp(self):
super().setUp()
self.login_as(self.user)

def test_simple(self):
self._create_monitor(name="Active Monitor 1")
self._create_monitor(name="Active Monitor 2")
self._create_monitor(name="Disabled Monitor", status=ObjectStatus.DISABLED)

# Monitors pending deletion should be excluded
self._create_monitor(name="Pending Deletion", status=ObjectStatus.PENDING_DELETION)

response = self.get_success_response(self.organization.slug)

assert response.data == {
"counts": {
"total": 3,
"active": 2,
"disabled": 1,
},
}

def test_filtered_by_environment(self):
# Create monitors with different environments
monitor1 = self._create_monitor(name="Monitor 1")
monitor2 = self._create_monitor(name="Monitor 2")
monitor3 = self._create_monitor(name="Monitor 3", status=ObjectStatus.DISABLED)

self._create_monitor_environment(monitor1, name="production")
self._create_monitor_environment(monitor2, name="staging")
self._create_monitor_environment(monitor3, name="production")

response = self.get_success_response(self.organization.slug, environment=["production"])

assert response.data == {
"counts": {
"total": 2,
"active": 1,
"disabled": 1,
},
}

response = self.get_success_response(self.organization.slug, environment=["staging"])

assert response.data == {
"counts": {
"total": 1,
"active": 1,
"disabled": 0,
},
}
Loading