diff --git a/commandes/bug_report.py b/commandes/bug_report.py index 06f931f..cf4626c 100644 --- a/commandes/bug_report.py +++ b/commandes/bug_report.py @@ -6,6 +6,7 @@ import logging import json import os import asyncio +import re from typing import Dict, Any, Optional from utils.premium import check_premium_tier @@ -47,14 +48,37 @@ def extract_issue_iid(payload: Dict[str, Any]) -> Optional[str]: return None -def extract_issue_identifier(issue_payload: Optional[Dict[str, Any]]) -> Optional[str]: - """Récupère un identifiant stable à stocker dans le tracking.""" - if not isinstance(issue_payload, dict): +def extract_issue_identifier(issue_payload: Optional[Any]) -> Optional[str]: + """Récupère un identifiant stable à stocker dans le tracking, même si le payload est imbriqué.""" + if issue_payload is None: return None - for key in ("iid", "number", "id"): - value = issue_payload.get(key) - if value not in (None, ""): - return str(value) + + if isinstance(issue_payload, dict): + for key in ("iid", "number", "id", "issue_id"): + value = issue_payload.get(key) + if value not in (None, ""): + return str(value) + + for key in ("html_url", "web_url", "url"): + value = issue_payload.get(key) + if isinstance(value, str): + match = re.search(r"/(?:issues|tickets)/([0-9]+)", value) + if match: + return match.group(1) + + for value in issue_payload.values(): + found = extract_issue_identifier(value) + if found: + return found + + return None + + if isinstance(issue_payload, list): + for item in issue_payload: + found = extract_issue_identifier(item) + if found: + return found + return None @@ -187,6 +211,26 @@ def color_from_progress(progress: float) -> disnake.Color: return disnake.Color.from_rgb(r, g, b) +def normalize_tracking_issue_id(issue_iid: Any) -> Optional[str]: + """Normalise un identifiant d’issue pour le tracking, en ne conservant que les valeurs numériques valides.""" + if issue_iid is None: + return None + + if isinstance(issue_iid, int): + return str(issue_iid) + + if isinstance(issue_iid, str): + normalized = issue_iid.strip() + if not normalized: + return None + if normalized.lower() in {"none", "null", "nan", "undefined"}: + return None + if re.fullmatch(r"\d+", normalized): + return normalized + + return None + + def load_tracking(): """Charge le tracking depuis gitlab_issues.json (avec migration automatique)""" tracking = {} @@ -202,14 +246,23 @@ def load_tracking(): 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" - } - kuby_logger.info(f"Migration automatique: {len(legacy_data)} issues fusionnées") + + migrated_count = 0 + for raw_issue_iid, user_id in legacy_data.items(): + issue_iid = normalize_tracking_issue_id(raw_issue_iid) + if not issue_iid: + kuby_logger.debug(f"Ignoré pendant la migration de tracking: issue_id={raw_issue_iid}") + continue + if issue_iid in tracking: + continue + tracking[issue_iid] = { + "requester_id": user_id, + "last_note_id": 0, + "type": "legacy_report" + } + migrated_count += 1 + + kuby_logger.info(f"Migration automatique: {migrated_count} issue(s) fusionnée(s) depuis l'historique") except Exception as e: kuby_logger.error(f"Erreur migration gitlab_reports.json: {e}") @@ -277,7 +330,12 @@ class BugReportModal(disnake.ui.Modal): if result: issue_iid = extract_issue_identifier(result) if not issue_iid: - kuby_logger.warning("Impossible de déterminer l’ID de l’issue créée depuis le retour Forgejo/GitLab") + kuby_logger.warning("Impossible de déterminer l’ID de l’issue créée depuis le retour Forgejo/GitLab. Tentative de fallback par titre.") + lookup = await gitlab_client.find_issue_by_title(f"[MANUAL] {bug_title}", state="open") + issue_iid = extract_issue_identifier(lookup) + + if not issue_iid: + kuby_logger.warning("Impossible de déterminer l’ID de l’issue créée après fallback. Payload reçu: %s", result) await interaction.edit_original_response(content="✅ Votre rapport a été envoyé, mais l’identifiant de l’issue n’a pas pu être récupéré automatiquement.") return @@ -346,7 +404,12 @@ class FeatureSuggestionModal(disnake.ui.Modal): if result: issue_iid = extract_issue_identifier(result) if not issue_iid: - kuby_logger.warning("Impossible de déterminer l’ID de l’issue créée depuis le retour Forgejo/GitLab") + kuby_logger.warning("Impossible de déterminer l’ID de l’issue créée depuis le retour Forgejo/GitLab. Tentative de fallback par titre.") + lookup = await gitlab_client.find_issue_by_title(f"[SUGGESTION] {suggestion_title}", state="open") + issue_iid = extract_issue_identifier(lookup) + + if not issue_iid: + kuby_logger.warning("Impossible de déterminer l’ID de l’issue créée après fallback. Payload reçu: %s", result) await interaction.edit_original_response(content="✅ Votre suggestion a été envoyée, mais l’identifiant de l’issue n’a pas pu être récupéré automatiquement.") return