Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/sentry/seer/autofix/autofix_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ def trigger_autofix_agent(
return run


def get_autofix_agent_state(organization: Organization, group_id: int):
def get_autofix_agent_state(organization: Organization, group_id: int) -> SeerRunState | None:
"""
Get the current state of an agent-based autofix run for a group.

Expand Down
14 changes: 10 additions & 4 deletions src/sentry/seer/autofix/issue_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
from sentry.seer.autofix.autofix_agent import (
AutofixStep,
NoSeerQuotaException,
get_autofix_agent_state,
trigger_autofix_agent,
)
from sentry.seer.autofix.constants import (
Expand All @@ -44,14 +43,15 @@
from sentry.seer.entrypoints.cache import SeerOperatorAutofixCache
from sentry.seer.entrypoints.operator import SeerAutofixOperator
from sentry.seer.models import SummarizeIssueResponse
from sentry.seer.models.run import SeerRun
from sentry.seer.models.run import SeerRun, SeerRunMirrorStatus
from sentry.seer.seer_setup import has_seer_access
from sentry.seer.signed_seer_api import (
SeerViewerContext,
SummarizeIssueRequest,
make_signed_seer_api_request,
make_summarize_issue_request,
)
from sentry.seer.utils import runs_for_group
from sentry.services import eventstore
from sentry.services.eventstore.models import Event, GroupEvent
from sentry.tasks.base import instrumented_task
Expand Down Expand Up @@ -438,8 +438,14 @@ def is_group_eligible_for_automation(group: Group) -> bool:
if not has_seer_access(group.organization):
return False

autofix_state = get_autofix_agent_state(group.organization, group.id)
if autofix_state:
Comment thread
cursor[bot] marked this conversation as resolved.
if (
runs_for_group(group.id, "autofix")
.filter(
run__mirror_status=SeerRunMirrorStatus.LIVE,
run__seer_run_state_id__isnull=False,
)
.exists()
):
return False

fixability_score = get_and_update_group_fixability_score(group)
Expand Down
61 changes: 31 additions & 30 deletions tests/sentry/seer/autofix/test_issue_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,6 @@ def test_get_event_provided(
mock_serialize.assert_called_once()

@patch("sentry.seer.autofix.issue_summary._trigger_autofix_task.delay")
@patch("sentry.seer.autofix.issue_summary.get_autofix_agent_state")
@patch("sentry.seer.autofix.issue_summary._generate_fixability_score")
@patch("sentry.quotas.backend.record_seer_run")
@patch("sentry.seer.autofix.issue_summary.get_trace_tree_for_event")
Expand All @@ -521,10 +520,8 @@ def test_get_issue_summary_with_web_vitals_issue(
mock_get_trace_tree,
mock_record_seer_run,
mock_generate_fixability_score,
mock_get_autofix_agent_state,
mock_trigger_autofix_task,
):
mock_get_autofix_agent_state.return_value = None
mock_fixability_response = SummarizeIssueResponse(
group_id=str(self.group.id),
headline="some headline",
Expand Down Expand Up @@ -895,11 +892,10 @@ def setUp(self) -> None:
"sentry.seer.autofix.issue_summary.is_seer_autotriggered_autofix_rate_limited_and_increment",
return_value=False,
)
@patch("sentry.seer.autofix.issue_summary.get_autofix_agent_state", return_value=None)
@patch("sentry.quotas.backend.check_seer_quota", return_value=True)
@patch("sentry.seer.autofix.issue_summary._generate_fixability_score")
def test_high_fixability_code_changes(
self, mock_gen, mock_budget, mock_state, mock_rate, mock_trigger, mock_seat_based_tier
self, mock_gen, mock_budget, mock_rate, mock_trigger, mock_seat_based_tier
):
self.project.update_option("sentry:autofix_automation_tuning", "always")
mock_gen.return_value = SummarizeIssueResponse(
Expand All @@ -919,11 +915,10 @@ def test_high_fixability_code_changes(
"sentry.seer.autofix.issue_summary.is_seer_autotriggered_autofix_rate_limited_and_increment",
return_value=False,
)
@patch("sentry.seer.autofix.issue_summary.get_autofix_agent_state", return_value=None)
@patch("sentry.quotas.backend.check_seer_quota", return_value=True)
@patch("sentry.seer.autofix.issue_summary._generate_fixability_score")
def test_medium_fixability_solution(
self, mock_gen, mock_budget, mock_state, mock_rate, mock_trigger, mock_seat_based_tier
self, mock_gen, mock_budget, mock_rate, mock_trigger, mock_seat_based_tier
):
self.project.update_option("sentry:autofix_automation_tuning", "always")
mock_gen.return_value = SummarizeIssueResponse(
Expand All @@ -944,10 +939,8 @@ def test_medium_fixability_solution(
return_value=False,
)
@patch("sentry.seer.autofix.issue_summary.is_group_eligible_for_automation", return_value=True)
@patch("sentry.seer.autofix.issue_summary.get_autofix_agent_state", return_value=None)
def test_without_seat_based_tier(
self,
mock_state,
mock_triggering,
mock_rate,
mock_trigger,
Expand Down Expand Up @@ -1048,14 +1041,12 @@ def setUp(self) -> None:
"sentry.seer.autofix.issue_summary.is_seer_autotriggered_autofix_rate_limited_and_increment",
return_value=False,
)
@patch("sentry.seer.autofix.issue_summary.get_autofix_agent_state", return_value=None)
@patch("sentry.quotas.backend.check_seer_quota", return_value=True)
@patch("sentry.seer.autofix.issue_summary._generate_fixability_score")
def test_user_preference_limits_high_fixability(
self,
mock_gen,
mock_budget,
mock_state,
mock_rate,
mock_trigger,
mock_seat_based_tier,
Expand Down Expand Up @@ -1083,14 +1074,12 @@ def test_user_preference_limits_high_fixability(
"sentry.seer.autofix.issue_summary.is_seer_autotriggered_autofix_rate_limited_and_increment",
return_value=False,
)
@patch("sentry.seer.autofix.issue_summary.get_autofix_agent_state", return_value=None)
@patch("sentry.quotas.backend.check_seer_quota", return_value=True)
@patch("sentry.seer.autofix.issue_summary._generate_fixability_score")
def test_fixability_limits_permissive_user_preference(
self,
mock_gen,
mock_budget,
mock_state,
mock_rate,
mock_trigger,
mock_seat_based_tier,
Expand Down Expand Up @@ -1264,11 +1253,8 @@ def setUp(self) -> None:

@patch("sentry.seer.autofix.issue_summary.is_seer_autotriggered_autofix_rate_limited")
@patch("sentry.quotas.backend.check_seer_quota")
@patch("sentry.seer.autofix.issue_summary.get_autofix_agent_state", return_value=None)
@patch("sentry.seer.autofix.issue_summary.get_and_update_group_fixability_score")
def test_returns_true_when_all_checks_pass(
self, mock_fixability, mock_state, mock_quota, mock_rate_limit
):
def test_returns_true_when_all_checks_pass(self, mock_fixability, mock_quota, mock_rate_limit):
mock_fixability.return_value = 0.80
mock_quota.return_value = True
mock_rate_limit.return_value = False
Expand All @@ -1285,18 +1271,37 @@ def test_returns_false_without_seer_access(self, mock_fixability):
mock_fixability.assert_not_called()

@patch("sentry.seer.autofix.issue_summary.get_and_update_group_fixability_score")
@patch(
"sentry.seer.autofix.issue_summary.get_autofix_agent_state",
return_value={"status": "in_progress"},
)
def test_returns_false_when_autofix_in_progress(self, mock_state, mock_fixability):
def test_returns_false_when_live_autofix_run_exists(self, mock_fixability):
run = self.create_seer_run(
organization=self.organization,
mirror_status="live",
seer_run_state_id=1,
)
self.create_seer_agent_run(run, source="autofix", group=self.group)
assert is_group_eligible_for_automation(self.group) is False

mock_fixability.assert_not_called()

@patch("sentry.seer.autofix.issue_summary.get_autofix_agent_state", return_value=None)
@patch("sentry.seer.autofix.issue_summary.is_seer_autotriggered_autofix_rate_limited")
@patch("sentry.quotas.backend.check_seer_quota")
@patch("sentry.seer.autofix.issue_summary.get_and_update_group_fixability_score")
def test_returns_true_when_only_failed_autofix_run_exists(
self, mock_fixability, mock_quota, mock_rate_limit
):
run = self.create_seer_run(
organization=self.organization,
mirror_status="failed",
seer_run_state_id=2,
)
self.create_seer_agent_run(run, source="autofix", group=self.group)
mock_fixability.return_value = 0.80
mock_quota.return_value = True
mock_rate_limit.return_value = False

assert is_group_eligible_for_automation(self.group) is True

@patch("sentry.seer.autofix.issue_summary.get_and_update_group_fixability_score")
def test_returns_false_when_not_fixable(self, mock_fixability, mock_state):
def test_returns_false_when_not_fixable(self, mock_fixability):
mock_fixability.return_value = 0.20
self.group.times_seen = 10
self.group.times_seen_pending = 0
Expand All @@ -1306,9 +1311,8 @@ def test_returns_false_when_not_fixable(self, mock_fixability, mock_state):
assert is_group_eligible_for_automation(self.group) is False

@patch("sentry.quotas.backend.check_seer_quota")
@patch("sentry.seer.autofix.issue_summary.get_autofix_agent_state", return_value=None)
@patch("sentry.seer.autofix.issue_summary.get_and_update_group_fixability_score")
def test_returns_false_when_no_budget(self, mock_fixability, mock_state, mock_quota):
def test_returns_false_when_no_budget(self, mock_fixability, mock_quota):
mock_fixability.return_value = 0.80
mock_quota.return_value = False
self.group.times_seen = 10
Expand All @@ -1318,11 +1322,8 @@ def test_returns_false_when_no_budget(self, mock_fixability, mock_state, mock_qu

@patch("sentry.seer.autofix.issue_summary.is_seer_autotriggered_autofix_rate_limited")
@patch("sentry.quotas.backend.check_seer_quota")
@patch("sentry.seer.autofix.issue_summary.get_autofix_agent_state", return_value=None)
@patch("sentry.seer.autofix.issue_summary.get_and_update_group_fixability_score")
def test_returns_false_when_rate_limited(
self, mock_fixability, mock_state, mock_quota, mock_rate_limit
):
def test_returns_false_when_rate_limited(self, mock_fixability, mock_quota, mock_rate_limit):
mock_fixability.return_value = 0.80
mock_quota.return_value = True
mock_rate_limit.return_value = True
Expand Down
Loading