8
8
from celery import uuid as celery_uuid
9
9
from django .apps import apps
10
10
from django .conf import settings
11
+ from django .core .cache import cache
11
12
from django .core .validators import MinLengthValidator
12
- from django .db import IntegrityError , models
13
+ from django .db import IntegrityError , models , transaction
13
14
from django .db .models import JSONField , Q , QuerySet
15
+ from django .db .models .signals import post_save
16
+ from django .dispatch import receiver
14
17
from django .utils import timezone
15
18
from django .utils .functional import cached_property
16
19
19
22
from apps .alerts .incident_appearance .renderers .slack_renderer import AlertGroupSlackRenderer
20
23
from apps .alerts .incident_log_builder import IncidentLogBuilder
21
24
from apps .alerts .signals import alert_group_action_triggered_signal
22
- from apps .alerts .tasks import acknowledge_reminder_task , call_ack_url , send_alert_group_signal , unsilence_task
25
+ from apps .alerts .tasks import (
26
+ acknowledge_reminder_task ,
27
+ call_ack_url ,
28
+ schedule_cache_for_alert_group ,
29
+ send_alert_group_signal ,
30
+ unsilence_task ,
31
+ )
23
32
from apps .slack .slack_formatter import SlackFormatter
24
33
from apps .user_management .models import User
34
+ from common .mixins .use_random_readonly_db_manager_mixin import UseRandomReadonlyDbManagerMixin
25
35
from common .public_primary_keys import generate_public_primary_key , increase_public_primary_key_length
26
36
from common .utils import clean_markup , str_or_backup
27
37
@@ -98,6 +108,10 @@ def filter(self, *args, **kwargs):
98
108
return super ().filter (* args , ** kwargs , is_archived = False )
99
109
100
110
111
+ class AlertGroupManager (UseRandomReadonlyDbManagerMixin , models .Manager ):
112
+ pass
113
+
114
+
101
115
class AlertGroupSlackRenderingMixin :
102
116
"""
103
117
Ideally this mixin should not exist. Instead of this instance of AlertGroupSlackRenderer should be created and used
@@ -120,8 +134,8 @@ def slack_templated_first_alert(self):
120
134
121
135
122
136
class AlertGroup (AlertGroupSlackRenderingMixin , EscalationSnapshotMixin , models .Model ):
123
- all_objects = AlertGroupQuerySet . as_manager ()
124
- unarchived_objects = UnarchivedAlertGroupQuerySet . as_manager ()
137
+ all_objects = AlertGroupManager . from_queryset ( AlertGroupQuerySet ) ()
138
+ unarchived_objects = AlertGroupManager . from_queryset ( UnarchivedAlertGroupQuerySet ) ()
125
139
126
140
(
127
141
NEW ,
@@ -228,6 +242,8 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
228
242
229
243
active_escalation_id = models .CharField (max_length = 100 , null = True , default = None ) # ID generated by celery
230
244
active_resolve_calculation_id = models .CharField (max_length = 100 , null = True , default = None ) # ID generated by celery
245
+ # ID generated by celery
246
+ active_cache_for_web_calculation_id = models .CharField (max_length = 100 , null = True , default = None )
231
247
232
248
SILENCE_DELAY_OPTIONS = (
233
249
(1800 , "30 minutes" ),
@@ -299,9 +315,7 @@ def status(self):
299
315
related_name = "dependent_alert_groups" ,
300
316
)
301
317
302
- # cached_render_for_web and active_cache_for_web_calculation_id are deprecated
303
- cached_render_for_web = models .JSONField (default = dict )
304
- active_cache_for_web_calculation_id = models .CharField (max_length = 100 , null = True , default = None )
318
+ cached_render_for_web = JSONField (default = dict )
305
319
306
320
last_unique_unacknowledge_process_id = models .CharField (max_length = 100 , null = True , default = None )
307
321
is_archived = models .BooleanField (default = False )
@@ -390,6 +404,18 @@ def skip_escalation_in_slack(self):
390
404
def is_alert_a_resolve_signal (self , alert ):
391
405
raise NotImplementedError
392
406
407
+ def cache_for_web (self , organization ):
408
+ from apps .api .serializers .alert_group import AlertGroupSerializer
409
+
410
+ # Re-take object to switch connection from readonly db to master.
411
+ _self = AlertGroup .all_objects .get (pk = self .pk )
412
+ _self .cached_render_for_web = AlertGroupSerializer (self , context = {"organization" : organization }).data
413
+ self .cached_render_for_web = _self .cached_render_for_web
414
+ _self .save (update_fields = ["cached_render_for_web" ])
415
+
416
+ def schedule_cache_for_web (self ):
417
+ schedule_cache_for_alert_group .apply_async ((self .pk ,))
418
+
393
419
@property
394
420
def permalink (self ):
395
421
if self .slack_message is not None :
@@ -399,6 +425,10 @@ def permalink(self):
399
425
def web_link (self ):
400
426
return urljoin (self .channel .organization .web_link , f"?page=incident&id={ self .public_primary_key } " )
401
427
428
+ @property
429
+ def alerts_count (self ):
430
+ return self .alerts .count ()
431
+
402
432
@property
403
433
def happened_while_maintenance (self ):
404
434
return self .root_alert_group is not None and self .root_alert_group .maintenance_uuid is not None
@@ -419,6 +449,10 @@ def acknowledge_by_user(self, user: User, action_source: Optional[str] = None) -
419
449
self .unresolve ()
420
450
self .log_records .create (type = AlertGroupLogRecord .TYPE_UN_RESOLVED , author = user , reason = "Acknowledge button" )
421
451
452
+ # clear resolve report cache
453
+ cache_key = "render_after_resolve_report_json_{}" .format (self .pk )
454
+ cache .delete (cache_key )
455
+
422
456
self .acknowledge (acknowledged_by_user = user , acknowledged_by = AlertGroup .USER )
423
457
self .stop_escalation ()
424
458
if self .is_root_alert_group :
@@ -639,6 +673,9 @@ def un_resolve_by_user(self, user: User, action_source: Optional[str] = None) ->
639
673
self .unresolve ()
640
674
log_record = self .log_records .create (type = AlertGroupLogRecord .TYPE_UN_RESOLVED , author = user )
641
675
676
+ # clear resolve report cache
677
+ self .drop_cached_after_resolve_report_json ()
678
+
642
679
if self .is_root_alert_group :
643
680
self .start_escalation_if_needed ()
644
681
@@ -811,6 +848,10 @@ def silence_by_user(self, user: User, silence_delay: Optional[int], action_sourc
811
848
self .unresolve ()
812
849
self .log_records .create (type = AlertGroupLogRecord .TYPE_UN_RESOLVED , author = user , reason = "Silence button" )
813
850
851
+ # clear resolve report cache
852
+ cache_key = "render_after_resolve_report_json_{}" .format (self .pk )
853
+ cache .delete (cache_key )
854
+
814
855
if self .acknowledged :
815
856
self .unacknowledge ()
816
857
self .log_records .create (type = AlertGroupLogRecord .TYPE_UN_ACK , author = user , reason = "Silence button" )
@@ -1019,6 +1060,8 @@ def bulk_acknowledge(user: User, alert_groups: "QuerySet[AlertGroup]") -> None:
1019
1060
author = user ,
1020
1061
reason = "Bulk action acknowledge" ,
1021
1062
)
1063
+ # clear resolve report cache
1064
+ alert_group .drop_cached_after_resolve_report_json ()
1022
1065
1023
1066
for alert_group in alert_groups_to_unsilence_before_acknowledge_list :
1024
1067
alert_group .log_records .create (
@@ -1151,6 +1194,8 @@ def bulk_restart(user: User, alert_groups: "QuerySet[AlertGroup]") -> None:
1151
1194
reason = "Bulk action restart" ,
1152
1195
)
1153
1196
1197
+ alert_group .drop_cached_after_resolve_report_json ()
1198
+
1154
1199
if alert_group .is_root_alert_group :
1155
1200
alert_group .start_escalation_if_needed ()
1156
1201
@@ -1248,6 +1293,7 @@ def bulk_silence(user: User, alert_groups: "QuerySet[AlertGroup]", silence_delay
1248
1293
author = user ,
1249
1294
reason = "Bulk action silence" ,
1250
1295
)
1296
+ alert_group .drop_cached_after_resolve_report_json ()
1251
1297
1252
1298
for alert_group in alert_groups_to_unsilence_before_silence_list :
1253
1299
alert_group .log_records .create (
@@ -1437,7 +1483,7 @@ def get_acknowledge_text(self, mention_user=False):
1437
1483
else :
1438
1484
return "Acknowledged"
1439
1485
1440
- def render_after_resolve_report_json (self ):
1486
+ def non_cached_after_resolve_report_json (self ):
1441
1487
AlertGroupLogRecord = apps .get_model ("alerts" , "AlertGroupLogRecord" )
1442
1488
UserNotificationPolicyLogRecord = apps .get_model ("base" , "UserNotificationPolicyLogRecord" )
1443
1489
ResolutionNote = apps .get_model ("alerts" , "ResolutionNote" )
@@ -1455,6 +1501,21 @@ def render_after_resolve_report_json(self):
1455
1501
result_log_report .append (log_record .render_log_line_json ())
1456
1502
return result_log_report
1457
1503
1504
+ def render_after_resolve_report_json (self ):
1505
+ cache_key = "render_after_resolve_report_json_{}" .format (self .pk )
1506
+
1507
+ # cache.get_or_set in some cases returns None, so use get and set cache methods separately
1508
+ log_report = cache .get (cache_key )
1509
+ if log_report is None :
1510
+ log_report = self .non_cached_after_resolve_report_json ()
1511
+ cache .set (cache_key , log_report )
1512
+ return log_report
1513
+
1514
+ def drop_cached_after_resolve_report_json (self ):
1515
+ cache_key = "render_after_resolve_report_json_{}" .format (self .pk )
1516
+ if cache_key in cache :
1517
+ cache .delete (cache_key )
1518
+
1458
1519
@property
1459
1520
def has_resolution_notes (self ):
1460
1521
return self .resolution_notes .exists ()
@@ -1534,3 +1595,14 @@ def last_stop_escalation_log(self):
1534
1595
)
1535
1596
1536
1597
return stop_escalation_log
1598
+
1599
+
1600
+ @receiver (post_save , sender = AlertGroup )
1601
+ def listen_for_alert_group_model_save (sender , instance , created , * args , ** kwargs ):
1602
+ if (
1603
+ kwargs is not None
1604
+ and "update_fields" in kwargs
1605
+ and kwargs ["update_fields" ] is dict
1606
+ and "cached_render_for_web" not in kwargs ["update_fields" ]
1607
+ ):
1608
+ transaction .on_commit (instance .schedule_cache_for_alert_group )
0 commit comments