11use std:: collections:: HashMap ;
22
33use anyhow:: Result ;
4+ use regex:: Regex ;
45use reqwest:: Client ;
56use serde:: { Deserialize , Serialize } ;
67use serde_json:: json;
@@ -73,14 +74,42 @@ pub fn decode_slack_token(
7374 . map_err ( |e| anyhow:: anyhow!( "Failed to convert decrypted bytes to string: {}" , e) )
7475}
7576
77+ fn replace_span_tags_with_links (
78+ text : & str ,
79+ project_id : & str ,
80+ trace_id : & str ,
81+ span_ids_map : & HashMap < String , String > ,
82+ ) -> String {
83+ // Regex to match <span id='X' name='Y' ... />
84+ let re = Regex :: new ( r#"`<span\s+id='(\d+)'\s+name='([^']+)'[^>]*/?>`"# ) . unwrap ( ) ;
85+
86+ re. replace_all ( text, |caps : & regex:: Captures | {
87+ let index_id = & caps[ 1 ] ;
88+ let name = & caps[ 2 ] ;
89+
90+ // Look up the actual span_id from the map
91+ if let Some ( actual_span_id) = span_ids_map. get ( index_id) {
92+ // Create Slack link: <url|text>
93+ format ! (
94+ "[{}](https://laminar.sh/project/{}/traces/{}?spanId={})" ,
95+ name, project_id, trace_id, actual_span_id
96+ )
97+ } else {
98+ // If span_id not found in map, just return the name without a link (but don't replace)
99+ name. to_string ( )
100+ }
101+ } )
102+ . to_string ( )
103+ }
104+
76105fn format_trace_analysis_blocks (
77106 project_id : & str ,
78107 trace_id : & str ,
79108 event_name : & str ,
80109 status : & str ,
81110 summary : & str ,
82111 analysis : & str ,
83- _span_ids_map : & HashMap < String , String > ,
112+ span_ids_map : & HashMap < String , String > ,
84113) -> serde_json:: Value {
85114 let emoji = match status {
86115 "error" => "🚨" ,
@@ -91,7 +120,7 @@ fn format_trace_analysis_blocks(
91120 let analysis_text = if analysis. is_empty ( ) {
92121 "No analysis available" . to_string ( )
93122 } else {
94- analysis . to_string ( )
123+ replace_span_tags_with_links ( analysis , project_id , trace_id , span_ids_map )
95124 } ;
96125
97126 let trace_link = format ! (
@@ -108,11 +137,8 @@ fn format_trace_analysis_blocks(
108137 }
109138 } ,
110139 {
111- "type" : "section" ,
112- "text" : {
113- "type" : "mrkdwn" ,
114- "text" : analysis_text
115- }
140+ "type" : "markdown" ,
141+ "text" : analysis_text
116142 } ,
117143 {
118144 "type" : "actions" ,
@@ -246,11 +272,18 @@ pub async fn send_message(
246272 channel_id : & str ,
247273 blocks : serde_json:: Value ,
248274) -> Result < ( ) > {
275+ let body = json ! ( {
276+ "channel" : channel_id,
277+ "blocks" : blocks,
278+ "unfurl_links" : false ,
279+ "unfurl_media" : false
280+ } ) ;
281+
249282 let response = slack_client
250283 . post ( format ! ( "{}/chat.postMessage" , SLACK_API_BASE ) )
251284 . header ( "Authorization" , format ! ( "Bearer {}" , token) )
252285 . header ( "Content-Type" , "application/json" )
253- . json ( & json ! ( { "channel" : channel_id , "blocks" : blocks } ) )
286+ . json ( & body )
254287 . send ( )
255288 . await ?;
256289 let status = response. status ( ) ;
0 commit comments