Repository: getsentry/sentry

Issue:
Fetching Errors fails in my test org, but not on emerging-tech or sentry org
**Problem:** 

My request
Request URL: https://dev.getsentry.net:7999/api/0/organizations/wildcard-inc/events/?end=2022-09-14T04%3A52%3A30.000Z&field=id&field=error.value&field=timestamp&field=error.type&field=issue.id&projects=-1&query=id%3A%5B990ed9f1ed6b4f96938fe22a7032b21c%5D&referrer=api.replay.details-page&start=2022-09-14T04%3A51%3A20.000Z
is returning 400 {"detail":"You cannot view events from multiple projects."} on a test org that i recently setup, but not when i run that request from the sentry org

**Issue:**
The test org does not have `organizations:global-views` enabled. Therefore it can't make requests to the events endpoint because there is a guard to prevent `>1 projects` from being allowed.


**Solution:**
We're going to make a new endpoint to support the replay use-case. The tip is:
> you should be able to copy something like `OrganizationEventsMetaEndpoint` in src/sentry/api/endpoints/organization_events_meta.py to get what you need
- via wmak 

existing call to update is here: https://github.com/getsentry/sentry/blob/c50743c63ef2da8bf175c9acc66ddcbd52bb0856/static/app/utils/replays/hooks/useReplayData.tsx#L173-L183


Discussion:

Resolving patch:
diff --git a/src/sentry/api/urls.py b/src/sentry/api/urls.py
--- a/src/sentry/api/urls.py
+++ b/src/sentry/api/urls.py
@@ -65,6 +65,9 @@
 from sentry.incidents.endpoints.project_alert_rule_task_details import (
     ProjectAlertRuleTaskDetailsEndpoint,
 )
+from sentry.replays.endpoints.organization_replay_events_meta import (
+    OrganizationReplayEventsMetaEndpoint,
+)
 from sentry.replays.endpoints.organization_replay_index import OrganizationReplayIndexEndpoint
 from sentry.replays.endpoints.project_replay_details import ProjectReplayDetailsEndpoint
 from sentry.replays.endpoints.project_replay_recording_segment_details import (
@@ -1563,6 +1566,11 @@
                     OrganizationReplayIndexEndpoint.as_view(),
                     name="sentry-api-0-organization-replay-index",
                 ),
