diff --git a/src/sentry/api/endpoints/project_details.py b/src/sentry/api/endpoints/project_details.py index f34affb4d0fbd8..6fb5152539af89 100644 --- a/src/sentry/api/endpoints/project_details.py +++ b/src/sentry/api/endpoints/project_details.py @@ -228,6 +228,7 @@ class ProjectAdminSerializer(ProjectMemberSerializer): performanceIssueSendToPlatform = serializers.BooleanField(required=False) tempestFetchScreenshots = serializers.BooleanField(required=False) tempestFetchDumps = serializers.BooleanField(required=False) + autoRunIssueSummaries = serializers.BooleanField(required=False) # DO NOT ADD MORE TO OPTIONS # Each param should be a field in the serializer like above. @@ -455,6 +456,15 @@ def validate_tempestFetchDumps(self, value): ) return value + def validate_autoRunIssueSummaries(self, value): + organization = self.context["project"] + actor = self.context["request"] + if not features.has("projects:trigger-issue-summary-on-alerts", organization, actor=actor): + raise serializers.ValidationError( + "Your organization does not have access to enable automatic issue summaries." + ) + return value + class RelaxedProjectPermission(ProjectPermission): scope_map = { @@ -762,6 +772,14 @@ def put(self, request: Request, project) -> Response: "dynamicSamplingBiases" ] + if result.get("autoRunIssueSummaries") is not None: + if project.update_option( + "sentry:auto_run_issue_summaries", result["autoRunIssueSummaries"] + ): + changed_proj_settings["sentry:auto_run_issue_summaries"] = result[ + "autoRunIssueSummaries" + ] + if has_elevated_scopes: options = result.get("options", {}) if "sentry:origins" in options: diff --git a/src/sentry/api/serializers/models/project.py b/src/sentry/api/serializers/models/project.py index 5fef46b3ef6920..c21bd1edcf7b75 100644 --- a/src/sentry/api/serializers/models/project.py +++ b/src/sentry/api/serializers/models/project.py @@ -1099,6 +1099,9 @@ def serialize( }, "symbolSources": serialized_sources, "isDynamicallySampled": sample_rate is not None and sample_rate < 1.0, + "autoRunIssueSummaries": bool( + self.get_value_with_default(attrs, "sentry:auto_run_issue_summaries") + ), } if has_tempest_access(obj.organization, user): @@ -1149,6 +1152,9 @@ def format_options(self, attrs: Mapping[str, Any]) -> dict[str, Any]: self.get_value_with_default(attrs, "sentry:toolbar_allowed_origins") or [] ), "quotas:spike-protection-disabled": options.get("quotas:spike-protection-disabled"), + "sentry:auto_run_issue_summaries": bool( + self.get_value_with_default(attrs, "sentry:auto_run_issue_summaries") + ), } def get_value_with_default(self, attrs, key): diff --git a/src/sentry/models/options/project_option.py b/src/sentry/models/options/project_option.py index 78d30aa07bfe1b..974db67c4c7a86 100644 --- a/src/sentry/models/options/project_option.py +++ b/src/sentry/models/options/project_option.py @@ -74,6 +74,7 @@ "filters:react-hydration-errors", "filters:chunk-load-error", "relay.cardinality-limiter.limits", + "sentry:auto_run_issue_summaries", ] ) diff --git a/src/sentry/projectoptions/defaults.py b/src/sentry/projectoptions/defaults.py index 65adfea73b4d33..2b4fc4d6cfe3c4 100644 --- a/src/sentry/projectoptions/defaults.py +++ b/src/sentry/projectoptions/defaults.py @@ -196,3 +196,6 @@ # Should tempest fetch dumps for this project register(key="sentry:tempest_fetch_dumps", default=False) + +# Auto-enable issue summaries for projects +register(key="sentry:auto_run_issue_summaries", default=True) diff --git a/tests/sentry/api/endpoints/test_project_details.py b/tests/sentry/api/endpoints/test_project_details.py index 97711b8396d6a9..d7d0c9f0943558 100644 --- a/tests/sentry/api/endpoints/test_project_details.py +++ b/tests/sentry/api/endpoints/test_project_details.py @@ -1307,6 +1307,35 @@ def test_target_sample_rate(self): self.get_success_response(self.org_slug, self.proj_slug, targetSampleRate=0.1) assert self.project.get_option("sentry:target_sample_rate") == 0.1 + def test_auto_run_issue_summaries(self): + # Check default is True + response = self.get_success_response(self.organization.slug, self.project.slug) + assert response.data["options"]["sentry:auto_run_issue_summaries"] is True + assert self.project.get_option("sentry:auto_run_issue_summaries") is True + + # Try to set to True without flag + self.get_error_response( + self.organization.slug, + self.project.slug, + autoRunIssueSummaries=True, + status_code=400, + ) + + # Enable flag and set to True + with self.feature("projects:trigger-issue-summary-on-alerts"): + response = self.get_success_response( + self.organization.slug, self.project.slug, autoRunIssueSummaries=True + ) + assert response.data["options"]["sentry:auto_run_issue_summaries"] is True + assert self.project.get_option("sentry:auto_run_issue_summaries") is True + + # Set to False + response = self.get_success_response( + self.organization.slug, self.project.slug, autoRunIssueSummaries=False + ) + assert response.data["options"]["sentry:auto_run_issue_summaries"] is False + assert self.project.get_option("sentry:auto_run_issue_summaries") is False + class CopyProjectSettingsTest(APITestCase): endpoint = "sentry-api-0-project-details" diff --git a/tests/sentry/api/serializers/test_project.py b/tests/sentry/api/serializers/test_project.py index de58297c8f863a..0a5432dcf02d37 100644 --- a/tests/sentry/api/serializers/test_project.py +++ b/tests/sentry/api/serializers/test_project.py @@ -797,6 +797,17 @@ def test_replay_hydration_error_flag(self): result = serialize(self.project, self.user, DetailedProjectSerializer()) assert result["options"]["sentry:replay_hydration_error_issues"] is False + def test_auto_run_issue_summaries_flag(self): + result = serialize(self.project, self.user, DetailedProjectSerializer()) + # default should be true + assert result["options"]["sentry:auto_run_issue_summaries"] is True + assert result["autoRunIssueSummaries"] is True + + self.project.update_option("sentry:auto_run_issue_summaries", False) + result = serialize(self.project, self.user, DetailedProjectSerializer()) + assert result["options"]["sentry:auto_run_issue_summaries"] is False + assert result["autoRunIssueSummaries"] is False + def test_toolbar_allowed_origins(self): # Does not allow trailing newline or extra whitespace. # Default is empty: