95 lines
3.0 KiB
Python
95 lines
3.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""jira.py — Fuente Jira del monitor (RF-3, RF-4, RF-16).
|
|
|
|
Recoge del proyecto DS (Service Desk):
|
|
- incidencias NUEVAS creadas desde el ciclo anterior,
|
|
- tickets SIN ASIGNAR no cerrados ni resueltos.
|
|
|
|
SOLO LECTURA (Regla 15): reutiliza el `JiraClient` de gestion_jira, que únicamente
|
|
expone consultas GET/POST de busqueda; nunca crea, modifica ni comenta.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from datetime import datetime
|
|
|
|
from .. import config # noqa: F401 (asegura sys.path con scripts/)
|
|
|
|
import infra_paths
|
|
|
|
# Estados que NO cuentan como "sin asignar pendiente de atencion".
|
|
_ESTADOS_EXCLUIDOS = (
|
|
"'En Curso', 'In Progress', 'En desarrollo', 'Pasado a Programación Área', "
|
|
"'Cerrado', 'Resuelto', 'Cancelado', 'Closed', 'Resolved', 'Canceled'"
|
|
)
|
|
|
|
|
|
def _gestion_jira_dir(cfg: dict | None = None):
|
|
"""Directorio del repo gestion_jira (donde vive jira_api.py)."""
|
|
cfg = cfg or config.cargar()
|
|
override = cfg.get("jira", {}).get("gestion_jira_dir")
|
|
if override:
|
|
return override
|
|
return str(infra_paths.INFRA_ROOT.parent / "gestion_jira")
|
|
|
|
|
|
def construir_cliente(cfg: dict | None = None):
|
|
"""Devuelve un JiraClient (solo lectura)."""
|
|
directorio = _gestion_jira_dir(cfg)
|
|
if directorio not in sys.path:
|
|
sys.path.insert(0, directorio)
|
|
from jira_api import JiraClient
|
|
|
|
return JiraClient()
|
|
|
|
|
|
def _iso_para_jql(desde) -> str:
|
|
"""Normaliza una fecha a 'YYYY-MM-DD HH:MM' para el JQL."""
|
|
if isinstance(desde, datetime):
|
|
return desde.strftime("%Y-%m-%d %H:%M")
|
|
return str(desde)
|
|
|
|
|
|
def _normalizar(issue: dict) -> dict:
|
|
"""Convierte una issue de Jira en un registro legible."""
|
|
key = issue.get("key", "")
|
|
f = issue.get("fields", {}) or {}
|
|
return {
|
|
"id": key,
|
|
"key": key,
|
|
"resumen": f.get("summary", ""),
|
|
"estado": (f.get("status") or {}).get("name", ""),
|
|
"creado": (f.get("created", "") or "")[:10],
|
|
"prioridad": (f.get("priority") or {}).get("name", ""),
|
|
"url": f"https://prolongo.atlassian.net/browse/{key}",
|
|
}
|
|
|
|
|
|
def ds_nuevos(cliente, desde) -> list[dict]:
|
|
"""Incidencias DS creadas desde `desde` (inclusive)."""
|
|
jql = f'project = DS AND created >= "{_iso_para_jql(desde)}" ORDER BY created DESC'
|
|
res = cliente.search(jql, max_results=100)
|
|
return [_normalizar(i) for i in res.get("issues", [])]
|
|
|
|
|
|
def ds_sin_asignar(cliente) -> list[dict]:
|
|
"""Tickets DS sin asignar, no cerrados ni resueltos."""
|
|
jql = (
|
|
"project = DS AND assignee = EMPTY "
|
|
f"AND status not in ({_ESTADOS_EXCLUIDOS}) "
|
|
"AND statusCategory != Done "
|
|
"ORDER BY created DESC"
|
|
)
|
|
res = cliente.search(jql, max_results=100)
|
|
return [_normalizar(i) for i in res.get("issues", [])]
|
|
|
|
|
|
def render_jira(m: dict) -> str:
|
|
"""Render Markdown de un ticket para el fichero del dia."""
|
|
key = m.get("key", "?")
|
|
url = m.get("url", "")
|
|
estado = m.get("estado", "")
|
|
resumen = m.get("resumen", "")
|
|
return f"- **[{key}]({url})** ({estado}) — {resumen}"
|