diff --git a/engine/apps/metrics_exporter/tasks.py b/engine/apps/metrics_exporter/tasks.py index 8947dd6031..578b870d13 100644 --- a/engine/apps/metrics_exporter/tasks.py +++ b/engine/apps/metrics_exporter/tasks.py @@ -159,7 +159,7 @@ def calculate_and_cache_user_was_notified_metric(organization_id): User.objects.using(get_random_readonly_database_key_if_present_otherwise_default()) .filter(organization_id=organization_id) .annotate(num_logs=Count("personal_log_records")) - .filter(num_logs__gt=1) + .filter(num_logs__gte=1) .select_related("organization") .prefetch_related("personal_log_records") ) diff --git a/engine/apps/metrics_exporter/tests/conftest.py b/engine/apps/metrics_exporter/tests/conftest.py index 19b45cdf1d..f82ee2115e 100644 --- a/engine/apps/metrics_exporter/tests/conftest.py +++ b/engine/apps/metrics_exporter/tests/conftest.py @@ -1,13 +1,22 @@ import pytest from django.core.cache import cache -from apps.metrics_exporter.constants import ALERT_GROUPS_RESPONSE_TIME, ALERT_GROUPS_TOTAL -from apps.metrics_exporter.helpers import get_metric_alert_groups_response_time_key, get_metric_alert_groups_total_key +from apps.metrics_exporter.constants import ( + ALERT_GROUPS_RESPONSE_TIME, + ALERT_GROUPS_TOTAL, + USER_WAS_NOTIFIED_OF_ALERT_GROUPS, +) +from apps.metrics_exporter.helpers import ( + get_metric_alert_groups_response_time_key, + get_metric_alert_groups_total_key, + get_metric_user_was_notified_of_alert_groups_key, +) METRICS_TEST_INTEGRATION_NAME = "Test integration" METRICS_TEST_ORG_ID = 123 # random number METRICS_TEST_INSTANCE_SLUG = "test_instance" METRICS_TEST_INSTANCE_ID = 292 # random number +METRICS_TEST_USER_USERNAME = "Alex" @pytest.fixture() @@ -17,6 +26,8 @@ def _mock_cache_get(key, *args, **kwargs): key = ALERT_GROUPS_TOTAL elif key.startswith(ALERT_GROUPS_RESPONSE_TIME): key = ALERT_GROUPS_RESPONSE_TIME + elif key.startswith(USER_WAS_NOTIFIED_OF_ALERT_GROUPS): + key = USER_WAS_NOTIFIED_OF_ALERT_GROUPS test_metrics = { ALERT_GROUPS_TOTAL: { 1: { @@ -43,6 +54,15 @@ def _mock_cache_get(key, *args, **kwargs): "response_time": [2, 10, 200, 650], } }, + USER_WAS_NOTIFIED_OF_ALERT_GROUPS: { + 1: { + "org_id": 1, + "slug": "Test stack", + "id": 1, + "user_username": "Alex", + "counter": 4, + } + }, } return test_metrics.get(key) @@ -106,3 +126,27 @@ def cache_get(key, *args, **kwargs): return cache_get return _make_cache_params + + +@pytest.fixture +def make_user_was_notified_metrics_cache_params(monkeypatch): + def _make_cache_params(user_id, organization_id): + metric_user_was_notified_key = get_metric_user_was_notified_of_alert_groups_key(organization_id) + + def cache_get(key, *args, **kwargs): + metrics_data = { + metric_user_was_notified_key: { + user_id: { + "org_id": METRICS_TEST_ORG_ID, + "slug": METRICS_TEST_INSTANCE_SLUG, + "id": METRICS_TEST_INSTANCE_ID, + "user_username": METRICS_TEST_USER_USERNAME, + "counter": 1, + } + }, + } + return metrics_data.get(key, {}) + + return cache_get + + return _make_cache_params diff --git a/engine/apps/metrics_exporter/tests/test_calculation_metrics.py b/engine/apps/metrics_exporter/tests/test_calculation_metrics.py index b992a34072..b46465467b 100644 --- a/engine/apps/metrics_exporter/tests/test_calculation_metrics.py +++ b/engine/apps/metrics_exporter/tests/test_calculation_metrics.py @@ -2,12 +2,13 @@ import pytest +from apps.base.models import UserNotificationPolicyLogRecord from apps.metrics_exporter.helpers import ( get_metric_alert_groups_response_time_key, get_metric_alert_groups_total_key, - get_metrics_cache_timer_key, + get_metric_user_was_notified_of_alert_groups_key, ) -from apps.metrics_exporter.tasks import calculate_and_cache_metrics +from apps.metrics_exporter.tasks import calculate_and_cache_metrics, calculate_and_cache_user_was_notified_metric @patch("apps.alerts.models.alert_group.MetricsCacheManager.metrics_update_state_cache_for_alert_group") @@ -44,7 +45,6 @@ def test_calculate_and_cache_metrics_task( make_alert(alert_group=alert_group_to_sil, raw_request_data={}) alert_group_to_sil.silence() - metrics_cache_timer_key = get_metrics_cache_timer_key(organization.id) metric_alert_groups_total_key = get_metric_alert_groups_total_key(organization.id) metric_alert_groups_response_time_key = get_metric_alert_groups_response_time_key(organization.id) @@ -98,18 +98,84 @@ def test_calculate_and_cache_metrics_task( with patch("apps.metrics_exporter.tasks.cache.set") as mock_cache_set: calculate_and_cache_metrics(organization.id) args = mock_cache_set.call_args_list - assert args[0].args[0] == metrics_cache_timer_key # check alert_groups_total metric cache - metric_alert_groups_total_values = args[1].args + metric_alert_groups_total_values = args[0].args assert metric_alert_groups_total_values[0] == metric_alert_groups_total_key assert metric_alert_groups_total_values[1] == expected_result_metric_alert_groups_total # check alert_groups_response_time metric cache - metric_alert_groups_response_time_values = args[2].args + metric_alert_groups_response_time_values = args[1].args assert metric_alert_groups_response_time_values[0] == metric_alert_groups_response_time_key for integration_id, values in metric_alert_groups_response_time_values[1].items(): assert len(values["response_time"]) == METRICS_RESPONSE_TIME_LEN # set response time to expected result because it is calculated on fly expected_result_metric_alert_groups_response_time[integration_id]["response_time"] = values["response_time"] assert metric_alert_groups_response_time_values[1] == expected_result_metric_alert_groups_response_time + + +@patch("apps.alerts.models.alert_group.MetricsCacheManager.metrics_update_state_cache_for_alert_group") +@pytest.mark.django_db +def test_calculate_and_cache_user_was_notified_metric_task( + mocked_update_state_cache, + make_organization, + make_user_for_organization, + make_team, + make_alert_receive_channel, + make_alert_group, + make_alert, + make_user_notification_policy_log_record, +): + organization = make_organization() + team = make_team(organization) + + user_1 = make_user_for_organization(organization) + user_2 = make_user_for_organization(organization) + + alert_receive_channel_1 = make_alert_receive_channel(organization) + alert_receive_channel_2 = make_alert_receive_channel(organization, team=team) + + alert_group_1 = make_alert_group(alert_receive_channel_1) + alert_group_2 = make_alert_group(alert_receive_channel_2) + + for _ in range(2): + make_user_notification_policy_log_record( + type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_TRIGGERED, + author=user_1, + alert_group=alert_group_1, + ) + + for user in [user_1, user_2]: + make_user_notification_policy_log_record( + type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_TRIGGERED, + author=user, + alert_group=alert_group_2, + ) + + metric_user_was_notified_key = get_metric_user_was_notified_of_alert_groups_key(organization.id) + + expected_result_metric_user_was_notified = { + user_1.id: { + "org_id": organization.org_id, + "slug": organization.stack_slug, + "id": organization.stack_id, + "user_username": user_1.username, + "counter": 2, + }, + user_2.id: { + "org_id": organization.org_id, + "slug": organization.stack_slug, + "id": organization.stack_id, + "user_username": user_2.username, + "counter": 1, + }, + } + + with patch("apps.metrics_exporter.tasks.cache.set") as mock_cache_set: + calculate_and_cache_user_was_notified_metric(organization.id) + args = mock_cache_set.call_args_list + + # check user_was_notified_of_alert_groups metric cache + metric_user_was_notified_of_alert_groups_values = args[0].args + assert metric_user_was_notified_of_alert_groups_values[0] == metric_user_was_notified_key + assert metric_user_was_notified_of_alert_groups_values[1] == expected_result_metric_user_was_notified diff --git a/engine/apps/metrics_exporter/tests/test_metrics_collectors.py b/engine/apps/metrics_exporter/tests/test_metrics_collectors.py index 6f17d01576..4dbe7d0565 100644 --- a/engine/apps/metrics_exporter/tests/test_metrics_collectors.py +++ b/engine/apps/metrics_exporter/tests/test_metrics_collectors.py @@ -4,7 +4,11 @@ from prometheus_client import CollectorRegistry, generate_latest from apps.alerts.constants import AlertGroupState -from apps.metrics_exporter.constants import ALERT_GROUPS_RESPONSE_TIME, ALERT_GROUPS_TOTAL +from apps.metrics_exporter.constants import ( + ALERT_GROUPS_RESPONSE_TIME, + ALERT_GROUPS_TOTAL, + USER_WAS_NOTIFIED_OF_ALERT_GROUPS, +) from apps.metrics_exporter.metrics_collectors import ApplicationMetricsCollector @@ -25,6 +29,9 @@ def test_application_metrics_collector( elif metric.name == ALERT_GROUPS_RESPONSE_TIME: # integration with labels for each value in collector's bucket + _count and _sum histogram values assert len(metric.samples) == len(collector._buckets) + 2 + elif metric.name == USER_WAS_NOTIFIED_OF_ALERT_GROUPS: + # metric with labels for each notified user + assert len(metric.samples) == 1 result = generate_latest(test_metrics_registry).decode("utf-8") assert result is not None assert mocked_org_ids.called diff --git a/engine/apps/metrics_exporter/tests/test_update_metics_cache.py b/engine/apps/metrics_exporter/tests/test_update_metics_cache.py index d9ee2e0a3b..313f08b8df 100644 --- a/engine/apps/metrics_exporter/tests/test_update_metics_cache.py +++ b/engine/apps/metrics_exporter/tests/test_update_metics_cache.py @@ -4,9 +4,12 @@ from django.core.cache import cache from django.test import override_settings +from apps.alerts.tasks import notify_user_task +from apps.base.models import UserNotificationPolicy, UserNotificationPolicyLogRecord from apps.metrics_exporter.helpers import ( get_metric_alert_groups_response_time_key, get_metric_alert_groups_total_key, + get_metric_user_was_notified_of_alert_groups_key, metrics_bulk_update_team_label_cache, ) from apps.metrics_exporter.metrics_cache_manager import MetricsCacheManager @@ -15,6 +18,7 @@ METRICS_TEST_INSTANCE_SLUG, METRICS_TEST_INTEGRATION_NAME, METRICS_TEST_ORG_ID, + METRICS_TEST_USER_USERNAME, ) @@ -450,3 +454,84 @@ def get_called_arg_index_and_compare_results(): ]: expected_result[alert_receive_channel.id].update(expected_result_delete_team) get_called_arg_index_and_compare_results() + + +@patch("apps.alerts.tasks.notify_user.perform_notification") +@override_settings(CELERY_TASK_ALWAYS_EAGER=True) +@pytest.mark.django_db +def test_update_metrics_cache_on_user_notification( + mocked_perform_notification_task, + make_organization, + make_user_for_organization, + make_alert_receive_channel, + make_alert_group, + make_user_notification_policy, + make_user_notification_policy_log_record, + make_user_was_notified_metrics_cache_params, + monkeypatch, + mock_get_metrics_cache, +): + organization = make_organization( + org_id=METRICS_TEST_ORG_ID, + stack_slug=METRICS_TEST_INSTANCE_SLUG, + stack_id=METRICS_TEST_INSTANCE_ID, + ) + alert_receive_channel = make_alert_receive_channel( + organization, + verbal_name=METRICS_TEST_INTEGRATION_NAME, + ) + user = make_user_for_organization(organization, username=METRICS_TEST_USER_USERNAME) + + notification_policy_1 = make_user_notification_policy(user, step=UserNotificationPolicy.Step.NOTIFY) + make_user_notification_policy(user, step=UserNotificationPolicy.Step.NOTIFY) + + alert_group_1 = make_alert_group(alert_receive_channel) + alert_group_2 = make_alert_group(alert_receive_channel) + + make_user_notification_policy_log_record( + type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_TRIGGERED, + author=user, + alert_group=alert_group_1, + ) + + metrics_cache = make_user_was_notified_metrics_cache_params(user.id, organization.id) + monkeypatch.setattr(cache, "get", metrics_cache) + + metric_user_was_notified_key = get_metric_user_was_notified_of_alert_groups_key(organization.id) + + expected_result_metric_user_was_notified = { + user.id: { + "org_id": organization.org_id, + "slug": organization.stack_slug, + "id": organization.stack_id, + "user_username": METRICS_TEST_USER_USERNAME, + "counter": 1, + } + } + + def get_called_arg_index_and_compare_results(cache_was_updated=False): + """find index for the metric argument, that was set in cache""" + for idx, called_arg in enumerate(mock_cache_set_called_args): + if idx >= arg_idx and called_arg.args[0] == metric_user_was_notified_key: + assert called_arg.args[1] == expected_result_metric_user_was_notified + return idx + 1 + if cache_was_updated: + raise AssertionError + return arg_idx + + with patch("apps.metrics_exporter.tasks.cache.set") as mock_cache_set: + arg_idx = 0 + notify_user_task(user.id, alert_group_1.id) + + # check user_was_notified_of_alert_groups metric cache, get called args + mock_cache_set_called_args = mock_cache_set.call_args_list + arg_idx = get_called_arg_index_and_compare_results() + + # counter grows after the first notification of alert group + notify_user_task(user.id, alert_group_2.id) + expected_result_metric_user_was_notified[user.id]["counter"] += 1 + arg_idx = get_called_arg_index_and_compare_results(cache_was_updated=True) + + # counter doesn't grow after the second notification of alert group + notify_user_task(user.id, alert_group_2.id, previous_notification_policy_pk=notification_policy_1.id) + arg_idx = get_called_arg_index_and_compare_results()