systemprompt_runtime/trace/repository/
list.rs1use chrono::{DateTime, Utc};
8
9use systemprompt_identifiers::TraceId;
10
11use super::{Result, TraceRepository};
12use crate::trace::models::{TraceListFilter, TraceListItem};
13
14struct TraceRow {
15 trace_id: TraceId,
16 first_timestamp: DateTime<Utc>,
17 last_timestamp: DateTime<Utc>,
18 agent: Option<String>,
19 status: String,
20 ai_requests: i64,
21 mcp_calls: i64,
22}
23
24impl TraceRepository {
25 pub async fn list_traces(&self, filter: &TraceListFilter) -> Result<Vec<TraceListItem>> {
26 let rows = self.fetch_trace_rows(filter).await?;
27
28 Ok(rows
29 .into_iter()
30 .map(|r| TraceListItem {
31 trace_id: r.trace_id,
32 first_timestamp: r.first_timestamp,
33 last_timestamp: r.last_timestamp,
34 agent: r.agent,
35 status: r.status,
36 ai_requests: r.ai_requests,
37 mcp_calls: r.mcp_calls,
38 })
39 .collect())
40 }
41
42 async fn fetch_trace_rows(&self, filter: &TraceListFilter) -> Result<Vec<TraceRow>> {
43 let tool_pattern = filter.tool.as_deref();
44 let agent_pattern = filter.agent.as_ref().map(|a| format!("%{a}%"));
45 let agent_pat = agent_pattern.as_deref();
46 let status_lower = filter.status.as_ref().map(|s| s.to_lowercase());
47 let status_val = status_lower.as_deref();
48 let exclude_system = !filter.include_system;
49
50 sqlx::query_as!(
51 TraceRow,
52 r#"
53 SELECT trace_id AS "trace_id!: TraceId", first_at AS "first_timestamp!",
54 last_at AS "last_timestamp!", agent_name AS "agent", status AS "status!",
55 ai_count AS "ai_requests!", mcp_count AS "mcp_calls!"
56 FROM trace_facts
57 WHERE ($2::timestamptz IS NULL OR last_at >= $2)
58 AND ($1::text IS NULL OR EXISTS (SELECT 1 FROM unnest(tool_names) AS t(name) WHERE t.name ILIKE $1))
59 AND (NOT $3::boolean OR trace_id <> 'system')
60 AND (NOT $3::boolean OR ai_count > 0 OR mcp_count > 0 OR agent_name IS NOT NULL)
61 AND ($4::text IS NULL OR agent_name ILIKE $4)
62 AND ($5::text IS NULL OR LOWER(status) = $5)
63 AND ($6::boolean IS NOT TRUE OR mcp_count > 0)
64 ORDER BY first_at DESC
65 LIMIT $7
66 "#,
67 tool_pattern,
68 filter.since,
69 exclude_system,
70 agent_pat,
71 status_val,
72 Some(filter.has_mcp),
73 filter.limit
74 )
75 .fetch_all(&*self.pool)
76 .await
77 .map_err(Into::into)
78 }
79}