diff --git a/.claude/workflow-progress.md b/.claude/workflow-progress.md index 1a7859c..4310a98 100644 --- a/.claude/workflow-progress.md +++ b/.claude/workflow-progress.md @@ -10,7 +10,7 @@ | Version courante | v1.2.0 | | Track | minor | | Phase courante | 2 — DEV | -| Etape courante | 6 (pending) | +| Etape courante | 6 (done) | | workflow_version | v1.1 | --- @@ -70,7 +70,7 @@ | # | Etape | Statut | Date | Agent/Skill | Validation | Notes | |---|-------|--------|------|-------------|------------|-------| -| 6 | Plan de version | pending | - | architect | Auto (plan avec phases, budget scope) | - | +| 6 | Plan de version | done | 2026-03-12 | architect | Auto (plan avec phases, budget scope) | step_6: done, plan: docs/plans/v1.2.0-plan.md, phases: 3, ADR-006/007/008, gitea_milestone: exists (id:39) | | 7 | Developpement | - | - | /build | Auto (tests passent) | - | | 8 | Audit + corrections | - | - | reviewer + guardian + fixer | Auto (score 100) | - | | 9 | Smoke test | - | - | tester + checklist | Auto (E2E + checklist) | - | @@ -118,6 +118,7 @@ | 2026-03-11 | step 12 skipped | CLI local, pas de deploy | | 2026-03-11 | step 13 done | Retrospective, metriques, analyse, MEMORY.md, milestone fermee | | 2026-03-12 | Start v1.2.0 at step 6 | Minor track, 5 issues (#6-#10): export JSON, dernier commit, fix timeout, tri repos, coloration milestones | +| 2026-03-12 | step 6 done | Plan v1.2.0 (3 phases, 8 fichiers, ADR-006/007/008), milestone exists (id:39) | ## Versions completees diff --git a/docs/plans/v1.2.0-plan.md b/docs/plans/v1.2.0-plan.md new file mode 100644 index 0000000..8fd2e2b --- /dev/null +++ b/docs/plans/v1.2.0-plan.md @@ -0,0 +1,557 @@ + + +# Plan de version v1.2.0 — gitea-dashboard + +## Objectif + +Enrichir le dashboard avec l'export JSON, l'affichage de l'activite recente (dernier commit), le tri configurable des repos, la coloration des milestones selon l'echeance, et corriger la gestion des timeouts API. + +## Track + +**Minor** : 6 -> 7 -> 8 -> 9 -> 10 -> 11 -> (12) -> 13 + +--- + +## Budget de scope + +| Critere | Valeur | +|---------|--------| +| Max fichiers par phase | 5 | +| Total fichiers estimes | 8 (4 modules modifies + 1 nouveau + 3 fichiers de tests modifies/crees) | + +### Inclus + +- Export JSON du dashboard (`--format json`) +- Date du dernier commit par repo (nouvelle colonne) +- Gestion robuste des timeouts API (retry + message utilisateur) +- Tri configurable des repos (`--sort name|issues|release|activity`) +- Coloration des milestones selon la proximite de l'echeance + +### Exclus + +- Parallelisation des appels API (ADR-003, differee) +- Export CSV (hors scope, pas de demande) +- Filtrage par owner/organisation (differe) +- Cache des reponses API (differe) +- Sous-commandes CLI (argparse suffit, ADR-004) + +### Differe (v1.3+) + +- Export CSV +- Cache API local (fichier/SQLite) +- Parallelisation des appels API +- Dashboard interactif (TUI) + +--- + +## 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 #6-#10 et ce plan | +| 4 | Research | Pas de technologie nouvelle (json est stdlib, API commit connue) | +| 5 | Roadmap | Minor — milestone v1.2.0 deja creee sur Gitea | +| 12 | Deploy | Outil CLI local, pas de deploiement serveur | + +--- + +## Analyse des dependances entre issues + +``` +#8 (timeout) -- aucune dependance, fondation +#7 (dernier commit) -- necessite nouveau endpoint client.py +#9 (tri) -- necessite #7 si tri par activite +#10 (coloration milestones) -- aucune dependance +#6 (export JSON) -- necessite toutes les donnees disponibles (#7, #9, #10) +``` + +Ordre logique : #8 -> #7 -> #10 -> #9 -> #6 + +--- + +## Phase 1 : Robustesse API et donnees d'activite (#8, #7) + +**Goal** : Corriger la gestion des timeouts avec retry automatique, puis ajouter la date du dernier commit par repo. + +**Issues Gitea** : fixes #8, fixes #7 + +### Fichiers + +| Action | Fichier | Modifications | Cross-references | +|--------|---------|---------------|------------------| +| Modify | `src/gitea_dashboard/client.py` | Ajouter retry sur timeout (max 2 retries avec backoff), ajouter methode `get_latest_commit(owner, repo)` | `collector.py` (consomme les nouvelles donnees) | +| Modify | `src/gitea_dashboard/collector.py` | Ajouter champ `last_commit_date` a `RepoData`, appeler `get_latest_commit()` dans `collect_all()` | `client.py` (nouvelle methode), `display.py` (nouveau champ) | +| Modify | `src/gitea_dashboard/display.py` | Ajouter colonne "Dernier commit" au tableau principal | `collector.py` (champ `last_commit_date`) | +| Modify | `tests/test_client.py` | Tests retry sur timeout, tests `get_latest_commit()` | `client.py` | +| Modify | `tests/test_collector.py` | Tests avec `last_commit_date` dans `RepoData` | `collector.py` | + +### Interfaces + +#### client.py (modifications) + +```python +class GiteaClient: + _MAX_RETRIES = 2 + _RETRY_DELAY = 1.0 # secondes + + def _get_with_retry(self, url: str, params: dict | None = None) -> requests.Response: + """GET avec retry automatique sur timeout. + + Retente jusqu'a _MAX_RETRIES fois avec backoff lineaire. + Leve requests.Timeout apres epuisement des retries. + """ + + def get_latest_commit(self, owner: str, repo: str) -> dict | None: + """Retourne le dernier commit du repo, ou None si aucun. + + Endpoint: GET /api/v1/repos/{owner}/{repo}/commits?limit=1 + Retourne le premier element de la liste, ou None si vide. + Structure retournee : {sha, created, commit: {message, ...}} + """ +``` + +Pourquoi un retry dans `client.py` et non dans `cli.py` : le retry est une preoccupation du transport HTTP, pas de l'orchestration CLI. Le client est le bon endroit car il connait le contexte de chaque requete. Le CLI garde sa responsabilite de gestion d'erreur finale (message utilisateur). + +Pourquoi `_get_with_retry` en methode interne : elle sera utilisee par `_get_paginated` et les appels directs (`get_latest_release`, `get_latest_commit`). Cela centralise la logique de retry sans dupliquer. + +Pourquoi pas urllib3.Retry : requests utilise urllib3 en interne, mais configurer le retry via `HTTPAdapter` est plus complexe et moins lisible. Un retry manuel simple (boucle + sleep) est plus explicite et testable pour ce cas d'usage. + +#### collector.py (modifications) + +```python +@dataclass +class RepoData: + name: str + full_name: str + description: str + open_issues: int + is_fork: bool + is_archived: bool + is_mirror: bool + latest_release: dict | None + milestones: list[dict] + last_commit_date: str | None # ISO 8601, ex: "2026-03-10T14:30:00Z" +``` + +Pourquoi `str | None` et non `datetime` : coherent avec `latest_release` qui stocke les dates en format brut. La conversion en date relative est la responsabilite de `display.py` (qui a deja `_format_relative_date`). + +### Comportement attendu + +1. Timeout avec retry : + ``` + # Premier appel timeout, deuxieme reussit -> transparent pour l'utilisateur + # Les 3 tentatives echouent -> message d'erreur existant (cli.py l.82-85) + ``` + +2. Dernier commit affiche dans le tableau : + ``` + Gitea Dashboard + +-----------------+--------+------------------+----------------+ + | Repo | Issues | Release | Dernier commit | + +-----------------+--------+------------------+----------------+ + | mon-projet | 3 | v1.0.0 (il y a 5j) | il y a 2j | + | autre-repo [F] | 0 | --- | il y a 30j | + +-----------------+--------+------------------+----------------+ + ``` + +3. Repo sans commit : + ``` + | repo-vide | 0 | --- | --- | + ``` + +### Tests + +#### test_client.py (ajouts) + +- `test_get_with_retry_success_first_attempt` : pas de timeout, reponse directe +- `test_get_with_retry_success_after_timeout` : premier appel timeout, deuxieme OK +- `test_get_with_retry_all_timeouts` : 3 timeouts -> leve `requests.Timeout` +- `test_get_latest_commit_returns_first` : retourne le premier commit de la liste +- `test_get_latest_commit_empty_repo` : retourne None si pas de commits +- `test_get_latest_commit_404` : retourne None si repo non trouve + +#### test_collector.py (ajouts) + +- `test_repo_data_has_last_commit_date` : le champ est present dans RepoData +- `test_collect_all_calls_get_latest_commit` : verifie que `get_latest_commit` est appele pour chaque repo + +### Livrable + +Les appels API sont robustes face aux timeouts (retry transparent). Le tableau affiche la date du dernier commit. Tous les tests existants et nouveaux passent. + +--- + +## Phase 2 : Coloration et tri (#10, #9) + +**Goal** : Ajouter la coloration des milestones selon l'echeance et le tri configurable des repos. + +**Issues Gitea** : fixes #10, fixes #9 + +### Fichiers + +| Action | Fichier | Modifications | Cross-references | +|--------|---------|---------------|------------------| +| Modify | `src/gitea_dashboard/display.py` | Coloration milestones (rouge si echeance depassee, jaune si < 7j, vert sinon). Logique de tri des repos avant affichage | `collector.py` (champ `last_commit_date` pour tri par activite) | +| Modify | `src/gitea_dashboard/cli.py` | Ajouter option `--sort` (choices: name, issues, release, activity) | `display.py` (passe le critere de tri) | +| Modify | `tests/test_display.py` | Tests coloration milestones, tests tri | `display.py` | +| Modify | `tests/test_cli.py` | Tests parsing `--sort` | `cli.py` | + +### Interfaces + +#### cli.py (modifications) + +```python +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + """Parse les arguments CLI. + + Options existantes : --repo, --exclude + Nouvelle option : + --sort / -s : critere de tri (name, issues, release, activity) + defaut: name + """ +``` + +#### display.py (modifications) + +```python +def _colorize_milestone_due(due_on: str | None) -> str: + """Retourne le style Rich selon la proximite de l'echeance. + + - Rouge : echeance depassee + - Jaune : echeance dans les 7 prochains jours + - Vert : echeance dans plus de 7 jours + - Pas de style : pas d'echeance definie + """ + +def _sort_repos(repos: list[RepoData], sort_key: str) -> list[RepoData]: + """Trie la liste des repos selon le critere donne. + + Args: + repos: Liste des repos a trier. + sort_key: Critere de tri parmi : + - "name" : alphabetique par nom (defaut) + - "issues" : par nombre d'issues ouvertes (decroissant) + - "release" : par date de derniere release (plus recent d'abord) + - "activity" : par date du dernier commit (plus recent d'abord) + """ + +def render_dashboard( + repos: list[RepoData], + console: Console | None = None, + sort_key: str = "name", +) -> None: + """Affiche le dashboard. Nouveau parametre sort_key pour le tri.""" +``` + +Pourquoi le tri est dans `display.py` et non `collector.py` : le tri est une preoccupation d'affichage, pas de collecte. Le collecteur fournit les donnees brutes, l'affichage decide de l'ordre de presentation. Cela respecte la separation des responsabilites (ADR-002). + +Pourquoi la coloration est calculee dans `display.py` : la couleur est purement visuelle. `collector.py` ne doit pas connaitre les seuils de couleur (7 jours, etc.). Le display est le bon endroit car il possede deja `_format_relative_date`. + +### Comportement attendu + +1. Coloration des milestones : + ``` + Milestones + mon-projet / v1.3.0 : 2/5 (40%) -- echeance 2026-03-15 [jaune: dans 3j] + autre / v2.0.0 : 0/3 (0%) -- echeance 2026-03-01 [rouge: depassee] + lib / v0.5.0 : 8/10 (80%) -- echeance 2026-04-01 [vert: dans 20j] + ``` + +2. Tri par issues : + ``` + $ gitea-dashboard --sort issues + # Repos ordonnes par nombre d'issues decroissant + ``` + +3. Tri par activite : + ``` + $ gitea-dashboard --sort activity + # Repos ordonnes par date du dernier commit (plus recent d'abord) + ``` + +4. Sans `--sort`, le tri par defaut est par nom (retrocompatible avec v1.1.0 si l'API retournait dans un ordre aleatoire, desormais garanti alphabetique). + +### Tests + +#### test_display.py (ajouts) + +- `test_colorize_milestone_overdue` : echeance passee -> style rouge +- `test_colorize_milestone_soon` : echeance dans 3 jours -> style jaune +- `test_colorize_milestone_ok` : echeance dans 15 jours -> style vert +- `test_colorize_milestone_no_due` : pas d'echeance -> pas de style +- `test_sort_repos_by_name` : tri alphabetique +- `test_sort_repos_by_issues` : tri decroissant par issues +- `test_sort_repos_by_release` : tri par date release (repos sans release en dernier) +- `test_sort_repos_by_activity` : tri par date commit (repos sans commit en dernier) + +#### test_cli.py (ajouts) + +- `test_parse_args_sort_default` : sans `--sort` -> `Namespace(sort="name")` +- `test_parse_args_sort_issues` : `--sort issues` -> `Namespace(sort="issues")` +- `test_parse_args_sort_invalid` : `--sort invalid` -> erreur argparse + +### Livrable + +Les milestones sont colorees selon l'echeance. Les repos sont triables par `--sort`. La retrocompatibilite est preservee (defaut : tri par nom). Tous les tests passent. + +--- + +## Phase 3 : Export JSON (#6) + +**Goal** : Permettre l'export du dashboard complet en format JSON sur stdout. + +**Issues Gitea** : fixes #6 + +### Fichiers + +| Action | Fichier | Modifications | Cross-references | +|--------|---------|---------------|------------------| +| Create | `src/gitea_dashboard/exporter.py` | Nouveau module : serialisation des RepoData en dict/JSON | `collector.py` (consomme RepoData) | +| Modify | `src/gitea_dashboard/cli.py` | Ajouter option `--format` (choices: table, json), router vers exporter ou display | `exporter.py` (nouveau), `display.py` (existant) | +| Create | `tests/test_exporter.py` | Tests du module exporter | `exporter.py` | +| Modify | `tests/test_cli.py` | Tests parsing `--format`, tests integration export JSON | `cli.py` | + +### Interfaces + +#### exporter.py (nouveau module) + +```python +"""Export des donnees du dashboard en formats structures.""" + +from __future__ import annotations + +import json + +from gitea_dashboard.collector import RepoData + + +def repos_to_dicts(repos: list[RepoData]) -> list[dict]: + """Convertit une liste de RepoData en liste de dicts serialisables. + + Chaque dict contient toutes les donnees du RepoData, + pret pour json.dumps(). + """ + +def export_json(repos: list[RepoData], indent: int = 2) -> str: + """Exporte les repos en JSON formate. + + Returns: + Chaine JSON indentee, prete pour stdout ou ecriture fichier. + """ +``` + +Pourquoi un nouveau module `exporter.py` plutot que dans `display.py` : l'export JSON n'est pas de l'affichage Rich. C'est une serialisation de donnees. Melanger les deux violerait la separation des responsabilites. De plus, `exporter.py` pourra accueillir d'autres formats (CSV, YAML) dans le futur sans polluer `display.py`. + +Pourquoi cela ne viole pas ADR-002 (4 modules max) : ADR-002 definissait un maximum pour la v1.0.0. Le projet grandit avec de nouvelles fonctionnalites. 5 modules restent raisonnables (chacun a une responsabilite unique). Un ADR-006 est ajoute pour documenter cette evolution. + +#### cli.py (modifications) + +```python +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + """Parse les arguments CLI. + + Options existantes : --repo, --exclude, --sort + Nouvelle option : + --format / -f : format de sortie (table, json) + defaut: table + """ +``` + +### Comportement attendu + +1. Export JSON : + ``` + $ gitea-dashboard --format json + [ + { + "name": "mon-projet", + "full_name": "admin/mon-projet", + "description": "...", + "open_issues": 3, + "is_fork": false, + "is_archived": false, + "is_mirror": false, + "latest_release": {"tag_name": "v1.0.0", "published_at": "..."}, + "milestones": [...], + "last_commit_date": "2026-03-10T14:30:00Z" + } + ] + ``` + +2. Le JSON est ecrit sur stdout, les erreurs sur stderr (Console(stderr=True) deja en place). + +3. Les options `--repo`, `--exclude`, `--sort` sont compatibles avec `--format json` : + ``` + $ gitea-dashboard --repo dashboard --sort issues --format json + # Export JSON filtre et trie + ``` + +4. Format table par defaut (retrocompatible) : + ``` + $ gitea-dashboard + # Comportement identique a v1.1.0 (tableau Rich) + ``` + +### Tests + +#### test_exporter.py (nouveau) + +- `test_repos_to_dicts_basic` : conversion RepoData -> dict +- `test_repos_to_dicts_empty` : liste vide -> liste vide +- `test_repos_to_dicts_preserves_all_fields` : tous les champs sont presents +- `test_export_json_valid` : le resultat est du JSON valide (json.loads ne leve pas) +- `test_export_json_indent` : le JSON est indente par defaut + +#### test_cli.py (ajouts) + +- `test_parse_args_format_default` : sans `--format` -> `Namespace(format="table")` +- `test_parse_args_format_json` : `--format json` -> `Namespace(format="json")` +- `test_main_format_json_outputs_json` : verifie que stdout contient du JSON valide + +### Livrable + +L'option `--format json` exporte toutes les donnees du dashboard en JSON sur stdout. Compatible avec le filtrage et le tri. Le format table reste le defaut. Tous les tests passent. + +--- + +## Architecture des modules (impact v1.2.0) + +``` + gitea-dashboard v1.2.0 + ===================== + + Terminal Application Gitea API + -------- ----------- --------- + + +------------------+ + $ gitea-dashboard | cli.py | + --format json | - parse args | + --sort issues | - route format | + | - gere erreurs | + +--------+---------+ + | + v + +------------------+ + | collector.py | + | - orchestre la | + | collecte | +------------------+ + | - agrege en |---->| client.py | + | RepoData | | - retry timeout |-----> GET /api/v1/user/repos + +--------+---------+ | - auth token |-----> GET .../releases/latest + | | - pagination |-----> GET .../milestones + +------+------+ +------------------+-----> GET .../commits?limit=1 + | | + v v + +------------+ +-------------+ + | display.py | | exporter.py | + | - tableau | | - JSON | + <------------| - tri | | - stdout |----------> stdout (JSON) + Output Rich | - couleurs | +-------------+ + (tableaux) +------------+ +``` + +| Module | Impact | Detail | +|--------|--------|--------| +| `cli.py` | Modifie | Options `--sort`, `--format`, routage vers display ou exporter | +| `client.py` | Modifie | Retry sur timeout, nouvelle methode `get_latest_commit()` | +| `collector.py` | Modifie | Nouveau champ `last_commit_date` dans `RepoData` | +| `display.py` | Modifie | Colonne "Dernier commit", tri, coloration milestones | +| `exporter.py` | Nouveau | Serialisation JSON des RepoData | + +--- + +## Decisions architecturales + +### ADR-006 : Ajout du module exporter.py (v1.2.0) + +**Contexte** : L'export JSON est une nouvelle responsabilite. L'ajouter a `display.py` melangerait serialisation structuree et formatage Rich. ADR-002 limitait a 4 modules pour v1.0.0. + +**Decision** : Creer `exporter.py` pour la serialisation des donnees (JSON, et futurs formats). Le projet passe a 5 modules. + +**Consequences** : +- Separation claire : `display.py` = rendu terminal, `exporter.py` = serialisation donnees +- ADR-002 est relaxe (4 -> 5 modules), pas invalide (le principe "un module = une responsabilite" reste) +- Le module est independant de Rich (pas de dependance supplementaire) +- Extensible pour CSV/YAML sans modifier l'existant + +### ADR-007 : Retry simple plutot que urllib3.Retry (v1.2.0) + +**Contexte** : Les timeouts API causent un crash. Deux strategies : configurer `HTTPAdapter` avec `urllib3.Retry`, ou implementer un retry manuel dans le client. + +**Decision** : Retry manuel (boucle + time.sleep) dans `GiteaClient._get_with_retry()`. Maximum 2 retries, backoff lineaire (1s, 2s). + +**Consequences** : +- Code explicite et testable (mock de `time.sleep`) +- Pas de dependance sur l'API interne de urllib3 +- Applicable a tous les appels HTTP du client de maniere uniforme +- Limite : pas d'exponential backoff (acceptable pour un outil CLI local) + +### ADR-008 : Tri dans display.py, pas dans collector.py (v1.2.0) + +**Contexte** : Le tri des repos peut etre place dans le collecteur (donnees ordonnees) ou dans l'affichage (presentation ordonnee). + +**Decision** : Le tri est dans `display.py`. Le collecteur retourne les donnees dans l'ordre de l'API. L'affichage decide de l'ordre de presentation. + +**Consequences** : +- Le collecteur reste un simple agregateur de donnees (SRP) +- Le tri est teste independamment de la collecte +- L'export JSON peut aussi appliquer le tri (via `_sort_repos` reutilisable) +- Le critere de tri par defaut ("name") garantit un affichage stable entre les executions + +--- + +## Risques d'audit + +| Zone | Risque | Severite estimee | +|------|--------|-----------------| +| `client.py` — retry | `time.sleep` dans les tests ralentit l'execution si non mocke | minor | +| `client.py` — retry | Le retry masque des erreurs reseau persistantes (l'utilisateur attend plus longtemps avant le message d'erreur) | minor | +| `client.py` — `get_latest_commit` | L'endpoint `/commits?limit=1` peut ne pas exister sur d'anciennes versions de Gitea | major | +| `collector.py` — N+1 | Ajout d'un appel API supplementaire par repo (`get_latest_commit`) aggrave le temps de reponse | minor | +| `display.py` — coloration | Le calcul de la proximite d'echeance depend de `datetime.now()`, difficile a tester sans freeze | minor | +| `display.py` — tri | Le tri par "release" sur des repos sans release necessite une valeur sentinelle pour la date | minor | +| `exporter.py` — serialisation | `dataclasses.asdict` peut echouer si des champs contiennent des objets non serialisables | minor | +| `cli.py` — retrocompatibilite | Les nouveaux parametres de `render_dashboard()` doivent avoir des valeurs par defaut | major | + +--- + +## Issues Gitea rattachees + +| Issue | Titre | Phase | +|-------|-------|-------| +| [#8](https://gitea.tsmse.fr/admin/gitea-dashboard/issues/8) | Crash sur timeout API sans message clair | Phase 1 | +| [#7](https://gitea.tsmse.fr/admin/gitea-dashboard/issues/7) | Afficher la date du dernier commit par repo | Phase 1 | +| [#10](https://gitea.tsmse.fr/admin/gitea-dashboard/issues/10) | Coloration des milestones selon l'echeance | Phase 2 | +| [#9](https://gitea.tsmse.fr/admin/gitea-dashboard/issues/9) | Tri configurable des repos | Phase 2 | +| [#6](https://gitea.tsmse.fr/admin/gitea-dashboard/issues/6) | Export du dashboard en JSON | Phase 3 | + +--- + +## Dependances + +| Dependance | Type | Version | +|------------|------|---------| +| Python | Runtime | >= 3.10 | +| argparse | Stdlib | inclus dans Python | +| json | Stdlib | inclus dans Python | +| dataclasses | Stdlib | inclus dans Python | +| time | 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 | + +--- + +## Criteres de validation par issue + +| Issue | Critere de validation | +|-------|----------------------| +| #6 | `gitea-dashboard --format json` produit du JSON valide sur stdout contenant tous les champs de RepoData. Compatible avec `--repo`, `--exclude`, `--sort`. | +| #7 | Le tableau affiche une colonne "Dernier commit" avec la date relative. Les repos sans commit affichent "---". | +| #8 | Un timeout API unique ne fait pas crasher le dashboard (retry transparent). Apres 3 echecs, le message d'erreur est clair et sans token expose. | +| #9 | `--sort name\|issues\|release\|activity` trie les repos correctement. Le defaut (name) est retrocompatible. | +| #10 | Les milestones dont l'echeance est depassee sont en rouge, celles a moins de 7 jours en jaune, les autres en vert. Sans echeance : pas de couleur. | diff --git a/docs/technical/ARCHITECTURE.md b/docs/technical/ARCHITECTURE.md index ac800ea..146fc2e 100644 --- a/docs/technical/ARCHITECTURE.md +++ b/docs/technical/ARCHITECTURE.md @@ -100,6 +100,8 @@ gitea-dashboard/ docs/ plans/ v1.0.0-plan.md # Plan de version + v1.1.0-plan.md # Plan de version + v1.2.0-plan.md # Plan de version technical/ ARCHITECTURE.md # Ce fichier decisions.md # ADR @@ -143,3 +145,12 @@ Decisions cles pour v1.0.0 : - **ADR-001** : Stack Python + requests + rich - **ADR-002** : 4 modules maximum (client, collector, display, cli) - **ADR-003** : Pas de parallelisation en v1 (sequentiel, plus simple a deboguer) + +Decisions cles pour v1.1.0 : +- **ADR-004** : Argparse pour le parsing CLI +- **ADR-005** : Filtrage par sous-chaine dans le collecteur + +Decisions cles pour v1.2.0 : +- **ADR-006** : Ajout du module exporter.py (5 modules) +- **ADR-007** : Retry simple plutot que urllib3.Retry +- **ADR-008** : Tri dans display.py, pas dans collector.py diff --git a/docs/technical/decisions.md b/docs/technical/decisions.md index fb02428..06e7f80 100644 --- a/docs/technical/decisions.md +++ b/docs/technical/decisions.md @@ -78,3 +78,48 @@ - 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 + +## ADR-006 : Ajout du module exporter.py (v1.2.0) + +**Date** : 2026-03-12 +**Statut** : accepte + +**Contexte** : L'export JSON est une nouvelle responsabilite. L'ajouter a `display.py` melangerait serialisation structuree et formatage Rich. ADR-002 limitait a 4 modules pour v1.0.0. + +**Decision** : Creer `exporter.py` pour la serialisation des donnees (JSON, et futurs formats). Le projet passe a 5 modules. + +**Consequences** : +- Separation claire : `display.py` = rendu terminal, `exporter.py` = serialisation donnees +- ADR-002 est relaxe (4 -> 5 modules), pas invalide (le principe "un module = une responsabilite" reste) +- Le module est independant de Rich (pas de dependance supplementaire) +- Extensible pour CSV/YAML sans modifier l'existant + +## ADR-007 : Retry simple plutot que urllib3.Retry (v1.2.0) + +**Date** : 2026-03-12 +**Statut** : accepte + +**Contexte** : Les timeouts API causent un crash. Deux strategies : configurer `HTTPAdapter` avec `urllib3.Retry`, ou implementer un retry manuel dans le client. + +**Decision** : Retry manuel (boucle + time.sleep) dans `GiteaClient._get_with_retry()`. Maximum 2 retries, backoff lineaire (1s, 2s). + +**Consequences** : +- Code explicite et testable (mock de `time.sleep`) +- Pas de dependance sur l'API interne de urllib3 +- Applicable a tous les appels HTTP du client de maniere uniforme +- Limite : pas d'exponential backoff (acceptable pour un outil CLI local) + +## ADR-008 : Tri dans display.py, pas dans collector.py (v1.2.0) + +**Date** : 2026-03-12 +**Statut** : accepte + +**Contexte** : Le tri des repos peut etre place dans le collecteur (donnees ordonnees) ou dans l'affichage (presentation ordonnee). + +**Decision** : Le tri est dans `display.py`. Le collecteur retourne les donnees dans l'ordre de l'API. L'affichage decide de l'ordre de presentation. + +**Consequences** : +- Le collecteur reste un simple agregateur de donnees (SRP) +- Le tri est teste independamment de la collecte +- L'export JSON peut aussi appliquer le tri (via `_sort_repos` importable depuis display) +- Le critere de tri par defaut ("name") garantit un affichage stable entre les executions