diff --git a/CHANGELOG.md b/CHANGELOG.md index 21b1cc16fe..18c3381eea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Modified `check_escalation_finished_task` celery task to use read-only databases for its query, if one is defined + make the validation logic stricter + ping a configurable heartbeat on successful completion of this task ([1266](https://github.com/grafana/oncall/pull/1266)) +- add new columns `gcom_org_contract_type` and `gcom_org_has_irm_sku` to `user_management_organization` table + + `is_restricted` column to `alerts_alertgroup` table ([1522](https://github.com/grafana/oncall/pull/1522)) +- emit two new Django signals ([1522](https://github.com/grafana/oncall/pull/1522)) + - `org_sync_signal` at the end of the `engine/apps/user_management/sync.py::sync_organization` method + - `alert_group_created_signal` when a new Alert Group is created ### Changed diff --git a/engine/apps/alerts/incident_appearance/renderers/classic_markdown_renderer.py b/engine/apps/alerts/incident_appearance/renderers/classic_markdown_renderer.py index aa7a059ef4..238862b716 100644 --- a/engine/apps/alerts/incident_appearance/renderers/classic_markdown_renderer.py +++ b/engine/apps/alerts/incident_appearance/renderers/classic_markdown_renderer.py @@ -10,13 +10,16 @@ def templater_class(self): def render(self): templated_alert = self.templated_alert - rendered_alert = { - "title": str_or_backup(templated_alert.title, "Alert"), - "message": str_or_backup(templated_alert.message, ""), - "image_url": str_or_backup(templated_alert.image_url, None), - "source_link": str_or_backup(templated_alert.source_link, None), + is_restricted = self.alert.group.is_restricted + + # TODO: update these.. + is_restricted_msg = "RESTRICTED TODO TODO" + return { + "title": is_restricted_msg if is_restricted else str_or_backup(templated_alert.title, "Alert"), + "message": is_restricted_msg if is_restricted else str_or_backup(templated_alert.message, ""), + "image_url": None if is_restricted else str_or_backup(templated_alert.image_url, None), + "source_link": None if is_restricted else str_or_backup(templated_alert.source_link, None), } - return rendered_alert class AlertGroupClassicMarkdownRenderer(AlertGroupBaseRenderer): diff --git a/engine/apps/alerts/incident_appearance/renderers/phone_call_renderer.py b/engine/apps/alerts/incident_appearance/renderers/phone_call_renderer.py index b804aaf825..238defe82f 100644 --- a/engine/apps/alerts/incident_appearance/renderers/phone_call_renderer.py +++ b/engine/apps/alerts/incident_appearance/renderers/phone_call_renderer.py @@ -24,10 +24,12 @@ def render(self): templated_alert = self.alert_renderer.templated_alert title = str_or_backup(templated_alert.title, DEFAULT_BACKUP_TITLE) - text = self.TEMPLATE.format( + if self.alert_group.is_restricted: + # TODO: update this text + return "RESTRICTED TODO TODO" + + return self.TEMPLATE.format( integration_name=self.alert_group.channel.short_name, title=title, alert_count=self.alert_group.alerts.count(), ) - - return text diff --git a/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py b/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py index 708effafc2..8515becc2c 100644 --- a/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py +++ b/engine/apps/alerts/incident_appearance/renderers/slack_renderer.py @@ -62,6 +62,7 @@ def render_alert_attachments(self): return attachments +# TODO: class AlertGroupSlackRenderer(AlertGroupBaseRenderer): def __init__(self, alert_group): super().__init__(alert_group) diff --git a/engine/apps/alerts/incident_appearance/renderers/sms_renderer.py b/engine/apps/alerts/incident_appearance/renderers/sms_renderer.py index 139247710a..49d64371d9 100644 --- a/engine/apps/alerts/incident_appearance/renderers/sms_renderer.py +++ b/engine/apps/alerts/incident_appearance/renderers/sms_renderer.py @@ -18,6 +18,11 @@ def alert_renderer_class(self): def render(self): templated_alert = self.alert_renderer.templated_alert title = str_or_backup(templated_alert.title, DEFAULT_BACKUP_TITLE) + + if self.alert_group.is_restricted: + # TODO: update this text + return "RESTRICTED TODO TODO" + if self.alert_group.channel.organization.slack_team_identity and ( permalink := self.alert_group.slack_permalink ): diff --git a/engine/apps/alerts/incident_appearance/renderers/telegram_renderer.py b/engine/apps/alerts/incident_appearance/renderers/telegram_renderer.py index 33d31b0afc..e6c1143e40 100644 --- a/engine/apps/alerts/incident_appearance/renderers/telegram_renderer.py +++ b/engine/apps/alerts/incident_appearance/renderers/telegram_renderer.py @@ -52,15 +52,20 @@ def render(self): # First line in the invisible link with id of organization. # It is needed to add info about organization to the telegram message for the oncall-gateway. text = f"" - text += f"{status_emoji} #{self.alert_group.inside_organization_number}, {title}\n" - text += f"{status_verbose}, alerts: {alerts_count_str}\n" - text += f"Source: {self.alert_group.channel.short_name}\n" - text += f"{self.alert_group.web_link}" - if message: - text += f"\n\n{message}" + if self.alert_group.is_restricted: + # TODO: update this text + text += "RESTRICTED TODO TODO" + else: + text += f"{status_emoji} #{self.alert_group.inside_organization_number}, {title}\n" + text += f"{status_verbose}, alerts: {alerts_count_str}\n" + text += f"Source: {self.alert_group.channel.short_name}\n" + text += f"{self.alert_group.web_link}" + + if message: + text += f"\n\n{message}" - if image_url is not None: - text = f"" + text + if image_url is not None: + text = f"" + text return emojize(text, use_aliases=True) diff --git a/engine/apps/alerts/incident_appearance/renderers/web_renderer.py b/engine/apps/alerts/incident_appearance/renderers/web_renderer.py index 681f94f551..ccb0f885f8 100644 --- a/engine/apps/alerts/incident_appearance/renderers/web_renderer.py +++ b/engine/apps/alerts/incident_appearance/renderers/web_renderer.py @@ -10,13 +10,16 @@ def templater_class(self): def render(self): templated_alert = self.templated_alert - rendered_alert = { - "title": str_or_backup(templated_alert.title, "Alert"), - "message": str_or_backup(templated_alert.message, ""), - "image_url": str_or_backup(templated_alert.image_url, None), - "source_link": str_or_backup(templated_alert.source_link, None), + is_restricted = self.alert.group.is_restricted + + # TODO: update these.. + is_restricted_msg = "RESTRICTED TODO TODO" + return { + "title": is_restricted_msg if is_restricted else str_or_backup(templated_alert.title, "Alert"), + "message": is_restricted_msg if is_restricted else str_or_backup(templated_alert.message, ""), + "image_url": None if is_restricted else str_or_backup(templated_alert.image_url, None), + "source_link": None if is_restricted else str_or_backup(templated_alert.source_link, None), } - return rendered_alert class AlertGroupWebRenderer(AlertGroupBaseRenderer): diff --git a/engine/apps/alerts/migrations/0011_auto_20230322_0951.py b/engine/apps/alerts/migrations/0011_auto_20230322_0951.py new file mode 100644 index 0000000000..a6437a635b --- /dev/null +++ b/engine/apps/alerts/migrations/0011_auto_20230322_0951.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.18 on 2023-03-22 09:51 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('alerts', '0010_channelfilter_filtering_term_type'), + ] + + operations = [ + migrations.AddField( + model_name='alertgroup', + name='is_restricted', + field=models.BooleanField(default=False, null=True), + ), + migrations.AddField( + model_name='alertgroupcounter', + name='current_month', + field=models.DateField(default=datetime.date.today), + ), + migrations.AddField( + model_name='alertgroupcounter', + name='value_this_month', + field=models.PositiveBigIntegerField(default=0), + ), + ] diff --git a/engine/apps/alerts/models/alert_group.py b/engine/apps/alerts/models/alert_group.py index 7d4c4c951f..f0fdfa06bd 100644 --- a/engine/apps/alerts/models/alert_group.py +++ b/engine/apps/alerts/models/alert_group.py @@ -19,7 +19,7 @@ from apps.alerts.incident_appearance.renderers.constants import DEFAULT_BACKUP_TITLE 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.signals import alert_group_action_triggered_signal, alert_group_created_signal from apps.alerts.tasks import acknowledge_reminder_task, call_ack_url, send_alert_group_signal, unsilence_task from apps.slack.slack_formatter import SlackFormatter from apps.user_management.models import User @@ -88,10 +88,9 @@ def get_or_create_grouping(self, channel, channel_filter, group_data): # Create a new group if we couldn't group it to any existing ones try: - return ( - self.create(**search_params, is_open_for_grouping=True, web_title_cache=group_data.web_title_cache), - True, - ) + ag = self.create(**search_params, is_open_for_grouping=True, web_title_cache=group_data.web_title_cache) + alert_group_created_signal.send(sender=self.__class__, alert_group=ag) + return (ag, True) except IntegrityError: try: return self.get(**search_params, is_open_for_grouping__isnull=False), False @@ -351,6 +350,8 @@ def status(self): # https://code.djangoproject.com/ticket/28545 is_open_for_grouping = models.BooleanField(default=None, null=True, blank=True) + is_restricted = models.BooleanField(default=False, null=True) + @staticmethod def get_silenced_state_filter(): """ diff --git a/engine/apps/alerts/models/alert_group_counter.py b/engine/apps/alerts/models/alert_group_counter.py index c5be00e107..770e3438a9 100644 --- a/engine/apps/alerts/models/alert_group_counter.py +++ b/engine/apps/alerts/models/alert_group_counter.py @@ -1,3 +1,5 @@ +from datetime import date + from django.db import models @@ -9,7 +11,18 @@ class AlertGroupCounterQuerySet(models.QuerySet): def get_value(self, organization): counter, _ = self.get_or_create(organization=organization) - num_updated_rows = self.filter(organization=organization, value=counter.value).update(value=counter.value + 1) + today = date.today() + update_kwargs = {} + if counter.current_month.month != today.month or counter.current_month.year != today.year: + # if the previous alert group was created not in the current month, reset the monthly counter values + update_kwargs["current_month"] = today + update_kwargs["value_this_month"] = 1 + else: + update_kwargs["value_this_month"] = counter.value_this_month + 1 + + num_updated_rows = self.filter(organization=organization, value=counter.value).update( + value=counter.value + 1, **update_kwargs + ) if num_updated_rows == 0: raise ConcurrentUpdateError() @@ -28,3 +41,6 @@ class AlertGroupCounter(models.Model): organization = models.OneToOneField("user_management.Organization", on_delete=models.CASCADE) value = models.PositiveBigIntegerField(default=0) + + current_month = models.DateField(default=date.today) + value_this_month = models.PositiveBigIntegerField(default=0) diff --git a/engine/apps/alerts/signals.py b/engine/apps/alerts/signals.py index c3934d05f6..1b4652aa32 100644 --- a/engine/apps/alerts/signals.py +++ b/engine/apps/alerts/signals.py @@ -14,6 +14,12 @@ ] ) +alert_group_created_signal = django.dispatch.Signal( + providing_args=[ + "alert_group", + ] +) + # Signal to rerender alert group in all connected integrations (Slack, Telegram) when its state is changed alert_group_action_triggered_signal = django.dispatch.Signal( providing_args=[ diff --git a/engine/apps/user_management/migrations/0010_auto_20230321_1054.py b/engine/apps/user_management/migrations/0010_auto_20230321_1054.py new file mode 100644 index 0000000000..d290c58d5e --- /dev/null +++ b/engine/apps/user_management/migrations/0010_auto_20230321_1054.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.18 on 2023-03-21 10:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user_management', '0009_organization_cluster_slug'), + ] + + operations = [ + migrations.AddField( + model_name='organization', + name='gcom_org_contract_type', + field=models.CharField(default=None, max_length=300, null=True), + ), + migrations.AddField( + model_name='organization', + name='gcom_org_has_irm_sku', + field=models.BooleanField(default=False, null=True), + ), + ] diff --git a/engine/apps/user_management/models/organization.py b/engine/apps/user_management/models/organization.py index 784f3d4ac6..04100eac1c 100644 --- a/engine/apps/user_management/models/organization.py +++ b/engine/apps/user_management/models/organization.py @@ -133,6 +133,8 @@ def _get_subscription_strategy(self): gcom_token = mirage_fields.EncryptedCharField(max_length=300, null=True, default=None) gcom_token_org_last_time_synced = models.DateTimeField(null=True, default=None) + gcom_org_contract_type = models.CharField(max_length=300, null=True, default=None) + gcom_org_has_irm_sku = models.BooleanField(default=False, null=True) last_time_synced = models.DateTimeField(null=True, default=None) diff --git a/engine/apps/user_management/signals.py b/engine/apps/user_management/signals.py new file mode 100644 index 0000000000..bee6e1c30a --- /dev/null +++ b/engine/apps/user_management/signals.py @@ -0,0 +1,3 @@ +import django.dispatch + +org_sync_signal = django.dispatch.Signal() diff --git a/engine/apps/user_management/sync.py b/engine/apps/user_management/sync.py index f2abac9b34..c5bf2f2244 100644 --- a/engine/apps/user_management/sync.py +++ b/engine/apps/user_management/sync.py @@ -6,6 +6,7 @@ from apps.grafana_plugin.helpers.client import GcomAPIClient, GrafanaAPIClient from apps.user_management.models import Organization, Team, User +from apps.user_management.signals import org_sync_signal logger = get_task_logger(__name__) logger.setLevel(logging.DEBUG) @@ -55,6 +56,8 @@ def sync_organization(organization): ] ) + org_sync_signal.send(sender=None, org_id=organization.id) + def _sync_instance_info(organization): if organization.gcom_token: