Merge pull request 'Amélioration du suivi des issues' (#242) from dev into main
Reviewed-on: #242
This commit is contained in:
commit
84ccbee6b0
1 changed files with 110 additions and 3 deletions
|
|
@ -58,6 +58,93 @@ def extract_issue_identifier(issue_payload: Optional[Dict[str, Any]]) -> Optiona
|
||||||
return None
|
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():
|
def load_tracking():
|
||||||
"""Charge le tracking depuis gitlab_issues.json (avec migration automatique)"""
|
"""Charge le tracking depuis gitlab_issues.json (avec migration automatique)"""
|
||||||
tracking = {}
|
tracking = {}
|
||||||
|
|
@ -397,12 +484,32 @@ class BugReport(commands.Cog):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
elif event_type == "issue":
|
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")
|
action = payload.get("object_attributes", {}).get("action") or payload.get("action")
|
||||||
state = payload.get("object_attributes", {}).get("state") or payload.get("issue", {}).get("state")
|
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"
|
is_closing_now = action in ["close", "closed"] or state == "closed"
|
||||||
|
|
||||||
if is_closing_now:
|
if is_closing_now:
|
||||||
try:
|
try:
|
||||||
embed = disnake.Embed(
|
embed = disnake.Embed(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue