diff --git a/engine/apps/alerts/escalation_snapshot/snapshot_classes/escalation_policy_snapshot.py b/engine/apps/alerts/escalation_snapshot/snapshot_classes/escalation_policy_snapshot.py index 3d988605c8..f8542c8fd9 100644 --- a/engine/apps/alerts/escalation_snapshot/snapshot_classes/escalation_policy_snapshot.py +++ b/engine/apps/alerts/escalation_snapshot/snapshot_classes/escalation_policy_snapshot.py @@ -71,10 +71,10 @@ def __init__( custom_webhook, notify_schedule, notify_to_group, - notify_to_team_members, escalation_counter, passed_last_time, pause_escalation, + notify_to_team_members=None, ): self.id = id self.order = order @@ -385,6 +385,7 @@ def _escalation_step_notify_team_members(self, alert_group: "AlertGroup", reason ), kwargs={ "reason": reason, + "important": self.step == EscalationPolicy.STEP_NOTIFY_TEAM_MEMBERS_IMPORTANT, }, immutable=True, ) diff --git a/engine/apps/alerts/migrations/0042_add_notify_to_team.py b/engine/apps/alerts/migrations/0045_add_notify_to_team.py similarity index 85% rename from engine/apps/alerts/migrations/0042_add_notify_to_team.py rename to engine/apps/alerts/migrations/0045_add_notify_to_team.py index 3556d3870d..71963496ea 100644 --- a/engine/apps/alerts/migrations/0042_add_notify_to_team.py +++ b/engine/apps/alerts/migrations/0045_add_notify_to_team.py @@ -7,7 +7,7 @@ class Migration(migrations.Migration): dependencies = [ - ('alerts', '0041_alertreceivechannel_unique_direct_paging_integration_per_team'), + ('alerts', '0044_alertreceivechannel_alertmanager_v2_backup_templates_and_more'), ] operations = [ diff --git a/engine/apps/alerts/models/alert_group_log_record.py b/engine/apps/alerts/models/alert_group_log_record.py index 65c511afca..b6d065da9f 100644 --- a/engine/apps/alerts/models/alert_group_log_record.py +++ b/engine/apps/alerts/models/alert_group_log_record.py @@ -521,7 +521,7 @@ def rendered_log_line_action(self, for_slack=False, html=False, substitute_autho elif self.escalation_error_code == AlertGroupLogRecord.ERROR_ESCALATION_NOTIFY_GROUP_STEP_IS_NOT_CONFIGURED: result += 'skipped escalation step "Notify Group" because it is not configured' elif self.escalation_error_code == AlertGroupLogRecord.ERROR_ESCALATION_NOTIFY_TEAM_MEMBERS_STEP_IS_NOT_CONFIGURED: - result += 'skipped escalation step "Notify Team" because it is not configured' + result += 'skipped escalation step "Notify Team Members" because it is not configured' elif ( self.escalation_error_code == AlertGroupLogRecord.ERROR_ESCALATION_TRIGGER_CUSTOM_BUTTON_STEP_IS_NOT_CONFIGURED diff --git a/engine/apps/alerts/models/escalation_policy.py b/engine/apps/alerts/models/escalation_policy.py index a072527843..16bb356ad3 100644 --- a/engine/apps/alerts/models/escalation_policy.py +++ b/engine/apps/alerts/models/escalation_policy.py @@ -120,7 +120,7 @@ class EscalationPolicy(OrderedModel): # Common steps STEP_WAIT: ("Wait {{wait_delay}} minute(s)", "Wait"), STEP_NOTIFY_MULTIPLE_USERS: ("Start {{importance}} notification for {{users}}", "Notify users"), - STEP_NOTIFY_TEAM_MEMBERS: ("Start {{importance}} notification for team members {{team}}", "Notify all team members"), + STEP_NOTIFY_TEAM_MEMBERS: ("Start {{importance}} notification for {{team}} team members", "Notify all team members"), STEP_NOTIFY_SCHEDULE: ( "Start {{importance}} notification for schedule {{schedule}}", "Notify users from on-call schedule", diff --git a/engine/apps/alerts/tasks/notify_team_members.py b/engine/apps/alerts/tasks/notify_team_members.py index 20d3cd9306..0748784f0d 100644 --- a/engine/apps/alerts/tasks/notify_team_members.py +++ b/engine/apps/alerts/tasks/notify_team_members.py @@ -13,26 +13,27 @@ def notify_team_members_task( team_pk, alert_group_pk, - previous_notification_policy_pk=None, - reason=None, - prevent_posting_to_thread=False, - notify_even_acknowledged=False, - important=False, - notify_anyway=False, + **kwargs # kwargs to pass through to notify_user_task.apply_async ): from apps.user_management.models import Team - with transaction.atomic(): - try: - team = Team.objects.filter(pk=team_pk).first() - except Team.DoesNotExist: - return f"notify_team_members_task: team {team_pk} doesn't exist" + try: + team = Team.objects.filter(pk=team_pk).first() + except Team.DoesNotExist: + return f"notify_team_members_task: team {team_pk} doesn't exist" - for user in team.users.all(): - try: + for user in team.users.all(): + try: + if user.is_notification_allowed: task_logger.debug(f"notify_team_members_task: notifying {user.pk}") - notify_user_task(user.pk, alert_group_pk, previous_notification_policy_pk, reason, prevent_posting_to_thread, notify_even_acknowledged, important, notify_anyway) - except: - task_logger.info(f"notify_team_members_task: user {user.pk} failed") + notify_user_task.apply_async( + args=( + user.pk, + alert_group_pk, + ), + kwargs=kwargs + ) + except: + task_logger.info(f"notify_team_members_task: user {user.pk} failed") diff --git a/engine/apps/alerts/tests/test_escalation_snapshot_mixin.py b/engine/apps/alerts/tests/test_escalation_snapshot_mixin.py index f29ec590bd..995eab482e 100644 --- a/engine/apps/alerts/tests/test_escalation_snapshot_mixin.py +++ b/engine/apps/alerts/tests/test_escalation_snapshot_mixin.py @@ -499,6 +499,66 @@ def test_deserialize_escalation_snapshot( assert deserialized_escalation_snapshot.stop_escalation is False +@pytest.mark.django_db +def test_deserialize_escalation_snapshot_missing_notify_to_team_members( + make_organization_and_user, + make_alert_receive_channel, + make_channel_filter, + make_escalation_chain, + make_escalation_policy, + make_alert_group, +): + + organization, _ = make_organization_and_user() + alert_receive_channel = make_alert_receive_channel(organization) + escalation_chain = make_escalation_chain(organization=organization) + channel_filter = make_channel_filter(alert_receive_channel, escalation_chain=escalation_chain) + escalation_policy = make_escalation_policy( + escalation_chain=channel_filter.escalation_chain, + escalation_policy_step=EscalationPolicy.STEP_WAIT, + wait_delay=EscalationPolicy.FIFTEEN_MINUTES, + ) + + alert_group = make_alert_group(alert_receive_channel, channel_filter=channel_filter) + alert_group.raw_escalation_snapshot = alert_group.build_raw_escalation_snapshot() + del alert_group.raw_escalation_snapshot['escalation_policies_snapshots'][0]['notify_to_team_members'] + + deserialized_escalation_snapshot = alert_group._deserialize_escalation_snapshot(alert_group.raw_escalation_snapshot) + assert deserialized_escalation_snapshot.escalation_policies_snapshots[0].notify_to_team_members == None + +@patch("apps.alerts.models.alert_group.AlertGroup.slack_channel_id", new_callable=PropertyMock) +@pytest.mark.django_db +def test_deserialize_escalation_snapshot_notify_to_team_members( + mock_alert_group_slack_channel_id, + make_organization_and_user, + make_alert_receive_channel, + make_channel_filter, + make_escalation_chain, + make_escalation_policy, + make_alert_group, + make_team +): + mock_alert_group_slack_channel_id.return_value = MOCK_SLACK_CHANNEL_ID + + organization, _ = make_organization_and_user() + alert_receive_channel = make_alert_receive_channel(organization) + escalation_chain = make_escalation_chain(organization=organization) + channel_filter = make_channel_filter(alert_receive_channel, escalation_chain=escalation_chain) + team = make_team(organization) + escalation_policy = make_escalation_policy( + escalation_chain=channel_filter.escalation_chain, + escalation_policy_step=EscalationPolicy.STEP_NOTIFY_TEAM_MEMBERS, + notify_to_team_members = team + ) + + alert_group = make_alert_group(alert_receive_channel, channel_filter=channel_filter) + alert_group.raw_escalation_snapshot = alert_group.build_raw_escalation_snapshot() + alert_group.raw_escalation_snapshot['escalation_policies_snapshots'][0]['notify_to_team_members'] + + deserialized_escalation_snapshot = alert_group._deserialize_escalation_snapshot(alert_group.raw_escalation_snapshot) + assert deserialized_escalation_snapshot.escalation_policies_snapshots[0].notify_to_team_members.id == team.id + + @pytest.mark.django_db def test_escalation_chain_exists( make_organization_and_user, diff --git a/engine/apps/alerts/tests/test_notify_team_members.py b/engine/apps/alerts/tests/test_notify_team_members.py index 9c0272247f..77643d839b 100644 --- a/engine/apps/alerts/tests/test_notify_team_members.py +++ b/engine/apps/alerts/tests/test_notify_team_members.py @@ -16,13 +16,13 @@ def test_notify_team_members( ): organization = make_organization() user_1 = make_user( - organization=organization, role=LegacyAccessControlRole.VIEWER, _verified_phone_number="1234567890" + organization=organization, role=LegacyAccessControlRole.ADMIN, _verified_phone_number="1234567890" ) user_2 = make_user( - organization=organization, role=LegacyAccessControlRole.VIEWER, _verified_phone_number="1234567890" + organization=organization, role=LegacyAccessControlRole.ADMIN, _verified_phone_number="1234567890" ) user_3 = make_user( - organization=organization, role=LegacyAccessControlRole.VIEWER, _verified_phone_number="1234567890" + organization=organization, role=LegacyAccessControlRole.ADMIN, _verified_phone_number="1234567890" ) team_1 = make_team( organization=organization, @@ -31,10 +31,36 @@ def test_notify_team_members( team_1.users.add(user_2) alert_receive_channel = make_alert_receive_channel(organization=organization) alert_group = make_alert_group(alert_receive_channel=alert_receive_channel) + with patch("apps.alerts.tasks.notify_team_members.notify_user_task") as mock_execute: notify_team_members_task(team_1.pk, alert_group.pk) - assert mock_execute.call_args_list[0] == call(user_1.pk, alert_group.pk, None, None, False, False, False, False) - assert mock_execute.call_args_list[1] == call(user_2.pk, alert_group.pk, None, None, False, False, False, False) - assert mock_execute.call_count == 2 + assert mock_execute.apply_async.call_args_list[0] == call(args=(user_1.pk, alert_group.pk), kwargs={}) + assert mock_execute.apply_async.call_args_list[1] == call(args=(user_2.pk, alert_group.pk), kwargs={}) + assert mock_execute.apply_async.call_count == 2 + +@pytest.mark.django_db +def test_notify_team_members_important( + make_organization, + make_user, + make_alert_receive_channel, + make_alert_group, + make_team +): + organization = make_organization() + user_1 = make_user( + organization=organization, role=LegacyAccessControlRole.ADMIN, _verified_phone_number="1234567890" + ) + team_1 = make_team( + organization=organization, + ) + team_1.users.add(user_1) + alert_receive_channel = make_alert_receive_channel(organization=organization) + alert_group = make_alert_group(alert_receive_channel=alert_receive_channel) + + with patch("apps.alerts.tasks.notify_team_members.notify_user_task") as mock_execute: + notify_team_members_task(team_1.pk, alert_group.pk, important=True) + + assert mock_execute.apply_async.call_args_list[0] == call(args=(user_1.pk, alert_group.pk), kwargs={"important": True}) + assert mock_execute.apply_async.call_count == 1 \ No newline at end of file diff --git a/engine/apps/public_api/tests/test_escalation_policies.py b/engine/apps/public_api/tests/test_escalation_policies.py index a04a2440e9..58ebcc6383 100644 --- a/engine/apps/public_api/tests/test_escalation_policies.py +++ b/engine/apps/public_api/tests/test_escalation_policies.py @@ -456,3 +456,30 @@ def test_update_escalation_policy_from_and_to_time( assert response.data == serializer.data else: assert response.json()[field][0] == "Time has wrong format. Use one of these formats instead: hh:mm:ssZ." + +@pytest.mark.django_db +def test_create_escalation_policy_using_notify_team_members( + make_organization_and_user_with_token, + make_team, + escalation_policies_setup, +): + organization, user, token = make_organization_and_user_with_token() + escalation_chain, _, _ = escalation_policies_setup(organization, user) + team = make_team(organization) + + data_for_create = { + "escalation_chain_id": escalation_chain.public_primary_key, + "type": "notify_team_members", + "position": 0, + "notify_to_team_members": team.team_id + } + + client = APIClient() + url = reverse("api-public:escalation_policies-list") + response = client.post(url, data=data_for_create, format="json", HTTP_AUTHORIZATION=token) + + assert response.status_code == status.HTTP_201_CREATED + + escalation_policy = EscalationPolicy.objects.get(public_primary_key=response.data["id"]) + serializer = EscalationPolicySerializer(escalation_policy) + assert response.data == serializer.data \ No newline at end of file diff --git a/grafana-plugin/src/models/escalation_policy.ts b/grafana-plugin/src/models/escalation_policy.ts deleted file mode 100644 index e917e743c8..0000000000 --- a/grafana-plugin/src/models/escalation_policy.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Channel } from 'models/channel'; -import { Schedule } from 'models/schedule/schedule.types'; -import { UserGroup } from 'models/user_group/user_group.types'; - -import { ChannelFilter } from './channel_filter'; -import { GrafanaTeam } from './grafana_team/grafana_team.types'; -import { ScheduleDTO } from './schedule'; -import { User } from './user/user.types'; - -export interface EscalationPolicyType { - id: string; - notify_to_user: User['pk'] | null; - // it's option value from api/internal/v1/escalation_policies/escalation_options/ - step: number; - wait_delay: string | null; - is_final: boolean; - channel_filter: ChannelFilter['id']; - notify_to_users_queue: Array; - from_time: string | null; - to_time: string | null; - notify_to_schedule: ScheduleDTO['id'] | null; - notify_to_channel: Channel['id'] | null; - notify_to_group: UserGroup['id']; - notify_to_team_members: GrafanaTeam['id']; - notify_schedule: Schedule['id']; -} - -export function prepareEscalationPolicy(value: EscalationPolicyType): EscalationPolicyType { - return { - ...value, - notify_to_user: null, - wait_delay: null, - notify_to_users_queue: [], - from_time: null, - to_time: null, - notify_to_schedule: null, - }; -}