diff --git a/src/sentry/features/temporary.py b/src/sentry/features/temporary.py index bce608914410..5b941ee53b17 100644 --- a/src/sentry/features/temporary.py +++ b/src/sentry/features/temporary.py @@ -304,6 +304,8 @@ def register_temporary_features(manager: FeatureManager) -> None: manager.add("organizations:issue-activity-feed-v2", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True) # Enable new stack trace component for issue details manager.add("organizations:issue-details-new-stack-trace", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True) + # Enable invalidating group derived data when groups are merged + manager.add("organizations:hard-delete-derived-data-invalidation", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False) # Enable double-reading from EAP for issue feed search queries manager.add("organizations:issue-feed.eap-search", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False) # Remove trace and breadcrumbs from issue summary input diff --git a/src/sentry/tasks/merge.py b/src/sentry/tasks/merge.py index cee271251bc1..d79fd1e5b2a4 100644 --- a/src/sentry/tasks/merge.py +++ b/src/sentry/tasks/merge.py @@ -8,8 +8,9 @@ from django.db.models.functions import Coalesce from taskbroker_client.retry import Retry -from sentry import eventstream, similarity, tsdb +from sentry import eventstream, features, similarity, tsdb from sentry.db.models.base import Model +from sentry.issues.derived.processing import invalidate_group_derived_data from sentry.issues.models.groupactionlogentry import GroupActionLogEntry from sentry.models.group import Group from sentry.silo.base import SiloMode @@ -59,7 +60,10 @@ def merge_groups( from_object_id = from_object_ids[0] try: - new_group, _ = get_group_with_redirect(to_object_id) + new_group, _ = get_group_with_redirect( + to_object_id, + queryset=Group.objects.select_related("project__organization"), + ) except Group.DoesNotExist: logger.warning( "group.malformed.invalid_id", @@ -206,9 +210,17 @@ def merge_groups( recursed=True, eventstream_state=eventstream_state, ) - elif eventstream_state: - # All `from_object_ids` have been merged! - eventstream.backend.end_merge(eventstream_state) + else: + if features.has( + "organizations:hard-delete-derived-data-invalidation", + new_group.project.organization, + ): + # hard delete derived data on the new group - it will be rebuilt when the next action is processed + invalidate_group_derived_data(new_group.id) + + if eventstream_state: + # All `from_object_ids` have been merged! + eventstream.backend.end_merge(eventstream_state) return True diff --git a/tests/sentry/tasks/test_merge.py b/tests/sentry/tasks/test_merge.py index 4a19075844a3..62412d183c21 100644 --- a/tests/sentry/tasks/test_merge.py +++ b/tests/sentry/tasks/test_merge.py @@ -2,6 +2,7 @@ from unittest.mock import patch from sentry import buffer, eventstream +from sentry.issues.models.groupderiveddata import GroupDerivedData from sentry.models.group import Group from sentry.models.groupenvironment import GroupEnvironment from sentry.models.groupmeta import GroupMeta @@ -13,6 +14,7 @@ from sentry.tasks.post_process import fetch_buffered_group_stats from sentry.testutils.cases import SnubaTestCase, TestCase from sentry.testutils.helpers.datetime import before_now +from sentry.testutils.helpers.features import with_feature from sentry.testutils.helpers.redis import mock_redis_buffer from sentry.utils import redis @@ -222,6 +224,20 @@ def test_merge_includes_pending_buffer_increments(self) -> None: assert new_group.times_seen_pending == 3 assert new_group.times_seen_with_pending == 168 + @with_feature("organizations:hard-delete-derived-data-invalidation") + def test_merge_invalidates_derived_data(self) -> None: + project = self.create_project() + old_group = self.create_group(project) + new_group = self.create_group(project) + + GroupDerivedData.objects.create(group=new_group) + + with self.tasks(): + merge_groups([old_group.id], new_group.id) + + assert Group.objects.filter(id=old_group.id).exists() is False + assert GroupDerivedData.objects.filter(group_id=new_group.id).exists() is False + @mock_redis_buffer() def test_merge_original_group_id(self) -> None: project = self.create_project()