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

1"""Cluster navigation helpers for Lexigram Admin. 

2 

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. 

7 

8The infrastructure group (web, sql, cache, events, queue, tasks) is the 

9built-in cluster. 

10""" 

11 

12from __future__ import annotations 

13 

14from typing import Any 

15 

16CLUSTER_GROUP = "infrastructure" 

17CLUSTER_LABEL = "Infrastructure" 

18CLUSTER_URL = "/admin/infrastructure" 

19CLUSTER_ICON = "server" 

20 

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] 

32 

33 

34def cluster_child_href(url: str | None) -> str: 

35 """Namespace a cluster child URL under the cluster center prefix. 

36 

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}" 

49 

50 

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 ()) 

56 

57 

58def is_cluster_path(current_path: str | None, items: list[Any]) -> bool: 

59 """Return True when the path belongs to the cluster center. 

60 

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 ) 

72 

73 

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 ) 

78 

79 

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. 

85 

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 

112 

113 

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. 

120 

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