Repo stats and features as aggregates

This commit is contained in:
2026-04-28 03:01:10 +02:00
parent c0a044fa0b
commit 2313e8675e
10 changed files with 258 additions and 24 deletions

View File

@@ -501,6 +501,7 @@ def repository_detail(
<h2>Run Analysis</h2>
<form class="stack" method="post" action="/ui/repos/{repository_id}/analysis-runs">
<label>Override source path <input name="source_path" placeholder="Optional local path"></label>
<label class="checkbox"><input type="checkbox" name="use_cached_checkout" value="1"> Analyze cached checkout without fetching upstream</label>
<label>Username <input name="access_username" autocomplete="username" placeholder="Optional for private HTTP(S) repos"></label>
<label>Password or access token <input name="access_password" type="password" autocomplete="current-password" placeholder="Used for this Git operation only"></label>
<div class="actions">
@@ -516,6 +517,7 @@ def repository_detail(
</section>
<section class="panel">
<h2>Approved Ability Map</h2>
{render_graph_counts(asdict(ability_map), facts_count=None)}
{render_ability_map(asdict(ability_map), repository_id)}
</section>
</div>
@@ -821,6 +823,7 @@ def delete_evidence_from_form(
def create_analysis_run_from_form(
repository_id: int,
source_path: str = Form(""),
use_cached_checkout: str | None = Form(None),
access_username: str = Form(""),
access_password: str = Form(""),
service: RegistryService = Depends(get_service),
@@ -828,6 +831,7 @@ def create_analysis_run_from_form(
summary = service.analyze_repository(
repository_id,
source_path=source_path or None,
use_cached_checkout=bool(use_cached_checkout),
access_username=access_username or None,
access_password=access_password or None,
)
@@ -869,6 +873,7 @@ def analysis_run_detail(
<section class="panel">
<div class="actions">
<h2 style="margin-right:auto">Candidate Graph</h2>
{render_graph_counts(asdict(candidate_graph), facts_count=len(facts))}
<form method="post" action="/ui/repos/{repository_id}/analysis-runs/{analysis_run_id}/candidate-graph/approve">
<button type="submit">Approve</button>
</form>
@@ -876,7 +881,10 @@ def analysis_run_detail(
{render_candidate_graph(asdict(candidate_graph), repository_id, analysis_run_id)}
</section>
<section class="panel">
<h2>Observed Facts</h2>
<div class="actions">
<h2 style="margin-right:auto">Observed Facts</h2>
{render_count_pills(facts=len(facts))}
</div>
<table>
<thead><tr><th>Kind</th><th>Name</th><th>Path</th><th>Value</th></tr></thead>
<tbody>{fact_rows or '<tr><td colspan="4" class="muted">No observed facts.</td></tr>'}</tbody>
@@ -1565,6 +1573,41 @@ def split_capability_lines(value: str) -> list[str]:
return [line.strip() for line in normalized.splitlines() if line.strip()]
def render_graph_counts(graph: dict, facts_count: int | None = None) -> str:
abilities = graph.get("abilities", [])
capabilities = [
capability
for ability in abilities
for capability in ability.get("capabilities", [])
]
features = [
feature
for capability in capabilities
for feature in capability.get("features", [])
]
counts: dict[str, int] = {
"abilities": len(abilities),
"capabilities": len(capabilities),
"features": len(features),
}
if facts_count is not None:
counts["facts"] = facts_count
return render_count_pills(**counts)
def render_count_pills(**counts: int) -> str:
labels = {
"abilities": "abilities",
"capabilities": "capabilities",
"features": "features",
"facts": "facts",
}
return "".join(
f'<span class="pill">{count} {labels[name]}</span>'
for name, count in counts.items()
)
def render_candidate_graph(graph: dict, repository_id: int, analysis_run_id: int) -> str:
abilities = graph.get("abilities", [])
if not abilities: