Add HTML5 object graph context
CI / python (push) Has been cancelled
CI / rust (push) Has been cancelled

This commit is contained in:
2026-05-17 00:32:16 +03:00
parent c871a8bbd2
commit 7ee6deb088
3 changed files with 26 additions and 0 deletions
@@ -326,6 +326,7 @@ def render_html5_object_context(
knowledge: Iterable[object] | None = None, knowledge: Iterable[object] | None = None,
privacy: object | None = None, privacy: object | None = None,
integrations: Iterable[object] | None = None, integrations: Iterable[object] | None = None,
flowchart: object | None = None,
) -> str: ) -> str:
if schema is None or impact is None: if schema is None or impact is None:
return f""" return f"""
@@ -353,6 +354,8 @@ def render_html5_object_context(
knowledge_items = list(knowledge or []) knowledge_items = list(knowledge or [])
privacy_markers = getattr(privacy, "markers", []) if privacy is not None else [] privacy_markers = getattr(privacy, "markers", []) if privacy is not None else []
integration_items = list(integrations or []) integration_items = list(integrations or [])
flow_nodes = getattr(flowchart, "nodes", []) if flowchart is not None else []
flow_edges = getattr(flowchart, "edges", []) if flowchart is not None else []
return f""" return f"""
<div class="object-context" data-html5-object-context data-html5-object-name="{escape(str(name))}"> <div class="object-context" data-html5-object-context data-html5-object-name="{escape(str(name))}">
<div class="panel-title">Object context</div> <div class="panel-title">Object context</div>
@@ -372,6 +375,8 @@ def render_html5_object_context(
{_metric("Writes", len(writes))} {_metric("Writes", len(writes))}
{_metric("Calls", len(callees))} {_metric("Calls", len(callees))}
{_metric("Integrations", len(integration_items))} {_metric("Integrations", len(integration_items))}
{_metric("Graph nodes", len(flow_nodes))}
{_metric("Graph edges", len(flow_edges))}
{_metric("Runtime", len(runtime_items))} {_metric("Runtime", len(runtime_items))}
{_metric("Knowledge", len(knowledge_items))} {_metric("Knowledge", len(knowledge_items))}
{_metric("Privacy", len(privacy_markers))} {_metric("Privacy", len(privacy_markers))}
@@ -386,6 +391,7 @@ def render_html5_object_context(
{''.join(_named_node_item("read", item) for item in query_tables[:4])} {''.join(_named_node_item("read", item) for item in query_tables[:4])}
{''.join(_named_node_item("write", item) for item in writes[:4])} {''.join(_named_node_item("write", item) for item in writes[:4])}
{''.join(_named_node_item("call", item) for item in callees[:6])} {''.join(_named_node_item("call", item) for item in callees[:6])}
{''.join(_flowchart_edge_item(item, flow_nodes) for item in flow_edges[:8])}
{''.join(_runtime_summary_item(item) for item in runtime_items[:6])} {''.join(_runtime_summary_item(item) for item in runtime_items[:6])}
{''.join(_knowledge_record_item(item) for item in knowledge_items[:6])} {''.join(_knowledge_record_item(item) for item in knowledge_items[:6])}
{''.join(_privacy_marker_item(item) for item in privacy_markers[:6])} {''.join(_privacy_marker_item(item) for item in privacy_markers[:6])}
@@ -1504,6 +1510,23 @@ def _integration_endpoint_item(endpoint: object) -> str:
""" """
def _flowchart_edge_item(edge: object, nodes: Iterable[object]) -> str:
node_names = {
str(getattr(node, "id", "")): str(getattr(node, "qualified_name", "") or getattr(node, "label", ""))
for node in nodes
}
source = node_names.get(str(getattr(edge, "source", "")), str(getattr(edge, "source", "")))
target = node_names.get(str(getattr(edge, "target", "")), str(getattr(edge, "target", "")))
label = str(getattr(edge, "label", "") or getattr(edge, "kind", "") or "link")
kind = str(getattr(edge, "kind", "") or "FLOW")
return f"""
<article class="object-context-item" data-html5-object-context-item="flow-edge">
<strong>{escape(label)}</strong>
<small>{escape(source)} -> {escape(target)} · {escape(kind)}</small>
</article>
"""
def _authoring_diff_item(line: object) -> str: def _authoring_diff_item(line: object) -> str:
kind = str(getattr(line, "kind", "")) kind = str(getattr(line, "kind", ""))
text = str(getattr(line, "text", "")) text = str(getattr(line, "text", ""))
@@ -1749,6 +1749,7 @@ async def html5_project_object_context(project_id: str, object_name: str) -> Res
ui = await get_object_ui(project_id, object_name) ui = await get_object_ui(project_id, object_name)
privacy = await object_privacy(project_id, object_name) privacy = await object_privacy(project_id, object_name)
integrations = _integrations_for_object_context(project_id, impact) integrations = _integrations_for_object_context(project_id, impact)
flowchart = await project_flowchart(project_id, focus=object_name, depth=1, limit=40)
runtime = _runtime_for_object_context(project_id, impact) runtime = _runtime_for_object_context(project_id, impact)
knowledge = _knowledge_for_object_context(schema, impact, ui) knowledge = _knowledge_for_object_context(schema, impact, ui)
return Response( return Response(
@@ -1762,6 +1763,7 @@ async def html5_project_object_context(project_id: str, object_name: str) -> Res
knowledge, knowledge,
privacy, privacy,
integrations, integrations,
flowchart,
), ),
media_type="text/html; charset=utf-8", media_type="text/html; charset=utf-8",
) )
+1
View File
@@ -322,6 +322,7 @@ def test_html5_object_context_fragment(tmp_path: Path):
assert "HTTPConnection" in context.text assert "HTTPConnection" in context.text
assert "https://api.example.local/orders" in context.text assert "https://api.example.local/orders" in context.text
assert "OUTBOUND" in context.text assert "OUTBOUND" in context.text
assert "data-html5-object-context-item=\"flow-edge\"" 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