Skip to content

Commit 4b453e0

Browse files
committed
fix ordering of sites for segment - site A should be on left, B on right
1 parent fb2cfa2 commit 4b453e0

File tree

2 files changed

+89
-28
lines changed

2 files changed

+89
-28
lines changed

cesnet_service_path_plugin/templates/cesnet_service_path_plugin/inc/topology_segment_card.html

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,26 @@
1010
<div class="col col-12">
1111
<div class="card">
1212
<h5 class="card-header">
13-
{% trans "Topologies" %}
14-
{% for topology_title, topology_data in topologies.items %}
15-
{% if topologies|length > 1 %}
16-
<button class="btn btn-ghost-primary btn-sm topology-btn {% if forloop.first %}active{% endif %}"
17-
id="btn-topology-{{ forloop.counter }}"
18-
onclick="showTopologyWithButtons('cy-topology-viewer', 'cy-topology-data-{{ forloop.counter }}', '{{ forloop.counter }}')">
19-
{{ topology_title }}
20-
</button>
21-
{% else %}
22-
{{ topology_title }}
23-
{% endif %}
24-
{% endfor %}
25-
13+
{% if topologies|length > 1 %}
14+
{% trans "Topologies" %}
15+
{% else %}
16+
{% trans "Topology of" %}
17+
{% endif %}
18+
{% for topology_title, topology_data in topologies.items %}
19+
{% if topologies|length > 1 %}
20+
<button class="btn btn-ghost-primary btn-sm topology-btn {% if forloop.first %}active{% endif %}"
21+
id="btn-topology-{{ forloop.counter }}"
22+
onclick="showTopologyWithButtons('cy-topology-viewer', 'cy-topology-data-{{ forloop.counter }}', '{{ forloop.counter }}')">
23+
{{ topology_title }}
24+
</button>
25+
{% else %}
26+
{{ topology_title }}
27+
{% endif %}
28+
{% endfor %}
2629
</h5>
27-
2830
<div class="card-body">
2931
<!-- Single Topology Container -->
3032
<div class="cy-topology" id="cy-topology-viewer"></div>
31-
3233
<!-- Hidden topology data containers -->
3334
{% for topology_title, topology_data in topologies.items %}
3435
<div id="cy-topology-data-{{ forloop.counter }}"
@@ -39,9 +40,7 @@ <h5 class="card-header">
3940
</div>
4041
</div>
4142
</div>
42-
43-
44-
43+
4544
<style>
4645
.card-header {
4746
display: flex;

cesnet_service_path_plugin/utils/utils_topology.py

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
"""
1616

1717
import logging
18-
from pprint import pprint
1918
from typing import Dict, List, Set, Optional, Any
2019

2120
logger = logging.getLogger(__name__)
@@ -48,12 +47,15 @@ def sort_nodes_by_path_traversal(self) -> None:
4847
4948
This method:
5049
1. Finds endpoint sites (sites with only one edge)
51-
2. Traverses the graph from an endpoint
50+
2. Traverses the graph from an endpoint (preferring site_a)
5251
3. Reorders nodes to match the traversal order
5352
5453
Only affects site and circuit nodes that are children of segments.
5554
Service and segment nodes maintain their original order.
5655
"""
56+
# First pass: mark site_a nodes for each segment
57+
self._mark_site_a_nodes()
58+
5759
# Group nodes by parent segment
5860
segments = {}
5961
non_segment_children = []
@@ -81,12 +83,35 @@ def sort_nodes_by_path_traversal(self) -> None:
8183
# Sort nodes within each segment
8284
sorted_segment_nodes = []
8385
for segment_id, segment_nodes in segments.items():
84-
sorted_nodes = self._sort_segment_nodes(segment_nodes, edge_map)
86+
sorted_nodes = self._sort_segment_nodes(segment_nodes, edge_map, segment_id)
8587
sorted_segment_nodes.extend(sorted_nodes)
8688

8789
# Reconstruct nodes list: service/segment nodes first, then sorted segment children
8890
self.nodes = non_segment_children + sorted_segment_nodes
8991

92+
def _mark_site_a_nodes(self) -> None:
93+
"""
94+
Mark nodes that represent site_a in their segments.
95+
This helps determine the correct starting point for traversal.
96+
"""
97+
# Build a map of segment_id -> site_a_id from segment nodes
98+
segment_site_a_map = {}
99+
100+
for node in self.nodes:
101+
if node["data"].get("type") == "segment":
102+
segment_id = node["data"]["id"]
103+
# Look for site_a_id stored during segment creation
104+
if "site_a_id" in node["data"]:
105+
segment_site_a_map[segment_id] = node["data"]["site_a_id"]
106+
107+
# Mark site nodes that are site_a
108+
for node in self.nodes:
109+
if node["data"].get("type") == "site":
110+
parent_segment = node["data"].get("parent")
111+
if parent_segment and parent_segment in segment_site_a_map:
112+
if node["data"]["id"] == segment_site_a_map[parent_segment]:
113+
node["data"]["is_site_a"] = True
114+
90115
def _build_edge_map(self) -> Dict[str, List[str]]:
91116
"""
92117
Build a bidirectional edge map for graph traversal.
@@ -111,13 +136,16 @@ def _build_edge_map(self) -> Dict[str, List[str]]:
111136

112137
return edge_map
113138

114-
def _sort_segment_nodes(self, segment_nodes: List[Dict], edge_map: Dict[str, List[str]]) -> List[Dict]:
139+
def _sort_segment_nodes(
140+
self, segment_nodes: List[Dict], edge_map: Dict[str, List[str]], segment_id: str = None
141+
) -> List[Dict]:
115142
"""
116143
Sort nodes within a segment by traversing the graph path.
117144
118145
Args:
119146
segment_nodes: List of site and circuit nodes in this segment
120147
edge_map: Bidirectional edge mapping
148+
segment_id: Optional segment ID to determine site_a preference
121149
122150
Returns:
123151
Sorted list of nodes following the graph path
@@ -129,17 +157,37 @@ def _sort_segment_nodes(self, segment_nodes: List[Dict], edge_map: Dict[str, Lis
129157
node_lookup = {node["data"]["id"]: node for node in segment_nodes}
130158
node_ids = set(node_lookup.keys())
131159

132-
# Find endpoint: a site with only one connection to nodes in this segment
133-
start_node_id = None
160+
# Find all endpoint sites (sites with only one connection)
161+
endpoint_sites = []
134162
for node_id in node_ids:
135163
node = node_lookup[node_id]
136164
if node["data"]["type"] == "site":
137165
# Count connections to other nodes in this segment
138166
connections = [conn for conn in edge_map.get(node_id, []) if conn in node_ids]
139167
if len(connections) == 1:
168+
endpoint_sites.append(node_id)
169+
170+
# Determine starting node
171+
start_node_id = None
172+
173+
# If we have segment_id and can identify site_a, prefer it as start
174+
if segment_id and endpoint_sites:
175+
# Look for site_a marker in the segment nodes
176+
# Check if any endpoint has site_a_id in its data
177+
for node_id in endpoint_sites:
178+
node = node_lookup[node_id]
179+
# Check if this node has a marker indicating it's site_a
180+
if node["data"].get("is_site_a"):
140181
start_node_id = node_id
141182
break
142183

184+
# If no site_a marker found, use first endpoint
185+
if start_node_id is None and endpoint_sites:
186+
start_node_id = endpoint_sites[0]
187+
elif endpoint_sites:
188+
# No segment context, just use first endpoint
189+
start_node_id = endpoint_sites[0]
190+
143191
# If no endpoint found (shouldn't happen in a path), use first site
144192
if start_node_id is None:
145193
for node_id in node_ids:
@@ -204,13 +252,14 @@ def add_service_path_node(self, service_path) -> str:
204252
)
205253
return node_id
206254

207-
def add_segment_node(self, segment, parent_id: Optional[str] = None) -> str:
255+
def add_segment_node(self, segment, parent_id: Optional[str] = None, site_a_id: Optional[str] = None) -> str:
208256
"""
209257
Add a segment node to the topology.
210258
211259
Args:
212260
segment: Segment model instance
213261
parent_id: Optional parent node ID (e.g., service path ID)
262+
site_a_id: Optional site_a ID for ordering reference
214263
215264
Returns:
216265
str: The node ID for the segment
@@ -229,6 +278,9 @@ def add_segment_node(self, segment, parent_id: Optional[str] = None) -> str:
229278
if parent_id:
230279
node_data["parent"] = parent_id
231280

281+
if site_a_id:
282+
node_data["site_a_id"] = site_a_id
283+
232284
self.nodes.append({"data": node_data})
233285
return node_id
234286

@@ -480,7 +532,12 @@ def build_service_path_topology(service_path) -> Dict[str, List[Dict]]:
480532

481533
# Process each segment
482534
for segment in segments:
483-
segment_id = builder.add_segment_node(segment, parent_id=service_path_id)
535+
# Add segment sites first to get their IDs
536+
site_a_id = f"site-{segment.site_a.pk}"
537+
site_b_id = f"site-{segment.site_b.pk}"
538+
539+
# Add segment node with site_a reference
540+
segment_id = builder.add_segment_node(segment, parent_id=service_path_id, site_a_id=site_a_id)
484541

485542
# Add segment sites
486543
site_a_id = builder.add_or_update_site_node(segment.site_a, segment_id)
@@ -492,6 +549,7 @@ def build_service_path_topology(service_path) -> Dict[str, List[Dict]]:
492549

493550
# Sort nodes by path traversal order
494551
builder.sort_nodes_by_path_traversal()
552+
495553
return builder.get_topology_data()
496554

497555

@@ -512,8 +570,12 @@ def build_segment_topology(segment) -> Dict[str, List[Dict]]:
512570
"""
513571
builder = TopologyBuilder()
514572

515-
# Add segment as root node (no parent)
516-
segment_id = builder.add_segment_node(segment, parent_id=None)
573+
# Get site IDs first
574+
site_a_id = f"site-{segment.site_a.pk}"
575+
site_b_id = f"site-{segment.site_b.pk}"
576+
577+
# Add segment as root node (no parent) with site_a reference
578+
segment_id = builder.add_segment_node(segment, parent_id=None, site_a_id=site_a_id)
517579

518580
# Add segment sites
519581
site_a_id = builder.add_or_update_site_node(segment.site_a, segment_id)

0 commit comments

Comments
 (0)