Browse Source

feat(ddm): Add audit log entries for metrics blocking (#64047)

Riccardo Busetti 1 year ago
parent
commit
5425626698

+ 28 - 2
src/sentry/api/endpoints/project_metrics.py

@@ -4,6 +4,7 @@ from typing import Mapping, Optional, Sequence, cast
 from rest_framework.request import Request
 from rest_framework.response import Response
 
+from sentry import audit_log
 from sentry.api.api_owners import ApiOwner
 from sentry.api.api_publish_status import ApiPublishStatus
 from sentry.api.base import region_silo_endpoint
@@ -51,6 +52,21 @@ class ProjectMetricsVisibilityEndpoint(ProjectEndpoint):
     publish_status = {"PUT": ApiPublishStatus.EXPERIMENTAL}
     owner = ApiOwner.TELEMETRY_EXPERIENCE
 
+    def _create_audit_log_entry(
+        self, event_id: str, metric_mri: str, tags: Optional[Sequence[str]], project: Project
+    ):
+        audit_data = {"metric_mri": metric_mri, "project_slug": project.slug}
+        if tags is not None:
+            audit_data["tags"] = tags
+
+        self.create_audit_entry(
+            request=self.request,
+            organization_id=project.organization_id,
+            target_object=project.id,
+            event=audit_log.get_event_id(event_id),
+            data=audit_data,
+        )
+
     def _handle_by_operation_type(
         self, request: Request, project: Project, metric_operation_type: MetricOperationType
     ) -> MetricBlocking:
@@ -63,14 +79,24 @@ class ProjectMetricsVisibilityEndpoint(ProjectEndpoint):
 
         if metric_operation_type == MetricOperationType.BLOCK_METRIC:
             patched_metrics = block_metric(metric_mri, [project])
+            self._create_audit_log_entry("METRIC_BLOCK", metric_mri, None, project)
         elif metric_operation_type == MetricOperationType.UNBLOCK_METRIC:
             patched_metrics = unblock_metric(metric_mri, [project])
+            self._create_audit_log_entry("METRIC_UNBLOCK", metric_mri, None, project)
         elif metric_operation_type == MetricOperationType.BLOCK_TAGS:
-            tags = request.data.get("tags") or []
+            tags = request.data.get("tags")
+            if not tags:
+                raise InvalidParams("You must supply at least one tag to block")
+
             patched_metrics = block_tags_of_metric(metric_mri, set(tags), [project])
+            self._create_audit_log_entry("METRIC_TAGS_BLOCK", metric_mri, tags, project)
         elif metric_operation_type == MetricOperationType.UNBLOCK_TAGS:
-            tags = request.data.get("tags") or []
+            tags = request.data.get("tags")
+            if not tags:
+                raise InvalidParams("You must supply at least one tag to unblock")
+
             patched_metrics = unblock_tags_of_metric(metric_mri, set(tags), [project])
+            self._create_audit_log_entry("METRIC_TAGS_UNBLOCK", metric_mri, tags, project)
 
         return patched_metrics[project.id]
 

+ 32 - 0
src/sentry/audit_log/register.py

@@ -426,3 +426,35 @@ default_manager.add(
         template="added team {team_slug} to project {project_slug}",
     )
 )
+default_manager.add(
+    AuditLogEvent(
+        event_id=182,
+        name="METRIC_BLOCK",
+        api_name="metric.block",
+        template="blocked metric {metric_mri} for project {project_slug}",
+    )
+)
+default_manager.add(
+    AuditLogEvent(
+        event_id=183,
+        name="METRIC_TAGS_BLOCK",
+        api_name="metric.tags.block",
+        template="blocked {tags} tags of metric {metric_mri} for project {project_slug}",
+    )
+)
+default_manager.add(
+    AuditLogEvent(
+        event_id=184,
+        name="METRIC_UNBLOCK",
+        api_name="metric.unblock",
+        template="unblocked metric {metric_mri} for project {project_slug}",
+    )
+)
+default_manager.add(
+    AuditLogEvent(
+        event_id=185,
+        name="METRIC_TAGS_UNBLOCK",
+        api_name="metric.tags.unblock",
+        template="unblocked {tags} tags of metric {metric_mri} for project {project_slug}",
+    )
+)

+ 18 - 0
tests/sentry/api/endpoints/test_project_metrics_visibility.py

@@ -1,3 +1,5 @@
+from unittest.mock import patch
+
 from django.urls import reverse
 
 from sentry.models.apitoken import ApiToken
@@ -105,3 +107,19 @@ class ProjectMetricsVisibilityEndpointTestCase(APITestCase):
         assert response.data["isBlocked"] is False
         assert response.data["blockedTags"] == []
         assert len(get_metrics_blocking_state([self.project])[self.project.id].metrics) == 0
+
+    @patch(
+        "sentry.api.endpoints.project_metrics.ProjectMetricsVisibilityEndpoint.create_audit_entry"
+    )
+    def test_audit_log_entry_emitted(self, create_audit_entry):
+        for operation_type in ("blockMetric", "unblockMetric", "blockTags", "unblockTags"):
+            self.get_success_response(
+                self.organization.slug,
+                self.project.slug,
+                method="put",
+                operationType=operation_type,
+                metricMri="s:custom/user@none",
+                tags=["release", "transaction"],
+            )
+            create_audit_entry.assert_called()
+            create_audit_entry.reset_mock()

+ 4 - 0
tests/sentry/audit_log/test_register.py

@@ -15,6 +15,10 @@ class AuditLogEventRegisterTest(TestCase):
             "member.join-team",
             "member.leave-team",
             "member.pending",
+            "metric.block",
+            "metric.tags.block",
+            "metric.tags.unblock",
+            "metric.unblock",
             "org.create",
             "org.edit",
             "org.remove",