-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
feat(night-shift): Trigger autofix for fixable candidates and add dry run mode #113056
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
Changes from all commits
2a1dee4
f08aeed
aadbe70
c6c6069
487e5c0
7ea5e3e
39e1d27
1b5add6
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 |
|---|---|---|
|
|
@@ -9,13 +9,24 @@ | |
|
|
||
| from sentry import features, options | ||
| from sentry.constants import ObjectStatus | ||
| from sentry.models.group import Group | ||
| from sentry.models.organization import Organization, OrganizationStatus | ||
| from sentry.models.project import Project | ||
| from sentry.seer.autofix.constants import AutofixAutomationTuningSettings | ||
| from sentry.seer.autofix.constants import ( | ||
| AutofixAutomationTuningSettings, | ||
| SeerAutomationSource, | ||
| ) | ||
| from sentry.seer.autofix.issue_summary import ( | ||
| _trigger_autofix_task, | ||
| auto_run_source_map, | ||
| referrer_map, | ||
| ) | ||
| from sentry.seer.autofix.utils import bulk_read_preferences | ||
| from sentry.seer.models.night_shift import SeerNightShiftRun, SeerNightShiftRunIssue | ||
| from sentry.seer.models.seer_api_models import SeerProjectPreference | ||
| from sentry.tasks.base import instrumented_task | ||
| from sentry.tasks.seer.night_shift.agentic_triage import agentic_triage_strategy | ||
| from sentry.tasks.seer.night_shift.models import TriageAction | ||
| from sentry.taskworker.namespaces import seer_tasks | ||
| from sentry.utils.iterators import chunked | ||
| from sentry.utils.query import RangeQuerySetWrapper | ||
|
|
@@ -76,7 +87,7 @@ def schedule_night_shift() -> None: | |
| namespace=seer_tasks, | ||
| processing_deadline_duration=5 * 60, | ||
| ) | ||
| def run_night_shift_for_org(organization_id: int) -> None: | ||
| def run_night_shift_for_org(organization_id: int, dry_run: bool = False) -> None: | ||
| try: | ||
| organization = Organization.objects.get( | ||
| id=organization_id, status=OrganizationStatus.ACTIVE | ||
|
|
@@ -94,7 +105,7 @@ def run_night_shift_for_org(organization_id: int) -> None: | |
| start_time = time.monotonic() | ||
|
|
||
| try: | ||
| eligible_projects = _get_eligible_projects(organization) | ||
| eligible_projects, preferences = _get_eligible_projects(organization) | ||
| if not eligible_projects: | ||
| logger.info( | ||
| "night_shift.no_eligible_projects", | ||
|
|
@@ -160,6 +171,7 @@ def run_night_shift_for_org(organization_id: int) -> None: | |
| "run_id": run.id, | ||
| "num_eligible_projects": len(eligible_projects), | ||
| "num_candidates": len(candidates), | ||
| "dry_run": dry_run, | ||
| "candidates": [ | ||
| { | ||
| "group_id": c.group.id, | ||
|
|
@@ -170,6 +182,16 @@ def run_night_shift_for_org(organization_id: int) -> None: | |
| }, | ||
| ) | ||
|
|
||
| autofix_triggered = 0 | ||
| if not dry_run: | ||
| for c in candidates: | ||
| if c.action == TriageAction.AUTOFIX: | ||
| pref = preferences.get(c.group.project_id) | ||
| stopping_point = pref.automated_run_stopping_point if pref else None | ||
| if _trigger_autofix_for_candidate(c.group, organization, stopping_point): | ||
| autofix_triggered += 1 | ||
| sentry_sdk.metrics.count("night_shift.autofix_triggered", autofix_triggered) | ||
|
|
||
|
|
||
| def _get_eligible_orgs_from_batch( | ||
| orgs: Sequence[Organization], | ||
|
|
@@ -193,21 +215,57 @@ def _get_eligible_orgs_from_batch( | |
| return eligible | ||
|
|
||
|
|
||
| def _get_eligible_projects(organization: Organization) -> list[Project]: | ||
| def _trigger_autofix_for_candidate( | ||
| group: Group, organization: Organization, stopping_point: str | None | ||
| ) -> bool: | ||
| """Trigger autofix for a single candidate identified as fixable by night shift triage. | ||
|
|
||
| Returns True if the autofix task was dispatched. | ||
| """ | ||
| try: | ||
| event = group.get_latest_event() | ||
| if not event: | ||
| logger.warning( | ||
| "night_shift.no_event_for_autofix", | ||
| extra={"group_id": group.id, "organization_id": organization.id}, | ||
| ) | ||
| return False | ||
|
|
||
| _trigger_autofix_task.delay( | ||
| group_id=group.id, | ||
| event_id=event.event_id, | ||
| user_id=None, | ||
| auto_run_source=auto_run_source_map[SeerAutomationSource.NIGHT_SHIFT], | ||
| referrer=referrer_map[SeerAutomationSource.NIGHT_SHIFT], | ||
| stopping_point=stopping_point, | ||
| ) | ||
| return True | ||
| except Exception: | ||
| logger.exception( | ||
| "night_shift.autofix_trigger_failed", | ||
| extra={"group_id": group.id, "organization_id": organization.id}, | ||
| ) | ||
| return False | ||
|
Contributor
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. Missing existing autofix run check before triggeringMedium Severity
Additional Locations (1)Reviewed by Cursor Bugbot for commit 1b5add6. Configure here.
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. There's slight potential for a race condition but not a big deal right now. Our issues query checks that the field is null. |
||
|
|
||
|
|
||
| def _get_eligible_projects( | ||
| organization: Organization, | ||
| ) -> tuple[list[Project], dict[int, SeerProjectPreference | None]]: | ||
| """Return active projects that have automation enabled and connected repos.""" | ||
| project_map = { | ||
| p.id: p | ||
| for p in Project.objects.filter(organization=organization, status=ObjectStatus.ACTIVE) | ||
| } | ||
| if not project_map: | ||
| return [] | ||
| return [], {} | ||
|
|
||
| preferences = bulk_read_preferences(organization, list(project_map)) | ||
|
|
||
| return [ | ||
| projects = [ | ||
| project_map[pid] | ||
| for pid, pref in preferences.items() | ||
| if pref is not None | ||
| and pref.repositories | ||
| and pref.autofix_automation_tuning != AutofixAutomationTuningSettings.OFF | ||
| ] | ||
| return projects, preferences | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did we want to handle
ROOT_CAUSE_ONLYor leave it out for now?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea spoke offline and going to ignore for now.