Skip to content

Commit d511944

Browse files
committed
Cont working on the new menu in the backend
1 parent bbc7d6f commit d511944

37 files changed

+473
-666
lines changed

backend/infrahub/api/menu.py

Lines changed: 5 additions & 221 deletions
Original file line numberDiff line numberDiff line change
@@ -3,251 +3,35 @@
33
from typing import TYPE_CHECKING
44

55
from fastapi import APIRouter, Depends
6-
from pydantic import BaseModel, Field
76

87
from infrahub.api.dependencies import get_branch_dep, get_current_user, get_db
98
from infrahub.core import registry
109
from infrahub.core.branch import Branch # noqa: TCH001
11-
from infrahub.core.constants import InfrahubKind
1210
from infrahub.core.protocols import CoreMenuItem
13-
from infrahub.core.schema import NodeSchema
1411
from infrahub.log import get_logger
1512
from infrahub.menu.generator import generate_menu
1613
from infrahub.menu.models import Menu # noqa: TCH001
1714

1815
if TYPE_CHECKING:
1916
from infrahub.auth import AccountSession
20-
from infrahub.core.schema import MainSchemaTypes
2117
from infrahub.database import InfrahubDatabase
2218

2319

2420
log = get_logger()
2521
router = APIRouter(prefix="/menu")
2622

2723

