03-oct: superguardado automático
This commit is contained in:
94
scripts/monitor_estado/fuentes/jira.py
Normal file
94
scripts/monitor_estado/fuentes/jira.py
Normal file
@@ -0,0 +1,94 @@
|
||||
# -*- 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}"
|
||||
Reference in New Issue
Block a user