feat(dashboard): add milestone view and configurable columns
fixes #16, fixes #19 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,19 @@ class RepoData:
|
||||
last_commit_date: str | None # ISO 8601, ex: "2026-03-10T14:30:00Z"
|
||||
|
||||
|
||||
@dataclass
|
||||
class MilestoneData:
|
||||
"""Donnees agregees d'une milestone avec son repo parent."""
|
||||
|
||||
repo_name: str
|
||||
title: str
|
||||
open_issues: int
|
||||
closed_issues: int
|
||||
progress_pct: int # Pourcentage de completion (0-100)
|
||||
due_on: str | None # ISO 8601 ou None
|
||||
state: str # "open" ou "closed"
|
||||
|
||||
|
||||
def _matches_any(name: str, patterns: list[str]) -> bool:
|
||||
"""Return True if name contains any of the patterns (case-insensitive)."""
|
||||
name_lower = name.lower()
|
||||
@@ -49,10 +62,7 @@ def collect_all(
|
||||
repos = client.get_repos()
|
||||
|
||||
# Filtrage post-fetch : l'API Gitea ne supporte pas le filtre par nom
|
||||
if include:
|
||||
repos = [r for r in repos if _matches_any(r["name"], include)]
|
||||
if exclude:
|
||||
repos = [r for r in repos if not _matches_any(r["name"], exclude)]
|
||||
repos = _filter_repos(repos, include, exclude)
|
||||
|
||||
result: list[RepoData] = []
|
||||
|
||||
@@ -79,3 +89,58 @@ def collect_all(
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _filter_repos(
|
||||
repos: list[dict],
|
||||
include: list[str] | None = None,
|
||||
exclude: list[str] | None = None,
|
||||
) -> list[dict]:
|
||||
"""Filtre les repos par include/exclude (logique partagee)."""
|
||||
if include:
|
||||
repos = [r for r in repos if _matches_any(r["name"], include)]
|
||||
if exclude:
|
||||
repos = [r for r in repos if not _matches_any(r["name"], exclude)]
|
||||
return repos
|
||||
|
||||
|
||||
def collect_milestones(
|
||||
client: GiteaClient,
|
||||
include: list[str] | None = None,
|
||||
exclude: list[str] | None = None,
|
||||
) -> list[MilestoneData]:
|
||||
"""Collecte les milestones de tous les repos accessibles.
|
||||
|
||||
Reutilise la logique de filtrage de collect_all (include/exclude).
|
||||
Pour chaque repo filtre, appelle client.get_milestones() avec state=all.
|
||||
|
||||
Retourne une liste plate de MilestoneData triee par repo puis milestone.
|
||||
"""
|
||||
repos = client.get_repos()
|
||||
repos = _filter_repos(repos, include, exclude)
|
||||
|
||||
result: list[MilestoneData] = []
|
||||
|
||||
for repo in repos:
|
||||
owner = repo["owner"]["login"]
|
||||
name = repo["name"]
|
||||
|
||||
milestones = client.get_milestones(owner, name, state="all")
|
||||
|
||||
for ms in milestones:
|
||||
total = ms["open_issues"] + ms["closed_issues"]
|
||||
pct = round(ms["closed_issues"] / total * 100) if total > 0 else 0
|
||||
|
||||
result.append(
|
||||
MilestoneData(
|
||||
repo_name=name,
|
||||
title=ms["title"],
|
||||
open_issues=ms["open_issues"],
|
||||
closed_issues=ms["closed_issues"],
|
||||
progress_pct=pct,
|
||||
due_on=ms.get("due_on"),
|
||||
state=ms.get("state", "open"),
|
||||
)
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user