diff --git a/backend/app/services/builds.py b/backend/app/services/builds.py index c46dae8..3b5ec8d 100644 --- a/backend/app/services/builds.py +++ b/backend/app/services/builds.py @@ -15,6 +15,7 @@ # along with this program. If not, see . import base64 +import html from typing import Dict, List import requests @@ -26,11 +27,9 @@ def _sort_builds(builds: List[Dict]) -> List[Dict]: return sorted(builds, key=lambda build: build.get("number", 0), reverse=True) -def normalize_build(build: Dict) -> Dict: - changes = build.get("changeSets", []) +def _extract_commits(build: Dict) -> List[Dict]: commits = [] - - for cs in changes: + for cs in build.get("changeSets", []): for item in cs.get("items", []): commits.append( { @@ -39,14 +38,33 @@ def normalize_build(build: Dict) -> Dict: "author": item.get("author", {}).get("fullName", "unknown"), } ) + return commits + + +def _extract_trigger(build: Dict) -> str: + for action in build.get("actions", []): + for cause in action.get("causes", []): + description = cause.get("shortDescription") + if description: + return html.unescape(description).strip() + return "" + + +def normalize_build(build: Dict) -> Dict: + commits = _extract_commits(build) + trigger = _extract_trigger(build) + status = (build.get("result") or "RUNNING").lower() + if build.get("building"): + status = "running" return { "number": build.get("number"), - "status": (build.get("result") or "RUNNING").lower(), + "status": status, "finished_at": build.get("timestamp"), "duration_seconds": build.get("duration", 0) // 1000, "url": build.get("url"), "commits": commits, + "trigger": trigger, } @@ -63,15 +81,16 @@ def fetch_builds(limit: int = 5) -> List[Dict]: raise ValueError("JENKINS_JOB_NAME not configured") url = ( f"{settings.jenkins_base_url}/job/{settings.jenkins_job_name}/api/json" - "?tree=builds[number,url,result,timestamp,duration," - "changesets[items[commitId,msg,author[fullName]]]]" + "?tree=builds[number,url,result,timestamp,duration,building," + "changeSets[items[commitId,msg,author[fullName]]]," + "actions[causes[shortDescription]]]" ) resp = requests.get(url, headers=_auth_header(), timeout=5) resp.raise_for_status() builds = resp.json().get("builds", []) - return builds[:limit] + return _sort_builds(builds)[:limit] def build_history() -> Dict: diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index 28165b9..ceb448f 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -61,6 +61,15 @@ def test_build_history(monkeypatch): "timestamp": 1719992400000, "duration": 75000, "url": "http://jenkins.local/job/demo/205", + "actions": [ + { + "causes": [ + { + "shortDescription": "Triggered by Merge pull request #37", + } + ] + } + ], "changeSets": [ { "items": [ @@ -79,6 +88,7 @@ def test_build_history(monkeypatch): "timestamp": 1719988800000, "duration": 1500, "url": "http://jenkins.local/job/demo/204", + "actions": [], "changeSets": [], }, ] @@ -101,12 +111,14 @@ def test_build_history(monkeypatch): assert first["commits"] == [ {"commit": "9ac3f91", "message": "Anade la API de Jenkins", "author": "Dev One"} ] + assert first["trigger"] == "Triggered by Merge pull request #37" second = builds[1] assert second["number"] == 204 assert second["status"] == "running" assert second["duration_seconds"] == 1 assert second["commits"] == [] + assert second["trigger"] == "" def test_build_history_error_returns_empty(monkeypatch): diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 12f227d..0fac69b 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -378,7 +378,9 @@ along with this program. If not, see . #{build.number} {#if build.commits.length === 0} - Ejecución manual + + {build.trigger || 'Ejecución manual'} + {:else} {build.commits[0].commit} · {build.commits[0].author} @@ -401,6 +403,13 @@ along with this program. If not, see .

{/if} + {#if build.trigger} +

+ Disparo + {build.trigger} +

+ {/if} +

Duración {build.duration_seconds} s @@ -408,6 +417,8 @@ along with this program. If not, see . {#if build.status === 'failure'}

Build fallida

+ {:else if build.status === 'running'} +

En curso

{:else}

Correcto

{/if} diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index 89e48c6..2eb6441 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -723,6 +723,11 @@ li { box-shadow: 0 0 8px rgba(248, 113, 113, 0.5); } +.status-dot.running { + background: #facc15; + box-shadow: 0 0 8px rgba(250, 204, 21, 0.5); +} + .history-body { padding: 0.75rem 0.9rem 0.9rem; border-top: 1px solid rgba(255, 255, 255, 0.05); @@ -758,6 +763,12 @@ li { color: #bbf7d0; } +.history-message.running { + background: rgba(250, 204, 21, 0.16); + border-color: rgba(250, 204, 21, 0.5); + color: #fef9c3; +} + .history-item[open] { box-shadow: 0 10px 30px rgba(0, 0, 0, 0.28); } @@ -770,6 +781,10 @@ li { border-color: rgba(248, 113, 113, 0.35); } +.history-item.running { + border-color: rgba(250, 204, 21, 0.35); +} + .chip.danger { background: rgba(248, 113, 113, 0.2); color: #fecdd3;