31
31
TIMEOUT_SECONDS = 60 * 30 # 30 minutes
32
32
33
33
34
+ def build_spans_tree (spans_data : list [dict ]) -> list [dict ]:
35
+ """
36
+ Builds a hierarchical tree structure from a flat list of spans.
37
+
38
+ Handles multiple potential roots and preserves parent-child relationships.
39
+ A span is considered a root if:
40
+ 1. It has no parent_span_id, or
41
+ 2. Its parent_span_id doesn't match any span_id in the provided data
42
+
43
+ Each node in the tree contains the span data and a list of children.
44
+ The tree is sorted by duration (longest spans first) at each level.
45
+ """
46
+ # Maps for quick lookup
47
+ spans_by_id : dict [str , dict ] = {}
48
+ children_by_parent_id : dict [str , list [dict ]] = {}
49
+ root_spans : list [dict ] = []
50
+
51
+ # First pass: organize spans by ID and parent_id
52
+ for span in spans_data :
53
+ span_id = span .get ("span_id" )
54
+ if not span_id :
55
+ continue
56
+
57
+ # Deep copy the span to avoid modifying the original
58
+ span_with_children = span .copy ()
59
+ span_with_children ["children" ] = []
60
+ spans_by_id [span_id ] = span_with_children
61
+
62
+ parent_id = span .get ("parent_span_id" )
63
+ if parent_id :
64
+ if parent_id not in children_by_parent_id :
65
+ children_by_parent_id [parent_id ] = []
66
+ children_by_parent_id [parent_id ].append (span_with_children )
67
+
68
+ # Second pass: identify root spans
69
+ # A root span is either:
70
+ # 1. A span without a parent_span_id
71
+ # 2. A span whose parent_span_id doesn't match any span_id in our data
72
+ for span_id , span in spans_by_id .items ():
73
+ parent_id = span .get ("parent_span_id" )
74
+ if not parent_id or parent_id not in spans_by_id :
75
+ root_spans .append (span )
76
+
77
+ # Third pass: build the tree by connecting children to parents
78
+ for parent_id , children in children_by_parent_id .items ():
79
+ if parent_id in spans_by_id :
80
+ parent = spans_by_id [parent_id ]
81
+ for child in children :
82
+ # Only add if not already a child
83
+ if child not in parent ["children" ]:
84
+ parent ["children" ].append (child )
85
+
86
+ # Function to sort children in each node by duration
87
+ def sort_span_tree (node ):
88
+ if node ["children" ]:
89
+ # Sort children by duration (in descending order to show longest spans first)
90
+ node ["children" ].sort (
91
+ key = lambda x : float (x .get ("duration" , "0" ).split ("s" )[0 ]), reverse = True
92
+ )
93
+ # Recursively sort each child's children
94
+ for child in node ["children" ]:
95
+ sort_span_tree (child )
96
+ del node ["parent_span_id" ]
97
+ return node
98
+
99
+ # Sort the root spans by duration
100
+ root_spans .sort (key = lambda x : float (x .get ("duration" , "0" ).split ("s" )[0 ]), reverse = True )
101
+ # Apply sorting to the whole tree
102
+ return [sort_span_tree (root ) for root in root_spans ]
103
+
104
+
34
105
def _get_serialized_event (
35
106
event_id : str , group : Group , user : User | RpcUser | AnonymousUser
36
107
) -> tuple [dict [str , Any ] | None , Event | GroupEvent | None ]:
@@ -65,6 +136,7 @@ def _get_trace_tree_for_event(event: Event | GroupEvent, project: Project) -> di
65
136
conditions = [
66
137
["trace_id" , "=" , trace_id ],
67
138
],
139
+ organization_id = project .organization_id ,
68
140
start = start ,
69
141
end = end ,
70
142
)
@@ -79,6 +151,7 @@ def _get_trace_tree_for_event(event: Event | GroupEvent, project: Project) -> di
79
151
conditions = [
80
152
["trace" , "=" , trace_id ],
81
153
],
154
+ organization_id = project .organization_id ,
82
155
start = start ,
83
156
end = end ,
84
157
)
@@ -111,6 +184,9 @@ def _get_trace_tree_for_event(event: Event | GroupEvent, project: Project) -> di
111
184
"parent_span_id" : event_data .get ("contexts" , {}).get ("trace" , {}).get ("parent_span_id" ),
112
185
"is_transaction" : is_transaction ,
113
186
"is_error" : is_error ,
187
+ "is_current_project" : event .project_id == project .id ,
188
+ "project_slug" : event .project .slug ,
189
+ "project_id" : event .project_id ,
114
190
"children" : [],
115
191
}
116
192
@@ -132,14 +208,26 @@ def _get_trace_tree_for_event(event: Event | GroupEvent, project: Project) -> di
132
208
spans = event_data .get ("spans" , [])
133
209
span_ids = [span .get ("span_id" ) for span in spans if span .get ("span_id" )]
134
210
211
+ spans_selected_data = [
212
+ {
213
+ "span_id" : span .get ("span_id" ),
214
+ "parent_span_id" : span .get ("parent_span_id" ),
215
+ "title" : f"{ span .get ('op' , '' )} - { span .get ('description' , '' )} " ,
216
+ "data" : span .get ("data" ),
217
+ "duration" : f"{ span .get ('timestamp' , 0 ) - span .get ('start_timestamp' , 0 )} s" ,
218
+ }
219
+ for span in spans
220
+ ]
221
+ selected_spans_tree = build_spans_tree (spans_selected_data )
222
+
135
223
event_node .update (
136
224
{
137
225
"title" : f"{ op } - { transaction_title } " if op else transaction_title ,
138
226
"platform" : event .platform ,
139
- "is_current_project" : event .project_id == project .id ,
140
227
"duration" : duration_str ,
141
228
"profile_id" : profile_id ,
142
229
"span_ids" : span_ids , # Store for later use
230
+ "spans" : selected_spans_tree ,
143
231
}
144
232
)
145
233
@@ -162,7 +250,6 @@ def _get_trace_tree_for_event(event: Event | GroupEvent, project: Project) -> di
162
250
{
163
251
"title" : error_title ,
164
252
"platform" : event .platform ,
165
- "is_current_project" : event .project_id == project .id ,
166
253
}
167
254
)
168
255
@@ -269,7 +356,11 @@ def cleanup_node(node):
269
356
270
357
cleaned_tree = [cleanup_node (root ) for root in sorted_tree ]
271
358
272
- return {"trace_id" : event .trace_id , "events" : cleaned_tree }
359
+ return {
360
+ "trace_id" : event .trace_id ,
361
+ "org_id" : project .organization_id ,
362
+ "events" : cleaned_tree ,
363
+ }
273
364
274
365
275
366
def _get_profile_from_trace_tree (
0 commit comments