diff --git a/engine/apps/alerts/integration_options_mixin.py b/engine/apps/alerts/integration_options_mixin.py index b5b00a41..a0a81bab 100644 --- a/engine/apps/alerts/integration_options_mixin.py +++ b/engine/apps/alerts/integration_options_mixin.py @@ -69,7 +69,6 @@ class IntegrationOptionsMixin: "grouping_id", "resolve_condition", "acknowledge_condition", - "group_verbose_name", "source_link", ] diff --git a/engine/apps/alerts/migrations/0007_populate_verbose_name.py b/engine/apps/alerts/migrations/0007_populate_verbose_name.py new file mode 100644 index 00000000..89d6fc44 --- /dev/null +++ b/engine/apps/alerts/migrations/0007_populate_verbose_name.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.15 on 2022-09-01 16:54 + +from django.db import migrations + +from apps.alerts.models import AlertReceiveChannel +from apps.alerts.tasks import update_verbose_name_for_alert_receive_channel + + +def populate_verbose_name(apps, _): + pks = AlertReceiveChannel.objects_with_deleted.values_list("pk", flat=True) + for pk in pks: + update_verbose_name_for_alert_receive_channel.delay(pk) + + +class Migration(migrations.Migration): + + dependencies = [ + ('alerts', '0006_alertgroup_alerts_aler_channel_ee84a7_idx'), + ] + + operations = [ + migrations.RunPython(populate_verbose_name, migrations.RunPython.noop), + ] diff --git a/engine/apps/alerts/models/alert.py b/engine/apps/alerts/models/alert.py index 8f5b272a..a41d3f12 100644 --- a/engine/apps/alerts/models/alert.py +++ b/engine/apps/alerts/models/alert.py @@ -179,19 +179,19 @@ class Alert(models.Model): is_resolve_signal = False is_acknowledge_signal = False group_distinction = None - group_verbose_name = "Incident" acknowledge_condition_template = template_manager.get_attr_template( "acknowledge_condition", alert_receive_channel ) resolve_condition_template = template_manager.get_attr_template("resolve_condition", alert_receive_channel) grouping_id_template = template_manager.get_attr_template("grouping_id", alert_receive_channel) - # use get_default_attr_template because there is no ability to customize group_verbose_name, only default value - group_verbose_name_template = template_manager.get_default_attr_template( - "group_verbose_name", alert_receive_channel - ) - if group_verbose_name_template is not None: - group_verbose_name, _ = apply_jinja_template(group_verbose_name_template, raw_request_data) + + # set verbose_name to web title to allow alert group searching based on verbose_name + web_title_template = template_manager.get_attr_template("title", alert_receive_channel, render_for="web") + if web_title_template: + group_verbose_name = apply_jinja_template(web_title_template, raw_request_data)[0] or None + else: + group_verbose_name = None if grouping_id_template is not None: group_distinction, _ = apply_jinja_template(grouping_id_template, raw_request_data) diff --git a/engine/apps/alerts/models/alert_group.py b/engine/apps/alerts/models/alert_group.py index 84a0a9aa..1f60ce91 100644 --- a/engine/apps/alerts/models/alert_group.py +++ b/engine/apps/alerts/models/alert_group.py @@ -899,7 +899,7 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models. self.resolve(resolved_by=AlertGroup.WIPED) self.stop_escalation() self.distinction = "" - self.verbose_name = "Wiped incident" + self.verbose_name = None self.wiped_at = timezone.now() self.wiped_by = user for alert in self.alerts.all(): diff --git a/engine/apps/alerts/tasks/__init__.py b/engine/apps/alerts/tasks/__init__.py index bf1ad097..48a30b7a 100644 --- a/engine/apps/alerts/tasks/__init__.py +++ b/engine/apps/alerts/tasks/__init__.py @@ -1,4 +1,5 @@ from .acknowledge_reminder import acknowledge_reminder_task # noqa: F401 +from .alert_group_verbose_name import update_verbose_name, update_verbose_name_for_alert_receive_channel # noqa:F401 from .calculcate_escalation_finish_time import calculate_escalation_finish_time # noqa from .call_ack_url import call_ack_url # noqa: F401 from .check_escalation_finished import check_escalation_finished_task # noqa: F401 diff --git a/engine/apps/alerts/tasks/alert_group_verbose_name.py b/engine/apps/alerts/tasks/alert_group_verbose_name.py new file mode 100644 index 00000000..202df5bc --- /dev/null +++ b/engine/apps/alerts/tasks/alert_group_verbose_name.py @@ -0,0 +1,72 @@ +from django.db.models import Min + +from apps.alerts.incident_appearance.templaters import TemplateLoader +from apps.alerts.tasks.task_logger import task_logger +from common.custom_celery_tasks import shared_dedicated_queue_retry_task +from common.jinja_templater import apply_jinja_template + +# BATCH_SIZE is how many alert groups will be processed per second (for every individual alert receive channel) +BATCH_SIZE = 1000 + + +def batch_ids(queryset, cursor): + return list(queryset.filter(id__gt=cursor).order_by("id").values_list("id", flat=True)[:BATCH_SIZE]) + + +@shared_dedicated_queue_retry_task +def update_verbose_name_for_alert_receive_channel(alert_receive_channel_pk): + from apps.alerts.models import AlertGroup + + countdown = 0 + cursor = 0 + queryset = AlertGroup.all_objects.filter(channel_id=alert_receive_channel_pk) + ids = batch_ids(queryset, cursor) + + while ids: + update_verbose_name.apply_async((alert_receive_channel_pk, ids[0], ids[-1]), countdown=countdown) + + cursor = ids[-1] + ids = batch_ids(queryset, cursor) + countdown += 1 + + +@shared_dedicated_queue_retry_task +def update_verbose_name(alert_receive_channel_pk, alert_group_pk_start, alert_group_pk_end): + from apps.alerts.models import Alert, AlertGroup, AlertReceiveChannel + + try: + alert_receive_channel = AlertReceiveChannel.objects_with_deleted.get(pk=alert_receive_channel_pk) + except AlertReceiveChannel.DoesNotExist: + task_logger.warning(f"AlertReceiveChannel {alert_receive_channel_pk} doesn't exist") + return + + alert_groups = AlertGroup.all_objects.filter(pk__gte=alert_group_pk_start, pk__lte=alert_group_pk_end).only("pk") + + # get first alerts in 2 SQL queries + alerts_info = ( + Alert.objects.values("group_id") + .filter(group_id__gte=alert_group_pk_start, group_id__lte=alert_group_pk_end) + .annotate(first_alert_id=Min("id")) + ) + alerts_info_map = {info["group_id"]: info for info in alerts_info} + + first_alert_ids = [info["first_alert_id"] for info in alerts_info_map.values()] + first_alerts = Alert.objects.filter(pk__in=first_alert_ids).values("group_id", "raw_request_data") + first_alert_map = {alert["group_id"]: alert for alert in first_alerts} + + template_manager = TemplateLoader() + web_title_template = template_manager.get_attr_template("title", alert_receive_channel, render_for="web") + + for alert_group in alert_groups: + if web_title_template: + if alert_group.pk in first_alert_map: + raw_request_data = first_alert_map[alert_group.pk]["raw_request_data"] + verbose_name = apply_jinja_template(web_title_template, raw_request_data)[0] or None + else: + verbose_name = None + else: + verbose_name = None + + alert_group.verbose_name = verbose_name + + AlertGroup.all_objects.bulk_update(alert_groups, ["verbose_name"]) diff --git a/engine/apps/alerts/tests/test_default_templates.py b/engine/apps/alerts/tests/test_default_templates.py index 63cfd0b8..259aa051 100644 --- a/engine/apps/alerts/tests/test_default_templates.py +++ b/engine/apps/alerts/tests/test_default_templates.py @@ -92,7 +92,6 @@ def test_render_group_data_templates( assert group_data.group_distinction == template_module.tests.get("group_distinction") assert group_data.is_resolve_signal == template_module.tests.get("is_resolve_signal") assert group_data.is_acknowledge_signal == template_module.tests.get("is_acknowledge_signal") - assert group_data.group_verbose_name == template_module.tests.get("group_verbose_name") def test_default_templates_are_valid(): diff --git a/engine/apps/api/serializers/alert_group.py b/engine/apps/api/serializers/alert_group.py index df5583c4..f9ecf443 100644 --- a/engine/apps/api/serializers/alert_group.py +++ b/engine/apps/api/serializers/alert_group.py @@ -61,7 +61,6 @@ class AlertGroupListSerializer(EagerLoadingMixin, serializers.ModelSerializer): "pk", "alerts_count", "inside_organization_number", - "verbose_name", "alert_receive_channel", "resolved", "resolved_by", diff --git a/engine/apps/api/views/alert_group.py b/engine/apps/api/views/alert_group.py index 9fd72296..0d44e150 100644 --- a/engine/apps/api/views/alert_group.py +++ b/engine/apps/api/views/alert_group.py @@ -192,7 +192,7 @@ class AlertGroupView( filter_backends = [SearchFilter, filters.DjangoFilterBackend] # todo: add ability to search by templated title - search_fields = ["public_primary_key", "inside_organization_number"] + search_fields = ["public_primary_key", "inside_organization_number", "verbose_name"] filterset_class = AlertGroupFilter diff --git a/engine/apps/api/views/alert_receive_channel_template.py b/engine/apps/api/views/alert_receive_channel_template.py index ff8cd923..b1fac13b 100644 --- a/engine/apps/api/views/alert_receive_channel_template.py +++ b/engine/apps/api/views/alert_receive_channel_template.py @@ -2,6 +2,7 @@ from rest_framework import mixins, viewsets from rest_framework.permissions import IsAuthenticated from apps.alerts.models import AlertReceiveChannel +from apps.alerts.tasks import update_verbose_name_for_alert_receive_channel from apps.api.permissions import MODIFY_ACTIONS, READ_ACTIONS, ActionPermission, AnyRole, IsAdmin from apps.api.serializers.alert_receive_channel import AlertReceiveChannelTemplatesSerializer from apps.auth_token.auth import PluginAuthentication @@ -36,9 +37,14 @@ class AlertReceiveChannelTemplateView( def update(self, request, *args, **kwargs): instance = self.get_object() prev_state = instance.insight_logs_serialized + prev_web_title_template = instance.web_title_template + result = super().update(request, *args, **kwargs) + instance = self.get_object() new_state = instance.insight_logs_serialized + new_web_title_template = instance.web_title_template + write_resource_insight_log( instance=instance, author=self.request.user, @@ -46,4 +52,8 @@ class AlertReceiveChannelTemplateView( prev_state=prev_state, new_state=new_state, ) + + if new_web_title_template != prev_web_title_template: + update_verbose_name_for_alert_receive_channel.delay(instance.pk) + return result diff --git a/engine/apps/public_api/views/integrations.py b/engine/apps/public_api/views/integrations.py index 36ef6ea3..5c5df6f3 100644 --- a/engine/apps/public_api/views/integrations.py +++ b/engine/apps/public_api/views/integrations.py @@ -5,6 +5,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.viewsets import ModelViewSet from apps.alerts.models import AlertReceiveChannel +from apps.alerts.tasks import update_verbose_name_for_alert_receive_channel from apps.auth_token.auth import ApiTokenAuthentication from apps.public_api.serializers import IntegrationSerializer, IntegrationUpdateSerializer from apps.public_api.throttlers.user_throttle import UserThrottle @@ -58,17 +59,27 @@ class IntegrationView( raise NotFound def perform_update(self, serializer): - prev_state = serializer.instance.insight_logs_serialized + instance = serializer.instance + + prev_state = instance.insight_logs_serialized + prev_web_title_template = instance.web_title_template + serializer.save() - new_state = serializer.instance.insight_logs_serialized + + new_state = instance.insight_logs_serialized + new_web_title_template = instance.web_title_template + write_resource_insight_log( - instance=serializer.instance, + instance=instance, author=self.request.user, event=EntityEvent.UPDATED, prev_state=prev_state, new_state=new_state, ) + if new_web_title_template != prev_web_title_template: + update_verbose_name_for_alert_receive_channel.delay(instance.pk) + def perform_destroy(self, instance): write_resource_insight_log(instance=instance, author=self.request.user, event=EntityEvent.DELETED) instance.delete() diff --git a/engine/config_integrations/alertmanager.py b/engine/config_integrations/alertmanager.py index cb2fa9b6..bfdcff2e 100644 --- a/engine/config_integrations/alertmanager.py +++ b/engine/config_integrations/alertmanager.py @@ -116,8 +116,6 @@ resolve_condition = """\ acknowledge_condition = None -group_verbose_name = "Incident" - tests = { "payload": { "endsAt": "0001-01-01T00:00:00Z", diff --git a/engine/config_integrations/elastalert.py b/engine/config_integrations/elastalert.py index 90e9bfcc..73320d53 100644 --- a/engine/config_integrations/elastalert.py +++ b/engine/config_integrations/elastalert.py @@ -61,6 +61,4 @@ resolve_condition = """\ acknowledge_condition = None -group_verbose_name = "Incident" - example_payload = {"message": "This alert was sent by user for the demonstration purposes"} diff --git a/engine/config_integrations/formatted_webhook.py b/engine/config_integrations/formatted_webhook.py index 6f712a23..6847639f 100644 --- a/engine/config_integrations/formatted_webhook.py +++ b/engine/config_integrations/formatted_webhook.py @@ -50,8 +50,6 @@ resolve_condition = '{{ payload.get("state", "").upper() == "OK" }}' acknowledge_condition = None -group_verbose_name = web_title - example_payload = { "alert_uid": "08d6891a-835c-e661-39fa-96b6a9e26552", "title": "TestAlert: The whole system is down", diff --git a/engine/config_integrations/grafana.py b/engine/config_integrations/grafana.py index 383390c4..4feefd61 100644 --- a/engine/config_integrations/grafana.py +++ b/engine/config_integrations/grafana.py @@ -143,10 +143,6 @@ resolve_condition = """\ acknowledge_condition = None -group_verbose_name = """\ -{{ payload.get("ruleName", "Incident") }} -""" - tests = { "payload": { "endsAt": "0001-01-01T00:00:00Z", @@ -257,7 +253,6 @@ tests = { "group_distinction": "c6bf5494a2d3052459b4dac837e41455", "is_resolve_signal": False, "is_acknowledge_signal": False, - "group_verbose_name": "Incident", } # Miscellaneous diff --git a/engine/config_integrations/grafana_alerting.py b/engine/config_integrations/grafana_alerting.py index ae07e12e..e8942b1e 100644 --- a/engine/config_integrations/grafana_alerting.py +++ b/engine/config_integrations/grafana_alerting.py @@ -120,8 +120,6 @@ resolve_condition = """\ acknowledge_condition = None -group_verbose_name = "Incident" - tests = { "payload": { "endsAt": "0001-01-01T00:00:00Z", diff --git a/engine/config_integrations/heartbeat.py b/engine/config_integrations/heartbeat.py index f051a44c..e339b56f 100644 --- a/engine/config_integrations/heartbeat.py +++ b/engine/config_integrations/heartbeat.py @@ -26,6 +26,4 @@ resolve_condition = '{{ payload.get("is_resolve", False) == True }}' acknowledge_condition = None -group_verbose_name = '{{ payload.get("title", "Title") }}' - example_payload = {"foo": "bar"} diff --git a/engine/config_integrations/inbound_email.py b/engine/config_integrations/inbound_email.py index b934e35a..4ecac8e4 100644 --- a/engine/config_integrations/inbound_email.py +++ b/engine/config_integrations/inbound_email.py @@ -49,5 +49,3 @@ grouping_id = '{{ payload.get("title", "")}}' resolve_condition = '{{ payload.get("state", "").upper() == "OK" }}' acknowledge_condition = None - -group_verbose_name = web_title diff --git a/engine/config_integrations/kapacitor.py b/engine/config_integrations/kapacitor.py index d5f013fe..3d761766 100644 --- a/engine/config_integrations/kapacitor.py +++ b/engine/config_integrations/kapacitor.py @@ -56,8 +56,6 @@ resolve_condition = '{{ payload.get("level", "").startswith("OK") }}' acknowledge_condition = None -group_verbose_name = '{{ payload.get("id", "") }}' - example_payload = { "id": "TestAlert", "message": "This alert was sent by user for the demonstration purposes", diff --git a/engine/config_integrations/maintenance.py b/engine/config_integrations/maintenance.py index 957e53e9..d27405ef 100644 --- a/engine/config_integrations/maintenance.py +++ b/engine/config_integrations/maintenance.py @@ -49,5 +49,3 @@ grouping_id = None resolve_condition = None acknowledge_condition = None - -group_verbose_name = "Incident" diff --git a/engine/config_integrations/manual.py b/engine/config_integrations/manual.py index 43f4852b..fdcaadaa 100644 --- a/engine/config_integrations/manual.py +++ b/engine/config_integrations/manual.py @@ -58,5 +58,3 @@ grouping_id = """{{ payload }}""" resolve_condition = None acknowledge_condition = None - -group_verbose_name = web_title diff --git a/engine/config_integrations/slack_channel.py b/engine/config_integrations/slack_channel.py index d01c186b..cd8ef14f 100644 --- a/engine/config_integrations/slack_channel.py +++ b/engine/config_integrations/slack_channel.py @@ -39,6 +39,4 @@ resolve_condition = None acknowledge_condition = None -group_verbose_name = '<#{{ payload.get("channel", "") }}>' - source_link = '{{ payload.get("amixr_mixin", {}).get("permalink", "")}}' diff --git a/engine/config_integrations/webhook.py b/engine/config_integrations/webhook.py index 113efc56..4a3b0b73 100644 --- a/engine/config_integrations/webhook.py +++ b/engine/config_integrations/webhook.py @@ -60,6 +60,4 @@ resolve_condition = """\ {%- endif %}""" acknowledge_condition = None -group_verbose_name = web_title - example_payload = {"message": "This alert was sent by user for the demonstration purposes"} diff --git a/engine/settings/prod_without_db.py b/engine/settings/prod_without_db.py index 88261cbb..fe99bed1 100644 --- a/engine/settings/prod_without_db.py +++ b/engine/settings/prod_without_db.py @@ -139,6 +139,8 @@ CELERY_TASK_ROUTES = { "apps.schedules.tasks.drop_cached_ical.drop_cached_ical_for_custom_events_for_organization": {"queue": "critical"}, "apps.schedules.tasks.drop_cached_ical.drop_cached_ical_task": {"queue": "critical"}, # LONG + "apps.alerts.tasks.alert_group_verbose_name.update_verbose_name_for_alert_receive_channel": {"queue": "long"}, + "apps.alerts.tasks.alert_group_verbose_name.update_verbose_name": {"queue": "long"}, "apps.alerts.tasks.check_escalation_finished.check_escalation_finished_task": {"queue": "long"}, "apps.grafana_plugin.tasks.sync.start_sync_organizations": {"queue": "long"}, "apps.grafana_plugin.tasks.sync.sync_organization_async": {"queue": "long"}, diff --git a/grafana-plugin/src/models/alertgroup/alertgroup.types.ts b/grafana-plugin/src/models/alertgroup/alertgroup.types.ts index 8f5e231b..d704c34e 100644 --- a/grafana-plugin/src/models/alertgroup/alertgroup.types.ts +++ b/grafana-plugin/src/models/alertgroup/alertgroup.types.ts @@ -72,7 +72,6 @@ export interface Alert { silenced_until: string; started_at: string; last_alert_at: string; - verbose_name: string; dependent_alert_groups: Alert[]; status: IncidentStatus; short?: boolean;