Skip to content
Draft
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
4 changes: 2 additions & 2 deletions src/sentry/seer/autofix/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
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.constants import get_supported_scm_providers
from sentry.seer.models import (
AutofixHandoffPoint,
BranchOverride,
Expand Down Expand Up @@ -830,7 +830,7 @@ def get_autofix_repos_from_project_code_mappings(
and repo.integration_id is not None
and repo.external_id
and repo.provider
and repo.provider in SEER_SUPPORTED_SCM_PROVIDERS
and repo.provider in get_supported_scm_providers(project.organization)
):
repo_dict = {
"repository_id": repo.id,
Expand Down
23 changes: 22 additions & 1 deletion src/sentry/seer/constants.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
from typing import Literal
from __future__ import annotations

from typing import TYPE_CHECKING, Literal

from sentry.integrations.types import IntegrationProviderSlug

if TYPE_CHECKING:
from sentry.models.organization import Organization

# Type for Seer-supported SCM provider strings
SeerSCMProvider = Literal[
"integrations:github",
"integrations:github_enterprise",
"integrations:gitlab",
"github",
"github_enterprise",
"gitlab",
]

# Supported repository providers for Seer features
Expand All @@ -17,3 +24,17 @@
IntegrationProviderSlug.GITHUB.value,
IntegrationProviderSlug.GITHUB_ENTERPRISE.value,
]

SEER_GITLAB_SCM_PROVIDERS = [
"integrations:gitlab",
IntegrationProviderSlug.GITLAB.value,
]


def get_supported_scm_providers(organization: Organization | None = None) -> list[str]:
from sentry import features

providers = list(SEER_SUPPORTED_SCM_PROVIDERS)
if organization is not None and features.has("organizations:seer-gitlab-support", organization):
providers.extend(SEER_GITLAB_SCM_PROVIDERS)
return providers
16 changes: 15 additions & 1 deletion src/sentry/seer/endpoints/project_seer_preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
read_preference_from_sentry_db,
write_preference_to_sentry_db,
)
from sentry.seer.constants import get_supported_scm_providers
from sentry.seer.endpoints.organization_autofix_automation_settings import (
RepositorySerializer as BaseRepositorySerializer,
)
Expand All @@ -29,6 +30,16 @@ class RepositorySerializer(BaseRepositorySerializer):
organization_id = serializers.IntegerField(required=True)
integration_id = serializers.CharField(required=True)

def validate_provider(self, value):
organization = self.context.get("organization")
supported = get_supported_scm_providers(organization)
if value not in supported:
supported_str = ", ".join(sorted(supported))
raise serializers.ValidationError(
f'"{value}" is not a supported Seer provider. Supported providers: {supported_str}'
)
return value


class SeerAutomationHandoffConfigurationSerializer(CamelSnakeSerializer):
handoff_point = serializers.ChoiceField(
Expand Down Expand Up @@ -90,7 +101,10 @@ class ProjectSeerPreferencesEndpoint(ProjectEndpoint):
)

def post(self, request: Request, project: Project) -> Response:
serializer = ProjectSeerPreferencesSerializer(data=request.data)
serializer = ProjectSeerPreferencesSerializer(
data=request.data,
context={"organization": project.organization},
)
serializer.is_valid(raise_exception=True)

for repo_data in serializer.validated_data.get("repositories", []):
Expand Down
27 changes: 27 additions & 0 deletions tests/sentry/autofix/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
)
from sentry.seer.models import SeerPermissionError
from sentry.testutils.cases import TestCase
from sentry.testutils.helpers.features import with_feature
from sentry.testutils.helpers.options import override_options


Expand Down Expand Up @@ -74,6 +75,32 @@ def test_filters_out_unsupported_providers(self) -> None:
assert len(repos) == 1
assert repos[0]["provider"] == "integrations:github"

@with_feature("organizations:seer-gitlab-support")
def test_includes_gitlab_repos_with_feature_flag(self) -> None:
project = self.create_project()
github_repo = self.create_repo(
name="getsentry/sentry",
provider="integrations:github",
external_id="123",
integration_id=234,
)
self.create_code_mapping(project=project, repo=github_repo)

gitlab_repo = self.create_repo(
name="getsentry/sentry-gitlab",
provider="integrations:gitlab",
external_id="456",
integration_id=345,
)
self.create_code_mapping(
project=project, repo=gitlab_repo, stack_root="gitlab/", source_root="src/gitlab/"
)

repos = get_autofix_repos_from_project_code_mappings(project)
assert len(repos) == 2
providers = {r["provider"] for r in repos}
assert providers == {"integrations:github", "integrations:gitlab"}

def test_filters_out_disabled_repos(self) -> None:
project = self.create_project()
active_repo = self.create_repo(
Expand Down
65 changes: 65 additions & 0 deletions tests/sentry/seer/endpoints/test_project_seer_preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from sentry.models.repository import Repository
from sentry.seer.models.project_repository import SeerProjectRepository
from sentry.testutils.cases import APITestCase
from sentry.testutils.helpers.features import with_feature


class ProjectSeerPreferencesEndpointTest(APITestCase):
Expand Down Expand Up @@ -471,3 +472,67 @@ def test_post_rejects_unsupported_repo_provider(self) -> None:

assert response.status_code == 400
assert SeerProjectRepository.objects.filter(project=self.project).count() == 0

@with_feature("organizations:seer-gitlab-support")
def test_post_accepts_gitlab_repo_with_feature_flag(self) -> None:
gitlab_repo = self.create_repo(
project=self.project,
name="getsentry/sentry-gitlab",
provider="integrations:gitlab",
external_id="789",
integration_id=456,
)

request_data = {
"repositories": [
{
"organization_id": self.org.id,
"integration_id": "456",
"provider": "integrations:gitlab",
"owner": "getsentry",
"name": "sentry-gitlab",
"external_id": "789",
}
],
}

response = self.client.post(self.url, data=request_data)

assert response.status_code == 204
seer_repos = list(
SeerProjectRepository.objects.filter(project=self.project).select_related("repository")
)
assert len(seer_repos) == 1
assert seer_repos[0].repository_id == gitlab_repo.id

@with_feature("organizations:seer-gitlab-support")
def test_post_accepts_gitlab_bare_provider_with_feature_flag(self) -> None:
gitlab_repo = self.create_repo(
project=self.project,
name="getsentry/sentry-gitlab",
provider="integrations:gitlab",
external_id="789",
integration_id=456,
)

request_data = {
"repositories": [
{
"organization_id": self.org.id,
"integration_id": "456",
"provider": "gitlab",
"owner": "getsentry",
"name": "sentry-gitlab",
"external_id": "789",
}
],
}

response = self.client.post(self.url, data=request_data)

assert response.status_code == 204
seer_repos = list(
SeerProjectRepository.objects.filter(project=self.project).select_related("repository")
)
assert len(seer_repos) == 1
assert seer_repos[0].repository_id == gitlab_repo.id
Loading