+                url(
+                    r"^(?P<organization_slug>[^\/]+)/replays-events-meta/$",
+                    OrganizationReplayEventsMetaEndpoint.as_view(),
+                    name="sentry-api-0-organization-replay-events-meta",
+                ),
                 url(
                     r"^(?P<organization_slug>[^\/]+)/functions/$",
                     OrganizationSentryFunctionEndpoint.as_view(),
diff --git a/src/sentry/replays/endpoints/organization_replay_events_meta.py b/src/sentry/replays/endpoints/organization_replay_events_meta.py
new file mode 100644
--- /dev/null
+++ b/src/sentry/replays/endpoints/organization_replay_events_meta.py
@@ -0,0 +1,69 @@
+from rest_framework.request import Request
+from rest_framework.response import Response
+
+from sentry import features
+from sentry.api.base import region_silo_endpoint
+from sentry.api.bases import NoProjects, OrganizationEventsV2EndpointBase
+from sentry.api.paginator import GenericOffsetPaginator
+from sentry.snuba import discover
+
+
+@region_silo_endpoint
+class OrganizationReplayEventsMetaEndpoint(OrganizationEventsV2EndpointBase):
+    """The generic Events endpoints require that the `organizations:global-views` feature
+    be enabled before they return across multiple projects.
+
+    This endpoint is purpose built for the Session Replay product which intentionally
+    requests data across multiple transactions, and therefore potentially multiple projects.
+    This is similar to performance, and modeled after `OrganizationEventsMetaEndpoint`.
+
+    This endpoint offers a narrow interface specific to the requirements of `useReplayData.tsx`
+    """
+
+    private = True
+
+    def get(self, request: Request, organization) -> Response:
+        if not features.has("organizations:session-replay", organization, actor=request.user):
+            return Response(status=404)
+
+        try:
+            params = self.get_snuba_params(request, organization, check_global_views=False)
+        except NoProjects:
+            return Response({"count": 0})
+
+        def data_fn(offset, limit):
+            query_details = {
+                "selected_columns": [
+                    "id",
+                    "error.value",
+                    "timestamp",
+                    "error.type",
+                    "issue.id",
+                ],
+                "query": request.GET.get("query"),
+                "params": params,
+                "equations": self.get_equation_list(organization, request),
+                "orderby": self.get_orderby(request),
+                "offset": offset,
+                "limit": limit,
+                "referrer": "api.replay.details-page",
+                "auto_fields": True,
+                "auto_aggregations": True,
+                "use_aggregate_conditions": True,
+                "allow_metric_aggregates": False,
+                "transform_alias_to_input_format": True,
+            }
+
+            return discover.query(**query_details)
+
+        return self.paginate(
+            request=request,
+            paginator=GenericOffsetPaginator(data_fn=data_fn),
+            on_results=lambda results: self.handle_results_with_meta(
+                request,
+                organization,
+                params["project_id"],
+                results,
+                standard_meta=True,
+            ),
+        )


Test patch:
diff --git a/tests/replays/endpoints/test_organization_replay_events_meta.py b/tests/replays/endpoints/test_organization_replay_events_meta.py
new file mode 100644
--- /dev/null
+++ b/tests/replays/endpoints/test_organization_replay_events_meta.py
@@ -0,0 +1,64 @@
+import pytest
+from django.urls import reverse
+
+from sentry.testutils import APITestCase, SnubaTestCase
+from sentry.testutils.helpers.datetime import before_now, iso_format
+from sentry.testutils.silo import region_silo_test
+
+pytestmark = pytest.mark.sentry_metrics
+
+
+@region_silo_test
+class OrganizationEventsMetaEndpoint(APITestCase, SnubaTestCase):
+    def setUp(self):
+        super().setUp()
+        self.min_ago = before_now(minutes=1)
+        self.login_as(user=self.user)
+        self.project_1 = self.create_project()
+        self.project_2 = self.create_project()
+        self.url = reverse(
+            "sentry-api-0-organization-replay-events-meta",
+            kwargs={"organization_slug": self.project.organization.slug},
+        )
+        self.features = {"organizations:session-replay": True}
+
+    def test_simple(self):
+        event_id_a = "a" * 32
+        event_id_b = "b" * 32
+
+        self.store_event(
+            data={"event_id": event_id_a, "timestamp": iso_format(self.min_ago)},
+            project_id=self.project_1.id,
+        )
+        self.store_event(
+            data={"event_id": event_id_b, "timestamp": iso_format(self.min_ago)},
+            project_id=self.project_2.id,
+        )
+        self.store_event(data={"timestamp": iso_format(self.min_ago)}, project_id=self.project_1.id)
+        self.store_event(data={"timestamp": iso_format(self.min_ago)}, project_id=self.project_1.id)
+
+        query = {"query": f"id:[{event_id_a}, {event_id_b}]"}
+        with self.feature(self.features):
+            response = self.client.get(self.url, query, format="json")
+
+        expected = [
+            {
+                "error.type": [],
+                "error.value": [],
+                "id": event_id_a,
+                "issue.id": 1,
+                "project.name": self.project_1.slug,
+                "timestamp": iso_format(self.min_ago) + "+00:00",
+            },
+            {
+                "error.type": [],
+                "error.value": [],
+                "id": event_id_b,
+                "issue.id": 2,
+                "project.name": self.project_2.slug,
+                "timestamp": iso_format(self.min_ago) + "+00:00",
+            },
+        ]
+
+        assert response.status_code == 200, response.content
+        assert response.data["data"] == expected