Coverage for src / lexigram / admin / navigation / clusters.py: 25%
52 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-13 22:14 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-13 22:14 +0800
1"""Cluster navigation helpers for Lexigram Admin.
3A cluster groups contributor navigation items (declared via
4``NavigationContribution(group=...)``) behind a single top-level sidebar
5entry that opens a center with its own secondary sidebar — mirroring how
6the Configuration Center works for settings.
8The infrastructure group (web, sql, cache, events, queue, tasks) is the
9built-in cluster.
10"""
12from __future__ import annotations
14from typing import Any
16CLUSTER_GROUP = "infrastructure"
17CLUSTER_LABEL = "Infrastructure"
18CLUSTER_URL = "/admin/infrastructure"
19CLUSTER_ICON = "server"
21__all__ = [
22 "CLUSTER_GROUP",
23 "CLUSTER_ICON",
24 "CLUSTER_LABEL",
25 "CLUSTER_URL",
26 "build_secondary_nav",
27 "cluster_child_href",
28 "cluster_items",
29 "collapse_cluster_in_primary",
30 "is_cluster_path",
31]
34def cluster_child_href(url: str | None) -> str:
35 """Namespace a cluster child URL under the cluster center prefix.
37 Maps e.g. ``/admin/web`` -> ``/admin/infrastructure/web`` so cluster
38 areas live under a single center namespace, mirroring how settings
39 sub-pages are nested below ``/admin/settings``. URLs already inside
40 the namespace, non-admin URLs, and empty values are returned unchanged.
41 """
42 if not url or not url.startswith("/admin/"):
43 return url or ""
44 cleaned = url.rstrip("/")
45 if cleaned == CLUSTER_URL or cleaned.startswith(CLUSTER_URL + "/"):
46 return url
47 relative = cleaned.removeprefix("/admin/")
48 return f"{CLUSTER_URL.rstrip('/')}/{relative}"
51def cluster_items(groups: dict[str, Any] | None) -> list[Any]:
52 """Return the top-level items contributed to the cluster group."""
53 if not groups:
54 return []
55 return list(groups.get(CLUSTER_GROUP, ()) or ())
58def is_cluster_path(current_path: str | None, items: list[Any]) -> bool:
59 """Return True when the path belongs to the cluster center.
61 Matches the landing URL plus every top-level item URL (prefix match,
62 so child pages count as well) contributed by the cluster group.
63 """
64 if not current_path:
65 return False
66 if current_path == CLUSTER_URL or current_path.startswith(CLUSTER_URL + "/"):
67 return True
68 return any(
69 current_path == item.url or current_path.startswith(item.url + "/")
70 for item in items
71 )
74def _is_active(current_path: str | None, url: str) -> bool:
75 return bool(current_path) and (
76 current_path == url or current_path.startswith(url + "/")
77 )
80def build_secondary_nav(
81 items: list[Any],
82 current_path: str | None,
83) -> list[dict[str, Any]]:
84 """Build secondary sidebar entries for the cluster center.
86 Each top-level item becomes an entry with an ``active`` flag; child
87 contributions are nested as ``children`` entries. Parents are marked
88 active when their own URL or any child URL matches the current path.
89 """
90 result: list[dict[str, Any]] = []
91 for item in items:
92 children = [
93 {
94 "label": child.label,
95 "href": cluster_child_href(child.url),
96 "icon": child.icon,
97 "active": _is_active(current_path, child.url),
98 }
99 for child in item.children
100 ]
101 entry: dict[str, Any] = {
102 "label": item.label,
103 "href": cluster_child_href(item.url),
104 "icon": item.icon,
105 "active": _is_active(current_path, item.url)
106 or any(child["active"] for child in children),
107 }
108 if children:
109 entry["children"] = children
110 result.append(entry)
111 return result
114def collapse_cluster_in_primary(
115 flat_items: list[dict[str, Any]],
116 current_path: str | None,
117 items: list[Any],
118) -> list[dict[str, Any]]:
119 """Remove the cluster group from the primary sidebar.
121 The group header (``CLUSTER_LABEL``) and all of its items are dropped
122 entirely; the center is reached from the user dropdown and its
123 secondary sidebar. Items outside the group are preserved in order.
124 """
125 in_cluster = False
126 result: list[dict[str, Any]] = []
127 for item in flat_items:
128 if not isinstance(item, dict):
129 result.append(item)
130 continue
131 if item.get("is_group"):
132 in_cluster = item.get("label") == CLUSTER_LABEL
133 if not in_cluster:
134 result.append(item)
135 continue
136 if in_cluster:
137 continue
138 result.append(item)
139 return result