216 lines
8.6 KiB
Python
216 lines
8.6 KiB
Python
import requests
|
||
import hashlib
|
||
from bs4 import BeautifulSoup
|
||
from datetime import datetime
|
||
|
||
# 最简版本 - 获取所有产品的Bug
|
||
def get_all_bugs():
|
||
"""获取所有产品的Bug"""
|
||
base_url = "http://39.170.26.156:8888"
|
||
username = "qiaoxinjiu"
|
||
password = "Qiao123456"
|
||
|
||
print("获取禅道Bug列表")
|
||
print("=" * 50)
|
||
|
||
# 登录
|
||
session = requests.Session()
|
||
password_md5 = hashlib.md5(password.encode()).hexdigest()
|
||
|
||
login_resp = session.post(f"{base_url}/api.php/v1/tokens",
|
||
json={"account": username, "password": password_md5},
|
||
headers={'Content-Type': 'application/json'})
|
||
|
||
if login_resp.status_code not in [200, 201]:
|
||
print("登录失败")
|
||
return
|
||
|
||
token = login_resp.json().get('token')
|
||
session.headers.update({'Token': token})
|
||
|
||
print("✅ 登录成功")
|
||
|
||
# 获取所有产品
|
||
products_resp = session.get(f"{base_url}/api.php/v1/products")
|
||
if products_resp.status_code != 200:
|
||
print("获取产品失败")
|
||
return
|
||
|
||
products = products_resp.json().get('products', [])
|
||
print(f"📦 共 {len(products)} 个产品")
|
||
|
||
all_bugs = []
|
||
|
||
# 遍历每个产品获取Bug
|
||
for product in products:
|
||
product_id = product.get('id')
|
||
product_name = product.get('name')
|
||
if product_id != 2:
|
||
continue
|
||
print(f"\n获取产品 '{product_name}' 的Bug...")
|
||
# 你的禅道实例没有开 REST bug 列表接口,只能爬取 HTML 页面
|
||
bugs_url = f"{base_url}/bug-browse-{product_id}.html"
|
||
bugs_resp = session.get(bugs_url, params={'product': product_id, 'limit': 50})
|
||
|
||
if bugs_resp.status_code == 200:
|
||
try:
|
||
html = bugs_resp.text
|
||
soup = BeautifulSoup(html, "html.parser")
|
||
|
||
# 找 bug 列表的主 table(尽量精确一点)
|
||
table = (
|
||
soup.find("table", id="bugList")
|
||
or soup.find("table", class_="table")
|
||
or soup.find("table")
|
||
)
|
||
if not table:
|
||
print(" ❌ 未找到 bug 列表 table")
|
||
# print(html[:500]) # 调试时可以打开
|
||
return
|
||
|
||
# 解析表头,建立「列名 -> 索引」映射
|
||
header_tr = table.find("tr")
|
||
if not header_tr:
|
||
print(" ❌ 未找到表头行")
|
||
return
|
||
|
||
header_map = {}
|
||
for idx, th in enumerate(header_tr.find_all(["th", "td"])):
|
||
text = th.get_text(strip=True)
|
||
if not text:
|
||
continue
|
||
if "ID" == text or text == "编号":
|
||
header_map["id"] = idx
|
||
elif "标题" in text:
|
||
header_map["title"] = idx
|
||
elif "状态" in text:
|
||
header_map["status"] = idx
|
||
elif "严重" in text:
|
||
header_map["severity"] = idx
|
||
elif "优先" in text or "优先级" in text:
|
||
header_map["pri"] = idx
|
||
elif "指派" in text:
|
||
header_map["assignedTo"] = idx
|
||
elif "创建" in text or "打开" in text:
|
||
# 如:创建日期 / 打开时间
|
||
header_map["openedDate"] = idx
|
||
|
||
bugs = []
|
||
|
||
# 遍历表体行
|
||
for tr in table.find_all("tr")[1:]:
|
||
tds = tr.find_all("td")
|
||
if not tds:
|
||
continue
|
||
|
||
# id
|
||
bug_id = tr.get("data-id")
|
||
if not bug_id and "id" in header_map and header_map["id"] < len(tds):
|
||
bug_id = tds[header_map["id"]].get_text(strip=True)
|
||
if not bug_id:
|
||
continue
|
||
|
||
# 标题
|
||
title = ""
|
||
if "title" in header_map and header_map["title"] < len(tds):
|
||
cell = tds[header_map["title"]]
|
||
link = cell.find("a")
|
||
title = (link or cell).get_text(strip=True)
|
||
|
||
# 状态
|
||
status = ""
|
||
if "status" in header_map and header_map["status"] < len(tds):
|
||
status = tds[header_map["status"]].get_text(strip=True)
|
||
|
||
# 严重程度(有的版本用颜色块 + title 提示)
|
||
severity = ""
|
||
if "severity" in header_map and header_map["severity"] < len(tds):
|
||
sev_cell = tds[header_map["severity"]]
|
||
severity = sev_cell.get_text(strip=True)
|
||
if not severity:
|
||
sev_span = sev_cell.find("span")
|
||
if sev_span and sev_span.get("title"):
|
||
severity = sev_span.get("title").strip()
|
||
|
||
# 优先级
|
||
pri = ""
|
||
if "pri" in header_map and header_map["pri"] < len(tds):
|
||
pri = tds[header_map["pri"]].get_text(strip=True)
|
||
|
||
# 指派给
|
||
assigned_to = ""
|
||
if "assignedTo" in header_map and header_map["assignedTo"] < len(tds):
|
||
assigned_to = tds[header_map["assignedTo"]].get_text(strip=True)
|
||
|
||
# 创建/打开日期
|
||
opened_date = ""
|
||
if "openedDate" in header_map and header_map["openedDate"] < len(tds):
|
||
opened_date = tds[header_map["openedDate"]].get_text(strip=True)
|
||
|
||
bug = {
|
||
"id": int(bug_id) if str(bug_id).isdigit() else bug_id,
|
||
"title": title,
|
||
"statusName": status,
|
||
"severity": severity,
|
||
"pri": pri,
|
||
"assignedToName": assigned_to,
|
||
"openedDate": opened_date,
|
||
"product_name": product_name,
|
||
}
|
||
bugs.append(bug)
|
||
|
||
print(f" ✅ 解析出 {len(bugs)} 个Bug")
|
||
all_bugs.extend(bugs)
|
||
|
||
except Exception as e:
|
||
print(f" ❌ 解析失败: {e}")
|
||
# 调试用:打印部分 HTML 看实际结构
|
||
# print(html[:800])
|
||
else:
|
||
print(f" ❌ 获取失败: {bugs_resp.status_code} - {bugs_resp.text[:100]}")
|
||
|
||
# 显示所有Bug
|
||
if all_bugs:
|
||
# 计算统计数据
|
||
today_str = datetime.today().strftime("%Y-%m-%d")
|
||
today_md_str = datetime.today().strftime("%m-%d")
|
||
total_bugs = len(all_bugs)
|
||
today_bugs = [
|
||
b for b in all_bugs
|
||
if (
|
||
today_str in str(b.get("openedDate", "")).strip()
|
||
or today_md_str in str(b.get("openedDate", "")).strip()
|
||
)
|
||
]
|
||
# 认为“未关闭”的状态:不在已关闭/已解决/已取消集合中
|
||
closed_keywords = {"已关闭", "已解决", "已完成", "已取消", "closed", "resolved", "done", "cancelled"}
|
||
open_bugs = [
|
||
b for b in all_bugs
|
||
if not any(kw in str(b.get("statusName", "")).lower() for kw in ["closed", "resolved", "done", "cancel"])
|
||
and not any(kw in str(b.get("statusName", "")) for kw in ["已关闭", "已解决", "已完成", "已取消"])
|
||
]
|
||
|
||
print(f"\n{'=' * 80}")
|
||
print(f"📊 Bug统计")
|
||
print(f"{'-' * 80}")
|
||
print(f" 今日新增 Bug 数: {len(today_bugs)}")
|
||
print(f" 未关闭 Bug 数: {len(open_bugs)}")
|
||
print(f" 总 Bug 数: {total_bugs}")
|
||
print(f"{'=' * 80}")
|
||
|
||
# 按ID排序
|
||
all_bugs.sort(key=lambda x: x.get('id', 0), reverse=True)
|
||
|
||
for i, bug in enumerate(all_bugs[:30], 1): # 只显示前30个
|
||
print(
|
||
f"{i}. [#{bug.get('id')}] {bug.get('title', '无标题')[:40]}{'...' if len(bug.get('title', '')) > 40 else ''}")
|
||
print(f" 产品: {bug.get('product_name')} | 状态: {bug.get('statusName', '未知')} | "
|
||
f"严重: {bug.get('severity', '未知')} | 指派: {bug.get('assignedToName', '未指派')} | "
|
||
f"创建时间: {bug.get('openedDate', '')}")
|
||
print(f"http://39.170.26.156:8888/bug-view-{bug.get('id')}.html")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
|
||
# 获取所有Bug版本
|
||
get_all_bugs() |