Skip to content

Commit 7627853

Browse files
authored
Merge pull request #273 from grafana/dev
Merge dev to main
2 parents 684c774 + 619e33c commit 7627853

35 files changed

+664
-436
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# Change Log
22

3-
## v1.0.8 (2022-07-21)
3+
## v1.0.9 (2022-07-21)
44
- Frontend bug fixes & improvements
55
- Support regex_replace() in templates
6+
- Bring back alert group caching and list view
67

78
## v1.0.7 (2022-07-18)
89
- Backend & frontend bug fixes

engine/apps/alerts/incident_appearance/renderers/base_renderer.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,9 @@ def templater_class(self):
1818

1919

2020
class AlertGroupBaseRenderer(ABC):
21-
def __init__(self, alert_group, alert=None):
22-
if alert is None:
23-
alert = alert_group.alerts.first()
24-
21+
def __init__(self, alert_group):
2522
self.alert_group = alert_group
26-
self.alert_renderer = self.alert_renderer_class(alert)
23+
self.alert_renderer = self.alert_renderer_class(self.alert_group.alerts.first())
2724

2825
@property
2926
@abstractmethod

engine/apps/alerts/incident_appearance/renderers/classic_markdown_renderer.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ def render(self):
2020

2121

2222
class AlertGroupClassicMarkdownRenderer(AlertGroupBaseRenderer):
23-
def __init__(self, alert_group, alert=None):
24-
if alert is None:
25-
alert = alert_group.alerts.last()
23+
def __init__(self, alert_group):
24+
super().__init__(alert_group)
2625

27-
super().__init__(alert_group, alert)
26+
# use the last alert to render content
27+
self.alert_renderer = self.alert_renderer_class(self.alert_group.alerts.last())
2828

2929
@property
3030
def alert_renderer_class(self):

engine/apps/alerts/incident_appearance/renderers/web_renderer.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ def render(self):
2020

2121

2222
class AlertGroupWebRenderer(AlertGroupBaseRenderer):
23-
def __init__(self, alert_group, alert=None):
24-
if alert is None:
25-
alert = alert_group.alerts.last()
23+
def __init__(self, alert_group):
24+
super().__init__(alert_group)
2625

27-
super().__init__(alert_group, alert)
26+
# use the last alert to render content
27+
self.alert_renderer = self.alert_renderer_class(self.alert_group.alerts.last())
2828

2929
@property
3030
def alert_renderer_class(self):

engine/apps/alerts/models/alert.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from django.apps import apps
66
from django.conf import settings
77
from django.core.validators import MinLengthValidator
8-
from django.db import models
8+
from django.db import models, transaction
99
from django.db.models import JSONField
1010
from django.db.models.signals import post_save
1111

@@ -261,6 +261,9 @@ def listen_for_alert_model_save(sender, instance, created, *args, **kwargs):
261261
else:
262262
distribute_alert.apply_async((instance.pk,), countdown=TASK_DELAY_SECONDS)
263263

264+
logger.info(f"Recalculate AG cache. Reason: save alert model {instance.pk}")
265+
transaction.on_commit(instance.group.schedule_cache_for_web)
266+
264267

265268
# Connect signal to base Alert class
266269
post_save.connect(listen_for_alert_model_save, Alert)

engine/apps/alerts/models/alert_group.py

+80-8
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
from celery import uuid as celery_uuid
99
from django.apps import apps
1010
from django.conf import settings
11+
from django.core.cache import cache
1112
from django.core.validators import MinLengthValidator
12-
from django.db import IntegrityError, models
13+
from django.db import IntegrityError, models, transaction
1314
from django.db.models import JSONField, Q, QuerySet
15+
from django.db.models.signals import post_save
16+
from django.dispatch import receiver
1417
from django.utils import timezone
1518
from django.utils.functional import cached_property
1619

@@ -19,9 +22,16 @@
1922
from apps.alerts.incident_appearance.renderers.slack_renderer import AlertGroupSlackRenderer
2023
from apps.alerts.incident_log_builder import IncidentLogBuilder
2124
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+
)
2332
from apps.slack.slack_formatter import SlackFormatter
2433
from apps.user_management.models import User
34+
from common.mixins.use_random_readonly_db_manager_mixin import UseRandomReadonlyDbManagerMixin
2535
from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length
2636
from common.utils import clean_markup, str_or_backup
2737

@@ -98,6 +108,10 @@ def filter(self, *args, **kwargs):
98108
return super().filter(*args, **kwargs, is_archived=False)
99109

