chore: remove references to SlackMessage._channel_id (#5325)

# What this PR does

- As a follow-up to https://github.com/grafana/oncall/pull/5292, and now
that `SlackMessage.channel` has been migrated via
[`engine/apps/slack/migrations/0007_migrate_slackmessage_channel_id.py`](https://github.com/grafana/oncall/pull/5292/files#diff-8aebe133401715a4262baad9b2c5c9fc59367c18d6bd6ac2b3c462fcdabafd66),
this PR removes reads/writes from `SlackMessage._channel_id` to
`SlackMessage.channel`. In a separate PR I will focus on dropping that
column from the model/db.
- Drops `SlackMessage.active_update_task_id`. There're zero references
to this column in the codebase.
- Removes two Django `manage.py` commands that're no longer needed:
- `engine/engine/management/commands/alertmanager_v2_migrate.py` (and
it's associated tests)
-
`engine/engine/management/commands/batch_migrate_slack_message_channel.py`

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
    show up in the autogenerated release notes.
This commit is contained in:
Joey Orlando 2024-12-06 09:28:26 -05:00 committed by GitHub
parent 710fb8bbc2
commit 3977c6e9ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 73 additions and 755 deletions

View file

@ -417,6 +417,10 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
)
prevent_posting_alerts = models.BooleanField(default=False)
"""
TODO: this column is no longer used, drop it in a subsequent PR/release
"""
maintenance_uuid = models.CharField(max_length=100, unique=True, null=True, default=None)
raw_escalation_snapshot = JSONField(null=True, default=None)
@ -1983,11 +1987,7 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models.
channel_filter = self.channel_filter
if self.slack_message:
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p173255546
#
# return self.slack_message.channel.slack_id
return self.slack_message._channel_id
return self.slack_message.channel.slack_id
elif channel_filter and channel_filter.slack_channel_or_org_default:
return channel_filter.slack_channel_or_org_default.slack_id
return None

View file

@ -827,8 +827,8 @@ class TestAlertGroupSlackChannelID:
slack_channel = make_slack_channel(slack_team_identity)
slack_message = make_slack_message(slack_channel, alert_group=alert_group)
# Assert that slack_channel_id returns the _channel_id from slack_message
assert alert_group.slack_channel_id == slack_message._channel_id
# Assert that slack_channel_id returns the channel.slack_id from slack_message
assert alert_group.slack_channel_id == slack_message.channel.slack_id
@pytest.mark.django_db
def test_slack_channel_id_with_channel_filter(

View file

@ -6,12 +6,10 @@ import pytest
from django.conf import settings
from django.db import IntegrityError
from django.urls import reverse
from django.utils import timezone
from apps.alerts.models import AlertReceiveChannel
from common.api_helpers.utils import create_engine_url
from common.exceptions import UnableToSendDemoAlert
from engine.management.commands import alertmanager_v2_migrate
from settings.base import DatabaseTypes
@ -306,381 +304,3 @@ def test_create_duplicate_direct_paging_integrations(make_organization, make_tea
integration=AlertReceiveChannel.INTEGRATION_DIRECT_PAGING,
)
super(AlertReceiveChannel, arc).save() # bypass the custom save method, so that IntegrityError is raised
@pytest.mark.django_db
def test_alertmanager_v2_migrate_forward(make_organization, make_alert_receive_channel):
organization = make_organization()
legacy_alertmanager = make_alert_receive_channel(
organization,
integration=AlertReceiveChannel.INTEGRATION_LEGACY_ALERTMANAGER,
slack_title_template="slack_title_template",
web_title_template="web_title_template",
grouping_id_template="grouping_id_template",
resolve_condition_template="resolve_condition_template",
)
alertmanager = make_alert_receive_channel(
organization,
integration=AlertReceiveChannel.INTEGRATION_ALERTMANAGER,
slack_title_template="slack_title_template",
)
legacy_grafana_alerting = make_alert_receive_channel(
organization, integration=AlertReceiveChannel.INTEGRATION_LEGACY_GRAFANA_ALERTING
)
grafana_alerting = make_alert_receive_channel(
organization,
integration=AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING,
slack_title_template="slack_title_template",
)
alertmanager_v2_migrate.Command().handle(backward=False)
legacy_alertmanager.refresh_from_db()
alertmanager.refresh_from_db()
legacy_grafana_alerting.refresh_from_db()
grafana_alerting.refresh_from_db()
assert legacy_alertmanager.integration == AlertReceiveChannel.INTEGRATION_ALERTMANAGER
assert legacy_alertmanager.alertmanager_v2_migrated_at is not None
assert legacy_alertmanager.slack_title_template is None
assert legacy_alertmanager.web_title_template is None
assert legacy_alertmanager.grouping_id_template is None
assert legacy_alertmanager.resolve_condition_template is None
assert legacy_alertmanager.alertmanager_v2_backup_templates["slack_title_template"] == "slack_title_template"
assert legacy_alertmanager.alertmanager_v2_backup_templates["web_title_template"] == "web_title_template"
assert legacy_alertmanager.alertmanager_v2_backup_templates["grouping_id_template"] == "grouping_id_template"
assert (
legacy_alertmanager.alertmanager_v2_backup_templates["resolve_condition_template"]
== "resolve_condition_template"
)
assert legacy_alertmanager.alertmanager_v2_backup_templates["messaging_backends_templates"] is None
assert legacy_grafana_alerting.integration == AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING
assert legacy_grafana_alerting.alertmanager_v2_migrated_at is not None
assert legacy_grafana_alerting.alertmanager_v2_backup_templates is None
assert alertmanager.integration == AlertReceiveChannel.INTEGRATION_ALERTMANAGER
assert alertmanager.alertmanager_v2_migrated_at is None
assert alertmanager.slack_title_template == "slack_title_template"
assert alertmanager.alertmanager_v2_backup_templates is None
assert grafana_alerting.integration == AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING
assert grafana_alerting.alertmanager_v2_migrated_at is None
assert grafana_alerting.slack_title_template == "slack_title_template"
assert grafana_alerting.alertmanager_v2_backup_templates is None
@pytest.mark.django_db
def test_alertmanager_v2_migrate_backward(make_organization, make_alert_receive_channel):
organization = make_organization()
migrated_alertmanager = make_alert_receive_channel(
organization,
integration=AlertReceiveChannel.INTEGRATION_ALERTMANAGER,
alertmanager_v2_migrated_at=timezone.now(),
alertmanager_v2_backup_templates={
"slack_title_template": "slack_title_template",
"web_title_template": "web_title_template",
"grouping_id_template": "grouping_id_template",
"resolve_condition_template": "resolve_condition_template",
},
)
alertmanager = make_alert_receive_channel(
organization,
integration=AlertReceiveChannel.INTEGRATION_ALERTMANAGER,
slack_title_template="slack_title_template",
)
migrated_grafana_alerting = make_alert_receive_channel(
organization,
integration=AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING,
alertmanager_v2_migrated_at=timezone.now(),
)
grafana_alerting = make_alert_receive_channel(
organization,
integration=AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING,
slack_title_template="slack_title_template",
)
alertmanager_v2_migrate.Command().handle(backward=True)
migrated_alertmanager.refresh_from_db()
alertmanager.refresh_from_db()
migrated_grafana_alerting.refresh_from_db()
grafana_alerting.refresh_from_db()
assert migrated_alertmanager.integration == AlertReceiveChannel.INTEGRATION_LEGACY_ALERTMANAGER
assert migrated_alertmanager.alertmanager_v2_migrated_at is None
assert migrated_alertmanager.slack_title_template == "slack_title_template"
assert migrated_alertmanager.web_title_template == "web_title_template"
assert migrated_alertmanager.grouping_id_template == "grouping_id_template"
assert migrated_alertmanager.resolve_condition_template == "resolve_condition_template"
assert migrated_alertmanager.alertmanager_v2_backup_templates is None
assert migrated_grafana_alerting.integration == AlertReceiveChannel.INTEGRATION_LEGACY_GRAFANA_ALERTING
assert migrated_grafana_alerting.alertmanager_v2_migrated_at is None
assert migrated_grafana_alerting.slack_title_template is None
assert migrated_grafana_alerting.web_title_template is None
assert migrated_grafana_alerting.grouping_id_template is None
assert migrated_grafana_alerting.resolve_condition_template is None
assert migrated_grafana_alerting.alertmanager_v2_backup_templates is None
assert alertmanager.integration == AlertReceiveChannel.INTEGRATION_ALERTMANAGER
assert alertmanager.alertmanager_v2_migrated_at is None
assert alertmanager.slack_title_template == "slack_title_template"
assert alertmanager.alertmanager_v2_backup_templates is None
assert grafana_alerting.integration == AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING
assert grafana_alerting.alertmanager_v2_migrated_at is None
assert grafana_alerting.slack_title_template == "slack_title_template"
assert grafana_alerting.alertmanager_v2_backup_templates is None
@pytest.mark.django_db
def test_alertmanager_v2_migrate_forward_one(make_organization, make_alert_receive_channel):
organization = make_organization()
# org which is not going to be migrated
organization_2 = make_organization()
legacy_alertmanager = make_alert_receive_channel(
organization,
integration=AlertReceiveChannel.INTEGRATION_LEGACY_ALERTMANAGER,
slack_title_template="slack_title_template",
web_title_template="web_title_template",
grouping_id_template="grouping_id_template",
resolve_condition_template="resolve_condition_template",
)
alertmanager = make_alert_receive_channel(
organization,
integration=AlertReceiveChannel.INTEGRATION_ALERTMANAGER,
slack_title_template="slack_title_template",
)
legacy_grafana_alerting = make_alert_receive_channel(
organization, integration=AlertReceiveChannel.INTEGRATION_LEGACY_GRAFANA_ALERTING
)
grafana_alerting = make_alert_receive_channel(
organization,
integration=AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING,
slack_title_template="slack_title_template",
)
# set up same set integrations for second org
legacy_alertmanager_2 = make_alert_receive_channel(
organization_2,
integration=AlertReceiveChannel.INTEGRATION_LEGACY_ALERTMANAGER,
slack_title_template="slack_title_template",
web_title_template="web_title_template",
grouping_id_template="grouping_id_template",
resolve_condition_template="resolve_condition_template",
)
alertmanager_2 = make_alert_receive_channel(
organization_2,
integration=AlertReceiveChannel.INTEGRATION_ALERTMANAGER,
slack_title_template="slack_title_template",
)
legacy_grafana_alerting_2 = make_alert_receive_channel(
organization_2, integration=AlertReceiveChannel.INTEGRATION_LEGACY_GRAFANA_ALERTING
)
grafana_alerting_2 = make_alert_receive_channel(
organization_2,
integration=AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING,
slack_title_template="slack_title_template",
)
alertmanager_v2_migrate.Command().handle(backward=False, org_id=organization.id)
legacy_alertmanager.refresh_from_db()
alertmanager.refresh_from_db()
legacy_grafana_alerting.refresh_from_db()
grafana_alerting.refresh_from_db()
legacy_alertmanager_2.refresh_from_db()
alertmanager_2.refresh_from_db()
legacy_grafana_alerting_2.refresh_from_db()
grafana_alerting_2.refresh_from_db()
assert legacy_alertmanager.integration == AlertReceiveChannel.INTEGRATION_ALERTMANAGER
assert legacy_alertmanager.alertmanager_v2_migrated_at is not None
assert legacy_alertmanager.slack_title_template is None
assert legacy_alertmanager.web_title_template is None
assert legacy_alertmanager.grouping_id_template is None
assert legacy_alertmanager.resolve_condition_template is None
assert legacy_alertmanager.alertmanager_v2_backup_templates["slack_title_template"] == "slack_title_template"
assert legacy_alertmanager.alertmanager_v2_backup_templates["web_title_template"] == "web_title_template"
assert legacy_alertmanager.alertmanager_v2_backup_templates["grouping_id_template"] == "grouping_id_template"
assert (
legacy_alertmanager.alertmanager_v2_backup_templates["resolve_condition_template"]
== "resolve_condition_template"
)
assert legacy_alertmanager.alertmanager_v2_backup_templates["messaging_backends_templates"] is None
assert legacy_grafana_alerting.integration == AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING
assert legacy_grafana_alerting.alertmanager_v2_migrated_at is not None
assert legacy_grafana_alerting.alertmanager_v2_backup_templates is None
assert alertmanager.integration == AlertReceiveChannel.INTEGRATION_ALERTMANAGER
assert alertmanager.alertmanager_v2_migrated_at is None
assert alertmanager.slack_title_template == "slack_title_template"
assert alertmanager.alertmanager_v2_backup_templates is None
assert grafana_alerting.integration == AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING
assert grafana_alerting.alertmanager_v2_migrated_at is None
assert grafana_alerting.slack_title_template == "slack_title_template"
assert grafana_alerting.alertmanager_v2_backup_templates is None
# check that second org is NOT affected
# check that legacy alertmanager not affected
assert legacy_alertmanager_2.integration == AlertReceiveChannel.INTEGRATION_LEGACY_ALERTMANAGER
assert legacy_alertmanager_2.alertmanager_v2_migrated_at is None
assert legacy_alertmanager_2.slack_title_template == "slack_title_template"
assert legacy_alertmanager_2.web_title_template == "web_title_template"
assert legacy_alertmanager_2.grouping_id_template == "grouping_id_template"
assert legacy_alertmanager_2.resolve_condition_template == "resolve_condition_template"
assert legacy_alertmanager_2.alertmanager_v2_backup_templates is None
# check that legacy grafana_alerting not affected
assert legacy_grafana_alerting_2.integration == AlertReceiveChannel.INTEGRATION_LEGACY_GRAFANA_ALERTING
assert legacy_grafana_alerting_2.alertmanager_v2_migrated_at is None
assert legacy_grafana_alerting_2.alertmanager_v2_backup_templates is None
# check that alertmanager which shouldn't be affected even by migration not touched
assert alertmanager_2.integration == AlertReceiveChannel.INTEGRATION_ALERTMANAGER
assert alertmanager_2.alertmanager_v2_migrated_at is None
assert alertmanager_2.slack_title_template == "slack_title_template"
assert alertmanager_2.alertmanager_v2_backup_templates is None
# same fpr grafana alerting
assert grafana_alerting_2.integration == AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING
assert grafana_alerting_2.alertmanager_v2_migrated_at is None
assert grafana_alerting_2.slack_title_template == "slack_title_template"
assert grafana_alerting_2.alertmanager_v2_backup_templates is None
@pytest.mark.django_db
def test_alertmanager_v2_migrate_backward_one(make_organization, make_alert_receive_channel):
organization = make_organization()
migrated_alertmanager = make_alert_receive_channel(
organization,
integration=AlertReceiveChannel.INTEGRATION_ALERTMANAGER,
alertmanager_v2_migrated_at=timezone.now(),
alertmanager_v2_backup_templates={
"slack_title_template": "slack_title_template",
"web_title_template": "web_title_template",
"grouping_id_template": "grouping_id_template",
"resolve_condition_template": "resolve_condition_template",
},
)
alertmanager = make_alert_receive_channel(
organization,
integration=AlertReceiveChannel.INTEGRATION_ALERTMANAGER,
slack_title_template="slack_title_template",
)
migrated_grafana_alerting = make_alert_receive_channel(
organization,
integration=AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING,
alertmanager_v2_migrated_at=timezone.now(),
)
grafana_alerting = make_alert_receive_channel(
organization,
integration=AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING,
slack_title_template="slack_title_template",
)
organization_2 = make_organization()
migrated_alertmanager_2 = make_alert_receive_channel(
organization_2,
integration=AlertReceiveChannel.INTEGRATION_ALERTMANAGER,
alertmanager_v2_migrated_at=timezone.now(),
alertmanager_v2_backup_templates={
"slack_title_template": "slack_title_template",
"web_title_template": "web_title_template",
"grouping_id_template": "grouping_id_template",
"resolve_condition_template": "resolve_condition_template",
},
)
alertmanager_2 = make_alert_receive_channel(
organization_2,
integration=AlertReceiveChannel.INTEGRATION_ALERTMANAGER,
slack_title_template="slack_title_template",
)
migrated_grafana_alerting_2 = make_alert_receive_channel(
organization_2,
integration=AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING,
alertmanager_v2_migrated_at=timezone.now(),
)
grafana_alerting_2 = make_alert_receive_channel(
organization_2,
integration=AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING,
slack_title_template="slack_title_template",
)
alertmanager_v2_migrate.Command().handle(backward=True, org_id=organization.id)
migrated_alertmanager.refresh_from_db()
alertmanager.refresh_from_db()
migrated_grafana_alerting.refresh_from_db()
grafana_alerting.refresh_from_db()
assert migrated_alertmanager.integration == AlertReceiveChannel.INTEGRATION_LEGACY_ALERTMANAGER
assert migrated_alertmanager.alertmanager_v2_migrated_at is None
assert migrated_alertmanager.slack_title_template == "slack_title_template"
assert migrated_alertmanager.web_title_template == "web_title_template"
assert migrated_alertmanager.grouping_id_template == "grouping_id_template"
assert migrated_alertmanager.resolve_condition_template == "resolve_condition_template"
assert migrated_alertmanager.alertmanager_v2_backup_templates is None
assert migrated_grafana_alerting.integration == AlertReceiveChannel.INTEGRATION_LEGACY_GRAFANA_ALERTING
assert migrated_grafana_alerting.alertmanager_v2_migrated_at is None
assert migrated_grafana_alerting.slack_title_template is None
assert migrated_grafana_alerting.web_title_template is None
assert migrated_grafana_alerting.grouping_id_template is None
assert migrated_grafana_alerting.resolve_condition_template is None
assert migrated_grafana_alerting.alertmanager_v2_backup_templates is None
assert alertmanager.integration == AlertReceiveChannel.INTEGRATION_ALERTMANAGER
assert alertmanager.alertmanager_v2_migrated_at is None
assert alertmanager.slack_title_template == "slack_title_template"
assert alertmanager.alertmanager_v2_backup_templates is None
assert grafana_alerting.integration == AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING
assert grafana_alerting.alertmanager_v2_migrated_at is None
assert grafana_alerting.slack_title_template == "slack_title_template"
assert grafana_alerting.alertmanager_v2_backup_templates is None
migrated_alertmanager_2.refresh_from_db()
alertmanager_2.refresh_from_db()
migrated_grafana_alerting_2.refresh_from_db()
grafana_alerting_2.refresh_from_db()
# check that migrated integrations is second org were not touced by backward migration of other org
assert migrated_alertmanager_2.integration == AlertReceiveChannel.INTEGRATION_ALERTMANAGER
assert migrated_alertmanager_2.alertmanager_v2_migrated_at is not None
assert migrated_alertmanager_2.slack_title_template is None
assert migrated_alertmanager_2.web_title_template is None
assert migrated_alertmanager_2.grouping_id_template is None
assert migrated_alertmanager_2.resolve_condition_template is None
assert migrated_alertmanager_2.alertmanager_v2_backup_templates is not None
assert migrated_grafana_alerting_2.integration == AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING
assert migrated_grafana_alerting_2.alertmanager_v2_migrated_at is not None
assert migrated_grafana_alerting_2.slack_title_template is None
assert migrated_grafana_alerting_2.web_title_template is None
assert migrated_grafana_alerting_2.grouping_id_template is None
assert migrated_grafana_alerting_2.resolve_condition_template is None
assert migrated_grafana_alerting_2.alertmanager_v2_backup_templates is None
assert alertmanager_2.integration == AlertReceiveChannel.INTEGRATION_ALERTMANAGER
assert alertmanager_2.alertmanager_v2_migrated_at is None
assert alertmanager_2.slack_title_template == "slack_title_template"
assert alertmanager_2.alertmanager_v2_backup_templates is None
assert grafana_alerting_2.integration == AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING
assert grafana_alerting_2.alertmanager_v2_migrated_at is None
assert grafana_alerting_2.slack_title_template == "slack_title_template"
assert grafana_alerting_2.alertmanager_v2_backup_templates is None

View file

@ -0,0 +1,22 @@
# NOTE: this is being left in this directory on purpose, it will be moved to apps/alerts/migrations
# in a separate PR/release
#
# Generated by Django 4.2.16 on 2024-12-04 12:00
from django.db import migrations
import common.migrations.remove_field
class Migration(migrations.Migration):
dependencies = [
("slack", "0008_remove_slackmessage_active_update_task_id_state"),
]
operations = [
common.migrations.remove_field.RemoveFieldDB(
model_name="SlackMessage",
name="active_update_task_id",
remove_state_migration=("slack", "0008_remove_slackmessage_active_update_task_id_state"),
),
]

View file

@ -48,10 +48,7 @@ class AlertGroupSlackService:
try:
result = self._slack_client.chat_postMessage(
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p173255546
# channel=slack_message.channel.slack_id,
channel=slack_message._channel_id,
channel=slack_message.channel.slack_id,
text=text,
attachments=attachments,
thread_ts=slack_message.slack_id,
@ -66,11 +63,8 @@ class AlertGroupSlackService:
):
return
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p1732555465144099
alert_group.slack_messages.create(
slack_id=result["ts"],
organization=alert_group.channel.organization,
_channel_id=slack_message.channel.slack_id,
channel=slack_message.channel,
)

View file

@ -0,0 +1,18 @@
# Generated by Django 4.2.16 on 2024-12-04 12:00
import common.migrations.remove_field
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('slack', '0007_migrate_slackmessage_channel_id'),
]
operations = [
common.migrations.remove_field.RemoveFieldState(
model_name='SlackMessage',
name='active_update_task_id',
),
]

View file

@ -89,11 +89,6 @@ class SlackMessage(models.Model):
related_name="slack_messages",
)
active_update_task_id = models.CharField(max_length=100, null=True, default=None)
"""
DEPRECATED/TODO: drop this field in a separate PR/release
"""
class Meta:
# slack_id is unique within the context of a channel or conversation
constraints = [
@ -112,10 +107,7 @@ class SlackMessage(models.Model):
try:
result = SlackClient(self.slack_team_identity).chat_getPermalink(
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p173255546
# channel=self.channel.slack_id,
channel=self._channel_id,
channel=self.channel.slack_id,
message_ts=self.slack_id,
)
except SlackAPIError:
@ -128,9 +120,7 @@ class SlackMessage(models.Model):
@property
def deep_link(self) -> str:
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p173255546
return f"https://slack.com/app_redirect?channel={self._channel_id}&team={self.slack_team_identity.slack_id}&message={self.slack_id}"
return f"https://slack.com/app_redirect?channel={self.channel.slack_id}&team={self.slack_team_identity.slack_id}&message={self.slack_id}"
@classmethod
def send_slack_notification(
@ -232,12 +222,9 @@ class SlackMessage(models.Model):
).save()
return
else:
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p1732555465144099
alert_group.slack_messages.create(
slack_id=result["ts"],
organization=organization,
_channel_id=slack_channel.slack_id,
channel=slack_channel,
)

View file

@ -132,12 +132,9 @@ class SlackUserIdentity(models.Model):
"elements": [
{
"type": "mrkdwn",
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p173255546
# f"<#{slack_message.channel.slack_id}>.\n"
"text": (
f"You received this message because you're not a member of "
f"<#{slack_message._channel_id}>.\n"
f"<#{slack_message.channel.slack_id}>.\n"
"Please join the channel to get notified right in the alert group thread."
),
}

View file

@ -87,10 +87,7 @@ class UpdateAppearanceStep(scenario_step.ScenarioStep):
slack_message = alert_group.slack_message
self._slack_client.chat_update(
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p173255546
# channel=slack_message.channel.slack_id,
channel=slack_message._channel_id,
channel=slack_message.channel.slack_id,
ts=slack_message.slack_id,
attachments=alert_group.render_slack_attachments(),
blocks=alert_group.render_slack_blocks(),

View file

@ -158,12 +158,9 @@ class IncomingAlertStep(scenario_step.ScenarioStep):
blocks=alert_group.render_slack_blocks(),
)
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p1732555465144099
alert_group.slack_messages.create(
slack_id=result["ts"],
organization=alert_group.channel.organization,
_channel_id=slack_channel.slack_id,
channel=slack_channel,
)
@ -554,10 +551,7 @@ class AttachGroupStep(AlertGroupActionsMixin, scenario_step.ScenarioStep):
if slack_user_identity:
self._slack_client.chat_postEphemeral(
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p173255546
# channel=alert_group.slack_message.channel.slack_id,
channel=alert_group.slack_message._channel_id,
channel=alert_group.slack_message.channel.slack_id,
user=slack_user_identity.slack_id,
text="{}{}".format(ephemeral_text[:1].upper(), ephemeral_text[1:]),
unfurl_links=True,
@ -804,10 +798,7 @@ class UnAcknowledgeGroupStep(AlertGroupActionsMixin, scenario_step.ScenarioStep)
if slack_message.ack_reminder_message_ts:
try:
self._slack_client.chat_update(
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p173255546
# channel=slack_message.channel.slack_id,
channel=slack_message._channel_id,
channel=slack_message.channel.slack_id,
ts=slack_message.ack_reminder_message_ts,
text=text,
attachments=message_attachments,
@ -921,12 +912,9 @@ class AcknowledgeConfirmationStep(AcknowledgeGroupStep):
except (SlackAPITokenError, SlackAPIChannelArchivedError, SlackAPIChannelNotFoundError):
pass
else:
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p1732555465144099
alert_group.slack_messages.create(
slack_id=response["ts"],
organization=organization,
_channel_id=slack_channel.slack_id,
channel=slack_channel,
)
@ -981,13 +969,7 @@ class DeleteGroupStep(scenario_step.ScenarioStep):
# Remove alert group Slack messages
for message in alert_group.slack_messages.all():
try:
self._slack_client.chat_delete(
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p173255546
# channel=message.channel.slack_id,
channel=message._channel_id,
ts=message.slack_id,
)
self._slack_client.chat_delete(channel=message.channel.slack_id, ts=message.slack_id)
except SlackAPIRatelimitError:
# retries on ratelimit are handled in apps.alerts.tasks.delete_alert_group.delete_alert_group
raise

View file

@ -18,11 +18,7 @@ class NotificationDeliveryStep(scenario_step.ScenarioStep):
user = log_record.author
alert_group = log_record.alert_group
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p173255546
# slack_channel_id = alert_group_slack_message.channel.slack_id
slack_channel_id = alert_group.slack_message._channel_id
slack_channel_id = alert_group.slack_message.channel.slack_id
user_verbal_with_mention = user.get_username_with_slack_verbal(mention=True)

View file

@ -92,13 +92,10 @@ class AddToResolutionNoteStep(scenario_step.ScenarioStep):
return
try:
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p1732555465144099
slack_message = SlackMessage.objects.get(
slack_id=payload["message"]["thread_ts"],
organization__slack_team_identity=slack_team_identity,
_channel_id=channel_id,
# channel__slack_id=channel_id,
channel__slack_id=channel_id,
)
except SlackMessage.DoesNotExist:
if settings.UNIFIED_SLACK_APP_ENABLED:
@ -164,14 +161,10 @@ class AddToResolutionNoteStep(scenario_step.ScenarioStep):
slack_channel = SlackChannel.objects.get(
slack_id=channel_id, slack_team_identity=slack_team_identity
)
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p1732555465144099
slack_message = SlackMessage.objects.get(
slack_id=thread_ts,
organization__slack_team_identity=slack_team_identity,
# channel__slack_id=channel_id,
_channel_id=channel_id,
channel__slack_id=channel_id,
)
alert_group = slack_message.alert_group
@ -262,14 +255,10 @@ class UpdateResolutionNoteStep(scenario_step.ScenarioStep):
resolution_note_slack_message = resolution_note.resolution_note_slack_message
alert_group = resolution_note.alert_group
alert_group_slack_message = alert_group.slack_message
slack_channel_id = alert_group_slack_message.channel.slack_id
blocks = self.get_resolution_note_blocks(resolution_note)
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p173255546
# slack_channel_id = alert_group_slack_message.channel.slack_id
slack_channel_id = alert_group_slack_message._channel_id
slack_channel = SlackChannel.objects.get(
slack_id=slack_channel_id, slack_team_identity=self.slack_team_identity
)

View file

@ -161,12 +161,9 @@ class BaseShiftSwapRequestStep(scenario_step.ScenarioStep):
blocks=self._generate_blocks(shift_swap_request),
)
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p1732555465144099
return SlackMessage.objects.create(
slack_id=result["ts"],
organization=self.organization,
_channel_id=shift_swap_request.slack_channel_id,
channel=shift_swap_request.slack_channel,
)
@ -186,11 +183,8 @@ class BaseShiftSwapRequestStep(scenario_step.ScenarioStep):
if not shift_swap_request.slack_message:
return
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p173255546
self._slack_client.chat_postMessage(
# channel=shift_swap_request.slack_message.channel.slack_id,
channel=shift_swap_request.slack_message._channel_id,
channel=shift_swap_request.slack_message.channel.slack_id,
thread_ts=shift_swap_request.slack_message.slack_id,
reply_broadcast=reply_broadcast,
blocks=blocks,

View file

@ -66,13 +66,10 @@ class SlackChannelMessageEventStep(scenario_step.ScenarioStep):
message_ts = payload["event"]["ts"]
try:
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p1732555465144099
slack_message = SlackMessage.objects.get(
slack_id=thread_ts,
organization__slack_team_identity=self.slack_team_identity,
_channel_id=channel_id,
# channel__slack_id=channel_id,
channel__slack_id=channel_id,
)
except SlackMessage.DoesNotExist:
return

View file

@ -63,12 +63,9 @@ class AlertGroupActionsMixin:
slack_team_identity=slack_team_identity,
)
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p1732555465144099
SlackMessage.objects.create(
slack_id=message_id,
organization=alert_group.channel.organization,
_channel_id=slack_channel.slack_id,
channel=slack_channel,
alert_group=alert_group,
)
@ -179,12 +176,9 @@ class AlertGroupActionsMixin:
logger.warning(f"alert_group_pk not found in payload, fetching SlackMessage from DB. message_ts: {message_ts}")
# Get SlackMessage from DB
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p1732555465144099
slack_message = SlackMessage.objects.get(
slack_id=message_ts,
organization__slack_team_identity=slack_team_identity,
_channel_id=channel_id,
# channel__slack_id=channel_id,
channel__slack_id=channel_id,
)
return slack_message.alert_group

View file

@ -98,10 +98,7 @@ def update_alert_group_slack_message(slack_message_pk: int) -> None:
try:
slack_client.chat_update(
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p173255546
# channel=slack_message.channel.slack_id,
channel=slack_message._channel_id,
channel=slack_message.channel.slack_id,
ts=slack_message.slack_id,
attachments=alert_group.render_slack_attachments(),
blocks=alert_group.render_slack_blocks(),

View file

@ -3,7 +3,7 @@ from unittest.mock import patch
import pytest
from django.utils import timezone
from apps.slack.models import SlackChannel
from apps.slack.models import SlackChannel, SlackMessage
from apps.slack.scenarios import slack_channel as slack_channel_scenarios
@ -84,6 +84,7 @@ class TestSlackChannelDeletedEventStep:
self,
make_organization_and_user_with_slack_identities,
make_slack_channel,
make_slack_message,
) -> None:
(
organization,
@ -92,6 +93,7 @@ class TestSlackChannelDeletedEventStep:
slack_user_identity,
) = make_organization_and_user_with_slack_identities()
slack_channel = make_slack_channel(slack_team_identity)
make_slack_message(slack_channel, organization=organization)
slack_channel_id = slack_channel.slack_id
# Ensure the SlackChannel exists
@ -100,6 +102,8 @@ class TestSlackChannelDeletedEventStep:
slack_team_identity=slack_team_identity,
).exists()
assert SlackMessage.objects.count() == 1
step = slack_channel_scenarios.SlackChannelDeletedEventStep(slack_team_identity, organization, user)
step.process_scenario(slack_user_identity, slack_team_identity, {"event": {"channel": slack_channel_id}})
@ -109,6 +113,9 @@ class TestSlackChannelDeletedEventStep:
slack_team_identity=slack_team_identity,
).exists()
# Slack messages should be cascade deleted when their channel is deleted
assert SlackMessage.objects.count() == 0
def test_process_scenario_channel_does_not_exist(
self,
make_organization_and_user_with_slack_identities,

View file

@ -190,7 +190,7 @@ class TestUpdateAlertGroupSlackMessageTask:
# Assert that SlackClient.chat_update was called with correct parameters
mock_chat_update.assert_called_once_with(
channel=slack_message._channel_id,
channel=slack_message.channel.slack_id,
ts=slack_message.slack_id,
attachments=alert_group.render_slack_attachments(),
blocks=alert_group.render_slack_blocks(),

View file

@ -516,13 +516,10 @@ class SlackEventApiEndpointView(APIView):
return None
try:
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p1732555465144099
slack_message = SlackMessage.objects.get(
slack_id=message_ts,
organization__slack_team_identity=slack_team_identity,
_channel_id=channel_id,
# channel__slack_id=channel_id,
channel__slack_id=channel_id,
)
except SlackMessage.DoesNotExist:
return None

View file

@ -528,12 +528,9 @@ def make_slack_user_identity():
@pytest.fixture
def make_slack_message():
def _make_slack_message(channel, alert_group=None, organization=None, **kwargs):
# TODO: once _channel_id has been fully migrated to channel, remove _channel_id
# see https://raintank-corp.slack.com/archives/C06K1MQ07GS/p1732555465144099
return SlackMessageFactory(
alert_group=alert_group,
organization=organization or alert_group.channel.organization,
_channel_id=channel.slack_id,
channel=channel,
**kwargs,
)

View file

@ -1,168 +0,0 @@
from django.core.management.base import BaseCommand
from django.db import transaction
from django.db.models import Q
from django.utils import timezone
from apps.alerts.models import AlertReceiveChannel
ALERTMANAGER = "alertmanager"
LEGACY_ALERTMANAGER = "legacy_alertmanager"
GRAFANA_ALERTING = "grafana_alerting"
LEGACY_GRAFANA_ALERTING = "legacy_grafana_alerting"
TEMPLATE_FIELDS = [
"web_title_template",
"web_message_template",
"web_image_url_template",
"sms_title_template",
"phone_call_title_template",
"source_link_template",
"grouping_id_template",
"resolve_condition_template",
"acknowledge_condition_template",
"slack_title_template",
"slack_message_template",
"slack_image_url_template",
"telegram_title_template",
"telegram_message_template",
"telegram_image_url_template",
"messaging_backends_templates",
]
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument("--backward", action="store_true", help="Run the migration backward.")
parser.add_argument(
"--org_id", type=int, help="Org id to perform the migration. " "If not present will migrate all."
)
def handle(self, *args, **options):
org_id = options.get("org_id", None)
if options["backward"]:
self.migrate_backward(org_id)
else:
self.migrate_forward(org_id)
@transaction.atomic
def migrate_forward(self, org_id=None):
now = timezone.now()
self.stdout.write(f"Forward migration started at {now}.")
self.stdout.write(
"Migrating legacy Alertmanager integrations "
"(updating fields 'integration' and 'alertmanager_v2_migrated_at')."
)
alertmanager_to_update = AlertReceiveChannel.objects.filter(integration=LEGACY_ALERTMANAGER)
if org_id:
alertmanager_to_update = alertmanager_to_update.filter(organization_id=org_id)
num_updated = alertmanager_to_update.update(integration=ALERTMANAGER, alertmanager_v2_migrated_at=now)
self.stdout.write(f"Migrated {num_updated} legacy Alertmanager integrations.")
self.stdout.write(
"Migrating legacy Grafana Alerting integrations "
"(updating fields 'integration' and 'alertmanager_v2_migrated_at')."
)
alerting_to_update = AlertReceiveChannel.objects.filter(integration=LEGACY_GRAFANA_ALERTING)
if org_id:
alerting_to_update = alerting_to_update.filter(organization_id=org_id)
num_updated = alerting_to_update.update(integration=GRAFANA_ALERTING, alertmanager_v2_migrated_at=now)
self.stdout.write(f"Migrated {num_updated} legacy Grafana Alerting integrations.")
self.stdout.write("Fetching integrations to back up & reset templates.")
alert_receive_channels = AlertReceiveChannel.objects.filter(
Q(
**{f"{field}__isnull": False for field in TEMPLATE_FIELDS},
_connector=Q.OR,
),
integration__in=[ALERTMANAGER, GRAFANA_ALERTING],
alertmanager_v2_migrated_at__isnull=False,
)
if org_id:
alert_receive_channels = alert_receive_channels.filter(organization_id=org_id)
self.stdout.write(f"Backing up & resetting templates for {len(alert_receive_channels)} integrations.")
for alert_receive_channel in alert_receive_channels:
self.stdout.write(
f"Backing up & resetting templates for integration {alert_receive_channel.public_primary_key}."
)
alert_receive_channel.alertmanager_v2_backup_templates = {
field: getattr(alert_receive_channel, field) for field in TEMPLATE_FIELDS
}
for field in TEMPLATE_FIELDS:
setattr(alert_receive_channel, field, None)
self.stdout.write(f"Bulk updating templates for {len(alert_receive_channels)} integrations.")
num_updated = AlertReceiveChannel.objects.bulk_update(
alert_receive_channels,
fields=[
*TEMPLATE_FIELDS,
"alertmanager_v2_backup_templates",
],
batch_size=1000,
)
self.stdout.write(f"Bulk updated templates for {num_updated} integrations.")
self.stdout.write("Forward migration finished.")
@transaction.atomic
def migrate_backward(self, org_id=None):
now = timezone.now()
self.stdout.write(f"Backward migration started at {now}.")
self.stdout.write(
"Backward migrating Alertmanager integrations "
"(updating fields 'integration' and 'alertmanager_v2_migrated_at')."
)
alertmanagers_to_restore = AlertReceiveChannel.objects.filter(
integration=ALERTMANAGER, alertmanager_v2_migrated_at__isnull=False
)
if org_id:
alertmanagers_to_restore = alertmanagers_to_restore.filter(organization_id=org_id)
num_updated = alertmanagers_to_restore.update(integration=LEGACY_ALERTMANAGER, alertmanager_v2_migrated_at=None)
self.stdout.write(f"Backward migrated {num_updated} Alertmanager integrations.")
self.stdout.write(
"Backward migrating Grafana Alerting integrations "
"(updating fields 'integration' and 'alertmanager_v2_migrated_at')."
)
alerting_to_restore = AlertReceiveChannel.objects.filter(
integration=GRAFANA_ALERTING, alertmanager_v2_migrated_at__isnull=False
)
if org_id:
alerting_to_restore = alerting_to_restore.filter(organization_id=org_id)
num_updated = alerting_to_restore.update(integration=LEGACY_GRAFANA_ALERTING, alertmanager_v2_migrated_at=None)
self.stdout.write(f"Backward migrated {num_updated} Grafana Alerting integrations.")
self.stdout.write("Fetching integrations to restore templates from backup.")
alert_receive_channels = AlertReceiveChannel.objects.filter(
integration__in=[LEGACY_ALERTMANAGER, LEGACY_GRAFANA_ALERTING],
alertmanager_v2_backup_templates__isnull=False,
)
if org_id:
alert_receive_channels = alert_receive_channels.filter(organization_id=org_id)
self.stdout.write(f"Restoring templates for {len(alert_receive_channels)} integrations.")
for alert_receive_channel in alert_receive_channels:
self.stdout.write(f"Restoring templates for integration {alert_receive_channel.public_primary_key}.")
if alert_receive_channel.alertmanager_v2_backup_templates is None:
continue
for field in TEMPLATE_FIELDS:
setattr(alert_receive_channel, field, alert_receive_channel.alertmanager_v2_backup_templates.get(field))
alert_receive_channel.alertmanager_v2_backup_templates = None
self.stdout.write(f"Bulk updating templates for {len(alert_receive_channels)} integrations.")
num_updated = AlertReceiveChannel.objects.bulk_update(
alert_receive_channels,
fields=[
*TEMPLATE_FIELDS,
"alertmanager_v2_backup_templates",
],
batch_size=1000,
)
self.stdout.write(f"Bulk updated templates for {num_updated} integrations.")
self.stdout.write("Backward migration finished.")

View file

@ -1,99 +0,0 @@
import time
from django.core.management.base import BaseCommand
from django.db import connection, transaction
from apps.slack.models import SlackChannel, SlackMessage
from apps.user_management.models import Organization
class Command(BaseCommand):
help = "Batch updates SlackMessage.channel_id in chunks to avoid locking the table."
def handle(self, *args, **options):
start_time = time.time()
self.stdout.write("Starting batch update of SlackMessage.channel_id...")
# Step 1: Determine the queryset to update
# qs is ordered by id to ensure consistent batching
# since id is indexed, this ordering operation "should" be more efficient (as opposed to say created_at
# which we don't have an index on)
qs = SlackMessage.objects.filter(
_channel_id__isnull=False, # old column
organization__isnull=False,
channel_id__isnull=True, # new column
).order_by("id")
total_records = qs.count()
if total_records == 0:
self.stdout.write("No records to update.")
return
self.stdout.write(f"Total records to update: {total_records}")
# some considerations here..
#
# Large IN clauses can be inefficient. Keep BATCH_SIZE reasonable (e.g., 1000)
# Fetching large batches of IDs consumes memory. With a BATCH_SIZE of 1000, this "should" be manageable
#
# references
# https://stackoverflow.com/a/5919165
BATCH_SIZE = 1000
total_batches = (total_records + BATCH_SIZE - 1) // BATCH_SIZE
self.stdout.write(f"Batch size: {BATCH_SIZE}")
self.stdout.write(f"Total batches: {total_batches}")
records_updated = 0
batch_number = 1
# Process updates in batches
while True:
# Get the next batch of IDs
batch_qs = qs[:BATCH_SIZE]
# collect the IDs to be updated
batch_ids = list(batch_qs.values_list("id", flat=True))
if not batch_ids:
break # No more records to process
placeholders = ", ".join(["%s"] * len(batch_ids))
update_query = f"""
UPDATE
{SlackMessage._meta.db_table} AS sm
INNER JOIN {Organization._meta.db_table} AS org
ON org.id = sm.organization_id
INNER JOIN {SlackChannel._meta.db_table} AS sc
ON sc.slack_id = sm._channel_id
AND sc.slack_team_identity_id = org.slack_team_identity_id
SET
sm.channel_id = sc.id
WHERE
sm.id IN ({placeholders})
"""
params = batch_ids
try:
# Execute the update
with transaction.atomic():
with connection.cursor() as cursor:
cursor.execute(update_query, params)
batch_records_updated = cursor.rowcount
records_updated += batch_records_updated
self.stdout.write(f"Batch {batch_number}/{total_batches}: Updated {batch_records_updated} records")
except Exception as e:
self.stderr.write(f"Error updating batch {batch_number}: {e}")
# Optionally, decide whether to continue or abort
continue
# Remove processed records from queryset for next batch
qs = qs.exclude(id__in=batch_ids)
batch_number += 1
end_time = time.time()
total_time = end_time - start_time
self.stdout.write(f"Batch update completed successfully. Total records updated: {records_updated}")
self.stdout.write(f"Total time taken: {total_time:.2f} seconds")