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

This commit is contained in:
2026-05-17 01:05:22 +03:00
parent 48070f0f70
commit 477a94d302
3 changed files with 99 additions and 21 deletions
+66 -20
View File
@@ -437,6 +437,7 @@ def render_html5_object_context(
privacy: object | None = None,
integrations: Iterable[object] | None = None,
flowchart: object | None = None,
mode: str = "overview",
) -> str:
if schema is None or impact is None:
return f"""
@@ -466,9 +467,50 @@ def render_html5_object_context(
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 []
normalized_mode = mode if mode in {"overview", "schema", "impact", "privacy"} else "overview"
if normalized_mode == "schema":
compact_body = (
''.join(_named_node_item("attr", item) for item in attributes[:12])
or '<p class="muted padded">Реквизиты не найдены</p>'
)
compact_body += ''.join(_tabular_section_item(item) for item in sections[:8])
compact_body += ''.join(_ui_form_item(item) for item in ui_forms[:8])
elif normalized_mode == "impact":
compact_body = ''.join(_integration_endpoint_item(item) for item in integration_items[:8])
compact_body += ''.join(_named_node_item("command", item) for item in commands[:8])
compact_body += ''.join(_named_node_item("read", item) for item in query_tables[:8])
compact_body += ''.join(_named_node_item("write", item) for item in writes[:8])
compact_body += ''.join(_named_node_item("call", item) for item in callees[:8])
compact_body += ''.join(_flowchart_edge_item(item, flow_nodes) for item in flow_edges[:8])
compact_body += ''.join(_named_node_item("routine", item) for item in routines[:8])
compact_body += ''.join(_named_node_item("job", item) for item in jobs[:6])
compact_body = compact_body or '<p class="muted padded">Impact-связи не найдены</p>'
elif normalized_mode == "privacy":
compact_body = ''.join(_role_access_item(item) for item in grants[:12])
compact_body += ''.join(_privacy_marker_item(item) for item in privacy_markers[:12])
compact_body = compact_body or '<p class="muted padded">Доступы и privacy-маркеры не найдены</p>'
else:
compact_body = (
''.join(_named_node_item("attr", item) for item in attributes[:6])
or '<p class="muted padded">Реквизиты не найдены</p>'
)
compact_body += ''.join(_tabular_section_item(item) for item in sections[:4])
compact_body += ''.join(_ui_form_item(item) for item in ui_forms[:4])
compact_body += ''.join(_role_access_item(item) for item in grants[:6])
compact_body += ''.join(_integration_endpoint_item(item) for item in integration_items[:4])
compact_body += ''.join(_named_node_item("command", item) for item in commands[:6])
compact_body += ''.join(_named_node_item("read", item) for item in query_tables[:4])
compact_body += ''.join(_named_node_item("write", item) for item in writes[:4])
compact_body += ''.join(_named_node_item("call", item) for item in callees[:6])
compact_body += ''.join(_flowchart_edge_item(item, flow_nodes) for item in flow_edges[:8])
compact_body += ''.join(_runtime_summary_item(item) for item in runtime_items[:6])
compact_body += ''.join(_knowledge_record_item(item) for item in knowledge_items[:6])
compact_body += ''.join(_privacy_marker_item(item) for item in privacy_markers[:6])
compact_body += ''.join(_named_node_item("routine", item) for item in routines[:6])
compact_body += ''.join(_named_node_item("job", item) for item in jobs[:4])
return f"""
<div class="object-context" data-html5-object-context data-html5-object-name="{escape(str(name))}">
<div class="panel-title">Object context</div>
<div class="object-context" data-html5-object-context data-html5-object-name="{escape(str(name))}" data-html5-object-mode="{escape(normalized_mode)}">
<div class="panel-title">Object context · {escape(normalized_mode)}</div>
<article class="object-focus">
<strong>{escape(str(name))}</strong>
<span>{escape(str(getattr(obj, "kind", "object")))}</span>
@@ -493,21 +535,7 @@ def render_html5_object_context(
{_metric("Privacy", len(privacy_markers))}
</dl>
<div class="compact-list">
{''.join(_named_node_item("attr", item) for item in attributes[:6]) or '<p class="muted padded">Реквизиты не найдены</p>'}
{''.join(_tabular_section_item(item) for item in sections[:4])}
{''.join(_ui_form_item(item) for item in ui_forms[:4])}
{''.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(_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])}
{''.join(_named_node_item("routine", item) for item in routines[:6])}
{''.join(_named_node_item("job", item) for item in jobs[:4])}
{compact_body}
</div>
</div>
"""
@@ -1539,9 +1567,27 @@ def _object_action_links(project_id: str, object_name: str, lineage_id: object,
)
return f"""
<nav class="object-actions" data-html5-object-actions>
<a class="button" href="/projects/{quoted_project}/objects/schema/{quoted_object}">Schema</a>
<a class="button" href="/projects/{quoted_project}/objects/impact/{quoted_object}">Impact</a>
<a class="button" href="/projects/{quoted_project}/objects/privacy/{quoted_object}">Privacy</a>
<a
class="button"
href="/projects/{quoted_project}/objects/schema/{quoted_object}"
hx-get="/html5/projects/{quoted_project}/objects/context/{quoted_object}?mode=schema"
hx-target="[data-html5-object-context]"
hx-swap="outerHTML"
>Schema</a>
<a
class="button"
href="/projects/{quoted_project}/objects/impact/{quoted_object}"
hx-get="/html5/projects/{quoted_project}/objects/context/{quoted_object}?mode=impact"
hx-target="[data-html5-object-context]"
hx-swap="outerHTML"
>Impact</a>
<a
class="button"
href="/projects/{quoted_project}/objects/privacy/{quoted_object}"
hx-get="/html5/projects/{quoted_project}/objects/context/{quoted_object}?mode=privacy"
hx-target="[data-html5-object-context]"
hx-swap="outerHTML"
>Privacy</a>
<a
class="button"
href="/html5/projects/{quoted_project}/flowchart?focus={quoted_object}"
+2 -1
View File
@@ -1758,7 +1758,7 @@ async def html5_project_flowchart(
@app.get("/html5/projects/{project_id}/objects/context/{object_name}")
async def html5_project_object_context(project_id: str, object_name: str) -> Response:
async def html5_project_object_context(project_id: str, object_name: str, mode: str = "overview") -> Response:
schema = await get_object_schema(project_id, object_name)
impact = await get_object_impact(project_id, object_name)
access = await get_object_access(project_id, object_name)
@@ -1782,6 +1782,7 @@ async def html5_project_object_context(project_id: str, object_name: str) -> Res
privacy,
integrations,
flowchart,
mode,
)
flowchart_context = render_html5_flowchart(project_id, flowchart, focus=object_name, oob=True)
source_context = render_html5_source(source_node, oob=True) if source_node is not None else ""
+31
View File
@@ -318,6 +318,10 @@ def test_html5_object_context_fragment(tmp_path: Path):
assert "data-html5-object-actions" in context.text
assert f"/projects/{project_id}/objects/schema/%D0%94%D0%BE%D0%BA%D1%83%D0%BC%D0%B5%D0%BD%D1%82.%D0%97%D0%B0%D0%BA%D0%B0%D0%B7%D0%9F%D0%BE%D0%BA%D1%83%D0%BF%D0%B0%D1%82%D0%B5%D0%BB%D1%8F" in context.text
assert f"/projects/{project_id}/objects/impact/%D0%94%D0%BE%D0%BA%D1%83%D0%BC%D0%B5%D0%BD%D1%82.%D0%97%D0%B0%D0%BA%D0%B0%D0%B7%D0%9F%D0%BE%D0%BA%D1%83%D0%BF%D0%B0%D1%82%D0%B5%D0%BB%D1%8F" in context.text
assert "mode=schema" in context.text
assert "mode=impact" in context.text
assert "mode=privacy" in context.text
assert 'hx-target="[data-html5-object-context]"' in context.text
assert 'hx-target="[data-html5-flowchart]"' in context.text
assert 'hx-target="[data-html5-source]"' in context.text
assert 'hx-target="[data-html5-symbol-detail]"' in context.text
@@ -357,6 +361,33 @@ def test_html5_object_context_fragment(tmp_path: Path):
assert "read, write, post" in context.text or "post, read, write" in context.text
assert "<html" not in context.text
schema_context = client.get(
f"/html5/projects/{project_id}/objects/context/Документ.ЗаказПокупателя",
params={"mode": "schema"},
)
assert schema_context.status_code == 200
assert 'data-html5-object-mode="schema"' in schema_context.text
assert "Object context · schema" in schema_context.text
assert "Контрагент" in schema_context.text
impact_context = client.get(
f"/html5/projects/{project_id}/objects/context/Документ.ЗаказПокупателя",
params={"mode": "impact"},
)
assert impact_context.status_code == 200
assert 'data-html5-object-mode="impact"' in impact_context.text
assert "Object context · impact" in impact_context.text
assert "HTTPConnection" in impact_context.text
privacy_context = client.get(
f"/html5/projects/{project_id}/objects/context/Документ.ЗаказПокупателя",
params={"mode": "privacy"},
)
assert privacy_context.status_code == 200
assert 'data-html5-object-mode="privacy"' in privacy_context.text
assert "Object context · privacy" in privacy_context.text
assert "PERSONAL_DATA" in privacy_context.text
def test_html5_flowchart_fragment(tmp_path: Path):
client = TestClient(app)