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

This commit is contained in:
2026-05-17 00:28:54 +03:00
parent 79ae2b3023
commit c871a8bbd2
3 changed files with 86 additions and 2 deletions
@@ -325,6 +325,7 @@ def render_html5_object_context(
runtime: Iterable[object] | None = None, runtime: Iterable[object] | None = None,
knowledge: Iterable[object] | None = None, knowledge: Iterable[object] | None = None,
privacy: object | None = None, privacy: object | None = None,
integrations: Iterable[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"""
@@ -340,13 +341,18 @@ def render_html5_object_context(
modules = getattr(impact, "modules", []) or [] modules = getattr(impact, "modules", []) or []
routines = getattr(impact, "routines", []) or [] routines = getattr(impact, "routines", []) or []
forms = getattr(impact, "forms", []) or [] forms = getattr(impact, "forms", []) or []
commands = getattr(impact, "commands", []) or []
roles = getattr(impact, "roles", []) or [] roles = getattr(impact, "roles", []) or []
jobs = getattr(impact, "jobs", []) or [] jobs = getattr(impact, "jobs", []) or []
callees = getattr(impact, "callees", []) or []
query_tables = getattr(impact, "query_tables", []) or []
writes = getattr(impact, "writes", []) or []
grants = getattr(access, "grants", []) if access is not None else [] grants = getattr(access, "grants", []) if access is not None else []
ui_forms = getattr(ui, "forms", []) if ui is not None else [] ui_forms = getattr(ui, "forms", []) if ui is not None else []
runtime_items = list(runtime or []) runtime_items = list(runtime or [])
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 [])
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>
@@ -360,7 +366,12 @@ def render_html5_object_context(
{_metric("Modules", len(modules))} {_metric("Modules", len(modules))}
{_metric("Routines", len(routines))} {_metric("Routines", len(routines))}
{_metric("Forms", len(ui_forms) or len(forms))} {_metric("Forms", len(ui_forms) or len(forms))}
{_metric("Commands", len(commands))}
{_metric("Roles", len(grants) or len(roles))} {_metric("Roles", len(grants) or len(roles))}
{_metric("Reads", len(query_tables))}
{_metric("Writes", len(writes))}
{_metric("Calls", len(callees))}
{_metric("Integrations", len(integration_items))}
{_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))}
@@ -370,6 +381,11 @@ def render_html5_object_context(
{''.join(_tabular_section_item(item) for item in sections[:4])} {''.join(_tabular_section_item(item) for item in sections[:4])}
{''.join(_ui_form_item(item) for item in ui_forms[:4])} {''.join(_ui_form_item(item) for item in ui_forms[:4])}
{''.join(_role_access_item(item) for item in grants[:6])} {''.join(_role_access_item(item) for item in grants[:6])}
{''.join(_integration_endpoint_item(item) for item in integration_items[:4])}
{''.join(_named_node_item("command", item) for item in commands[:6])}
{''.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(_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])}
@@ -1475,6 +1491,19 @@ def _privacy_marker_item(marker: object) -> str:
""" """
def _integration_endpoint_item(endpoint: object) -> str:
name = str(getattr(endpoint, "name", "") or "integration")
kind = _enum_text(getattr(endpoint, "kind", "UNKNOWN"))
direction = str(getattr(endpoint, "direction", "UNKNOWN") or "UNKNOWN")
owner = str(getattr(endpoint, "owner", "") or "owner unavailable")
return f"""
<article class="object-context-item" data-html5-object-context-item="integration">
<strong>{escape(name)}</strong>
<small>{escape(kind)} · {escape(direction)} · {escape(owner)}</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", ""))
+40 -1
View File
@@ -1748,10 +1748,21 @@ async def html5_project_object_context(project_id: str, object_name: str) -> Res
access = await get_object_access(project_id, object_name) access = await get_object_access(project_id, object_name)
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)
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(
render_html5_object_context(project_id, schema, impact, access, ui, runtime, knowledge, privacy), render_html5_object_context(
project_id,
schema,
impact,
access,
ui,
runtime,
knowledge,
privacy,
integrations,
),
media_type="text/html; charset=utf-8", media_type="text/html; charset=utf-8",
) )
@@ -8178,6 +8189,34 @@ def _knowledge_for_object_context(
return sorted(records, key=lambda item: item.title.lower())[:12] return sorted(records, key=lambda item: item.title.lower())[:12]
def _integrations_for_object_context(
project_id: str,
impact: ObjectImpactResponse,
) -> list[IntegrationEndpointResponse]:
owner_names = {
name
for group in [impact.modules, impact.routines]
for item in group
for name in [item.qualified_name, item.name]
if name
}
if not owner_names:
return []
snapshot = _project_snapshot_or_404(project_id)
return [
IntegrationEndpointResponse(
endpoint_id=endpoint.endpoint_id,
name=endpoint.name,
kind=endpoint.kind.value,
direction=endpoint.direction,
owner=endpoint.owner,
attributes=endpoint.attributes,
)
for endpoint in build_integration_topology(snapshot).endpoints
if endpoint.owner in owner_names
]
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:
+17 -1
View File
@@ -250,7 +250,19 @@ def test_html5_object_context_fragment(tmp_path: Path):
) )
module = tmp_path / "Documents" / "ЗаказПокупателя" / "Ext" / "ObjectModule.bsl" module = tmp_path / "Documents" / "ЗаказПокупателя" / "Ext" / "ObjectModule.bsl"
module.parent.mkdir(parents=True) module.parent.mkdir(parents=True)
module.write_text("Процедура ПровестиКоманда()\nКонецПроцедуры\n", encoding="utf-8") module.write_text(
"""
Процедура ПровестиКоманда()
ПроверитьКонтрагента();
Соединение = Новый HTTPСоединение("api.example.local");
Адрес = "https://api.example.local/orders";
КонецПроцедуры
Процедура ПроверитьКонтрагента()
КонецПроцедуры
""",
encoding="utf-8",
)
client = TestClient(app) client = TestClient(app)
indexed = client.post("/projects/index", json={"path": str(tmp_path), "project_id": project_id}) indexed = client.post("/projects/index", json={"path": str(tmp_path), "project_id": project_id})
assert indexed.status_code == 200 assert indexed.status_code == 200
@@ -306,6 +318,10 @@ def test_html5_object_context_fragment(tmp_path: Path):
assert "ФормаДокумента" in context.text assert "ФормаДокумента" in context.text
assert "Провести" in context.text assert "Провести" in context.text
assert "ПровестиКоманда" in context.text assert "ПровестиКоманда" in context.text
assert "ПроверитьКонтрагента" in context.text
assert "HTTPConnection" in context.text
assert "https://api.example.local/orders" in context.text
assert "OUTBOUND" 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