From ff4ab5d4a562910cf80894cb9c85368fea72f6f5 Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Thu, 30 Oct 2025 10:13:04 -0700 Subject: [PATCH 1/3] simplify getting exception component values --- src/sentry/grouping/strategies/newstyle.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/sentry/grouping/strategies/newstyle.py b/src/sentry/grouping/strategies/newstyle.py index 0a1869b93c41cf..c258c111213473 100644 --- a/src/sentry/grouping/strategies/newstyle.py +++ b/src/sentry/grouping/strategies/newstyle.py @@ -13,6 +13,7 @@ ErrorTypeGroupingComponent, ErrorValueGroupingComponent, ExceptionGroupingComponent, + ExceptionGroupingComponentChildren, FilenameGroupingComponent, FrameGroupingComponent, FunctionGroupingComponent, @@ -558,16 +559,6 @@ def single_exception( exception_components_by_variant = {} for variant_name, stacktrace_component in stacktrace_components_by_variant.items(): - values: list[ - ErrorTypeGroupingComponent - | ErrorValueGroupingComponent - | NSErrorGroupingComponent - | StacktraceGroupingComponent - ] = [stacktrace_component, type_component] - - if ns_error_component is not None: - values.append(ns_error_component) - value_component = ErrorValueGroupingComponent() raw = exception.value @@ -593,7 +584,11 @@ def single_exception( hint="ignored because ns-error info takes precedence", ) - values.append(value_component) + values: list[ExceptionGroupingComponentChildren] = [] + if ns_error_component is not None: + values = [stacktrace_component, type_component, ns_error_component, value_component] + else: + values = [stacktrace_component, type_component, value_component] exception_components_by_variant[variant_name] = ExceptionGroupingComponent( values=values, frame_counts=stacktrace_component.frame_counts From 9b9fe756a3fe2383fef00777bc95ae89eca86920 Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Thu, 30 Oct 2025 10:13:04 -0700 Subject: [PATCH 2/3] only check config option existence and config validity once --- src/sentry/grouping/ingest/config.py | 33 +++++++++++----------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/sentry/grouping/ingest/config.py b/src/sentry/grouping/ingest/config.py index 1105b4505e9750..3614cd05508bf9 100644 --- a/src/sentry/grouping/ingest/config.py +++ b/src/sentry/grouping/ingest/config.py @@ -35,22 +35,22 @@ def update_or_set_grouping_config_if_needed(project: Project, source: str) -> st use by scripts. """ current_config = project.get_option("sentry:grouping_config") + current_config_is_valid = current_config in GROUPING_CONFIG_CLASSES.keys() + + # If the project's current config comes back as the default one, it might be because that's + # actually what's set in the database for that project, or it might be relying on the default + # value of that project option. In the latter case, we can use this upgrade check as a chance to + # set it. (We want projects to have their own record of the config they're using, so that when + # we introduce a new one, we know to transition them.) + project_option_exists = ProjectOption.objects.filter( + key="sentry:grouping_config", project_id=project.id + ).exists() if current_config == BETA_GROUPING_CONFIG: return "skipped - beta config" - if current_config == DEFAULT_GROUPING_CONFIG: - # If the project's current config comes back as the default one, it might be because that's - # actually what's set in the database for that project, or it might be relying on the - # default value of that project option. In the latter case, we can use this upgrade check as - # a chance to set it. (We want projects to have their own record of the config they're - # using, so that when we introduce a new one, we know to transition them.) - project_option_exists = ProjectOption.objects.filter( - key="sentry:grouping_config", project_id=project.id - ).exists() - - if project_option_exists: - return "skipped - up-to-date record exists" + if current_config == DEFAULT_GROUPING_CONFIG and project_option_exists: + return "skipped - up-to-date record exists" # We want to try to write the audit log entry and project option change just once, so we use a # cache key to avoid raciness. It's not perfect, but it reduces the risk significantly. @@ -69,10 +69,7 @@ def update_or_set_grouping_config_if_needed(project: Project, source: str) -> st changes: dict[str, str | int] = {"sentry:grouping_config": DEFAULT_GROUPING_CONFIG} # If the current config is out of date but still valid, start a transition period - if ( - current_config != DEFAULT_GROUPING_CONFIG - and current_config in GROUPING_CONFIG_CLASSES.keys() - ): + if current_config != DEFAULT_GROUPING_CONFIG and current_config_is_valid: # This is when we will stop calculating the old hash in cases where we don't find the # new hash (which we do in an effort to preserve group continuity). transition_expiry = ( @@ -86,10 +83,6 @@ def update_or_set_grouping_config_if_needed(project: Project, source: str) -> st } ) - project_option_exists = ProjectOption.objects.filter( - key="sentry:grouping_config", project_id=project.id - ).exists() - for key, value in changes.items(): project.update_option(key, value) From d6afe918537033b176dd453558484b801c6641d7 Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Wed, 29 Oct 2025 15:16:59 -0700 Subject: [PATCH 3/3] switch to using `has_url_origin` in single-frame JS check --- src/sentry/grouping/strategies/newstyle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry/grouping/strategies/newstyle.py b/src/sentry/grouping/strategies/newstyle.py index c258c111213473..5c309174798437 100644 --- a/src/sentry/grouping/strategies/newstyle.py +++ b/src/sentry/grouping/strategies/newstyle.py @@ -462,7 +462,7 @@ def _single_stacktrace_variant( and frame_components[0].contributes and get_behavior_family_for_platform(frames[0].platform or event.platform) == "javascript" and not frames[0].function - and frames[0].is_url() + and has_url_origin(frames[0].abs_path, files_count_as_urls=True) ): frame_components[0].update( contributes=False, hint="ignored single non-URL JavaScript frame"