Files
gitea-dashboard/docs/plans/v1.4.0-plan.md
sylvain a1f613f3d8 docs(v1.4.0): version plan and ADR
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>
2026-03-13 03:34:55 +01:00

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

  1. 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
    
  2. Timeout sur la premiere page :

    GET /api/v1/user/repos?page=1 -> ReadTimeout (apres 2 retries)
    # Exception remonte normalement -> message d'erreur CLI
    
  3. 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
    
  4. 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 que warnings.warn est 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 seulement url et sort -> 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

  1. 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%     |
    +------------------+-------------+------+--------+----------+
    
  2. Vue milestones avec filtre :

    $ gitea-dashboard --milestones --repo gitea
    # Affiche uniquement les milestones des repos contenant "gitea"
    
  3. Vue milestones en JSON :

    $ gitea-dashboard --milestones --format json
    [{"repo_name": "gitea-dashboard", "title": "v1.4.0", ...}]
    
  4. 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
    
  5. 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=all augmente le nombre d'appels API mais donne une vue complete
  • Le client.get_milestones() existant utilise state=open -> la nouvelle collecte appellera directement avec state=all ou 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-desc continue de fonctionner
  • --columns help fournit une aide contextuelle sans documentation externe
  • Les deux syntaxes (inclusion et exclusion) couvrent les cas d'usage courants
  • Risque : --no-desc + --columns en 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.