feat: convert organization.general_log_channel_id to organization.default_slack_channel (#5191)

# What this PR does

Related to https://github.com/grafana/oncall-private/issues/2947

Right now `general_log_channel_id` is just a string value representing
the Slack Channel ID (ex. `C043HQ70QMB`). This PR migrates this instead
to be a foreign key relationship on the `slack_slackchannel` table and
updates all references to `general_log_channel_id`.

Tested migrations locally:
```bash
Operations to perform:
  Apply all migrations: [redacted secret grafana-admin-creds:admin-user], alerts, auth, auth_token, base, contenttypes, email, exotel, fcm_django, google, heartbeat, labels, mobile_app, oss_installation, phone_notifications, schedules, sessions, slack, social_django, telegram, twilioapp, user_management, webhooks, zvonok
Running migrations:
  Applying user_management.0024_organization_general_log_slack_channel... OK
source=engine:app google_trace_id=none logger=apps.user_management.migrations.0025_auto_20241017_1919 Starting migration to populate general_log_slack_channel field.
source=engine:app google_trace_id=none logger=apps.user_management.migrations.0025_auto_20241017_1919 Total organizations to process: 1
source=engine:app google_trace_id=none logger=apps.user_management.migrations.0025_auto_20241017_1919 Organization 1 updated with SlackChannel 2 (slack_id: C043LL6RTS7).
source=engine:app google_trace_id=none logger=apps.user_management.migrations.0025_auto_20241017_1919 Finished migration. Total organizations processed: 1. Organizations updated: 1. Missing SlackChannels: 0.
  Applying user_management.0025_auto_20241017_1919... OK
```

## Future incoming PRs

- Drop `Organization.general_log_channel_id` column
- Migrate `ChannelFilter.slack_channel_id` and
`ResolutionNoteSlackMessage.slack_channel_id` to use foreign key
relationships

## 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-11-01 06:41:38 +01:00 committed by GitHub
parent 23aa7ebac2
commit e9969f4bd0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 228 additions and 90 deletions

View file

@ -617,7 +617,7 @@ class AlertReceiveChannel(IntegrationOptionsMixin, MaintainableObject):
def force_disable_maintenance(self, user):
disable_maintenance(alert_receive_channel_id=self.pk, force=True, user_id=user.pk)
def notify_about_maintenance_action(self, text, send_to_general_log_channel=True):
def notify_about_maintenance_action(self, text: str, send_to_general_log_channel=True) -> None:
# TODO: this method should be refactored.
# It's binded to slack and sending maintenance notification only there.
channel_ids = list(
@ -627,7 +627,7 @@ class AlertReceiveChannel(IntegrationOptionsMixin, MaintainableObject):
)
if send_to_general_log_channel:
general_log_channel_id = self.organization.general_log_channel_id
general_log_channel_id = self.organization.default_slack_channel_slack_id
if general_log_channel_id is not None:
channel_ids.append(general_log_channel_id)
unique_channels_id = set(channel_ids)

View file

@ -45,6 +45,7 @@ class ChannelFilter(OrderedModel):
"""
alert_groups: "RelatedManager['AlertGroup']"
alert_receive_channel: "AlertReceiveChannel"
filtering_labels: typing.Optional[list["LabelPair"]]
order_with_respect_to = ["alert_receive_channel_id", "is_default"]
@ -68,6 +69,14 @@ class ChannelFilter(OrderedModel):
notify_in_telegram = models.BooleanField(null=True, default=False)
slack_channel_id = models.CharField(max_length=100, null=True, default=None)
# TODO: migrate slack_channel_id to slack_channel
# slack_channel = models.ForeignKey(
# 'slack.SlackChannel',
# null=True,
# default=None,
# on_delete=models.SET_NULL,
# related_name='+',
# )
telegram_channel = models.ForeignKey(
"telegram.TelegramToOrganizationConnector",
@ -167,7 +176,7 @@ class ChannelFilter(OrderedModel):
if slack_team_identity is None:
return None
if self.slack_channel_id is None:
return organization.general_log_channel_id
return organization.default_slack_channel_slack_id
else:
return self.slack_channel_id

View file

@ -73,7 +73,17 @@ class ResolutionNoteSlackMessage(models.Model):
related_name="added_resolution_note_slack_messages",
)
text = models.TextField(max_length=3000, default=None, null=True)
slack_channel_id = models.CharField(max_length=100, null=True, default=None)
# TODO: migrate slack_channel_id to slack_channel
# slack_channel = models.ForeignKey(
# 'slack.SlackChannel',
# null=True,
# default=None,
# on_delete=models.SET_NULL,
# related_name='+',
# )
ts = models.CharField(max_length=100, null=True, default=None)
thread_ts = models.CharField(max_length=100, null=True, default=None)
permalink = models.CharField(max_length=250, null=True, default=None)

View file

@ -167,7 +167,7 @@ def test_send_demo_alert_not_enabled(mocked_create_alert, make_organization, mak
@pytest.mark.django_db
def test_notify_maintenance_no_general_channel(make_organization, make_alert_receive_channel):
organization = make_organization(general_log_channel_id=None)
organization = make_organization(default_slack_channel=None)
alert_receive_channel = make_alert_receive_channel(organization)
with patch("apps.alerts.models.alert_receive_channel.post_message_to_channel") as mock_post_message:
@ -177,21 +177,34 @@ def test_notify_maintenance_no_general_channel(make_organization, make_alert_rec
@pytest.mark.django_db
def test_notify_maintenance_with_general_channel(make_organization, make_alert_receive_channel):
organization = make_organization(general_log_channel_id="CHANNEL-ID")
def test_notify_maintenance_with_general_channel(
make_organization,
make_alert_receive_channel,
make_slack_team_identity,
make_slack_channel,
):
slack_channel = make_slack_channel(make_slack_team_identity())
organization = make_organization(default_slack_channel=slack_channel)
alert_receive_channel = make_alert_receive_channel(organization)
with patch("apps.alerts.models.alert_receive_channel.post_message_to_channel") as mock_post_message:
alert_receive_channel.notify_about_maintenance_action("maintenance mode enabled")
mock_post_message.assert_called_once_with(
organization, organization.general_log_channel_id, "maintenance mode enabled"
organization, organization.default_slack_channel.slack_id, "maintenance mode enabled"
)
@pytest.mark.django_db
def test_get_or_create_manual_integration_deleted_team(make_organization, make_team, make_alert_receive_channel):
organization = make_organization(general_log_channel_id="CHANNEL-ID")
def test_get_or_create_manual_integration_deleted_team(
make_organization,
make_team,
make_slack_team_identity,
make_slack_channel,
):
slack_channel = make_slack_channel(make_slack_team_identity())
organization = make_organization(default_slack_channel=slack_channel)
# setup general manual integration
general_manual = AlertReceiveChannel.get_or_create_manual_integration(
organization=organization, team=None, integration=AlertReceiveChannel.INTEGRATION_MANUAL, defaults={}

View file

@ -2,6 +2,7 @@ from dataclasses import asdict
from rest_framework import serializers
from apps.api.serializers.slack_channel import SlackChannelSerializer
from apps.base.messaging import get_messaging_backend_from_id
from apps.base.models import LiveSetting
from apps.phone_notifications.phone_provider import get_phone_provider
@ -21,7 +22,7 @@ class OrganizationSerializer(EagerLoadingMixin, serializers.ModelSerializer):
slack_team_identity = FastSlackTeamIdentitySerializer(read_only=True)
name = serializers.CharField(required=False, allow_null=True, allow_blank=True, source="org_title")
slack_channel = serializers.SerializerMethodField()
slack_channel = SlackChannelSerializer(read_only=True, allow_null=True, required=False)
rbac_enabled = serializers.BooleanField(read_only=True, source="is_rbac_permissions_enabled")
grafana_incident_enabled = serializers.BooleanField(read_only=True, source="is_grafana_incident_enabled")
@ -47,22 +48,6 @@ class OrganizationSerializer(EagerLoadingMixin, serializers.ModelSerializer):
"grafana_incident_enabled",
]
def get_slack_channel(self, obj):
from apps.slack.models import SlackChannel
if obj.general_log_channel_id is None or obj.slack_team_identity is None:
return None
try:
channel = obj.slack_team_identity.get_cached_channels().get(slack_id=obj.general_log_channel_id)
except SlackChannel.DoesNotExist:
return {"display_name": None, "slack_id": obj.general_log_channel_id, "id": None}
return {
"display_name": channel.name,
"slack_id": channel.slack_id,
"id": channel.public_primary_key,
}
class CurrentOrganizationSerializer(OrganizationSerializer):
env_status = serializers.SerializerMethodField()

View file

@ -20,7 +20,7 @@ from apps.api.permissions import LegacyAccessControlRole
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
],
)
def test_set_general_log_channel_permissions(
def test_set_org_default_slack_channel_permissions(
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
role,
@ -29,8 +29,10 @@ def test_set_general_log_channel_permissions(
_, user, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
url = reverse("api-internal:api-set-general-log-channel")
with patch("apps.api.views.organization.SetGeneralChannel.post", return_value=Response(status=status.HTTP_200_OK)):
url = reverse("api-internal:set-default-slack-channel")
with patch(
"apps.api.views.organization.SetDefaultSlackChannel.post", return_value=Response(status=status.HTTP_200_OK)
):
response = client.post(url, format="json", **make_user_auth_headers(user, token))
assert response.status_code == expected_status

View file

@ -22,7 +22,7 @@ from .views.organization import (
GetChannelVerificationCode,
GetTelegramVerificationCode,
OrganizationConfigChecksView,
SetGeneralChannel,
SetDefaultSlackChannel,
)
from .views.preview_template_options import PreviewTemplateOptionsView
from .views.public_api_tokens import PublicApiTokenView
@ -71,7 +71,7 @@ router.register(r"shift_swaps", ShiftSwapViewSet, basename="shift_swap")
urlpatterns = [
path("", include(router.urls)),
optional_slash_path("user", CurrentUserView.as_view(), name="api-user"),
optional_slash_path("set_general_channel", SetGeneralChannel.as_view(), name="api-set-general-log-channel"),
optional_slash_path("set_general_channel", SetDefaultSlackChannel.as_view(), name="set-default-slack-channel"),
optional_slash_path("organization", CurrentOrganizationView.as_view(), name="api-organization"),
optional_slash_path(
"organization/config-checks",

View file

@ -108,7 +108,7 @@ class GetChannelVerificationCode(APIView):
return Response(code)
class SetGeneralChannel(APIView):
class SetDefaultSlackChannel(APIView):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, RBACPermission)
@ -127,6 +127,6 @@ class SetGeneralChannel(APIView):
public_primary_key=slack_channel_id, slack_team_identity=slack_team_identity
)
organization.set_general_log_channel(slack_channel.slack_id, slack_channel.name, request.user)
organization.set_default_slack_channel(slack_channel, request.user)
return Response(status=200)

View file

@ -169,9 +169,17 @@ def notify_about_integration_ratelimit_in_slack(organization_id, text, **kwargs)
else:
cache.set(cache_key, True, 60 * 15) # Set cache before sending message to make sure we don't ratelimit slack
slack_team_identity = organization.slack_team_identity
if slack_team_identity is not None:
org_default_slack_channel_id = organization.default_slack_channel_slack_id
if slack_team_identity is not None and org_default_slack_channel_id is not None:
try:
sc = SlackClient(slack_team_identity, enable_ratelimit_retry=True)
sc.chat_postMessage(channel=organization.general_log_channel_id, text=text)
sc.chat_postMessage(channel=org_default_slack_channel_id, text=text)
except SlackAPIError as e:
logger.warning(f"Slack exception {e} while sending message for organization {organization_id}")
else:
logger.info(
f"Slack team identity or general log channel is not set for organization {organization_id} "
f"skipping rest of notify_about_integration_ratelimit_in_slack "
f"slack_team_identity={slack_team_identity} org_default_slack_channel_id={org_default_slack_channel_id}"
)

View file

@ -19,10 +19,13 @@ from apps.user_management.models import Organization, User
if typing.TYPE_CHECKING:
from django.db.models.manager import RelatedManager
from apps.slack.models import SlackChannel
logger = logging.getLogger(__name__)
class SlackTeamIdentity(models.Model):
cached_channels: "RelatedManager['SlackChannel']"
organizations: "RelatedManager[Organization]"
id = models.AutoField(primary_key=True)

View file

@ -71,7 +71,7 @@ class AlertShootingStep(scenario_step.ScenarioStep):
alert.group.channel_filter.slack_channel_id_or_general_log_id
if alert.group.channel_filter
# if channel filter is deleted mid escalation, use default Slack channel
else alert.group.channel.organization.general_log_channel_id
else alert.group.channel.organization.default_slack_channel_slack_id
)
self._send_first_alert(alert, channel_id)
except (SlackAPIError, TimeoutError):

View file

@ -198,8 +198,8 @@ def unpopulate_slack_user_identities(organization_pk, force=False, ts=None):
if force:
organization.slack_team_identity = None
organization.general_log_channel_id = None
organization.save(update_fields=["slack_team_identity", "general_log_channel_id"])
organization.default_slack_channel = None
organization.save(update_fields=["slack_team_identity", "default_slack_channel"])
@shared_dedicated_queue_retry_task(autoretry_for=(Exception,), retry_backoff=True, max_retries=0)
@ -555,11 +555,14 @@ def clean_slack_integration_leftovers(organization_id, *args, **kwargs):
@shared_dedicated_queue_retry_task(autoretry_for=(Exception,), retry_backoff=True, max_retries=10)
def clean_slack_channel_leftovers(slack_team_identity_id, slack_channel_id):
"""
This task removes binding to slack channel after channel arcived or deleted in slack.
TODO: once we add/migrate to ChannelFilter.slack_channel, this will mean that we no longer need this task
and it can be safely removed (foreign key relationships to a slack channel that is deleted in the db will
automatically be set to None due to on_delete=models.SET_NULL)
This task removes binding to slack channel after channel archived or deleted in slack.
"""
from apps.alerts.models import ChannelFilter
from apps.slack.models import SlackTeamIdentity
from apps.user_management.models import Organization
try:
sti = SlackTeamIdentity.objects.get(id=slack_team_identity_id)
@ -569,16 +572,7 @@ def clean_slack_channel_leftovers(slack_team_identity_id, slack_channel_id):
)
return
orgs_to_clean_general_log_channel_id = []
for org in sti.organizations.all():
if org.general_log_channel_id == slack_channel_id:
logger.info(
f"Set general_log_channel_id to None for org_id={org.id} slack_channel_id={slack_channel_id} since slack_channel is arcived or deleted"
)
org.general_log_channel_id = None
orgs_to_clean_general_log_channel_id.append(org)
ChannelFilter.objects.filter(alert_receive_channel__organization=org, slack_channel_id=slack_channel_id).update(
slack_channel_id=None
)
Organization.objects.bulk_update(orgs_to_clean_general_log_channel_id, ["general_log_channel_id"], batch_size=5000)

View file

@ -69,10 +69,19 @@ def test_clean_slack_integration_leftovers(
@pytest.mark.django_db
def test_unpopulate_slack_user_identities(
make_organization_and_user_with_slack_identities, make_user_with_slack_user_identity
make_slack_team_identity,
make_slack_channel,
make_organization,
make_user_for_organization,
make_user_with_slack_user_identity,
):
# create organization and user with Slack connected
organization, user, slack_team_identity, _ = make_organization_and_user_with_slack_identities()
slack_team_identity = make_slack_team_identity()
slack_channel = make_slack_channel(slack_team_identity)
organization = make_organization(slack_team_identity=slack_team_identity, default_slack_channel=slack_channel)
user = make_user_for_organization(organization)
assert organization.default_slack_channel_slack_id is not None
# create & delete user with Slack connected
deleted_user, _ = make_user_with_slack_user_identity(slack_team_identity, organization)
@ -90,4 +99,4 @@ def test_unpopulate_slack_user_identities(
# check that Slack specific info is reset for organization
assert organization.slack_team_identity is None
assert organization.general_log_channel_id is None
assert organization.default_slack_channel_slack_id is None

View file

@ -55,6 +55,7 @@ def test_skip_escalations_error(
@pytest.mark.django_db
def test_timeout_error(
make_slack_team_identity,
make_slack_channel,
make_organization,
make_alert_receive_channel,
make_alert_group,
@ -62,9 +63,8 @@ def test_timeout_error(
):
SlackAlertShootingStep = ScenarioStep.get_step("distribute_alerts", "AlertShootingStep")
slack_team_identity = make_slack_team_identity()
organization = make_organization(
slack_team_identity=slack_team_identity, general_log_channel_id="DEFAULT_CHANNEL_ID"
)
slack_channel = make_slack_channel(slack_team_identity)
organization = make_organization(slack_team_identity=slack_team_identity, default_slack_channel=slack_channel)
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
alert = make_alert(alert_group, raw_request_data="{}")
@ -89,15 +89,15 @@ def test_timeout_error(
def test_alert_shooting_no_channel_filter(
mock_post_alert_group_to_slack,
make_slack_team_identity,
make_slack_channel,
make_organization,
make_alert_receive_channel,
make_alert_group,
make_alert,
):
slack_team_identity = make_slack_team_identity()
organization = make_organization(
slack_team_identity=slack_team_identity, general_log_channel_id="DEFAULT_CHANNEL_ID"
)
slack_channel = make_slack_channel(slack_team_identity, slack_id="DEFAULT_CHANNEL_ID")
organization = make_organization(slack_team_identity=slack_team_identity, default_slack_channel=slack_channel)
alert_receive_channel = make_alert_receive_channel(organization)
# simulate an alert group with channel filter deleted in the middle of the escalation

View file

@ -0,0 +1,20 @@
# Generated by Django 4.2.16 on 2024-10-17 19:07
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('slack', '0005_slackteamidentity__unified_slack_app_installed'),
('user_management', '0024_organization_direct_paging_prefer_important_policy'),
]
operations = [
migrations.AddField(
model_name='organization',
name='default_slack_channel',
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='slack.slackchannel'),
),
]

View file

@ -0,0 +1,65 @@
# Generated by Django 4.2.15 on 2024-10-17 19:19
import logging
from django.db import migrations
import django_migration_linter as linter
logger = logging.getLogger(__name__)
def populate_default_slack_channel(apps, schema_editor):
Organization = apps.get_model("user_management", "Organization")
SlackChannel = apps.get_model("slack", "SlackChannel")
logger.info("Starting migration to populate default_slack_channel field.")
queryset = Organization.objects.filter(general_log_channel_id__isnull=False, slack_team_identity__isnull=False)
total_orgs = queryset.count()
updated_orgs = 0
missing_channels = 0
organizations_to_update = []
logger.info(f"Total organizations to process: {total_orgs}")
for org in queryset:
slack_id = org.general_log_channel_id
slack_team_identity = org.slack_team_identity
try:
slack_channel = SlackChannel.objects.get(slack_id=slack_id, slack_team_identity=slack_team_identity)
org.default_slack_channel = slack_channel
organizations_to_update.append(org)
updated_orgs += 1
logger.info(
f"Organization {org.id} updated with SlackChannel {slack_channel.id} (slack_id: {slack_id})."
)
except SlackChannel.DoesNotExist:
missing_channels += 1
logger.warning(
f"SlackChannel with slack_id {slack_id} and slack_team_identity {slack_team_identity} "
f"does not exist for Organization {org.id}."
)
if organizations_to_update:
Organization.objects.bulk_update(organizations_to_update, ["default_slack_channel"])
logger.info(f"Bulk updated {len(organizations_to_update)} organizations with their default Slack channel.")
logger.info(
f"Finished migration. Total organizations processed: {total_orgs}. "
f"Organizations updated: {updated_orgs}. Missing SlackChannels: {missing_channels}."
)
class Migration(migrations.Migration):
dependencies = [
("user_management", "0025_organization_default_slack_channel"),
]
operations = [
# simply setting this new field is okay, we are not deleting the value of general_log_channel_id
# therefore, no need to revert it
linter.IgnoreMigration(),
migrations.RunPython(populate_default_slack_channel, migrations.RunPython.noop),
]

View file

@ -34,7 +34,7 @@ if typing.TYPE_CHECKING:
)
from apps.mobile_app.models import MobileAppAuthToken
from apps.schedules.models import CustomOnCallShift, OnCallSchedule
from apps.slack.models import SlackTeamIdentity
from apps.slack.models import SlackChannel, SlackTeamIdentity
from apps.telegram.models import TelegramToOrganizationConnector
from apps.user_management.models import Region, Team, User
@ -89,6 +89,7 @@ class Organization(MaintainableObject):
alert_receive_channels: "RelatedManager['AlertReceiveChannel']"
auth_tokens: "RelatedManager['ApiAuthToken']"
custom_on_call_shifts: "RelatedManager['CustomOnCallShift']"
default_slack_channel: typing.Optional["SlackChannel"]
migration_destination: typing.Optional["Region"]
mobile_app_auth_tokens: "RelatedManager['MobileAppAuthToken']"
oncall_schedules: "RelatedManager['OnCallSchedule']"
@ -103,25 +104,6 @@ class Organization(MaintainableObject):
objects: models.Manager["Organization"] = OrganizationManager()
objects_with_deleted = models.Manager()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.subscription_strategy = self._get_subscription_strategy()
def delete(self):
if settings.FEATURE_MULTIREGION_ENABLED:
unregister_oncall_tenant(str(self.uuid), settings.ONCALL_BACKEND_REGION)
if self.slack_team_identity and not settings.UNIFIED_SLACK_APP_ENABLED:
unlink_slack_team(str(self.uuid), self.slack_team_identity.slack_id)
self.deleted_at = timezone.now()
self.save(update_fields=["deleted_at"])
def hard_delete(self):
super().delete()
def _get_subscription_strategy(self):
if self.pricing_version == self.FREE_PUBLIC_BETA_PRICING:
return FreePublicBetaSubscriptionStrategy(self)
public_primary_key = models.CharField(
max_length=20,
validators=[MinLengthValidator(settings.PUBLIC_PRIMARY_KEY_MIN_LENGTH + 1)],
@ -181,8 +163,15 @@ class Organization(MaintainableObject):
"slack.SlackTeamIdentity", on_delete=models.PROTECT, null=True, default=None, related_name="organizations"
)
# Slack specific field with general log channel id
# TODO: drop this field in a subsequent release, this has been migrated to default_slack_channel field
general_log_channel_id = models.CharField(max_length=100, null=True, default=None)
default_slack_channel = models.ForeignKey(
"slack.SlackChannel",
null=True,
default=None,
on_delete=models.SET_NULL,
related_name="+",
)
# uuid used to unuqie identify organization in different clusters
uuid = models.UUIDField(default=uuid.uuid4, editable=False)
@ -264,6 +253,25 @@ class Organization(MaintainableObject):
class Meta:
unique_together = ("stack_id", "org_id")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.subscription_strategy = self._get_subscription_strategy()
def delete(self):
if settings.FEATURE_MULTIREGION_ENABLED:
unregister_oncall_tenant(str(self.uuid), settings.ONCALL_BACKEND_REGION)
if self.slack_team_identity and not settings.UNIFIED_SLACK_APP_ENABLED:
unlink_slack_team(str(self.uuid), self.slack_team_identity.slack_id)
self.deleted_at = timezone.now()
self.save(update_fields=["deleted_at"])
def hard_delete(self):
super().delete()
def _get_subscription_strategy(self):
if self.pricing_version == self.FREE_PUBLIC_BETA_PRICING:
return FreePublicBetaSubscriptionStrategy(self)
def provision_plugin(self) -> ProvisionedPlugin:
from apps.auth_token.models import PluginAuthToken
@ -301,20 +309,20 @@ class Organization(MaintainableObject):
self.alert_group_table_columns = columns
self.save(update_fields=["alert_group_table_columns"])
def set_general_log_channel(self, channel_id, channel_name, user):
if self.general_log_channel_id != channel_id:
old_general_log_channel_id = self.slack_team_identity.cached_channels.filter(
slack_id=self.general_log_channel_id
).first()
old_channel_name = old_general_log_channel_id.name if old_general_log_channel_id else None
self.general_log_channel_id = channel_id
self.save(update_fields=["general_log_channel_id"])
def set_default_slack_channel(self, slack_channel: "SlackChannel", user: "User") -> None:
if self.default_slack_channel != slack_channel:
old_default_slack_channel = self.default_slack_channel
old_channel_name = old_default_slack_channel.name if old_default_slack_channel else None
self.default_slack_channel = slack_channel
self.save(update_fields=["default_slack_channel"])
write_chatops_insight_log(
author=user,
event_name=ChatOpsEvent.DEFAULT_CHANNEL_CHANGED,
chatops_type=ChatOpsTypePlug.SLACK.value,
prev_channel=old_channel_name,
new_channel=channel_name,
new_channel=slack_channel.name,
)
def get_notifiable_direct_paging_integrations(self) -> "RelatedManager['AlertReceiveChannel']":
@ -348,6 +356,10 @@ class Organization(MaintainableObject):
.distinct()
)
@property
def default_slack_channel_slack_id(self) -> typing.Optional[str]:
return self.default_slack_channel.slack_id if self.default_slack_channel else None
@property
def web_link_with_uuid(self):
"""

View file

@ -1,9 +1,13 @@
import enum
import json
import logging
import typing
from .insight_logs_enabled_check import is_insight_logs_enabled
if typing.TYPE_CHECKING:
from apps.user_management.models import User
insight_logger = logging.getLogger("insight_logger")
logger = logging.getLogger(__name__)
@ -24,7 +28,7 @@ class ChatOpsTypePlug(enum.Enum):
TELEGRAM = "telegram"
def write_chatops_insight_log(author, event_name: ChatOpsEvent, chatops_type: str, **kwargs):
def write_chatops_insight_log(author: "User", event_name: ChatOpsEvent, chatops_type: str, **kwargs):
try:
organization = author.organization

View file

@ -1,11 +1,15 @@
import logging
import typing
from django.conf import settings
if typing.TYPE_CHECKING:
from apps.user_management.models import Organization
logger = logging.getLogger(__name__)
def is_insight_logs_enabled(organization):
def is_insight_logs_enabled(organization: "Organization") -> bool:
"""
is_insight_logs_enabled checks if inside logs enabled for given organization.
Now it checks if oncall is deployed on same cluster that its grafana instance to be able to forward logs