diff --git a/services/api-server/src/api_server/html5.py b/services/api-server/src/api_server/html5.py
index 4decd8b..3187d16 100644
--- a/services/api-server/src/api_server/html5.py
+++ b/services/api-server/src/api_server/html5.py
@@ -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:
return f"""
str:
hx-get="/html5/projects/{quote(project_id)}/review"
hx-trigger="load"
hx-swap="outerHTML"
+ {oob_attr}
>
-
Review
+
{escape(title)}
Сервер готовит findings.
"""
@@ -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-trigger="every 20s"
hx-swap="outerHTML"
+ {oob_attr}
>
- Review · {len(findings)}
+ {escape(title)} · {len(findings)}
{body}
"""
diff --git a/services/api-server/src/api_server/main.py b/services/api-server/src/api_server/main.py
index d5a8d0c..badc319 100644
--- a/services/api-server/src/api_server/main.py
+++ b/services/api-server/src/api_server/main.py
@@ -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)
source_node = _source_node_for_object_context(project_id, impact)
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(
project_id,
schema,
@@ -1794,8 +1795,9 @@ async def html5_project_object_context(project_id: str, object_name: str) -> Res
integrations=integrations,
oob=True,
)
+ review_context = render_html5_review(project_id, focused_findings, title="Review объекта", oob=True)
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",
)
@@ -8262,6 +8264,66 @@ def _source_node_for_object_context(
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:
setup = _project_setup_response(project_id)
if setup.current_source is not None:
diff --git a/services/api-server/tests/test_api.py b/services/api-server/tests/test_api.py
index b89c7be..61144f7 100644
--- a/services/api-server/tests/test_api.py
+++ b/services/api-server/tests/test_api.py
@@ -337,6 +337,9 @@ def test_html5_object_context_fragment(tmp_path: Path):
assert "data-html5-project-report" in context.text
assert "Отчет объекта" 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 errors" in context.text
assert "125.0 ms" in context.text