Skip to content

Commit abe79fa

Browse files
committed
feat: Implement Bitbucket integration and PR revert synchronization
- Added RevertPRsBitbucketSyncHandler to manage pull request revert mappings. - Enhanced ExternalIntegrationsService with Bitbucket API methods for workspace and repository management. - Updated incidents integration to support Bitbucket as an incident provider. - Modified ETL incidents factory to handle Bitbucket incidents. - Introduced BITBUCKET enum in IncidentProvider for better integration handling. - Improved datetime parsing in time utility to support various formats. - Created unit tests for Bitbucket ETL handler to ensure proper functionality. - Updated Bitbucket authentication API to use email and API token instead of username and app password. - Refactored ConfigureBitbucketModalBody to accommodate new authentication method. - Adjusted auth utility functions to validate Bitbucket credentials using email and API token.
1 parent c4a5247 commit abe79fa

File tree

12 files changed

+1113
-228
lines changed

12 files changed

+1113
-228
lines changed

backend/analytics_server/mhq/exapi/models/bitbucket.py

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,110 @@ class BitbucketRepo:
2020

2121
def __init__(self, repo: Dict):
2222
self.name = repo.get("name", "")
23-
self.org_name = repo.get("workspace", {}).get("name", "")
24-
self.default_branch = repo.get("mainbranch", {}).get("name", "")
23+
workspace = repo.get("workspace", {})
24+
self.org_name = workspace.get("slug", workspace.get("name", ""))
25+
self.default_branch = repo.get("mainbranch", {}).get("name", "main")
2526
self.idempotency_key = str(repo.get("uuid", ""))
2627
self.slug = repo.get("slug", "")
2728
self.description = repo.get("description", "")
2829
self.web_url = repo.get("links", {}).get("html", {}).get("href", "")
2930
self.languages = repo.get("language")
3031

3132
def __hash__(self):
32-
return hash(self.idempotency_key)
33+
return hash(self.idempotency_key)
34+
35+
36+
class BitbucketPRState(Enum):
37+
OPEN = "OPEN"
38+
MERGED = "MERGED"
39+
SUPERSEDED = "SUPERSEDED"
40+
DECLINED = "DECLINED"
41+
42+
43+
@dataclass
44+
class BitbucketPR:
45+
number: int
46+
title: str
47+
url: str
48+
author: str
49+
reviewers: List[str]
50+
state: BitbucketPRState
51+
base_branch: str
52+
head_branch: str
53+
data: Dict
54+
created_at: datetime
55+
updated_at: datetime
56+
merged_at: Optional[datetime] = None
57+
closed_at: Optional[datetime] = None
58+
merge_commit_sha: Optional[str] = None
59+
60+
def __init__(self, pr: Dict):
61+
self.number = pr.get("id", 0)
62+
self.title = pr.get("title", "")
63+
self.url = pr.get("links", {}).get("html", {}).get("href", "")
64+
self.author = pr.get("author", {}).get("display_name", "")
65+
self.reviewers = [
66+
reviewer.get("display_name", "")
67+
for reviewer in pr.get("reviewers", [])
68+
]
69+
state_str = pr.get("state", "OPEN").upper()
70+
try:
71+
self.state = BitbucketPRState(state_str)
72+
except ValueError:
73+
74+
self.state = BitbucketPRState.OPEN
75+
self.base_branch = pr.get("destination", {}).get("branch", {}).get("name", "")
76+
self.head_branch = pr.get("source", {}).get("branch", {}).get("name", "")
77+
self.data = pr
78+
self.created_at = dt_from_iso_time_string(pr.get("created_on", "")) or datetime.now()
79+
self.updated_at = dt_from_iso_time_string(pr.get("updated_on", "")) or datetime.now()
80+
81+
# Parse merge/close dates
82+
if pr.get("merge_commit"):
83+
self.merged_at = self.updated_at
84+
self.merge_commit_sha = pr.get("merge_commit", {}).get("hash", "")
85+
86+
if self.state in [BitbucketPRState.DECLINED, BitbucketPRState.SUPERSEDED]:
87+
self.closed_at = self.updated_at
88+
89+
90+
@dataclass
91+
class BitbucketCommit:
92+
hash: str
93+
message: str
94+
url: str
95+
data: Dict
96+
author_email: str
97+
created_at: datetime
98+
99+
def __init__(self, commit: Dict):
100+
self.hash = commit.get("hash", "")
101+
self.message = commit.get("message", "")
102+
self.url = commit.get("links", {}).get("html", {}).get("href", "")
103+
self.data = commit
104+
self.author_email = commit.get("author", {}).get("raw", "").split("<")[-1].replace(">", "").strip()
105+
self.created_at = dt_from_iso_time_string(commit.get("date", "")) or datetime.now()
106+
107+
108+
class BitbucketReviewState(Enum):
109+
APPROVED = "approved"
110+
CHANGES_REQUESTED = "changes_requested"
111+
COMMENTED = "commented"
112+
113+
114+
@dataclass
115+
class BitbucketReview:
116+
id: str
117+
state: BitbucketReviewState
118+
created_at: datetime
119+
actor_username: str
120+
data: Dict
121+
idempotency_key: str
122+
123+
def __init__(self, review: Dict):
124+
self.id = str(review.get("uuid", ""))
125+
self.state = BitbucketReviewState(review.get("state", "commented"))
126+
self.created_at = dt_from_iso_time_string(review.get("date", "")) or datetime.now()
127+
self.actor_username = review.get("user", {}).get("display_name", "")
128+
self.data = review
129+
self.idempotency_key = self.id

0 commit comments

Comments
 (0)