From 8056e5eaf781e15359f86116fc484d4eb74cb53f Mon Sep 17 00:00:00 2001 From: Mihir Mavalankar Date: Wed, 26 Nov 2025 14:07:56 -0800 Subject: [PATCH] feat(triage signals): Swap project level feature flag with org level --- src/sentry/features/temporary.py | 2 +- src/sentry/seer/autofix/issue_summary.py | 19 +++++++++----- src/sentry/tasks/post_process.py | 2 +- .../sentry/seer/autofix/test_issue_summary.py | 10 +++---- tests/sentry/tasks/test_post_process.py | 26 ++++++++++++++----- 5 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/sentry/features/temporary.py b/src/sentry/features/temporary.py index 462bbdabb0a594..ed10d9cfadf4d3 100644 --- a/src/sentry/features/temporary.py +++ b/src/sentry/features/temporary.py @@ -674,7 +674,7 @@ def register_temporary_features(manager: FeatureManager) -> None: manager.add("projects:plugins", ProjectPluginFeature, FeatureHandlerStrategy.INTERNAL, default=True, api_expose=True) # Enables experimental span v2 processing in Relay. manager.add("projects:span-v2-experimental-processing", ProjectFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False) - # Enbale Triage signals V0 for AI powered issue classifiaction in sentry + # Enable Triage signals V0 for AI powered issue classification in sentry manager.add("projects:triage-signals-v0", ProjectFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False) manager.add("projects:profiling-ingest-unsampled-profiles", ProjectFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False) diff --git a/src/sentry/seer/autofix/issue_summary.py b/src/sentry/seer/autofix/issue_summary.py index a7f3b1fab218ab..772b0dcde24519 100644 --- a/src/sentry/seer/autofix/issue_summary.py +++ b/src/sentry/seer/autofix/issue_summary.py @@ -320,9 +320,9 @@ def run_automation( if source == SeerAutomationSource.ISSUE_DETAILS: return - # Check event count for ALERT source with triage-signals-v0 + # Check event count for ALERT source with triage-signals-v0-org if source == SeerAutomationSource.ALERT and features.has( - "projects:triage-signals-v0", group.project + "organizations:triage-signals-v0-org", group.organization ): # Use times_seen_with_pending if available (set by post_process), otherwise fall back times_seen = ( @@ -333,19 +333,24 @@ def run_automation( if times_seen < 10: logger.info( "Triage signals V0: skipping alert automation, event count < 10", - extra={"group_id": group.id, "event_count": times_seen}, + extra={ + "group_id": group.id, + "project_id": group.project.id, + "event_count": times_seen, + }, ) return - # Only log for projects with triage-signals-v0 - if features.has("projects:triage-signals-v0", group.project): + # Only log for orgs with triage-signals-v0-org + if features.has("organizations:triage-signals-v0-org", group.organization): try: times_seen = group.times_seen_with_pending except (AssertionError, AttributeError): times_seen = group.times_seen logger.info( - "Triage signals V0: %s: run_automation called: source=%s, times_seen=%s", + "Triage signals V0: %s: run_automation called: project_id=%s, source=%s, times_seen=%s", group.id, + group.project.id, source.value, times_seen, ) @@ -389,7 +394,7 @@ def run_automation( return stopping_point = None - if features.has("projects:triage-signals-v0", group.project): + if features.has("organizations:triage-signals-v0-org", group.organization): logger.info("Triage signals V0: %s: generating stopping point", group.id) fixability_stopping_point = _get_stopping_point_from_fixability(fixability_score) logger.info("Fixability-based stopping point: %s", fixability_stopping_point) diff --git a/src/sentry/tasks/post_process.py b/src/sentry/tasks/post_process.py index d3330a01e082ee..5a8aab091e899a 100644 --- a/src/sentry/tasks/post_process.py +++ b/src/sentry/tasks/post_process.py @@ -1618,7 +1618,7 @@ def kick_off_seer_automation(job: PostProcessJob) -> None: group = event.group # Default behaviour - if not features.has("projects:triage-signals-v0", group.project): + if not features.has("organizations:triage-signals-v0-org", group.organization): # Only run on issues with no existing scan if group.seer_fixability_score is not None: return diff --git a/tests/sentry/seer/autofix/test_issue_summary.py b/tests/sentry/seer/autofix/test_issue_summary.py index 015e0fa03abeb8..3ccc668c6b3af3 100644 --- a/tests/sentry/seer/autofix/test_issue_summary.py +++ b/tests/sentry/seer/autofix/test_issue_summary.py @@ -771,7 +771,7 @@ def test_stopping_point_mapping(self, score, expected): assert _get_stopping_point_from_fixability(score) == expected -@with_feature({"organizations:gen-ai-features": True, "projects:triage-signals-v0": True}) +@with_feature({"organizations:gen-ai-features": True, "organizations:triage-signals-v0-org": True}) class TestRunAutomationStoppingPoint(APITestCase, SnubaTestCase): def setUp(self) -> None: super().setUp() @@ -851,7 +851,7 @@ def test_without_feature_flag(self, mock_gen, mock_budget, mock_state, mock_rate ) with self.feature( - {"organizations:gen-ai-features": True, "projects:triage-signals-v0": False} + {"organizations:gen-ai-features": True, "organizations:triage-signals-v0-org": False} ): run_automation(self.group, self.user, self.event, SeerAutomationSource.ALERT) @@ -974,7 +974,7 @@ def test_upper_bound_combinations(self, fixability, user_pref, expected): assert result == expected -@with_feature({"organizations:gen-ai-features": True, "projects:triage-signals-v0": True}) +@with_feature({"organizations:gen-ai-features": True, "organizations:triage-signals-v0-org": True}) class TestRunAutomationWithUpperBound(APITestCase, SnubaTestCase): def setUp(self) -> None: super().setUp() @@ -1080,7 +1080,7 @@ def test_no_user_preference_uses_fixability_only( @with_feature("organizations:gen-ai-features") -@with_feature("projects:triage-signals-v0") +@with_feature("organizations:triage-signals-v0-org") class TestRunAutomationAlertEventCount(APITestCase, SnubaTestCase): def setUp(self) -> None: super().setUp() @@ -1096,7 +1096,7 @@ def setUp(self) -> None: def test_alert_skips_automation_below_threshold( self, mock_budget, mock_state, mock_fixability, mock_trigger ): - """Alert automation should skip when event count < 10 with triage-signals-v0""" + """Alert automation should skip when event count < 10 with triage-signals-v0-org""" self.project.update_option("sentry:autofix_automation_tuning", "always") mock_budget.return_value = True mock_state.return_value = None diff --git a/tests/sentry/tasks/test_post_process.py b/tests/sentry/tasks/test_post_process.py index 88a9a0e1d73197..4c1dd311d89e74 100644 --- a/tests/sentry/tasks/test_post_process.py +++ b/tests/sentry/tasks/test_post_process.py @@ -3033,14 +3033,16 @@ def test_kick_off_seer_automation_with_hide_ai_features_enabled( class TriageSignalsV0TestMixin(BasePostProgressGroupMixin): - """Tests for the triage signals V0 flow behind the projects:triage-signals-v0 feature flag.""" + """Tests for the triage signals V0 flow behind the organizations:triage-signals-v0-org feature flag.""" @patch( "sentry.seer.seer_setup.get_seer_org_acknowledgement_for_scanner", return_value=True, ) @patch("sentry.tasks.autofix.generate_issue_summary_only.delay") - @with_feature({"organizations:gen-ai-features": True, "projects:triage-signals-v0": True}) + @with_feature( + {"organizations:gen-ai-features": True, "organizations:triage-signals-v0-org": True} + ) def test_triage_signals_event_count_less_than_10_no_cache( self, mock_generate_summary_only, mock_get_seer_org_acknowledgement ): @@ -3072,7 +3074,9 @@ def test_triage_signals_event_count_less_than_10_no_cache( return_value=True, ) @patch("sentry.tasks.autofix.generate_issue_summary_only.delay") - @with_feature({"organizations:gen-ai-features": True, "projects:triage-signals-v0": True}) + @with_feature( + {"organizations:gen-ai-features": True, "organizations:triage-signals-v0-org": True} + ) def test_triage_signals_event_count_less_than_10_with_cache( self, mock_generate_summary_only, mock_get_seer_org_acknowledgement ): @@ -3105,7 +3109,9 @@ def test_triage_signals_event_count_less_than_10_with_cache( return_value=True, ) @patch("sentry.tasks.autofix.run_automation_only_task.delay") - @with_feature({"organizations:gen-ai-features": True, "projects:triage-signals-v0": True}) + @with_feature( + {"organizations:gen-ai-features": True, "organizations:triage-signals-v0-org": True} + ) def test_triage_signals_event_count_gte_10_with_cache( self, mock_run_automation, mock_get_seer_org_acknowledgement ): @@ -3152,7 +3158,9 @@ def mock_buffer_get(model, columns, filters): return_value=True, ) @patch("sentry.tasks.autofix.generate_summary_and_run_automation.delay") - @with_feature({"organizations:gen-ai-features": True, "projects:triage-signals-v0": True}) + @with_feature( + {"organizations:gen-ai-features": True, "organizations:triage-signals-v0-org": True} + ) def test_triage_signals_event_count_gte_10_no_cache( self, mock_generate_summary_and_run_automation, mock_get_seer_org_acknowledgement ): @@ -3193,7 +3201,9 @@ def mock_buffer_get(model, columns, filters): return_value=True, ) @patch("sentry.tasks.autofix.run_automation_only_task.delay") - @with_feature({"organizations:gen-ai-features": True, "projects:triage-signals-v0": True}) + @with_feature( + {"organizations:gen-ai-features": True, "organizations:triage-signals-v0-org": True} + ) def test_triage_signals_event_count_gte_10_skips_with_seer_last_triggered( self, mock_run_automation, mock_get_seer_org_acknowledgement ): @@ -3241,7 +3251,9 @@ def mock_buffer_get(model, columns, filters): return_value=True, ) @patch("sentry.tasks.autofix.run_automation_only_task.delay") - @with_feature({"organizations:gen-ai-features": True, "projects:triage-signals-v0": True}) + @with_feature( + {"organizations:gen-ai-features": True, "organizations:triage-signals-v0-org": True} + ) def test_triage_signals_event_count_gte_10_skips_with_existing_fixability_score( self, mock_run_automation, mock_get_seer_org_acknowledgement ):