Просмотр исходного кода

fix(issue-platform): Fix `get_event_by_id` to correctly return occurrence information for transactions (#49059)

When cleaning up some flags I noticed that we're not properly handling
transactions with performance issues in `get_event_by_id`. We'd convert
them to a `GroupEvent`, but not fetch the occurrence info from snuba, so
the occurrence would be unavailable.
Dan Fuller 1 год назад
Родитель
Сommit
d2092350b2

+ 6 - 15
src/sentry/api/endpoints/project_event_details.py

@@ -9,7 +9,6 @@ from sentry.api.base import region_silo_endpoint
 from sentry.api.bases.project import ProjectEndpoint
 from sentry.api.bases.project import ProjectEndpoint
 from sentry.api.serializers import IssueEventSerializer, serialize
 from sentry.api.serializers import IssueEventSerializer, serialize
 from sentry.eventstore.models import Event
 from sentry.eventstore.models import Event
-from sentry.issues.query import apply_performance_conditions
 from sentry.models.project import Project
 from sentry.models.project import Project
 
 
 
 
@@ -21,22 +20,14 @@ def wrap_event_response(request_user: Any, event: Event, project: Project, envir
     prev_event_id = None
     prev_event_id = None
 
 
     if event.group_id:
     if event.group_id:
-        if event.get_event_type() == "transaction":
-            conditions = apply_performance_conditions([], event.group)
-            _filter = eventstore.Filter(
-                conditions=conditions,
-                project_ids=[event.project_id],
-            )
-        else:
-            conditions = [["event.type", "!=", "transaction"]]
-            _filter = eventstore.Filter(
-                conditions=conditions,
-                project_ids=[event.project_id],
-                group_ids=[event.group_id],
-            )
-
+        conditions = []
         if environments:
         if environments:
             conditions.append(["environment", "IN", environments])
             conditions.append(["environment", "IN", environments])
+        _filter = eventstore.Filter(
+            conditions=conditions,
+            project_ids=[event.project_id],
+            group_ids=[event.group_id],
+        )
 
 
         prev_ids, next_ids = eventstore.get_adjacent_event_ids(event, filter=_filter)
         prev_ids, next_ids = eventstore.get_adjacent_event_ids(event, filter=_filter)
 
 

+ 21 - 14
src/sentry/eventstore/snuba/backend.py

@@ -214,15 +214,12 @@ class SnubaEventStorage(EventStorage):
         if len(event.data) == 0:
         if len(event.data) == 0:
             return None
             return None
 
 
-        if group_id is not None and event.get_event_type() != "generic":
-            # Set passed group_id if not a transaction
-            if event.get_event_type() == "transaction" and not skip_transaction_groupevent:
-                logger.warning("eventstore.passed-group-id-for-transaction")
-                return event.for_group(Group.objects.get(id=group_id))
-            else:
-                event.group_id = group_id
-
-        elif event.get_event_type() != "transaction":
+        if group_id is not None and (
+            event.get_event_type() == "error"
+            or (event.get_event_type() == "transaction" and skip_transaction_groupevent)
+        ):
+            event.group_id = group_id
+        elif event.get_event_type() != "transaction" or group_id:
             # Load group_id from Snuba if not a transaction
             # Load group_id from Snuba if not a transaction
             raw_query_kwargs = {}
             raw_query_kwargs = {}
             if event.datetime > timezone.now() - timedelta(hours=1):
             if event.datetime > timezone.now() - timedelta(hours=1):
@@ -233,16 +230,21 @@ class SnubaEventStorage(EventStorage):
                     ["timestamp", ">", datetime.fromtimestamp(random.randint(0, 1000000000))]
                     ["timestamp", ">", datetime.fromtimestamp(random.randint(0, 1000000000))]
                 ]
                 ]
             dataset = (
             dataset = (
-                Dataset.IssuePlatform if event.get_event_type() == "generic" else Dataset.Events
+                Dataset.IssuePlatform
+                if event.get_event_type() in ("transaction", "generic")
+                else Dataset.Events
             )
             )
             try:
             try:
                 tenant_ids = tenant_ids or {"organization_id": event.project.organization_id}
                 tenant_ids = tenant_ids or {"organization_id": event.project.organization_id}
