-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
feat(seer): Add dual-read helpers for Seer project preferences from Sentry DB #111591
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
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
db1575c
feat(seer): Add read helpers for Seer project preferences from Sentry DB
srest2021 3be1c6d
test(seer): Add tests for Sentry DB read preference helpers
srest2021 241b8bd
fixes
srest2021 e6ca4d1
remove handoff helper
srest2021 24873f1
ref(seer): Simplify preference reads using cached project options
srest2021 bd5959d
resolve conflicts
srest2021 14f573d
ref(seer): Restore batched queries in bulk_read_preferences
srest2021 03f8fce
fix typing
srest2021 770c187
more test coverage
srest2021 061dc64
add build handoff helper
srest2021 35bbeff
move comment
srest2021 c47e758
Use epoch-aware defaults in bulk preference reads
cursoragent 92dd7e4
Merge branch 'master' into srest2021/AIML-2611
srest2021 bdb89e2
fix defualt
srest2021 a0b46d0
cleaning up
srest2021 cdcff9e
raise valueerror on malformed repo name explicitly
srest2021 46f2d14
capture sentry exception
srest2021 4b6c36a
add test
srest2021 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,21 @@ | ||
| import logging | ||
| from collections.abc import Iterable | ||
| from collections import defaultdict | ||
| from collections.abc import Callable, Iterable, Mapping | ||
| from datetime import UTC, datetime | ||
| from enum import StrEnum | ||
| from typing import Any, NotRequired, TypedDict | ||
|
|
||
| import orjson | ||
| import pydantic | ||
| import sentry_sdk | ||
| from django.conf import settings | ||
| from django.db import router, transaction | ||
| from pydantic import BaseModel | ||
| from rest_framework import serializers | ||
| from urllib3 import BaseHTTPResponse, HTTPConnectionPool | ||
| from urllib3.util.retry import Retry | ||
|
|
||
| from sentry import features, options, ratelimits | ||
| from sentry import features, options, projectoptions, ratelimits | ||
| from sentry.constants import ( | ||
| AUTO_OPEN_PRS_DEFAULT, | ||
| SEER_AUTOMATED_RUN_STOPPING_POINT_DEFAULT, | ||
|
|
@@ -25,16 +27,20 @@ | |
| get_sorted_code_mapping_configs, | ||
| ) | ||
| from sentry.models.group import Group | ||
| from sentry.models.options.project_option import ProjectOption | ||
| from sentry.models.organization import Organization | ||
| from sentry.models.project import Project | ||
| from sentry.models.repository import Repository | ||
| from sentry.net.http import connection_from_url | ||
| from sentry.projectoptions.defaults import SEER_PROJECT_PREFERENCE_OPTION_KEYS | ||
| from sentry.seer.autofix.constants import AutofixAutomationTuningSettings, AutofixStatus | ||
| from sentry.seer.constants import SEER_SUPPORTED_SCM_PROVIDERS | ||
| from sentry.seer.models import ( | ||
| AutofixHandoffPoint, | ||
| BranchOverride, | ||
| SeerApiError, | ||
| SeerApiResponseValidationError, | ||
| SeerAutomationHandoffConfiguration, | ||
| SeerPermissionError, | ||
| SeerProjectPreference, | ||
| SeerRawPreferenceResponse, | ||
|
|
@@ -44,10 +50,6 @@ | |
| SeerProjectRepository, | ||
| SeerProjectRepositoryBranchOverride, | ||
| ) | ||
| from sentry.seer.models.seer_api_models import ( | ||
| AutofixHandoffPoint, | ||
| SeerAutomationHandoffConfiguration, | ||
| ) | ||
| from sentry.seer.signed_seer_api import SeerViewerContext, make_signed_seer_api_request | ||
| from sentry.utils.cache import cache | ||
| from sentry.utils.outcomes import Outcome, track_outcome | ||
|
|
@@ -674,6 +676,145 @@ def bulk_write_preferences_to_sentry_db( | |
| _write_preferences_to_sentry_db(project_preferences) | ||
|
|
||
|
|
||
| def build_repo_definition_from_project_repo( | ||
| seer_project_repo: SeerProjectRepository, | ||
| ) -> SeerRepoDefinition | None: | ||
| """Build a SeerRepoDefinition from a SeerProjectRepository with its joined Repository. | ||
|
|
||
| Returns None if Repository name is invalid.""" | ||
| repo = seer_project_repo.repository | ||
| repo_name_sections = repo.name.split("/") | ||
| if len(repo_name_sections) < 2: | ||
| sentry_sdk.capture_exception(ValueError(f"Invalid repository name format: {repo.name}")) | ||
| return None | ||
|
|
||
| return SeerRepoDefinition( | ||
| repository_id=repo.id, | ||
| organization_id=repo.organization_id, | ||
| integration_id=str(repo.integration_id) if repo.integration_id is not None else None, | ||
| provider=repo.provider or "", | ||
| owner=repo_name_sections[0], | ||
| name="/".join(repo_name_sections[1:]), | ||
| external_id=repo.external_id or "", | ||
| branch_name=seer_project_repo.branch_name, | ||
| instructions=seer_project_repo.instructions, | ||
| branch_overrides=[ | ||
| BranchOverride( | ||
| tag_name=bo.tag_name, | ||
| tag_value=bo.tag_value, | ||
| branch_name=bo.branch_name, | ||
| ) | ||
| for bo in seer_project_repo.branch_overrides.all() | ||
| ], | ||
| ) | ||
|
|
||
|
|
||
| def _build_automation_handoff( | ||
| get_option: Callable[[str], Any], | ||
| ) -> SeerAutomationHandoffConfiguration | None: | ||
| """Build a SeerAutomationHandoffConfiguration from option values, or None if incomplete.""" | ||
|
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. (Private for now since idk if we'll have any other use cases where this callable arg will be helpful or relevant) |
||
| handoff_point = get_option("sentry:seer_automation_handoff_point") | ||
| handoff_target = get_option("sentry:seer_automation_handoff_target") | ||
| handoff_integration_id = get_option("sentry:seer_automation_handoff_integration_id") | ||
|
|
||
| if handoff_point is None or handoff_target is None or handoff_integration_id is None: | ||
| return None | ||
|
|
||
| return SeerAutomationHandoffConfiguration( | ||
| handoff_point=handoff_point, | ||
| target=handoff_target, | ||
| integration_id=handoff_integration_id, | ||
| auto_create_pr=get_option("sentry:seer_automation_handoff_auto_create_pr"), | ||
| ) | ||
|
|
||
|
|
||
| def read_preference_from_sentry_db(project: Project) -> SeerProjectPreference | None: | ||
| """Read a single project's Seer preferences from Sentry DB. | ||
|
|
||
| For now, should only be used under feature flag `organizations:seer-project-settings-read-from-sentry`.""" | ||
| seer_project_repo_qs = ( | ||
| SeerProjectRepository.objects.filter(project=project) | ||
| .select_related("repository") | ||
| .prefetch_related("branch_overrides") | ||
| ) | ||
| repo_definitions = [ | ||
| repo_def | ||
| for project_repo in seer_project_repo_qs | ||
| if (repo_def := build_repo_definition_from_project_repo(project_repo)) is not None | ||
| ] | ||
|
|
||
| has_configured_options = any( | ||
| ProjectOption.objects.isset(project, key) for key in SEER_PROJECT_PREFERENCE_OPTION_KEYS | ||
| ) | ||
| if not repo_definitions and not has_configured_options: | ||
| return None | ||
|
|
||
| return SeerProjectPreference( | ||
| organization_id=project.organization_id, | ||
| project_id=project.id, | ||
| repositories=repo_definitions, | ||
| automated_run_stopping_point=project.get_option("sentry:seer_automated_run_stopping_point"), | ||
| automation_handoff=_build_automation_handoff(project.get_option), | ||
| ) | ||
|
|
||
|
|
||
| def bulk_read_preferences_from_sentry_db( | ||
| organization_id: int, project_ids: list[int] | ||
| ) -> dict[int, SeerProjectPreference | None]: | ||
| """Bulk read Seer preferences from Sentry DB. | ||
|
|
||
| For now, should only be used under feature flag `organizations:seer-project-settings-read-from-sentry`.""" | ||
| if not project_ids: | ||
| return {} | ||
|
|
||
| projects = list(Project.objects.filter(id__in=project_ids, organization_id=organization_id)) | ||
|
|
||
| repo_definitions_by_project: defaultdict[int, list[SeerRepoDefinition]] = defaultdict(list) | ||
| for project_repo in ( | ||
| SeerProjectRepository.objects.filter(project_id__in=project_ids) | ||
| .select_related("repository") | ||
| .prefetch_related("branch_overrides") | ||
| ): | ||
| repo_def = build_repo_definition_from_project_repo(project_repo) | ||
| if repo_def is not None: | ||
| repo_definitions_by_project[project_repo.project_id].append(repo_def) | ||
|
|
||
| # get_value_bulk_id returns None for missing options, unlike project.get_option | ||
| # which automatically falls back to the registered well-known key default. | ||
| project_options: dict[str, Mapping[int, Any]] = { | ||
| key: ProjectOption.objects.get_value_bulk_id(project_ids, key) | ||
| for key in SEER_PROJECT_PREFERENCE_OPTION_KEYS | ||
| } | ||
|
|
||
| result: dict[int, SeerProjectPreference | None] = {} | ||
| for project in projects: | ||
| has_configured_options = any( | ||
| project_options[key][project.id] is not None | ||
| for key in SEER_PROJECT_PREFERENCE_OPTION_KEYS | ||
| ) | ||
|
srest2021 marked this conversation as resolved.
|
||
| if project.id not in repo_definitions_by_project and not has_configured_options: | ||
| result[project.id] = None | ||
| continue | ||
|
|
||
| def _get_project_option(key: str) -> Any: | ||
| value = project_options[key][project.id] | ||
| if value is None: | ||
| return projectoptions.get_well_known_default(key, project=project) | ||
| return value | ||
|
srest2021 marked this conversation as resolved.
|
||
|
|
||
| result[project.id] = SeerProjectPreference( | ||
| organization_id=project.organization_id, | ||
| project_id=project.id, | ||
| repositories=repo_definitions_by_project.get(project.id, []), | ||
| automated_run_stopping_point=_get_project_option( | ||
| "sentry:seer_automated_run_stopping_point" | ||
| ), | ||
| automation_handoff=_build_automation_handoff(_get_project_option), | ||
| ) | ||
|
|
||
| return result | ||
|
|
||
|
|
||
| def set_project_seer_preference(preference: SeerProjectPreference) -> None: | ||
| """Set Seer project preference for a single project via Seer API.""" | ||
| response = make_set_project_preference_request( | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.