Skip to content
Merged
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: 2 additions & 0 deletions src/sentry/features/temporary.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ def register_temporary_features(manager: FeatureManager) -> None:
manager.add("organizations:seer-explorer-index", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
# Enable Seer Night Shift nightly autofix cron
manager.add("organizations:seer-night-shift", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
# Per-project gate for Seer Night Shift (requires organizations:seer-night-shift on the org)
manager.add("projects:seer-night-shift", ProjectFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
# Enable streaming responses for Seer Explorer
manager.add("organizations:seer-explorer-streaming", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
# Enable context engine for Seer Explorer
Expand Down
11 changes: 10 additions & 1 deletion src/sentry/tasks/seer/night_shift/cron.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,11 +276,20 @@ def _get_eligible_projects(

preferences = bulk_read_preferences(organization, list(project_map))

projects = [
candidates = [
project_map[pid]
for pid, pref in preferences.items()
if pref is not None
and pref.repositories
and pref.autofix_automation_tuning != AutofixAutomationTuningSettings.OFF
]
if not candidates:
return [], preferences

flag_result = features.batch_has(["projects:seer-night-shift"], projects=candidates)
projects = [
p
for p in candidates
if (flag_result or {}).get(f"project:{p.id}", {}).get("projects:seer-night-shift", False)
]
return projects, preferences
82 changes: 73 additions & 9 deletions tests/sentry/tasks/seer/test_night_shift.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,35 @@ def test_filters_by_automation_and_repos(self) -> None:
# No connected repo
self.create_project(organization=org)

with self.feature("organizations:seer-project-settings-read-from-sentry"):
with self.feature(
[
"organizations:seer-project-settings-read-from-sentry",
"projects:seer-night-shift",
]
):
projects, preferences = _get_eligible_projects(org)
assert projects == [eligible]
assert eligible.id in preferences

def test_filters_by_project_flag_disabled(self) -> None:
org = self.create_organization()

project = self.create_project(organization=org)
project.update_option(
"sentry:autofix_automation_tuning", AutofixAutomationTuningSettings.MEDIUM
)
repo = self.create_repo(project=project, provider="github", name="owner/repo")
SeerProjectRepository.objects.create(project=project, repository=repo)

with self.feature(
{
"organizations:seer-project-settings-read-from-sentry": True,
"projects:seer-night-shift": False,
}
):
projects, _ = _get_eligible_projects(org)
assert projects == []


@django_db_all
class TestRunNightShiftForOrg(TestCase, SnubaTestCase):
Expand Down Expand Up @@ -161,7 +185,12 @@ def test_no_eligible_projects(self) -> None:
self.create_project(organization=org)

with (
self.feature("organizations:seer-project-settings-read-from-sentry"),
self.feature(
[
"organizations:seer-project-settings-read-from-sentry",
"projects:seer-night-shift",
]
),
patch("sentry.tasks.seer.night_shift.cron.logger") as mock_logger,
):
run_night_shift_for_org(org.id)
Expand Down Expand Up @@ -191,7 +220,12 @@ def test_selects_candidates_and_skips_triggered(self) -> None:

fake_client = FakeExplorerClient([high_fix.id, low_fix.id])
with (
self.feature("organizations:seer-project-settings-read-from-sentry"),
self.feature(
[
"organizations:seer-project-settings-read-from-sentry",
"projects:seer-night-shift",
]
),
patch(
"sentry.tasks.seer.night_shift.agentic_triage.SeerExplorerClient",
return_value=fake_client,
Expand Down Expand Up @@ -231,7 +265,12 @@ def test_global_ranking_across_projects(self) -> None:

fake_client = FakeExplorerClient([high_group.id, low_group.id])
with (
self.feature("organizations:seer-project-settings-read-from-sentry"),
self.feature(
[
"organizations:seer-project-settings-read-from-sentry",
"projects:seer-night-shift",
]
),
patch(
"sentry.tasks.seer.night_shift.agentic_triage.SeerExplorerClient",
return_value=fake_client,
Expand All @@ -254,7 +293,12 @@ def test_triage_error_records_error_message(self) -> None:
)

with (
self.feature("organizations:seer-project-settings-read-from-sentry"),
self.feature(
[
"organizations:seer-project-settings-read-from-sentry",
"projects:seer-night-shift",
]
),
patch(
"sentry.tasks.seer.night_shift.cron.agentic_triage_strategy",
side_effect=RuntimeError("boom"),
Expand All @@ -277,7 +321,12 @@ def test_triggers_autofix_for_fixable_candidates(self) -> None:

fake_client = FakeExplorerClient([group.id], action="autofix")
with (
self.feature("organizations:seer-project-settings-read-from-sentry"),
self.feature(
[
"organizations:seer-project-settings-read-from-sentry",
"projects:seer-night-shift",
]
),
patch(
"sentry.tasks.seer.night_shift.agentic_triage.SeerExplorerClient",
return_value=fake_client,
Expand All @@ -303,7 +352,12 @@ def test_dry_run_skips_autofix(self) -> None:

fake_client = FakeExplorerClient([group.id], action="autofix")
with (
self.feature("organizations:seer-project-settings-read-from-sentry"),
self.feature(
[
"organizations:seer-project-settings-read-from-sentry",
"projects:seer-night-shift",
]
),
patch(
"sentry.tasks.seer.night_shift.agentic_triage.SeerExplorerClient",
return_value=fake_client,
Expand All @@ -329,7 +383,12 @@ def test_skips_autofix_for_non_autofix_candidates(self) -> None:

fake_client = FakeExplorerClient([group.id], action="root_cause_only")
with (
self.feature("organizations:seer-project-settings-read-from-sentry"),
self.feature(
[
"organizations:seer-project-settings-read-from-sentry",
"projects:seer-night-shift",
]
),
patch(
"sentry.tasks.seer.night_shift.agentic_triage.SeerExplorerClient",
return_value=fake_client,
Expand All @@ -350,7 +409,12 @@ def test_empty_candidates_creates_run_with_no_issues(self) -> None:
)

with (
self.feature("organizations:seer-project-settings-read-from-sentry"),
self.feature(
[
"organizations:seer-project-settings-read-from-sentry",
"projects:seer-night-shift",
]
),
patch(
"sentry.tasks.seer.night_shift.cron.agentic_triage_strategy",
return_value=([], None),
Expand Down
Loading