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: