xai-review 0.37.0__py3-none-any.whl → 0.38.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of xai-review might be problematic. Click here for more details.
- ai_review/clients/{bitbucket → bitbucket_cloud}/client.py +6 -6
- ai_review/clients/{bitbucket → bitbucket_cloud}/pr/client.py +51 -39
- ai_review/clients/bitbucket_cloud/pr/schema/comments.py +59 -0
- ai_review/clients/{bitbucket → bitbucket_cloud}/pr/schema/files.py +7 -7
- ai_review/clients/bitbucket_cloud/pr/schema/pull_request.py +34 -0
- ai_review/clients/{bitbucket → bitbucket_cloud}/pr/schema/user.py +1 -1
- ai_review/clients/bitbucket_cloud/pr/types.py +44 -0
- ai_review/clients/{bitbucket → bitbucket_cloud}/tools.py +1 -1
- ai_review/clients/bitbucket_server/client.py +32 -0
- ai_review/clients/bitbucket_server/pr/client.py +163 -0
- ai_review/clients/bitbucket_server/pr/schema/changes.py +36 -0
- ai_review/clients/bitbucket_server/pr/schema/comments.py +55 -0
- ai_review/clients/bitbucket_server/pr/schema/pull_request.py +48 -0
- ai_review/clients/bitbucket_server/pr/schema/user.py +13 -0
- ai_review/clients/bitbucket_server/pr/types.py +44 -0
- ai_review/clients/bitbucket_server/tools.py +6 -0
- ai_review/libs/config/vcs/base.py +23 -6
- ai_review/libs/config/vcs/{bitbucket.py → bitbucket_cloud.py} +2 -2
- ai_review/libs/config/vcs/bitbucket_server.py +13 -0
- ai_review/libs/constants/vcs_provider.py +2 -1
- ai_review/libs/http/client.py +1 -1
- ai_review/services/vcs/bitbucket_cloud/__init__.py +0 -0
- ai_review/services/vcs/{bitbucket → bitbucket_cloud}/adapter.py +2 -2
- ai_review/services/vcs/{bitbucket → bitbucket_cloud}/client.py +24 -21
- ai_review/services/vcs/bitbucket_server/__init__.py +0 -0
- ai_review/services/vcs/bitbucket_server/adapter.py +27 -0
- ai_review/services/vcs/bitbucket_server/client.py +263 -0
- ai_review/services/vcs/factory.py +6 -3
- ai_review/tests/fixtures/clients/bitbucket_cloud.py +207 -0
- ai_review/tests/fixtures/clients/bitbucket_server.py +265 -0
- ai_review/tests/suites/clients/bitbucket_cloud/__init__.py +0 -0
- ai_review/tests/suites/clients/bitbucket_cloud/test_client.py +14 -0
- ai_review/tests/suites/clients/bitbucket_cloud/test_tools.py +31 -0
- ai_review/tests/suites/clients/bitbucket_server/__init__.py +0 -0
- ai_review/tests/suites/clients/bitbucket_server/test_client.py +14 -0
- ai_review/tests/suites/clients/bitbucket_server/test_tools.py +38 -0
- ai_review/tests/suites/services/vcs/bitbucket_cloud/__init__.py +0 -0
- ai_review/tests/suites/services/vcs/{bitbucket → bitbucket_cloud}/test_adapter.py +24 -24
- ai_review/tests/suites/services/vcs/{bitbucket → bitbucket_cloud}/test_client.py +51 -51
- ai_review/tests/suites/services/vcs/bitbucket_server/__init__.py +0 -0
- ai_review/tests/suites/services/vcs/bitbucket_server/test_adapter.py +115 -0
- ai_review/tests/suites/services/vcs/bitbucket_server/test_client.py +201 -0
- ai_review/tests/suites/services/vcs/test_factory.py +11 -4
- {xai_review-0.37.0.dist-info → xai_review-0.38.0.dist-info}/METADATA +9 -7
- {xai_review-0.37.0.dist-info → xai_review-0.38.0.dist-info}/RECORD +55 -33
- ai_review/clients/bitbucket/pr/schema/comments.py +0 -63
- ai_review/clients/bitbucket/pr/schema/pull_request.py +0 -34
- ai_review/clients/bitbucket/pr/types.py +0 -44
- ai_review/tests/fixtures/clients/bitbucket.py +0 -204
- ai_review/tests/suites/clients/bitbucket/test_client.py +0 -14
- ai_review/tests/suites/clients/bitbucket/test_tools.py +0 -31
- /ai_review/clients/{bitbucket → bitbucket_cloud}/__init__.py +0 -0
- /ai_review/clients/{bitbucket → bitbucket_cloud}/pr/__init__.py +0 -0
- /ai_review/clients/{bitbucket → bitbucket_cloud}/pr/schema/__init__.py +0 -0
- /ai_review/{services/vcs/bitbucket → clients/bitbucket_server}/__init__.py +0 -0
- /ai_review/{tests/suites/clients/bitbucket → clients/bitbucket_server/pr}/__init__.py +0 -0
- /ai_review/{tests/suites/services/vcs/bitbucket → clients/bitbucket_server/pr/schema}/__init__.py +0 -0
- {xai_review-0.37.0.dist-info → xai_review-0.38.0.dist-info}/WHEEL +0 -0
- {xai_review-0.37.0.dist-info → xai_review-0.38.0.dist-info}/entry_points.txt +0 -0
- {xai_review-0.37.0.dist-info → xai_review-0.38.0.dist-info}/licenses/LICENSE +0 -0
- {xai_review-0.37.0.dist-info → xai_review-0.38.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
2
|
+
|
|
3
|
+
from ai_review.clients.bitbucket_server.pr.schema.user import BitbucketServerUserSchema
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BitbucketServerCommentParentSchema(BaseModel):
|
|
7
|
+
id: int
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BitbucketServerCommentAnchorSchema(BaseModel):
|
|
11
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
12
|
+
|
|
13
|
+
path: str | None = None
|
|
14
|
+
line: int | None = None
|
|
15
|
+
line_type: str | None = Field(default=None, alias="lineType")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BitbucketServerCommentSchema(BaseModel):
|
|
19
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
20
|
+
|
|
21
|
+
id: int
|
|
22
|
+
text: str
|
|
23
|
+
author: BitbucketServerUserSchema
|
|
24
|
+
anchor: BitbucketServerCommentAnchorSchema | None = None
|
|
25
|
+
comments: list["BitbucketServerCommentSchema"] = Field(default_factory=list)
|
|
26
|
+
created_date: int = Field(alias="createdDate")
|
|
27
|
+
updated_date: int = Field(alias="updatedDate")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BitbucketServerGetPRCommentsQuerySchema(BaseModel):
|
|
31
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
32
|
+
|
|
33
|
+
start: int = 0
|
|
34
|
+
limit: int = 100
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class BitbucketServerGetPRCommentsResponseSchema(BaseModel):
|
|
38
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
39
|
+
|
|
40
|
+
size: int
|
|
41
|
+
limit: int
|
|
42
|
+
start: int
|
|
43
|
+
values: list[BitbucketServerCommentSchema]
|
|
44
|
+
is_last_page: bool = Field(alias="isLastPage")
|
|
45
|
+
next_page_start: int | None = Field(default=None, alias="nextPageStart")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class BitbucketServerCreatePRCommentRequestSchema(BaseModel):
|
|
49
|
+
text: str
|
|
50
|
+
parent: BitbucketServerCommentParentSchema | None = None
|
|
51
|
+
anchor: BitbucketServerCommentAnchorSchema | None = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class BitbucketServerCreatePRCommentResponseSchema(BitbucketServerCommentSchema):
|
|
55
|
+
pass
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
2
|
+
|
|
3
|
+
from ai_review.clients.bitbucket_server.pr.schema.user import BitbucketServerUserSchema
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BitbucketServerProjectSchema(BaseModel):
|
|
7
|
+
key: str
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BitbucketServerRepositorySchema(BaseModel):
|
|
11
|
+
slug: str
|
|
12
|
+
name: str
|
|
13
|
+
project: BitbucketServerProjectSchema
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BitbucketServerRefSchema(BaseModel):
|
|
17
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
18
|
+
|
|
19
|
+
id: str
|
|
20
|
+
display_id: str = Field(alias="displayId")
|
|
21
|
+
latest_commit: str = Field(alias="latestCommit")
|
|
22
|
+
repository: BitbucketServerRepositorySchema
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BitbucketServerParticipantSchema(BaseModel):
|
|
26
|
+
user: BitbucketServerUserSchema
|
|
27
|
+
role: str
|
|
28
|
+
approved: bool | None = None
|
|
29
|
+
status: str | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BitbucketServerGetPRResponseSchema(BaseModel):
|
|
33
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
34
|
+
|
|
35
|
+
id: int
|
|
36
|
+
version: int | None = None
|
|
37
|
+
title: str
|
|
38
|
+
description: str | None = None
|
|
39
|
+
state: str
|
|
40
|
+
open: bool
|
|
41
|
+
locked: bool
|
|
42
|
+
author: BitbucketServerParticipantSchema
|
|
43
|
+
reviewers: list[BitbucketServerParticipantSchema] = Field(default_factory=list)
|
|
44
|
+
from_ref: BitbucketServerRefSchema = Field(alias="fromRef")
|
|
45
|
+
to_ref: BitbucketServerRefSchema = Field(alias="toRef")
|
|
46
|
+
created_date: int = Field(alias="createdDate")
|
|
47
|
+
updated_date: int = Field(alias="updatedDate")
|
|
48
|
+
links: dict | None = None
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BitbucketServerUserSchema(BaseModel):
|
|
5
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
6
|
+
|
|
7
|
+
id: int | None = None
|
|
8
|
+
name: str
|
|
9
|
+
slug: str | None = None
|
|
10
|
+
type: str | None = None
|
|
11
|
+
active: bool | None = None
|
|
12
|
+
display_name: str = Field(alias="displayName")
|
|
13
|
+
email_address: str | None = Field(default=None, alias="emailAddress")
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import Protocol
|
|
2
|
+
|
|
3
|
+
from ai_review.clients.bitbucket_server.pr.schema.changes import BitbucketServerGetPRChangesResponseSchema
|
|
4
|
+
from ai_review.clients.bitbucket_server.pr.schema.comments import (
|
|
5
|
+
BitbucketServerGetPRCommentsResponseSchema,
|
|
6
|
+
BitbucketServerCreatePRCommentRequestSchema,
|
|
7
|
+
BitbucketServerCreatePRCommentResponseSchema
|
|
8
|
+
)
|
|
9
|
+
from ai_review.clients.bitbucket_server.pr.schema.pull_request import BitbucketServerGetPRResponseSchema
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BitbucketServerPullRequestsHTTPClientProtocol(Protocol):
|
|
13
|
+
async def get_pull_request(
|
|
14
|
+
self,
|
|
15
|
+
project_key: str,
|
|
16
|
+
repo_slug: str,
|
|
17
|
+
pull_request_id: int,
|
|
18
|
+
) -> BitbucketServerGetPRResponseSchema:
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
async def get_changes(
|
|
22
|
+
self,
|
|
23
|
+
project_key: str,
|
|
24
|
+
repo_slug: str,
|
|
25
|
+
pull_request_id: int,
|
|
26
|
+
) -> BitbucketServerGetPRChangesResponseSchema:
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
async def get_comments(
|
|
30
|
+
self,
|
|
31
|
+
project_key: str,
|
|
32
|
+
repo_slug: str,
|
|
33
|
+
pull_request_id: int,
|
|
34
|
+
) -> BitbucketServerGetPRCommentsResponseSchema:
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
async def create_comment(
|
|
38
|
+
self,
|
|
39
|
+
project_key: str,
|
|
40
|
+
repo_slug: str,
|
|
41
|
+
pull_request_id: int,
|
|
42
|
+
request: BitbucketServerCreatePRCommentRequestSchema,
|
|
43
|
+
) -> BitbucketServerCreatePRCommentResponseSchema:
|
|
44
|
+
...
|
|
@@ -2,7 +2,14 @@ from typing import Annotated, Literal
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, Field
|
|
4
4
|
|
|
5
|
-
from ai_review.libs.config.vcs.
|
|
5
|
+
from ai_review.libs.config.vcs.bitbucket_cloud import (
|
|
6
|
+
BitbucketCloudPipelineConfig,
|
|
7
|
+
BitbucketCloudHTTPClientConfig
|
|
8
|
+
)
|
|
9
|
+
from ai_review.libs.config.vcs.bitbucket_server import (
|
|
10
|
+
BitbucketServerPipelineConfig,
|
|
11
|
+
BitbucketServerHTTPClientConfig
|
|
12
|
+
)
|
|
6
13
|
from ai_review.libs.config.vcs.gitea import GiteaPipelineConfig, GiteaHTTPClientConfig
|
|
7
14
|
from ai_review.libs.config.vcs.github import GitHubPipelineConfig, GitHubHTTPClientConfig
|
|
8
15
|
from ai_review.libs.config.vcs.gitlab import GitLabPipelineConfig, GitLabHTTPClientConfig
|
|
@@ -33,13 +40,23 @@ class GitHubVCSConfig(VCSConfigBase):
|
|
|
33
40
|
http_client: GitHubHTTPClientConfig
|
|
34
41
|
|
|
35
42
|
|
|
36
|
-
class
|
|
37
|
-
provider: Literal[VCSProvider.
|
|
38
|
-
pipeline:
|
|
39
|
-
http_client:
|
|
43
|
+
class BitbucketCloudVCSConfig(VCSConfigBase):
|
|
44
|
+
provider: Literal[VCSProvider.BITBUCKET_CLOUD]
|
|
45
|
+
pipeline: BitbucketCloudPipelineConfig
|
|
46
|
+
http_client: BitbucketCloudHTTPClientConfig
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class BitbucketServerVCSConfig(VCSConfigBase):
|
|
50
|
+
provider: Literal[VCSProvider.BITBUCKET_SERVER]
|
|
51
|
+
pipeline: BitbucketServerPipelineConfig
|
|
52
|
+
http_client: BitbucketServerHTTPClientConfig
|
|
40
53
|
|
|
41
54
|
|
|
42
55
|
VCSConfig = Annotated[
|
|
43
|
-
GiteaVCSConfig
|
|
56
|
+
GiteaVCSConfig
|
|
57
|
+
| GitLabVCSConfig
|
|
58
|
+
| GitHubVCSConfig
|
|
59
|
+
| BitbucketCloudVCSConfig
|
|
60
|
+
| BitbucketServerVCSConfig,
|
|
44
61
|
Field(discriminator="provider")
|
|
45
62
|
]
|
|
@@ -3,11 +3,11 @@ from pydantic import BaseModel
|
|
|
3
3
|
from ai_review.libs.config.http import HTTPClientWithTokenConfig
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class
|
|
6
|
+
class BitbucketCloudPipelineConfig(BaseModel):
|
|
7
7
|
workspace: str
|
|
8
8
|
repo_slug: str
|
|
9
9
|
pull_request_id: str
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class
|
|
12
|
+
class BitbucketCloudHTTPClientConfig(HTTPClientWithTokenConfig):
|
|
13
13
|
pass
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
from ai_review.libs.config.http import HTTPClientWithTokenConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BitbucketServerPipelineConfig(BaseModel):
|
|
7
|
+
project_key: str
|
|
8
|
+
repo_slug: str
|
|
9
|
+
pull_request_id: int
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BitbucketServerHTTPClientConfig(HTTPClientWithTokenConfig):
|
|
13
|
+
pass
|
ai_review/libs/http/client.py
CHANGED
|
@@ -8,7 +8,7 @@ class HTTPClient:
|
|
|
8
8
|
self.client = client
|
|
9
9
|
|
|
10
10
|
async def get(self, url: str, query: QueryParams | None = None) -> Response:
|
|
11
|
-
return await self.client.get(url=url, params=query)
|
|
11
|
+
return await self.client.get(url=url, params=query, follow_redirects=True)
|
|
12
12
|
|
|
13
13
|
async def post(self, url: str, json: Any | None = None) -> Response:
|
|
14
14
|
return await self.client.post(url=url, json=json)
|
|
File without changes
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
from ai_review.clients.
|
|
1
|
+
from ai_review.clients.bitbucket_cloud.pr.schema.comments import BitbucketCloudPRCommentSchema
|
|
2
2
|
from ai_review.services.vcs.types import ReviewCommentSchema, UserSchema
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
def get_review_comment_from_bitbucket_pr_comment(comment:
|
|
5
|
+
def get_review_comment_from_bitbucket_pr_comment(comment: BitbucketCloudPRCommentSchema) -> ReviewCommentSchema:
|
|
6
6
|
parent_id = comment.parent.id if comment.parent else None
|
|
7
7
|
thread_id = parent_id or comment.id
|
|
8
8
|
|
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
2
|
|
|
3
|
-
from ai_review.clients.
|
|
4
|
-
from ai_review.clients.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
from ai_review.clients.bitbucket_cloud.client import get_bitbucket_cloud_http_client
|
|
4
|
+
from ai_review.clients.bitbucket_cloud.pr.schema.comments import (
|
|
5
|
+
BitbucketCloudCommentParentSchema,
|
|
6
|
+
BitbucketCloudCommentInlineSchema,
|
|
7
|
+
BitbucketCloudCommentContentSchema,
|
|
8
|
+
BitbucketCloudCreatePRCommentRequestSchema,
|
|
8
9
|
)
|
|
9
10
|
from ai_review.config import settings
|
|
10
11
|
from ai_review.libs.logger import get_logger
|
|
11
|
-
from ai_review.services.vcs.
|
|
12
|
+
from ai_review.services.vcs.bitbucket_cloud.adapter import get_review_comment_from_bitbucket_pr_comment
|
|
12
13
|
from ai_review.services.vcs.types import (
|
|
13
14
|
VCSClientProtocol,
|
|
15
|
+
ThreadKind,
|
|
14
16
|
UserSchema,
|
|
15
17
|
BranchRefSchema,
|
|
16
18
|
ReviewInfoSchema,
|
|
17
|
-
|
|
19
|
+
ReviewThreadSchema,
|
|
20
|
+
ReviewCommentSchema,
|
|
18
21
|
)
|
|
19
22
|
|
|
20
|
-
logger = get_logger("
|
|
23
|
+
logger = get_logger("BITBUCKET_CLOUD_VCS_CLIENT")
|
|
21
24
|
|
|
22
25
|
|
|
23
|
-
class
|
|
26
|
+
class BitbucketCloudVCSClient(VCSClientProtocol):
|
|
24
27
|
def __init__(self):
|
|
25
|
-
self.http_client =
|
|
28
|
+
self.http_client = get_bitbucket_cloud_http_client()
|
|
26
29
|
self.workspace = settings.vcs.pipeline.workspace
|
|
27
30
|
self.repo_slug = settings.vcs.pipeline.repo_slug
|
|
28
31
|
self.pull_request_id = settings.vcs.pipeline.pull_request_id
|
|
@@ -129,8 +132,8 @@ class BitbucketVCSClient(VCSClientProtocol):
|
|
|
129
132
|
async def create_general_comment(self, message: str) -> None:
|
|
130
133
|
try:
|
|
131
134
|
logger.info(f"Posting general comment to PR {self.pull_request_ref}: {message}")
|
|
132
|
-
request =
|
|
133
|
-
content=
|
|
135
|
+
request = BitbucketCloudCreatePRCommentRequestSchema(
|
|
136
|
+
content=BitbucketCloudCommentContentSchema(raw=message)
|
|
134
137
|
)
|
|
135
138
|
await self.http_client.pr.create_comment(
|
|
136
139
|
workspace=self.workspace,
|
|
@@ -146,9 +149,9 @@ class BitbucketVCSClient(VCSClientProtocol):
|
|
|
146
149
|
async def create_inline_comment(self, file: str, line: int, message: str) -> None:
|
|
147
150
|
try:
|
|
148
151
|
logger.info(f"Posting inline comment in {self.pull_request_ref} at {file}:{line}: {message}")
|
|
149
|
-
request =
|
|
150
|
-
content=
|
|
151
|
-
inline=
|
|
152
|
+
request = BitbucketCloudCreatePRCommentRequestSchema(
|
|
153
|
+
content=BitbucketCloudCommentContentSchema(raw=message),
|
|
154
|
+
inline=BitbucketCloudCommentInlineSchema(path=file, to_line=line),
|
|
152
155
|
)
|
|
153
156
|
await self.http_client.pr.create_comment(
|
|
154
157
|
workspace=self.workspace,
|
|
@@ -165,9 +168,9 @@ class BitbucketVCSClient(VCSClientProtocol):
|
|
|
165
168
|
async def create_inline_reply(self, thread_id: int | str, message: str) -> None:
|
|
166
169
|
try:
|
|
167
170
|
logger.info(f"Replying to inline thread {thread_id=} in PR {self.pull_request_ref}")
|
|
168
|
-
request =
|
|
169
|
-
parent=
|
|
170
|
-
content=
|
|
171
|
+
request = BitbucketCloudCreatePRCommentRequestSchema(
|
|
172
|
+
parent=BitbucketCloudCommentParentSchema(id=int(thread_id)),
|
|
173
|
+
content=BitbucketCloudCommentContentSchema(raw=message),
|
|
171
174
|
)
|
|
172
175
|
await self.http_client.pr.create_comment(
|
|
173
176
|
workspace=self.workspace,
|
|
@@ -185,9 +188,9 @@ class BitbucketVCSClient(VCSClientProtocol):
|
|
|
185
188
|
async def create_summary_reply(self, thread_id: int | str, message: str) -> None:
|
|
186
189
|
try:
|
|
187
190
|
logger.info(f"Replying to summary thread {thread_id=} in PR {self.pull_request_ref}")
|
|
188
|
-
request =
|
|
189
|
-
content=
|
|
190
|
-
parent=
|
|
191
|
+
request = BitbucketCloudCreatePRCommentRequestSchema(
|
|
192
|
+
content=BitbucketCloudCommentContentSchema(raw=message),
|
|
193
|
+
parent=BitbucketCloudCommentParentSchema(id=int(thread_id)),
|
|
191
194
|
)
|
|
192
195
|
await self.http_client.pr.create_comment(
|
|
193
196
|
workspace=self.workspace,
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from ai_review.clients.bitbucket_server.pr.schema.comments import BitbucketServerCommentSchema
|
|
2
|
+
from ai_review.services.vcs.types import ReviewCommentSchema, UserSchema
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_review_comment_from_bitbucket_server_comment(comment: BitbucketServerCommentSchema) -> ReviewCommentSchema:
|
|
6
|
+
parent_id = None
|
|
7
|
+
thread_id = comment.id
|
|
8
|
+
|
|
9
|
+
user = comment.author
|
|
10
|
+
author = UserSchema(
|
|
11
|
+
id=user.id if user else None,
|
|
12
|
+
name=user.display_name or user.name or "",
|
|
13
|
+
username=user.slug or user.name or "",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
file = comment.anchor.path if comment.anchor and comment.anchor.path else None
|
|
17
|
+
line = comment.anchor.line if comment.anchor else None
|
|
18
|
+
|
|
19
|
+
return ReviewCommentSchema(
|
|
20
|
+
id=comment.id,
|
|
21
|
+
body=comment.text or "",
|
|
22
|
+
file=file,
|
|
23
|
+
line=line,
|
|
24
|
+
author=author,
|
|
25
|
+
parent_id=parent_id,
|
|
26
|
+
thread_id=thread_id,
|
|
27
|
+
)
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
|
|
3
|
+
from ai_review.clients.bitbucket_server.client import get_bitbucket_server_http_client
|
|
4
|
+
from ai_review.clients.bitbucket_server.pr.schema.comments import (
|
|
5
|
+
BitbucketServerCommentAnchorSchema,
|
|
6
|
+
BitbucketServerCommentParentSchema,
|
|
7
|
+
BitbucketServerCreatePRCommentRequestSchema
|
|
8
|
+
)
|
|
9
|
+
from ai_review.config import settings
|
|
10
|
+
from ai_review.libs.logger import get_logger
|
|
11
|
+
from ai_review.services.vcs.bitbucket_server.adapter import get_review_comment_from_bitbucket_server_comment
|
|
12
|
+
from ai_review.services.vcs.types import (
|
|
13
|
+
VCSClientProtocol,
|
|
14
|
+
ThreadKind,
|
|
15
|
+
UserSchema,
|
|
16
|
+
BranchRefSchema,
|
|
17
|
+
ReviewInfoSchema,
|
|
18
|
+
ReviewThreadSchema,
|
|
19
|
+
ReviewCommentSchema,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
logger = get_logger("BITBUCKET_SERVER_VCS_CLIENT")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BitbucketServerVCSClient(VCSClientProtocol):
|
|
26
|
+
def __init__(self):
|
|
27
|
+
self.http_client = get_bitbucket_server_http_client()
|
|
28
|
+
self.project_key = settings.vcs.pipeline.project_key
|
|
29
|
+
self.repo_slug = settings.vcs.pipeline.repo_slug
|
|
30
|
+
self.pull_request_id = settings.vcs.pipeline.pull_request_id
|
|
31
|
+
self.pull_request_ref = f"{self.project_key}/{self.repo_slug}#{self.pull_request_id}"
|
|
32
|
+
|
|
33
|
+
# --- Review info ---
|
|
34
|
+
async def get_review_info(self) -> ReviewInfoSchema:
|
|
35
|
+
try:
|
|
36
|
+
pr = await self.http_client.pr.get_pull_request(
|
|
37
|
+
project_key=self.project_key,
|
|
38
|
+
repo_slug=self.repo_slug,
|
|
39
|
+
pull_request_id=self.pull_request_id,
|
|
40
|
+
)
|
|
41
|
+
changes = await self.http_client.pr.get_changes(
|
|
42
|
+
project_key=self.project_key,
|
|
43
|
+
repo_slug=self.repo_slug,
|
|
44
|
+
pull_request_id=self.pull_request_id,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
logger.info(f"Fetched PR info for {self.pull_request_ref}")
|
|
48
|
+
|
|
49
|
+
return ReviewInfoSchema(
|
|
50
|
+
id=pr.id,
|
|
51
|
+
title=pr.title,
|
|
52
|
+
description=pr.description or "",
|
|
53
|
+
author=UserSchema(
|
|
54
|
+
id=pr.author.user.id,
|
|
55
|
+
name=pr.author.user.display_name,
|
|
56
|
+
username=pr.author.user.slug or pr.author.user.name,
|
|
57
|
+
),
|
|
58
|
+
labels=[], # Bitbucket Server не поддерживает labels напрямую
|
|
59
|
+
base_sha=pr.to_ref.latest_commit,
|
|
60
|
+
head_sha=pr.from_ref.latest_commit,
|
|
61
|
+
assignees=[], # нет отдельного поля в Bitbucket Server API
|
|
62
|
+
reviewers=[
|
|
63
|
+
UserSchema(
|
|
64
|
+
id=user.user.id,
|
|
65
|
+
name=user.user.display_name,
|
|
66
|
+
username=user.user.slug or user.user.name,
|
|
67
|
+
)
|
|
68
|
+
for user in pr.reviewers
|
|
69
|
+
],
|
|
70
|
+
source_branch=BranchRefSchema(
|
|
71
|
+
ref=pr.from_ref.display_id,
|
|
72
|
+
sha=pr.from_ref.latest_commit,
|
|
73
|
+
),
|
|
74
|
+
target_branch=BranchRefSchema(
|
|
75
|
+
ref=pr.to_ref.display_id,
|
|
76
|
+
sha=pr.to_ref.latest_commit,
|
|
77
|
+
),
|
|
78
|
+
changed_files=[
|
|
79
|
+
change.path.to_string
|
|
80
|
+
for change in changes.values
|
|
81
|
+
if change.path and change.path.to_string
|
|
82
|
+
],
|
|
83
|
+
)
|
|
84
|
+
except Exception as error:
|
|
85
|
+
logger.exception(f"Failed to fetch PR info {self.pull_request_ref}: {error}")
|
|
86
|
+
return ReviewInfoSchema()
|
|
87
|
+
|
|
88
|
+
# --- Comments ---
|
|
89
|
+
async def get_general_comments(self) -> list[ReviewCommentSchema]:
|
|
90
|
+
try:
|
|
91
|
+
response = await self.http_client.pr.get_comments(
|
|
92
|
+
project_key=self.project_key,
|
|
93
|
+
repo_slug=self.repo_slug,
|
|
94
|
+
pull_request_id=self.pull_request_id,
|
|
95
|
+
)
|
|
96
|
+
logger.info(f"Fetched general comments for {self.pull_request_ref}")
|
|
97
|
+
|
|
98
|
+
return [
|
|
99
|
+
get_review_comment_from_bitbucket_server_comment(comment)
|
|
100
|
+
for comment in response.values
|
|
101
|
+
if comment.anchor is None # нет привязки к файлу/строке — значит общий комментарий
|
|
102
|
+
]
|
|
103
|
+
except Exception as error:
|
|
104
|
+
logger.exception(f"Failed to fetch general comments for {self.pull_request_ref}: {error}")
|
|
105
|
+
return []
|
|
106
|
+
|
|
107
|
+
async def get_inline_comments(self) -> list[ReviewCommentSchema]:
|
|
108
|
+
try:
|
|
109
|
+
response = await self.http_client.pr.get_comments(
|
|
110
|
+
project_key=self.project_key,
|
|
111
|
+
repo_slug=self.repo_slug,
|
|
112
|
+
pull_request_id=self.pull_request_id,
|
|
113
|
+
)
|
|
114
|
+
logger.info(f"Fetched inline comments for {self.pull_request_ref}")
|
|
115
|
+
|
|
116
|
+
return [
|
|
117
|
+
get_review_comment_from_bitbucket_server_comment(comment)
|
|
118
|
+
for comment in response.values
|
|
119
|
+
if comment.anchor is not None and comment.anchor.path is not None
|
|
120
|
+
]
|
|
121
|
+
except Exception as error:
|
|
122
|
+
logger.exception(f"Failed to fetch inline comments for {self.pull_request_ref}: {error}")
|
|
123
|
+
return []
|
|
124
|
+
|
|
125
|
+
async def create_general_comment(self, message: str) -> None:
|
|
126
|
+
try:
|
|
127
|
+
logger.info(f"Posting general comment to PR {self.pull_request_ref}: {message}")
|
|
128
|
+
|
|
129
|
+
request = BitbucketServerCreatePRCommentRequestSchema(text=message)
|
|
130
|
+
|
|
131
|
+
await self.http_client.pr.create_comment(
|
|
132
|
+
project_key=self.project_key,
|
|
133
|
+
repo_slug=self.repo_slug,
|
|
134
|
+
pull_request_id=self.pull_request_id,
|
|
135
|
+
request=request,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
logger.info(f"Created general comment in PR {self.pull_request_ref}")
|
|
139
|
+
|
|
140
|
+
except Exception as error:
|
|
141
|
+
logger.exception(f"Failed to create general comment in PR {self.pull_request_ref}: {error}")
|
|
142
|
+
raise
|
|
143
|
+
|
|
144
|
+
async def create_inline_comment(self, file: str, line: int, message: str) -> None:
|
|
145
|
+
try:
|
|
146
|
+
logger.info(f"Posting inline comment in {self.pull_request_ref} at {file}:{line}: {message}")
|
|
147
|
+
|
|
148
|
+
anchor = BitbucketServerCommentAnchorSchema(path=file, line=line, line_type="ADDED")
|
|
149
|
+
request = BitbucketServerCreatePRCommentRequestSchema(text=message, anchor=anchor)
|
|
150
|
+
|
|
151
|
+
await self.http_client.pr.create_comment(
|
|
152
|
+
project_key=self.project_key,
|
|
153
|
+
repo_slug=self.repo_slug,
|
|
154
|
+
pull_request_id=self.pull_request_id,
|
|
155
|
+
request=request,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
logger.info(f"Created inline comment in {self.pull_request_ref} at {file}:{line}")
|
|
159
|
+
|
|
160
|
+
except Exception as error:
|
|
161
|
+
logger.exception(
|
|
162
|
+
f"Failed to create inline comment in {self.pull_request_ref} at {file}:{line}: {error}")
|
|
163
|
+
raise
|
|
164
|
+
|
|
165
|
+
# --- Replies ---
|
|
166
|
+
async def create_inline_reply(self, thread_id: int | str, message: str) -> None:
|
|
167
|
+
try:
|
|
168
|
+
logger.info(f"Replying to inline thread {thread_id=} in PR {self.pull_request_ref}")
|
|
169
|
+
request = BitbucketServerCreatePRCommentRequestSchema(
|
|
170
|
+
text=message,
|
|
171
|
+
parent=BitbucketServerCommentParentSchema(id=int(thread_id)),
|
|
172
|
+
)
|
|
173
|
+
await self.http_client.pr.create_comment(
|
|
174
|
+
project_key=self.project_key,
|
|
175
|
+
repo_slug=self.repo_slug,
|
|
176
|
+
pull_request_id=self.pull_request_id,
|
|
177
|
+
request=request,
|
|
178
|
+
)
|
|
179
|
+
logger.info(f"Created inline reply to thread {thread_id=} in PR {self.pull_request_ref}")
|
|
180
|
+
except Exception as error:
|
|
181
|
+
logger.exception(
|
|
182
|
+
f"Failed to create inline reply to thread {thread_id=} in PR {self.pull_request_ref}: {error}"
|
|
183
|
+
)
|
|
184
|
+
raise
|
|
185
|
+
|
|
186
|
+
async def create_summary_reply(self, thread_id: int | str, message: str) -> None:
|
|
187
|
+
try:
|
|
188
|
+
logger.info(f"Replying to summary thread {thread_id=} in PR {self.pull_request_ref}")
|
|
189
|
+
request = BitbucketServerCreatePRCommentRequestSchema(
|
|
190
|
+
text=message,
|
|
191
|
+
parent=BitbucketServerCommentParentSchema(id=int(thread_id)),
|
|
192
|
+
)
|
|
193
|
+
await self.http_client.pr.create_comment(
|
|
194
|
+
project_key=self.project_key,
|
|
195
|
+
repo_slug=self.repo_slug,
|
|
196
|
+
pull_request_id=self.pull_request_id,
|
|
197
|
+
request=request,
|
|
198
|
+
)
|
|
199
|
+
logger.info(f"Created summary reply to thread {thread_id=} in PR {self.pull_request_ref}")
|
|
200
|
+
except Exception as error:
|
|
201
|
+
logger.exception(
|
|
202
|
+
f"Failed to create summary reply to thread {thread_id=} in PR {self.pull_request_ref}: {error}"
|
|
203
|
+
)
|
|
204
|
+
raise
|
|
205
|
+
|
|
206
|
+
# --- Threads ---
|
|
207
|
+
async def get_inline_threads(self) -> list[ReviewThreadSchema]:
|
|
208
|
+
try:
|
|
209
|
+
comments = await self.get_inline_comments()
|
|
210
|
+
|
|
211
|
+
threads_by_id: dict[str | int, list[ReviewCommentSchema]] = defaultdict(list)
|
|
212
|
+
for comment in comments:
|
|
213
|
+
if not comment.file:
|
|
214
|
+
continue
|
|
215
|
+
threads_by_id[comment.thread_id].append(comment)
|
|
216
|
+
|
|
217
|
+
logger.info(f"Built {len(threads_by_id)} inline threads for {self.pull_request_ref}")
|
|
218
|
+
|
|
219
|
+
threads: list[ReviewThreadSchema] = []
|
|
220
|
+
for thread_id, thread in threads_by_id.items():
|
|
221
|
+
file = thread[0].file
|
|
222
|
+
line = thread[0].line
|
|
223
|
+
if not file:
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
threads.append(
|
|
227
|
+
ReviewThreadSchema(
|
|
228
|
+
id=thread_id,
|
|
229
|
+
kind=ThreadKind.INLINE,
|
|
230
|
+
file=file,
|
|
231
|
+
line=line,
|
|
232
|
+
comments=sorted(thread, key=lambda c: int(c.id)),
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
return threads
|
|
237
|
+
|
|
238
|
+
except Exception as error:
|
|
239
|
+
logger.exception(f"Failed to fetch inline threads for {self.pull_request_ref}: {error}")
|
|
240
|
+
return []
|
|
241
|
+
|
|
242
|
+
async def get_general_threads(self) -> list[ReviewThreadSchema]:
|
|
243
|
+
try:
|
|
244
|
+
comments = await self.get_general_comments()
|
|
245
|
+
|
|
246
|
+
threads_by_id: dict[str | int, list[ReviewCommentSchema]] = defaultdict(list)
|
|
247
|
+
for comment in comments:
|
|
248
|
+
threads_by_id[comment.thread_id].append(comment)
|
|
249
|
+
|
|
250
|
+
logger.info(f"Built {len(threads_by_id)} general threads for {self.pull_request_ref}")
|
|
251
|
+
|
|
252
|
+
return [
|
|
253
|
+
ReviewThreadSchema(
|
|
254
|
+
id=thread_id,
|
|
255
|
+
kind=ThreadKind.SUMMARY,
|
|
256
|
+
comments=sorted(thread, key=lambda c: int(c.id)),
|
|
257
|
+
)
|
|
258
|
+
for thread_id, thread in threads_by_id.items()
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
except Exception as error:
|
|
262
|
+
logger.exception(f"Failed to fetch general threads for {self.pull_request_ref}: {error}")
|
|
263
|
+
return []
|