mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
[Feat]: BitBucket integration for Cloud OpenHands (#9225)
Co-authored-by: chuckbutkus <chuck@all-hands.dev>
This commit is contained in:
parent
0c1c570dac
commit
5c8bdd364e
@ -7,6 +7,7 @@ import { ModalBody } from "#/components/shared/modals/modal-body";
|
||||
import { BrandButton } from "../settings/brand-button";
|
||||
import GitHubLogo from "#/assets/branding/github-logo.svg?react";
|
||||
import GitLabLogo from "#/assets/branding/gitlab-logo.svg?react";
|
||||
import BitbucketLogo from "#/assets/branding/bitbucket-logo.svg?react";
|
||||
import { useAuthUrl } from "#/hooks/use-auth-url";
|
||||
import { GetConfigResponse } from "#/api/open-hands.types";
|
||||
|
||||
@ -23,6 +24,11 @@ export function AuthModal({ githubAuthUrl, appMode }: AuthModalProps) {
|
||||
identityProvider: "gitlab",
|
||||
});
|
||||
|
||||
const bitbucketAuthUrl = useAuthUrl({
|
||||
appMode: appMode || null,
|
||||
identityProvider: "bitbucket",
|
||||
});
|
||||
|
||||
const handleGitHubAuth = () => {
|
||||
if (githubAuthUrl) {
|
||||
// Always start the OIDC flow, let the backend handle TOS check
|
||||
@ -37,6 +43,13 @@ export function AuthModal({ githubAuthUrl, appMode }: AuthModalProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleBitbucketAuth = () => {
|
||||
if (bitbucketAuthUrl) {
|
||||
// Always start the OIDC flow, let the backend handle TOS check
|
||||
window.location.href = bitbucketAuthUrl;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalBackdrop>
|
||||
<ModalBody className="border border-tertiary">
|
||||
@ -67,6 +80,16 @@ export function AuthModal({ githubAuthUrl, appMode }: AuthModalProps) {
|
||||
>
|
||||
{t(I18nKey.GITLAB$CONNECT_TO_GITLAB)}
|
||||
</BrandButton>
|
||||
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={handleBitbucketAuth}
|
||||
className="w-full"
|
||||
startContent={<BitbucketLogo width={20} height={20} />}
|
||||
>
|
||||
{t(I18nKey.BITBUCKET$CONNECT_TO_BITBUCKET)}
|
||||
</BrandButton>
|
||||
</div>
|
||||
</ModalBody>
|
||||
</ModalBackdrop>
|
||||
|
||||
@ -34,10 +34,7 @@ export const useAuthCallback = () => {
|
||||
const loginMethod = searchParams.get("login_method");
|
||||
|
||||
// Set the login method if it's valid
|
||||
if (
|
||||
loginMethod === LoginMethod.GITHUB ||
|
||||
loginMethod === LoginMethod.GITLAB
|
||||
) {
|
||||
if (Object.values(LoginMethod).includes(loginMethod as LoginMethod)) {
|
||||
setLoginMethod(loginMethod as LoginMethod);
|
||||
|
||||
// Clean up the URL by removing the login_method parameter
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import base64
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
@ -15,9 +16,10 @@ from openhands.integrations.service_types import (
|
||||
User,
|
||||
)
|
||||
from openhands.server.types import AppMode
|
||||
from openhands.utils.import_utils import get_impl
|
||||
|
||||
|
||||
class BitbucketService(BaseGitService, GitService):
|
||||
class BitBucketService(BaseGitService, GitService):
|
||||
"""Default implementation of GitService for Bitbucket integration.
|
||||
|
||||
This is an extension point in OpenHands that allows applications to customize Bitbucket
|
||||
@ -300,3 +302,10 @@ class BitbucketService(BaseGitService, GitService):
|
||||
|
||||
# Return the URL to the pull request
|
||||
return data.get('links', {}).get('html', {}).get('href', '')
|
||||
|
||||
|
||||
bitbucket_service_cls = os.environ.get(
|
||||
'OPENHANDS_BITBUCKET_SERVICE_CLS',
|
||||
'openhands.integrations.bitbucket.bitbucket_service.BitBucketService',
|
||||
)
|
||||
BitBucketServiceImpl = get_impl(BitBucketService, bitbucket_service_cls)
|
||||
|
||||
@ -15,7 +15,7 @@ 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
|
||||
from openhands.integrations.bitbucket.bitbucket_service import BitbucketService
|
||||
from openhands.integrations.bitbucket.bitbucket_service import BitBucketServiceImpl
|
||||
from openhands.integrations.github.github_service import GithubServiceImpl
|
||||
from openhands.integrations.gitlab.gitlab_service import GitLabServiceImpl
|
||||
from openhands.integrations.service_types import (
|
||||
@ -110,7 +110,7 @@ class ProviderHandler:
|
||||
self.service_class_map: dict[ProviderType, type[GitService]] = {
|
||||
ProviderType.GITHUB: GithubServiceImpl,
|
||||
ProviderType.GITLAB: GitLabServiceImpl,
|
||||
ProviderType.BITBUCKET: BitbucketService,
|
||||
ProviderType.BITBUCKET: BitBucketServiceImpl,
|
||||
}
|
||||
|
||||
self.external_auth_id = external_auth_id
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from pydantic import SecretStr
|
||||
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.integrations.bitbucket.bitbucket_service import BitbucketService
|
||||
from openhands.integrations.bitbucket.bitbucket_service import BitBucketService
|
||||
from openhands.integrations.github.github_service import GitHubService
|
||||
from openhands.integrations.gitlab.gitlab_service import GitLabService
|
||||
from openhands.integrations.provider import ProviderType
|
||||
@ -49,7 +49,7 @@ async def validate_provider_token(
|
||||
# Try Bitbucket last
|
||||
bitbucket_error = None
|
||||
try:
|
||||
bitbucket_service = BitbucketService(token=token, base_domain=base_domain)
|
||||
bitbucket_service = BitBucketService(token=token, base_domain=base_domain)
|
||||
await bitbucket_service.get_user()
|
||||
return ProviderType.BITBUCKET
|
||||
except Exception as e:
|
||||
|
||||
@ -8,7 +8,7 @@ from fastmcp.server.dependencies import get_http_request
|
||||
from pydantic import Field
|
||||
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.integrations.bitbucket.bitbucket_service import BitbucketService
|
||||
from openhands.integrations.bitbucket.bitbucket_service import BitBucketServiceImpl
|
||||
from openhands.integrations.github.github_service import GithubServiceImpl
|
||||
from openhands.integrations.gitlab.gitlab_service import GitLabServiceImpl
|
||||
from openhands.integrations.provider import ProviderToken
|
||||
@ -242,7 +242,7 @@ async def create_bitbucket_pr(
|
||||
else ProviderToken()
|
||||
)
|
||||
|
||||
bitbucket_service = BitbucketService(
|
||||
bitbucket_service = BitBucketServiceImpl(
|
||||
user_id=bitbucket_token.user_id,
|
||||
external_auth_id=user_id,
|
||||
external_auth_token=access_token,
|
||||
|
||||
@ -7,6 +7,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||
import pytest
|
||||
from pydantic import SecretStr
|
||||
|
||||
from openhands.integrations.bitbucket.bitbucket_service import BitBucketService
|
||||
from openhands.integrations.provider import ProviderToken, ProviderType
|
||||
from openhands.integrations.service_types import ProviderType as ServiceProviderType
|
||||
from openhands.integrations.service_types import Repository
|
||||
@ -18,6 +19,7 @@ from openhands.resolver.send_pull_request import send_pull_request
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.server.routes.secrets import check_provider_tokens
|
||||
from openhands.server.settings import POSTProviderModel
|
||||
from openhands.server.types import AppMode
|
||||
|
||||
|
||||
# BitbucketIssueHandler Tests
|
||||
@ -360,7 +362,7 @@ async def test_validate_provider_token_with_bitbucket_token():
|
||||
patch('openhands.integrations.utils.GitHubService') as mock_github_service,
|
||||
patch('openhands.integrations.utils.GitLabService') as mock_gitlab_service,
|
||||
patch(
|
||||
'openhands.integrations.utils.BitbucketService'
|
||||
'openhands.integrations.utils.BitBucketService'
|
||||
) as mock_bitbucket_service,
|
||||
):
|
||||
# Set up the mocks
|
||||
@ -433,15 +435,9 @@ async def test_bitbucket_sort_parameter_mapping():
|
||||
"""
|
||||
Test that the Bitbucket service correctly maps sort parameters.
|
||||
"""
|
||||
from unittest.mock import patch
|
||||
|
||||
from pydantic import SecretStr
|
||||
|
||||
from openhands.integrations.bitbucket.bitbucket_service import BitbucketService
|
||||
from openhands.server.types import AppMode
|
||||
|
||||
# Create a service instance
|
||||
service = BitbucketService(token=SecretStr('test-token'))
|
||||
service = BitBucketService(token=SecretStr('test-token'))
|
||||
|
||||
# Mock the _make_request method to avoid actual API calls
|
||||
with patch.object(service, '_make_request') as mock_request:
|
||||
@ -478,7 +474,7 @@ async def test_validate_provider_token_with_empty_tokens():
|
||||
patch('openhands.integrations.utils.GitHubService') as mock_github_service,
|
||||
patch('openhands.integrations.utils.GitLabService') as mock_gitlab_service,
|
||||
patch(
|
||||
'openhands.integrations.utils.BitbucketService'
|
||||
'openhands.integrations.utils.BitBucketService'
|
||||
) as mock_bitbucket_service,
|
||||
):
|
||||
# Configure mocks to raise exceptions for invalid tokens
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user