diff --git a/services/api-server/src/api_server/html5.py b/services/api-server/src/api_server/html5.py index 40681d3..c7f5c3e 100644 --- a/services/api-server/src/api_server/html5.py +++ b/services/api-server/src/api_server/html5.py @@ -326,6 +326,7 @@ def render_html5_object_context( knowledge: Iterable[object] | None = None, privacy: object | None = None, integrations: Iterable[object] | None = None, + flowchart: object | None = None, ) -> str: if schema is None or impact is None: return f""" @@ -353,6 +354,8 @@ def render_html5_object_context( knowledge_items = list(knowledge or []) privacy_markers = getattr(privacy, "markers", []) if privacy is not None else [] 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"""
Object context
@@ -372,6 +375,8 @@ def render_html5_object_context( {_metric("Writes", len(writes))} {_metric("Calls", len(callees))} {_metric("Integrations", len(integration_items))} + {_metric("Graph nodes", len(flow_nodes))} + {_metric("Graph edges", len(flow_edges))} {_metric("Runtime", len(runtime_items))} {_metric("Knowledge", len(knowledge_items))} {_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("write", item) for item in writes[:4])} {''.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(_knowledge_record_item(item) for item in knowledge_items[: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""" +
+ {escape(label)} + {escape(source)} -> {escape(target)} ยท {escape(kind)} +
+ """ + + def _authoring_diff_item(line: object) -> str: kind = str(getattr(line, "kind", "")) text = str(getattr(line, "text", "")) diff --git a/services/api-server/src/api_server/main.py b/services/api-server/src/api_server/main.py index a122a43..d9ecb95 100644 --- a/services/api-server/src/api_server/main.py +++ b/services/api-server/src/api_server/main.py @@ -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) privacy = await object_privacy(project_id, object_name) 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) knowledge = _knowledge_for_object_context(schema, impact, ui) return Response( @@ -1762,6 +1763,7 @@ async def html5_project_object_context(project_id: str, object_name: str) -> Res knowledge, privacy, integrations, + flowchart, ), media_type="text/html; charset=utf-8", ) diff --git a/services/api-server/tests/test_api.py b/services/api-server/tests/test_api.py index c1e7f6b..8526598 100644 --- a/services/api-server/tests/test_api.py +++ b/services/api-server/tests/test_api.py @@ -322,6 +322,7 @@ def test_html5_object_context_fragment(tmp_path: Path): assert "HTTPConnection" in context.text assert "https://api.example.local/orders" 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 errors" in context.text assert "125.0 ms" in context.text