+                filter_keys = {"project_id": [project_id], "event_id": [event_id]}
+                if group_id:
+                    filter_keys["group_id"] = [group_id]
                 result = snuba.raw_query(
                 result = snuba.raw_query(
                     dataset=dataset,
                     dataset=dataset,
                     selected_columns=self.__get_columns(dataset),
                     selected_columns=self.__get_columns(dataset),
                     start=event.datetime,
                     start=event.datetime,
                     end=event.datetime + timedelta(seconds=1),
                     end=event.datetime + timedelta(seconds=1),
-                    filter_keys={"project_id": [project_id], "event_id": [event_id]},
+                    filter_keys=filter_keys,
                     limit=1,
                     limit=1,
                     referrer="eventstore.get_event_by_id_nodestore",
                     referrer="eventstore.get_event_by_id_nodestore",
                     tenant_ids=tenant_ids,
                     tenant_ids=tenant_ids,
@@ -274,13 +276,18 @@ class SnubaEventStorage(EventStorage):
             # Inject the snuba data here to make sure any snuba columns are available
             # Inject the snuba data here to make sure any snuba columns are available
             event._snuba_data = result["data"][0]
             event._snuba_data = result["data"][0]
 
 
+        # Set passed group_id if not a transaction
+        if event.get_event_type() == "transaction" and not skip_transaction_groupevent and group_id:
+            logger.warning("eventstore.passed-group-id-for-transaction")
+            return event.for_group(Group.objects.get(id=group_id))
+
         return event
         return event
 
 
     def _get_dataset_for_event(self, event):
     def _get_dataset_for_event(self, event):
-        if event.get_event_type() == "transaction":
-            return snuba.Dataset.Transactions
-        elif event.get_event_type() == "generic":
+        if getattr(event, "occurrence", None) or event.get_event_type() == "generic":
             return snuba.Dataset.IssuePlatform
             return snuba.Dataset.IssuePlatform
+        elif event.get_event_type() == "transaction":
+            return snuba.Dataset.Transactions
         else:
         else:
             return snuba.Dataset.Discover
             return snuba.Dataset.Discover
 
 

+ 40 - 13
src/sentry/testutils/cases.py

@@ -83,6 +83,7 @@ from sentry.auth.superuser import COOKIE_SECURE as SU_COOKIE_SECURE
 from sentry.auth.superuser import ORG_ID as SU_ORG_ID
 from sentry.auth.superuser import ORG_ID as SU_ORG_ID
 from sentry.auth.superuser import Superuser
 from sentry.auth.superuser import Superuser
 from sentry.event_manager import EventManager
 from sentry.event_manager import EventManager
+from sentry.eventstore.models import Event
 from sentry.eventstream.snuba import SnubaEventStream
 from sentry.eventstream.snuba import SnubaEventStream
 from sentry.issues.grouptype import NoiseConfig, PerformanceNPlusOneGroupType
 from sentry.issues.grouptype import NoiseConfig, PerformanceNPlusOneGroupType
 from sentry.issues.ingest import send_issue_occurrence_to_eventstream
 from sentry.issues.ingest import send_issue_occurrence_to_eventstream
@@ -106,6 +107,7 @@ from sentry.models import (
     IdentityStatus,
     IdentityStatus,
     NotificationSetting,
     NotificationSetting,
     Organization,
     Organization,
+    Project,
     ProjectOption,
     ProjectOption,
     Release,
     Release,
     ReleaseCommit,
     ReleaseCommit,
@@ -136,6 +138,7 @@ from sentry.types.integrations import ExternalProviders
 from sentry.utils import json
 from sentry.utils import json
 from sentry.utils.auth import SsoSession
 from sentry.utils.auth import SsoSession
 from sentry.utils.json import dumps_htmlsafe
 from sentry.utils.json import dumps_htmlsafe
+from sentry.utils.performance_issues.performance_detection import detect_performance_problems
 from sentry.utils.pytest.selenium import Browser
 from sentry.utils.pytest.selenium import Browser
 from sentry.utils.retries import TimedRetryPolicy
 from sentry.utils.retries import TimedRetryPolicy
 from sentry.utils.samples import load_data
 from sentry.utils.samples import load_data
@@ -486,32 +489,55 @@ class TransactionTestCase(BaseTestCase, TransactionTestCase):
 
 
 class PerformanceIssueTestCase(BaseTestCase):
 class PerformanceIssueTestCase(BaseTestCase):
     def create_performance_issue(
     def create_performance_issue(
-        self, tags=None, contexts=None, fingerprint="group1", transaction=None
+        self,
+        tags=None,
+        contexts=None,
+        fingerprint=None,
+        transaction=None,
+        event_data=None,
+        issue_type=None,
+        noise_limit=0,
+        project_id=None,
+        detector_option="performance.issues.n_plus_one_db.problem-creation",
     ):
     ):
-        event_data = load_data(
-            "transaction-n-plus-one",
-            timestamp=before_now(minutes=10),
-            fingerprint=[f"{PerformanceNPlusOneGroupType.type_id}-{fingerprint}"],
-        )
+        if issue_type is None:
+            issue_type = PerformanceNPlusOneGroupType
+        if event_data is None:
+            event_data = load_data(
+                "transaction-n-plus-one",
+                timestamp=before_now(minutes=10),
+            )
         if tags is not None:
         if tags is not None:
             event_data["tags"] = tags
             event_data["tags"] = tags
         if contexts is not None:
         if contexts is not None:
             event_data["contexts"] = contexts
             event_data["contexts"] = contexts
         if transaction:
         if transaction:
             event_data["transaction"] = transaction
             event_data["transaction"] = transaction
+        if project_id is None:
+            project_id = self.project.id
 
 
         perf_event_manager = EventManager(event_data)
         perf_event_manager = EventManager(event_data)
         perf_event_manager.normalize()
         perf_event_manager.normalize()
 
 
+        def detect_performance_problems_interceptor(data: Event, project: Project):
+            perf_problems = detect_performance_problems(data, project)
+            if fingerprint:
+                for perf_problem in perf_problems:
+                    perf_problem.fingerprint = fingerprint
+            return perf_problems
+
         with mock.patch(
         with mock.patch(
             "sentry.issues.ingest.send_issue_occurrence_to_eventstream",
             "sentry.issues.ingest.send_issue_occurrence_to_eventstream",
             side_effect=send_issue_occurrence_to_eventstream,
             side_effect=send_issue_occurrence_to_eventstream,
-        ) as mock_eventstream, mock.patch.object(
-            PerformanceNPlusOneGroupType, "noise_config", new=NoiseConfig(0, timedelta(minutes=1))
+        ) as mock_eventstream, mock.patch(
+            "sentry.event_manager.detect_performance_problems",
+            side_effect=detect_performance_problems_interceptor,
+        ), mock.patch.object(
+            issue_type, "noise_config", new=NoiseConfig(noise_limit, timedelta(minutes=1))
         ), override_options(
         ), override_options(
             {
             {
                 "performance.issues.all.problem-detection": 1.0,
                 "performance.issues.all.problem-detection": 1.0,
-                "performance.issues.n_plus_one_db.problem-creation": 1.0,
+                detector_option: 1.0,
                 "performance.issues.send_to_issues_platform": True,
                 "performance.issues.send_to_issues_platform": True,
                 "performance.issues.create_issues_through_platform": True,
                 "performance.issues.create_issues_through_platform": True,
             }
             }
@@ -520,10 +546,11 @@ class PerformanceIssueTestCase(BaseTestCase):
                 "projects:performance-suspect-spans-ingestion",
                 "projects:performance-suspect-spans-ingestion",
             ]
             ]
         ):
         ):
-            event = perf_event_manager.save(self.project.id)
-            group_event = event.for_group(mock_eventstream.call_args[0][2].group)
-            group_event.occurrence = mock_eventstream.call_args[0][1]
-            return group_event
+            event = perf_event_manager.save(project_id)
+            if mock_eventstream.call_args:
+                event = event.for_group(mock_eventstream.call_args[0][2].group)
+                event.occurrence = mock_eventstream.call_args[0][1]
+            return event
 
 
 
 
 class APITestCase(BaseTestCase, BaseAPITestCase):
 class APITestCase(BaseTestCase, BaseAPITestCase):

+ 4 - 0
src/sentry/utils/samples.py

@@ -114,6 +114,7 @@ def load_data(
     spans=None,
     spans=None,
     trace_context=None,
     trace_context=None,
     fingerprint=None,
     fingerprint=None,
+    event_id=None,
 ):
 ):
     # NOTE: Before editing this data, make sure you understand the context
     # NOTE: Before editing this data, make sure you understand the context
     # in which its being used. It is NOT only used for local development and
     # in which its being used. It is NOT only used for local development and
