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

@ -2,7 +2,7 @@ import os
import json
import asyncio
from datetime import datetime, timezone
from typing import Dict, Any, List
from typing import Dict, Any, List, Optional
import aiohttp
import disnake
@ -15,14 +15,39 @@ GITLAB_PROJECT_ID = os.getenv("GITLAB_PROJECT_ID") # numeric ID or URL-encoded
if not GITLAB_TOKEN or not GITLAB_PROJECT_ID:
raise RuntimeError("GitLab token and project ID must be set in environment variables GITLAB_TOKEN and GITLAB_PROJECT_ID")
# File to persist issue tracking information
# Files to persist issue tracking information
ISSUE_TRACK_FILE = os.path.join(os.path.dirname(__file__), "gitlab_issues.json")
LEGACY_REPORTS_FILE = os.path.join(os.path.dirname(__file__), "..", "data", "gitlab_reports.json")
def load_issue_tracking() -> Dict[str, Any]:
"""Charge le tracking et fusionne automatiquement avec gitlab_reports.json (migration)"""
tracking = {}
# Charge le fichier principal
if os.path.exists(ISSUE_TRACK_FILE):
with open(ISSUE_TRACK_FILE, "r", encoding="utf-8") as f:
return json.load(f)
return {}
try:
with open(ISSUE_TRACK_FILE, "r", encoding="utf-8") as f:
tracking = json.load(f)
except Exception as e:
print(f"⚠️ Erreur chargement gitlab_issues.json: {e}")
# Migration automatique depuis l'ancien système
if os.path.exists(LEGACY_REPORTS_FILE):
try:
with open(LEGACY_REPORTS_FILE, "r", encoding="utf-8") as f:
legacy_data = json.load(f)
for issue_iid, user_id in legacy_data.items():
if issue_iid not in tracking:
tracking[issue_iid] = {
"requester_id": user_id,
"last_note_id": 0,
"type": "legacy_report"
}
print(f"🔄 Migration automatique: {len(legacy_data)} issues fusionnées depuis gitlab_reports.json")
except Exception as e:
print(f"⚠️ Erreur migration gitlab_reports.json: {e}")
return tracking
def save_issue_tracking(data: Dict[str, Any]) -> None:
with open(ISSUE_TRACK_FILE, "w", encoding="utf-8") as f:
@ -39,8 +64,8 @@ class GitLabIntegration(commands.Cog):
def cog_unload(self):
self.check_new_notes.cancel()
async def _create_gitlab_issue(self, title: str, description: str, labels: List[str]) -> Dict[str, Any]:
"""Create an issue on GitLab and return the JSON response."""
async def _create_gitlab_issue(self, title: str, description: str, labels: List[str], requester_id: int = None) -> Optional[Dict[str, Any]]:
"""Crée une issue sur GitLab et la track automatiquement."""
url = f"https://gitlab.com/api/v4/projects/{GITLAB_PROJECT_ID}/issues"
headers = {
"PRIVATE-TOKEN": GITLAB_TOKEN,
@ -51,10 +76,24 @@ class GitLabIntegration(commands.Cog):
"description": description,
"labels": ",".join(labels),
}
async with aiohttp.ClientSession() as session:
async with session.post(url, headers=headers, json=payload) as resp:
resp.raise_for_status()
return await resp.json()
try:
async with aiohttp.ClientSession() as session:
async with session.post(url, headers=headers, json=payload) as resp:
resp.raise_for_status()
result = await resp.json()
# Track automatiquement la nouvelle issue
if requester_id:
issue_iid = str(result["iid"])
self.issue_data[issue_iid] = {
"requester_id": requester_id,
"last_note_id": 0,
"type": "discord-command"
}
save_issue_tracking(self.issue_data)
return result
except Exception as e:
print(f"❌ Erreur création issue GitLab: {e}")
return None
async def _get_issue_notes(self, issue_iid: int) -> List[Dict[str, Any]]:
"""Fetch all notes (comments) for a given issue."""
@ -85,41 +124,61 @@ class GitLabIntegration(commands.Cog):
@tasks.loop(minutes=1)
async def check_new_notes(self):
"""Periodic task that checks for new notes on tracked issues and notifies requesters."""
"""Vérifie TOUS les nouveaux commentaires (pas seulement le dernier) et notifie."""
for issue_iid_str, info in list(self.issue_data.items()):
issue_iid = int(issue_iid_str)
last_known_id = info.get("last_note_id", 0)
requester_id = info.get("requester_id")
try:
notes = await self._get_issue_notes(issue_iid)
if not notes:
continue
# ⚡ NOUVEAU: Notifie TOUS les commentaires manquants, pas seulement le dernier
missing_notes = [n for n in notes if n["id"] > last_known_id]
if missing_notes:
for note in missing_notes:
await self._notify_requester(requester_id, issue_iid, note)
# Met à jour avec le dernier ID vu
new_last_id = max(n["id"] for n in notes)
self.issue_data[issue_iid_str]["last_note_id"] = new_last_id
except Exception as e:
print(f"⚠️ Erreur vérification issue #{issue_iid}: {e}")
continue
save_issue_tracking(self.issue_data)
@commands.slash_command(name="recuperer_suivi_issues", description="Récupère le suivi des commentaires après un incident")
async def recuperer_suivi(self, inter: disnake.ApplicationCommandInteraction):
"""Reconstruit gitlab_issues.json avec les derniers commentaires."""
await inter.response.defer(ephemeral=True)
updated_count = 0
for issue_iid_str, info in list(self.issue_data.items()):
issue_iid = int(issue_iid_str)
try:
notes = await self._get_issue_notes(issue_iid)
except Exception as exc:
# Skip this issue if we cannot fetch notes.
if notes:
latest_id = max(n["id"] for n in notes)
if latest_id > info.get("last_note_id", 0):
self.issue_data[issue_iid_str]["last_note_id"] = latest_id
updated_count += 1
except Exception as e:
print(f"⚠️ Erreur récupération issue #{issue_iid}: {e}")
continue
# Find the newest note ID
if not notes:
continue
latest_note = max(notes, key=lambda n: n["id"])
last_known_id = info.get("last_note_id", 0)
if latest_note["id"] > last_known_id:
# New comment detected
await self._notify_requester(info["requester_id"], issue_iid, latest_note)
# Update stored last_note_id
self.issue_data[issue_iid_str]["last_note_id"] = latest_note["id"]
save_issue_tracking(self.issue_data)
await inter.edit_original_message(
content=f"✅ Suivi récupéré pour **{updated_count}/{len(self.issue_data)}** issues. "
f"Fichier `{ISSUE_TRACK_FILE}` mis à jour."
)
# ---------- Slash command: /suggerer_fonctionnalite ----------
# ---------- Slash commands ----------
@commands.slash_command(name="suggerer_fonctionnalite", description="Suggérer une fonctionnalité pour le projet")
async def suggere_fonctionnalite(self, inter: disnake.ApplicationCommandInteraction):
await inter.response.send_modal(modal=FeatureModal(self))
# ---------- Slash command: /signaler_bug ----------
@commands.slash_command(name="notify_deploy", description="Envoyer un DM de confirmation du déploiement")
async def notify_deploy(self, inter: disnake.ApplicationCommandInteraction):
"""DM the user that the deployment is done."""
try:
await inter.author.send("✅ C'est fait ! Le code a été déployé sur la branche `main`.")
await inter.response.send_message("✅ DM de confirmation envoyé.", ephemeral=True)
except disnake.HTTPException:
await inter.response.send_message("⚠️ Impossible d'envoyer le DM (DMs désactivés).", ephemeral=True)
@commands.slash_command(name="signaler_bug", description="Signaler un bug")
async def signaler_bug(self, inter: disnake.ApplicationCommandInteraction):
await inter.response.send_modal(modal=BugModal(self))
@ -158,16 +217,14 @@ class FeatureModal(disnake.Modal):
title=title,
description=description,
labels=["feature", "discord-bot"],
requester_id=inter.author.id # ⚡ NOUVEAU: Passe le requester_id pour tracking auto
)
# Store tracking info
issue_iid = str(issue["iid"])
self.cog.issue_data[issue_iid] = {
"requester_id": inter.author.id,
"last_note_id": 0,
"type": "feature",
}
save_issue_tracking(self.cog.issue_data)
await inter.edit_original_message(content=f"✅ Fonctionnalité créée: [{title}](https://gitlab.com/{GITLAB_PROJECT_ID}/-/issues/{issue_iid})")
if issue:
await inter.edit_original_message(
content=f"✅ Fonctionnalité créée: [{title}](https://gitlab.com/{GITLAB_PROJECT_ID}/-/issues/{issue['iid']})"
)
else:
await inter.edit_original_message(content="❌ Erreur lors de la création de la fonctionnalité.")
except Exception as e:
await inter.edit_original_message(content=f"❌ Erreur lors de la création de la fonctionnalité: {e}")
@ -205,15 +262,14 @@ class BugModal(disnake.Modal):
title=title,
description=description,
labels=["bug", "discord-bot"],
requester_id=inter.author.id # ⚡ NOUVEAU: Passe le requester_id pour tracking auto
)
issue_iid = str(issue["iid"])
self.cog.issue_data[issue_iid] = {
"requester_id": inter.author.id,
"last_note_id": 0,
"type": "bug",
}
save_issue_tracking(self.cog.issue_data)
await inter.edit_original_message(content=f"✅ Bug créé: [{title}](https://gitlab.com/{GITLAB_PROJECT_ID}/-/issues/{issue_iid})")
if issue:
await inter.edit_original_message(
content=f"✅ Bug signalé: [{title}](https://gitlab.com/{GITLAB_PROJECT_ID}/-/issues/{issue['iid']})"
)
else:
await inter.edit_original_message(content="❌ Erreur lors du signalement du bug.")
except Exception as e:
await inter.edit_original_message(content=f"❌ Erreur lors du signalement du bug: {e}")