use anyhow::Result;
use clap::Args;
use crate::cmd::{Ctx, now_secs};
use crate::output::{self, Table};
use crate::types::FeedEvent;
#[derive(Args, Debug)]
pub struct FeedArgs {
#[arg(value_name = "NAMESPACE")]
pub namespace: Option<String>,
#[arg(long, conflicts_with = "limit")]
pub xml: bool,
#[arg(long, short = 'n', value_name = "N")]
pub limit: Option<usize>,
}
pub fn run(ctx: &Ctx, args: &FeedArgs) -> Result<()> {
match &args.namespace {
None => index(ctx),
Some(ns) if args.xml => {
print!("{}", ctx.client.feed_rss(ns)?);
Ok(())
}
Some(ns) => events(ctx, ns, args.limit),
}
}
fn index(ctx: &Ctx) -> Result<()> {
if ctx.out.is_machine() {
let raw = ctx.client.raw().feeds()?;
let names: Vec<String> = raw
.as_array()
.map(|a| {
a.iter()
.filter_map(|f| f.get("namespace").and_then(serde_json::Value::as_str))
.map(|ns| format!("feed/{ns}"))
.collect()
})
.unwrap_or_default();
return output::emit(&raw, ctx.out, &names);
}
let feeds = ctx.client.feeds()?;
if feeds.is_empty() {
println!("No feed events. Deployments publish only when their spec opts in");
println!("(\"feed\": {{\"announce\": true, \"issues\": true}}).");
return Ok(());
}
let mut table = Table::new(["NAMESPACE", "EVENTS"]);
for f in &feeds {
table.row(&[f.namespace.clone(), f.events.to_string()]);
}
table.print();
Ok(())
}
fn events(ctx: &Ctx, namespace: &str, limit: Option<usize>) -> Result<()> {
if ctx.out.is_machine() {
let raw = ctx.client.raw().feed_events(namespace)?;
let names: Vec<String> = raw
.as_array()
.map(|a| {
a.iter()
.filter_map(|e| e.get("id").and_then(serde_json::Value::as_u64))
.map(|id| format!("feed/{namespace}/{id}"))
.collect()
})
.unwrap_or_default();
return output::emit(&raw, ctx.out, &names);
}
let events = ctx.client.feed_events(namespace)?;
if events.is_empty() {
println!("No events in namespace {namespace:?}.");
return Ok(());
}
let now = now_secs();
let mut table = Table::new(["WHEN", "KIND", "DEPLOYMENT", "WHAT"]);
for e in events.iter().take(limit.unwrap_or(usize::MAX)) {
table.row(&[
format!("{} ago", output::duration(now.saturating_sub(e.last_ts))),
e.kind.clone(),
e.deployment.clone(),
what_cell(e),
]);
}
table.print();
Ok(())
}
fn what_cell(e: &FeedEvent) -> String {
let mut out = e.title.clone();
if !e.detail.is_empty() {
out.push_str(" — ");
out.push_str(&e.detail);
}
if e.count > 1 {
out.push_str(&format!(" (×{})", e.count));
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn event(title: &str, detail: &str, count: u64) -> FeedEvent {
FeedEvent {
title: title.into(),
detail: detail.into(),
count,
..Default::default()
}
}
#[test]
fn the_what_cell_folds_title_detail_and_count_into_one_line() {
assert_eq!(what_cell(&event("web deployed", "", 1)), "web deployed");
assert_eq!(
what_cell(&event("web: VM failed to boot", "gave up after 300s", 1)),
"web: VM failed to boot — gave up after 300s"
);
assert_eq!(
what_cell(&event("web: cold start timed out", "a request waited 120s", 7)),
"web: cold start timed out — a request waited 120s (×7)"
);
}
}