26
26
from apps .api .serializers .team import TeamSerializer
27
27
from apps .auth_token .auth import PluginAuthentication
28
28
from apps .base .models .user_notification_policy_log_record import UserNotificationPolicyLogRecord
29
+ from apps .grafana_plugin .ui_url_builder import UIURLBuilder
29
30
from apps .labels .utils import is_labels_feature_enabled
30
31
from apps .mobile_app .auth import MobileAppAuthTokenAuthentication
31
32
from apps .user_management .models import Team , User
@@ -283,6 +284,7 @@ class AlertGroupView(
283
284
"bulk_action" : [RBACPermission .Permissions .ALERT_GROUPS_WRITE ],
284
285
"preview_template" : [RBACPermission .Permissions .INTEGRATIONS_TEST ],
285
286
"escalation_snapshot" : [RBACPermission .Permissions .ALERT_GROUPS_READ ],
287
+ "filter_affected_services" : [RBACPermission .Permissions .ALERT_GROUPS_READ ],
286
288
}
287
289
288
290
queryset = AlertGroup .objects .none () # needed for drf-spectacular introspection
@@ -299,9 +301,18 @@ def get_serializer_class(self):
299
301
300
302
return super ().get_serializer_class ()
301
303
302
- def get_queryset (self , ignore_filtering_by_available_teams = False ):
303
- # no select_related or prefetch_related is used at this point, it will be done on paginate_queryset.
304
-
304
+ def _get_queryset (
305
+ self ,
306
+ action = None ,
307
+ ignore_filtering_by_available_teams = False ,
308
+ team_values = None ,
309
+ started_at = None ,
310
+ label_query = None ,
311
+ ):
312
+ # make base get_queryset reusable via params
313
+ if action is None :
314
+ # assume stats by default
315
+ action = "stats"
305
316
alert_receive_channels_qs = AlertReceiveChannel .objects_with_deleted .filter (
306
317
organization_id = self .request .auth .organization .id
307
318
)
@@ -310,7 +321,6 @@ def get_queryset(self, ignore_filtering_by_available_teams=False):
310
321
311
322
# Filter by team(s). Since we really filter teams from integrations, this is not an AlertGroup model filter.
312
323
# This is based on the common.api_helpers.ByTeamModelFieldFilterMixin implementation
313
- team_values = self .request .query_params .getlist ("team" , [])
314
324
if team_values :
315
325
null_team_lookup = Q (team__isnull = True ) if NO_TEAM_VALUE in team_values else None
316
326
teams_lookup = Q (team__public_primary_key__in = [ppk for ppk in team_values if ppk != NO_TEAM_VALUE ])
@@ -321,10 +331,10 @@ def get_queryset(self, ignore_filtering_by_available_teams=False):
321
331
alert_receive_channels_ids = list (alert_receive_channels_qs .values_list ("id" , flat = True ))
322
332
queryset = AlertGroup .objects .filter (channel__in = alert_receive_channels_ids )
323
333
324
- if self . action in ("list" , "stats" ) and not self . request . query_params . get ( " started_at" ) :
334
+ if action in ("list" , "stats" ) and not started_at :
325
335
queryset = queryset .filter (started_at__gte = timezone .now () - timezone .timedelta (days = 30 ))
326
336
327
- if self . action in ("list" , "stats" ) and settings .ALERT_GROUPS_DISABLE_PREFER_ORDERING_INDEX :
337
+ if action in ("list" , "stats" ) and settings .ALERT_GROUPS_DISABLE_PREFER_ORDERING_INDEX :
328
338
# workaround related to MySQL "ORDER BY LIMIT Query Optimizer Bug"
329
339
# read more: https://hackmysql.com/infamous-order-by-limit-query-optimizer-bug/
330
340
from django_mysql .models import add_QuerySetMixin
@@ -333,18 +343,28 @@ def get_queryset(self, ignore_filtering_by_available_teams=False):
333
343
queryset = queryset .force_index ("alert_group_list_index" )
334
344
335
345
# Filter by labels. Since alert group labels are "static" filter by names, not IDs.
336
- label_query = self . request . query_params . getlist ( "label" , [])
337
- kv_pairs = parse_label_query (label_query )
338
- for key , value in kv_pairs :
339
- # Utilize (organization, key_name, value_name, alert_group) index on AlertGroupAssociatedLabel
340
- queryset = queryset .filter (
341
- labels__organization = self .request .auth .organization ,
342
- labels__key_name = key ,
343
- labels__value_name = value ,
344
- )
346
+ if label_query :
347
+ kv_pairs = parse_label_query (label_query )
348
+ for key , value in kv_pairs :
349
+ # Utilize (organization, key_name, value_name, alert_group) index on AlertGroupAssociatedLabel
350
+ queryset = queryset .filter (
351
+ labels__organization = self .request .auth .organization ,
352
+ labels__key_name = key ,
353
+ labels__value_name = value ,
354
+ )
345
355
346
356
return queryset
347
357
358
+ def get_queryset (self , ignore_filtering_by_available_teams = False ):
359
+ # no select_related or prefetch_related is used at this point, it will be done on paginate_queryset.
360
+ return self ._get_queryset (
361
+ action = self .action ,
362
+ ignore_filtering_by_available_teams = ignore_filtering_by_available_teams ,
363
+ team_values = self .request .query_params .getlist ("team" , []),
364
+ started_at = self .request .query_params .get ("started_at" ),
365
+ label_query = self .request .query_params .getlist ("label" , []),
366
+ )
367
+
348
368
def get_object (self ):
349
369
obj = super ().get_object ()
350
370
obj = self .enrich ([obj ])[0 ]
@@ -881,3 +901,46 @@ def escalation_snapshot(self, request, pk=None):
881
901
escalation_snapshot = alert_group .escalation_snapshot
882
902
result = AlertGroupEscalationSnapshotAPISerializer (escalation_snapshot ).data if escalation_snapshot else {}
883
903
return Response (result )
904
+
905
+ @extend_schema (
906
+ responses = inline_serializer (
907
+ name = "AffectedServices" ,
908
+ fields = {
909
+ "name" : serializers .CharField (),
910
+ "service_url" : serializers .CharField (),
911
+ "alert_groups_url" : serializers .CharField (),
912
+ },
913
+ many = True ,
914
+ )
915
+ )
916
+ @action (methods = ["get" ], detail = False )
917
+ def filter_affected_services (self , request ):
918
+ """Given a list of service names, return the ones that have active alerts."""
919
+ organization = self .request .auth .organization
920
+ services = self .request .query_params .getlist ("service" , [])
921
+ url_builder = UIURLBuilder (organization )
922
+ affected_services = []
923
+ days_to_check = 7
924
+ for service_name in services :
925
+ is_affected = (
926
+ self ._get_queryset (
927
+ started_at = timezone .now () - timezone .timedelta (days = days_to_check ),
928
+ label_query = [f"service_name:{ service_name } " ],
929
+ )
930
+ .filter (
931
+ resolved = False ,
932
+ silenced = False ,
933
+ )
934
+ .exists ()
935
+ )
936
+ if is_affected :
937
+ affected_services .append (
938
+ {
939
+ "name" : service_name ,
940
+ "service_url" : url_builder .service_page (service_name ),
941
+ "alert_groups_url" : url_builder .alert_groups (
942
+ f"?status=0&status=1&started_at=now-{ days_to_check } d_now&label=service_name:{ service_name } "
943
+ ),
944
+ }
945
+ )
946
+ return Response (affected_services )
0 commit comments