Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 67 additions & 3 deletions result_server/routes/results_detail_routes.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
from flask import abort, current_app, render_template, request
from flask import abort, current_app, render_template, request, url_for
from werkzeug.exceptions import Forbidden, NotFound

from utils.environment_snapshots import (
get_environment_snapshot,
list_environment_snapshot_results,
)
from utils.node_hours import compute_node_hours
from utils.result_compare_view import load_result_compare_context
from utils.result_detail_view import build_result_detail_context
from utils.result_file import (
load_permitted_result_json,
serve_permitted_result_file,
)
from utils.result_records import summarize_result_quality
from utils.trigger_display import load_trigger_run_lookup
from utils.result_records import (
format_numeric_value,
format_result_timestamp,
summarize_result_quality,
)
from utils.trigger_display import load_trigger_run_lookup, summarize_execution_trigger


def register_results_detail_routes(results_bp):
Expand Down Expand Up @@ -35,8 +45,62 @@ def result_detail(filename):
quality,
load_trigger_run_lookup(current_app.config.get("EXECUTION_PROFILE_DB_PATH")),
)
if detail_context.get("environment_snapshot_hash"):
detail_context["environment_snapshot_results_url"] = url_for(
"results.environment_snapshot_results",
snapshot_hash=detail_context["environment_snapshot_hash"],
)
return render_template("result_detail.html", result=result, quality=quality, **detail_context)

@results_bp.route("/environment-snapshots/<path:snapshot_hash>")
def environment_snapshot_results(snapshot_hash):
db_path = current_app.config.get("EXECUTION_PROFILE_DB_PATH")
snapshot = get_environment_snapshot(db_path, snapshot_hash)
if snapshot is None:
abort(404, "Environment snapshot not found")

trigger_run_lookup = load_trigger_run_lookup(db_path)
result_rows = []
for link in list_environment_snapshot_results(db_path, snapshot_hash):
filename = link.get("json_file") or ""
try:
result = load_permitted_result_json(
filename,
current_app.config["RECEIVED_DIR"],
not_found_message="Result file not found",
)
except (Forbidden, NotFound):
continue
trigger_summary = summarize_execution_trigger(result, trigger_run_lookup)
result_rows.append({
"filename": filename,
"timestamp": format_result_timestamp(filename),
"code": result.get("code") or link.get("code") or "-",
"system": result.get("system") or link.get("system") or "-",
"exp": result.get("Exp") or link.get("exp") or "-",
"fom": format_numeric_value(result.get("FOM")),
"fom_unit": result.get("FOM_unit") or "",
"pipeline_id": result.get("pipeline_id") or link.get("pipeline_id") or "-",
"node_hours": compute_node_hours(result),
"trigger_headline": trigger_summary["headline"],
"trigger_subline": trigger_summary.get("subline") or "",
"trigger_title": trigger_summary.get("title") or "",
})

visible_node_hours = round(sum(row["node_hours"] for row in result_rows), 2)
result_summary = {
"visible_count": len(result_rows),
"linked_count": snapshot.get("result_count") or len(result_rows),
"node_hours": visible_node_hours,
"latest_timestamp": result_rows[0]["timestamp"] if result_rows else "-",
}
return render_template(
"environment_snapshot_results.html",
snapshot=snapshot,
result_rows=result_rows,
result_summary=result_summary,
)

@results_bp.route("/<filename>")
def show_result(filename):
if filename.endswith(".tgz"):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,23 @@ <h3>Results</h3>
<td>
{{ row.result_count }} results
<span class="profile-usage-subline">{{ row.node_hours }} node-hours</span>
<span class="profile-usage-subline">{{ row.snapshot_count }} snapshots</span>
<span class="profile-usage-subline">
attribution trigger/manual/legacy:
{{ row.attribution_counts.trigger_id_match }}/{{ row.attribution_counts.manual_profile_match }}/{{ row.attribution_counts.legacy_scope_fallback }}
</span>
</td>
<td>
{% if row.latest_result %}
<a href="{{ url_for('results.result_detail', filename=row.latest_result.filename) }}">{{ row.latest_result.timestamp }}</a>
<span class="profile-usage-subline">{{ row.latest_result.code }} / {{ row.latest_result.system }} / {{ row.latest_result.exp }}</span>
<span class="profile-usage-subline">{{ row.latest_result.trigger_headline }} / pipeline {{ row.latest_result.pipeline_id }}</span>
{% if row.latest_result.attribution %}
<span class="profile-usage-subline">attributed by {{ row.latest_result.attribution.label }}</span>
{% endif %}
{% if row.latest_result.environment_snapshot %}
<span class="profile-usage-subline" title="{{ row.latest_result.environment_snapshot.hash }}">
snapshot {{ row.latest_result.environment_snapshot.short_hash }}
<a href="{{ url_for('results.environment_snapshot_results', snapshot_hash=row.latest_result.environment_snapshot.hash) }}">snapshot {{ row.latest_result.environment_snapshot.short_hash }}</a>
/ {{ row.latest_result.environment_snapshot.allocation_project_id }}
/ {{ row.latest_result.environment_snapshot.scheduler }}
</span>
Expand Down
102 changes: 102 additions & 0 deletions result_server/templates/environment_snapshot_results.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{% extends "_results_base.html" %}
{% from "_detail_tables.html" import render_titled_key_value_table %}

