Skip to content

Commit d7cbec6

Browse files
authored
Merge pull request #111 from quanbisen/lebo_custom
增加Merge Request目标分支为受保护分支时才Review的开关;添加自定webhook通知支持 EXTRA_WEBHOOK_ENABLED、EXTRA_WEBHOOK_URL
2 parents aa17b10 + a47dea0 commit d7cbec6

File tree

10 files changed

+127
-18
lines changed

10 files changed

+127
-18
lines changed

biz/entity/review_entity.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
class MergeRequestReviewEntity:
22
def __init__(self, project_name: str, author: str, source_branch: str, target_branch: str, updated_at: int,
3-
commits: list, score: float, url: str, review_result: str, url_slug: str, additions: int,
4-
deletions: int):
3+
commits: list, score: float, url: str, review_result: str, url_slug: str, webhook_data: dict,
4+
additions: int, deletions: int):
55
self.project_name = project_name
66
self.author = author
77
self.source_branch = source_branch
@@ -12,6 +12,7 @@ def __init__(self, project_name: str, author: str, source_branch: str, target_br
1212
self.url = url
1313
self.review_result = review_result
1414
self.url_slug = url_slug
15+
self.webhook_data = webhook_data
1516
self.additions = additions
1617
self.deletions = deletions
1718

@@ -23,7 +24,7 @@ def commit_messages(self):
2324

2425
class PushReviewEntity:
2526
def __init__(self, project_name: str, author: str, branch: str, updated_at: int, commits: list, score: float,
26-
review_result: str, url_slug: str, additions: int, deletions: int):
27+
review_result: str, url_slug: str, webhook_data: dict, additions: int, deletions: int):
2728
self.project_name = project_name
2829
self.author = author
2930
self.branch = branch
@@ -32,6 +33,7 @@ def __init__(self, project_name: str, author: str, branch: str, updated_at: int,
3233
self.score = score
3334
self.review_result = review_result
3435
self.url_slug = url_slug
36+
self.webhook_data = webhook_data
3537
self.additions = additions
3638
self.deletions = deletions
3739

biz/event/event_manager.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ def on_merge_request_reviewed(mr_review_entity: MergeRequestReviewEntity):
3232
{mr_review_entity.review_result}
3333
"""
3434
notifier.send_notification(content=im_msg, msg_type='markdown', title='Merge Request Review',
35-
project_name=mr_review_entity.project_name,
36-
url_slug=mr_review_entity.url_slug)
35+
project_name=mr_review_entity.project_name, url_slug=mr_review_entity.url_slug,
36+
webhook_data=mr_review_entity.webhook_data)
3737

3838
# 记录到数据库
3939
ReviewService().insert_mr_review_log(mr_review_entity)
@@ -58,9 +58,9 @@ def on_push_reviewed(entity: PushReviewEntity):
5858

5959
if entity.review_result:
6060
im_msg += f"#### AI Review 结果: \n {entity.review_result}\n\n"
61-
notifier.send_notification(content=im_msg, msg_type='markdown',
62-
title=f"{entity.project_name} Push Event", project_name=entity.project_name,
63-
url_slug=entity.url_slug)
61+
notifier.send_notification(content=im_msg, msg_type='markdown',title=f"{entity.project_name} Push Event",
62+
project_name=entity.project_name, url_slug=entity.url_slug,
63+
webhook_data=entity.webhook_data)
6464

6565
# 记录到数据库
6666
ReviewService().insert_push_review_log(entity)

biz/github/webhook_handler.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import json
21
import os
32
import re
43
import time
54

65
import requests
7-
6+
import fnmatch
87
from biz.utils.log import logger
98

109

@@ -177,6 +176,22 @@ def add_pull_request_notes(self, review_result):
177176
logger.error(f"Failed to add comment: {response.status_code}")
178177
logger.error(response.text)
179178

179+
def target_branch_protected(self) -> bool:
180+
url = f"https://api.github.com/repos/{self.repo_full_name}/branches?protected=true"
181+
headers = {
182+
'Authorization': f'token {self.github_token}',
183+
'Accept': 'application/vnd.github.v3+json'
184+
}
185+
186+
response = requests.get(url, headers=headers)
187+
if response.status_code == 200:
188+
data = response.json()
189+
target_branch = self.webhook_data['pull_request']['base']['ref']
190+
return any(fnmatch.fnmatch(target_branch, item['name']) for item in data)
191+
else:
192+
logger.warn(f"Failed to get protected branches: {response.status_code}, {response.text}")
193+
return False
194+
180195

181196
class PushHandler:
182197
def __init__(self, webhook_data: dict, github_token: str, github_url: str):

biz/gitlab/webhook_handler.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import re
33
import time
44
from urllib.parse import urljoin
5-
5+
import fnmatch
66
import requests
77

88
from biz.utils.log import logger
@@ -15,7 +15,7 @@ def filter_changes(changes: list):
1515
# 从环境变量中获取支持的文件扩展名
1616
supported_extensions = os.getenv('SUPPORTED_EXTENSIONS', '.java,.py,.php').split(',')
1717

18-
filter_deleted_files_changes = [change for change in changes if change.get("deleted_file") == False]
18+
filter_deleted_files_changes = [change for change in changes if not change.get("deleted_file")]
1919

2020
# 过滤 `new_path` 以支持的扩展名结尾的元素, 仅保留diff和new_path字段
2121
filtered_changes = [
@@ -49,7 +49,6 @@ def slugify_url(original_url: str) -> str:
4949
return target
5050

5151

52-
5352
class MergeRequestHandler:
5453
def __init__(self, webhook_data: dict, gitlab_token: str, gitlab_url: str):
5554
self.merge_request_iid = None
@@ -148,6 +147,24 @@ def add_merge_request_notes(self, review_result):
148147
logger.error(f"Failed to add note: {response.status_code}")
149148
logger.error(response.text)
150149

150+
def target_branch_protected(self) -> bool:
151+
url = urljoin(f"{self.gitlab_url}/",
152+
f"api/v4/projects/{self.project_id}/protected_branches")
153+
headers = {
154+
'Private-Token': self.gitlab_token,
155+
'Content-Type': 'application/json'
156+
}
157+
response = requests.get(url, headers=headers, verify=False)
158+
logger.debug(f"Get protected branches response from gitlab: {response.status_code}, {response.text}")
159+
# 检查请求是否成功
160+
if response.status_code == 200:
161+
data = response.json()
162+
target_branch = self.webhook_data['object_attributes']['target_branch']
163+
return any(fnmatch.fnmatch(target_branch, item['name']) for item in data)
164+
else:
165+
logger.warn(f"Failed to get protected branches: {response.status_code}, {response.text}")
166+
return False
167+
151168

152169
class PushHandler:
153170
def __init__(self, webhook_data: dict, gitlab_token: str, gitlab_url: str):

biz/queue/worker.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def handle_push_event(webhook_data: dict, gitlab_token: str, gitlab_url: str, gi
5454
score=score,
5555
review_result=review_result,
5656
url_slug=gitlab_url_slug,
57+
webhook_data=webhook_data,
5758
additions=additions,
5859
deletions=deletions,
5960
))
@@ -73,10 +74,15 @@ def handle_merge_request_event(webhook_data: dict, gitlab_token: str, gitlab_url
7374
:param gitlab_url_slug:
7475
:return:
7576
'''
77+
merge_review_only_protected_branches = os.environ.get('MERGE_REVIEW_ONLY_PROTECTED_BRANCHES_ENABLED', '0') == '1'
7678
try:
7779
# 解析Webhook数据
7880
handler = MergeRequestHandler(webhook_data, gitlab_token, gitlab_url)
7981
logger.info('Merge Request Hook event received')
82+
# 如果开启了仅review projected branches的,判断当前目标分支是否为projected branches
83+
if merge_review_only_protected_branches and not handler.target_branch_protected():
84+
logger.info("Merge Request target branch not match protected branches, ignored.")
85+
return
8086

8187
if handler.action not in ['open', 'update']:
8288
logger.info(f"Merge Request Hook event, action={handler.action}, ignored.")
@@ -123,6 +129,7 @@ def handle_merge_request_event(webhook_data: dict, gitlab_token: str, gitlab_url
123129
url=webhook_data['object_attributes']['url'],
124130
review_result=review_result,
125131
url_slug=gitlab_url_slug,
132+
webhook_data=webhook_data,
126133
additions=additions,
127134
deletions=deletions,
128135
)
@@ -175,6 +182,7 @@ def handle_github_push_event(webhook_data: dict, github_token: str, github_url:
175182
score=score,
176183
review_result=review_result,
177184
url_slug=github_url_slug,
185+
webhook_data=webhook_data,
178186
additions=additions,
179187
deletions=deletions,
180188
))
@@ -194,10 +202,15 @@ def handle_github_pull_request_event(webhook_data: dict, github_token: str, gith
194202
:param github_url_slug:
195203
:return:
196204
'''
205+
merge_review_only_protected_branches = os.environ.get('MERGE_REVIEW_ONLY_PROTECTED_BRANCHES_ENABLED', '0') == '1'
197206
try:
198207
# 解析Webhook数据
199208
handler = GithubPullRequestHandler(webhook_data, github_token, github_url)
200209
logger.info('GitHub Pull Request event received')
210+
# 如果开启了仅review projected branches的,判断当前目标分支是否为projected branches
211+
if merge_review_only_protected_branches and not handler.target_branch_protected():
212+
logger.info("Merge Request target branch not match protected branches, ignored.")
213+
return
201214

202215
if handler.action not in ['opened', 'synchronize']:
203216
logger.info(f"Pull Request Hook event, action={handler.action}, ignored.")
@@ -244,6 +257,7 @@ def handle_github_pull_request_event(webhook_data: dict, github_token: str, gith
244257
url=webhook_data['pull_request']['html_url'],
245258
review_result=review_result,
246259
url_slug=github_url_slug,
260+
webhook_data=webhook_data,
247261
additions=additions,
248262
deletions=deletions,
249263
))

biz/utils/code_reviewer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class BaseReviewer(abc.ABC):
1616

1717
def __init__(self, prompt_key: str):
1818
self.client = Factory().getClient()
19-
self.prompts = self._load_prompts(prompt_key,os.getenv("REVIEW_STYLE", "professional"))
19+
self.prompts = self._load_prompts(prompt_key, os.getenv("REVIEW_STYLE", "professional"))
2020

2121
def _load_prompts(self, prompt_key: str, style="professional") -> Dict[str, Any]:
2222
"""加载提示词配置"""

biz/utils/im/feishu.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import json
21
import requests
32
import os
4-
import re
53
from biz.utils.log import logger
64

75

biz/utils/im/notifier.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
from biz.utils.im.dingtalk import DingTalkNotifier
22
from biz.utils.im.feishu import FeishuNotifier
3+
from biz.utils.im.webhook import ExtraWebhookNotifier
34
from biz.utils.im.wecom import WeComNotifier
45

56

6-
def send_notification(content, msg_type='text', title="通知", is_at_all=False, project_name=None, url_slug=None):
7+
def send_notification(content, msg_type='text', title="通知", is_at_all=False, project_name=None, url_slug=None,
8+
webhook_data: dict={}):
79
"""
810
发送通知消息到配置的平台(钉钉和企业微信)
911
:param content: 消息内容
1012
:param msg_type: 消息类型,支持text和markdown
1113
:param title: 消息标题(markdown类型时使用)
1214
:param is_at_all: 是否@所有人
1315
:param url_slug: 由gitlab服务器的url地址(如:http://www.gitlab.com)转换成的slug格式,如: www_gitlab_com
16+
:param webhook_data: push event、merge event的数据内容
1417
"""
1518
# 钉钉推送
1619
dingtalk_notifier = DingTalkNotifier()
1720
dingtalk_notifier.send_message(content=content, msg_type=msg_type, title=title, is_at_all=is_at_all,
18-
project_name=project_name, url_slug=url_slug)
21+
project_name=project_name, url_slug=url_slug)
1922

2023
# 企业微信推送
2124
wecom_notifier = WeComNotifier()
@@ -26,3 +29,15 @@ def send_notification(content, msg_type='text', title="通知", is_at_all=False,
2629
feishu_notifier = FeishuNotifier()
2730
feishu_notifier.send_message(content=content, msg_type=msg_type, title=title, is_at_all=is_at_all,
2831
project_name=project_name, url_slug=url_slug)
32+
33+
# 额外自定义webhook通知
34+
extra_webhook_notifier = ExtraWebhookNotifier()
35+
system_data = {
36+
"content": content,
37+
"msg_type": msg_type,
38+
"title": title,
39+
"is_at_all": is_at_all,
40+
"project_name": project_name,
41+
"url_slug": url_slug
42+
}
43+
extra_webhook_notifier.send_message(system_data=system_data, webhook_data=webhook_data)

biz/utils/im/webhook.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import os
2+
from biz.utils.log import logger
3+
import requests
4+
5+
6+
class ExtraWebhookNotifier:
7+
def __init__(self, webhook_url=None):
8+
"""
9+
初始化ExtraWebhook通知器
10+
:param webhook_url: 自定义webhook地址
11+
"""
12+
self.default_webhook_url = webhook_url or os.environ.get('EXTRA_WEBHOOK_URL', '')
13+
self.enabled = os.environ.get('EXTRA_WEBHOOK_ENABLED', '0') == '1'
14+
15+
def send_message(self, system_data: dict, webhook_data: dict):
16+
"""
17+
发送额外自定义webhook消息
18+
:param system_data: 系统消息内容
19+
:param webhook_data: github、gitlab的push event、merge event的原始数据
20+
"""
21+
if not self.enabled:
22+
logger.info("ExtraWebhook推送未启用")
23+
return
24+
25+
try:
26+
data = {
27+
"ai_codereview_data": system_data,
28+
"webhook_data": webhook_data
29+
}
30+
response = requests.post(
31+
url=self.default_webhook_url,
32+
json=data,
33+
headers={'Content-Type': 'application/json'}
34+
)
35+
36+
if response.status_code != 200:
37+
logger.error(f"ExtraWebhook消息发送失败! webhook_url:{self.default_webhook_url}, error_msg:{response.text}")
38+
return
39+
40+
except Exception as e:
41+
logger.error(f"ExtraWebhook消息发送失败! ", e)

conf/.env.dist

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ WECOM_WEBHOOK_URL=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx
5050
FEISHU_ENABLED=0
5151
FEISHU_WEBHOOK_URL=https://open.feishu.cn/open-apis/bot/v2/hook/xxx
5252

53+
#自定义webhook配置,使用场景:通过飞书发送应用消息可以实现Push评审通知到提交人,在自定义webhook里可以实现各种定制通知功能
54+
#参数EXTRA_WEBHOOK_URL接收POST请求,data={ai_codereview_data: {}, webhook_data: {}},ai_codereview_data为本系统通知的数据,webhook_data为原github、gitlab hook触发的数据
55+
EXTRA_WEBHOOK_ENABLED=0
56+
EXTRA_WEBHOOK_URL=https://xxx/xxx
57+
5358
#日志配置
5459
LOG_FILE=log/app.log
5560
LOG_MAX_BYTES=10485760
@@ -68,6 +73,8 @@ REPORT_CRONTAB_EXPRESSION=0 18 * * 1-5
6873

6974
# 开启Push Review功能(如果不需要push事件触发Code Review,设置为0)
7075
PUSH_REVIEW_ENABLED=1
76+
# 开启Merge请求过滤,过滤仅当合并目标分支是受保护分支时才Review(开启此选项请确保仓库已配置受保护分支protected branches)
77+
MERGE_REVIEW_ONLY_PROTECTED_BRANCHES_ENABLED=0
7178

7279
# Dashboard登录用户名和密码
7380
DASHBOARD_USER=admin

0 commit comments

Comments
 (0)