@@ -256,6 +257,9 @@ def load_data(
 
 
             data["fingerprint"] = fingerprint
             data["fingerprint"] = fingerprint
 
 
+    if event_id is not None:
+        data["event_id"] = event_id
+
     data["platform"] = platform
     data["platform"] = platform
     # XXX: Message is a legacy alias for logentry. Do not overwrite if set.
     # XXX: Message is a legacy alias for logentry. Do not overwrite if set.
     if "message" not in data:
     if "message" not in data:

+ 28 - 23
tests/sentry/eventstore/snuba/test_backend.py

@@ -3,12 +3,9 @@ from unittest import mock
 from sentry.eventstore.base import Filter
 from sentry.eventstore.base import Filter
 from sentry.eventstore.models import Event
 from sentry.eventstore.models import Event
 from sentry.eventstore.snuba.backend import SnubaEventStorage
 from sentry.eventstore.snuba.backend import SnubaEventStorage
-from sentry.issues.grouptype import (
-    PerformanceRenderBlockingAssetSpanGroupType,
-    PerformanceSlowDBQueryGroupType,
-)
-from sentry.issues.query import apply_performance_conditions
+from sentry.issues.grouptype import PerformanceNPlusOneGroupType
 from sentry.testutils import SnubaTestCase, TestCase
 from sentry.testutils import SnubaTestCase, TestCase
+from sentry.testutils.cases import PerformanceIssueTestCase
 from sentry.testutils.helpers.datetime import before_now, iso_format
 from sentry.testutils.helpers.datetime import before_now, iso_format
 from sentry.testutils.silo import region_silo_test
 from sentry.testutils.silo import region_silo_test
 from sentry.utils import snuba
 from sentry.utils import snuba
@@ -16,7 +13,7 @@ from sentry.utils.samples import load_data
 
 
 
 
 @region_silo_test(stable=True)
 @region_silo_test(stable=True)
-class SnubaEventStorageTest(TestCase, SnubaTestCase):
+class SnubaEventStorageTest(TestCase, SnubaTestCase, PerformanceIssueTestCase):
     def setUp(self):
     def setUp(self):
         super().setUp()
         super().setUp()
         self.min_ago = iso_format(before_now(minutes=1))
         self.min_ago = iso_format(before_now(minutes=1))
@@ -66,23 +63,27 @@ class SnubaEventStorageTest(TestCase, SnubaTestCase):
         self.transaction_event = self.store_event(data=event_data, project_id=self.project1.id)
         self.transaction_event = self.store_event(data=event_data, project_id=self.project1.id)
 
 
         event_data_2 = load_data(
         event_data_2 = load_data(
-            platform="transaction",
-            fingerprint=[f"{PerformanceRenderBlockingAssetSpanGroupType.type_id}-group3"],
+            platform="transaction-n-plus-one",
+            fingerprint=[f"{PerformanceNPlusOneGroupType.type_id}-group3"],
         )
         )
         event_data_2["timestamp"] = iso_format(before_now(seconds=30))
         event_data_2["timestamp"] = iso_format(before_now(seconds=30))
         event_data_2["start_timestamp"] = iso_format(before_now(seconds=31))
         event_data_2["start_timestamp"] = iso_format(before_now(seconds=31))
         event_data_2["event_id"] = "e" * 32
         event_data_2["event_id"] = "e" * 32
 
 
-        self.transaction_event_2 = self.store_event(data=event_data_2, project_id=self.project2.id)
+        self.transaction_event_2 = self.create_performance_issue(
+            event_data=event_data_2, project_id=self.project2.id
+        )
 
 
         event_data_3 = load_data(
         event_data_3 = load_data(
-            "transaction", fingerprint=[f"{PerformanceSlowDBQueryGroupType.type_id}-group3"]
+            "transaction-n-plus-one", fingerprint=[f"{PerformanceNPlusOneGroupType.type_id}-group3"]
         )
         )
         event_data_3["timestamp"] = iso_format(before_now(seconds=30))
         event_data_3["timestamp"] = iso_format(before_now(seconds=30))
         event_data_3["start_timestamp"] = iso_format(before_now(seconds=31))
         event_data_3["start_timestamp"] = iso_format(before_now(seconds=31))
         event_data_3["event_id"] = "f" * 32
         event_data_3["event_id"] = "f" * 32
 
 
-        self.transaction_event_3 = self.store_event(data=event_data_3, project_id=self.project2.id)
+        self.transaction_event_3 = self.create_performance_issue(
+            event_data=event_data_3, project_id=self.project2.id
+        )
 
 
         """
         """
         event_data_4 = load_data("transaction")
         event_data_4 = load_data("transaction")
@@ -182,23 +183,23 @@ class SnubaEventStorageTest(TestCase, SnubaTestCase):
         with mock.patch("sentry.eventstore.snuba.backend.Event") as mock_event:
         with mock.patch("sentry.eventstore.snuba.backend.Event") as mock_event:
             dummy_event = Event(
             dummy_event = Event(
                 project_id=self.project2.id,
                 project_id=self.project2.id,
-                event_id="f" * 32,
-                data={"something": "hi", "timestamp": self.min_ago},
+                event_id="1" * 32,
+                data={"something": "hi", "timestamp": self.min_ago, "type": "error"},
             )
             )
             mock_event.return_value = dummy_event
             mock_event.return_value = dummy_event
-            event = self.eventstore.get_event_by_id(self.project2.id, "f" * 32)
+            event = self.eventstore.get_event_by_id(self.project2.id, "1" * 32)
             # Result of query should be None
             # Result of query should be None
             assert event is None
             assert event is None
 
 
         # Now we store the event properly, so it will exist in Snuba.
         # Now we store the event properly, so it will exist in Snuba.
         self.store_event(
         self.store_event(
-            data={"event_id": "f" * 32, "timestamp": self.min_ago},
+            data={"event_id": "1" * 32, "timestamp": self.min_ago, "type": "error"},
             project_id=self.project2.id,
             project_id=self.project2.id,
         )
         )
 
 
         # Make sure that the negative cache isn't causing the event to not show up
         # Make sure that the negative cache isn't causing the event to not show up
-        event = self.eventstore.get_event_by_id(self.project2.id, "f" * 32)
-        assert event.event_id == "f" * 32
+        event = self.eventstore.get_event_by_id(self.project2.id, "1" * 32)
+        assert event.event_id == "1" * 32
         assert event.project_id == self.project2.id
         assert event.project_id == self.project2.id
         assert event.group_id == event.group.id
         assert event.group_id == event.group.id
 
 
@@ -299,19 +300,23 @@ class SnubaEventStorageTest(TestCase, SnubaTestCase):
         assert next_event is None
         assert next_event is None
 
 
     def test_transaction_get_next_prev_event_id(self):
     def test_transaction_get_next_prev_event_id(self):
-        group = self.transaction_event_2.groups[0]
+        group = self.transaction_event_2.group
         _filter = Filter(
         _filter = Filter(
             project_ids=[self.project2.id],
             project_ids=[self.project2.id],
-            conditions=apply_performance_conditions([], group),
+            group_ids=[group.id],
+        )
+        event = self.eventstore.get_event_by_id(
+            self.project2.id, self.transaction_event_3.event_id, group_id=group.id
         )
         )
-        event = self.eventstore.get_event_by_id(self.project2.id, "f" * 32)
         prev_event, next_event = self.eventstore.get_adjacent_event_ids(event, filter=_filter)
         prev_event, next_event = self.eventstore.get_adjacent_event_ids(event, filter=_filter)
 
 
-        assert prev_event == (str(self.project2.id), "e" * 32)
+        assert prev_event == (str(self.project2.id), self.transaction_event_2.event_id)
         assert next_event is None
         assert next_event is None
 
 
-        event = self.eventstore.get_event_by_id(self.project2.id, "e" * 32)
+        event = self.eventstore.get_event_by_id(
+            self.project2.id, self.transaction_event_2.event_id, group_id=group.id
+        )
         prev_event, next_event = self.eventstore.get_adjacent_event_ids(event, filter=_filter)
         prev_event, next_event = self.eventstore.get_adjacent_event_ids(event, filter=_filter)
 
 
         assert prev_event is None
         assert prev_event is None
-        assert next_event == (str(self.project2.id), "f" * 32)
+        assert next_event == (str(self.project2.id), self.transaction_event_3.event_id)

+ 41 - 56
tests/snuba/api/endpoints/test_project_event_details.py

@@ -1,10 +1,11 @@
 from django.urls import reverse
 from django.urls import reverse
 
 
-from sentry.issues.grouptype import PerformanceRenderBlockingAssetSpanGroupType
 from sentry.issues.occurrence_consumer import process_event_and_issue_occurrence
 from sentry.issues.occurrence_consumer import process_event_and_issue_occurrence
 from sentry.testutils import APITestCase, SnubaTestCase
 from sentry.testutils import APITestCase, SnubaTestCase
+from sentry.testutils.cases import PerformanceIssueTestCase
 from sentry.testutils.helpers.datetime import before_now, iso_format
 from sentry.testutils.helpers.datetime import before_now, iso_format
 from sentry.testutils.silo import region_silo_test
 from sentry.testutils.silo import region_silo_test
+from sentry.utils.samples import load_data
 from tests.sentry.issues.test_utils import OccurrenceTestMixin
 from tests.sentry.issues.test_utils import OccurrenceTestMixin
 
 
 
 
@@ -203,72 +204,56 @@ class ProjectEventDetailsGenericTest(OccurrenceTestMixin, ProjectEventDetailsTes
 
 
 
 
 @region_silo_test
 @region_silo_test
-class ProjectEventDetailsTransactionTest(APITestCase, SnubaTestCase):
+class ProjectEventDetailsTransactionTest(APITestCase, SnubaTestCase, PerformanceIssueTestCase):
     def setUp(self):
     def setUp(self):
         super().setUp()
         super().setUp()
         self.login_as(user=self.user)
         self.login_as(user=self.user)
         project = self.create_project()
         project = self.create_project()
 
 
-        one_min_ago = iso_format(before_now(minutes=1))
-        two_min_ago = iso_format(before_now(minutes=2))
-        three_min_ago = iso_format(before_now(minutes=3))
-        four_min_ago = iso_format(before_now(minutes=4))
-
-        transaction_event_data = {
-            "level": "info",
-            "message": "ayoo",
-            "type": "transaction",
-            "culprit": "app/components/events/eventEntries in map",
-            "contexts": {"trace": {"trace_id": "b" * 32, "span_id": "c" * 16, "op": ""}},
-        }
-
-        self.prev_transaction_event = self.store_event(
-            data={
-                **transaction_event_data,
-                "event_id": "a" * 32,
-                "timestamp": four_min_ago,
-                "start_timestamp": four_min_ago,
-                "fingerprint": [f"{PerformanceRenderBlockingAssetSpanGroupType.type_id}-group1"],
-            },
+        one_min_ago = before_now(minutes=1)
+        two_min_ago = before_now(minutes=2)
+        three_min_ago = before_now(minutes=3)
+        four_min_ago = before_now(minutes=4)
+
+        self.prev_transaction_event = self.create_performance_issue(
+            event_data=load_data(
+                event_id="a" * 32,
+                platform="transaction-n-plus-one",
+                timestamp=four_min_ago,
+                start_timestamp=four_min_ago,
+            ),
             project_id=project.id,
             project_id=project.id,
         )
         )
-
-        self.cur_transaction_event = self.store_event(
-            data={
-                **transaction_event_data,
-                "event_id": "b" * 32,
-                "timestamp": three_min_ago,
-                "start_timestamp": three_min_ago,
-                "fingerprint": [f"{PerformanceRenderBlockingAssetSpanGroupType.type_id}-group1"],
-            },
+        self.group = self.prev_transaction_event.group
+
+        self.cur_transaction_event = self.create_performance_issue(
+            event_data=load_data(
+                event_id="b" * 32,
+                platform="transaction-n-plus-one",
+                timestamp=three_min_ago,
+                start_timestamp=three_min_ago,
+            ),
             project_id=project.id,
             project_id=project.id,
         )
         )
 
 
-        self.next_transaction_event = self.store_event(
-            data={
-                **transaction_event_data,
-                "event_id": "c" * 32,
-                "timestamp": two_min_ago,
-                "start_timestamp": two_min_ago,
-                "environment": "production",
-                "tags": {"environment": "production"},
-                "fingerprint": [f"{PerformanceRenderBlockingAssetSpanGroupType.type_id}-group1"],
-            },
+        self.next_transaction_event = self.create_performance_issue(
+            event_data=load_data(
+                event_id="c" * 32,
+                platform="transaction-n-plus-one",
+                timestamp=two_min_ago,
+                start_timestamp=two_min_ago,
+            ),
             project_id=project.id,
             project_id=project.id,
         )
         )
 
 
-        self.group = self.prev_transaction_event.groups[0]
-
-        # Event in different group
-        self.store_event(
-            data={
-                **transaction_event_data,
-                "event_id": "d" * 32,
-                "timestamp": one_min_ago,
-                "start_timestamp": one_min_ago,
-                "environment": "production",
-                "tags": {"environment": "production"},
-            },
+        self.create_performance_issue(
+            event_data=load_data(
+                event_id="d" * 32,
+                platform="transaction-n-plus-one",
+                timestamp=one_min_ago,
+                start_timestamp=one_min_ago,
+            ),
+            fingerprint="other_group",
             project_id=project.id,
             project_id=project.id,
         )
         )
 
 
@@ -288,7 +273,7 @@ class ProjectEventDetailsTransactionTest(APITestCase, SnubaTestCase):
         assert response.data["id"] == str(self.cur_transaction_event.event_id)
         assert response.data["id"] == str(self.cur_transaction_event.event_id)
         assert response.data["nextEventID"] == str(self.next_transaction_event.event_id)
         assert response.data["nextEventID"] == str(self.next_transaction_event.event_id)
         assert response.data["previousEventID"] == str(self.prev_transaction_event.event_id)
         assert response.data["previousEventID"] == str(self.prev_transaction_event.event_id)
-        assert response.data["groupID"] == str(self.cur_transaction_event.groups[0].id)
+        assert response.data["groupID"] == str(self.cur_transaction_event.group.id)
 
 
     def test_no_previous_event(self):
     def test_no_previous_event(self):
         """Test the case in which there is no previous event"""
         """Test the case in which there is no previous event"""
@@ -306,7 +291,7 @@ class ProjectEventDetailsTransactionTest(APITestCase, SnubaTestCase):
         assert response.data["id"] == str(self.prev_transaction_event.event_id)
         assert response.data["id"] == str(self.prev_transaction_event.event_id)
         assert response.data["previousEventID"] is None
         assert response.data["previousEventID"] is None
         assert response.data["nextEventID"] == self.cur_transaction_event.event_id
         assert response.data["nextEventID"] == self.cur_transaction_event.event_id
-        assert response.data["groupID"] == str(self.prev_transaction_event.groups[0].id)
+        assert response.data["groupID"] == str(self.prev_transaction_event.group.id)
 
 
     def test_ignores_different_group(self):
     def test_ignores_different_group(self):
         """Test that a different group's events aren't attributed to the one that was passed"""
         """Test that a different group's events aren't attributed to the one that was passed"""