From 8e8271be9dda381c19319d2945bafe57fcf2a709 Mon Sep 17 00:00:00 2001 From: sylvain Date: Wed, 11 Mar 2026 04:30:07 +0100 Subject: [PATCH] =?UTF-8?q?docs(v1.1.0):=20version=20plan=20and=20ADR=20?= =?UTF-8?q?=E2=80=94=20repo=20filtering=20feature?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- docs/plans/v1.1.0-plan.md | 259 ++++++++++++++++++++++++++++++++++++ docs/technical/decisions.md | 32 +++++ 2 files changed, 291 insertions(+) create mode 100644 docs/plans/v1.1.0-plan.md diff --git a/docs/plans/v1.1.0-plan.md b/docs/plans/v1.1.0-plan.md new file mode 100644 index 0000000..3a3d482 --- /dev/null +++ b/docs/plans/v1.1.0-plan.md @@ -0,0 +1,259 @@ + + +# Plan de version v1.1.0 — gitea-dashboard + +## Objectif + +Permettre a l'utilisateur de filtrer l'affichage du dashboard par nom de repo (inclusion et exclusion), via des options CLI. Premiere evolution fonctionnelle du dashboard. + +## Track + +**Minor** : 6 -> 7 -> 8 -> 9 -> 10 -> 11 -> (12) -> 13 + +--- + +## Budget de scope + +| Critere | Valeur | +|---------|--------| +| Max fichiers par phase | 4 | +| Total fichiers estimes | 6 (3 modules modifies + 3 fichiers de tests modifies) | + +### Inclus + +- Option CLI `--repo` / `-r` pour filtrer par nom(s) de repo (inclusion) +- Option CLI `--exclude` / `-x` pour exclure des repos par nom +- Filtrage par sous-chaine (match partiel, insensible a la casse) +- Filtrage applicable aux deux options (cumulable : `--repo X --exclude Y`) +- Ajout d'argparse pour le parsing des options CLI +- Tests unitaires du filtrage et tests d'integration CLI + +### Exclus + +- Filtrage par owner/organisation (hors scope, pas de demande) +- Filtrage par regex (sous-chaine suffit pour v1.1.0) +- Filtrage par labels, activite ou date +- Tri des resultats (differe) +- Options `--format`, `--sort` (differees) +- Parallelisation des appels API (ADR-003, differee) + +### Differe (v1.2+) + +- Filtrage par owner/organisation +- Option `--sort` (par nom, issues, date de release) +- Cache des reponses API +- Option `--format` (json, csv) + +--- + +## 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 l'issue #5 et ce plan | +| 4 | Research | Pas de technologie nouvelle (argparse est stdlib) | +| 5 | Roadmap | Minor — roadmap existante, issue #5 deja creee | +| 12 | Deploy | Outil CLI local, pas de deploiement serveur | + +--- + +## Phase 1 : Parsing CLI et logique de filtrage + +**Goal** : Ajouter les options `--repo` et `--exclude` a la CLI et implementer la logique de filtrage dans le collecteur. + +**Issues Gitea** : fixes #5 + +### Fichiers + +| Action | Fichier | Modifications | Cross-references | +|--------|---------|---------------|------------------| +| Modify | `src/gitea_dashboard/cli.py` | Ajouter argparse, options `--repo`/`-r` et `--exclude`/`-x`, passer les filtres a `collect_all()` | `collector.py` (passe les filtres) | +| Modify | `src/gitea_dashboard/collector.py` | Ajouter parametres `include` et `exclude` a `collect_all()`, logique de filtrage | `cli.py` (appele avec filtres), `client.py` (inchange) | +| Modify | `tests/test_cli.py` | Tests argparse, passage des filtres, combinaison d'options | `cli.py` | +| Modify | `tests/test_collector.py` | Tests du filtrage : inclusion, exclusion, combinaison, casse, sous-chaine | `collector.py` | + +### Interfaces + +#### cli.py (modifications) + +```python +import argparse + +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + """Parse les arguments CLI. + + Options: + --repo / -r : noms de repos a inclure (repeatable) + --exclude / -x : noms de repos a exclure (repeatable) + + Returns: + Namespace avec .repo (list[str] | None) et .exclude (list[str] | None) + """ + +def main(argv: list[str] | None = None) -> None: + """Point d'entree principal. + + Modification : accepte argv pour testabilite. + Appelle parse_args(), puis passe include/exclude a collect_all(). + """ +``` + +Pourquoi `parse_args` separe de `main` : testabilite. On peut tester le parsing seul sans mocker l'environnement complet. + +Pourquoi `argv` en parametre de `main` : permet aux tests d'injecter des arguments sans patcher `sys.argv`. + +#### collector.py (modifications) + +```python +def collect_all( + client: GiteaClient, + include: list[str] | None = None, + exclude: list[str] | None = None, +) -> list[RepoData]: + """Collecte les donnees des repos, avec filtrage optionnel. + + Args: + client: Client API Gitea. + include: Si fourni, ne garde que les repos dont le nom contient + au moins une des sous-chaines (insensible a la casse). + exclude: Si fourni, exclut les repos dont le nom contient + au moins une des sous-chaines (insensible a la casse). + + Ordre d'application : include d'abord (si present), puis exclude. + Si include est None ou vide, tous les repos sont inclus avant l'etape exclude. + """ +``` + +Pourquoi le filtrage est dans `collector.py` et non `cli.py` : le collecteur est responsable de "quels repos collecter". Cela evite de polluer le CLI avec de la logique metier et garde la testabilite (on teste le filtrage sans mocker argparse). + +Pourquoi le filtrage est post-fetch (apres `get_repos()`) et non pre-fetch : l'API Gitea `/user/repos` ne supporte pas de filtre par nom cote serveur. On doit recuperer tous les repos puis filtrer localement. + +### Comportement attendu + +1. Sans options, comportement identique a v1.0.0 : + ``` + $ gitea-dashboard + # Affiche tous les repos (aucun changement) + ``` + +2. Filtrage par inclusion : + ``` + $ gitea-dashboard --repo dashboard --repo infra + # Affiche uniquement les repos dont le nom contient "dashboard" ou "infra" + ``` + +3. Filtrage par exclusion : + ``` + $ gitea-dashboard --exclude fork --exclude test + # Affiche tous les repos sauf ceux dont le nom contient "fork" ou "test" + ``` + +4. Combinaison inclusion + exclusion : + ``` + $ gitea-dashboard --repo projet -x old + # Inclut les repos contenant "projet", puis exclut ceux contenant "old" + ``` + +5. Insensibilite a la casse : + ``` + $ gitea-dashboard --repo Dashboard + # Match "gitea-dashboard", "Dashboard-test", etc. + ``` + +6. Aucun repo ne correspond : + ``` + $ gitea-dashboard --repo inexistant + # Affiche "Aucun repo trouve." (comportement existant de render_dashboard) + ``` + +### Tests + +#### test_cli.py (ajouts) + +- `test_parse_args_no_options` : retourne `Namespace(repo=None, exclude=None)` +- `test_parse_args_single_repo` : `--repo foo` -> `Namespace(repo=["foo"], ...)` +- `test_parse_args_multiple_repo` : `--repo foo --repo bar` -> `Namespace(repo=["foo", "bar"], ...)` +- `test_parse_args_short_flags` : `-r foo -x bar` fonctionne comme les formes longues +- `test_main_passes_filters_to_collect_all` : verifie que `collect_all` est appele avec les bons `include`/`exclude` +- `test_main_no_filters_passes_none` : sans options, `collect_all(client, include=None, exclude=None)` + +#### test_collector.py (ajouts) + +- `test_collect_all_no_filter` : comportement identique a v1.0.0 (retrocompatibilite) +- `test_collect_all_include_single` : filtre par une sous-chaine +- `test_collect_all_include_multiple` : filtre par plusieurs sous-chaines (OR) +- `test_collect_all_exclude_single` : exclut par une sous-chaine +- `test_collect_all_include_and_exclude` : inclusion puis exclusion +- `test_collect_all_case_insensitive` : "Dashboard" matche "gitea-dashboard" +- `test_collect_all_no_match` : retourne une liste vide si aucun repo ne correspond +- `test_collect_all_exclude_all` : retourne une liste vide si tout est exclu + +### Livrable + +La commande `gitea-dashboard --repo X -x Y` filtre l'affichage. Sans options, le comportement est identique a v1.0.0. Tous les tests passent (existants + nouveaux). + +--- + +## Phase 2 : Smoke test et documentation + +**Goal** : Valider le filtrage sur l'instance reelle et mettre a jour la documentation. + +**Dependances** : phase 1 terminee, acces a l'instance Gitea (192.168.0.106:3000) + +**Composants cles** : + +- Test E2E manuel : `gitea-dashboard --repo dashboard`, `gitea-dashboard -x fork`, combinaison +- Verification de la retrocompatibilite : `gitea-dashboard` sans options +- Mise a jour de README.md (section usage avec les nouvelles options) +- Mise a jour de CHANGELOG.md (section Added pour v1.1.0) +- Mise a jour de `--help` (automatique via argparse) + +--- + +## Architecture des modules (impact) + +Le changement est minimal et respecte l'architecture existante (ADR-002) : + +| Module | Impact | Detail | +|--------|--------|--------| +| `cli.py` | Modifie | Ajout argparse + passage des filtres | +| `collector.py` | Modifie | Nouveaux parametres `include`/`exclude` dans `collect_all()` | +| `client.py` | Inchange | Aucun impact (le filtrage est local, pas API) | +| `display.py` | Inchange | Recoit toujours `list[RepoData]`, ne sait pas si c'est filtre | + +Pas de nouveau module. La signature de `collect_all()` est modifiee avec des parametres optionnels : **retrocompatible** (les parametres ont des valeurs par defaut `None`). + +--- + +## Risques d'audit + +| Zone | Risque | Severite estimee | +|------|--------|-----------------| +| `cli.py` — argparse | Interaction entre `argv` et `sys.argv` : s'assurer que `parse_args(None)` delegue bien a `sys.argv` | minor | +| `collector.py` — filtrage | Match partiel trop agressif (ex: `--repo a` matche tous les repos contenant "a") | minor | +| `collector.py` — ordre include/exclude | L'ordre d'application doit etre documente et teste | minor | +| `cli.py` — retrocompatibilite | Entry point `main()` ne doit pas casser si appele sans arguments | major | + +--- + +## Issues Gitea rattachees + +| Issue | Titre | Phase | +|-------|-------|-------| +| [#5](https://gitea.tsmse.fr/admin/gitea-dashboard/issues/5) | Ajouter le filtrage par repo | Phase 1 | + +--- + +## Dependances + +| Dependance | Type | Version | +|------------|------|---------| +| Python | Runtime | >= 3.10 | +| argparse | Stdlib | inclus dans Python | +| requests | Librairie | >= 2.31 (inchange) | +| rich | Librairie | >= 13.0 (inchange) | +| pytest | Dev | >= 7.0 (inchange) | +| ruff | Dev | >= 0.4 (inchange) | +| Instance Gitea | Service externe | 192.168.0.106:3000 | diff --git a/docs/technical/decisions.md b/docs/technical/decisions.md index 0f14fcf..fb02428 100644 --- a/docs/technical/decisions.md +++ b/docs/technical/decisions.md @@ -46,3 +46,35 @@ - Temps de reponse acceptable pour < 20 repos (estimee < 10s) - Pas de problemes de concurrence - Facile a ajouter plus tard sans changer les interfaces (le collecteur est le seul point d'appel) + +## ADR-004 : Argparse pour le parsing CLI (v1.1.0) + +**Date** : 2026-03-11 +**Statut** : accepte + +**Contexte** : La v1.1.0 introduit des options CLI (`--repo`, `--exclude`). Un parser d'arguments est necessaire. Trois options : argparse (stdlib), click, typer. + +**Decision** : Utiliser argparse (stdlib Python). Pas de dependance externe pour le parsing CLI. + +**Consequences** : +- Zero nouvelle dependance (argparse est dans la stdlib) +- Coherent avec ADR-001 (stack simple, pas de framework lourd) +- `--help` genere automatiquement +- Suffisant pour des options simples (repeatable flags) +- Si les options deviennent complexes (sous-commandes, autocompletion), migration vers click/typer sera possible + +## ADR-005 : Filtrage par sous-chaine dans le collecteur (v1.1.0) + +**Date** : 2026-03-11 +**Statut** : accepte + +**Contexte** : Le filtrage des repos peut etre fait dans le CLI (apres collecte) ou dans le collecteur (pendant la collecte). Le filtrage par regex est plus puissant mais plus complexe que la sous-chaine. + +**Decision** : Filtrage par sous-chaine (insensible a la casse) dans `collect_all()`. Ordre : include d'abord, exclude ensuite. + +**Consequences** : +- Le collecteur reste le seul responsable de "quels repos collecter" +- Le CLI reste un simple orchestrateur (ADR-002 respecte) +- Retrocompatible : les nouveaux parametres ont des valeurs par defaut None +- Sous-chaine est intuitive pour l'utilisateur (pas besoin de connaitre les regex) +- Le filtrage est post-fetch car l'API Gitea ne supporte pas le filtre par nom