28-
class InterfaceMenu(BaseModel):
29-
title: str = Field(..., description="Title of the menu item")
30-
path: str = Field(default="", description="URL endpoint if applicable")
31-
icon: str = Field(default="", description="The icon to show for the current view")
32-
children: list[InterfaceMenu] = Field(default_factory=list, description="Child objects")
33-
kind: str = Field(default="")
34-
35-
def __lt__(self, other: object) -> bool:
36-
if not isinstance(other, InterfaceMenu):
37-
raise NotImplementedError
38-
return self.title < other.title
39-
40-
def list_title(self) -> str:
41-
return f"All {self.title}(s)"
42-
43-
44-
def add_to_menu(structure: dict[str, list[InterfaceMenu]], menu_item: InterfaceMenu) -> None:
45-
all_items = InterfaceMenu(title=menu_item.list_title(), path=menu_item.path, icon=menu_item.icon)
46-
menu_item.path = ""
47-
menu_item.icon = ""
48-
for child in structure[menu_item.kind]:
49-
menu_item.children.append(child)
50-
if child.kind in structure:
51-
add_to_menu(structure, child)
52-
menu_item.children.sort()
53-
menu_item.children.insert(0, all_items)
54-
55-
56-
def _extract_node_icon(model: MainSchemaTypes) -> str:
57-
if not model.icon:
58-
return ""
59-
return model.icon
60-
61-
6224
@router.get("")
63-
async def get_menu(branch: Branch = Depends(get_branch_dep)) -> list[InterfaceMenu]:
64-
log.info("menu_request", branch=branch.name)
65-
66-
full_schema = registry.schema.get_full(branch=branch, duplicate=False)
67-
objects = InterfaceMenu(title="Objects", children=[])
68-
69-
structure: dict[str, list[InterfaceMenu]] = {}
70-
71-
ipam = InterfaceMenu(
72-
title="IPAM",
73-
children=[
74-
InterfaceMenu(
75-
title="Namespaces",
76-
path=f"/objects/{InfrahubKind.IPNAMESPACE}",
77-
icon=_extract_node_icon(full_schema[InfrahubKind.IPNAMESPACE]),
78-
),
79-
InterfaceMenu(
80-
title="IP Prefixes", path="/ipam/prefixes", icon=_extract_node_icon(full_schema[InfrahubKind.IPPREFIX])
81-
),
82-
InterfaceMenu(
83-
title="IP Addresses",
84-
path="/ipam/addresses?ipam-tab=ip-details",
85-
icon=_extract_node_icon(full_schema[InfrahubKind.IPADDRESS]),
86-
),
87-
],
88-
)
89-
90-
has_ipam = False
91-
92-
for key in full_schema.keys():
93-
model = full_schema[key]
94-
95-
if isinstance(model, NodeSchema) and (
96-
InfrahubKind.IPADDRESS in model.inherit_from or InfrahubKind.IPPREFIX in model.inherit_from
97-
):
98-
has_ipam = True
99-
100-
if not model.include_in_menu:
101-
continue
102-
103-
menu_name = model.menu_placement or "base"
104-
if menu_name not in structure:
105-
structure[menu_name] = []
106-
107-
structure[menu_name].append(
108-
InterfaceMenu(title=model.menu_title, path=f"/objects/{model.kind}", icon=model.icon or "", kind=model.kind)
109-
)
110-
111-
for menu_item in structure["base"]:
112-
if menu_item.kind in structure:
113-
add_to_menu(structure, menu_item)
114-
115-
objects.children.append(menu_item)
116-
117-
objects.children.sort()
118-
groups = InterfaceMenu(
119-
title="Object Management",
120-
children=[
121-
InterfaceMenu(
122-
title="All Groups",
123-
path=f"/objects/{InfrahubKind.GENERICGROUP}",
124-
icon=_extract_node_icon(full_schema[InfrahubKind.GENERICGROUP]),
125-
),
126-
InterfaceMenu(
127-
title="All Profiles",
128-
path=f"/objects/{InfrahubKind.PROFILE}",
129-
icon=_extract_node_icon(full_schema[InfrahubKind.PROFILE]),
130-
),
131-
InterfaceMenu(
132-
title="Resource Manager",
133-
path="/resource-manager",
134-
icon=_extract_node_icon(full_schema[InfrahubKind.RESOURCEPOOL]),
135-
),
136-
],
137-
)
138-
139-
unified_storage = InterfaceMenu(
140-
title="Unified Storage",
141-
children=[
142-
InterfaceMenu(title="Schema", path="/schema", icon="mdi:file-code"),
143-
InterfaceMenu(
144-
title="Repository",
145-
path=f"/objects/{InfrahubKind.GENERICREPOSITORY}",
146-
icon=_extract_node_icon(full_schema[InfrahubKind.GENERICREPOSITORY]),
147-
),
148-
InterfaceMenu(
149-
title="GraphQL Query",
150-
path=f"/objects/{InfrahubKind.GRAPHQLQUERY}",
151-
icon=_extract_node_icon(full_schema[InfrahubKind.GRAPHQLQUERY]),
152-
),
153-
],
154-
)
155-
change_control = InterfaceMenu(
156-
title="Change Control",
157-
children=[
158-
InterfaceMenu(title="Branches", path="/branches", icon="mdi:layers-triple"),
159-
InterfaceMenu(
160-
title="Proposed Changes",
161-
path="/proposed-changes",
162-
icon=_extract_node_icon(full_schema[InfrahubKind.PROPOSEDCHANGE]),
163-
),
164-
InterfaceMenu(
165-
title="Check Definition",
166-
path=f"/objects/{InfrahubKind.CHECKDEFINITION}",
167-
icon=_extract_node_icon(full_schema[InfrahubKind.CHECKDEFINITION]),
168-
),
169-
InterfaceMenu(title="Tasks", path="/tasks", icon="mdi:shield-check"),
170-
],
171-
)
172-
deployment = InterfaceMenu(
173-
title="Deployment",
174-
children=[
175-
InterfaceMenu(
176-
title="Artifact",
177-
path=f"/objects/{InfrahubKind.ARTIFACT}",
178-
icon=_extract_node_icon(full_schema[InfrahubKind.ARTIFACT]),
179-
),
180-
InterfaceMenu(
181-
title="Artifact Definition",
182-
path=f"/objects/{InfrahubKind.ARTIFACTDEFINITION}",
183-
icon=_extract_node_icon(full_schema[InfrahubKind.ARTIFACTDEFINITION]),
184-
),
185-
InterfaceMenu(
186-
title="Generator Definition",
187-
path=f"/objects/{InfrahubKind.GENERATORDEFINITION}",
188-
icon=_extract_node_icon(full_schema[InfrahubKind.GENERATORDEFINITION]),
189-
),
190-
InterfaceMenu(
191-
title="Generator Instance",
192-
path=f"/objects/{InfrahubKind.GENERATORINSTANCE}",
193-
icon=_extract_node_icon(full_schema[InfrahubKind.GENERATORINSTANCE]),
194-
),
195-
InterfaceMenu(
196-
title="Transformation",
197-
path=f"/objects/{InfrahubKind.TRANSFORM}",
198-
icon=_extract_node_icon(full_schema[InfrahubKind.TRANSFORM]),
199-
),
200-
],
201-
)
202-
203-
admin = InterfaceMenu(
204-
title="Admin",
205-
children=[
206-
InterfaceMenu(
207-
title="Role Management",
208-
path="/role-management",
209-
icon=_extract_node_icon(full_schema[InfrahubKind.BASEPERMISSION]),
210-
),
211-
InterfaceMenu(
212-
title="Credentials",
213-
path=f"/objects/{InfrahubKind.CREDENTIAL}",
214-
icon=_extract_node_icon(full_schema[InfrahubKind.CREDENTIAL]),
215-
),
216-
InterfaceMenu(
217-
title="Webhooks",
218-
children=[
219-
InterfaceMenu(
220-
title="Webhook",
221-
path=f"/objects/{InfrahubKind.STANDARDWEBHOOK}",
222-
icon=_extract_node_icon(full_schema[InfrahubKind.STANDARDWEBHOOK]),
223-
),
224-
InterfaceMenu(
225-
title="Custom Webhook",
226-
path=f"/objects/{InfrahubKind.CUSTOMWEBHOOK}",
227-
icon=_extract_node_icon(full_schema[InfrahubKind.CUSTOMWEBHOOK]),
228-
),
229-
],
230-
),
231-
],
232-
)
233-
234-
menu_items = [objects]
235-
if has_ipam:
236-
menu_items.append(ipam)
237-
menu_items.extend([groups, unified_storage, change_control, deployment, admin])
238-
239-
return menu_items
240-
241-
242-
@router.get("/new")
243-
async def get_new_menu(
25+
async def get_menu(
24426
db: InfrahubDatabase = Depends(get_db),
24527
branch: Branch = Depends(get_branch_dep),
24628
account_session: AccountSession = Depends(get_current_user),
24729
) -> Menu:
248-
log.info("new_menu_request", branch=branch.name)
30+
log.info("menu_request", branch=branch.name)
24931

250-
menu_items = await registry.manager.query(db=db, schema=CoreMenuItem, branch=branch, prefetch_relationships=True)
32+
menu_items = await registry.manager.query(
33+
db=db, schema=CoreMenuItem, branch=branch
34+
) # , prefetch_relationships=True)
25135
menu = await generate_menu(db=db, branch=branch, account=account_session, menu_items=menu_items)
25236

25337
return menu.to_rest()

backend/infrahub/core/initialization.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@
2020
from infrahub.core.node.resource_manager.ip_address_pool import CoreIPAddressPool
2121
from infrahub.core.node.resource_manager.ip_prefix_pool import CoreIPPrefixPool
2222
from infrahub.core.node.resource_manager.number_pool import CoreNumberPool
23-
from infrahub.core.protocols import CoreAccount, CoreMenuItem
23+
from infrahub.core.protocols import CoreAccount
2424
from infrahub.core.root import Root
2525
from infrahub.core.schema import SchemaRoot, core_models, internal_schema
2626
from infrahub.core.schema.manager import SchemaManager
2727
from infrahub.database import InfrahubDatabase
2828
from infrahub.exceptions import DatabaseError
2929
from infrahub.log import get_logger
3030
from infrahub.menu.menu import default_menu
31-
from infrahub.menu.models import MenuItemDefinition
31+
from infrahub.menu.utils import create_menu_children
3232
from infrahub.permissions import PermissionBackend
3333
from infrahub.storage import InfrahubObjectStorage
3434
from infrahub.utils import format_label
@@ -307,14 +307,6 @@ async def create_initial_permission(db: InfrahubDatabase) -> Node:
307307
return permission
308308

309309

310-
async def create_menu_children(db: InfrahubDatabase, parent: CoreMenuItem, children: list[MenuItemDefinition]) -> None:
311-
for child in children:
312-
obj = await child.to_node(db=db, parent=parent)
313-
await obj.save(db=db)
314-
if child.children:
315-
await create_menu_children(db=db, parent=obj, children=child.children)
316-
317-
318310
async def create_default_menu(db: InfrahubDatabase) -> None:
319311
for item in default_menu:
320312
obj = await item.to_node(db=db)

backend/infrahub/core/protocols.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ class CoreMenu(CoreNode):
137137
namespace: String
138138
name: String
139139
label: StringOptional
140+
kind: StringOptional
140141
path: StringOptional
141142
description: StringOptional
142143
icon: StringOptional

backend/infrahub/core/schema/definitions/core.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,12 @@
6767
"description": "Base node for the menu",
6868
"label": "Menu Item",
6969
"hierarchical": True,
70-
"uniqueness_constraints": [["namespace__value", "name__value"]],
70+
"human_friendly_id": ["namespace__value", "name__value"],
7171
"attributes": [
7272
{"name": "namespace", "kind": "Text", "regex": NAMESPACE_REGEX, "order_weight": 1000},
7373
{"name": "name", "kind": "Text", "order_weight": 1000},
7474
{"name": "label", "kind": "Text", "optional": True, "order_weight": 2000},
75+
{"name": "kind", "kind": "Text", "optional": True, "order_weight": 2500},
7576
{"name": "path", "kind": "Text", "optional": True, "order_weight": 2500},
7677
{"name": "description", "kind": "Text", "optional": True, "order_weight": 3000},
7778
{"name": "icon", "kind": "Text", "optional": True, "order_weight": 4000},

backend/infrahub/core/schema/schema_branch.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,6 @@ def process_pre_validation(self) -> None:
486486

487487
def process_validate(self) -> None:
488488
self.validate_names()
489-
self.validate_menu_placements()
490489
self.validate_kinds()
491490
self.validate_default_values()
492491
self.validate_count_against_cardinality()
@@ -899,28 +898,6 @@ def validate_names(self) -> None:
899898
):
900899
raise ValueError(f"{node.kind}: {rel.name} isn't allowed as a relationship name.")
901900

902-
def validate_menu_placements(self) -> None:
903-
menu_placements: dict[str, str] = {}
904-
905-
for name in list(self.nodes.keys()) + list(self.generics.keys()):
906-
node = self.get(name=name, duplicate=False)
907-
if node.menu_placement:
908-
try:
909-
placement_node = self.get(name=node.menu_placement, duplicate=False)
910-
except SchemaNotFoundError as exc:
911-
raise SchemaNotFoundError(
912-
branch_name=self.name,
913-
identifier=node.menu_placement,
914-
message=f"{node.kind} refers to an invalid menu placement node: {node.menu_placement}.",
915-
) from exc
916-
if node == placement_node:
917-
raise ValueError(f"{node.kind}: cannot be placed under itself in the menu") from None
918-
919-
if menu_placements.get(placement_node.kind) == node.kind:
920-
raise ValueError(f"{node.kind}: cyclic menu placement with {placement_node.kind}") from None
921-
922-
menu_placements[node.kind] = placement_node.kind
923-
924901
def validate_kinds(self) -> None:
925902
for name in list(self.nodes.keys()):
926903
node = self.get_node(name=name, duplicate=False)

backend/infrahub/menu/generator.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,31 +32,29 @@ async def generate_menu(
3232
full_schema = registry.schema.get_full(branch=branch, duplicate=False)
3333

3434
already_processed = []
35-
havent_been_processed = []
3635

3736
# Process the parent first
3837
for item in menu_items:
3938
full_name = get_full_name(item)
40-
parent = await item.parent.get_peer(db=db, peer_type=CoreMenuItem)
41-
if parent:
42-
havent_been_processed.append(full_name)
39+
parent1 = await item.parent.get_peer(db=db, peer_type=CoreMenuItem)
40+
if parent1:
4341
continue
4442
structure.data[full_name] = MenuItemDict.from_node(obj=item)
4543
already_processed.append(full_name)
4644

4745
# Process the children
48-
havent_been_processed = []
46+
havent_been_processed: list[str] = []
4947
for item in menu_items:
5048
full_name = get_full_name(item)
5149
if full_name in already_processed:
5250
continue
5351

54-
parent = await item.parent.get_peer(db=db, peer_type=CoreMenuItem)
55-
if not parent:
52+
parent2 = await item.parent.get_peer(db=db, peer_type=CoreMenuItem)
53+
if not parent2:
5654
havent_been_processed.append(full_name)
5755
continue
5856

59-
parent_full_name = get_full_name(parent)
57+
parent_full_name = get_full_name(parent2)
6058
menu_item = structure.find_item(name=parent_full_name)
6159
if menu_item:
6260
child_item = MenuItemDict.from_node(obj=item)
@@ -65,8 +63,8 @@ async def generate_menu(
6563
log.warning(
6664
"new_menu_request: unable to find the parent menu item",
6765
branch=branch.name,
68-
menu_item=item.name.value,
69-
parent_item=parent.name.value,
66+
menu_item=child_item.identifier,
67+
parent_item=parent_full_name,
7068
)
7169

7270
default_menu = structure.find_item(name=FULL_DEFAULT_MENU)

0 commit comments

Comments
 (0)