Plan 2 phases : bugfix timeout + config YAML, puis vue milestones + colonnes. ADR-012 a ADR-015 couvrant degradation gracieuse, config.py, MilestoneData, et colonnes configurables. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
31 KiB
Plan de version v1.4.0 — gitea-dashboard
Objectif
Ajouter une vue milestones dediee (--milestones), le support d'un fichier de configuration YAML, la visibilite configurable des colonnes (--columns), et corriger la gestion des timeouts reseau pendant la pagination.
Track
Minor : 6 -> 7 -> 8 -> 9 -> 10+11 -> (12) -> 13
Budget de scope
| Critere | Valeur |
|---|---|
| Max fichiers par phase | 5 |
| Total fichiers estimes | 10 (6 modules source + 4 fichiers de tests) |
| Fichiers crees | 1 (config.py) |
| Tests estimes | ~45 nouveaux (total ~167) |
Inclus
- Vue milestones avec
--milestones(#16) - Fichier de configuration YAML (#17)
- Gestion des timeouts reseau pendant la pagination (#18)
- Visibilite configurable des colonnes avec
--columns(#19)
Exclus
- Parallelisation des appels API (ADR-003, differee)
- Export CSV/YAML
- Cache API local (fichier/SQLite)
- Dashboard interactif TUI
Differe (v1.5+)
- Parallelisation des appels API
- Export CSV
- Cache API local
- Dashboard interactif (TUI)
- Sous-commandes CLI (ADR-011, a reconsiderer si modes alternatifs continuent de croitre)
Etapes skippees
| Etape | Nom | Raison |
|---|---|---|
| 1 | Discovery | Projet existant, discovery v1.0.0 suffisante |
| 2 | Project creation | Projet existant |
| 3 | Specs | Minor -- specs couvertes par les issues #16-#19 et ce plan |
| 4 | Research | API milestones deja utilisee (client.get_milestones). PyYAML est standard |
| 5 | Roadmap | Minor -- milestone v1.4.0 deja creee sur Gitea |
| 12 | Deploy | Outil CLI local, pas de deploiement serveur |
Analyse des dependances entre issues
#18 (timeout pagination) -- fondation, corrige _get_paginated dans client.py
#17 (config YAML) -- nouveau module config.py, modifie cli.py
#16 (--milestones) -- nouveau endpoint d'affichage, modifie collector.py + display.py
#19 (--columns) -- modifie display.py + cli.py, depend de #16 (nouvelles colonnes)
Dependances :
- #18 est un bugfix independant, doit etre fait en premier (stabilite du collecteur)
- #17 cree config.py et modifie cli.py ; #16 modifie aussi cli.py -> separer les phases
- #19 depend de #16 car les colonnes de la vue milestones doivent etre connues avant de les rendre configurables
- #17 et #19 modifient tous les deux cli.py mais dans des zones differentes (config resolution vs argparse columns)
Ordre : #18 -> #17 -> #16 -> #19
Evaluation de sous-versions
4 issues dont 2 features independantes, 1 improvement, 1 bugfix. Scope < 10 fichiers. Les issues ne justifient pas de sous-versions : elles sont suffisamment couplees (toutes touchent cli.py) et le scope total reste gerable en une version.
Phase 1 : Bugfix timeout pagination + configuration YAML (#18, #17)
Goal : Corriger la degradation gracieuse sur timeout reseau pendant la pagination, et ajouter le support de configuration YAML.
Issues Gitea : fixes #18, fixes #17
Fichiers
| Action | Fichier | Modifications | Cross-references |
|---|---|---|---|
| Modify | src/gitea_dashboard/client.py |
_get_paginated : catch ReadTimeout/ConnectTimeout sur page intermediaire, retry avec backoff, degradation gracieuse (retourner les donnees partielles + warning) |
collector.py (consomme _get_paginated) |
| Create | src/gitea_dashboard/config.py |
Nouveau module : lecture YAML, resolution ${VAR}, merge des priorites (CLI > env > config > defaults) |
cli.py (consomme pour initialiser les args) |
| Modify | src/gitea_dashboard/cli.py |
Ajouter --config dans argparse. Appeler config.load_config() avant le merge des args. Passer les valeurs resolues au reste du pipeline |
config.py (load_config) |
| Modify | tests/test_client.py |
Tests timeout pendant pagination (mock ReadTimeout sur page 2), test degradation gracieuse, test retry avec backoff | client.py |
| Modify | tests/test_config.py |
Nouveau fichier tests : fixtures YAML (valide, invalide, partiel, vide), resolution ${VAR}, priorite CLI > env > config > defaults |
config.py |
Interfaces
client.py (modifications)
class GiteaClient:
def _get_paginated(self, endpoint: str, params: dict | None = None) -> list[dict]:
"""Requete GET avec pagination automatique.
Comportement actuel : boucle tant que len(page) == limit (50).
Utilise _get_with_retry pour la resilience aux timeouts.
Ajout v1.4.0 : si _get_with_retry leve une exception Timeout sur
une page intermediaire (page > 1), catch l'exception et retourner
les donnees collectees jusque-la au lieu de crasher.
Emet un warning via warnings.warn() pour signaler les donnees partielles.
Si la premiere page echoue (page == 1), l'exception remonte
normalement (pas de donnees partielles possibles).
"""
Pourquoi modifier _get_paginated et non _get_with_retry : le retry existe deja dans _get_with_retry (ADR-007/ADR-009). Le probleme est que quand toutes les tentatives de retry echouent sur une page intermediaire, _get_paginated crashe au lieu de retourner les donnees partielles. La degradation gracieuse est une responsabilite de la pagination, pas du retry.
Pourquoi warnings.warn et non logging : le projet n'utilise pas le module logging. warnings.warn est la convention stdlib pour signaler un probleme non-fatal sans dependance supplementaire. Le CLI peut capturer les warnings pour l'affichage Rich.
config.py (nouveau module)
import os
from pathlib import Path
from typing import Any
_DEFAULT_CONFIG_PATHS = [
Path(".gitea-dashboard.yml"),
Path.home() / ".config" / "gitea-dashboard" / "config.yml",
]
def resolve_env_vars(value: str) -> str:
"""Resout les references ${VAR} dans une valeur string.
Remplace ${VAR} par os.environ[VAR].
Si VAR n'est pas defini, laisse la reference telle quelle.
Ne resout pas les references imbriquees.
"""
def load_config(config_path: str | None = None) -> dict[str, Any]:
"""Charge la configuration depuis un fichier YAML.
Ordre de recherche si config_path est None :
1. .gitea-dashboard.yml (repertoire courant)
2. ~/.config/gitea-dashboard/config.yml
Retourne un dict vide si aucun fichier trouve.
Leve une erreur claire si config_path est fourni mais le fichier
n'existe pas ou est invalide.
Les valeurs string contenant ${VAR} sont resolues via resolve_env_vars.
"""
def merge_config(
cli_args: dict[str, Any],
env_vars: dict[str, Any],
file_config: dict[str, Any],
defaults: dict[str, Any],
) -> dict[str, Any]:
"""Fusionne les sources de configuration par priorite.
Priorite : cli_args > env_vars > file_config > defaults.
Une valeur None dans une source de priorite superieure ne masque pas
la valeur d'une source de priorite inferieure.
"""
Pourquoi un nouveau module plutot qu'une extension de cli.py : la gestion de configuration YAML (lecture fichier, resolution de variables, merge de priorites) est une responsabilite distincte du parsing d'arguments. ADR-006 a deja montre que la creation de modules supplementaires est acceptable quand la responsabilite est clairement distincte. Le projet passe a 7 modules source (6 + config.py).
Pourquoi PyYAML et non la stdlib : Python n'a pas de parseur YAML dans la stdlib. PyYAML est la dependance la plus legere et la plus utilisee pour ce besoin. C'est une nouvelle dependance explicite dans pyproject.toml.
cli.py (modifications)
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
"""Parse les arguments CLI.
Ajout v1.4.0 :
--config : chemin vers un fichier de configuration YAML alternatif
"""
def _resolve_config(args: argparse.Namespace) -> argparse.Namespace:
"""Resout la configuration en appliquant la priorite CLI > env > config > defaults.
1. Charge le fichier config (args.config ou chemin par defaut)
2. Lit les variables d'environnement pertinentes (GITEA_URL, GITEA_TOKEN)
3. Fusionne avec les valeurs CLI
4. Retourne un Namespace enrichi
"""
Comportement attendu
-
Timeout sur page intermediaire pendant la pagination :
GET /api/v1/user/repos?page=1 -> 200 OK (50 repos) GET /api/v1/user/repos?page=2 -> ReadTimeout (apres 2 retries) # Warning : "Donnees partielles : timeout sur la page 2 de /api/v1/user/repos" # Dashboard affiche les 50 premiers repos avec un avertissement -
Timeout sur la premiere page :
GET /api/v1/user/repos?page=1 -> ReadTimeout (apres 2 retries) # Exception remonte normalement -> message d'erreur CLI -
Configuration YAML :
# ~/.config/gitea-dashboard/config.yml url: http://192.168.0.106:3000 token: ${GITEA_TOKEN} sort: activity exclude: - archived-repo no_desc: false -
Priorite de configuration :
# config.yml a sort: activity, CLI passe --sort name $ gitea-dashboard --sort name # -> tri par name (CLI gagne)
Tests
test_client.py (ajouts)
test_get_paginated_timeout_page2_returns_partial: mock page 1 OK (50 items), page 2 leve ReadTimeout -> retourne les 50 items de page 1.test_get_paginated_timeout_page1_raises: mock page 1 leve ReadTimeout -> exception remonte.test_get_paginated_connect_timeout_graceful: mock ConnectTimeout sur page 2 -> degradation gracieuse.test_get_paginated_partial_data_emits_warning: verifie quewarnings.warnest appele avec le message de donnees partielles.
test_config.py (nouveau fichier)
test_load_config_valid_yaml: fixture YAML valide -> dict avec toutes les cles.test_load_config_partial_yaml: fixture YAML avec seulementurletsort-> dict partiel.test_load_config_empty_file: fichier vide -> dict vide.test_load_config_invalid_yaml: YAML syntaxiquement invalide -> erreur claire.test_load_config_custom_path:--config /tmp/custom.yml-> charge le fichier specifie.test_load_config_missing_custom_path:--config /inexistant.yml-> erreur claire.test_load_config_default_paths: fixture dans.gitea-dashboard.yml-> charge automatiquement.test_resolve_env_vars_simple:${GITEA_TOKEN}-> valeur de la variable.test_resolve_env_vars_undefined:${UNDEFINED}-> laisse la reference telle quelle.test_resolve_env_vars_in_list: liste YAML avec${VAR}-> chaque element resolu.test_merge_config_priority: CLI > env > config > defaults, verifie la precedence.test_merge_config_none_does_not_override: CLI avec None ne masque pas config.
Livrable
Le timeout pendant la pagination ne crashe plus le collecteur -- les donnees partielles sont retournees avec un warning. Le fichier .gitea-dashboard.yml est supporte avec resolution de variables et priorite CLI > env > config > defaults. Tous les tests passent.
Phase 2 : Vue milestones et colonnes configurables (#16, #19)
Goal : Ajouter le flag --milestones pour une vue dediee des milestones par repo, et le flag --columns pour choisir les colonnes affichees.
Issues Gitea : fixes #16, fixes #19
Fichiers
| Action | Fichier | Modifications | Cross-references |
|---|---|---|---|
| Modify | src/gitea_dashboard/collector.py |
Nouvelle fonction collect_milestones() pour collecter les milestones de tous les repos (avec filtrage include/exclude) |
client.py (get_milestones), cli.py (appelle si --milestones) |
| Modify | src/gitea_dashboard/display.py |
Nouvelle fonction render_milestones() pour le tableau milestones dedie. Constante AVAILABLE_COLUMNS et fonction parse_columns() pour le parsing de --columns. Modifier render_dashboard() pour respecter la visibilite des colonnes |
collector.py (MilestoneData ou dicts), cli.py (passe les colonnes) |
| Modify | src/gitea_dashboard/exporter.py |
Supporter l'export JSON des milestones (milestones_to_dicts()) |
collector.py (donnees milestones) |
| Modify | src/gitea_dashboard/cli.py |
Ajouter --milestones et --columns dans argparse. Router vers render_milestones() ou export_json() selon le mode. Gerer --columns help. Alias --no-desc vers --columns -description |
display.py (render_milestones, parse_columns), config.py (colonnes dans config YAML) |
| Modify | tests/test_collector.py |
Tests collect_milestones() : repos avec/sans milestones, filtrage, repos vides |
collector.py |
Interfaces
collector.py (ajouts)
@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 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
(pas seulement open, pour afficher la progression globale).
Retourne une liste plate de MilestoneData triee par repo puis milestone.
"""
Pourquoi un dataclass MilestoneData plutot que des dicts bruts : coherent avec RepoData (ADR-002). Un dataclass documente les champs attendus et permet la validation. Le calcul de progress_pct est centralise dans le collecteur, pas dans l'affichage.
Pourquoi state=all et non state=open : l'issue #16 demande une vue de progression des milestones. Les milestones fermees (100%) sont informatives pour voir l'historique. Le filtre open-only est deja dans get_milestones() actuel ; pour la vue dediee, on veut tout.
display.py (ajouts)
AVAILABLE_COLUMNS: dict[str, str] = {
"name": "Nom du repo",
"description": "Description du repo",
"issues": "Nombre d'issues ouvertes",
"release": "Derniere release",
"commit": "Date du dernier commit",
"activity": "Indicateur d'activite",
}
def parse_columns(columns_arg: str | None, no_desc: bool = False) -> list[str]:
"""Parse l'argument --columns et retourne la liste des colonnes a afficher.
Si columns_arg est None : retourne toutes les colonnes (sauf description si no_desc).
Si columns_arg est "help" : retourne la liste speciale ["__help__"].
Les colonnes sont separees par des virgules.
Le prefixe "-" exclut une colonne (ex: "-description").
Leve ValueError si une colonne inconnue est specifiee.
"""
def render_milestones(
milestones: list[MilestoneData],
console: Console | None = None,
) -> None:
"""Affiche le tableau des milestones.
Colonnes : Repo, Milestone, Open, Closed, Progress (%).
La barre de progression utilise le pourcentage calcule.
Coloration : vert > 80%, jaune 50-80%, rouge < 50%.
"""
Pourquoi AVAILABLE_COLUMNS est un dict et non une liste : le dict mappe nom technique -> description lisible, utile pour --columns help. Une liste ne suffirait pas pour l'affichage d'aide.
Pourquoi parse_columns retourne ["help"] : le CLI doit detecter le mode aide pour afficher les colonnes et quitter. Une valeur sentinelle est plus propre qu'un booleen supplementaire dans la signature.
exporter.py (ajouts)
def milestones_to_dicts(milestones: list[MilestoneData]) -> list[dict]:
"""Convertit une liste de MilestoneData en liste de dicts serialisables.
Sanitize les champs texte (repo_name, title) pour les caracteres de controle.
"""
def export_milestones_json(milestones: list[MilestoneData], indent: int = 2) -> str:
"""Exporte les milestones en JSON formate."""
cli.py (ajouts)
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
"""Parse les arguments CLI.
Ajout v1.4.0 :
--milestones : affiche la vue milestones au lieu du dashboard repos
--columns : liste des colonnes a afficher (separe par virgules)
Supporte l'exclusion par prefixe "-"
"--columns help" affiche les colonnes disponibles
"""
Comportement attendu
-
Vue milestones :
$ gitea-dashboard --milestones +------------------+-------------+------+--------+----------+ | Repo | Milestone | Open | Closed | Progress | +------------------+-------------+------+--------+----------+ | gitea-dashboard | v1.4.0 | 4 | 0 | 0% | | gitea-dashboard | v1.3.0 | 0 | 5 | 100% | | workflow | v2.6.1 | 0 | 5 | 100% | +------------------+-------------+------+--------+----------+ -
Vue milestones avec filtre :
$ gitea-dashboard --milestones --repo gitea # Affiche uniquement les milestones des repos contenant "gitea" -
Vue milestones en JSON :
$ gitea-dashboard --milestones --format json [{"repo_name": "gitea-dashboard", "title": "v1.4.0", ...}] -
Colonnes configurables :
$ gitea-dashboard --columns name,issues,release # Affiche seulement les colonnes name, issues, release $ gitea-dashboard --columns -description,-commit # Affiche tout sauf description et commit $ gitea-dashboard --columns help # Colonnes disponibles : name, description, issues, release, commit, activity -
Retrocompatibilite
--no-desc:$ gitea-dashboard --no-desc # Equivalent a --columns -description # Les deux flags coexistent
Tests
test_collector.py (ajouts)
test_collect_milestones_basic: 2 repos avec milestones -> liste plate de MilestoneData.test_collect_milestones_empty_repo: repo sans milestone -> pas dans la liste.test_collect_milestones_progress_calculation: 3 open, 7 closed -> progress_pct == 70.test_collect_milestones_with_include_filter: filtre include respecte.test_collect_milestones_with_exclude_filter: filtre exclude respecte.
test_display.py (ajouts)
test_render_milestones_basic: capture console, verifie le tableau avec colonnes attendues.test_render_milestones_empty: liste vide -> message "Aucune milestone trouvee."test_render_milestones_progress_colors: verifie la coloration selon le pourcentage.test_parse_columns_all_default: None -> toutes les colonnes.test_parse_columns_inclusion: "name,issues" -> ["name", "issues"].test_parse_columns_exclusion: "-description,-commit" -> toutes sauf description et commit.test_parse_columns_unknown_raises: "unknown" -> ValueError.test_parse_columns_help: "help" -> ["help"].test_parse_columns_no_desc_compat: no_desc=True -> description exclue.test_render_dashboard_with_columns: colonnes specifiques -> seules ces colonnes affichees.
test_exporter.py (ajouts)
test_export_milestones_json_basic: MilestoneData -> JSON valide.test_export_milestones_json_empty: liste vide -> "[]".
test_cli.py (ajouts)
test_parse_args_milestones:--milestones->Namespace(milestones=True).test_main_milestones_mode: mock collect_milestones + render_milestones, verifie le routage.test_parse_args_columns:--columns name,issues->Namespace(columns="name,issues").test_main_columns_help:--columns help-> affiche la liste et quitte.test_main_no_desc_and_columns_compat:--no-desc --columns -commit-> les deux s'appliquent.
Livrable
Le flag --milestones affiche un tableau dedie avec la progression des milestones par repo. Le flag --columns permet de choisir les colonnes affichees avec support d'inclusion et d'exclusion. --no-desc reste fonctionnel comme alias. L'export JSON fonctionne pour les deux modes. Tous les tests passent.
Phase 3 : Audit
Goal : Audit de qualite (reviewer) et de securite (guardian). Score cible : 100. Plancher : 50.
Phase 4 : Smoke test
Goal : Tests E2E sur l'instance Gitea reelle. Verification manuelle des nouvelles fonctionnalites (--milestones, --columns, config YAML, degradation gracieuse timeout).
Phase 5 : Documentation + Release
Goal : Mise a jour README.md, CHANGELOG.md (format Keep a Changelog). Bump de version a 1.4.0. Creation du tag et de la release Gitea.
Phase 6 : Retrospective
Goal : Metriques, MEMORY.md, revue des issues Gitea, analyse du workflow.
Architecture des modules (impact v1.4.0)
gitea-dashboard v1.4.0
=====================
Terminal Application Gitea API
-------- ----------- ---------
+------------------+
$ gitea-dashboard | cli.py |
--milestones | - parse args |
--columns | - resolve config |
--config | - route modes |
| - gere erreurs |
+--------+---------+
|
+--------+---------+
| --milestones? |
+--+----------+----+
| |
oui | | non
v v
collect_milestones() collect_all()
| |
v +-------+-------+
render_milestones | |
ou export_json v v
+------------+ +-------------+
| display.py | | exporter.py |
| + colonnes | | + milestones|
<--------------------| + --columns| | + sanitize |---------> stdout (JSON)
Output Rich | + milest. | +-------------+
(tableaux) +------------+
+------------------+
| config.py | <-- NEW
| + load YAML |
| + resolve ${VAR} |
| + merge priority |
+------------------+
+------------------+
| client.py |
| + get_version() |-----> GET /api/v1/version
| + retry HTTP 429 |-----> GET /api/v1/user/repos
| + timeout gracf. |-----> GET .../releases/latest
+------------------+-----> GET .../milestones (state=all)
-----> GET .../commits?limit=1
| Module | Impact v1.4.0 | Detail |
|---|---|---|
client.py |
Modifie | Degradation gracieuse dans _get_paginated sur timeout page intermediaire |
collector.py |
Modifie | Nouveau dataclass MilestoneData, nouvelle fonction collect_milestones() |
display.py |
Modifie | render_milestones(), parse_columns(), AVAILABLE_COLUMNS, colonnes configurables dans render_dashboard() |
exporter.py |
Modifie | milestones_to_dicts(), export_milestones_json() |
cli.py |
Modifie | Options --milestones, --columns, --config. Resolution config YAML. Routage du mode milestones |
config.py |
Cree | Lecture YAML, resolution ${VAR}, merge de priorites |
Decisions architecturales
ADR-012 : Degradation gracieuse sur timeout dans _get_paginated (v1.4.0)
Date : 2026-03-13 Statut : accepte
Contexte : Un timeout reseau sur une page intermediaire de la pagination fait crasher tout le collecteur. Le retry existant (ADR-007/ADR-009) retente les requetes individuelles, mais apres epuisement des retries, l'exception remonte et les donnees des pages precedentes sont perdues.
Decision : Dans _get_paginated, catch les exceptions Timeout apres epuisement des retries uniquement pour les pages > 1. Retourner les donnees collectees jusque-la et emettre un warnings.warn(). Si la premiere page echoue, l'exception remonte normalement (pas de donnees partielles possibles).
Consequences :
- Le dashboard affiche un resultat partiel plutot qu'un crash
- L'utilisateur est informe via un warning (visible dans la console)
- La premiere page echouee reste un crash clair (pas de faux resultat vide)
- Coherent avec le principe "Gestion gracieuse" de CLAUDE.md
- Risque : l'utilisateur pourrait ne pas remarquer le warning -> l'affichage CLI devra etre explicite
ADR-013 : Nouveau module config.py pour la configuration YAML (v1.4.0)
Date : 2026-03-13 Statut : accepte
Contexte : L'issue #17 demande un fichier de configuration YAML. La logique (lecture fichier, resolution variables, merge de priorites) est substantielle et distincte du parsing CLI.
Decision : Creer config.py comme 7eme module source. ADR-002 (4 modules max) est relaxe pour la 3eme fois (apres ADR-006 pour exporter.py). Le principe "un module = une responsabilite" reste respecte.
Consequences :
- Separation claire : cli.py parse les args, config.py resout la configuration
- Le module est testable independamment avec des fixtures YAML
- Nouvelle dependance PyYAML dans pyproject.toml (premiere dependance ajoutee depuis la creation du projet)
- Le merge de priorites (CLI > env > config > defaults) est centralise et testable
- Si d'autres formats de config apparaissent (TOML, INI), le module absorbe la complexite
ADR-014 : Dataclass MilestoneData pour la vue milestones (v1.4.0)
Date : 2026-03-13 Statut : accepte
Contexte : La vue --milestones collecte des milestones de tous les repos. Les milestones de l'API sont des dicts bruts sans reference au repo parent. Le calcul du pourcentage de progression est necessaire.
Decision : Creer un dataclass MilestoneData dans collector.py, incluant repo_name et progress_pct pre-calcule. La collecte utilise state=all (pas seulement open) pour afficher l'historique complet.
Consequences :
- Coherent avec RepoData : donnees normalisees et documentees
- Le calcul du pourcentage est centralise dans le collecteur (pas dans display.py)
state=allaugmente le nombre d'appels API mais donne une vue complete- Le client.get_milestones() existant utilise
state=open-> la nouvelle collecte appellera directement avecstate=allou une nouvelle methode
ADR-015 : Colonnes configurables par inclusion/exclusion (v1.4.0)
Date : 2026-03-13 Statut : accepte
Contexte : L'issue #19 demande de pouvoir choisir les colonnes affichees. L'approche actuelle (--no-desc) est ad hoc pour une seule colonne. Un systeme generique est maintenant justifie.
Decision : Ajouter --columns avec une syntaxe a virgules. Support de l'inclusion directe (name,issues) et de l'exclusion par prefixe - (-description,-commit). --no-desc reste fonctionnel comme alias de --columns -description.
Consequences :
- Remplace l'approche YAGNI de v1.3.0 (ADR-011 notait "un systeme generique serait over-engineere") -- maintenant justifie par l'issue #19
- Retrocompatible :
--no-desccontinue de fonctionner --columns helpfournit une aide contextuelle sans documentation externe- Les deux syntaxes (inclusion et exclusion) couvrent les cas d'usage courants
- Risque :
--no-desc+--columnsen meme temps doit etre gere (les deux s'appliquent cumulativement)
Risques d'audit
| Zone | Risque | Severite estimee |
|---|---|---|
client.py -- degradation gracieuse |
Le warning pourrait etre silencieux si capture par un framework de test. Doit etre visible dans la sortie CLI | major |
config.py -- resolution ${VAR} |
Un ${VAR} non resolu dans token pourrait envoyer une reference liteerale comme token API. Doit etre detecte et signale |
critical |
config.py -- YAML injection |
PyYAML safe_load requis pour eviter l'execution de code. yaml.load sans Loader est une faille connue |
critical |
config.py -- token en clair dans le fichier |
Si l'utilisateur ecrit token: abc123 au lieu de token: ${GITEA_TOKEN}, le token est en clair sur le disque. Documenter le risque, recommander ${VAR} |
major |
display.py -- --columns + --no-desc |
Les deux flags doivent etre cumulatifs, pas contradictoires. Tester la combinaison | minor |
display.py -- colonnes inconnues |
--columns unknown doit lever une erreur claire, pas un KeyError silencieux |
minor |
collector.py -- state=all milestones |
Plus d'appels API que state=open. Risque de rate limiting sur les instances avec beaucoup de repos/milestones |
minor |
exporter.py -- milestones JSON |
Le format JSON des milestones doit etre coherent avec celui des repos (meme structure de sanitisation) | minor |
pyproject.toml -- PyYAML |
Nouvelle dependance a auditer (pas de CVE connue sur les versions recentes) | minor |
Issues Gitea rattachees
| Issue | Titre | Phase |
|---|---|---|
| #18 | fix: handle API timeout during paginated requests | Phase 1 |
| #17 | feat: YAML configuration file support | Phase 1 |
| #16 | feat: milestone progress view (--milestones) | Phase 2 |
| #19 | improvement: configurable column visibility (--columns) | Phase 2 |
Dependances
| Dependance | Type | Version | Changement v1.4.0 |
|---|---|---|---|
| Python | Runtime | >= 3.10 | Inchange |
| requests | Librairie | >= 2.31 | Inchange |
| rich | Librairie | >= 13.0 | Inchange |
| PyYAML | Librairie | >= 6.0 | Nouveau (#17) |
| pytest | Dev | >= 7.0 | Inchange |
| ruff | Dev | >= 0.4 | Inchange |
| Instance Gitea | Service externe | 192.168.0.106:3000 | Inchange |
Criteres de validation par issue
| Issue | Criteres de validation |
|---|---|
| #16 | --milestones affiche un tableau avec colonnes Repo/Milestone/Open/Closed/Progress. Compatible --repo et --exclude. Compatible --format json. Tests : collecte, affichage, filtrage, export JSON. |
| #17 | .gitea-dashboard.yml ou ~/.config/gitea-dashboard/config.yml charge. --config <path> fonctionne. Priorite CLI > env > config > defaults. Resolution ${VAR}. Tests : YAML valide/invalide/partiel/vide, resolution vars, priorite. PyYAML dans pyproject.toml. |
| #18 | Timeout sur page > 1 retourne donnees partielles + warning. Timeout sur page 1 crashe normalement. Retry (max 2) avec backoff lineaire (1s, 2s) sur ReadTimeout et ConnectTimeout. Tests : mock timeout page 2, degradation gracieuse, warning emis. |
| #19 | --columns name,issues affiche seulement ces colonnes. --columns -description exclut la colonne. --columns help affiche la liste. --no-desc reste fonctionnel. Erreur claire si colonne inconnue. Tests : parsing, inclusion, exclusion, combinaison avec --no-desc, validation. |