# display.py import html import re from collections import defaultdict import streamlit as st from dateutil import parser from utils.logger import setup_logger from utils.translations import _ logger = setup_logger(__name__) def extraire_statut_par_label(ticket): """Extrait le statut d'un ticket depuis ses labels Gitea. Recherche parmi les labels du ticket le premier correspondant a un statut connu (Backlog, En attente, En cours, Termine, Rejete). Args: ticket: Dictionnaire representant un ticket Gitea avec cle 'labels'. Returns: str: Statut du ticket ou "Autres" si aucun statut reconnu. """ labels = [label.get('name', '') for label in ticket.get('labels', [])] for statut in ["Backlog", str(_("pages.fiches.tickets.status.awaiting")), str(_("pages.fiches.tickets.status.in_progress")), str(_("pages.fiches.tickets.status.completed")), str(_("pages.fiches.tickets.status.rejected"))]: if statut in labels: return statut return str(_("pages.fiches.tickets.status.others")) def afficher_tickets_par_fiche(tickets): """Affiche les tickets associes a une fiche, groupes par statut. Organise les tickets dans des expanders par statut (En attente, En cours, etc.) et affiche un compteur de tickets en backlog si present. Args: tickets: Liste de tickets Gitea (dictionnaires). """ if not tickets: st.info(str(_("pages.fiches.tickets.no_linked_tickets"))) return st.markdown(str(_("pages.fiches.tickets.associated_tickets"))) tickets_groupes = defaultdict(list) for ticket in tickets: statut = extraire_statut_par_label(ticket) tickets_groupes[statut].append(ticket) nb_backlogs = len(tickets_groupes["Backlog"]) if nb_backlogs: st.info(f"⤇ {nb_backlogs} {str(_('pages.fiches.tickets.moderation_notice'))}") ordre_statuts = [ str(_("pages.fiches.tickets.status.awaiting")), str(_("pages.fiches.tickets.status.in_progress")), str(_("pages.fiches.tickets.status.completed")), str(_("pages.fiches.tickets.status.rejected")), str(_("pages.fiches.tickets.status.others")) ] for statut in ordre_statuts: if tickets_groupes[statut]: with st.expander(f"{statut} ({len(tickets_groupes[statut])})", expanded=(statut == "En cours")): for ticket in tickets_groupes[statut]: afficher_carte_ticket(ticket) def recuperer_commentaires_ticket(issue_index): """Recupere tous les commentaires d'un ticket Gitea. Args: issue_index: Numero d'index du ticket dans Gitea. Returns: list[dict]: Liste des commentaires (format JSON Gitea), liste vide si erreur. """ import requests from config import DEPOT_FICHES, GITEA_TOKEN, GITEA_URL, ORGANISATION headers = {"Authorization": f"token {GITEA_TOKEN}"} url = f"{GITEA_URL}/repos/{ORGANISATION}/{DEPOT_FICHES}/issues/{issue_index}/comments" try: response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() return response.json() except Exception as e: st.error(f"{str(_('pages.fiches.tickets.comment_error'))} {e}") return [] def afficher_carte_ticket(ticket): """Affiche une carte Streamlit detaillee pour un ticket Gitea. Affiche le titre, auteur, dates, labels, corps, et commentaires du ticket dans un format visuellement organise. Args: ticket: Dictionnaire representant un ticket Gitea complet. """ titre = ticket.get("title", str(_("pages.fiches.tickets.no_title"))) url = ticket.get("html_url", "") user = ticket.get("user", {}).get("login", str(_("pages.fiches.tickets.unknown"))) created = ticket.get("created_at", "") updated = ticket.get("updated_at", "") body = ticket.get("body", "") labels = [l["name"] for l in ticket.get("labels", []) if "name" in l] sujet = "" match = re.search(r"## Sujet de la proposition\s+(.+?)(\n|$)", body, re.DOTALL) if match: sujet = match.group(1).strip() def format_date(iso): try: return parser.isoparse(iso).strftime("%d/%m/%Y") except (ValueError, TypeError) as e: logger.warning(f"Format de date invalide: {iso} - {e}") return "?" date_created_str = format_date(created) maj_info = f"({str(_('pages.fiches.tickets.updated'))} {format_date(updated)})" if updated and updated != created else "" commentaires = recuperer_commentaires_ticket(ticket.get("number")) commentaires_html = "" for commentaire in commentaires: auteur = html.escape(commentaire.get('user', {}).get('login', str(_("pages.fiches.tickets.unknown")))) contenu = html.escape(commentaire.get('body', '')) date = format_date(commentaire.get('created_at', '')) commentaires_html += f"""

{auteur} ({date})

{contenu}

""" with st.container(): st.markdown(f"""

{titre}

{str(_("pages.fiches.tickets.opened_by"))} {html.escape(user)} {str(_("pages.fiches.tickets.on_date"))} {date_created_str} {maj_info}

{str(_("pages.fiches.tickets.subject_label"))} : {html.escape(sujet)}

Labels : {' • '.join(labels) if labels else str(_("pages.fiches.tickets.no_labels"))}

""", unsafe_allow_html=True) st.markdown(body, unsafe_allow_html=False) st.markdown("---") st.markdown(str(_("pages.fiches.tickets.comments"))) st.markdown(commentaires_html or str(_("pages.fiches.tickets.no_comments")), unsafe_allow_html=True)