Link HTML5 review findings to source
CI / python (push) Has been cancelled
CI / rust (push) Has been cancelled

This commit is contained in:
2026-05-17 01:52:28 +03:00
parent 2aaf5d0082
commit de119c2106
3 changed files with 46 additions and 2 deletions
+16 -2
View File
@@ -433,7 +433,7 @@ def render_html5_review(
if not findings: if not findings:
body = '<p class="muted padded">Findings не найдены</p>' body = '<p class="muted padded">Findings не найдены</p>'
else: else:
body = "".join(_review_item(finding) for finding in findings[:12]) body = "".join(_review_item(project_id, finding) for finding in findings[:12])
return f""" return f"""
<div <div
class="review-panel" class="review-panel"
@@ -1463,18 +1463,32 @@ def _preflight_item(item: object) -> str:
""" """
def _review_item(finding: dict) -> str: def _review_item(project_id: str, finding: dict) -> str:
title = str(finding.get("title") or finding.get("code") or "Finding") title = str(finding.get("title") or finding.get("code") or "Finding")
severity = str(finding.get("severity") or finding.get("level") or "INFO") severity = str(finding.get("severity") or finding.get("level") or "INFO")
message = str(finding.get("message") or finding.get("description") or "") message = str(finding.get("message") or finding.get("description") or "")
source_path = str(finding.get("source_path") or finding.get("path") or "") source_path = str(finding.get("source_path") or finding.get("path") or "")
line = finding.get("line_start") or finding.get("line") line = finding.get("line_start") or finding.get("line")
location = f"{source_path}:{line}" if source_path and line else source_path location = f"{source_path}:{line}" if source_path and line else source_path
source_link = (
f"""
<a
href="/html5/projects/{quote(project_id)}/source/by-path?path={quote(source_path, safe='')}"
hx-get="/html5/projects/{quote(project_id)}/source/by-path?path={quote(source_path, safe='')}"
hx-target="[data-html5-source]"
hx-swap="outerHTML"
data-html5-review-source="{escape(source_path)}"
>Source</a>
"""
if source_path
else ""
)
return f""" return f"""
<article class="review-item" data-html5-review-finding="{escape(severity)}"> <article class="review-item" data-html5-review-finding="{escape(severity)}">
<strong>{escape(title)}</strong> <strong>{escape(title)}</strong>
<span>{escape(severity)}</span> <span>{escape(severity)}</span>
<small>{escape(message or location or "no details")}</small> <small>{escape(message or location or "no details")}</small>
<span class="inline-actions">{source_link}</span>
</article> </article>
""" """
@@ -1713,6 +1713,25 @@ async def html5_project_symbol_detail(project_id: str, lineage_id: str) -> Respo
) )
@app.get("/html5/projects/{project_id}/source/by-path")
async def html5_project_source_by_path(project_id: str, path: str) -> Response:
snapshot = _project_snapshot_or_404(project_id)
node = next(
(
item
for item in snapshot.nodes
if item.source_ref is not None and item.source_ref.source_path == path
),
None,
)
if node is None:
raise HTTPException(status_code=404, detail=f"Source not found: {path}")
return Response(
render_html5_source(node),
media_type="text/html; charset=utf-8",
)
@app.get("/html5/projects/{project_id}/source/{lineage_id}") @app.get("/html5/projects/{project_id}/source/{lineage_id}")
async def html5_project_source(project_id: str, lineage_id: str) -> Response: async def html5_project_source(project_id: str, lineage_id: str) -> Response:
snapshot = _project_snapshot_or_404(project_id) snapshot = _project_snapshot_or_404(project_id)
+11
View File
@@ -171,6 +171,15 @@ def test_html5_server_rendered_project_editor(tmp_path: Path):
assert "Проверить" in source.text assert "Проверить" in source.text
assert "<html" not in source.text assert "<html" not in source.text
source_by_path = client.get(
f"/html5/projects/{project_id}/source/by-path",
params={"path": module_node["source_ref"]["source_path"]},
)
assert source_by_path.status_code == 200
assert "data-html5-source-summary" in source_by_path.text
assert "Проверить" in source_by_path.text
assert "<html" not in source_by_path.text
detail = client.get(f"/html5/projects/{project_id}/symbols/{procedure_node['lineage_id']}/detail") detail = client.get(f"/html5/projects/{project_id}/symbols/{procedure_node['lineage_id']}/detail")
assert detail.status_code == 200 assert detail.status_code == 200
assert "text/html" in detail.headers["content-type"] assert "text/html" in detail.headers["content-type"]
@@ -193,6 +202,7 @@ def test_html5_server_rendered_project_editor(tmp_path: Path):
assert review.status_code == 200 assert review.status_code == 200
assert "text/html" in review.headers["content-type"] assert "text/html" in review.headers["content-type"]
assert "data-html5-review" in review.text assert "data-html5-review" in review.text
assert "data-html5-review-source" in review.text or "Findings не найдены" in review.text
assert "<html" not in review.text assert "<html" not in review.text
authoring = client.get(f"/html5/projects/{project_id}/authoring/changes") authoring = client.get(f"/html5/projects/{project_id}/authoring/changes")
@@ -379,6 +389,7 @@ def test_html5_object_context_fragment(tmp_path: Path):
assert "Отчет объекта" in context.text assert "Отчет объекта" in context.text
assert "server focused summary" in context.text assert "server focused summary" in context.text
assert "data-html5-review" in context.text assert "data-html5-review" in context.text
assert "data-html5-review-source" in context.text
assert "Review объекта" in context.text assert "Review объекта" in context.text
assert "External integration endpoint" in context.text assert "External integration endpoint" in context.text
assert "1 signals" in context.text assert "1 signals" in context.text