Update front & back
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
import html
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
import requests
|
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)
|
return sorted(builds, key=lambda build: build.get("number", 0), reverse=True)
|
||||||
|
|
||||||
|
|
||||||
def normalize_build(build: Dict) -> Dict:
|
def _extract_commits(build: Dict) -> List[Dict]:
|
||||||
changes = build.get("changeSets", [])
|
|
||||||
commits = []
|
commits = []
|
||||||
|
for cs in build.get("changeSets", []):
|
||||||
for cs in changes:
|
|
||||||
for item in cs.get("items", []):
|
for item in cs.get("items", []):
|
||||||
commits.append(
|
commits.append(
|
||||||
{
|
{
|
||||||
@@ -39,14 +38,33 @@ def normalize_build(build: Dict) -> Dict:
|
|||||||
"author": item.get("author", {}).get("fullName", "unknown"),
|
"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 {
|
return {
|
||||||
"number": build.get("number"),
|
"number": build.get("number"),
|
||||||
"status": (build.get("result") or "RUNNING").lower(),
|
"status": status,
|
||||||
"finished_at": build.get("timestamp"),
|
"finished_at": build.get("timestamp"),
|
||||||
"duration_seconds": build.get("duration", 0) // 1000,
|
"duration_seconds": build.get("duration", 0) // 1000,
|
||||||
"url": build.get("url"),
|
"url": build.get("url"),
|
||||||
"commits": commits,
|
"commits": commits,
|
||||||
|
"trigger": trigger,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -63,15 +81,16 @@ def fetch_builds(limit: int = 5) -> List[Dict]:
|
|||||||
raise ValueError("JENKINS_JOB_NAME not configured")
|
raise ValueError("JENKINS_JOB_NAME not configured")
|
||||||
url = (
|
url = (
|
||||||
f"{settings.jenkins_base_url}/job/{settings.jenkins_job_name}/api/json"
|
f"{settings.jenkins_base_url}/job/{settings.jenkins_job_name}/api/json"
|
||||||
"?tree=builds[number,url,result,timestamp,duration,"
|
"?tree=builds[number,url,result,timestamp,duration,building,"
|
||||||
"changesets[items[commitId,msg,author[fullName]]]]"
|
"changeSets[items[commitId,msg,author[fullName]]],"
|
||||||
|
"actions[causes[shortDescription]]]"
|
||||||
)
|
)
|
||||||
|
|
||||||
resp = requests.get(url, headers=_auth_header(), timeout=5)
|
resp = requests.get(url, headers=_auth_header(), timeout=5)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
|
|
||||||
builds = resp.json().get("builds", [])
|
builds = resp.json().get("builds", [])
|
||||||
return builds[:limit]
|
return _sort_builds(builds)[:limit]
|
||||||
|
|
||||||
|
|
||||||
def build_history() -> Dict:
|
def build_history() -> Dict:
|
||||||
|
|||||||
@@ -61,6 +61,15 @@ def test_build_history(monkeypatch):
|
|||||||
"timestamp": 1719992400000,
|
"timestamp": 1719992400000,
|
||||||
"duration": 75000,
|
"duration": 75000,
|
||||||
"url": "http://jenkins.local/job/demo/205",
|
"url": "http://jenkins.local/job/demo/205",
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"causes": [
|
||||||
|
{
|
||||||
|
"shortDescription": "Triggered by Merge pull request #37",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"changeSets": [
|
"changeSets": [
|
||||||
{
|
{
|
||||||
"items": [
|
"items": [
|
||||||
@@ -79,6 +88,7 @@ def test_build_history(monkeypatch):
|
|||||||
"timestamp": 1719988800000,
|
"timestamp": 1719988800000,
|
||||||
"duration": 1500,
|
"duration": 1500,
|
||||||
"url": "http://jenkins.local/job/demo/204",
|
"url": "http://jenkins.local/job/demo/204",
|
||||||
|
"actions": [],
|
||||||
"changeSets": [],
|
"changeSets": [],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -101,12 +111,14 @@ def test_build_history(monkeypatch):
|
|||||||
assert first["commits"] == [
|
assert first["commits"] == [
|
||||||
{"commit": "9ac3f91", "message": "Anade la API de Jenkins", "author": "Dev One"}
|
{"commit": "9ac3f91", "message": "Anade la API de Jenkins", "author": "Dev One"}
|
||||||
]
|
]
|
||||||
|
assert first["trigger"] == "Triggered by Merge pull request #37"
|
||||||
|
|
||||||
second = builds[1]
|
second = builds[1]
|
||||||
assert second["number"] == 204
|
assert second["number"] == 204
|
||||||
assert second["status"] == "running"
|
assert second["status"] == "running"
|
||||||
assert second["duration_seconds"] == 1
|
assert second["duration_seconds"] == 1
|
||||||
assert second["commits"] == []
|
assert second["commits"] == []
|
||||||
|
assert second["trigger"] == ""
|
||||||
|
|
||||||
|
|
||||||
def test_build_history_error_returns_empty(monkeypatch):
|
def test_build_history_error_returns_empty(monkeypatch):
|
||||||
|
|||||||
@@ -378,7 +378,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
<span class="summary-title">#{build.number}</span>
|
<span class="summary-title">#{build.number}</span>
|
||||||
|
|
||||||
{#if build.commits.length === 0}
|
{#if build.commits.length === 0}
|
||||||
<span class="muted">Ejecución manual</span>
|
<span class="muted">
|
||||||
|
{build.trigger || 'Ejecución manual'}
|
||||||
|
</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="summary-commit">
|
<span class="summary-commit">
|
||||||
{build.commits[0].commit} · {build.commits[0].author}
|
{build.commits[0].commit} · {build.commits[0].author}
|
||||||
@@ -401,6 +403,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if build.trigger}
|
||||||
|
<p class="history-row">
|
||||||
|
<span>Disparo</span>
|
||||||
|
<span>{build.trigger}</span>
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<p class="history-row">
|
<p class="history-row">
|
||||||
<span>Duración</span>
|
<span>Duración</span>
|
||||||
<span>{build.duration_seconds} s</span>
|
<span>{build.duration_seconds} s</span>
|
||||||
@@ -408,6 +417,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
{#if build.status === 'failure'}
|
{#if build.status === 'failure'}
|
||||||
<p class="history-message danger">Build fallida</p>
|
<p class="history-message danger">Build fallida</p>
|
||||||
|
{:else if build.status === 'running'}
|
||||||
|
<p class="history-message running">En curso</p>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="history-message success">Correcto</p>
|
<p class="history-message success">Correcto</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -723,6 +723,11 @@ li {
|
|||||||
box-shadow: 0 0 8px rgba(248, 113, 113, 0.5);
|
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 {
|
.history-body {
|
||||||
padding: 0.75rem 0.9rem 0.9rem;
|
padding: 0.75rem 0.9rem 0.9rem;
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
@@ -758,6 +763,12 @@ li {
|
|||||||
color: #bbf7d0;
|
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] {
|
.history-item[open] {
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.28);
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.28);
|
||||||
}
|
}
|
||||||
@@ -770,6 +781,10 @@ li {
|
|||||||
border-color: rgba(248, 113, 113, 0.35);
|
border-color: rgba(248, 113, 113, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.history-item.running {
|
||||||
|
border-color: rgba(250, 204, 21, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
.chip.danger {
|
.chip.danger {
|
||||||
background: rgba(248, 113, 113, 0.2);
|
background: rgba(248, 113, 113, 0.2);
|
||||||
color: #fecdd3;
|
color: #fecdd3;
|
||||||
|
|||||||
Reference in New Issue
Block a user