diff --git a/src/sentry/event_manager.py b/src/sentry/event_manager.py index d9b1e82e5c7428..c72070803de380 100644 --- a/src/sentry/event_manager.py +++ b/src/sentry/event_manager.py @@ -1,6 +1,7 @@ from __future__ import annotations import copy +import hashlib import ipaddress import logging import mimetypes @@ -2551,7 +2552,13 @@ def _calculate_event_grouping( # event. If that config has since been deleted (because it was an # experimental grouping config) we fall back to the default. try: - hashes = event.get_hashes(grouping_config) + hashes = None + # If the event platform is javascript, it could have a ChunkLoadError that we want to force group + if event.platform == "javascript": + hashes = get_chunk_load_error_hash(event) + + if not hashes: + hashes = event.get_hashes(grouping_config) except GroupingConfigNotFound: event.data["grouping_config"] = get_grouping_config_dict_for_project(project) hashes = event.get_hashes() @@ -2593,6 +2600,30 @@ def _detect_performance_problems(jobs: Sequence[Job], projects: ProjectsMapping) ) +def get_chunk_load_error_hash(event: Event) -> Optional[CalculatedHashes]: + """ + Return the same hash if the event is a ChunkLoadError, otherwise return None + """ + try: + exception_value = event.data["exception"]["values"][0]["value"] + exception_type = event.data["exception"]["values"][0]["type"] + except KeyError: + return None + + hashes = None + # Only check for the flag after it is established if it's a ChunkLoadError to avoid + # unnecessary querying + if "ChunkLoadError" in exception_value or exception_type == "ChunkLoadError": + organization = Project.objects.get(id=event.project_id).organization + if features.has("organizations:group-chunk-load-errors", organization): + hashes = CalculatedHashes( + hashes=[hashlib.md5(b"chunkloaderror").hexdigest()], + hierarchical_hashes=[], + tree_labels=[], + ) + return hashes + + class PerformanceJob(TypedDict, total=False): performance_problems: Sequence[PerformanceProblem] event: Event diff --git a/tests/sentry/event_manager/test_event_manager_grouping.py b/tests/sentry/event_manager/test_event_manager_grouping.py index 1d0f5c74a6d701..e9c4eeff4b9f4f 100644 --- a/tests/sentry/event_manager/test_event_manager_grouping.py +++ b/tests/sentry/event_manager/test_event_manager_grouping.py @@ -9,6 +9,7 @@ from sentry.models.grouphash import GroupHash from sentry.testutils.cases import TestCase from sentry.testutils.helpers.datetime import before_now, freeze_time, iso_format +from sentry.testutils.helpers.features import with_feature from sentry.testutils.silo import region_silo_test from sentry.testutils.skips import requires_snuba from sentry.tsdb.base import TSDBModel @@ -342,3 +343,138 @@ def test_ignores_fingerprint_on_transaction_event(self): )[event1.group.id] == 1 ) + + @with_feature("organizations:group-chunk-load-errors") + def test_group_chunk_load_errors_flag_on(self): + """ + Test that when `group-chunk-load-errors` flag is on, ChunkLoadErrors with different stack + traces and values are grouped together + """ + manager = EventManager( + make_event( + platform="javascript", + exception={ + "values": [ + { + "type": "Error", + "stacktrace": { + "frames": [ + { + "function": "in_app_function", + } + ] + }, + "value": "ChunkLoadError: Loading chunk 123 failed", + "in_app": True, + } + ] + }, + ) + ) + with self.tasks(): + manager.normalize() + event = manager.save(self.project.id) + + manager = EventManager( + make_event( + platform="javascript", + exception={ + "values": [ + { + "type": "ChunkLoadError", + "stacktrace": { + "frames": [ + { + "function": "different_in_app_function", + } + ] + }, + "value": "Loading chunk 321 failed", + "in_app": True, + } + ] + }, + ) + ) + + with self.tasks(): + manager.normalize() + event2 = manager.save(self.project.id) + + assert event.group_id == event2.group_id + + @with_feature({"organizations:group-chunk-load-errors": False}) + def test_do_not_group_chunk_load_errors_flag_off(self): + """ + Test that when `group-chunk-load-errors` flag is off, ChunkLoadErrors with different stack + traces and values are not grouped together + """ + manager = EventManager( + make_event( + platform="javascript", + exception={ + "values": [ + { + "type": "Error", + "stacktrace": { + "frames": [ + { + "function": "in_app_function", + } + ] + }, + "value": "ChunkLoadError: Loading chunk 123 failed", + "in_app": True, + } + ] + }, + ) + ) + with self.tasks(): + manager.normalize() + event = manager.save(self.project.id) + + manager = EventManager( + make_event( + platform="javascript", + exception={ + "values": [ + { + "type": "ChunkLoadError", + "stacktrace": { + "frames": [ + { + "function": "different_in_app_function", + } + ] + }, + "value": "Loading chunk 321 failed", + "in_app": True, + } + ] + }, + ) + ) + + with self.tasks(): + manager.normalize() + event2 = manager.save(self.project.id) + + assert event.group_id != event2.group_id + + def test_chunk_load_errors_exception(self): + """ + Test that when an error does not have an exception, the `get_chunk_load_error_hash` + function catches the exception and does not stop execution + """ + manager = EventManager( + make_event( + platform="javascript", + exception={}, + ) + ) + with self.tasks(): + manager.normalize() + event = manager.save(self.project.id) + + assert event.group