fix(api): fallback weekly git stats to gitea
Some checks failed
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / tests (push) Successful in 2m28s
CD Pipeline / build-and-deploy (push) Successful in 5m1s
CD Pipeline / post-deploy-checks (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-27 16:13:42 +08:00
parent 67f1da991d
commit 562a61990e
2 changed files with 108 additions and 3 deletions

View File

@@ -20,13 +20,17 @@ Weekly Report Service - Phase 21.3 定期報告
"""
import asyncio
import json
import subprocess
from datetime import datetime, timedelta
from urllib.parse import quote, urlencode
from urllib.request import Request, urlopen
from typing import Protocol, runtime_checkable
import structlog
from zoneinfo import ZoneInfo
from src.core.config import settings
from src.services.stats_service import StatsService, get_stats_service
from src.services.k3s_monitor_service import K3sMonitorService, get_k3s_monitor_service
from src.services.telegram_gateway import WeeklyReportMessage, get_telegram_gateway
@@ -112,7 +116,7 @@ class WeeklyReportService:
returncode=result.returncode,
stderr=result.stderr[-300:],
)
return 0, 0, False
return self._get_gitea_commit_stats(since)
commits = len(result.stdout.strip().split("\n")) if result.stdout.strip() else 0
# 取得部署次數 (計算含 "deploy" 或 "CD" 的 commits)
@@ -129,12 +133,56 @@ class WeeklyReportService:
returncode=result_deploy.returncode,
stderr=result_deploy.stderr[-300:],
)
return commits, 0, False
return self._get_gitea_commit_stats(since)
deploys = len(result_deploy.stdout.strip().split("\n")) if result_deploy.stdout.strip() else 0
return commits, deploys, True
except Exception as e:
logger.warning("git_stats_failed", error=str(e))
return self._get_gitea_commit_stats(since)
def _get_gitea_commit_stats(self, since: datetime) -> tuple[int, int, bool]:
"""從 Gitea commits API 讀取週報開發活動統計。"""
api_url = settings.GITEA_API_URL.rstrip("/")
owner = quote(str(settings.GITEA_REPO_OWNER), safe="")
repo = quote(str(settings.GITEA_REPO_NAME), safe="")
since_utc = since.astimezone(ZoneInfo("UTC")).isoformat().replace("+00:00", "Z")
headers = {"Accept": "application/json", "User-Agent": "awoooi-weekly-report/1.0"}
if settings.GITEA_API_TOKEN:
headers["Authorization"] = f"token {settings.GITEA_API_TOKEN}"
commits = 0
deploys = 0
page = 1
limit = 50
try:
while page <= 20:
query = urlencode({
"sha": "main",
"since": since_utc,
"page": page,
"limit": limit,
})
url = f"{api_url}/api/v1/repos/{owner}/{repo}/commits?{query}"
request = Request(url, headers=headers)
with urlopen(request, timeout=8) as response:
payload = json.loads(response.read().decode("utf-8"))
if not isinstance(payload, list):
logger.warning("gitea_git_stats_unexpected_payload", payload_type=type(payload).__name__)
return 0, 0, False
commits += len(payload)
for item in payload:
commit = item.get("commit") if isinstance(item, dict) else {}
message = str((commit or {}).get("message") or "")
subject = message.splitlines()[0].lower()
if "deploy" in subject or subject.startswith("chore(cd):"):
deploys += 1
if len(payload) < limit:
break
page += 1
return commits, deploys, True
except Exception as exc:
logger.warning("gitea_git_stats_failed", error=str(exc))
return 0, 0, False
async def generate_report(self) -> WeeklyReportMessage:

View File

@@ -58,12 +58,33 @@ class TestWeeklyReportGitStats:
)
svc = WeeklyReportService(stats_service=object(), k3s_monitor=object())
monkeypatch.setattr(svc, "_get_gitea_commit_stats", lambda since: (0, 0, False))
commits, deploys, source_ok = svc._get_git_stats(datetime.now(_TZ_TAIPEI))
assert commits == 0
assert deploys == 0
assert source_ok is False
def test_git_log_failure_uses_gitea_fallback(self, monkeypatch):
class Result:
returncode = 128
stdout = ""
stderr = "fatal: not a git repository"
monkeypatch.setattr(
weekly_report_module.subprocess,
"run",
lambda *args, **kwargs: Result(),
)
svc = WeeklyReportService(stats_service=object(), k3s_monitor=object())
monkeypatch.setattr(svc, "_get_gitea_commit_stats", lambda since: (12, 4, True))
commits, deploys, source_ok = svc._get_git_stats(datetime.now(_TZ_TAIPEI))
assert commits == 12
assert deploys == 4
assert source_ok is True
def test_git_deploy_log_failure_marks_source_failed_after_commits(self, monkeypatch):
class CommitResult:
returncode = 0
@@ -84,12 +105,48 @@ class TestWeeklyReportGitStats:
monkeypatch.setattr(weekly_report_module.subprocess, "run", fake_run)
svc = WeeklyReportService(stats_service=object(), k3s_monitor=object())
monkeypatch.setattr(svc, "_get_gitea_commit_stats", lambda since: (0, 0, False))
commits, deploys, source_ok = svc._get_git_stats(datetime.now(_TZ_TAIPEI))
assert commits == 2
assert commits == 0
assert deploys == 0
assert source_ok is False
def test_gitea_commit_stats_counts_deploy_markers(self, monkeypatch):
payload = [
{"commit": {"message": "chore(cd): deploy abc123 [skip ci]\n"}},
{"commit": {"message": "fix(api): repair source health\n"}},
{"commit": {"message": "docs(logbook): record deploy verification [skip ci]\n"}},
]
class Response:
def __enter__(self):
return self
def __exit__(self, *_args):
return None
def read(self):
return weekly_report_module.json.dumps(payload).encode("utf-8")
def fake_urlopen(request, timeout):
assert "/api/v1/repos/wooo/awoooi/commits?" in request.full_url
assert timeout == 8
return Response()
monkeypatch.setattr(weekly_report_module, "urlopen", fake_urlopen)
monkeypatch.setattr(weekly_report_module.settings, "GITEA_API_URL", "https://gitea.example.test")
monkeypatch.setattr(weekly_report_module.settings, "GITEA_REPO_OWNER", "wooo")
monkeypatch.setattr(weekly_report_module.settings, "GITEA_REPO_NAME", "awoooi")
monkeypatch.setattr(weekly_report_module.settings, "GITEA_API_TOKEN", "")
svc = WeeklyReportService(stats_service=object(), k3s_monitor=object())
commits, deploys, source_ok = svc._get_gitea_commit_stats(datetime.now(_TZ_TAIPEI))
assert commits == 3
assert deploys == 2
assert source_ok is True
# =============================================================================
# DailyKpi 計算屬性