Skip to content

Commit

Permalink
Merge pull request #273 from grafana/dev
Browse files Browse the repository at this point in the history
Merge dev to main
  • Loading branch information
vadimkerr authored Jul 21, 2022
2 parents 684c774 + 619e33c commit 7627853
Show file tree
Hide file tree
Showing 35 changed files with 664 additions and 436 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Change Log

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

## v1.0.7 (2022-07-18)
- Backend & frontend bug fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,9 @@ def templater_class(self):


class AlertGroupBaseRenderer(ABC):
def __init__(self, alert_group, alert=None):
if alert is None:
alert = alert_group.alerts.first()

def __init__(self, alert_group):
self.alert_group = alert_group
self.alert_renderer = self.alert_renderer_class(alert)
self.alert_renderer = self.alert_renderer_class(self.alert_group.alerts.first())

@property
@abstractmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ def render(self):


class AlertGroupClassicMarkdownRenderer(AlertGroupBaseRenderer):
def __init__(self, alert_group, alert=None):
if alert is None:
alert = alert_group.alerts.last()
def __init__(self, alert_group):
super().__init__(alert_group)

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

@property
def alert_renderer_class(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ def render(self):


class AlertGroupWebRenderer(AlertGroupBaseRenderer):
def __init__(self, alert_group, alert=None):
if alert is None:
alert = alert_group.alerts.last()
def __init__(self, alert_group):
super().__init__(alert_group)

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

@property
def alert_renderer_class(self):
Expand Down
5 changes: 4 additions & 1 deletion engine/apps/alerts/models/alert.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.apps import apps
from django.conf import settings
from django.core.validators import MinLengthValidator
from django.db import models
from django.db import models, transaction
from django.db.models import JSONField
from django.db.models.signals import post_save

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

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


# Connect signal to base Alert class
post_save.connect(listen_for_alert_model_save, Alert)
Expand Down
88 changes: 80 additions & 8 deletions engine/apps/alerts/models/alert_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
from celery import uuid as celery_uuid
from django.apps import apps
from django.conf import settings
from django.core.cache import cache
from django.core.validators import MinLengthValidator
from django.db import IntegrityError, models
from django.db import IntegrityError, models, transaction
from django.db.models import JSONField, Q, QuerySet
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils import timezone
from django.utils.functional import cached_property

Expand All @@ -19,9 +22,16 @@
from apps.alerts.incident_appearance.renderers.slack_renderer import AlertGroupSlackRenderer
from apps.alerts.incident_log_builder import IncidentLogBuilder
from apps.alerts.signals import alert_group_action_triggered_signal
from apps.alerts.tasks import acknowledge_reminder_task, call_ack_url, send_alert_group_signal, unsilence_task
from apps.alerts.tasks import (
acknowledge_reminder_task,
call_ack_url,
schedule_cache_for_alert_group,
send_alert_group_signal,
unsilence_task,
)
from apps.slack.slack_formatter import SlackFormatter
from apps.user_management.models import User
from common.mixins.use_random_readonly_db_manager_mixin import UseRandomReadonlyDbManagerMixin
from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length
from common.utils import clean_markup, str_or_backup

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


class AlertGroupManager(UseRandomReadonlyDbManagerMixin, models.Manager):
pass


class AlertGroupSlackRenderingMixin:
"""
Ideally this mixin should not exist. Instead of this instance of AlertGroupSlackRenderer should be created and used
Expand All @@ -120,8 +134,8 @@ def slack_templated_first_alert(self):


class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.Model):
all_objects = AlertGroupQuerySet.as_manager()
unarchived_objects = UnarchivedAlertGroupQuerySet.as_manager()
all_objects = AlertGroupManager.from_queryset(AlertGroupQuerySet)()
unarchived_objects = AlertGroupManager.from_queryset(UnarchivedAlertGroupQuerySet)()

(
NEW,
Expand Down Expand Up @@ -228,6 +242,8 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.

active_escalation_id = models.CharField(max_length=100, null=True, default=None) # ID generated by celery
active_resolve_calculation_id = models.CharField(max_length=100, null=True, default=None) # ID generated by celery
# ID generated by celery
active_cache_for_web_calculation_id = models.CharField(max_length=100, null=True, default=None)

SILENCE_DELAY_OPTIONS = (
(1800, "30 minutes"),
Expand Down Expand Up @@ -299,9 +315,7 @@ def status(self):
related_name="dependent_alert_groups",
)

# cached_render_for_web and active_cache_for_web_calculation_id are deprecated
cached_render_for_web = models.JSONField(default=dict)
active_cache_for_web_calculation_id = models.CharField(max_length=100, null=True, default=None)
cached_render_for_web = JSONField(default=dict)

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

def cache_for_web(self, organization):
from apps.api.serializers.alert_group import AlertGroupSerializer

# Re-take object to switch connection from readonly db to master.
_self = AlertGroup.all_objects.get(pk=self.pk)
_self.cached_render_for_web = AlertGroupSerializer(self, context={"organization": organization}).data
self.cached_render_for_web = _self.cached_render_for_web
_self.save(update_fields=["cached_render_for_web"])

def schedule_cache_for_web(self):
schedule_cache_for_alert_group.apply_async((self.pk,))

@property
def permalink(self):
if self.slack_message is not None:
Expand All @@ -399,6 +425,10 @@ def permalink(self):
def web_link(self):
return urljoin(self.channel.organization.web_link, f"?page=incident&id={self.public_primary_key}")

@property
def alerts_count(self):
return self.alerts.count()

@property
def happened_while_maintenance(self):
return self.root_alert_group is not None and self.root_alert_group.maintenance_uuid is not None
Expand All @@ -419,6 +449,10 @@ def acknowledge_by_user(self, user: User, action_source: Optional[str] = None) -
self.unresolve()
self.log_records.create(type=AlertGroupLogRecord.TYPE_UN_RESOLVED, author=user, reason="Acknowledge button")

# clear resolve report cache
cache_key = "render_after_resolve_report_json_{}".format(self.pk)
cache.delete(cache_key)

self.acknowledge(acknowledged_by_user=user, acknowledged_by=AlertGroup.USER)
self.stop_escalation()
if self.is_root_alert_group:
Expand Down Expand Up @@ -639,6 +673,9 @@ def un_resolve_by_user(self, user: User, action_source: Optional[str] = None) ->
self.unresolve()
log_record = self.log_records.create(type=AlertGroupLogRecord.TYPE_UN_RESOLVED, author=user)

# clear resolve report cache
self.drop_cached_after_resolve_report_json()

if self.is_root_alert_group:
self.start_escalation_if_needed()

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

# clear resolve report cache
cache_key = "render_after_resolve_report_json_{}".format(self.pk)
cache.delete(cache_key)

if self.acknowledged:
self.unacknowledge()
self.log_records.create(type=AlertGroupLogRecord.TYPE_UN_ACK, author=user, reason="Silence button")
Expand Down Expand Up @@ -1019,6 +1060,8 @@ def bulk_acknowledge(user: User, alert_groups: "QuerySet[AlertGroup]") -> None:
author=user,
reason="Bulk action acknowledge",
)
# clear resolve report cache
alert_group.drop_cached_after_resolve_report_json()

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

alert_group.drop_cached_after_resolve_report_json()

if alert_group.is_root_alert_group:
alert_group.start_escalation_if_needed()

Expand Down Expand Up @@ -1248,6 +1293,7 @@ def bulk_silence(user: User, alert_groups: "QuerySet[AlertGroup]", silence_delay
author=user,
reason="Bulk action silence",
)
alert_group.drop_cached_after_resolve_report_json()

for alert_group in alert_groups_to_unsilence_before_silence_list:
alert_group.log_records.create(
Expand Down Expand Up @@ -1437,7 +1483,7 @@ def get_acknowledge_text(self, mention_user=False):
else:
return "Acknowledged"

def render_after_resolve_report_json(self):
def non_cached_after_resolve_report_json(self):
AlertGroupLogRecord = apps.get_model("alerts", "AlertGroupLogRecord")
UserNotificationPolicyLogRecord = apps.get_model("base", "UserNotificationPolicyLogRecord")
ResolutionNote = apps.get_model("alerts", "ResolutionNote")
Expand All @@ -1455,6 +1501,21 @@ def render_after_resolve_report_json(self):
result_log_report.append(log_record.render_log_line_json())
return result_log_report

def render_after_resolve_report_json(self):
cache_key = "render_after_resolve_report_json_{}".format(self.pk)

# cache.get_or_set in some cases returns None, so use get and set cache methods separately
log_report = cache.get(cache_key)
if log_report is None:
log_report = self.non_cached_after_resolve_report_json()
cache.set(cache_key, log_report)
return log_report

def drop_cached_after_resolve_report_json(self):
cache_key = "render_after_resolve_report_json_{}".format(self.pk)
if cache_key in cache:
cache.delete(cache_key)

@property
def has_resolution_notes(self):
return self.resolution_notes.exists()
Expand Down Expand Up @@ -1534,3 +1595,14 @@ def last_stop_escalation_log(self):
)

return stop_escalation_log


@receiver(post_save, sender=AlertGroup)
def listen_for_alert_group_model_save(sender, instance, created, *args, **kwargs):
if (
kwargs is not None
and "update_fields" in kwargs
and kwargs["update_fields"] is dict
and "cached_render_for_web" not in kwargs["update_fields"]
):
transaction.on_commit(instance.schedule_cache_for_alert_group)
6 changes: 5 additions & 1 deletion engine/apps/alerts/models/alert_group_log_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import humanize
from django.apps import apps
from django.db import models
from django.db import models, transaction
from django.db.models import JSONField
from django.db.models.signals import post_save
from django.dispatch import receiver
Expand Down Expand Up @@ -546,6 +546,7 @@ def get_step_specific_info(self):

@receiver(post_save, sender=AlertGroupLogRecord)
def listen_for_alertgrouplogrecord(sender, instance, created, *args, **kwargs):
instance.alert_group.drop_cached_after_resolve_report_json()
if instance.type != AlertGroupLogRecord.TYPE_DELETED:
if not instance.alert_group.is_maintenance_incident:
alert_group_pk = instance.alert_group.pk
Expand All @@ -554,3 +555,6 @@ def listen_for_alertgrouplogrecord(sender, instance, created, *args, **kwargs):
f"alert group event: {instance.get_type_display()}"
)
send_update_log_report_signal.apply_async(kwargs={"alert_group_pk": alert_group_pk}, countdown=8)

logger.info(f"Recalculate AG cache. Reason: save alert_group_log_record model {instance.pk}")
transaction.on_commit(instance.alert_group.schedule_cache_for_web)
16 changes: 15 additions & 1 deletion engine/apps/alerts/models/alert_receive_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@
from apps.alerts.grafana_alerting_sync_manager.grafana_alerting_sync import GrafanaAlertingSyncManager
from apps.alerts.integration_options_mixin import IntegrationOptionsMixin
from apps.alerts.models.maintainable_object import MaintainableObject
from apps.alerts.tasks import disable_maintenance, sync_grafana_alerting_contact_points
from apps.alerts.tasks import (
disable_maintenance,
invalidate_web_cache_for_alert_group,
sync_grafana_alerting_contact_points,
)
from apps.base.messaging import get_messaging_backend_from_id
from apps.base.utils import live_settings
from apps.integrations.metadata import heartbeat
Expand Down Expand Up @@ -689,6 +693,16 @@ def listen_for_alertreceivechannel_model_save(sender, instance, created, *args,
create_organization_log(
instance.organization, None, OrganizationLogType.TYPE_HEARTBEAT_CREATED, description
)
else:
logger.info(f"Drop AG cache. Reason: save alert_receive_channel {instance.pk}")
if kwargs is not None:
if "update_fields" in kwargs:
if kwargs["update_fields"] is not None:
# Hack to not to invalidate web cache on AlertReceiveChannel.start_send_rate_limit_message_task
if "rate_limit_message_task_id" in kwargs["update_fields"]:
return

invalidate_web_cache_for_alert_group.apply_async(kwargs={"channel_pk": instance.pk})

if instance.integration == AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING:
if created:
Expand Down
2 changes: 1 addition & 1 deletion engine/apps/alerts/tasks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .delete_alert_group import delete_alert_group # noqa: F401
from .distribute_alert import distribute_alert # noqa: F401
from .escalate_alert_group import escalate_alert_group # noqa: F401
from .invalidate_web_cache_for_alert_group import invalidate_web_cache_for_alert_group # noqa: F401, todo: remove
from .invalidate_web_cache_for_alert_group import invalidate_web_cache_for_alert_group # noqa: F401
from .invite_user_to_join_incident import invite_user_to_join_incident # noqa: F401
from .maintenance import disable_maintenance # noqa: F401
from .notify_all import notify_all_task # noqa: F401
Expand Down
Loading

0 comments on commit 7627853

Please sign in to comment.