From 236376c50a8cdd166a427d77bfe4f38c6f0751b4 Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Thu, 18 Jun 2026 13:04:34 -0700 Subject: [PATCH 1/3] include seer match distance in merged issues response --- src/sentry/issues/endpoints/group_hashes.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/sentry/issues/endpoints/group_hashes.py b/src/sentry/issues/endpoints/group_hashes.py index 4b9c23b0523127..b06845221ce6ac 100644 --- a/src/sentry/issues/endpoints/group_hashes.py +++ b/src/sentry/issues/endpoints/group_hashes.py @@ -42,6 +42,7 @@ class GroupHashesResult(TypedDict): id: str latestEvent: EventSerializerResponse | SimpleEventSerializerResponse | None mergedBySeer: bool + seerMatchDistance: float | None @extend_schema(tags=["Events"]) @@ -180,15 +181,19 @@ def __handle_result( grouphash: GroupHash | None = None, ) -> GroupHashesResult: event = eventstore.backend.get_event_by_id(project_id, result["event_id"]) - merged_by_seer = bool( - grouphash and grouphash.metadata and grouphash.metadata.seer_matched_grouphash - ) + if grouphash and grouphash.metadata and grouphash.metadata.seer_matched_grouphash: + merged_by_seer = True + seer_match_distance = grouphash.metadata.seer_match_distance + else: + merged_by_seer = False + seer_match_distance = None serializer = EventSerializer if full else SimpleEventSerializer response: GroupHashesResult = { "id": result["primary_hash"], "latestEvent": serialize(event, user, serializer()), "mergedBySeer": merged_by_seer, + "seerMatchDistance": seer_match_distance, } return response From 6004464775f37c4c76d63fe04e765077a7f71a1f Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Thu, 18 Jun 2026 13:04:35 -0700 Subject: [PATCH 2/3] update example response --- src/sentry/apidocs/examples/event_examples.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sentry/apidocs/examples/event_examples.py b/src/sentry/apidocs/examples/event_examples.py index f915d291d56b68..85beaf011403e8 100644 --- a/src/sentry/apidocs/examples/event_examples.py +++ b/src/sentry/apidocs/examples/event_examples.py @@ -626,7 +626,8 @@ class EventExamples: { "id": "53aa54ab27474eaab1fc1d0f7f86d2f1", "latestEvent": EVENT_RESPONSE, - "mergedBySeer": False, + "mergedBySeer": True, + "seerMatchDistance": 0.01, } ], response_only=True, From fc0587b2b2108f432e72b2aa9c3ab8dae95dc695 Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Thu, 18 Jun 2026 13:04:35 -0700 Subject: [PATCH 3/3] fix test --- .../issues/endpoints/test_group_hashes.py | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/tests/sentry/issues/endpoints/test_group_hashes.py b/tests/sentry/issues/endpoints/test_group_hashes.py index 1afed4d5f64e4b..72c37a5d9406dc 100644 --- a/tests/sentry/issues/endpoints/test_group_hashes.py +++ b/tests/sentry/issues/endpoints/test_group_hashes.py @@ -3,7 +3,6 @@ from sentry.eventstream.snuba import SnubaEventStream from sentry.models.grouphash import GroupHash -from sentry.models.grouphashmetadata import GroupHashMetadata from sentry.testutils.cases import APITestCase, SnubaTestCase from sentry.testutils.helpers.datetime import before_now @@ -120,32 +119,20 @@ def test_return_multiple_hashes_with_seer_match(self) -> None: eventstream.end_merge(state) - # Get the grouphashes for both events (refresh after merge) + # Get the grouphashes for both events hash1 = event1.get_primary_hash() hash2 = event2.get_primary_hash() - - # Refresh the grouphashes after merge to get updated group assignments grouphash1 = GroupHash.objects.get(project=self.project, hash=hash1) grouphash2 = GroupHash.objects.get(project=self.project, hash=hash2) + assert grouphash2.metadata - # Manually update grouphash2 to point to the merged group (event1.group_id) + # Manually update grouphash2 to point to the merged group (event1.group_id) and its metadata + # to reflect the Seer match grouphash2.group = event1.group + grouphash2.metadata.seer_matched_grouphash = grouphash1 + grouphash2.metadata.seer_match_distance = 0.01 grouphash2.save() - - # Get or create metadata for both grouphashes - metadata1, _ = GroupHashMetadata.objects.get_or_create( - grouphash=grouphash1, defaults={"schema_version": "8"} - ) - metadata2, _ = GroupHashMetadata.objects.get_or_create( - grouphash=grouphash2, - defaults={ - "schema_version": "8", - "seer_matched_grouphash": grouphash1, # hash2 points to hash1 as its seer match - }, - ) - # Update the seer match if metadata already existed - metadata2.seer_matched_grouphash = grouphash1 - metadata2.save() + grouphash2.metadata.save() url = f"/api/0/organizations/{self.organization.slug}/issues/{event1.group_id}/hashes/" response = self.client.get(url, format="json") @@ -159,9 +146,11 @@ def test_return_multiple_hashes_with_seer_match(self) -> None: # hash1 should not be matched by seer (it's the parent) assert hash1_data["mergedBySeer"] is False + assert hash1_data["seerMatchDistance"] is None # hash2 should be matched by seer (it points to hash1) assert hash2_data["mergedBySeer"] is True + assert hash2_data["seerMatchDistance"] == 0.01 def test_full_param(self) -> None: self.login_as(user=self.user)