-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #37 from Tomoko-hjf/HackathonBot
【New feature】新增黑客松小助手
- Loading branch information
Showing
10 changed files
with
787 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
__pycache__ | ||
**/logs/*.md | ||
**/logs/*.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
FROM python:3.8 | ||
|
||
RUN pip install requests schedule | ||
|
||
WORKDIR /home/code/HackathonBot | ||
|
||
COPY . /home/code/HackathonBot | ||
|
||
CMD ["python", "bot.py"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
# 黑客松小助手RFC | ||
|
||
## 📄 背景 | ||
|
||
💡💡💡 `Hackathon5`开赛在即,本次大赛会有上百个赛题等着大家攻克,但统计这些赛题的报名信息和当前进度是一项繁重的工作。 | ||
|
||
如果有一个小助手💡可以帮助我们自动统计和展示赛题进度,那自然是极好的,所以`黑客松小助手`它来了!🎉🎉🎉 | ||
|
||
## 🔥 功能 | ||
|
||
`黑客松小助手`目前的主要功能如下,欢迎补充: | ||
|
||
* 根据issue回复自动填写报名信息,完成任务认领。 | ||
* 根据提交的PR状态,自动更新issue中表单信息,完成状态变更。 | ||
* 汇总所有的赛题,形成总体任务看板。 | ||
|
||
## 🔥 运行方式 | ||
1、修改配置文件,其中每个配置的含义如下: | ||
* access_token:账号token | ||
* proxies:代理地址 | ||
* issue_url:黑客松 issue页面 url 地址, 注意结尾不要有斜杠 | ||
* repo_urls:监控的仓库列表 | ||
* task_num:总的任务数量 | ||
* start_time:黑客松开始时间,只会统计黑客松开始时间之后的PR(注意时间中的字母T和Z不能缺少) | ||
* un_handle_tasks: 忽略不处理的题号,这部分留给人工处理 | ||
* removed_tasks:已删除的赛题 | ||
* type_names: 赛道名 | ||
* task_types:每个赛题所属的赛道,每个赛道是一个数组 | ||
* hackathon: 为True时代表黑客松任务。 为False时代表框架计划,此时 repo_urls 要设为 [] | ||
|
||
2、执行如下命令,代码会每两小时更新一次issue,每次更新后的issue内容会保存在`logs`文件夹下; | ||
```shell | ||
cd HackathonBot | ||
|
||
python bot.py | ||
``` | ||
|
||
### 注意事项 | ||
* issue 中任务表格的题号前后必须只能有一个空格,如 `| 1 |`。 | ||
* issue 中必须有`看板信息`这几个字,并且表格后面接上`#####`这个特殊标志。 | ||
|
||
示例 | ||
```markdown | ||
| 序号 | 难度 | 任务 ISSUE | 队伍名称/状态/PR | 完成队伍 | | ||
| :--: | :--: |:--: | :--: |:--: | | ||
| 1 | ⭐ | [新增星星任务](https://github.com/Tomoko-hjf/paddleviz/issues/1) | | | | ||
| 2 | ⭐⭐ | [保护星星](https://github.com/Tomoko-hjf/paddleviz/issues/1) | | | | ||
| 3 | ⭐⭐⭐ | [收集星星](https://github.com/Tomoko-hjf/paddleviz/issues/1) | | | | ||
| 4 | ⭐⭐⭐⭐ | [飞桨之星](https://github.com/Tomoko-hjf/paddleviz/issues/1) | | | | ||
|
||
## 看板信息 | ||
|
||
##### | ||
``` | ||
|
||
## 🔥 实现方案 | ||
|
||
### 🚀 状态 | ||
|
||
在比赛期间,我们为参加比赛的大佬们设置了`五种状态`(如下),整体状态的变更顺序为:`报名状态` -> `提交RFC` -> `完成设计文档` -> `提交PR` -> `完成任务`。 | ||
|
||
| 状态名称 | 状态标志 | 状态说明 | | ||
| :--------------: | :----------------------------------------------------------: | :-------------------------: | | ||
| 报名状态 | <img src="https://img.shields.io/badge/状态-报名-2ECC71" /> | 表明通过issue评论区进行报名 | | ||
| 提交RFC状态 | <img src="https://img.shields.io/badge/状态-提交RFC-F1C40F" /> | 表明已经提交设计方案RFC | | ||
| 完成设计文档状态 | <img src="https://img.shields.io/badge/状态-完成设计文档-3498DB" /> | 表明已经完成设计文档 | | ||
| 提交PR状态 | <img src="https://img.shields.io/badge/状态-提交PR-F39C12" /> | 表明已经提交PR | | ||
| 完成任务状态 | <img src="https://img.shields.io/badge/状态-完成任务-9B59B6" /> | 表明已经完成任务 | | ||
|
||
### 🚀 榜单设计 | ||
|
||
为了更好地展示赛题进度,我们设计了榜单。整体的榜单设计如下,比赛进行期间,小助手会自动更新`Github ID/状态/PR`栏和`完成队伍`栏信息。 | ||
|
||
![image-20230729121046527](./images/1.png) | ||
|
||
`Github ID/状态/PR`栏内容的格式为`Github ID + 状态 + Pr`,其中`PR`可以有多个,`赛题参加人数`也可以有多个,以回车`<br>`分隔。 | ||
|
||
### 🚀 报名信息监控 | ||
|
||
考虑到报名赛题不会产生任何`PR`,所以通过`评论`来实现此功能,其他状态均可以通过监控`PR`实现。 | ||
|
||
#### 实现逻辑 | ||
|
||
通过监控`issue`下的评论完成报名信息监控,实现逻辑如下: | ||
|
||
* 获取`issue`下评论。 | ||
* 从评论中抽取报名信息,比如`Github ID`、`报名赛题编号`等信息。 | ||
* 更新`issue`中个人报名信息,将个人状态更新为`报名状态`。 | ||
|
||
#### 报名格式 | ||
|
||
为了自动填写报名信息,需要在`issue`下回复报名信息,如果报名格式不正确,则会在comment区提示报名不正确,格式如下: | ||
|
||
``` | ||
【报名】: 2、3 | ||
``` | ||
|
||
> 其中`【报名】: ` 后直接是`报名的赛题序号,多个赛题之间需要用`中文顿号、分隔,多个连续赛题可以用横线表示`2-5`。 | ||
### 🚀 其他状态变更监控 | ||
|
||
除`报名状态`外,剩下`四种状态`的变更可以通过监控`PR`的状态来完成, 具体的实现逻辑如下: | ||
|
||
* 获取指定仓库下黑客松`开始之后`标题中包含`Hackathon No.`字样的所有`PR`,具体是`bot.py`文件的`repo_urls`变量下的所有仓库。 | ||
* 如果`PR`是`PaddlePaddle/community`仓库下的,说明该PR与设计文档有关。继续判断是否`merge`,如果未`merge`,说明状态为`提交RFC`;如果已经`merge`,说明状态为`完成设计文档`。 | ||
* 如果`PR`不是`PaddlePaddle/community`仓库下的,说明该`PR`与提交代码有关。继续判断是否`merge`,如果未`merge`,说明状态为`提交PR`;如果已经`merge`,说明状态为`完成任务`。 | ||
* 每次更新后的issue内容将会保存在`logs`文件夹下,文件名是更新日期。 | ||
|
||
> 对于人工处理的赛题,可以将赛题题号加入`utils.py`文件的`un_handle_tasks`变量中,小助手不会处理这些任务。 | ||
#### PR格式 | ||
|
||
为了完成状态变更,只需要在`PR`的标题中以`【Hackathon No.xxx】`开头即可,程序会自动提取赛题编号并更新榜单。 | ||
|
||
如果PR格式不正确,也会在comment区域进行提示。 | ||
|
||
### 🚀 看板功能 | ||
看板功能会按照赛道类型统计每个赛道赛题的认领数、完成率等信息。 | ||
![img](./images/board.png) | ||
|
||
感谢 [@AndSonder](https://github.com/AndSonder) 提供看板样式的代码🍻。 | ||
|
||
## 🔥 代码结构 | ||
|
||
整体的代码文件分为三个: | ||
|
||
* `utils.py`:负责拉取评论和PR、根据评论更新状态、根据PR更新状态。 | ||
* `bot.py`:小助手整体运行逻辑。 | ||
* `config.py`:常用的配置项 | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import time | ||
import json | ||
import schedule | ||
|
||
import utils | ||
from config import config, logger | ||
|
||
|
||
def update_issue_automatically(): | ||
|
||
try: | ||
issue_url = config['issue_url'] | ||
|
||
# 1. 获取issue表格 | ||
response = utils.request_get_issue(issue_url) | ||
|
||
# 从issue中提取题目列表 | ||
task_list = utils.process_issue(response['body']) | ||
|
||
# 2. 根据评论更新表格 | ||
comment_url = issue_url + '/comments' | ||
comments = utils.request_get_multi(comment_url) | ||
for comment in comments: | ||
utils.update_status_by_comment(task_list, comment) | ||
|
||
|
||
# 3. 根据PR更新表格 | ||
# - 根据提出的PR更新状态为已提交 | ||
# - 根据close的PR更新状态为已完成 | ||
repo_urls = config['repo_urls'] | ||
|
||
for repo_url in repo_urls: | ||
params = { | ||
"state": "open" | ||
} | ||
pulls = utils.request_get_multi(repo_url, params) | ||
for pull in pulls: | ||
utils.update_status_by_pull(task_list, pull) | ||
|
||
params = { | ||
"state": "closed" | ||
} | ||
pulls = utils.request_get_multi(repo_url, params) | ||
for pull in pulls: | ||
if pull['merged_at']: | ||
utils.update_status_by_pull(task_list, pull) | ||
|
||
|
||
# 4. 更新榜单内容 | ||
updated_issue = response['body'] | ||
for task in task_list: | ||
if task == None: | ||
continue | ||
num = int(task['num'].strip(' ')) | ||
start = updated_issue.find('| {} |'.format(num)) | ||
end = start + 1 | ||
while end < len(updated_issue) and updated_issue[end] != '\r' and updated_issue[end] != '\n': | ||
end += 1 | ||
|
||
# TODO:这里后期需要定制化表头 | ||
row = '| {} | {} | {} | {} | {} |'.format(num, task['difficulty'], task['issue'], task['status'], task['team']) | ||
updated_issue = f'{updated_issue[:start]}{row}{updated_issue[end:]}' | ||
|
||
|
||
if config["hackathon"]: | ||
# 5. 更新看板信息,框架计划不需要更新看板信息 | ||
board_info = utils.update_board(task_list) | ||
start = updated_issue.find('看板信息') | ||
while updated_issue[start] != '\n': | ||
start += 1 | ||
end = updated_issue.find('#####', start) | ||
updated_issue = updated_issue[: start + 1] + board_info + updated_issue[end:] | ||
|
||
# 6. 处理换行符,写入日志存档 | ||
updated_issue = updated_issue.replace('\r', '') | ||
file_name = time.strftime('%Y-%m-%dT%H-%M-%S', time.localtime()) | ||
with open('./logs/{}.md'.format(file_name), mode='w', encoding='utf-8') as f: | ||
f.write(updated_issue) | ||
|
||
# 7. 更新 issue | ||
data = {} | ||
data['body'] = updated_issue | ||
data['title'] = response['title'] | ||
data['assignee'] = response['assignee'] | ||
data['state'] = response['state'] | ||
data['state_reason'] = response['state_reason'] | ||
data['milestone'] = response['milestone'] | ||
data['labels'] = response['labels'] | ||
|
||
res = utils.request_update_issue(issue_url, json.dumps(data)) | ||
# logger.info('更新issue内容返回结果: ' + str(res)) | ||
|
||
except Exception as e: | ||
logger.exception(e) | ||
|
||
|
||
|
||
|
||
if __name__ == '__main__': | ||
|
||
# 运行一次查看效果 | ||
update_issue_automatically() | ||
|
||
# 每两小时运行一次 | ||
schedule.every(2).hours.do(update_issue_automatically) | ||
|
||
while True: | ||
try: | ||
schedule.run_pending() | ||
time.sleep(100) | ||
except Exception as e: | ||
logger.error(e) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import logging | ||
|
||
# 监控的仓库列表 | ||
repo_urls = ['https://api.github.com/repos/PaddlePaddle/Paddle/pulls'] | ||
|
||
config = { | ||
# 更新issue的token | ||
'issue_token': '', | ||
|
||
# 更新评论的token | ||
'comment_token': '', | ||
|
||
# 代理地址 | ||
'proxies': { | ||
'http': 'http://127.0.0.1:7890', | ||
'https': 'http://127.0.0.1:7890' | ||
}, | ||
|
||
# 黑客松开始时间,只会统计黑客松开始时间之后的PR(注意时间中的字母T和Z不能缺少) | ||
'start_time' : '2023-09-13T15:28:48Z', | ||
|
||
# 黑客松 issue页面 url 地址, 注意结尾不要有斜杠 | ||
'issue_url': 'https://api.github.com/repos/PaddlePaddle/Paddle/issues/57262', | ||
|
||
# 监控的仓库列表 | ||
'repo_urls': repo_urls, | ||
|
||
# 总的任务数量 | ||
'task_num' : 11, | ||
|
||
# 忽略不处理的题号,这部分留给人工处理 | ||
'un_handle_tasks' : [], | ||
|
||
# 已删除的赛题 | ||
'removed_tasks' : [], | ||
|
||
# 赛道名 | ||
'type_names' : ["热身赛"], | ||
|
||
# 每个赛题所属的赛道,每个赛道是一个数组 | ||
'task_types' : [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]], | ||
|
||
# 为True时代表黑客松任务。 为False时代表框架计划,此时 repo_urls 要设为 [] | ||
'hackathon': True | ||
|
||
} | ||
|
||
def get_logger(): | ||
logger = logging.getLogger('logger') | ||
logger.setLevel(level=logging.DEBUG) | ||
|
||
formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s') | ||
|
||
file_handler = logging.FileHandler('./logs/output.txt', encoding='utf-8') | ||
file_handler.setLevel(level=logging.INFO) | ||
file_handler.setFormatter(formatter) | ||
|
||
stream_handler = logging.StreamHandler() | ||
stream_handler.setLevel(logging.INFO) | ||
stream_handler.setFormatter(formatter) | ||
|
||
logger.addHandler(file_handler) | ||
logger.addHandler(stream_handler) | ||
|
||
return logger | ||
|
||
logger = get_logger() |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
| 序号 | 难度 | 任务 ISSUE | 队伍名称/状态/PR | 完成队伍 | | ||
| :--: | :--: |:--: | :--: |:--: | | ||
| 1 | ⭐ | [新增星星任务](https://github.com/Tomoko-hjf/paddleviz/issues/1) | | | | ||
| 2 | ⭐⭐ | [保护星星](https://github.com/Tomoko-hjf/paddleviz/issues/1) | | | | ||
| 3 | ⭐⭐⭐ | [收集星星](https://github.com/Tomoko-hjf/paddleviz/issues/1) | | | | ||
| 4 | ⭐⭐⭐⭐ | [飞桨之星](https://github.com/Tomoko-hjf/paddleviz/issues/1) | | | | ||
|
||
## 看板信息 | ||
|
||
##### |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
设计文档详见README,这里仅列举验收需要的测试样例 | ||
|
||
| 测试case | 测试名称 | 测试步骤 | 预期效果 | 测试结果 | 备注 | | ||
| -------- | ------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | -------- | ---- | | ||
| 1 | 首次评论区信息抽取 | 1. 构造2个任务列表,每个任务列表包含5-10个任务</br>2. 构造至少2个github账号,按照规定格式报名若干任务,任务均已发布</br>规定格式:【报名】: 2、3`【报名】: ` 后直接是报名的任务序号,多个任务之间需要用中文顿号、分隔,多个连续任务可以用横线表示`2-5`。</br>3. 首次运行脚本 | 报名信息正确更新到表单中 | | | | ||
| 2 | 二次评论区信息抽取 | 1. 基于case1,在评论区按照规定格式二次构造若干报名信息,任务均已发布</br>2. 在case1的基础上运行脚本 | 新增报名信息正确更新到表单中 | | | | ||
| 3 | 新增/删除/修改任务列表信息 | 1. 在任务列表中随机删除1个任务(md中划掉)</br>2. 在任务列表中随机增加1个任务(新增一行,任务编号不重复)</br>3. 在任务列表中随机修改1个任务的难度和issue描述文案</br>4. 在task_list中补充增加的任务编号</br>5. 任意github账号报名删除任务的编号</br>6. 任意github账号报名新增任务的编号</br>7. 在case2的基础上运行脚本 | 1. 新增/删除/修改任务列表信息保留</br>2. 报名信息正确更新到表单中 | | | | ||
| 4 | 评论区负样本信息抽取与报错提醒 | 1. 构造规定格式以外的评论信息,包括:</br>a. 不按照规定格式的报名信息,如</br>①【取消报名】:2、3</br>②【链接】:1-5</br>③报名:https://github.com/Tomoko-hjf/paddleviz/issues/1</br>④【报名】:1111111</br>b. 非报名信息,如问答或闲聊信息</br>2. 在case3的基础上运行脚本 | 1. 准确识别负样本信息,记录log</br>2. @github id,提醒对方未成功报名 | | | | ||
| 5 | PR状态抽取(含负样本) | 1. 已报名的github账号在community下提交一个pr,pr标题按照规定格式</br>规定格式:在`PR`的标题中以`【Hackathon No.xxx】`开头</br>2. 未报名的github账号在community下提交一个pr,pr标题按照规定格式</br>3. 已报名的github账号在任意监控范围内的repo下提交一个pr,pr标题按照规定格式,但编号不在task_list中</br>4. 未报名的github账号在任意监控范围内的repo下提交一个pr,pr标题按照规定格式</br>5. 在case4的基础上运行脚本 | 1. 准确更新 1 2 4的PR信息</br>2. 3记录log,@github id,提醒对方编号有误 | | | | ||
| 6 | 更改PR标题后的状态抽取 | 1. 更改case5中不符合规范的PR标题,使其正确</br>2. 在case5的基础上运行脚本 | 修改后PR被正确更新到表单中 | | | | ||
| 7 | PR状态变更后抽取 | 1. community下合入一个pr</br>2. 监控范围内的repo合入一个pr</br>3. 在case6的基础上运行脚本 | rfc和pr状态被正确更新为完成设计文档和完成任务</br>备注:rfc和pr合入由黑客松组委会管理,可以**人工避免**要求rfc的任务绕过rfc提交直接合入pr的情况,因此不测试这个case | | | | ||
| 8 | 多个PR指向一个任务,且具有不同状态(open/close/merge) | 1. 构造5个pr,标题中的任务序号指向同一个任务</br>2. 合入其中3个pr,其中2个pr由同一个github id提交</br>3. 关闭其中1个pr</br>4. 在case7的基础上运行脚本 | 5个pr的状态可以被准确更新到表单中</br>备注:用于应对一个任务对应多个pr的情况 | | | | ||
| 9 | 看板功能 | | 验证上述测试样例中,看板统计是否正常 | | | | ||
| 10 | | | | | | |
Oops, something went wrong.