Add HTML5 focused object review
CI / python (push) Has been cancelled
CI / rust (push) Has been cancelled

This commit is contained in:
2026-05-17 00:54:46 +03:00
parent 6e89cdcd84
commit f830c4290f
3 changed files with 78 additions and 4 deletions
+12 -3
View File
@@ -385,7 +385,14 @@ def render_html5_object_report(
""" """
def render_html5_review(project_id: str, findings: list[dict] | None) -> str: def render_html5_review(
project_id: str,
findings: list[dict] | None,
*,
title: str = "Review",
oob: bool = False,
) -> str:
oob_attr = ' hx-swap-oob="outerHTML"' if oob else ""
if findings is None: if findings is None:
return f""" return f"""
<div <div
@@ -394,8 +401,9 @@ def render_html5_review(project_id: str, findings: list[dict] | None) -> str:
hx-get="/html5/projects/{quote(project_id)}/review" hx-get="/html5/projects/{quote(project_id)}/review"
hx-trigger="load" hx-trigger="load"
hx-swap="outerHTML" hx-swap="outerHTML"
{oob_attr}
> >
<div class="panel-title">Review</div> <div class="panel-title">{escape(title)}</div>
<p class="muted padded">Сервер готовит findings.</p> <p class="muted padded">Сервер готовит findings.</p>
</div> </div>
""" """
@@ -410,8 +418,9 @@ def render_html5_review(project_id: str, findings: list[dict] | None) -> str:
hx-get="/html5/projects/{quote(project_id)}/review" hx-get="/html5/projects/{quote(project_id)}/review"
hx-trigger="every 20s" hx-trigger="every 20s"
hx-swap="outerHTML" hx-swap="outerHTML"
{oob_attr}
> >
<div class="panel-title">Review · {len(findings)}</div> <div class="panel-title">{escape(title)} · {len(findings)}</div>
<div class="review-list">{body}</div> <div class="review-list">{body}</div>
</div> </div>
""" """
+63 -1
View File
@@ -1770,6 +1770,7 @@ async def html5_project_object_context(project_id: str, object_name: str) -> Res
knowledge = _knowledge_for_object_context(schema, impact, ui) knowledge = _knowledge_for_object_context(schema, impact, ui)
source_node = _source_node_for_object_context(project_id, impact) source_node = _source_node_for_object_context(project_id, impact)
symbol_references = await project_symbol_references(project_id, schema.object.lineage_id, direction="both") symbol_references = await project_symbol_references(project_id, schema.object.lineage_id, direction="both")
focused_findings = _review_for_object_context(project_id, schema, impact, ui)
object_context = render_html5_object_context( object_context = render_html5_object_context(
project_id, project_id,
schema, schema,
@@ -1794,8 +1795,9 @@ async def html5_project_object_context(project_id: str, object_name: str) -> Res
integrations=integrations, integrations=integrations,
oob=True, oob=True,
) )
review_context = render_html5_review(project_id, focused_findings, title="Review объекта", oob=True)
return Response( return Response(
object_context + flowchart_context + source_context + symbol_context + report_context, object_context + flowchart_context + source_context + symbol_context + report_context + review_context,
media_type="text/html; charset=utf-8", media_type="text/html; charset=utf-8",
) )
@@ -8262,6 +8264,66 @@ def _source_node_for_object_context(
return None return None
def _review_for_object_context(
project_id: str,
schema: ObjectSchemaResponse,
impact: ObjectImpactResponse,
ui: ObjectUiResponse,
) -> list[dict]:
snapshot = _project_snapshot_or_404(project_id)
nodes_by_lineage = {node.lineage_id: node for node in snapshot.nodes}
names: set[str] = set()
lineages: set[str] = set()
def add_node(node: NamedNode | None) -> None:
if node is None:
return
lineages.add(node.lineage_id)
names.add(node.name.casefold())
names.add(node.qualified_name.casefold())
add_node(schema.object)
for attribute in schema.attributes:
add_node(attribute)
for section in schema.tabular_sections:
add_node(section.tabular_section)
for column in section.columns:
add_node(column)
for group in [
impact.modules,
impact.routines,
impact.forms,
impact.commands,
impact.roles,
impact.jobs,
impact.callees,
impact.query_tables,
impact.writes,
]:
for node in group:
add_node(node)
for form in ui.forms:
add_node(form.form)
for node in [*form.commands, *form.elements, *form.command_handlers.values()]:
add_node(node)
source_paths = {
node.source_ref.source_path
for lineage_id in lineages
if (node := nodes_by_lineage.get(lineage_id)) is not None and node.source_ref is not None
}
focused: list[dict] = []
for finding in get_review_payload(snapshot):
source_path = finding.get("source_path")
haystack = " ".join(
str(finding.get(key) or "").casefold()
for key in ["title", "message", "source_path"]
)
if (source_path and source_path in source_paths) or any(name and name in haystack for name in names):
focused.append(finding)
return focused
def _current_import_source(project_id: str) -> ImportSourceKind: def _current_import_source(project_id: str) -> ImportSourceKind:
setup = _project_setup_response(project_id) setup = _project_setup_response(project_id)
if setup.current_source is not None: if setup.current_source is not None:
+3
View File
@@ -337,6 +337,9 @@ def test_html5_object_context_fragment(tmp_path: Path):
assert "data-html5-project-report" in context.text assert "data-html5-project-report" in context.text
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 "Review объекта" in context.text
assert "External integration endpoint" in context.text
assert "1 signals" in context.text assert "1 signals" in context.text
assert "1 errors" in context.text assert "1 errors" in context.text
assert "125.0 ms" in context.text assert "125.0 ms" in context.text