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
8 changes: 8 additions & 0 deletions src/sentry/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@
from sentry.integrations.api.endpoints.organization_repository_details import (
OrganizationRepositoryDetailsEndpoint,
)
from sentry.integrations.api.endpoints.organization_repository_platforms import (
OrganizationRepositoryPlatformsEndpoint,
)
from sentry.integrations.api.endpoints.organization_repository_settings import (
OrganizationRepositorySettingsEndpoint,
)
Expand Down Expand Up @@ -2218,6 +2221,11 @@ def create_group_urls(name_prefix: str) -> list[URLPattern | URLResolver]:
OrganizationRepositoryCommitsEndpoint.as_view(),
name="sentry-api-0-organization-repository-commits",
),
re_path(
r"^(?P<organization_id_or_slug>[^/]+)/repos/(?P<repo_id>[^/]+)/platforms/$",
OrganizationRepositoryPlatformsEndpoint.as_view(),
name="sentry-api-0-organization-repository-platforms",
),
re_path(
r"^(?P<organization_id_or_slug>[^/]+)/plugins/$",
OrganizationPluginsEndpoint.as_view(),
Expand Down
33 changes: 33 additions & 0 deletions src/sentry/integrations/api/bases/organization_repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from __future__ import annotations

from typing import Any

from rest_framework.request import Request

from sentry.api.bases.organization import OrganizationEndpoint, OrganizationIntegrationsPermission
from sentry.api.exceptions import ResourceDoesNotExist
from sentry.models.organization import Organization
from sentry.models.repository import Repository


class OrganizationRepositoryEndpoint(OrganizationEndpoint):
"""Base endpoint that resolves repo_id to a Repository in convert_args."""

permission_classes = (OrganizationIntegrationsPermission,)

def convert_args(
self,
request: Request,
organization_id_or_slug: int | str | None = None,
*args: Any,
**kwargs: Any,
) -> tuple[tuple[Any, ...], dict[str, Any]]:
args, kwargs = super().convert_args(request, organization_id_or_slug, *args, **kwargs)
organization: Organization = kwargs["organization"]
repo_id = kwargs.pop("repo_id")
try:
kwargs["repo"] = Repository.objects.get(id=repo_id, organization_id=organization.id)
except (Repository.DoesNotExist, ValueError):
raise ResourceDoesNotExist

return args, kwargs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from __future__ import annotations

import logging

from rest_framework.request import Request
from rest_framework.response import Response

from sentry import features
from sentry.api.api_owners import ApiOwner
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import region_silo_endpoint
from sentry.integrations.api.bases.organization_repository import OrganizationRepositoryEndpoint
from sentry.integrations.github.client import GitHubApiClient
from sentry.integrations.github.platform_detection import detect_platforms
from sentry.integrations.services.integration import integration_service
from sentry.integrations.types import IntegrationProviderSlug
from sentry.models.organization import Organization
from sentry.models.repository import Repository
from sentry.shared_integrations.exceptions import ApiError

logger = logging.getLogger(__name__)


@region_silo_endpoint
class OrganizationRepositoryPlatformsEndpoint(OrganizationRepositoryEndpoint):
owner = ApiOwner.INTEGRATIONS
publish_status = {
"GET": ApiPublishStatus.PRIVATE,
}

def get(self, request: Request, organization: Organization, repo: Repository) -> Response:
if not features.has(
"organizations:integrations-github-platform-detection",
organization,
actor=request.user,
):
return Response(status=404)

if (
not repo.integration_id
or repo.provider != f"integrations:{IntegrationProviderSlug.GITHUB}"
):
return Response(
{"detail": "Platform detection is only supported for GitHub repositories."},
status=400,
)

integration = integration_service.get_integration(integration_id=repo.integration_id)
if integration is None:
return Response({"detail": "GitHub integration not found."}, status=400)

org_integration = integration_service.get_organization_integration(
integration_id=repo.integration_id, organization_id=organization.id
)
if org_integration is None:
return Response(
{"detail": "GitHub integration is not configured for this organization."},
status=400,
)

client = GitHubApiClient(integration=integration, org_integration_id=org_integration.id)

try:
platforms = detect_platforms(client=client, repo=repo.name)
except (ApiError, ValueError):
logger.exception(
"integrations.github.platform_detection_failed",
extra={"repo_id": repo.id, "repo_name": repo.name},
)
return Response({"detail": "Failed to detect platforms from GitHub."}, status=502)

return Response({"platforms": platforms})
10 changes: 10 additions & 0 deletions src/sentry/integrations/github/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,16 @@ def get_repo(self, repo: str) -> Any:
"""
return self.get(f"/repos/{repo}")

def get_languages(self, repo: str) -> dict[str, int]:
"""
https://docs.github.com/en/rest/repos/repos#list-repository-languages

:param repo: "owner/repo" format
:returns: {"Python": 50000, "JavaScript": 30000, ...}
Keys are GitHub Linguist names, values are bytes of code.
"""
return self.get(f"/repos/{repo}/languages")

# https://docs.github.com/en/rest/rate-limit?apiVersion=2022-11-28
def get_rate_limit(self, specific_resource: str = "core") -> GithubRateLimitInfo:
"""This gives information of the current rate limit"""
Expand Down
Loading
Loading