100110

111+
class AlertGroupManager(UseRandomReadonlyDbManagerMixin, models.Manager):
112+
pass
113+
114+
101115
class AlertGroupSlackRenderingMixin:
102116
"""
103117
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):
120134

121135

122136
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)()
125139

126140
(
127141
NEW,
@@ -228,6 +242,8 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
228242

229243
active_escalation_id = models.CharField(max_length=100, null=True, default=None) # ID generated by celery
230244
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)
231247

232248
SILENCE_DELAY_OPTIONS = (
233249
(1800, "30 minutes"),
@@ -299,9 +315,7 @@ def status(self):
299315
related_name="dependent_alert_groups",
300316
)
301317

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)
305319

306320
last_unique_unacknowledge_process_id = models.CharField(max_length=100, null=True, default=None)
307321
is_archived = models.BooleanField(default=False)
@@ -390,6 +404,18 @@ def skip_escalation_in_slack(self):
390404
def is_alert_a_resolve_signal(self, alert):
391405
raise NotImplementedError
392406

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+
393419
@property
394420
def permalink(self):
395421
if self.slack_message is not None:
@@ -399,6 +425,10 @@ def permalink(self):
399425
def web_link(self):
400426
return urljoin(self.channel.organization.web_link, f"?page=incident&id={self.public_primary_key}")
401427

428+
@property
429+
def alerts_count(self):
430+
return self.alerts.count()
431+
402432
@property
403433
def happened_while_maintenance(self):
404434
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) -
419449
self.unresolve()
420450
self.log_records.create(type=AlertGroupLogRecord.TYPE_UN_RESOLVED, author=user, reason="Acknowledge button")
421451

452+
# clear resolve report cache
453+
cache_key = "render_after_resolve_report_json_{}".format(self.pk)
454+
cache.delete(cache_key)
455+
422456
self.acknowledge(acknowledged_by_user=user, acknowledged_by=AlertGroup.USER)
423457
self.stop_escalation()
424458
if self.is_root_alert_group:
@@ -639,6 +673,9 @@ def un_resolve_by_user(self, user: User, action_source: Optional[str] = None) ->
639673
self.unresolve()
640674
log_record = self.log_records.create(type=AlertGroupLogRecord.TYPE_UN_RESOLVED, author=user)
641675

676+
# clear resolve report cache
677+
self.drop_cached_after_resolve_report_json()
678+
642679
if self.is_root_alert_group:
643680
self.start_escalation_if_needed()
644681

@@ -811,6 +848,10 @@ def silence_by_user(self, user: User, silence_delay: Optional[int], action_sourc
811848
self.unresolve()
812849
self.log_records.create(type=AlertGroupLogRecord.TYPE_UN_RESOLVED, author=user, reason="Silence button")
813850

851+
# clear resolve report cache
852+
cache_key = "render_after_resolve_report_json_{}".format(self.pk)
853+
cache.delete(cache_key)
854+
814855
if self.acknowledged:
815856
self.unacknowledge()
816857
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:
10191060
author=user,
10201061
reason="Bulk action acknowledge",
10211062
)
1063+
# clear resolve report cache
1064+
alert_group.drop_cached_after_resolve_report_json()
10221065

10231066
for alert_group in alert_groups_to_unsilence_before_acknowledge_list:
10241067
alert_group.log_records.create(
@@ -1151,6 +1194,8 @@ def bulk_restart(user: User, alert_groups: "QuerySet[AlertGroup]") -> None:
11511194
reason="Bulk action restart",
11521195
)
11531196

1197+
alert_group.drop_cached_after_resolve_report_json()
1198+
11541199
if alert_group.is_root_alert_group:
11551200
alert_group.start_escalation_if_needed()
11561201

@@ -1248,6 +1293,7 @@ def bulk_silence(user: User, alert_groups: "QuerySet[AlertGroup]", silence_delay
12481293
author=user,
12491294
reason="Bulk action silence",
12501295
)
1296+
alert_group.drop_cached_after_resolve_report_json()
12511297

12521298
for alert_group in alert_groups_to_unsilence_before_silence_list:
12531299
alert_group.log_records.create(
@@ -1437,7 +1483,7 @@ def get_acknowledge_text(self, mention_user=False):
14371483
else:
14381484
return "Acknowledged"
14391485

1440-
def render_after_resolve_report_json(self):
1486+
def non_cached_after_resolve_report_json(self):
14411487
AlertGroupLogRecord = apps.get_model("alerts", "AlertGroupLogRecord")
14421488
UserNotificationPolicyLogRecord = apps.get_model("base", "UserNotificationPolicyLogRecord")
14431489
ResolutionNote = apps.get_model("alerts", "ResolutionNote")
@@ -1455,6 +1501,21 @@ def render_after_resolve_report_json(self):
14551501
result_log_report.append(log_record.render_log_line_json())
14561502
return result_log_report
14571503

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+
14581519
@property
14591520
def has_resolution_notes(self):
14601521
return self.resolution_notes.exists()
@@ -1534,3 +1595,14 @@ def last_stop_escalation_log(self):
15341595
)
15351596

15361597
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)

engine/apps/alerts/models/alert_group_log_record.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import humanize
55
from django.apps import apps
6-
from django.db import models
6+
from django.db import models, transaction
77
from django.db.models import JSONField
88
from django.db.models.signals import post_save
99
from django.dispatch import receiver
@@ -546,6 +546,7 @@ def get_step_specific_info(self):
546546

547547
@receiver(post_save, sender=AlertGroupLogRecord)
548548
def listen_for_alertgrouplogrecord(sender, instance, created, *args, **kwargs):
549+
instance.alert_group.drop_cached_after_resolve_report_json()
549550
if instance.type != AlertGroupLogRecord.TYPE_DELETED:
550551
if not instance.alert_group.is_maintenance_incident:
551552
alert_group_pk = instance.alert_group.pk
@@ -554,3 +555,6 @@ def listen_for_alertgrouplogrecord(sender, instance, created, *args, **kwargs):
554555
f"alert group event: {instance.get_type_display()}"
555556
)
556557
send_update_log_report_signal.apply_async(kwargs={"alert_group_pk": alert_group_pk}, countdown=8)
558+
559+
logger.info(f"Recalculate AG cache. Reason: save alert_group_log_record model {instance.pk}")
560+
transaction.on_commit(instance.alert_group.schedule_cache_for_web)

engine/apps/alerts/models/alert_receive_channel.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@
1919
from apps.alerts.grafana_alerting_sync_manager.grafana_alerting_sync import GrafanaAlertingSyncManager
2020
from apps.alerts.integration_options_mixin import IntegrationOptionsMixin
2121
from apps.alerts.models.maintainable_object import MaintainableObject
22-
from apps.alerts.tasks import disable_maintenance, sync_grafana_alerting_contact_points
22+
from apps.alerts.tasks import (
23+
disable_maintenance,
24+
invalidate_web_cache_for_alert_group,
25+
sync_grafana_alerting_contact_points,
26+
)
2327
from apps.base.messaging import get_messaging_backend_from_id
2428
from apps.base.utils import live_settings
2529
from apps.integrations.metadata import heartbeat
@@ -689,6 +693,16 @@ def listen_for_alertreceivechannel_model_save(sender, instance, created, *args,
689693
create_organization_log(
690694
instance.organization, None, OrganizationLogType.TYPE_HEARTBEAT_CREATED, description
691695
)
696+
else:
697+
logger.info(f"Drop AG cache. Reason: save alert_receive_channel {instance.pk}")
698+
if kwargs is not None:
699+
if "update_fields" in kwargs:
700+
if kwargs["update_fields"] is not None:
701+
# Hack to not to invalidate web cache on AlertReceiveChannel.start_send_rate_limit_message_task
702+
if "rate_limit_message_task_id" in kwargs["update_fields"]:
703+
return
704+
705+
invalidate_web_cache_for_alert_group.apply_async(kwargs={"channel_pk": instance.pk})
692706

693707
if instance.integration == AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING:
694708
if created:

engine/apps/alerts/tasks/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from .delete_alert_group import delete_alert_group # noqa: F401
1010
from .distribute_alert import distribute_alert # noqa: F401
1111
from .escalate_alert_group import escalate_alert_group # noqa: F401
12-
from .invalidate_web_cache_for_alert_group import invalidate_web_cache_for_alert_group # noqa: F401, todo: remove
12+
from .invalidate_web_cache_for_alert_group import invalidate_web_cache_for_alert_group # noqa: F401
1313
from .invite_user_to_join_incident import invite_user_to_join_incident # noqa: F401
1414
from .maintenance import disable_maintenance # noqa: F401
1515
from .notify_all import notify_all_task # noqa: F401

0 commit comments

Comments
 (0)