From 4af9510244966e39535acea3475ec721db056035 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Tue, 11 Jul 2023 15:44:06 +0200 Subject: [PATCH 01/11] wip --- ...resolve_alert_group_by_source_if_needed.py | 2 +- engine/apps/api/serializers/organization.py | 23 ----------- engine/apps/api/tests/test_subscription.py | 39 ------------------- engine/apps/api/urls.py | 2 - engine/apps/api/views/subscription.py | 16 -------- .../user_management/models/organization.py | 4 +- .../base_subsription_strategy.py | 4 -- grafana-plugin/src/models/team.ts | 4 -- grafana-plugin/src/models/team/team.types.ts | 38 ------------------ 9 files changed, 4 insertions(+), 128 deletions(-) delete mode 100644 engine/apps/api/tests/test_subscription.py delete mode 100644 engine/apps/api/views/subscription.py delete mode 100644 grafana-plugin/src/models/team.ts diff --git a/engine/apps/alerts/tasks/resolve_alert_group_by_source_if_needed.py b/engine/apps/alerts/tasks/resolve_alert_group_by_source_if_needed.py index ade38de96c..fc4040a2aa 100644 --- a/engine/apps/alerts/tasks/resolve_alert_group_by_source_if_needed.py +++ b/engine/apps/alerts/tasks/resolve_alert_group_by_source_if_needed.py @@ -31,7 +31,7 @@ def resolve_alert_group_by_source_if_needed(alert_group_pk): alert_group.save(update_fields=["resolved_by"]) if alert_group.resolved_by == alert_group.NOT_YET_STOP_AUTORESOLVE: return "alert_group is too big to auto-resolve" - print("YOLO") + last_alert = AlertForAlertManager.objects.get(pk=alert_group.alerts.last().pk) if alert_group.is_alert_a_resolve_signal(last_alert): alert_group.resolve_by_source() diff --git a/engine/apps/api/serializers/organization.py b/engine/apps/api/serializers/organization.py index 742c280fb2..ee065d572b 100644 --- a/engine/apps/api/serializers/organization.py +++ b/engine/apps/api/serializers/organization.py @@ -1,7 +1,6 @@ import datetime from dataclasses import asdict -import humanize import pytz from django.apps import apps from django.utils import timezone @@ -53,8 +52,6 @@ class Meta: "slack_team_identity", "maintenance_mode", "maintenance_till", - # "incident_retention_web_report", - # "number_of_employees", "slack_channel", ] read_only_fields = [ @@ -62,7 +59,6 @@ class Meta: "slack_team_identity", "maintenance_mode", "maintenance_till", - # "incident_retention_web_report", ] def get_slack_channel(self, obj): @@ -122,28 +118,9 @@ def get_env_status(self, obj): phone_provider_config = get_phone_provider().flags return { "telegram_configured": telegram_configured, - "twilio_configured": phone_provider_config.configured, # keep for backward compatibility "phone_provider": asdict(phone_provider_config), } - def get_stats(self, obj): - if isinstance(obj.cached_seconds_saved_by_amixr, int): - verbal_time_saved_by_amixr = humanize.naturaldelta( - datetime.timedelta(seconds=obj.cached_seconds_saved_by_amixr) - ) - else: - verbal_time_saved_by_amixr = None - - result = { - "grouped_percent": obj.cached_grouped_percent, - "alerts_count": obj.cached_alerts_count, - "noise_reduction": obj.cached_noise_reduction, - "average_response_time": humanize.naturaldelta(obj.cached_average_response_time), - "verbal_time_saved_by_amixr": verbal_time_saved_by_amixr, - } - - return result - def update(self, instance, validated_data): current_archive_date = instance.archive_alerts_from archive_alerts_from = validated_data.get("archive_alerts_from") diff --git a/engine/apps/api/tests/test_subscription.py b/engine/apps/api/tests/test_subscription.py deleted file mode 100644 index 2753784bef..0000000000 --- a/engine/apps/api/tests/test_subscription.py +++ /dev/null @@ -1,39 +0,0 @@ -from unittest.mock import patch - -import pytest -from django.urls import reverse -from rest_framework import status -from rest_framework.response import Response -from rest_framework.test import APIClient - -from apps.api.permissions import LegacyAccessControlRole - - -@pytest.mark.django_db -@pytest.mark.parametrize( - "role,expected_status", - [ - (LegacyAccessControlRole.ADMIN, status.HTTP_200_OK), - (LegacyAccessControlRole.EDITOR, status.HTTP_200_OK), - (LegacyAccessControlRole.VIEWER, status.HTTP_200_OK), - ], -) -def test_subscription_retrieve_permissions( - make_organization_and_user_with_plugin_token, - make_user_auth_headers, - role, - expected_status, -): - _, user, token = make_organization_and_user_with_plugin_token(role) - client = APIClient() - - url = reverse("api-internal:subscription") - with patch( - "apps.api.views.subscription.SubscriptionView.get", - return_value=Response( - status=status.HTTP_200_OK, - ), - ): - response = client.get(url, format="json", **make_user_auth_headers(user, token)) - - assert response.status_code == expected_status diff --git a/engine/apps/api/urls.py b/engine/apps/api/urls.py index 02f20f6239..ffd52356ba 100644 --- a/engine/apps/api/urls.py +++ b/engine/apps/api/urls.py @@ -35,7 +35,6 @@ SlackTeamSettingsAPIView, UnAcknowledgeTimeoutOptionsAPIView, ) -from .views.subscription import SubscriptionView from .views.team import TeamViewSet from .views.telegram_channels import TelegramChannelViewSet from .views.user import CurrentUserView, UserView @@ -83,7 +82,6 @@ GetChannelVerificationCode.as_view(), name="api-get-channel-verification-code", ), - optional_slash_path("current_subscription", SubscriptionView.as_view(), name="subscription"), optional_slash_path("terraform_file", TerraformGitOpsView.as_view(), name="terraform_file"), optional_slash_path("terraform_imports", TerraformStateView.as_view(), name="terraform_imports"), optional_slash_path("maintenance", MaintenanceAPIView.as_view(), name="maintenance"), diff --git a/engine/apps/api/views/subscription.py b/engine/apps/api/views/subscription.py deleted file mode 100644 index 47b0e5f042..0000000000 --- a/engine/apps/api/views/subscription.py +++ /dev/null @@ -1,16 +0,0 @@ -from rest_framework.permissions import IsAuthenticated -from rest_framework.response import Response -from rest_framework.views import APIView - -from apps.auth_token.auth import PluginAuthentication - - -class SubscriptionView(APIView): - authentication_classes = (PluginAuthentication,) - permission_classes = (IsAuthenticated,) - - def get(self, request): - raise NotImplementedError - organization = self.request.auth.organization - user = self.request.user - return Response(organization.get_subscription_web_report_for_user(user)) diff --git a/engine/apps/user_management/models/organization.py b/engine/apps/user_management/models/organization.py index 0afbbd1c60..e40959d343 100644 --- a/engine/apps/user_management/models/organization.py +++ b/engine/apps/user_management/models/organization.py @@ -8,6 +8,7 @@ from django.core.validators import MinLengthValidator from django.db import models from django.utils import timezone +from django_deprecate_fields import deprecate_field from mirage import fields as mirage_fields from apps.alerts.models import MaintainableObject @@ -237,7 +238,8 @@ def _get_subscription_strategy(self): PRICING_CHOICES = ((FREE_PUBLIC_BETA_PRICING, "Free public beta"),) pricing_version = models.PositiveIntegerField(choices=PRICING_CHOICES, default=FREE_PUBLIC_BETA_PRICING) - is_amixr_migration_started = models.BooleanField(default=False) + # TODO: remove is_amixr_migration_started soon (aka shortly after July 11, 2023) + is_amixr_migration_started = deprecate_field(models.BooleanField(default=False)) is_rbac_permissions_enabled = models.BooleanField(default=False) is_grafana_incident_enabled = models.BooleanField(default=False) diff --git a/engine/apps/user_management/subscription_strategy/base_subsription_strategy.py b/engine/apps/user_management/subscription_strategy/base_subsription_strategy.py index 5d7c61dac4..029d63106d 100644 --- a/engine/apps/user_management/subscription_strategy/base_subsription_strategy.py +++ b/engine/apps/user_management/subscription_strategy/base_subsription_strategy.py @@ -5,10 +5,6 @@ class BaseSubscriptionStrategy(ABC): def __init__(self, organization): self.organization = organization - @abstractmethod - def notifications_limit_web_report(self, user): - raise NotImplementedError - @abstractmethod def phone_calls_left(self, user): raise NotImplementedError diff --git a/grafana-plugin/src/models/team.ts b/grafana-plugin/src/models/team.ts deleted file mode 100644 index 776ed378ce..0000000000 --- a/grafana-plugin/src/models/team.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface TeamDTO { - is_free_version: boolean; - cached_name: string; -} diff --git a/grafana-plugin/src/models/team/team.types.ts b/grafana-plugin/src/models/team/team.types.ts index 9fdc1df625..ca0b11c15c 100644 --- a/grafana-plugin/src/models/team/team.types.ts +++ b/grafana-plugin/src/models/team/team.types.ts @@ -1,27 +1,7 @@ import { SlackChannel } from 'models/slack_channel/slack_channel.types'; -export enum SubscriptionStatus { - OK, - VIOLATION, - HARD_VIOLATION, -} - -export interface Limit { - left: number; - limit_title: string; - total: number; -} - export interface Team { pk: string; - is_free_version: boolean; - limits: { - period_title: string; - show_limits_popup: boolean; - limits_to_show: Limit[]; - show_limits_warning: boolean; - warning_text: string; - }; banner: { title: string; body: string; @@ -42,29 +22,11 @@ export interface Team { slack_channel: SlackChannel | null; - number_of_employees: number; - - subscription_status: SubscriptionStatus; - - stats: { - alerts_count: number; - average_response_time: string; - grouped_percent: number; - noise_reduction: number; - verbal_time_saved_by_amixr: string; - }; - - incident_retention_web_report: { - num_month_available: number; - incidents_hidden: number; - } | null; - // ex team settings archive_alerts_from: string; is_resolution_note_required: boolean; env_status: { - twilio_configured: boolean; telegram_configured: boolean; phone_provider: { configured: boolean; From 1db4ef34834482660b7580ff5932c41d3c0141a1 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Tue, 11 Jul 2023 17:29:36 +0200 Subject: [PATCH 02/11] more cleanup --- engine/apps/alerts/models/alert_group.py | 42 +---- engine/apps/api/serializers/organization.py | 45 +---- .../slack/scenarios/alertgroup_appearance.py | 7 +- .../apps/slack/scenarios/distribute_alerts.py | 141 ++++---------- .../apps/slack/scenarios/resolution_note.py | 12 +- engine/apps/slack/scenarios/step_mixins.py | 12 -- engine/apps/slack/tasks.py | 177 ------------------ .../test_alert_group_actions.py | 13 -- .../test_resolution_note.py | 1 - .../user_management/models/organization.py | 12 +- .../free_public_beta_subscription_strategy.py | 19 -- engine/settings/prod_without_db.py | 4 - grafana-plugin/src/models/team/team.ts | 90 --------- grafana-plugin/src/models/team/team.types.ts | 2 - grafana-plugin/src/utils/url.ts | 12 -- 15 files changed, 50 insertions(+), 539 deletions(-) diff --git a/engine/apps/alerts/models/alert_group.py b/engine/apps/alerts/models/alert_group.py index a9552849f5..220f797d8f 100644 --- a/engine/apps/alerts/models/alert_group.py +++ b/engine/apps/alerts/models/alert_group.py @@ -163,7 +163,7 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models. (USER, "user"), (NOT_YET, "not yet"), (LAST_STEP, "last escalation step"), - (ARCHIVED, "archived"), + (ARCHIVED, "archived"), # deprecated. don't use (WIPED, "wiped"), (DISABLE_MAINTENANCE, "stop maintenance"), (NOT_YET_STOP_AUTORESOLVE, "not yet, autoresolve disabled"), @@ -327,9 +327,8 @@ def status(self): related_name="dependent_alert_groups", ) - # cached_render_for_web and active_cache_for_web_calculation_id are deprecated - cached_render_for_web = models.JSONField(default=dict) - active_cache_for_web_calculation_id = models.CharField(max_length=100, null=True, default=None) + # TODO: remove cached_render_for_web in a subsequent release + cached_render_for_web = deprecate_field(models.JSONField(default=dict)) # NOTE: we should probably migrate this field to models.UUIDField as it's ONLY ever being # set to the result of uuid.uuid1 @@ -361,9 +360,6 @@ def status(self): raw_escalation_snapshot = JSONField(null=True, default=None) - # THIS FIELD IS DEPRECATED AND SHOULD EVENTUALLY BE REMOVED - estimate_escalation_finish_time = deprecate_field(models.DateTimeField(null=True, default=None)) - # This field is used for constraints so we can use get_or_create() in concurrent calls # https://docs.djangoproject.com/en/3.2/ref/models/querysets/#get-or-create # Combined with unique_together below, it allows only one alert group with @@ -715,38 +711,6 @@ def resolve_by_source(self): for dependent_alert_group in self.dependent_alert_groups.all(): dependent_alert_group.resolve_by_source() - # deprecated - def resolve_by_archivation(self): - AlertGroupLogRecord = apps.get_model("alerts", "AlertGroupLogRecord") - # if incident was silenced, unsilence it without starting escalation - if self.silenced: - self.un_silence() - self.log_records.create( - type=AlertGroupLogRecord.TYPE_UN_SILENCE, - silence_delay=None, - reason="Resolve by archivation", - ) - self.archive() - self.stop_escalation() - if not self.resolved: - self.resolve(resolved_by=AlertGroup.ARCHIVED) - - log_record = self.log_records.create(type=AlertGroupLogRecord.TYPE_RESOLVED) - - logger.debug( - f"send alert_group_action_triggered_signal for alert_group {self.pk}, " - f"log record {log_record.pk} with type '{log_record.get_type_display()}', action source: archivation" - ) - - alert_group_action_triggered_signal.send( - sender=self.resolve_by_archivation, - log_record=log_record.pk, - action_source=None, - ) - - for dependent_alert_group in self.dependent_alert_groups.all(): - dependent_alert_group.resolve_by_archivation() - def resolve_by_last_step(self): AlertGroupLogRecord = apps.get_model("alerts", "AlertGroupLogRecord") initial_state = self.state diff --git a/engine/apps/api/serializers/organization.py b/engine/apps/api/serializers/organization.py index ee065d572b..da345d622a 100644 --- a/engine/apps/api/serializers/organization.py +++ b/engine/apps/api/serializers/organization.py @@ -1,30 +1,15 @@ -import datetime from dataclasses import asdict -import pytz from django.apps import apps -from django.utils import timezone -from rest_framework import fields, serializers +from rest_framework import serializers from apps.base.models import LiveSetting from apps.phone_notifications.phone_provider import get_phone_provider from apps.slack.models import SlackTeamIdentity -from apps.slack.tasks import resolve_archived_incidents_for_organization, unarchive_incidents_for_organization from apps.user_management.models import Organization from common.api_helpers.mixins import EagerLoadingMixin -class CustomDateField(fields.TimeField): - def to_internal_value(self, data): - try: - archive_datetime = datetime.datetime.fromisoformat(data).astimezone(pytz.UTC) - except (TypeError, ValueError): - raise serializers.ValidationError({"archive_alerts_from": ["Invalid date format"]}) - if archive_datetime.date() >= timezone.now().date(): - raise serializers.ValidationError({"archive_alerts_from": ["Invalid date. Date must be less than today."]}) - return archive_datetime - - class FastSlackTeamIdentitySerializer(serializers.ModelSerializer): class Meta: model = SlackTeamIdentity @@ -36,7 +21,6 @@ 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") - # name_slug = serializers.CharField(required=False, allow_null=True, allow_blank=False) maintenance_till = serializers.ReadOnlyField(source="till_maintenance_timestamp") slack_channel = serializers.SerializerMethodField() @@ -47,15 +31,12 @@ class Meta: fields = [ "pk", "name", - # "name_slug", - # "is_new_version", "slack_team_identity", "maintenance_mode", "maintenance_till", "slack_channel", ] read_only_fields = [ - "is_new_version", "slack_team_identity", "maintenance_mode", "maintenance_till", @@ -78,22 +59,18 @@ def get_slack_channel(self, obj): class CurrentOrganizationSerializer(OrganizationSerializer): - limits = serializers.SerializerMethodField() env_status = serializers.SerializerMethodField() banner = serializers.SerializerMethodField() class Meta(OrganizationSerializer.Meta): fields = [ *OrganizationSerializer.Meta.fields, - "limits", - "archive_alerts_from", "is_resolution_note_required", "env_status", "banner", ] read_only_fields = [ *OrganizationSerializer.Meta.read_only_fields, - "limits", "banner", ] @@ -105,10 +82,6 @@ def get_banner(self, obj): )[0] return banner.json_value - def get_limits(self, obj): - user = self.context["request"].user - return obj.notifications_limit_web_report(user) - def get_env_status(self, obj): # deprecated in favour of ConfigAPIView. # All new env statuses should be added there @@ -121,22 +94,6 @@ def get_env_status(self, obj): "phone_provider": asdict(phone_provider_config), } - def update(self, instance, validated_data): - current_archive_date = instance.archive_alerts_from - archive_alerts_from = validated_data.get("archive_alerts_from") - - result = super().update(instance, validated_data) - if archive_alerts_from is not None and current_archive_date != archive_alerts_from: - if current_archive_date > archive_alerts_from: - unarchive_incidents_for_organization.apply_async( - (instance.pk,), - ) - resolve_archived_incidents_for_organization.apply_async( - (instance.pk,), - ) - - return result - class FastOrganizationSerializer(serializers.ModelSerializer): pk = serializers.CharField(read_only=True, source="public_primary_key") diff --git a/engine/apps/slack/scenarios/alertgroup_appearance.py b/engine/apps/slack/scenarios/alertgroup_appearance.py index dc1ea8a2c1..befcfcbf2d 100644 --- a/engine/apps/slack/scenarios/alertgroup_appearance.py +++ b/engine/apps/slack/scenarios/alertgroup_appearance.py @@ -5,10 +5,10 @@ from apps.api.permissions import RBACPermission from apps.slack.scenarios import scenario_step -from .step_mixins import AlertGroupActionsMixin, CheckAlertIsUnarchivedMixin +from .step_mixins import AlertGroupActionsMixin -class OpenAlertAppearanceDialogStep(CheckAlertIsUnarchivedMixin, AlertGroupActionsMixin, scenario_step.ScenarioStep): +class OpenAlertAppearanceDialogStep(AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] def process_scenario(self, slack_user_identity, slack_team_identity, payload): @@ -17,9 +17,6 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): self.open_unauthorized_warning(payload) return - if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): - return - private_metadata = { "organization_id": self.organization.pk if self.organization else alert_group.organization.pk, "alert_group_pk": alert_group.pk, diff --git a/engine/apps/slack/scenarios/distribute_alerts.py b/engine/apps/slack/scenarios/distribute_alerts.py index 0c0b1f798d..b07b05984d 100644 --- a/engine/apps/slack/scenarios/distribute_alerts.py +++ b/engine/apps/slack/scenarios/distribute_alerts.py @@ -35,7 +35,7 @@ from apps.slack.utils import get_cache_key_update_incident_slack_message from common.utils import clean_markup, is_string_with_visible_characters -from .step_mixins import AlertGroupActionsMixin, CheckAlertIsUnarchivedMixin +from .step_mixins import AlertGroupActionsMixin ATTACH_TO_ALERT_GROUPS_LIMIT = 20 @@ -216,11 +216,7 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): pass -class InviteOtherPersonToIncident( - CheckAlertIsUnarchivedMixin, - AlertGroupActionsMixin, - scenario_step.ScenarioStep, -): +class InviteOtherPersonToIncident(AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] def process_scenario(self, slack_user_identity, slack_team_identity, payload): @@ -233,9 +229,6 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): selected_user = None - if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): - return - try: # user selection selected_user_id = json.loads(payload["actions"][0]["selected_option"]["value"])["user_id"] @@ -255,11 +248,7 @@ def process_signal(self, log_record): self.alert_group_slack_service.update_alert_group_slack_message(alert_group) -class SilenceGroupStep( - CheckAlertIsUnarchivedMixin, - AlertGroupActionsMixin, - scenario_step.ScenarioStep, -): +class SilenceGroupStep(AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] def process_scenario(self, slack_user_identity, slack_team_identity, payload): @@ -275,19 +264,14 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): # Deprecated handler kept for backward compatibility (so older Slack messages can still be processed) silence_delay = int(value) - if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): - alert_group.silence_by_user(self.user, silence_delay, action_source=ActionSource.SLACK) + alert_group.silence_by_user(self.user, silence_delay, action_source=ActionSource.SLACK) def process_signal(self, log_record): alert_group = log_record.alert_group self.alert_group_slack_service.update_alert_group_slack_message(alert_group) -class UnSilenceGroupStep( - CheckAlertIsUnarchivedMixin, - AlertGroupActionsMixin, - scenario_step.ScenarioStep, -): +class UnSilenceGroupStep(AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] def process_scenario(self, slack_user_identity, slack_team_identity, payload): @@ -296,19 +280,14 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): self.open_unauthorized_warning(payload) return - if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): - alert_group.un_silence_by_user(self.user, action_source=ActionSource.SLACK) + alert_group.un_silence_by_user(self.user, action_source=ActionSource.SLACK) def process_signal(self, log_record): alert_group = log_record.alert_group self.alert_group_slack_service.update_alert_group_slack_message(alert_group) -class SelectAttachGroupStep( - CheckAlertIsUnarchivedMixin, - AlertGroupActionsMixin, - scenario_step.ScenarioStep, -): +class SelectAttachGroupStep(AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] def process_scenario(self, slack_user_identity, slack_team_identity, payload): @@ -317,9 +296,6 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): self.open_unauthorized_warning(payload) return - if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): - return - blocks = [] view = { "callback_id": AttachGroupStep.routing_uid(), @@ -455,11 +431,7 @@ def get_select_incidents_blocks(self, alert_group): return blocks -class AttachGroupStep( - CheckAlertIsUnarchivedMixin, - AlertGroupActionsMixin, - scenario_step.ScenarioStep, -): +class AttachGroupStep(AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [] # Permissions are handled in SelectAttachGroupStep def process_signal(self, log_record): @@ -502,19 +474,11 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): root_alert_group = AlertGroup.all_objects.get(pk=root_alert_group_pk) alert_group = self.get_alert_group(slack_team_identity, payload) - if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group) and self.check_alert_is_unarchived( - slack_team_identity, payload, root_alert_group - ): - alert_group.attach_by_user(self.user, root_alert_group, action_source=ActionSource.SLACK) - else: - self.alert_group_slack_service.update_alert_group_slack_message(alert_group) + + alert_group.attach_by_user(self.user, root_alert_group, action_source=ActionSource.SLACK) -class UnAttachGroupStep( - CheckAlertIsUnarchivedMixin, - AlertGroupActionsMixin, - scenario_step.ScenarioStep, -): +class UnAttachGroupStep(AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] def process_scenario(self, slack_user_identity, slack_team_identity, payload): @@ -523,15 +487,14 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): self.open_unauthorized_warning(payload) return - if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): - alert_group.un_attach_by_user(self.user, action_source=ActionSource.SLACK) + alert_group.un_attach_by_user(self.user, action_source=ActionSource.SLACK) def process_signal(self, log_record): alert_group = log_record.alert_group self.alert_group_slack_service.update_alert_group_slack_message(alert_group) -class StopInvitationProcess(CheckAlertIsUnarchivedMixin, AlertGroupActionsMixin, scenario_step.ScenarioStep): +class StopInvitationProcess(AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] def process_scenario(self, slack_user_identity, slack_team_identity, payload): @@ -540,9 +503,6 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): self.open_unauthorized_warning(payload) return - if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): - return - try: value = json.loads(payload["actions"][0]["value"]) invitation_id = value["invitation_id"] @@ -556,11 +516,7 @@ def process_signal(self, log_record): self.alert_group_slack_service.update_alert_group_slack_message(log_record.invitation.alert_group) -class CustomButtonProcessStep( - CheckAlertIsUnarchivedMixin, - AlertGroupActionsMixin, - scenario_step.ScenarioStep, -): +class CustomButtonProcessStep(AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] def process_scenario(self, slack_user_identity, slack_team_identity, payload): @@ -570,23 +526,22 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): self.open_unauthorized_warning(payload) return - if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): - custom_button_pk = payload["actions"][0]["name"].split("_")[1] - alert_group_pk = payload["actions"][0]["name"].split("_")[2] - try: - CustomButtom.objects.get(pk=custom_button_pk) - except CustomButtom.DoesNotExist: - warning_text = "Oops! This button was deleted" - self.open_warning_window(payload, warning_text=warning_text) - self.alert_group_slack_service.update_alert_group_slack_message(alert_group) - else: - custom_button_result.apply_async( - args=( - custom_button_pk, - alert_group_pk, - ), - kwargs={"user_pk": self.user.pk}, - ) + custom_button_pk = payload["actions"][0]["name"].split("_")[1] + alert_group_pk = payload["actions"][0]["name"].split("_")[2] + try: + CustomButtom.objects.get(pk=custom_button_pk) + except CustomButtom.DoesNotExist: + warning_text = "Oops! This button was deleted" + self.open_warning_window(payload, warning_text=warning_text) + self.alert_group_slack_service.update_alert_group_slack_message(alert_group) + else: + custom_button_result.apply_async( + args=( + custom_button_pk, + alert_group_pk, + ), + kwargs={"user_pk": self.user.pk}, + ) def process_signal(self, log_record): alert_group = log_record.alert_group @@ -618,11 +573,7 @@ def process_signal(self, log_record): ) -class ResolveGroupStep( - CheckAlertIsUnarchivedMixin, - AlertGroupActionsMixin, - scenario_step.ScenarioStep, -): +class ResolveGroupStep(AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] def process_scenario(self, slack_user_identity, slack_team_identity, payload): @@ -633,9 +584,6 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): self.open_unauthorized_warning(payload) return - if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): - return - if alert_group.is_maintenance_incident: alert_group.stop_maintenance(self.user) else: @@ -661,11 +609,7 @@ def process_signal(self, log_record): self.alert_group_slack_service.update_alert_group_slack_message(alert_group) -class UnResolveGroupStep( - CheckAlertIsUnarchivedMixin, - AlertGroupActionsMixin, - scenario_step.ScenarioStep, -): +class UnResolveGroupStep(AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] def process_scenario(self, slack_user_identity, slack_team_identity, payload): @@ -674,19 +618,14 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): self.open_unauthorized_warning(payload) return - if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): - alert_group.un_resolve_by_user(self.user, action_source=ActionSource.SLACK) + alert_group.un_resolve_by_user(self.user, action_source=ActionSource.SLACK) def process_signal(self, log_record): alert_group = log_record.alert_group self.alert_group_slack_service.update_alert_group_slack_message(alert_group) -class AcknowledgeGroupStep( - CheckAlertIsUnarchivedMixin, - AlertGroupActionsMixin, - scenario_step.ScenarioStep, -): +class AcknowledgeGroupStep(AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] def process_scenario(self, slack_user_identity, slack_team_identity, payload): @@ -695,19 +634,14 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): self.open_unauthorized_warning(payload) return - if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): - alert_group.acknowledge_by_user(self.user, action_source=ActionSource.SLACK) + alert_group.acknowledge_by_user(self.user, action_source=ActionSource.SLACK) def process_signal(self, log_record): alert_group = log_record.alert_group self.alert_group_slack_service.update_alert_group_slack_message(alert_group) -class UnAcknowledgeGroupStep( - CheckAlertIsUnarchivedMixin, - AlertGroupActionsMixin, - scenario_step.ScenarioStep, -): +class UnAcknowledgeGroupStep(AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] def process_scenario(self, slack_user_identity, slack_team_identity, payload): @@ -716,8 +650,7 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): self.open_unauthorized_warning(payload) return - if self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): - alert_group.un_acknowledge_by_user(self.user, action_source=ActionSource.SLACK) + alert_group.un_acknowledge_by_user(self.user, action_source=ActionSource.SLACK) def process_signal(self, log_record): AlertGroupLogRecord = apps.get_model("alerts", "AlertGroupLogRecord") diff --git a/engine/apps/slack/scenarios/resolution_note.py b/engine/apps/slack/scenarios/resolution_note.py index d3e7622212..13de30604f 100644 --- a/engine/apps/slack/scenarios/resolution_note.py +++ b/engine/apps/slack/scenarios/resolution_note.py @@ -11,13 +11,13 @@ from apps.user_management.models import User from common.api_helpers.utils import create_engine_url -from .step_mixins import AlertGroupActionsMixin, CheckAlertIsUnarchivedMixin +from .step_mixins import AlertGroupActionsMixin logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) -class AddToResolutionNoteStep(CheckAlertIsUnarchivedMixin, scenario_step.ScenarioStep): +class AddToResolutionNoteStep(scenario_step.ScenarioStep): callback_id = [ "add_resolution_note", "add_resolution_note_staging", @@ -61,9 +61,6 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload): ) raise e - if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): - return - if payload["message"]["type"] == "message" and "user" in payload["message"]: message_ts = payload["message_ts"] thread_ts = payload["message"]["thread_ts"] @@ -373,7 +370,7 @@ def get_resolution_note_blocks(self, resolution_note): return blocks -class ResolutionNoteModalStep(CheckAlertIsUnarchivedMixin, AlertGroupActionsMixin, scenario_step.ScenarioStep): +class ResolutionNoteModalStep(AlertGroupActionsMixin, scenario_step.ScenarioStep): REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE] RESOLUTION_NOTE_TEXT_BLOCK_ID = "resolution_note_text" RESOLUTION_NOTE_MESSAGES_MAX_COUNT = 25 @@ -396,9 +393,6 @@ def process_scenario(self, slack_user_identity, slack_team_identity, payload, da action_resolve = value.get("action_resolve", False) channel_id = payload["channel"]["id"] if "channel" in payload else None - if not self.check_alert_is_unarchived(slack_team_identity, payload, alert_group): - return - blocks = [] if channel_id: diff --git a/engine/apps/slack/scenarios/step_mixins.py b/engine/apps/slack/scenarios/step_mixins.py index 0f04be998e..428c6ce5f2 100644 --- a/engine/apps/slack/scenarios/step_mixins.py +++ b/engine/apps/slack/scenarios/step_mixins.py @@ -160,15 +160,3 @@ def _get_alert_group_from_slack_message_in_db( channel_id=channel_id, ) return slack_message.get_alert_group() - - -class CheckAlertIsUnarchivedMixin: - def check_alert_is_unarchived(self, slack_team_identity, payload, alert_group, warning=True): - alert_group_is_unarchived = alert_group.started_at.date() > self.organization.archive_alerts_from - if not alert_group_is_unarchived: - if warning: - warning_text = "Action is impossible: the Alert is archived." - self.open_warning_window(payload, warning_text) - if not alert_group.resolved or not alert_group.is_archived: - alert_group.resolve_by_archivation() - return alert_group_is_unarchived diff --git a/engine/apps/slack/tasks.py b/engine/apps/slack/tasks.py index 07fb2969f7..df90e16179 100644 --- a/engine/apps/slack/tasks.py +++ b/engine/apps/slack/tasks.py @@ -1,6 +1,5 @@ import logging import random -import time from celery.utils.log import get_task_logger from django.apps import apps @@ -135,91 +134,6 @@ def check_slack_message_exists_before_post_message_to_thread( ).save() -@shared_dedicated_queue_retry_task(autoretry_for=(Exception,), retry_backoff=True) -def resolve_archived_incidents_for_organization(organization_id): - Organization = apps.get_model("user_management", "Organization") - AlertGroup = apps.get_model("alerts", "AlertGroup") - - organization = Organization.objects.get(pk=organization_id) - - alert_groups_queryset = AlertGroup.unarchived_objects.filter( - channel__organization=organization, - started_at__date__lte=organization.archive_alerts_from, - resolved=False, - ) - - for alert_group in alert_groups_queryset: - try: - alert_group.resolve_by_archivation() - except SlackAPIException as e: - if e.response["error"] == "channel_not_found": # Todo: investigate and remove this hack - print(e) - elif e.response["error"] == "rate_limited" or e.response["error"] == "ratelimited": - if "headers" in e.response and e.response["headers"].get("Retry-After") is not None: - delay = int(e.response["headers"]["Retry-After"]) - else: - delay = random.randint(1, 10) - resolve_archived_incidents_for_organization.apply_async((organization_id,), countdown=delay) - else: - raise e - - -@shared_dedicated_queue_retry_task(autoretry_for=(Exception,), retry_backoff=True) -def unarchive_incidents_for_organization(organization_id): - Organization = apps.get_model("user_management", "Organization") - AlertGroup = apps.get_model("alerts", "AlertGroup") - SlackMessage = apps.get_model("slack", "SlackMessage") - - organization = Organization.objects.get(pk=organization_id) - - alert_groups_queryset = AlertGroup.all_objects.filter( - channel__organization=organization, - started_at__date__gt=organization.archive_alerts_from, - is_archived=True, - ) - # convert qs to list to prevent it from changing after qs update - alert_groups_with_slack_message = list( - alert_groups_queryset.select_related("slack_message").filter(slack_message__isnull=False) - ) - - alert_groups_queryset.update(is_archived=False) - slack_team_identity = organization.slack_team_identity - if slack_team_identity is not None: - sc = SlackClientWithErrorHandling(slack_team_identity.bot_access_token) - slack_messages_to_create = [] - - for alert_group_with_slack_message in alert_groups_with_slack_message: - try: - result = sc.api_call( - "chat.postMessage", - channel=alert_group_with_slack_message.slack_message.channel_id, - thread_ts=alert_group_with_slack_message.slack_message.slack_id, - text="Incident has been unarchived", - ) - except SlackAPIException as e: - if e.response["error"] == "channel_not_found": - print(e) - elif e.response["error"] == "rate_limited" or e.response["error"] == "ratelimited": - if "headers" in e.response and e.response["headers"].get("Retry-After") is not None: - delay = int(e.response["headers"]["Retry-After"]) - else: - delay = random.randint(1, 10) - time.sleep(delay) - else: - raise e - else: - slack_message = SlackMessage( - slack_id=result["ts"], - organization=organization, - _slack_team_identity=slack_team_identity, - channel_id=alert_group_with_slack_message.slack_message.channel_id, - alert_group_id=alert_group_with_slack_message.pk, - ) - slack_messages_to_create.append(slack_message) - - SlackMessage.objects.bulk_create(slack_messages_to_create, batch_size=5000) - - @shared_dedicated_queue_retry_task(autoretry_for=(Exception,), retry_backoff=True, max_retries=1) def send_message_to_thread_if_bot_not_in_channel(alert_group_pk, slack_team_identity_pk, channel_id): """ @@ -241,43 +155,6 @@ def send_message_to_thread_if_bot_not_in_channel(alert_group_pk, slack_team_iden AlertGroupSlackService(slack_team_identity, sc).publish_message_to_alert_group_thread(alert_group, text=text) -@shared_dedicated_queue_retry_task(autoretry_for=(Exception,), retry_backoff=True, max_retries=1) -def send_debug_message_to_thread(alert_group_pk, slack_team_identity_pk): - AlertGroup = apps.get_model("alerts", "AlertGroup") - SlackTeamIdentity = apps.get_model("slack", "SlackTeamIdentity") - SlackMessage = apps.get_model("slack", "SlackMessage") - - slack_team_identity = SlackTeamIdentity.objects.get(pk=slack_team_identity_pk) - current_alert_group = AlertGroup.all_objects.get(pk=alert_group_pk) - try: - channel_id = current_alert_group.slack_message.channel_id - except AttributeError: - print("SlackMessage object doesn't exist for the alert group") - return None - - blocks = [] - text = "Escalations are silenced due to Debug mode" - blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": text}}) - sc = SlackClientWithErrorHandling(slack_team_identity.bot_access_token) - - result = sc.api_call( - "chat.postMessage", - channel=channel_id, - text=text, - attachments=[], - thread_ts=current_alert_group.slack_message.slack_id, - mrkdwn=True, - blocks=blocks, - ) - SlackMessage( - slack_id=result["ts"], - organization=current_alert_group.channel.organization, - _slack_team_identity=slack_team_identity, - channel_id=channel_id, - alert_group=current_alert_group, - ).save() - - @shared_dedicated_queue_retry_task(autoretry_for=(Exception,), retry_backoff=True, max_retries=0) def unpopulate_slack_user_identities(organization_pk, force=False, ts=None): User = apps.get_model("user_management", "User") @@ -381,60 +258,6 @@ def populate_slack_user_identities(organization_pk): SlackUserIdentity.objects.bulk_update(slack_user_identities_to_update, fields_to_update, batch_size=5000) -@shared_dedicated_queue_retry_task() -def refresh_slack_user_identity_emails(): - SlackUserIdentity = apps.get_model("slack", "SlackUserIdentity") - - qs = ( - SlackUserIdentity.all_objects.filter(cached_slack_email="") - .exclude(deleted=True) - .exclude(cached_is_bot=True) - .exclude( - cached_name="user_not_found", - ) - .exclude(slack_team_identity__cached_name="no_enough_permissions_to_retrieve") - .exclude(slack_team_identity__detected_token_revoked__isnull=False) - ) - - total = qs.count() - for index, slack_user_identity in enumerate(qs, start=1): - try: - sc = SlackClientWithErrorHandling(slack_user_identity.slack_team_identity.bot_access_token) - result = sc.api_call("users.info", user=slack_user_identity.slack_id) - - if "email" in result.get("user").get("profile", None): - slack_user_identity.cached_slack_email = result["user"]["profile"]["email"] - slack_user_identity.save(update_fields=["cached_slack_email"]) - logger.info(f"({index}/{total}). Email is found") - elif result.get("user").get("is_bot") is True or result.get("user").get("id") == SLACK_BOT_ID: - slack_user_identity.cached_is_bot = True - slack_user_identity.save(update_fields=["cached_is_bot"]) - logger.info(f"({index}/{total}). Bot is found") - elif result.get("user").get("deleted") is True: - slack_user_identity.deleted = True - slack_user_identity.save(update_fields=["deleted"]) - logger.info(f"({index}/{total}). Deleted is found") - elif result.get("user").get("is_stranger", False): - # case: strangers or external members, - # see https://api.slack.com/enterprise/shared-channels - slack_user_identity.is_stranger = True - slack_user_identity.save(update_fields=["is_stranger"]) - logger.info(f"({index}/{total}). Stranger or external user detected.") - else: - logger.error( - f"({index}/{total}). Error!!! Email definition error for SlackUserIdentity pk: " - f"{slack_user_identity.pk}. It will be generated unknown_email." - ) - except SlackAPIException as e: - # case: user_not_found - if e.response["error"] == "user_not_found": - slack_user_identity.is_not_found = True - slack_user_identity.save(update_fields=["is_not_found"]) - logger.info(f"({index}/{total}). User_not_found detected.") - else: - logger.error(f"({index}/{total}). Error!!! Exception: {e}") - - @shared_dedicated_queue_retry_task( autoretry_for=(Exception,), retry_backoff=True, max_retries=1 if settings.DEBUG else None ) diff --git a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py index 38f9b01210..9f91aac358 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_alert_group_actions.py @@ -90,7 +90,6 @@ def test_alert_group_actions_unauthorized( organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities( role=LegacyAccessControlRole.VIEWER ) - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from alert_receive_channel = make_alert_receive_channel(organization) alert_group = make_alert_group(alert_receive_channel) @@ -262,7 +261,6 @@ def test_step_acknowledge( slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from user = make_user(organization=organization, slack_user_identity=slack_user_identity) alert_receive_channel = make_alert_receive_channel(organization) @@ -312,7 +310,6 @@ def test_step_unacknowledge( slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from user = make_user(organization=organization, slack_user_identity=slack_user_identity) alert_receive_channel = make_alert_receive_channel(organization) @@ -362,7 +359,6 @@ def test_step_resolve( slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from user = make_user(organization=organization, slack_user_identity=slack_user_identity) alert_receive_channel = make_alert_receive_channel(organization) @@ -412,7 +408,6 @@ def test_step_unresolve( slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from user = make_user(organization=organization, slack_user_identity=slack_user_identity) alert_receive_channel = make_alert_receive_channel(organization) @@ -467,7 +462,6 @@ def test_step_invite( slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from user = make_user(organization=organization, slack_user_identity=slack_user_identity) second_user = make_user(organization=organization, pk=USER_ID) @@ -529,7 +523,6 @@ def test_step_stop_invite( slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from user = make_user(organization=organization, slack_user_identity=slack_user_identity) second_user = make_user(organization=organization, pk=USER_ID) @@ -587,7 +580,6 @@ def test_step_silence( slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from user = make_user(organization=organization, slack_user_identity=slack_user_identity) alert_receive_channel = make_alert_receive_channel(organization) @@ -642,7 +634,6 @@ def test_step_unsilence( slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from user = make_user(organization=organization, slack_user_identity=slack_user_identity) alert_receive_channel = make_alert_receive_channel(organization) @@ -686,7 +677,6 @@ def test_step_select_attach( slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from user = make_user(organization=organization, slack_user_identity=slack_user_identity) alert_receive_channel = make_alert_receive_channel(organization) @@ -743,7 +733,6 @@ def test_step_unattach( slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from user = make_user(organization=organization, slack_user_identity=slack_user_identity) alert_receive_channel = make_alert_receive_channel(organization) @@ -800,7 +789,6 @@ def test_step_format_alert( slack_channel = make_slack_channel(slack_team_identity, slack_id=SLACK_CHANNEL_ID) organization = make_organization(pk=ORGANIZATION_ID, slack_team_identity=slack_team_identity) - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from user = make_user(organization=organization, slack_user_identity=slack_user_identity) alert_receive_channel = make_alert_receive_channel(organization) @@ -826,7 +814,6 @@ def test_step_resolution_note( make_organization_and_user_with_slack_identities, make_alert_receive_channel, make_alert_group, make_alert ): organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from alert_receive_channel = make_alert_receive_channel(organization) alert_group = make_alert_group(alert_receive_channel) diff --git a/engine/apps/slack/tests/test_scenario_steps/test_resolution_note.py b/engine/apps/slack/tests/test_scenario_steps/test_resolution_note.py index cc463b01d1..1e31a8f247 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_resolution_note.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_resolution_note.py @@ -198,7 +198,6 @@ def test_resolution_notes_modal_closed_before_update( ResolutionNoteModalStep = ScenarioStep.get_step("resolution_note", "ResolutionNoteModalStep") organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() - organization.refresh_from_db() # without this there's something weird with organization.archive_alerts_from alert_receive_channel = make_alert_receive_channel(organization) alert_group = make_alert_group(alert_receive_channel) diff --git a/engine/apps/user_management/models/organization.py b/engine/apps/user_management/models/organization.py index e40959d343..a0693f6d7b 100644 --- a/engine/apps/user_management/models/organization.py +++ b/engine/apps/user_management/models/organization.py @@ -161,8 +161,6 @@ def _get_subscription_strategy(self): is_resolution_note_required = models.BooleanField(default=False) - archive_alerts_from = models.DateField(default="1970-01-01") - # TODO: this field is specific to slack and will be moved to a different model slack_team_identity = models.ForeignKey( "slack.SlackTeamIdentity", on_delete=models.PROTECT, null=True, default=None, related_name="organizations" @@ -238,8 +236,10 @@ def _get_subscription_strategy(self): PRICING_CHOICES = ((FREE_PUBLIC_BETA_PRICING, "Free public beta"),) pricing_version = models.PositiveIntegerField(choices=PRICING_CHOICES, default=FREE_PUBLIC_BETA_PRICING) - # TODO: remove is_amixr_migration_started soon (aka shortly after July 11, 2023) + # TODO: remove is_amixr_migration_started and archive_alerts_from soon (aka shortly after July 11, 2023) is_amixr_migration_started = deprecate_field(models.BooleanField(default=False)) + archive_alerts_from = deprecate_field(models.DateField(default="1970-01-01")) + is_rbac_permissions_enabled = models.BooleanField(default=False) is_grafana_incident_enabled = models.BooleanField(default=False) @@ -295,7 +295,7 @@ def notify_about_maintenance_action(self, text, send_to_general_log_channel=True """ Following methods: - phone_calls_left, sms_left, emails_left, notifications_limit_web_report + phone_calls_left, sms_left, emails_left serve for calculating notifications' limits and composed from self.subscription_strategy. """ @@ -309,9 +309,6 @@ def sms_left(self, user): def emails_left(self, user): return self.subscription_strategy.emails_left(user) - def notifications_limit_web_report(self, user): - return self.subscription_strategy.notifications_limit_web_report(user) - 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( @@ -354,7 +351,6 @@ def insight_logs_serialized(self): return { "name": self.org_title, "is_resolution_note_required": self.is_resolution_note_required, - "archive_alerts_from": self.archive_alerts_from.isoformat(), } @property diff --git a/engine/apps/user_management/subscription_strategy/free_public_beta_subscription_strategy.py b/engine/apps/user_management/subscription_strategy/free_public_beta_subscription_strategy.py index 116a985f4d..1b469ebf25 100644 --- a/engine/apps/user_management/subscription_strategy/free_public_beta_subscription_strategy.py +++ b/engine/apps/user_management/subscription_strategy/free_public_beta_subscription_strategy.py @@ -31,25 +31,6 @@ def emails_left(self, user): ).count() return self._emails_limit - emails_today - def notifications_limit_web_report(self, user): - limits_to_show = [] - left = self._calculate_phone_notifications_left(user) - limit = self._phone_notifications_limit - limits_to_show.append({"limit_title": "Phone Calls & SMS", "total": limit, "left": left}) - show_limits_warning = left <= limit * 0.2 # Show limit popup if less than 20% of notifications left - - warning_text = ( - f"You{'' if left == 0 else ' almost'} have exceeded the limit of phone calls and sms:" - f" {left} of {limit} left." - ) - - return { - "period_title": "Daily limit", - "limits_to_show": limits_to_show, - "show_limits_warning": show_limits_warning, - "warning_text": warning_text, - } - def _calculate_phone_notifications_left(self, user): """ Count sms and calls together and they have common limit. diff --git a/engine/settings/prod_without_db.py b/engine/settings/prod_without_db.py index 355e81d2b8..877ffdf362 100644 --- a/engine/settings/prod_without_db.py +++ b/engine/settings/prod_without_db.py @@ -135,12 +135,8 @@ def on_uwsgi_worker_exit(): "apps.slack.tasks.populate_slack_usergroups_for_team": {"queue": "slack"}, "apps.slack.tasks.post_or_update_log_report_message_task": {"queue": "slack"}, "apps.slack.tasks.post_slack_rate_limit_message": {"queue": "slack"}, - "apps.slack.tasks.refresh_slack_user_identity_emails": {"queue": "slack"}, - "apps.slack.tasks.resolve_archived_incidents_for_organization": {"queue": "slack"}, - "apps.slack.tasks.send_debug_message_to_thread": {"queue": "slack"}, "apps.slack.tasks.send_message_to_thread_if_bot_not_in_channel": {"queue": "slack"}, "apps.slack.tasks.start_update_slack_user_group_for_schedules": {"queue": "slack"}, - "apps.slack.tasks.unarchive_incidents_for_organization": {"queue": "slack"}, "apps.slack.tasks.unpopulate_slack_user_identities": {"queue": "slack"}, "apps.slack.tasks.update_incident_slack_message": {"queue": "slack"}, "apps.slack.tasks.update_slack_user_group_for_schedules": {"queue": "slack"}, diff --git a/grafana-plugin/src/models/team/team.ts b/grafana-plugin/src/models/team/team.ts index 1f01add54c..22abcbac45 100644 --- a/grafana-plugin/src/models/team/team.ts +++ b/grafana-plugin/src/models/team/team.ts @@ -2,10 +2,7 @@ import { action, observable } from 'mobx'; import BaseStore from 'models/base_store'; import { makeRequest } from 'network'; -import { Mixpanel } from 'services/mixpanel'; import { RootStore } from 'state'; -import { openErrorNotification } from 'utils'; -import { getPathnameByTeamNameSlug } from 'utils/url'; import { Team } from './team.types'; @@ -30,38 +27,6 @@ export class TeamStore extends BaseStore { this.currentTeam = await makeRequest('/current_team/', {}); } - @action - async setCurrentTeam(teamId: Team['pk']) { - this.redirectingToProperTeam = true; - - const team = await makeRequest(`/current_team/`, { - method: 'POST', - data: { team_id: teamId }, - }); - - const pathName = getPathnameByTeamNameSlug(team.name_slug); - - window.location.pathname = pathName; - } - - @action - async addTeam(data: Partial) { - let createdTeam; - try { - createdTeam = await makeRequest('/teams/', { - method: 'POST', - data, - }); - } catch (e) { - openErrorNotification(e.response.data); - return; - } - - this.setCurrentTeam(createdTeam.pk); - - Mixpanel.track('Add Team', null); - } - @action async saveCurrentTeam(data: any) { this.currentTeam = await makeRequest('/current_team/', { @@ -69,59 +34,4 @@ export class TeamStore extends BaseStore { data, }); } - - @action - async justSaveCurrentTeam(data: any) { - return await makeRequest('/current_team/', { - method: 'PUT', - data, - }); - } - - @action - async getTelegramVerificationCode(pk: Team['pk']) { - const response = await makeRequest(`/teams/${pk}/get_telegram_verification_code/`, { - withCredentials: true, - }); - - return response; - } - - @action - async unlinkTelegram(pk: Team['pk']) { - const response = await makeRequest(`/teams/${pk}/unlink_telegram/`, { - method: 'POST', - withCredentials: true, - }); - - return response; - } - - @action - async getInvitationLink() { - const response = await makeRequest('/invitation_link/', { - withCredentials: true, - }); - - return response; - } - - @action - async joinToTeam(invitation_token: string) { - const response = await makeRequest('/join_to_team/', { - method: 'POST', - params: { invitation_token, token: invitation_token }, - withCredentials: true, - }); - - return response; - } - - @action - async updateTeam(_teamId: Team['pk']) { - await makeRequest(this.path, { - params: {}, - withCredentials: true, - }); - } } diff --git a/grafana-plugin/src/models/team/team.types.ts b/grafana-plugin/src/models/team/team.types.ts index ca0b11c15c..315a53ae4a 100644 --- a/grafana-plugin/src/models/team/team.types.ts +++ b/grafana-plugin/src/models/team/team.types.ts @@ -13,7 +13,6 @@ export interface Team { discussion_group_name: string; }; name: string; - name_slug: string; slack_team_identity: { general_log_channel_id: string; general_log_channel_pk: string; @@ -23,7 +22,6 @@ export interface Team { slack_channel: SlackChannel | null; // ex team settings - archive_alerts_from: string; is_resolution_note_required: boolean; env_status: { diff --git a/grafana-plugin/src/utils/url.ts b/grafana-plugin/src/utils/url.ts index f286f99688..01e7cbaec4 100644 --- a/grafana-plugin/src/utils/url.ts +++ b/grafana-plugin/src/utils/url.ts @@ -2,18 +2,6 @@ import qs, { ParsedQuery } from 'query-string'; import { PLUGIN_ROOT } from './consts'; -export function getTeamNameSlugFromUrl(): string | undefined { - const teamName = window.location.pathname.split('/')[2]; - return teamName === 'admin' || teamName === 'auth' ? undefined : teamName; -} - -export function getPathnameByTeamNameSlug(teamNameSlug: string): string { - return window.location.pathname - .split('/') - .map((part: string, index) => (index === 2 ? teamNameSlug : part)) - .join('/'); -} - export function getPathFromQueryParams(query: ParsedQuery) { const normalizedQuery = { ...query }; From 574829f6df58aa11a8c2888f9bca7e5e4c91ed46 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Tue, 11 Jul 2023 17:33:53 +0200 Subject: [PATCH 03/11] add database migrations --- .../migrations/0020_auto_20230711_1532.py | 26 +++++++++++++++++++ .../migrations/0012_auto_20230711_1532.py | 23 ++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 engine/apps/alerts/migrations/0020_auto_20230711_1532.py create mode 100644 engine/apps/user_management/migrations/0012_auto_20230711_1532.py diff --git a/engine/apps/alerts/migrations/0020_auto_20230711_1532.py b/engine/apps/alerts/migrations/0020_auto_20230711_1532.py new file mode 100644 index 0000000000..acfc1216dc --- /dev/null +++ b/engine/apps/alerts/migrations/0020_auto_20230711_1532.py @@ -0,0 +1,26 @@ +# Generated by Django 3.2.20 on 2023-07-11 15:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('alerts', '0019_auto_20230705_1619'), + ] + + operations = [ + migrations.RemoveField( + model_name='alertgroup', + name='active_cache_for_web_calculation_id', + ), + migrations.RemoveField( + model_name='alertgroup', + name='estimate_escalation_finish_time', + ), + migrations.AlterField( + model_name='alertgroup', + name='cached_render_for_web', + field=models.JSONField(default=dict, null=True), + ), + ] diff --git a/engine/apps/user_management/migrations/0012_auto_20230711_1532.py b/engine/apps/user_management/migrations/0012_auto_20230711_1532.py new file mode 100644 index 0000000000..555f08afb9 --- /dev/null +++ b/engine/apps/user_management/migrations/0012_auto_20230711_1532.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.20 on 2023-07-11 15:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user_management', '0011_auto_20230411_1358'), + ] + + operations = [ + migrations.AlterField( + model_name='organization', + name='archive_alerts_from', + field=models.DateField(default='1970-01-01', null=True), + ), + migrations.AlterField( + model_name='organization', + name='is_amixr_migration_started', + field=models.BooleanField(default=False, null=True), + ), + ] From 5100f98d80344dcd8d5f0df81d68de328c165b8b Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Tue, 11 Jul 2023 17:55:18 +0200 Subject: [PATCH 04/11] update database migration --- ...uto_20230711_1532.py => 0012_auto_20230711_1554.py} | 10 ++++------ engine/apps/user_management/models/organization.py | 5 ----- 2 files changed, 4 insertions(+), 11 deletions(-) rename engine/apps/user_management/migrations/{0012_auto_20230711_1532.py => 0012_auto_20230711_1554.py} (54%) diff --git a/engine/apps/user_management/migrations/0012_auto_20230711_1532.py b/engine/apps/user_management/migrations/0012_auto_20230711_1554.py similarity index 54% rename from engine/apps/user_management/migrations/0012_auto_20230711_1532.py rename to engine/apps/user_management/migrations/0012_auto_20230711_1554.py index 555f08afb9..44dcac87ae 100644 --- a/engine/apps/user_management/migrations/0012_auto_20230711_1532.py +++ b/engine/apps/user_management/migrations/0012_auto_20230711_1554.py @@ -1,6 +1,6 @@ -# Generated by Django 3.2.20 on 2023-07-11 15:32 +# Generated by Django 3.2.20 on 2023-07-11 15:54 -from django.db import migrations, models +from django.db import migrations class Migration(migrations.Migration): @@ -10,14 +10,12 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AlterField( + migrations.RemoveField( model_name='organization', name='archive_alerts_from', - field=models.DateField(default='1970-01-01', null=True), ), - migrations.AlterField( + migrations.RemoveField( model_name='organization', name='is_amixr_migration_started', - 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 a0693f6d7b..fa2b8b2d99 100644 --- a/engine/apps/user_management/models/organization.py +++ b/engine/apps/user_management/models/organization.py @@ -8,7 +8,6 @@ from django.core.validators import MinLengthValidator from django.db import models from django.utils import timezone -from django_deprecate_fields import deprecate_field from mirage import fields as mirage_fields from apps.alerts.models import MaintainableObject @@ -236,10 +235,6 @@ def _get_subscription_strategy(self): PRICING_CHOICES = ((FREE_PUBLIC_BETA_PRICING, "Free public beta"),) pricing_version = models.PositiveIntegerField(choices=PRICING_CHOICES, default=FREE_PUBLIC_BETA_PRICING) - # TODO: remove is_amixr_migration_started and archive_alerts_from soon (aka shortly after July 11, 2023) - is_amixr_migration_started = deprecate_field(models.BooleanField(default=False)) - archive_alerts_from = deprecate_field(models.DateField(default="1970-01-01")) - is_rbac_permissions_enabled = models.BooleanField(default=False) is_grafana_incident_enabled = models.BooleanField(default=False) From 6b6a009f274256be5281b8f7cac37e01ab667f95 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Tue, 11 Jul 2023 18:22:39 +0200 Subject: [PATCH 05/11] update database migrations --- engine/apps/alerts/migrations/0020_auto_20230711_1532.py | 2 ++ engine/apps/alerts/models/alert_group.py | 3 +-- .../apps/user_management/migrations/0012_auto_20230711_1554.py | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/engine/apps/alerts/migrations/0020_auto_20230711_1532.py b/engine/apps/alerts/migrations/0020_auto_20230711_1532.py index acfc1216dc..8d103f856b 100644 --- a/engine/apps/alerts/migrations/0020_auto_20230711_1532.py +++ b/engine/apps/alerts/migrations/0020_auto_20230711_1532.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.20 on 2023-07-11 15:32 from django.db import migrations, models +import django_migration_linter as linter class Migration(migrations.Migration): @@ -10,6 +11,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.RemoveField( model_name='alertgroup', name='active_cache_for_web_calculation_id', diff --git a/engine/apps/alerts/models/alert_group.py b/engine/apps/alerts/models/alert_group.py index 220f797d8f..20305357fc 100644 --- a/engine/apps/alerts/models/alert_group.py +++ b/engine/apps/alerts/models/alert_group.py @@ -16,7 +16,6 @@ from django.dispatch import receiver from django.utils import timezone from django.utils.functional import cached_property -from django_deprecate_fields import deprecate_field from apps.alerts.constants import AlertGroupState from apps.alerts.escalation_snapshot import EscalationSnapshotMixin @@ -328,7 +327,7 @@ def status(self): ) # TODO: remove cached_render_for_web in a subsequent release - cached_render_for_web = deprecate_field(models.JSONField(default=dict)) + cached_render_for_web = models.JSONField(default=dict) # NOTE: we should probably migrate this field to models.UUIDField as it's ONLY ever being # set to the result of uuid.uuid1 diff --git a/engine/apps/user_management/migrations/0012_auto_20230711_1554.py b/engine/apps/user_management/migrations/0012_auto_20230711_1554.py index 44dcac87ae..ba74ed1df6 100644 --- a/engine/apps/user_management/migrations/0012_auto_20230711_1554.py +++ b/engine/apps/user_management/migrations/0012_auto_20230711_1554.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.20 on 2023-07-11 15:54 from django.db import migrations +import django_migration_linter as linter class Migration(migrations.Migration): @@ -10,6 +11,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.RemoveField( model_name='organization', name='archive_alerts_from', From 1705e46ac3ecdc72faa58556e8e915e2a9f909a4 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Tue, 11 Jul 2023 18:24:12 +0200 Subject: [PATCH 06/11] update database migration --- engine/apps/alerts/migrations/0020_auto_20230711_1532.py | 3 +-- engine/apps/alerts/models/alert_group.py | 3 --- engine/apps/api/views/alert_group.py | 3 --- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/engine/apps/alerts/migrations/0020_auto_20230711_1532.py b/engine/apps/alerts/migrations/0020_auto_20230711_1532.py index 8d103f856b..9a505f2e36 100644 --- a/engine/apps/alerts/migrations/0020_auto_20230711_1532.py +++ b/engine/apps/alerts/migrations/0020_auto_20230711_1532.py @@ -20,9 +20,8 @@ class Migration(migrations.Migration): model_name='alertgroup', name='estimate_escalation_finish_time', ), - migrations.AlterField( + migrations.RemoveField( model_name='alertgroup', name='cached_render_for_web', - field=models.JSONField(default=dict, null=True), ), ] diff --git a/engine/apps/alerts/models/alert_group.py b/engine/apps/alerts/models/alert_group.py index 20305357fc..1cf41fecdb 100644 --- a/engine/apps/alerts/models/alert_group.py +++ b/engine/apps/alerts/models/alert_group.py @@ -326,9 +326,6 @@ def status(self): related_name="dependent_alert_groups", ) - # TODO: remove cached_render_for_web in a subsequent release - cached_render_for_web = models.JSONField(default=dict) - # NOTE: we should probably migrate this field to models.UUIDField as it's ONLY ever being # set to the result of uuid.uuid1 last_unique_unacknowledge_process_id: UUID | None = models.CharField(max_length=100, null=True, default=None) diff --git a/engine/apps/api/views/alert_group.py b/engine/apps/api/views/alert_group.py index 164c591928..0378decf12 100644 --- a/engine/apps/api/views/alert_group.py +++ b/engine/apps/api/views/alert_group.py @@ -364,9 +364,6 @@ def enrich(self, alert_groups): alert_group_pks = [alert_group.pk for alert_group in alert_groups] queryset = AlertGroup.all_objects.filter(pk__in=alert_group_pks).order_by("-pk") - # do not load cached_render_for_web as it's deprecated and can be very large - queryset = queryset.defer("cached_render_for_web") - queryset = self.get_serializer_class().setup_eager_loading(queryset) alert_groups = list(queryset) From 7ed2f68337fef19e5bcf9898c658114d0e1247de Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Tue, 11 Jul 2023 22:35:50 +0200 Subject: [PATCH 07/11] remove more unused stuff --- engine/engine/logging/formatters.py | 18 ---- .../management/commands/restart_escalation.py | 87 ------------------- .../management/commands/start_celery.py | 35 -------- engine/requirements.txt | 1 - 4 files changed, 141 deletions(-) delete mode 100644 engine/engine/logging/formatters.py delete mode 100644 engine/engine/management/commands/restart_escalation.py delete mode 100644 engine/engine/management/commands/start_celery.py diff --git a/engine/engine/logging/formatters.py b/engine/engine/logging/formatters.py deleted file mode 100644 index 9ad7f029a9..0000000000 --- a/engine/engine/logging/formatters.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.conf import settings -from pythonjsonlogger import jsonlogger - - -class CustomStackdriverJsonFormatter(jsonlogger.JsonFormatter): - def add_fields(self, log_record, record, message_dict): - super(CustomStackdriverJsonFormatter, self).add_fields(log_record, record, message_dict) - if ( - settings.GCP_PROJECT_ID - and log_record["request_id"] is not None - and len(log_record["request_id"].split("/")) == 2 - ): - trace = log_record["request_id"].split("/") - log_record["logging.googleapis.com/trace"] = f"projects/{settings.GCP_PROJECT_ID}/traces/{trace[0]}" - if "levelname" in log_record: - log_record["severity"] = log_record["levelname"] - if "exc_info" in log_record: - log_record["@type"] = "type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent" diff --git a/engine/engine/management/commands/restart_escalation.py b/engine/engine/management/commands/restart_escalation.py deleted file mode 100644 index 8ceaf01d85..0000000000 --- a/engine/engine/management/commands/restart_escalation.py +++ /dev/null @@ -1,87 +0,0 @@ -from celery import uuid as celery_uuid -from django.core.management import BaseCommand -from django.db.models import Q -from django.utils import timezone - -from apps.alerts.models import AlertGroup, AlertReceiveChannel -from apps.alerts.tasks import escalate_alert_group, unsilence_task - - -class Command(BaseCommand): - def add_arguments(self, parser): - group = parser.add_mutually_exclusive_group(required=True) - - group.add_argument("--alert_group_ids", type=int, nargs="+", help="Alert group IDs to restart escalation for.") - group.add_argument( - "--all", action="store_true", help="Restart escalation for all alert groups with unfinished escalation." - ) - - def handle(self, *args, **options): - alert_group_ids = options["alert_group_ids"] - restart_all = options["all"] - - if restart_all: - alert_groups = AlertGroup.all_objects.filter( - ~Q(channel__integration=AlertReceiveChannel.INTEGRATION_MAINTENANCE), - ~Q(silenced=True, silenced_until__isnull=True), # filter silenced forever alert_groups - Q(Q(is_escalation_finished=False) | Q(silenced_until__isnull=False)), - resolved=False, - acknowledged=False, - root_alert_group=None, - ) - else: - alert_groups = AlertGroup.all_objects.filter( - pk__in=alert_group_ids, - ) - - if not alert_groups: - self.stdout.write("No escalations to restart.") - return - - tasks = [] - alert_groups_to_update = [] - now = timezone.now() - - for alert_group in alert_groups: - task_id = celery_uuid() - # if incident was silenced, start unsilence_task - if alert_group.is_silenced_for_period: - alert_group.unsilence_task_uuid = task_id - - escalation_start_time = max(now, alert_group.silenced_until) - alert_groups_to_update.append(alert_group) - - tasks.append( - unsilence_task.signature( - args=(alert_group.pk,), - immutable=True, - task_id=task_id, - eta=escalation_start_time, - ) - ) - # otherwise start escalate_alert_group task - else: - if alert_group.escalation_snapshot: - alert_group.active_escalation_id = task_id - alert_groups_to_update.append(alert_group) - - tasks.append( - escalate_alert_group.signature( - args=(alert_group.pk,), - immutable=True, - task_id=task_id, - eta=alert_group.next_step_eta, - ) - ) - - AlertGroup.all_objects.bulk_update( - alert_groups_to_update, - ["active_escalation_id", "unsilence_task_uuid"], - batch_size=5000, - ) - - for task in tasks: - task.apply_async() - - restarted_alert_group_ids = ", ".join(str(alert_group.pk) for alert_group in alert_groups) - self.stdout.write("Escalations restarted for alert groups: {}".format(restarted_alert_group_ids)) diff --git a/engine/engine/management/commands/start_celery.py b/engine/engine/management/commands/start_celery.py deleted file mode 100644 index 23ced0ec79..0000000000 --- a/engine/engine/management/commands/start_celery.py +++ /dev/null @@ -1,35 +0,0 @@ -import os -import shlex -import subprocess - -from django.core.management.base import BaseCommand -from django.utils import autoreload - -from common.utils import getenv_boolean - -WORKER_ID = 0 - - -def restart_celery(*args, **kwargs): - global WORKER_ID - kill_worker_cmd = "celery -A engine control shutdown" - subprocess.call(shlex.split(kill_worker_cmd)) - - queues = os.environ.get("CELERY_WORKER_QUEUE", "celery,retry") - max_tasks_per_child = os.environ.get("CELERY_WORKER_MAX_TASKS_PER_CHILD", 100) - concurrency = os.environ.get("CELERY_WORKER_CONCURRENCY", 3) - log_level = "debug" if getenv_boolean("CELERY_WORKER_DEBUG_LOGS", False) else "info" - - celery_args = f"-A engine worker -l {log_level} --concurrency={concurrency} -Q {queues} --max-tasks-per-child={max_tasks_per_child} -n {WORKER_ID}" - - if getenv_boolean("CELERY_WORKER_BEAT_ENABLED", False): - celery_args += " --beat" - - subprocess.call(shlex.split(f"celery {celery_args}")) - WORKER_ID = 1 + WORKER_ID - - -class Command(BaseCommand): - def handle(self, *args, **options): - self.stdout.write("Starting celery worker with autoreload...") - autoreload.run_with_reloader(restart_celery, args=None, kwargs=None) diff --git a/engine/requirements.txt b/engine/requirements.txt index 5baa8a0d6c..02fc9f4b25 100644 --- a/engine/requirements.txt +++ b/engine/requirements.txt @@ -25,7 +25,6 @@ beautifulsoup4==4.12.2 social-auth-app-django==5.0.0 cryptography==38.0.4 # version 39.0.0 introduced an issue - https://stackoverflow.com/a/75053968/3902555 factory-boy<3.0 -python-json-logger==2.0.1 django-log-request-id==1.6.0 django-polymorphic==3.0.0 django-rest-polymorphic==0.1.9 From 3e7980a9ed27a648073817e15e625113a6fd688c Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Wed, 12 Jul 2023 07:31:06 +0200 Subject: [PATCH 08/11] few more pieces of cleanup --- engine/apps/alerts/tasks/__init__.py | 1 - .../tasks/send_update_postmortem_signal.py | 29 ------------ engine/apps/twilioapp/models/__init__.py | 1 - .../twilioapp/models/twilio_log_record.py | 47 ------------------- engine/engine/urls.py | 2 - engine/engine/views.py | 19 -------- engine/settings/prod_without_db.py | 4 -- 7 files changed, 103 deletions(-) delete mode 100644 engine/apps/alerts/tasks/send_update_postmortem_signal.py delete mode 100644 engine/apps/twilioapp/models/twilio_log_record.py diff --git a/engine/apps/alerts/tasks/__init__.py b/engine/apps/alerts/tasks/__init__.py index 8df7d0c02e..09fbba79f1 100644 --- a/engine/apps/alerts/tasks/__init__.py +++ b/engine/apps/alerts/tasks/__init__.py @@ -22,7 +22,6 @@ from .resolve_by_last_step import resolve_by_last_step_task # noqa: F401 from .send_alert_group_signal import send_alert_group_signal # noqa: F401 from .send_update_log_report_signal import send_update_log_report_signal # noqa: F401 -from .send_update_postmortem_signal import send_update_postmortem_signal # noqa: F401 from .send_update_resolution_note_signal import send_update_resolution_note_signal # noqa: F401 from .sync_grafana_alerting_contact_points import sync_grafana_alerting_contact_points # noqa: F401 from .unsilence import unsilence_task # noqa: F401 diff --git a/engine/apps/alerts/tasks/send_update_postmortem_signal.py b/engine/apps/alerts/tasks/send_update_postmortem_signal.py deleted file mode 100644 index 92217e1e53..0000000000 --- a/engine/apps/alerts/tasks/send_update_postmortem_signal.py +++ /dev/null @@ -1,29 +0,0 @@ -from django.apps import apps -from django.conf import settings - -from apps.alerts.signals import alert_group_update_resolution_note_signal -from common.custom_celery_tasks import shared_dedicated_queue_retry_task - - -@shared_dedicated_queue_retry_task( - autoretry_for=(Exception,), retry_backoff=True, max_retries=1 if settings.DEBUG else None -) -def send_update_postmortem_signal(alert_group_pk, resolution_note_pk=None): - # Legacy task, remove - AlertGroup = apps.get_model("alerts", "AlertGroup") - ResolutionNote = apps.get_model("alerts", "ResolutionNote") - - alert_group = AlertGroup.unarchived_objects.filter(pk=alert_group_pk).first() - if alert_group is None: - print("Sent signal to update postmortem, but alert group is archived or does not exist") - return - - resolution_note = None - if resolution_note_pk is not None: - resolution_note = ResolutionNote.objects_with_deleted.get(pk=resolution_note_pk) - - alert_group_update_resolution_note_signal.send( - sender=send_update_postmortem_signal, - alert_group=alert_group, - resolution_note=resolution_note, - ) diff --git a/engine/apps/twilioapp/models/__init__.py b/engine/apps/twilioapp/models/__init__.py index ba7b537dfd..519f3bbe26 100644 --- a/engine/apps/twilioapp/models/__init__.py +++ b/engine/apps/twilioapp/models/__init__.py @@ -1,4 +1,3 @@ -from .twilio_log_record import TwilioLogRecord # noqa: F401 from .twilio_phone_call import TwilioCallStatuses, TwilioPhoneCall # noqa: F401 from .twilio_sender import ( # noqa: F401 TwilioAccount, diff --git a/engine/apps/twilioapp/models/twilio_log_record.py b/engine/apps/twilioapp/models/twilio_log_record.py deleted file mode 100644 index e26b70465f..0000000000 --- a/engine/apps/twilioapp/models/twilio_log_record.py +++ /dev/null @@ -1,47 +0,0 @@ -from django.db import models - - -class TwilioLogRecordType(object): - VERIFICATION_START = 10 - VERIFICATION_CHECK = 20 - - CHOICES = ((VERIFICATION_START, "verification start"), (VERIFICATION_CHECK, "verification check")) - - -class TwilioLogRecordStatus(object): - # For verification and check it has used the same statuses - # https://www.twilio.com/docs/verify/api/verification#verification-response-properties - # https://www.twilio.com/docs/verify/api/verification-check - - PENDING = 10 - APPROVED = 20 - DENIED = 30 - # Our customized status for TwilioException - ERROR = 40 - - CHOICES = ((PENDING, "pending"), (APPROVED, "approved"), (DENIED, "denied"), (ERROR, "error")) - - DETERMINANT = {"pending": PENDING, "approved": APPROVED, "denied": DENIED, "error": ERROR} - - -# Deprecated model. Kept here for backward compatibility, should be removed after phone notificator release -class TwilioLogRecord(models.Model): - user = models.ForeignKey("user_management.User", on_delete=models.CASCADE) - - phone_number = models.CharField(max_length=16) - - type = models.PositiveSmallIntegerField( - choices=TwilioLogRecordType.CHOICES, default=TwilioLogRecordType.VERIFICATION_START - ) - - status = models.PositiveSmallIntegerField( - choices=TwilioLogRecordStatus.CHOICES, default=TwilioLogRecordStatus.PENDING - ) - - payload = models.TextField(null=True, default=None) - - error_message = models.TextField(null=True, default=None) - - succeed = models.BooleanField(default=False) - - created_at = models.DateTimeField(auto_now_add=True) diff --git a/engine/engine/urls.py b/engine/engine/urls.py index 83ede8662d..eb1a66fd7d 100644 --- a/engine/engine/urls.py +++ b/engine/engine/urls.py @@ -31,8 +31,6 @@ urlpatterns = [ *paths_to_work_even_when_maintenance_mode_is_active, - # path('slow/', SlowView.as_view()), - # path('exception/', ExceptionView.as_view()), path(settings.ONCALL_DJANGO_ADMIN_PATH, admin.site.urls), path("api/gi/v1/", include("apps.api_for_grafana_incident.urls", namespace="api-gi")), path("api/internal/v1/", include("apps.api.urls", namespace="api-internal")), diff --git a/engine/engine/views.py b/engine/engine/views.py index 410bf0483f..7f3e2f5d6c 100644 --- a/engine/engine/views.py +++ b/engine/engine/views.py @@ -1,17 +1,9 @@ -import time - from django.conf import settings from django.core.cache import cache from django.http import HttpResponse, JsonResponse from django.views.generic import View from apps.integrations.mixins import AlertChannelDefiningMixin -from common.custom_celery_tasks import shared_dedicated_queue_retry_task - - -@shared_dedicated_queue_retry_task(ignore_result=True) -def health_check_task(): - return "Ok" class HealthCheckView(View): @@ -60,17 +52,6 @@ def get(self, request): return HttpResponse("Ok") -class SlowView(View): - def get(self, request): - time.sleep(1.5) - return HttpResponse("Slept well.") - - -class ExceptionView(View): - def get(self, request): - raise Exception("Trying exception!") - - class MaintenanceModeStatusView(View): def get(self, _request): return JsonResponse( diff --git a/engine/settings/prod_without_db.py b/engine/settings/prod_without_db.py index 877ffdf362..eeb67edc02 100644 --- a/engine/settings/prod_without_db.py +++ b/engine/settings/prod_without_db.py @@ -46,8 +46,6 @@ def on_uwsgi_worker_exit(): CELERY_TASK_ROUTES = { # DEFAULT "apps.alerts.tasks.call_ack_url.call_ack_url": {"queue": "default"}, - "apps.alerts.tasks.cache_alert_group_for_web.cache_alert_group_for_web": {"queue": "default"}, - "apps.alerts.tasks.cache_alert_group_for_web.schedule_cache_for_alert_group": {"queue": "default"}, "apps.alerts.tasks.create_contact_points_for_datasource.create_contact_points_for_datasource": {"queue": "default"}, "apps.alerts.tasks.sync_grafana_alerting_contact_points.sync_grafana_alerting_contact_points": {"queue": "default"}, "apps.alerts.tasks.delete_alert_group.delete_alert_group": {"queue": "default"}, @@ -78,7 +76,6 @@ def on_uwsgi_worker_exit(): "apps.schedules.tasks.notify_about_empty_shifts_in_schedule.start_notify_about_empty_shifts_in_schedule": { "queue": "default" }, - "engine.views.health_check_task": {"queue": "default"}, # CRITICAL "apps.alerts.tasks.acknowledge_reminder.acknowledge_reminder_task": {"queue": "critical"}, "apps.alerts.tasks.acknowledge_reminder.unacknowledge_timeout_task": {"queue": "critical"}, @@ -99,7 +96,6 @@ def on_uwsgi_worker_exit(): }, "apps.alerts.tasks.resolve_by_last_step.resolve_by_last_step_task": {"queue": "critical"}, "apps.alerts.tasks.send_update_log_report_signal.send_update_log_report_signal": {"queue": "critical"}, - "apps.alerts.tasks.send_update_postmortem_signal.send_update_postmortem_signal": {"queue": "critical"}, "apps.alerts.tasks.send_update_resolution_note_signal.send_update_resolution_note_signal": {"queue": "critical"}, "apps.alerts.tasks.unsilence.unsilence_task": {"queue": "critical"}, "apps.base.tasks.process_failed_to_invoke_celery_tasks": {"queue": "critical"}, From 547a0ff4f159527c4e5c0abd343e792ce93d7cb8 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Wed, 12 Jul 2023 07:32:45 +0200 Subject: [PATCH 09/11] drop twilio log record table --- .../migrations/0007_delete_twiliologrecord.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 engine/apps/twilioapp/migrations/0007_delete_twiliologrecord.py diff --git a/engine/apps/twilioapp/migrations/0007_delete_twiliologrecord.py b/engine/apps/twilioapp/migrations/0007_delete_twiliologrecord.py new file mode 100644 index 0000000000..63920ebd59 --- /dev/null +++ b/engine/apps/twilioapp/migrations/0007_delete_twiliologrecord.py @@ -0,0 +1,16 @@ +# Generated by Django 3.2.20 on 2023-07-12 05:32 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('twilioapp', '0006_auto_20230601_0807'), + ] + + operations = [ + migrations.DeleteModel( + name='TwilioLogRecord', + ), + ] From 83df306f85da00b20fb1cf4579a095ad80886754 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Wed, 12 Jul 2023 07:40:46 +0200 Subject: [PATCH 10/11] ignore migration linting for drop twilio log record table --- engine/apps/twilioapp/migrations/0007_delete_twiliologrecord.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engine/apps/twilioapp/migrations/0007_delete_twiliologrecord.py b/engine/apps/twilioapp/migrations/0007_delete_twiliologrecord.py index 63920ebd59..33e260eee8 100644 --- a/engine/apps/twilioapp/migrations/0007_delete_twiliologrecord.py +++ b/engine/apps/twilioapp/migrations/0007_delete_twiliologrecord.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.20 on 2023-07-12 05:32 from django.db import migrations +import django_migration_linter as linter class Migration(migrations.Migration): @@ -10,6 +11,7 @@ class Migration(migrations.Migration): ] operations = [ + linter.IgnoreMigration(), migrations.DeleteModel( name='TwilioLogRecord', ), From 22d8be9a9fafb5540c26b93d92101d939c8943fc Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Wed, 12 Jul 2023 07:58:12 +0200 Subject: [PATCH 11/11] whoops, add back start_celery django manage command --- .../management/commands/start_celery.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 engine/engine/management/commands/start_celery.py diff --git a/engine/engine/management/commands/start_celery.py b/engine/engine/management/commands/start_celery.py new file mode 100644 index 0000000000..23ced0ec79 --- /dev/null +++ b/engine/engine/management/commands/start_celery.py @@ -0,0 +1,35 @@ +import os +import shlex +import subprocess + +from django.core.management.base import BaseCommand +from django.utils import autoreload + +from common.utils import getenv_boolean + +WORKER_ID = 0 + + +def restart_celery(*args, **kwargs): + global WORKER_ID + kill_worker_cmd = "celery -A engine control shutdown" + subprocess.call(shlex.split(kill_worker_cmd)) + + queues = os.environ.get("CELERY_WORKER_QUEUE", "celery,retry") + max_tasks_per_child = os.environ.get("CELERY_WORKER_MAX_TASKS_PER_CHILD", 100) + concurrency = os.environ.get("CELERY_WORKER_CONCURRENCY", 3) + log_level = "debug" if getenv_boolean("CELERY_WORKER_DEBUG_LOGS", False) else "info" + + celery_args = f"-A engine worker -l {log_level} --concurrency={concurrency} -Q {queues} --max-tasks-per-child={max_tasks_per_child} -n {WORKER_ID}" + + if getenv_boolean("CELERY_WORKER_BEAT_ENABLED", False): + celery_args += " --beat" + + subprocess.call(shlex.split(f"celery {celery_args}")) + WORKER_ID = 1 + WORKER_ID + + +class Command(BaseCommand): + def handle(self, *args, **options): + self.stdout.write("Starting celery worker with autoreload...") + autoreload.run_with_reloader(restart_celery, args=None, kwargs=None)