{% block title %}Environment Snapshot Results{% endblock %}
{% block page_subtitle %}Results collected from the same indexed execution environment snapshot.{% endblock %}

{% block content %}
{% include "_detail_page_styles.html" %}
<style>
.snapshot-table { width: 100%; table-layout: fixed; }
.snapshot-table th,
.snapshot-table td { white-space: normal; vertical-align: top; line-height: 1.4; }
.snapshot-table th:nth-child(1) { width: 150px; }
.snapshot-table th:nth-child(2) { width: 230px; }
.snapshot-table th:nth-child(4) { width: 120px; }
.snapshot-table th:nth-child(5) { width: 110px; }
.snapshot-table th:nth-child(6) { width: 240px; }
.snapshot-subline { display: block; color: #5f6b7a; font-size: 0.88em; margin-top: 0.2rem; }
.snapshot-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.snapshot-hash { overflow-wrap: anywhere; }
</style>

<a href="{{ url_for('results.results') }}" class="back-link">&larr; Back to Results</a>

{% set summary = snapshot.summary if snapshot.summary is mapping else {} %}
{% set payload = snapshot.payload if snapshot.payload is mapping else {} %}
{% set system_payload = payload.system if payload.system is mapping else {} %}
{% set scheduler_payload = payload.scheduler if payload.scheduler is mapping else {} %}
{% set runner_payload = payload.runner if payload.runner is mapping else {} %}
{% set benchkit_payload = payload.benchkit if payload.benchkit is mapping else {} %}
{% set snapshot_rows = [
{"label": "Snapshot Hash", "value": snapshot.snapshot_hash, "value_class": "snapshot-hash snapshot-mono"},
{"label": "System", "value": summary.system or system_payload.name or "N/A"},
{"label": "Allocation Project ID", "value": summary.allocation_project_id or system_payload.allocation_project_id or "not specified"},
{"label": "Scheduler", "value": summary.scheduler or scheduler_payload.kind or "N/A"},
{"label": "Runner", "value": summary.runner or runner_payload.description or "N/A"},
{"label": "BenchKit Commit", "value": summary.benchkit_commit or benchkit_payload.commit_hash or "N/A"},
{"label": "Result Count", "value": snapshot.result_count},
{"label": "First Seen", "value": snapshot.first_seen_at},
{"label": "Last Seen", "value": snapshot.last_seen_at},
] %}

{{ render_titled_key_value_table("Environment Snapshot", snapshot_rows, "meta-table") }}

<div class="diagnostic-grid">
<div class="diagnostic-card">
<h3>Visible Results</h3>
<p>{{ result_summary.visible_count }} / {{ result_summary.linked_count }} linked</p>
</div>
<div class="diagnostic-card">
<h3>Node-hours</h3>
<p>{{ result_summary.node_hours }}</p>
</div>
<div class="diagnostic-card">
<h3>Latest Result</h3>
<p>{{ result_summary.latest_timestamp }}</p>
</div>
</div>

<div class="section">
<h2>Results</h2>
{% if result_rows %}
<div class="detail-table-wrap">
<table class="snapshot-table">
<thead>
<tr>
<th>Time</th>
<th>Scope</th>
<th>FOM</th>
<th>Pipeline</th>
<th>Node-hours</th>
<th>Run Cause</th>
</tr>
</thead>
<tbody>
{% for row in result_rows %}
<tr>
<td><a href="{{ url_for('results.result_detail', filename=row.filename) }}">{{ row.timestamp }}</a></td>
<td>
<span class="snapshot-mono">{{ row.code }}</span>
/ <span class="snapshot-mono">{{ row.system }}</span>
<span class="snapshot-subline">exp {{ row.exp }}</span>
</td>
<td>{{ row.fom }}{% if row.fom_unit %} {{ row.fom_unit }}{% endif %}</td>
<td>{{ row.pipeline_id }}</td>
<td>{{ row.node_hours }}</td>
<td title="{{ row.trigger_title }}">
{{ row.trigger_headline }}
{% if row.trigger_subline %}
<span class="snapshot-subline">{{ row.trigger_subline }}</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="muted-empty">No permitted results are linked to this snapshot.</p>
{% endif %}
</div>
{% endblock %}
5 changes: 5 additions & 0 deletions result_server/templates/result_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ <h2>Quality</h2>

{% if environment_rows %}
{{ render_titled_key_value_table("Environment Snapshot", environment_rows, "meta-table") }}
{% if environment_snapshot_results_url %}
<p class="detail-action-link">
<a href="{{ environment_snapshot_results_url }}">View results with this snapshot</a>
</p>
{% endif %}
{% endif %}

{% if vector_metrics %}
Expand Down
4 changes: 4 additions & 0 deletions result_server/test_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ def result_compare():
def result_detail(filename):
return filename

@results_bp.route("/environment-snapshots/<path:snapshot_hash>")
def environment_snapshot_results(snapshot_hash):
return snapshot_hash

@results_bp.route("/usage")
def usage_report():
return ""
Expand Down
114 changes: 114 additions & 0 deletions result_server/tests/test_environment_snapshot_results_route.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""Route tests for environment snapshot result listings."""

from __future__ import annotations

import json
import os
import sys

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from test_support import build_results_route_app, install_portal_test_stubs # noqa: E402

install_portal_test_stubs()

from utils.environment_snapshots import index_environment_snapshot # noqa: E402


def _add_navigation_routes(app):
app.add_url_rule("/", "home", lambda: "home")
app.add_url_rule("/systems", "systemlist", lambda: "systems")
app.add_url_rule("/login", "auth.login", lambda: "login")
app.add_url_rule("/logout", "auth.logout", lambda: "logout")


def _write_json(path, payload):
with open(path, "w", encoding="utf-8") as handle:
json.dump(payload, handle)


def _payload(uuid, snapshot_hash="sha256:routeabc"):
return {
"_server_uuid": uuid,
"code": "qws",
"system": "Fugaku",
"Exp": "CASE1",
"FOM": "0.423",
"FOM_unit": "s",
"node_count": "1",
"pipeline_id": 3270,
"pipeline_timing": {"run_time": 120},
"execution_trigger": {
"id": "qws-fugaku-time",
"type": "scheduled",
"reason": "cron:0 14 * * *",
},
"environment_snapshot": {
"schema_version": 1,
"hash": snapshot_hash,
"summary": {
"system": "Fugaku",
"allocation_project_id": "rkp00010",
"scheduler": "pbs",
"runner": "fugaku-runner",
"benchkit_commit": "abcdef",
},
"payload": {
"schema_version": 1,
"system": {
"name": "Fugaku",
"allocation_project_id": "rkp00010",
},
"scheduler": {"kind": "pbs"},
},
},
}


def test_environment_snapshot_results_route_lists_linked_results(tmp_path):
received_dir = tmp_path / "received"
received_dir.mkdir()
db_path = tmp_path / "cx_portal.sqlite3"
filename = "result_20260810_160604_11111111-2222-3333-4444-555555555555.json"
payload = _payload("11111111-2222-3333-4444-555555555555")
_write_json(received_dir / filename, payload)
assert index_environment_snapshot(
db_path=str(db_path),
payload=payload,
json_file=filename,
)

app = build_results_route_app(received_dir=str(received_dir))
_add_navigation_routes(app)
app.config["EXECUTION_PROFILE_DB_PATH"] = str(db_path)
response = app.test_client().get("/results/environment-snapshots/sha256:routeabc")

assert response.status_code == 200
html = response.get_data(as_text=True)
assert "Environment Snapshot" in html
assert "Visible Results" in html
assert "1 / 1 linked" in html
assert "Node-hours" in html
assert "sha256:routeabc" in html
assert "result_detail" not in html
assert "2026-08-10 16:06:04" in html
assert "qws" in html
assert "Fugaku" in html
assert "Scheduled / qws-fugaku-time" in html
assert "0.03" in html


def test_result_detail_links_to_environment_snapshot_results(tmp_path):
received_dir = tmp_path / "received"
received_dir.mkdir()
filename = "result_20260810_160604_11111111-2222-3333-4444-555555555555.json"
_write_json(received_dir / filename, _payload("11111111-2222-3333-4444-555555555555"))

app = build_results_route_app(received_dir=str(received_dir))
_add_navigation_routes(app)
app.config["EXECUTION_PROFILE_DB_PATH"] = str(tmp_path / "cx_portal.sqlite3")
response = app.test_client().get(f"/results/detail/{filename}")

assert response.status_code == 200
html = response.get_data(as_text=True)
assert "View results with this snapshot" in html
assert "/results/environment-snapshots/sha256:routeabc" in html
8 changes: 8 additions & 0 deletions result_server/tests/test_environment_snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@

from utils.environment_snapshots import ( # noqa: E402
extract_environment_snapshot_record,
get_environment_snapshot,
index_environment_snapshot,
list_environment_snapshot_results,
list_environment_snapshots,
)

Expand Down Expand Up @@ -75,6 +77,12 @@ def test_index_environment_snapshot_deduplicates_payloads(tmp_path):
assert len(rows) == 1
assert rows[0]["snapshot_hash"] == "sha256:abc123"
assert rows[0]["result_count"] == 2
snapshot = get_environment_snapshot(str(db_path), "sha256:abc123")
assert snapshot is not None
assert snapshot["summary"]["system"] == "Fugaku"
assert snapshot["payload"]["scheduler"]["kind"] == "pbs"
linked_results = list_environment_snapshot_results(str(db_path), "sha256:abc123")
assert [row["json_file"] for row in linked_results] == ["result-b.json", "result-a.json"]

import sqlite3

Expand Down
Loading
Loading