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
25 changes: 17 additions & 8 deletions src/sentry/seer/autofix/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
AutofixHandoffPoint,
BranchOverride,
SeerAutomationHandoffConfiguration,
SeerCodeMapping,
SeerProjectPreference,
SeerRepoDefinition,
)
Expand Down Expand Up @@ -499,7 +500,9 @@ def get_token_based_billing_project_repos(
project_id__in=project_ids,
)
.select_related("repository", "seerprojectrepository")
.prefetch_related("seerprojectrepository__branch_overrides")
.prefetch_related(
"seerprojectrepository__branch_overrides", "repositoryprojectpathconfig_set"
)
)


Expand All @@ -509,11 +512,12 @@ def get_seer_project_repo(project_repo: ProjectRepository) -> SeerProjectReposit


def build_seer_repo_definition(
repo: Repository, seer_project_repo: SeerProjectRepository | None
project_repo: ProjectRepository, seer_project_repo: SeerProjectRepository | None
) -> SeerRepoDefinition | None:
"""Build a SeerRepoDefinition from a Repository and its optional per-repo Seer config.
"""Build a SeerRepoDefinition from a project's repo link and its optional per-repo Seer config.

Returns None if Repository name is invalid."""
repo = project_repo.repository
repo_name_sections = get_repo_url_path(repo).split("/")
if len(repo_name_sections) < 2:
sentry_sdk.capture_exception(ValueError(f"Invalid repository name format: {repo.name}"))
Expand All @@ -537,6 +541,11 @@ def build_seer_repo_definition(
)
for bo in (seer_project_repo.branch_overrides.all() if seer_project_repo else [])
],
project_repository_source=ProjectRepositorySource(project_repo.source).label,
code_mappings=[
SeerCodeMapping(stack_root=cm.stack_root, source_root=cm.source_root)
for cm in project_repo.repositoryprojectpathconfig_set.all()
],
)


Expand All @@ -546,9 +555,7 @@ def _get_repo_definitions_by_project(
repo_definitions_by_project: defaultdict[int, list[SeerRepoDefinition]] = defaultdict(list)
if is_seer_token_based_billing_onboarding_enabled(organization):
for project_repo in get_token_based_billing_project_repos(organization, project_ids):
repo_def = build_seer_repo_definition(
project_repo.repository, get_seer_project_repo(project_repo)
)
repo_def = build_seer_repo_definition(project_repo, get_seer_project_repo(project_repo))
if repo_def is not None:
repo_definitions_by_project[project_repo.project_id].append(repo_def)
else:
Expand All @@ -558,10 +565,12 @@ def _get_repo_definitions_by_project(
project_repository__repository__status=ObjectStatus.ACTIVE,
)
.select_related("project_repository", "project_repository__repository")
.prefetch_related("branch_overrides")
.prefetch_related(
"branch_overrides", "project_repository__repositoryprojectpathconfig_set"
)
):
repo_def = build_seer_repo_definition(
seer_project_repo.project_repository.repository, seer_project_repo
seer_project_repo.project_repository, seer_project_repo
)
if repo_def is not None:
repo_definitions_by_project[seer_project_repo.project_repository.project_id].append(
Expand Down
13 changes: 13 additions & 0 deletions src/sentry/seer/models/seer_api_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ class IssueSummary(SummarizeIssueResponse):
event_id: str


class SeerCodeMapping(BaseModel):
stack_root: str
source_root: str


class SeerRepoDefinition(BaseModel):
repository_id: int | None = None
organization_id: int | None = None
Expand All @@ -56,6 +61,14 @@ class SeerRepoDefinition(BaseModel):
)
base_commit_sha: str | None = None
provider_raw: str | None = None
project_repository_source: str | None = Field(
default=None,
description="How the repo was linked to the project, such as 'manual' or 'auto_name_match'.",
)
code_mappings: list[SeerCodeMapping] = Field(
default_factory=list,
description="The project's code mappings for this repo.",
)


class SpanInsight(BaseModel):
Expand Down
63 changes: 62 additions & 1 deletion tests/sentry/seer/autofix/test_autofix_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
)
from sentry.models.options.project_option import ProjectOption
from sentry.models.project import Project
from sentry.models.projectrepository import ProjectRepository
from sentry.models.projectrepository import ProjectRepository, ProjectRepositorySource
from sentry.seer.autofix.constants import (
AutofixAutomationTuningSettings,
AutofixStatus,
Expand Down Expand Up @@ -42,6 +42,7 @@
AutofixHandoffPoint,
BranchOverride,
SeerAutomationHandoffConfiguration,
SeerCodeMapping,
SeerProjectPreference,
SeerRepoDefinition,
)
Expand Down Expand Up @@ -1049,6 +1050,38 @@ def test_reads_via_project_repository_fk(self):
assert len(result.repositories) == 1
assert result.repositories[0].branch_name == "main"

def test_includes_link_source_and_code_mappings(self):
self.create_seer_project_repository(project=self.project, repository=self.repo)
self.create_seer_project_repository(project=self.project, repository=self.repo2)
self.create_code_mapping(
project=self.project,
repo=self.repo,
stack_root="app/",
source_root="services/api/app/",
)

result = read_preference_from_sentry_db(self.project)
repo_by_name = {r.name: r for r in result.repositories}
assert repo_by_name["test-repo"].project_repository_source == "seer_preference"
assert repo_by_name["test-repo"].code_mappings == [
SeerCodeMapping(stack_root="app/", source_root="services/api/app/")
]
assert repo_by_name["other-repo"].code_mappings == []

def test_excludes_other_projects_code_mappings(self):
other_project = self.create_project(organization=self.organization)
self.create_seer_project_repository(project=self.project, repository=self.repo)
self.create_code_mapping(
project=other_project,
repo=self.repo,
stack_root="web/",
source_root="services/web/",
)

result = read_preference_from_sentry_db(self.project)
assert len(result.repositories) == 1
assert result.repositories[0].code_mappings == []

def test_autofix_automation_tuning_default(self):
self.create_seer_project_repository(
project=self.project, repository=self.repo, branch_name="main"
Expand Down Expand Up @@ -1233,6 +1266,17 @@ def test_includes_repos_without_seer_config(self) -> None:
assert result.repositories[0].instructions is None
assert result.repositories[0].branch_overrides == []

def test_includes_auto_link_source(self) -> None:
self.create_project_repository(
project=self.project,
repository=self.repo,
source=ProjectRepositorySource.AUTO_NAME_MATCH,
)

result = read_preference_from_sentry_db(self.project)
assert len(result.repositories) == 1
assert result.repositories[0].project_repository_source == "auto_name_match"


class BulkReadPreferencesFromSentryDbBaseTest(TestCase):
__test__ = False
Expand Down Expand Up @@ -1340,6 +1384,23 @@ def test_autofix_automation_tuning_defaults_to_off(self):
assert pref is not None
assert pref.autofix_automation_tuning == AutofixAutomationTuningSettings.OFF

def test_code_mappings_are_per_project(self):
for project, source_root in ((self.project1, "services/api/"), (self.project2, "web/")):
self.create_seer_project_repository(project=project, repository=self.repo)
self.create_code_mapping(
project=project, repo=self.repo, stack_root="", source_root=source_root
)

result = bulk_read_preferences_from_sentry_db(
self.organization.id, [self.project1.id, self.project2.id]
)
assert result[self.project1.id].repositories[0].code_mappings == [
SeerCodeMapping(stack_root="", source_root="services/api/")
]
assert result[self.project2.id].repositories[0].code_mappings == [
SeerCodeMapping(stack_root="", source_root="web/")
]

def test_wrong_organization_excluded(self):
other_org = self.create_organization()
self.create_seer_project_repository(
Expand Down
Loading