From b23c0dde530b6b7e58625a8a8738d572500c5754 Mon Sep 17 00:00:00 2001 From: Mathis Date: Sun, 12 Jul 2026 12:26:01 +0200 Subject: [PATCH] =?UTF-8?q?Am=C3=A9lioration=20du=20suivi=20des=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- commandes/bug_report.py | 113 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 3 deletions(-) diff --git a/commandes/bug_report.py b/commandes/bug_report.py index b9ccf90..9c2f42a 100644 --- a/commandes/bug_report.py +++ b/commandes/bug_report.py @@ -58,6 +58,93 @@ def extract_issue_identifier(issue_payload: Optional[Dict[str, Any]]) -> Optiona return None +def normalize_labels(labels: Any) -> list[str]: + """Normalise une liste de labels provenant de Forgejo/GitLab.""" + if not labels: + return [] + if isinstance(labels, str): + return [labels] + if isinstance(labels, list): + normalized = [] + for item in labels: + if isinstance(item, dict): + name = item.get("name") or item.get("title") or item.get("label") or item.get("id") + else: + name = item + if name is None: + continue + name = str(name).strip() + if name and name not in normalized: + normalized.append(name) + return normalized + return [] + + +def extract_label_change(payload: Dict[str, Any]) -> Optional[Dict[str, Any]]: + """Extrait le changement de labels d’un payload d’issue.""" + changes = payload.get("changes") or {} + labels_change = changes.get("labels") or changes.get("label") + if not isinstance(labels_change, dict): + return None + + previous = normalize_labels(labels_change.get("previous")) + current = normalize_labels(labels_change.get("current")) + if not previous and not current: + return None + + added = [label for label in current if label not in previous] + removed = [label for label in previous if label not in current] + if not added and not removed: + return None + + return { + "previous": previous, + "current": current, + "added": added, + "removed": removed, + } + + +def calculate_progress_from_labels(labels: list[str]) -> float: + """Estime une progression visuelle à partir des labels présents.""" + if not labels: + return 0.1 + + normalized = [label.lower() for label in labels] + if any(token in normalized for token in ["done", "resolved", "fixed", "closed", "terminé", "résolu", "ok"]): + return 1.0 + if any(token in normalized for token in ["review", "ready", "qa", "testing", "test", "validated", "validation"]): + return 0.8 + if any(token in normalized for token in ["in progress", "working", "wip", "develop", "dev", "investigating", "progress"]): + return 0.6 + if any(token in normalized for token in ["confirmed", "accepted", "triaged", "priority", "urgent"]): + return 0.4 + if any(token in normalized for token in ["bug", "enhancement", "feature", "manual", "new"]): + return 0.2 + return 0.3 + + +def build_progress_bar(progress: float, width: int = 18) -> str: + """Construit une barre de progression stylée en Unicode.""" + progress = max(0.0, min(1.0, progress)) + filled = int(round(progress * width)) + filled = max(0, min(width, filled)) + empty = width - filled + percent = int(progress * 100) + return f"{'▰' * filled}{'▱' * empty} {percent}%" + + +def color_from_progress(progress: float) -> disnake.Color: + """Retourne une couleur violet→bleu selon la progression.""" + progress = max(0.0, min(1.0, progress)) + purple = (117, 82, 255) + blue = (74, 144, 255) + r = int(purple[0] + (blue[0] - purple[0]) * progress) + g = int(purple[1] + (blue[1] - purple[1]) * progress) + b = int(purple[2] + (blue[2] - purple[2]) * progress) + return disnake.Color.from_rgb(r, g, b) + + def load_tracking(): """Charge le tracking depuis gitlab_issues.json (avec migration automatique)""" tracking = {} @@ -397,12 +484,32 @@ class BugReport(commands.Cog): pass elif event_type == "issue": - # 🕵️ 5. GESTION DES CLÔTURES (Optionnel : si tu fermes le ticket sur Forgejo) action = payload.get("object_attributes", {}).get("action") or payload.get("action") state = payload.get("object_attributes", {}).get("state") or payload.get("issue", {}).get("state") - + label_change = extract_label_change(payload) + + if label_change: + current_labels = label_change["current"] + progress = calculate_progress_from_labels(current_labels) + bar = build_progress_bar(progress) + added = ", ".join(label_change["added"]) if label_change["added"] else "aucun" + removed = ", ".join(label_change["removed"]) if label_change["removed"] else "aucun" + try: + embed = disnake.Embed( + title="📈 Évolution du ticket", + description=f"Le suivi de **{issue_title}** a été mis à jour après un changement de labels.", + color=color_from_progress(progress), + ) + embed.add_field(name="Progression", value=bar, inline=False) + embed.add_field(name="Labels ajoutés", value=added if added != "aucun" else "Aucun", inline=True) + embed.add_field(name="Labels retirés", value=removed if removed != "aucun" else "Aucun", inline=True) + embed.set_thumbnail(url=self.bot.user.display_avatar.url) + await user.send(embed=embed) + kuby_logger.info(f"Notification de progression envoyée à {user} pour l'issue #{issue_iid}") + except Exception as e: + kuby_logger.error(f"Erreur notification progression: {e}") + is_closing_now = action in ["close", "closed"] or state == "closed" - if is_closing_now: try: embed = disnake.Embed(