use anyhow::Result;
use clap::Parser;
use crate::cli::datadog::format::{output_as, OutputFormat};
use crate::cli::datadog::monitor::{render_monitor_table, MonitorRow};
use crate::datadog::client::DatadogClient;
use crate::datadog::monitors_api::MonitorsApi;
use crate::datadog::types::MonitorSearchItem;
#[derive(Parser)]
pub struct SearchCommand {
#[arg(long)]
pub query: String,
#[arg(long, default_value_t = 100)]
pub limit: usize,
#[arg(short = 'o', long, value_enum, default_value_t = OutputFormat::Table)]
pub output: OutputFormat,
}
impl SearchCommand {
pub async fn execute(self, client: &DatadogClient) -> Result<()> {
run_search(client, &self.query, self.limit, &self.output).await
}
}
async fn run_search(
client: &DatadogClient,
query: &str,
limit: usize,
output: &OutputFormat,
) -> Result<()> {
let result = MonitorsApi::new(client).search(query, limit).await?;
if output_as(&result, output)? {
return Ok(());
}
let stdout = std::io::stdout();
let mut handle = stdout.lock();
let rows: Vec<MonitorRow<'_>> = result.monitors.iter().map(search_row).collect();
render_monitor_table(&rows, &mut handle)
}
fn search_row(item: &MonitorSearchItem) -> MonitorRow<'_> {
MonitorRow {
id: item.id,
name: item.name.as_str(),
status: item.status_label(),
tags: &item.tags,
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
fn search_body() -> serde_json::Value {
serde_json::json!({
"monitors": [
{
"id": 1_i64,
"name": "Disk full",
"status": "ALERT",
"tags": ["team:sre"]
}
],
"metadata": {
"page": 0,
"per_page": 30,
"page_count": 1,
"total_count": 1
}
})
}
#[test]
fn search_row_uses_dash_when_status_missing() {
let item = MonitorSearchItem {
id: 1,
name: "n".into(),
status: None,
tags: vec![],
monitor_type: None,
query: None,
last_triggered_ts: None,
creator: None,
};
let row = search_row(&item);
assert_eq!(row.id, 1);
assert_eq!(row.status, "-");
}
#[tokio::test]
async fn run_search_table_path_writes_to_stdout() {
let server = wiremock::MockServer::start().await;
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/api/v1/monitor/search"))
.and(wiremock::matchers::query_param("query", "status:alert"))
.and(wiremock::matchers::query_param("page", "0"))
.respond_with(wiremock::ResponseTemplate::new(200).set_body_json(search_body()))
.expect(1)
.mount(&server)
.await;
let client = DatadogClient::new(&server.uri(), "api", "app").unwrap();
run_search(&client, "status:alert", 30, &OutputFormat::Table)
.await
.unwrap();
}
#[tokio::test]
async fn run_search_json_path_returns_ok() {
let server = wiremock::MockServer::start().await;
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/api/v1/monitor/search"))
.respond_with(wiremock::ResponseTemplate::new(200).set_body_json(search_body()))
.mount(&server)
.await;
let client = DatadogClient::new(&server.uri(), "api", "app").unwrap();
run_search(&client, "q", 30, &OutputFormat::Json)
.await
.unwrap();
}
#[tokio::test]
async fn run_search_propagates_api_errors() {
let server = wiremock::MockServer::start().await;
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/api/v1/monitor/search"))
.respond_with(wiremock::ResponseTemplate::new(400).set_body_string("bad"))
.mount(&server)
.await;
let client = DatadogClient::new(&server.uri(), "api", "app").unwrap();
let err = run_search(&client, "??", 30, &OutputFormat::Table)
.await
.unwrap_err();
assert!(err.to_string().contains("400"));
}
#[tokio::test]
async fn search_command_execute_passes_query_and_limit_through() {
let server = wiremock::MockServer::start().await;
wiremock::Mock::given(wiremock::matchers::method("GET"))
.and(wiremock::matchers::path("/api/v1/monitor/search"))
.and(wiremock::matchers::query_param("query", "status:alert"))
.respond_with(wiremock::ResponseTemplate::new(200).set_body_json(search_body()))
.expect(1)
.mount(&server)
.await;
let client = DatadogClient::new(&server.uri(), "api", "app").unwrap();
let cmd = SearchCommand {
query: "status:alert".into(),
limit: 30,
output: OutputFormat::Json,
};
cmd.execute(&client).await.unwrap();
}
}