Repository: getsentry/sentry
Pull request: feat(use-case): Add visibility modifiers to use cases and use internal MRI parser
This PR adds the concept of visibility to use cases. The rationale behind this PR is that we are seeing an increase in the number of use cases added to the generic metrics platform and we want to differentiate between use cases that are meant to be surfaced directly in the product or just internally to power features of the product.

This PR also includes some minor improvements like moving into another module `extract_use_case_id` and cleanups of dead code.

Closes: https://github.com/getsentry/sentry/issues/67602

Problem statement:
Formalize visibility of use case ids
We have been adding new use case ids to the `UseCaseID` mapping and such use cases are some user facing and some internal only. We would like to explore a system to define the visibility of use case ids.

For example, if we want `UseCaseID.Profiles` to be private we should have an easy way to define that and all public facing endpoints should block them from querying any metrics or data related to that use case.

Hints from issue discussion:

Commit message: feat(ddm): Allow access to public use case ids and private only in admin

Patch to review:
diff --git a/tests/sentry/api/endpoints/test_organization_metrics.py b/tests/sentry/api/endpoints/test_organization_metrics.py
index 9b846d9e531227..2990545eeebda6 100644
--- a/tests/sentry/api/endpoints/test_organization_metrics.py
+++ b/tests/sentry/api/endpoints/test_organization_metrics.py
@@ -79,27 +79,43 @@ class OrganizationMetricsPermissionTest(APITestCase):
             "post",
             "sentry-api-0-organization-metrics-query",
         ),
+        ("get", "sentry-api-0-organization-metrics-samples"),
     )
 
-    def send_request(self, token, method, endpoint, *args):
-        url = reverse(endpoint, args=(self.project.organization.slug,) + args)
+    def setUp(self):
+        self.create_project(name="Bar", slug="bar", teams=[self.team], fire_project_created=True)
+
+    def send_request(self, organization, token, method, endpoint, *args):
+        url = reverse(endpoint, args=(organization.slug,) + args)
         return getattr(self.client, method)(
             url, HTTP_AUTHORIZATION=f"Bearer {token.token}", format="json"
         )
 
-    def test_permissions(self):
+    def test_access_with_wrong_permission_scopes(self):
         with assume_test_silo_mode(SiloMode.CONTROL):
             token = ApiToken.objects.create(user=self.user, scope_list=[])
 
         for method, endpoint, *rest in self.endpoints:
-            response = self.send_request(token, method, endpoint, *rest)
+            response = self.send_request(self.organization, token, method, endpoint, *rest)
+            assert response.status_code == 403
+
+    def test_access_of_another_organization(self):
+        other_user = self.create_user("admin_2@localhost", is_superuser=True, is_staff=True)
+        self.create_organization(name="foo", slug="foo", owner=other_user)
+
+        with assume_test_silo_mode(SiloMode.CONTROL):
+            token = ApiToken.objects.create(user=other_user, scope_list=["org:read"])
+
+        for method, endpoint, *rest in self.endpoints:
+            response = self.send_request(self.organization, token, method, endpoint, *rest)
             assert response.status_code == 403
 
+    def test_access_with_permissions(self):
         with assume_test_silo_mode(SiloMode.CONTROL):
             token = ApiToken.objects.create(user=self.user, scope_list=["org:read"])
 
         for method, endpoint, *rest in self.endpoints:
-            response = self.send_request(token, method, endpoint, *rest)
+            response = self.send_request(self.organization, token, method, endpoint, *rest)
             assert response.status_code in (200, 400, 404)
 
 


Code review comments:
diff_hunk: @@ -1,14 +1,19 @@
 from __future__ import annotations
 
-import re
 from collections.abc import Mapping
 from enum import Enum
 
 from sentry_kafka_schemas.codecs import ValidationError
 
 from sentry.sentry_metrics.configuration import UseCaseKey
+from sentry.snuba.metrics import parse_mri
 
-MRI_RE_PATTERN = re.compile("^([c|s|d|g|e]):([a-zA-Z0-9_]+)/.*$")
path: src/sentry/sentry_metrics/use_case_id_registry.py
text: @author:
Removed this in favor of our internal `parse_mri` function to avoid the two diverging.

diff_hunk: @@ -424,7 +424,7 @@ def test_process_messages_default_card_rollout(set_sentry_option: Callable[...,
     ),
     (
         {
-            "name": "c:transactions/alert@none" * 21,
+            "name": "c:transactions/alert@" + ("none" * 50),
path: tests/sentry/sentry_metrics/test_gen_metrics_multiprocess_steps.py
text: @author:
The test passed before because the MRI validation was not fully correct and it was accepting a wrong MRI. Now we pass a syntactically correct MRI but the unit is so long that it surpasses the 200 chars limit.