|  | 
| 7 | 7 | 
 | 
| 8 | 8 | from snuba_sdk import Column, Condition, Entity, Function, Op, Query, Request | 
| 9 | 9 | 
 | 
| 10 |  | -from sentry import nodestore | 
|  | 10 | +from sentry import deletions, nodestore | 
| 11 | 11 | from sentry.deletions.defaults.group import ErrorEventsDeletionTask | 
| 12 | 12 | from sentry.deletions.tasks.groups import delete_groups_for_project | 
| 13 | 13 | from sentry.issues.grouptype import FeedbackGroup, GroupCategory | 
|  | 
| 16 | 16 | from sentry.models.group import Group | 
| 17 | 17 | from sentry.models.groupassignee import GroupAssignee | 
| 18 | 18 | from sentry.models.grouphash import GroupHash | 
|  | 19 | +from sentry.models.grouphashmetadata import GroupHashMetadata | 
| 19 | 20 | from sentry.models.grouphistory import GroupHistory, GroupHistoryStatus | 
| 20 | 21 | from sentry.models.groupmeta import GroupMeta | 
| 21 | 22 | from sentry.models.groupredirect import GroupRedirect | 
| @@ -254,6 +255,69 @@ def test_invalid_group_type_handling( | 
| 254 | 255 |                 "args": [self.project.id, error_group_hashes, 0] | 
| 255 | 256 |             } | 
| 256 | 257 | 
 | 
|  | 258 | +    def test_delete_grouphash_metadata_first(self) -> None: | 
|  | 259 | +        """ | 
|  | 260 | +        Test that the group hash metadata is deleted first when the group hash is deleted. | 
|  | 261 | +        """ | 
|  | 262 | +        # This enables checking Seer for similarity and to mock the call to return a specific grouphash | 
|  | 263 | +        self.project.update_option("sentry:similarity_backfill_completed", int(time())) | 
|  | 264 | + | 
|  | 265 | +        # Create two events/grouphashes and one of them | 
|  | 266 | +        # Event A will be deleted | 
|  | 267 | +        event_a = self.store_event( | 
|  | 268 | +            data={ | 
|  | 269 | +                "platform": "python", | 
|  | 270 | +                "stacktrace": {"frames": [{"filename": "error_a.py"}]}, | 
|  | 271 | +            }, | 
|  | 272 | +            project_id=self.project.id, | 
|  | 273 | +        ) | 
|  | 274 | +        grouphash_a = GroupHash.objects.get(group_id=event_a.group_id) | 
|  | 275 | +        # assert grouphash_a.metadata is not None | 
|  | 276 | +        assert grouphash_a.metadata.seer_matched_grouphash is None | 
|  | 277 | +        metadata_a_id = grouphash_a.metadata.id | 
|  | 278 | + | 
|  | 279 | +        with mock.patch( | 
|  | 280 | +            "sentry.grouping.ingest.seer.get_seer_similar_issues" | 
|  | 281 | +        ) as mock_get_seer_similar_issues: | 
|  | 282 | +            # This will allow grouphash_b to be matched to grouphash_a by Seer | 
|  | 283 | +            mock_get_seer_similar_issues.return_value = (0.01, grouphash_a) | 
|  | 284 | + | 
|  | 285 | +            # Event B will be kept - different exception to ensure different group hash to grouphash_a | 
|  | 286 | +            event_b = self.store_event( | 
|  | 287 | +                data={ | 
|  | 288 | +                    "platform": "python", | 
|  | 289 | +                    "stacktrace": {"frames": [{"filename": "error_b.py"}]}, | 
|  | 290 | +                }, | 
|  | 291 | +                project_id=self.project.id, | 
|  | 292 | +            ) | 
|  | 293 | +            grouphash_b = GroupHash.objects.get(hash=event_b.get_primary_hash()) | 
|  | 294 | +            # assert grouphash_b.metadata is not None | 
|  | 295 | +            assert grouphash_b.metadata is not None | 
|  | 296 | +            metadata_b_id = grouphash_b.metadata.id | 
|  | 297 | + | 
|  | 298 | +            # Verify that seer matched event_b to event_a's hash | 
|  | 299 | +            assert event_a.group_id == event_b.group_id | 
|  | 300 | +            # Make sure it has not changed | 
|  | 301 | +            assert grouphash_a.metadata is not None | 
|  | 302 | +            assert grouphash_a.metadata.seer_matched_grouphash is None | 
|  | 303 | +            assert grouphash_b.metadata is not None | 
|  | 304 | +            assert grouphash_b.metadata.seer_matched_grouphash == grouphash_a | 
|  | 305 | + | 
|  | 306 | +            with ( | 
|  | 307 | +                self.tasks(), | 
|  | 308 | +                self.options({"deletions.group.delete_group_hashes_metadata_first": True}), | 
|  | 309 | +            ): | 
|  | 310 | +                # It will delete all groups, group hashes and group hash metadata | 
|  | 311 | +                task = deletions.get(model=Group, query={"id__in": [event_a.group_id]}) | 
|  | 312 | +                more = task.chunk() | 
|  | 313 | +                assert not more | 
|  | 314 | + | 
|  | 315 | +            assert not Group.objects.filter(id=event_a.group_id).exists() | 
|  | 316 | +            assert not GroupHash.objects.filter(id=grouphash_a.id).exists() | 
|  | 317 | +            assert not GroupHashMetadata.objects.filter(id=metadata_a_id).exists() | 
|  | 318 | +            assert not GroupHash.objects.filter(id=grouphash_b.id).exists() | 
|  | 319 | +            assert not GroupHashMetadata.objects.filter(id=metadata_b_id).exists() | 
|  | 320 | + | 
| 257 | 321 | 
 | 
| 258 | 322 | class DeleteIssuePlatformTest(TestCase, SnubaTestCase, OccurrenceTestMixin): | 
| 259 | 323 |     referrer = Referrer.TESTING_TEST.value | 
|  | 
0 commit comments