use crate::config::Config;
use crate::error::Result;
use crate::output::{json, pretty};
use otelite_client::models::TraceEntry;
use otelite_client::ApiClient;
#[allow(clippy::too_many_arguments)]
pub async fn handle_list(
client: &ApiClient,
config: &Config,
limit: Option<u32>,
min_duration: Option<u64>,
status: Option<String>,
query: Option<String>,
) -> Result<()> {
let mut params = Vec::new();
if let Some(limit) = limit {
params.push(("limit", limit.to_string()));
}
if let Some(min_duration) = min_duration {
params.push(("min_duration", min_duration.to_string()));
}
if let Some(status) = status {
params.push(("status", status));
}
if let Some(query_str) = query {
let predicates = otelite_core::query::parse_query(&query_str)
.map_err(|e| crate::error::Error::InvalidArgument(format!("Invalid query: {}", e)))?;
for predicate in predicates {
let param_value = format!(
"{} {} {}",
predicate.field, predicate.operator, predicate.value
);
params.push(("query", param_value));
}
}
let traces_response = client.fetch_traces(params).await?;
match config.format {
crate::config::OutputFormat::Pretty => {
pretty::print_traces_table(&traces_response.traces, config)?;
},
crate::config::OutputFormat::Json => {
json::print_traces_json(&traces_response.traces)?;
},
crate::config::OutputFormat::JsonCompact => {
json::print_traces_json_compact(&traces_response.traces)?;
},
}
Ok(())
}
pub async fn handle_show(client: &ApiClient, config: &Config, id: &str) -> Result<()> {
let trace = client.fetch_trace_by_id(id).await?;
match config.format {
crate::config::OutputFormat::Pretty => {
pretty::print_trace_tree(&trace, config)?;
},
crate::config::OutputFormat::Json => {
json::print_trace_json(&trace)?;
},
crate::config::OutputFormat::JsonCompact => {
json::print_trace_json_compact(&trace)?;
},
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub async fn handle_export(
client: &ApiClient,
_config: &Config,
format: &str,
status: Option<String>,
min_duration: Option<u64>,
since: Option<String>,
output: Option<String>,
) -> Result<()> {
let mut params = vec![("format", format.to_string())];
if let Some(status) = status {
params.push(("status", status));
}
if let Some(min_duration) = min_duration {
params.push(("min_duration", min_duration.to_string()));
}
if let Some(since) = since {
params.push(("since", since));
}
let data = client.export_traces(params).await?;
if let Some(output_path) = output {
std::fs::write(&output_path, &data)?;
let count = data.matches("\"id\"").count();
eprintln!("✓ Exported {} traces to {}", count, output_path);
} else {
print!("{}", data);
}
Ok(())
}
pub fn filter_by_duration(traces: Vec<TraceEntry>, min_duration_ms: u64) -> Vec<TraceEntry> {
traces
.into_iter()
.filter(|trace| (trace.duration / 1_000_000) >= min_duration_ms as i64)
.collect()
}
pub fn filter_by_status(traces: Vec<TraceEntry>, status: &str) -> Vec<TraceEntry> {
traces
.into_iter()
.filter(|trace| {
let trace_status = if trace.has_errors { "ERROR" } else { "OK" };
trace_status.eq_ignore_ascii_case(status)
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filter_by_duration() {
let traces = vec![
TraceEntry {
trace_id: "trace-001".to_string(),
root_span_name: "fast-request".to_string(),
start_time: 1234567890000000000,
duration: 100_000_000, span_count: 1,
service_names: vec!["service1".to_string()],
has_errors: false,
},
TraceEntry {
trace_id: "trace-002".to_string(),
root_span_name: "slow-request".to_string(),
start_time: 1234567890000000000,
duration: 2_000_000_000, span_count: 1,
service_names: vec!["service1".to_string()],
has_errors: false,
},
TraceEntry {
trace_id: "trace-003".to_string(),
root_span_name: "medium-request".to_string(),
start_time: 1234567890000000000,
duration: 500_000_000, span_count: 1,
service_names: vec!["service1".to_string()],
has_errors: false,
},
];
let filtered = filter_by_duration(traces.clone(), 500);
assert_eq!(filtered.len(), 2);
assert_eq!(filtered[0].trace_id, "trace-002");
assert_eq!(filtered[1].trace_id, "trace-003");
let filtered = filter_by_duration(traces.clone(), 1000);
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].trace_id, "trace-002");
let filtered = filter_by_duration(traces.clone(), 0);
assert_eq!(filtered.len(), 3);
let filtered = filter_by_duration(traces, 10000);
assert_eq!(filtered.len(), 0);
}
#[test]
fn test_filter_by_duration_empty() {
let traces: Vec<TraceEntry> = vec![];
let filtered = filter_by_duration(traces, 1000);
assert_eq!(filtered.len(), 0);
}
#[test]
fn test_filter_by_duration_exact_match() {
let traces = vec![TraceEntry {
trace_id: "trace-001".to_string(),
root_span_name: "request".to_string(),
start_time: 1234567890000000000,
duration: 1_000_000_000, span_count: 1,
service_names: vec!["service1".to_string()],
has_errors: false,
}];
let filtered = filter_by_duration(traces.clone(), 1000);
assert_eq!(filtered.len(), 1);
let filtered = filter_by_duration(traces, 1001);
assert_eq!(filtered.len(), 0);
}
#[test]
fn test_filter_by_status() {
let traces = vec![
TraceEntry {
trace_id: "trace-001".to_string(),
root_span_name: "success-request".to_string(),
start_time: 1234567890000000000,
duration: 100_000_000,
span_count: 1,
service_names: vec!["service1".to_string()],
has_errors: false,
},
TraceEntry {
trace_id: "trace-002".to_string(),
root_span_name: "error-request".to_string(),
start_time: 1234567890000000000,
duration: 200_000_000,
span_count: 1,
service_names: vec!["service1".to_string()],
has_errors: true,
},
TraceEntry {
trace_id: "trace-003".to_string(),
root_span_name: "another-success".to_string(),
start_time: 1234567890000000000,
duration: 150_000_000,
span_count: 1,
service_names: vec!["service1".to_string()],
has_errors: false,
},
];
let filtered = filter_by_status(traces.clone(), "OK");
assert_eq!(filtered.len(), 2);
assert_eq!(filtered[0].trace_id, "trace-001");
assert_eq!(filtered[1].trace_id, "trace-003");
let filtered = filter_by_status(traces.clone(), "ERROR");
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].trace_id, "trace-002");
let filtered = filter_by_status(traces.clone(), "ok");
assert_eq!(filtered.len(), 2);
let filtered = filter_by_status(traces, "UNKNOWN");
assert_eq!(filtered.len(), 0);
}
}