From 9a929e2459a6e04aae0198bd37abfa7a611e8c7c Mon Sep 17 00:00:00 2001 From: Matias Bordese Date: Thu, 24 Oct 2024 13:52:40 -0300 Subject: [PATCH] Add org setting to configure direct paging preferred policy (#5189) For context: https://raintank-corp.slack.com/archives/C01DHQ6LH1S/p1729267368387299?thread_ts=1729185737.051889&cid=C01DHQ6LH1S Next steps: - use the setting in frontend (add participants, add responders) and mobile_app - expose setting in org settings page --- engine/apps/api/serializers/organization.py | 1 + engine/apps/api/tests/test_organization.py | 7 +++++- engine/apps/slack/scenarios/paging.py | 10 ++++++-- .../tests/test_scenario_steps/test_paging.py | 24 +++++++++++++++---- ...n_direct_paging_prefer_important_policy.py | 18 ++++++++++++++ .../user_management/models/organization.py | 2 ++ 6 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 engine/apps/user_management/migrations/0024_organization_direct_paging_prefer_important_policy.py diff --git a/engine/apps/api/serializers/organization.py b/engine/apps/api/serializers/organization.py index cac0edd1..124b51b7 100644 --- a/engine/apps/api/serializers/organization.py +++ b/engine/apps/api/serializers/organization.py @@ -38,6 +38,7 @@ class OrganizationSerializer(EagerLoadingMixin, serializers.ModelSerializer): "slack_channel", "rbac_enabled", "grafana_incident_enabled", + "direct_paging_prefer_important_policy", ] read_only_fields = [ "stack_slug", diff --git a/engine/apps/api/tests/test_organization.py b/engine/apps/api/tests/test_organization.py index 4d04d6c1..ecfb5bff 100644 --- a/engine/apps/api/tests/test_organization.py +++ b/engine/apps/api/tests/test_organization.py @@ -44,6 +44,7 @@ def test_get_organization( "slack_channel": None, "rbac_enabled": organization.is_rbac_permissions_enabled, "grafana_incident_enabled": organization.is_grafana_incident_enabled, + "direct_paging_prefer_important_policy": organization.direct_paging_prefer_important_policy, "is_resolution_note_required": False, "env_status": mock_env_status, "banner": mock_banner, @@ -75,7 +76,10 @@ def test_update_organization_settings(make_organization_and_user_with_plugin_tok client = APIClient() url = reverse("api-internal:api-organization") - data = {"is_resolution_note_required": True} + data = { + "is_resolution_note_required": True, + "direct_paging_prefer_important_policy": True, + } assert organization.is_resolution_note_required is False @@ -83,6 +87,7 @@ def test_update_organization_settings(make_organization_and_user_with_plugin_tok assert response.status_code == status.HTTP_200_OK organization.refresh_from_db() assert organization.is_resolution_note_required is True + assert organization.direct_paging_prefer_important_policy is True @pytest.mark.django_db diff --git a/engine/apps/slack/scenarios/paging.py b/engine/apps/slack/scenarios/paging.py index d944971b..f1981fc5 100644 --- a/engine/apps/slack/scenarios/paging.py +++ b/engine/apps/slack/scenarios/paging.py @@ -373,7 +373,10 @@ class OnPagingUserChange(scenario_step.ScenarioStep): # user is currently on-call error_msg = None try: - updated_payload = add_or_update_item(payload, DataKey.USERS, selected_user.pk, Policy.DEFAULT) + policy = Policy.DEFAULT + if selected_user.organization.direct_paging_prefer_important_policy: + policy = Policy.IMPORTANT + updated_payload = add_or_update_item(payload, DataKey.USERS, selected_user.pk, policy) except ValueError: updated_payload = payload error_msg = "Cannot add user, maximum responders exceeded" @@ -449,7 +452,10 @@ class OnPagingConfirmUserChange(scenario_step.ScenarioStep): error_msg = None try: - updated_payload = add_or_update_item(previous_view_payload, DataKey.USERS, selected_user.pk, Policy.DEFAULT) + policy = Policy.DEFAULT + if selected_user.organization.direct_paging_prefer_important_policy: + policy = Policy.IMPORTANT + updated_payload = add_or_update_item(previous_view_payload, DataKey.USERS, selected_user.pk, policy) except ValueError: updated_payload = payload error_msg = "Cannot add user, maximum responders exceeded" diff --git a/engine/apps/slack/tests/test_scenario_steps/test_paging.py b/engine/apps/slack/tests/test_scenario_steps/test_paging.py index 9c62d8e5..46c32f3c 100644 --- a/engine/apps/slack/tests/test_scenario_steps/test_paging.py +++ b/engine/apps/slack/tests/test_scenario_steps/test_paging.py @@ -162,9 +162,15 @@ def test_initial_unauthorized(make_organization_and_user_with_slack_identities, ) +@pytest.mark.parametrize("use_important_policy", (False, True)) @pytest.mark.django_db -def test_add_user_no_warning(make_organization_and_user_with_slack_identities, make_schedule, make_on_call_shift): +def test_add_user_no_warning( + make_organization_and_user_with_slack_identities, make_schedule, make_on_call_shift, use_important_policy +): organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + if use_important_policy: + organization.direct_paging_prefer_important_policy = use_important_policy + organization.save() # set up schedule: user is on call schedule = make_schedule( organization, @@ -195,7 +201,10 @@ def test_add_user_no_warning(make_organization_and_user_with_slack_identities, m step.process_scenario(slack_user_identity, slack_team_identity, payload) metadata = json.loads(mock_slack_api_call.call_args.kwargs["view"]["private_metadata"]) - assert metadata[DataKey.USERS] == {str(user.pk): Policy.DEFAULT} + if use_important_policy: + assert metadata[DataKey.USERS] == {str(user.pk): Policy.IMPORTANT} + else: + assert metadata[DataKey.USERS] == {str(user.pk): Policy.DEFAULT} @pytest.mark.django_db @@ -269,15 +278,20 @@ def test_add_user_raise_warning(make_organization_and_user_with_slack_identities assert metadata[DataKey.USERS] == {} +@pytest.mark.parametrize("use_important_policy", (False, True)) @pytest.mark.django_db -def test_change_user_policy(make_organization_and_user_with_slack_identities): +def test_change_user_policy(make_organization_and_user_with_slack_identities, use_important_policy): organization, user, slack_team_identity, slack_user_identity = make_organization_and_user_with_slack_identities() + if use_important_policy: + organization.direct_paging_prefer_important_policy = use_important_policy + organization.save() + value = Policy.IMPORTANT if not use_important_policy else Policy.DEFAULT payload = make_paging_view_slack_payload( selected_org=organization, actions=[ { "selected_option": { - "value": make_value({"action": Policy.IMPORTANT, "key": DataKey.USERS, "id": user.pk}, organization) + "value": make_value({"action": value, "key": DataKey.USERS, "id": user.pk}, organization) } } ], @@ -288,7 +302,7 @@ def test_change_user_policy(make_organization_and_user_with_slack_identities): step.process_scenario(slack_user_identity, slack_team_identity, payload) metadata = json.loads(mock_slack_api_call.call_args.kwargs["view"]["private_metadata"]) - assert metadata[DataKey.USERS] == {str(user.pk): Policy.IMPORTANT} + assert metadata[DataKey.USERS] == {str(user.pk): value} @pytest.mark.django_db diff --git a/engine/apps/user_management/migrations/0024_organization_direct_paging_prefer_important_policy.py b/engine/apps/user_management/migrations/0024_organization_direct_paging_prefer_important_policy.py new file mode 100644 index 00000000..ef05593b --- /dev/null +++ b/engine/apps/user_management/migrations/0024_organization_direct_paging_prefer_important_policy.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.15 on 2024-10-18 16:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user_management', '0023_organization_is_grafana_irm_enabled'), + ] + + operations = [ + migrations.AddField( + model_name='organization', + name='direct_paging_prefer_important_policy', + 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 a6dcc622..d9e74a43 100644 --- a/engine/apps/user_management/models/organization.py +++ b/engine/apps/user_management/models/organization.py @@ -259,6 +259,8 @@ class Organization(MaintainableObject): alert_group_table_columns: list[AlertGroupTableColumn] | None = JSONField(default=None, null=True) grafana_incident_backend_url = models.CharField(max_length=300, null=True, default=None) + direct_paging_prefer_important_policy = models.BooleanField(default=False, null=True) + class Meta: unique_together = ("stack_id", "org_id")