Fix: récupération des issues_gitlab après incident

This commit is contained in:
Mathis 2026-06-04 08:40:14 +02:00
parent ed6e25c219
commit b6d2995916
8 changed files with 682 additions and 308 deletions

View file

@ -0,0 +1,247 @@
#!/usr/bin/env python3
"""
Script de récupération des commentaires GitLab pour le bot Kuby.
Ce script permet de :
1. Récupérer toutes les issues suivies depuis gitlab_issues.json et data/gitlab_reports.json
2. Pour chaque issue, récupérer TOUS les commentaires via l'API GitLab
3. Reconstruire gitlab_issues.json avec les bons last_note_id
4. Afficher un rapport des commentaires manquants
Usage:
python scripts/recover_gitlab_comments.py
Requires:
- GITLAB_TOKEN (env var)
- GITLAB_PROJECT_ID (env var)
- aiohttp
"""
import os
import json
import asyncio
import sys
from datetime import datetime
from typing import Dict, List, Any, Optional
# Chemin des fichiers de tracking
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
GITLAB_ISSUES_FILE = os.path.join(BASE_DIR, "commandes", "gitlab_issues.json")
GITLAB_REPORTS_FILE = os.path.join(BASE_DIR, "data", "gitlab_reports.json")
# Configuration GitLab
GITLAB_TOKEN = os.getenv("GITLAB_TOKEN")
GITLAB_PROJECT_ID = os.getenv("GITLAB_PROJECT_ID")
if not GITLAB_TOKEN or not GITLAB_PROJECT_ID:
print("❌ ERREUR : Les variables d'environnement GITLAB_TOKEN et GITLAB_PROJECT_ID doivent être définies.")
sys.exit(1)
GITLAB_API_URL = f"https://gitlab.com/api/v4/projects/{GITLAB_PROJECT_ID}"
async def fetch_all_notes(issue_iid: int) -> List[Dict[str, Any]]:
"""Récupère tous les commentaires d'une issue via l'API GitLab."""
url = f"{GITLAB_API_URL}/issues/{issue_iid}/notes?per_page=100"
headers = {"PRIVATE-TOKEN": GITLAB_TOKEN}
all_notes = []
page = 1
while True:
try:
import aiohttp
async with aiohttp.ClientSession() as session:
async with session.get(f"{url}&page={page}", headers=headers) as resp:
if resp.status != 200:
print(f" ⚠️ Erreur API pour l'issue #{issue_iid} (page {page}): {resp.status}")
break
notes = await resp.json()
if not notes:
break
all_notes.extend(notes)
# Vérifie s'il y a plus de pages
if len(notes) < 100:
break
page += 1
except Exception as e:
print(f" ❌ Erreur lors de la récupération des notes pour l'issue #{issue_iid}: {e}")
break
return all_notes
def load_existing_tracking() -> Dict[str, Any]:
"""Charge les données de suivi existantes."""
tracking_data = {}
# Charge gitlab_issues.json
if os.path.exists(GITLAB_ISSUES_FILE):
try:
with open(GITLAB_ISSUES_FILE, "r", encoding="utf-8") as f:
tracking_data = json.load(f)
print(f"✅ Chargé {len(tracking_data)} issues depuis gitlab_issues.json")
except Exception as e:
print(f"⚠️ Erreur lors du chargement de gitlab_issues.json: {e}")
# Charge gitlab_reports.json et fusionne
if os.path.exists(GITLAB_REPORTS_FILE):
try:
with open(GITLAB_REPORTS_FILE, "r", encoding="utf-8") as f:
reports_data = json.load(f)
for issue_iid, user_id in reports_data.items():
if issue_iid not in tracking_data:
tracking_data[issue_iid] = {
"requester_id": user_id,
"last_note_id": 0,
"type": "manual-report"
}
print(f" + Ajout de l'issue #{issue_iid} depuis gitlab_reports.json")
print(f"✅ Fusionné {len(reports_data)} issues depuis gitlab_reports.json")
except Exception as e:
print(f"⚠️ Erreur lors du chargement de gitlab_reports.json: {e}")
return tracking_data
def save_tracking_data(data: Dict[str, Any]) -> None:
"""Sauvegarde les données de suivi dans gitlab_issues.json."""
os.makedirs(os.path.dirname(GITLAB_ISSUES_FILE), exist_ok=True)
# Sauvegarde dans un fichier temporaire puis renomme
temp_file = f"{GITLAB_ISSUES_FILE}.tmp"
with open(temp_file, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
os.replace(temp_file, GITLAB_ISSUES_FILE)
print(f"✅ Sauvegardé {len(data)} issues dans gitlab_issues.json")
async def recover_comments():
"""Fonction principale de récupération."""
print("=" * 60)
print("🔍 RÉCUPÉRATION DES COMMENTAIRES GITLAB")
print("=" * 60)
# Charge les données existantes
print("\n📂 Chargement des données de suivi existantes...")
tracking_data = load_existing_tracking()
if not tracking_data:
print("❌ Aucune issue à suivre trouvée. Vérifiez que les fichiers existent.")
return
print(f"\n🔄 Traitement de {len(tracking_data)} issues...")
print("-" * 60)
issues_with_new_comments = []
total_notes = 0
total_missing = 0
for issue_iid_str, info in tracking_data.items():
issue_iid = int(issue_iid_str)
last_known_id = info.get("last_note_id", 0)
requester_id = info.get("requester_id", "Inconnu")
print(f"\n📝 Issue #{issue_iid} (demandeur: {requester_id})")
print(f" Dernier note_id connu: {last_known_id}")
# Récupère tous les commentaires
notes = await fetch_all_notes(issue_iid)
if not notes:
print(f" ⚠️ Aucune note trouvée pour cette issue.")
continue
# Trie les notes par ID
notes.sort(key=lambda n: n["id"])
latest_note_id = max(n["id"] for n in notes)
total_notes += len(notes)
print(f"{len(notes)} commentaires trouvés (dernier ID: {latest_note_id})")
# Vérifie s'il y a des commentaires manquants
if last_known_id == 0:
# Premier lancement ou fichier réinitialisé
missing_count = len(notes)
total_missing += missing_count
print(f" ⚠️ {missing_count} commentaires NON SUIVIS (fichier réinitialisé)")
issues_with_new_comments.append({
"issue_iid": issue_iid,
"missing_notes": notes,
"requester_id": requester_id,
"new_last_note_id": latest_note_id
})
else:
# Vérifie quels commentaires sont nouveaux
missing_notes = [n for n in notes if n["id"] > last_known_id]
if missing_notes:
missing_count = len(missing_notes)
total_missing += missing_count
print(f" ⚠️ {missing_count} NOUVEAUX commentaires détectés")
issues_with_new_comments.append({
"issue_iid": issue_iid,
"missing_notes": missing_notes,
"requester_id": requester_id,
"new_last_note_id": latest_note_id
})
else:
print(f" ✅ Aucun nouveau commentaire")
# Met à jour le last_note_id
tracking_data[issue_iid_str]["last_note_id"] = latest_note_id
# Sauvegarde les données mises à jour
print("\n" + "=" * 60)
print("💾 SAUVEGARDE DES DONNÉES")
print("=" * 60)
save_tracking_data(tracking_data)
# Génère le rapport
print("\n" + "=" * 60)
print("📊 RAPPORT DE RÉCUPÉRATION")
print("=" * 60)
print(f"Total issues traitées: {len(tracking_data)}")
print(f"Total commentaires récupérés: {total_notes}")
print(f"Total commentaires manquants: {total_missing}")
if issues_with_new_comments:
print(f"\n📋 Issues avec des commentaires non suivis ({len(issues_with_new_comments)}):")
for item in issues_with_new_comments:
print(f"\n Issue #{item['issue_iid']}:")
print(f" Demandeur: {item['requester_id']}")
print(f" Nombre de commentaires manquants: {len(item['missing_notes'])}")
print(f" Nouveau last_note_id: {item['new_last_note_id']}")
# Affiche les commentaires manquants
for note in item["missing_notes"]:
author = note.get("author", {}).get("name", "Inconnu")
created_at = note.get("created_at", "Inconnu")
body = note.get("body", "")[:100] + "..." if len(note.get("body", "")) > 100 else note.get("body", "")
print(f" - [{created_at}] {author}: {body}")
else:
print("\n✅ Aucune issue avec des commentaires manquants !")
print("\n" + "=" * 60)
print("✅ RÉCUPÉRATION TERMINÉE")
print("=" * 60)
print("\nProchaines étapes:")
print("1. Vérifiez le fichier commandes/gitlab_issues.json")
print("2. Redémarrez le bot pour qu'il utilise les nouvelles données")
print("3. Les nouveaux commentaires seront désormais suivis correctement")
if __name__ == "__main__":
print("\n" + "=" * 60)
print("SCRIPT DE RÉCUPÉRATION DES COMMENTAIRES GITLAB")
print("=" * 60)
print(f"Projet GitLab: {GITLAB_PROJECT_ID}")
print(f"Fichier de sortie: {GITLAB_ISSUES_FILE}")
print("=" * 60 + "\n")
asyncio.run(recover_comments())