-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
ref(seer): Unify Seer project settings update helper and add tuning and auto_create_pr fields #116352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ref(seer): Unify Seer project settings update helper and add tuning and auto_create_pr fields #116352
Changes from all commits
460fd64
f5eccf4
fc26fed
f682313
9f45557
e0a4c7c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ | |
| from sentry.api.event_search import QueryToken, SearchConfig, SearchFilter | ||
| from sentry.api.event_search import parse_search_query as base_parse_search_query | ||
| from sentry.api.paginator import OffsetPaginator | ||
| from sentry.api.serializers.rest_framework import CamelSnakeSerializer | ||
| from sentry.constants import ( | ||
| AUTOFIX_AUTOMATION_TUNING_DEFAULT, | ||
| SEER_AUTOMATED_RUN_STOPPING_POINT_DEFAULT, | ||
|
|
@@ -36,7 +37,6 @@ | |
| from sentry.seer.autofix.utils import ( | ||
| AutofixStoppingPoint, | ||
| AutomationCodingAgent, | ||
| bulk_update_seer_project_settings, | ||
| get_automation_handoff, | ||
| get_valid_automated_run_stopping_points, | ||
| update_seer_project_settings, | ||
|
|
@@ -80,6 +80,8 @@ class SeerProjectSettingsResponse(TypedDict): | |
| agent: str | ||
| integrationId: str | None | ||
| stoppingPoint: str | ||
| autoCreatePr: bool | None | ||
| automationTuning: str | ||
| scannerAutomation: bool | ||
| reposCount: int | ||
|
|
||
|
|
@@ -154,16 +156,20 @@ def _serialize(project: Project, settings: SeerProjectSettings) -> SeerProjectSe | |
| # No configured external handoff means use Seer agent. | ||
| agent: str = "seer" | ||
| integration_id: str | None = None | ||
| auto_create_pr: bool | None = None | ||
| else: | ||
| agent = handoff.target | ||
| integration_id = str(handoff.integration_id) | ||
| auto_create_pr = handoff.auto_create_pr | ||
|
|
||
| return SeerProjectSettingsResponse( | ||
| projectId=str(project.id), | ||
| projectSlug=project.slug, | ||
| agent=agent, | ||
| integrationId=integration_id, | ||
| stoppingPoint=stopping_point, | ||
| autoCreatePr=auto_create_pr, | ||
| automationTuning=settings["automation_tuning"], | ||
| scannerAutomation=settings["scanner_automation"], | ||
| reposCount=settings["repos_count"], | ||
| ) | ||
|
|
@@ -307,22 +313,22 @@ def _apply_search_filters(queryset, filters: Sequence[QueryToken]): | |
| return queryset | ||
|
|
||
|
|
||
| class ProjectSettingsUpdateSerializer(serializers.Serializer): | ||
| class ProjectSettingsUpdateSerializer(CamelSnakeSerializer): | ||
| agent = serializers.ChoiceField(choices=[*AutomationCodingAgent], required=False) | ||
| integrationId = serializers.IntegerField(required=False) | ||
| stoppingPoint = serializers.ChoiceField(choices=["off", *AutofixStoppingPoint], required=False) | ||
| scannerAutomation = serializers.BooleanField(required=False) | ||
|
|
||
| def validate_stoppingPoint(self, value: str) -> str: | ||
| if value == "off": | ||
| return value | ||
| integration_id = serializers.IntegerField(required=False) | ||
| stopping_point = serializers.ChoiceField(choices=[*AutofixStoppingPoint], required=False) | ||
|
srest2021 marked this conversation as resolved.
|
||
| scanner_automation = serializers.BooleanField(required=False) | ||
| automation_tuning = serializers.ChoiceField( | ||
| choices=[AutofixAutomationTuningSettings.OFF, AutofixAutomationTuningSettings.MEDIUM], | ||
| required=False, | ||
| ) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's easier to keep stopping point and tuning as independent fields, so that we can support legacy seer which allows other tuning values besides "off" and "medium". |
||
|
|
||
| organization = self.context["organization"] | ||
| if value not in get_valid_automated_run_stopping_points(organization): | ||
| def validate_stopping_point(self, value: str) -> str: | ||
| if value not in get_valid_automated_run_stopping_points(self.context["organization"]): | ||
| raise serializers.ValidationError(f'"{value}" is not a valid choice.') | ||
| return value | ||
|
|
||
| def validate_integrationId(self, value: int) -> int: | ||
| def validate_integration_id(self, value: int) -> int: | ||
| organization = self.context["organization"] | ||
| org_integrations = integration_service.get_organization_integrations( | ||
| organization_id=organization.id, integration_id=value | ||
|
|
@@ -332,25 +338,31 @@ def validate_integrationId(self, value: int) -> int: | |
| return value | ||
|
|
||
| def validate(self, data): | ||
| if "agent" in data and data["agent"] != "seer" and "integrationId" not in data: | ||
| if "agent" in data and data["agent"] != "seer" and "integration_id" not in data: | ||
| raise serializers.ValidationError( | ||
| {"integrationId": "Required when agent is an external coding agent."} | ||
| {"integration_id": "Required when agent is an external coding agent."} | ||
| ) | ||
|
|
||
| if "integrationId" in data: | ||
| if "integration_id" in data: | ||
| if "agent" not in data: | ||
| raise serializers.ValidationError( | ||
| {"agent": "Required when integrationId is provided."} | ||
| {"agent": "Required when integration_id is provided."} | ||
| ) | ||
| elif data["agent"] == "seer": | ||
| raise serializers.ValidationError( | ||
| {"agent": "Must be an external coding agent when integrationId is provided."} | ||
| {"agent": "Must be an external coding agent when integration_id is provided."} | ||
| ) | ||
|
|
||
| has_update = any(k in data for k in ("agent", "stoppingPoint", "scannerAutomation")) | ||
| if not has_update: | ||
| if not any( | ||
| k in data | ||
| for k in ("agent", "stopping_point", "scanner_automation", "automation_tuning") | ||
| ): | ||
| raise serializers.ValidationError("At least one update field must be provided.") | ||
|
|
||
| # Keep stopping point in sync with handoff auto_create_pr. | ||
| if "stopping_point" in data and "auto_create_pr" not in data: | ||
| data["auto_create_pr"] = data["stopping_point"] == AutofixStoppingPoint.OPEN_PR | ||
|
srest2021 marked this conversation as resolved.
|
||
|
|
||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here we keep auto_create_pr in sync with the stopping point before we pass the serializer data to update_seer_project_settings. |
||
| return data | ||
|
|
||
|
|
||
|
|
@@ -373,20 +385,15 @@ def put(self, request: Request, project: Project) -> Response: | |
| if not serializer.is_valid(): | ||
| return Response(serializer.errors, status=400) | ||
|
|
||
| update_seer_project_settings(project, serializer.validated_data) | ||
| data = serializer.validated_data | ||
| update_seer_project_settings([project.id], data) | ||
|
|
||
| self.create_audit_entry( | ||
| request=request, | ||
| organization=project.organization, | ||
| target_object=project.id, | ||
| event=audit_log.get_event_id("AUTOFIX_SETTINGS_EDIT"), | ||
| data={ | ||
| "project_id": project.id, | ||
| "agent": serializer.validated_data.get("agent"), | ||
| "integration_id": serializer.validated_data.get("integrationId"), | ||
| "stopping_point": serializer.validated_data.get("stoppingPoint"), | ||
| "scanner_automation": serializer.validated_data.get("scannerAutomation"), | ||
| }, | ||
| data={"project_id": project.id, **data}, | ||
| ) | ||
|
|
||
| return Response(serialize_project(project)) | ||
|
|
@@ -455,21 +462,15 @@ def put(self, request: Request, organization: Organization) -> Response: | |
| return Response({"detail": "Invalid search query"}, status=400) | ||
|
|
||
| projects = list(queryset) | ||
| bulk_update_seer_project_settings(projects, data) | ||
| if projects: | ||
| update_seer_project_settings([p.id for p in projects], data) | ||
|
|
||
| self.create_audit_entry( | ||
| request=request, | ||
| organization=organization, | ||
| target_object=organization.id, | ||
| event=audit_log.get_event_id("AUTOFIX_SETTINGS_EDIT"), | ||
| data={ | ||
| "project_count": len(projects), | ||
| "project_ids": [p.id for p in projects], | ||
| "agent": data.get("agent"), | ||
| "integration_id": data.get("integrationId"), | ||
| "stopping_point": data.get("stoppingPoint"), | ||
| "scanner_automation": data.get("scannerAutomation"), | ||
| }, | ||
| data={"project_count": len(projects), "project_ids": [p.id for p in projects], **data}, | ||
| ) | ||
|
|
||
| return Response(status=204) | ||
Uh oh!
There was an error while loading. Please reload this page.