Cette mise a jour complete ameliore significativement la qualite et la maintenabilite du projet. 1. Extension de la couverture de tests Couverture globale passee de 8% a 16% (+100%) - Ajout de 25 nouveaux tests (total: 67 tests, 100% passent) - Nouveaux fichiers de tests: * tests/unit/test_gitea.py (17 tests) * tests/unit/test_fiches_tickets.py (8 tests) Etat de la couverture par module: - utils/gitea.py: 100% - utils/widgets.py: 100% - utils/logger.py: 94% - app/fiches/utils/tickets/core.py: 77% - utils/graph_utils.py: 59% 2. Documentation d'architecture complete Creation de 3 nouveaux documents (30 Ko total): - docs/ARCHITECTURE.md (15 Ko) * Architecture complete du projet * Flux de donnees detailles * Indices de vulnerabilite (IHH, ISG, ICS, IVC) * Structure du graphe NetworkX - docs/MODULES.md (15 Ko) * Guide des 11 modules principaux * Exemples de code (15+ snippets) * Bonnes pratiques * Guide de depannage - docs/README.md (4 Ko) * Index de toute la documentation Contenu documente: - 5 modules applicatifs - 6 modules utilitaires - 4 indices de vulnerabilite avec formules et seuils - Conventions de code 3. Reorganisation de la documentation Structure finale optimisee: - Racine: README.md (mis a jour) + Instructions.md - docs/: 11 documents organises par categorie Fichiers deplaces vers docs/: - README_connexion.md -> docs/CONNEXION.md - GUIDE_LOGS.md -> docs/ - GUIDE_RUFF.md -> docs/ - RAPPORT_RUFF.md -> docs/ - RAPPORT_CORRECTIONS_AUTO.md -> docs/ - REFACTORING_REPORT.md -> docs/ - VERIFICATION_LOGS.md -> docs/ - TODO_IA_BATCH.md -> docs/ 4. Ajout de docstrings 52 fonctions documentees en style Google (100%) Documentation en francais avec Args, Returns, Raises 5. Corrections automatiques Ruff Application de 347 corrections automatiques: - Formatage du code (line-length: 120) - Organisation des imports - Simplifications syntaxiques - Suppressions de code mort - Ameliorations de performance 6. Configuration qualite du code Nouveaux fichiers: - pyproject.toml: configuration Ruff complete - .vscode/settings.json: integration Ruff avec formatOnSave - GUIDE_RUFF.md: documentation du linter - GUIDE_LOGS.md: documentation du logging - .gitignore: ajout htmlcov/ pour rapports de couverture Etat final du projet: - Linter: Ruff configure (15 regles actives) - Tests: 67 tests (100% passent) - Couverture de code: 16% - Docstrings: 52/52 (100%) - Documentation: 11 fichiers organises Impact: - Tests plus robustes et maintenables - Documentation technique complete - Meilleure organisation des fichiers - Workflow optimise avec Ruff - Code pret pour integration continue References: - Architecture: docs/ARCHITECTURE.md - Guide modules: docs/MODULES.md - Tests: tests/unit/ - Configuration: pyproject.toml Co-Authored-By: Claude <noreply@anthropic.com>
178 lines
5.5 KiB
Python
178 lines
5.5 KiB
Python
# core.py
|
|
|
|
import csv
|
|
import json
|
|
import os
|
|
|
|
import requests
|
|
import streamlit as st
|
|
|
|
from config import DEPOT_FICHES, ENV, GITEA_TOKEN, GITEA_URL, ORGANISATION
|
|
from utils.translations import _
|
|
|
|
|
|
def gitea_request(method, url, **kwargs):
|
|
"""Execute une requete HTTP vers l'API Gitea avec authentification automatique.
|
|
|
|
Args:
|
|
method: Methode HTTP (get, post, patch, delete, etc.).
|
|
url: URL complete de l'endpoint API Gitea.
|
|
**kwargs: Arguments passes a requests.request (params, data, json, etc.).
|
|
|
|
Returns:
|
|
requests.Response | None: Objet Response si succes, None si erreur.
|
|
"""
|
|
headers = kwargs.pop("headers", {})
|
|
headers["Authorization"] = f"token {GITEA_TOKEN}"
|
|
try:
|
|
response = requests.request(method, url, headers=headers, timeout=10, **kwargs)
|
|
response.raise_for_status()
|
|
return response
|
|
except requests.RequestException as e:
|
|
st.error(f"{str(_('errors.gitea_error'))} ({method.upper()}): {e}")
|
|
return None
|
|
|
|
|
|
def charger_fiches_et_labels():
|
|
"""Charge la correspondance entre fiches et labels depuis le fichier CSV.
|
|
|
|
Lit le fichier assets/fiches_labels.csv et construit un dictionnaire associant
|
|
chaque fiche a ses labels (operations et item).
|
|
|
|
Returns:
|
|
dict: Dictionnaire au format {nom_fiche: {"operations": [str], "item": str}}.
|
|
Retourne un dict vide en cas d'erreur.
|
|
"""
|
|
chemin_csv = os.path.join("assets", "fiches_labels.csv")
|
|
dictionnaire_fiches = {}
|
|
|
|
try:
|
|
with open(chemin_csv, encoding="utf-8") as fichier_csv:
|
|
lecteur = csv.DictReader(fichier_csv)
|
|
for ligne in lecteur:
|
|
fiche = ligne.get("Fiche")
|
|
operations = ligne.get("Label opération")
|
|
item = ligne.get("Label item")
|
|
|
|
if fiche and operations and item:
|
|
dictionnaire_fiches[fiche.strip()] = {
|
|
"operations": [op.strip() for op in operations.split("/")],
|
|
"item": item.strip()
|
|
}
|
|
except FileNotFoundError:
|
|
st.error(f"❌ {str(_('errors.file_not_found'))} {chemin_csv} {str(_('errors.is_missing'))}")
|
|
except Exception as e:
|
|
st.error(f"❌ {str(_('errors.file_loading'))} {str(e)}")
|
|
|
|
return dictionnaire_fiches
|
|
|
|
|
|
def rechercher_tickets_gitea(fiche_selectionnee):
|
|
"""Recherche les tickets Gitea ouverts associes a une fiche specifique.
|
|
|
|
Filtre les issues ouvertes du depot DEPOT_FICHES par branche (ENV) et par
|
|
labels correspondant a la fiche selectionnee.
|
|
|
|
Args:
|
|
fiche_selectionnee: Nom de la fiche pour laquelle chercher les tickets.
|
|
|
|
Returns:
|
|
list[dict]: Liste des issues Gitea correspondantes (format JSON Gitea).
|
|
"""
|
|
params = {"state": "open"}
|
|
url = f"{GITEA_URL}/repos/{ORGANISATION}/{DEPOT_FICHES}/issues"
|
|
|
|
reponse = gitea_request("get", url, params=params)
|
|
if not reponse:
|
|
return []
|
|
|
|
try:
|
|
issues = reponse.json()
|
|
except Exception as e:
|
|
st.error(f"{str(_('errors.json_decode'))} {e}")
|
|
return []
|
|
|
|
correspondances = charger_fiches_et_labels()
|
|
cible = correspondances.get(fiche_selectionnee)
|
|
if not cible:
|
|
return []
|
|
|
|
labels_cibles = set([cible["item"]])
|
|
tickets_associes = []
|
|
|
|
for issue in issues:
|
|
if issue.get("ref") != f"refs/heads/{ENV}":
|
|
continue
|
|
issue_labels = set(label.get("name", "") for label in issue.get("labels", []))
|
|
if labels_cibles.issubset(issue_labels):
|
|
tickets_associes.append(issue)
|
|
|
|
return tickets_associes
|
|
|
|
|
|
def get_labels_existants():
|
|
"""Recupere tous les labels existants dans le depot Gitea.
|
|
|
|
Returns:
|
|
dict: Dictionnaire {nom_label: id_label}. Retourne un dict vide en cas d'erreur.
|
|
"""
|
|
url = f"{GITEA_URL}/repos/{ORGANISATION}/{DEPOT_FICHES}/labels"
|
|
reponse = gitea_request("get", url)
|
|
if not reponse:
|
|
return {}
|
|
|
|
try:
|
|
return {label['name']: label['id'] for label in reponse.json()}
|
|
except Exception as e:
|
|
st.error(f"{str(_('errors.label_parsing'))} {e}")
|
|
return {}
|
|
|
|
|
|
def nettoyer_labels(labels):
|
|
"""Nettoie et deduplique une liste de labels.
|
|
|
|
Args:
|
|
labels: Liste de labels (peut contenir des non-strings, espaces, doublons).
|
|
|
|
Returns:
|
|
list[str]: Liste triee de labels uniques et non vides.
|
|
"""
|
|
return sorted(set(l.strip() for l in labels if isinstance(l, str) and l.strip()))
|
|
|
|
|
|
def construire_corps_ticket_markdown(reponses):
|
|
"""Construit le corps markdown d'un ticket a partir des reponses utilisateur.
|
|
|
|
Args:
|
|
reponses: Dictionnaire {nom_section: texte_reponse}.
|
|
|
|
Returns:
|
|
str: Corps du ticket en format markdown avec sections de niveau 2.
|
|
"""
|
|
return "\n\n".join(f"## {section}\n{texte}" for section, texte in reponses.items())
|
|
|
|
|
|
def creer_ticket_gitea(titre, corps, labels):
|
|
"""Cree un nouveau ticket (issue) dans le depot Gitea.
|
|
|
|
Args:
|
|
titre: Titre du ticket.
|
|
corps: Corps du ticket en markdown.
|
|
labels: Liste d'IDs de labels a associer au ticket.
|
|
|
|
Returns:
|
|
bool: True si creation reussie, False sinon.
|
|
"""
|
|
data = {
|
|
"title": titre,
|
|
"body": corps,
|
|
"labels": labels,
|
|
"ref": f"refs/heads/{ENV}"
|
|
}
|
|
url = f"{GITEA_URL}/repos/{ORGANISATION}/{DEPOT_FICHES}/issues"
|
|
|
|
reponse = gitea_request("post", url, headers={"Content-Type": "application/json"}, data=json.dumps(data))
|
|
if not reponse:
|
|
return False
|
|
return True
|