diff --git a/openhands/integrations/gitlab/gitlab_service.py b/openhands/integrations/gitlab/gitlab_service.py index 6c711f600a..26bac72dd8 100644 --- a/openhands/integrations/gitlab/gitlab_service.py +++ b/openhands/integrations/gitlab/gitlab_service.py @@ -14,10 +14,11 @@ from openhands.integrations.service_types import ( ) from openhands.server.types import AppMode from openhands.utils.import_utils import get_impl - +from openhands.core.logger import openhands_logger as logger class GitLabService(GitService): BASE_URL = 'https://gitlab.com/api/v4' + GRAPHQL_URL = 'https://gitlab.com/api/graphql' token: SecretStr = SecretStr('') refresh = False @@ -35,7 +36,7 @@ class GitLabService(GitService): if token: self.token = token - async def _get_gitlab_headers(self) -> dict: + async def _get_gitlab_headers(self) -> dict[str, Any]: """ Retrieve the GitLab Token to construct the headers """ @@ -80,6 +81,66 @@ class GitLabService(GitService): except httpx.HTTPError: raise UnknownException('Unknown error') + + async def execute_graphql_query( + self, query: str, variables: dict[str, Any] + ) -> Any: + """ + Execute a GraphQL query against the GitLab GraphQL API + + Args: + query: The GraphQL query string + variables: Optional variables for the GraphQL query + + Returns: + The data portion of the GraphQL response + """ + try: + async with httpx.AsyncClient() as client: + gitlab_headers = await self._get_gitlab_headers() + # Add content type header for GraphQL + gitlab_headers['Content-Type'] = 'application/json' + + payload = { + "query": query, + "variables": variables, + } + + response = await client.post( + self.GRAPHQL_URL, + headers=gitlab_headers, + json=payload + ) + + if self.refresh and self._has_token_expired(response.status_code): + await self.get_latest_token() + gitlab_headers = await self._get_gitlab_headers() + gitlab_headers['Content-Type'] = 'application/json' + response = await client.post( + self.GRAPHQL_URL, + headers=gitlab_headers, + json=payload + ) + + response.raise_for_status() + result = response.json() + + # Check for GraphQL errors + if "errors" in result: + error_message = result["errors"][0].get("message", "Unknown GraphQL error") + raise UnknownException(f"GraphQL error: {error_message}") + + return result.get("data") + except httpx.HTTPStatusError as e: + if e.response.status_code == 401: + raise AuthenticationError('Invalid GitLab token') + + logger.warning(f'Status error on GL API: {e}') + raise UnknownException('Unknown error') + + except httpx.HTTPError as e: + logger.warning(f'HTTP error on GL API: {e}') + raise UnknownException('Unknown error') async def get_user(self) -> User: url = f'{self.BASE_URL}/user' diff --git a/openhands/integrations/provider.py b/openhands/integrations/provider.py index fed04f31cc..10f16eda1d 100644 --- a/openhands/integrations/provider.py +++ b/openhands/integrations/provider.py @@ -14,6 +14,7 @@ from pydantic import ( ) from pydantic.json import pydantic_encoder +from openhands.core.logger import openhands_logger as logger from openhands.events.action.action import Action from openhands.events.action.commands import CmdRunAction from openhands.events.stream import EventStream @@ -199,8 +200,8 @@ class ProviderHandler: service = self._get_service(provider) service_repos = await service.get_repositories(sort, app_mode) all_repos.extend(service_repos) - except Exception: - continue + except Exception as e: + logger.warning(f'Error fetching repos from {provider}: {e}') return all_repos