huskies: merge 1193 story overview chat command: active work across all connected sleds
This commit is contained in:
@@ -112,6 +112,56 @@ async fn eval_gateway_status_command(
|
||||
format!("Unknown project or story number `{arg}`. Available projects: {available}")
|
||||
}
|
||||
|
||||
/// Evaluate a gateway-local `overview` command (story 1193).
|
||||
///
|
||||
/// - Non-empty `args`: unchanged legacy behavior — proxies `overview <args>`
|
||||
/// to the active project's own per-story overview handler.
|
||||
/// - Empty `args`: fetches active pipeline items from every registered
|
||||
/// project and renders a compact cross-sled overview of In Progress / QA /
|
||||
/// Merge work. Projects with no configured URL are surfaced as
|
||||
/// unreachable rather than silently dropped, matching AC 2.
|
||||
async fn eval_gateway_overview_command(
|
||||
args: &str,
|
||||
active_project: &tokio::sync::RwLock<String>,
|
||||
store: &tokio::sync::RwLock<
|
||||
std::collections::BTreeMap<String, crate::service::gateway::config::ProjectEntry>,
|
||||
>,
|
||||
) -> String {
|
||||
if !args.is_empty() {
|
||||
let name = active_project.read().await.clone();
|
||||
let url = store.read().await.get(&name).and_then(|e| e.url.clone());
|
||||
return match url {
|
||||
Some(url) => BotContext::run_proxy_bot_command(&url, "overview", args).await,
|
||||
None => "No active project selected or project URL not configured.".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
let entries: Vec<(String, Option<String>)> = store
|
||||
.read()
|
||||
.await
|
||||
.iter()
|
||||
.map(|(name, entry)| (name.clone(), entry.url.clone()))
|
||||
.collect();
|
||||
|
||||
let with_url: std::collections::BTreeMap<String, String> = entries
|
||||
.iter()
|
||||
.filter_map(|(name, url)| url.clone().map(|u| (name.clone(), u)))
|
||||
.collect();
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let mut items_by_project =
|
||||
crate::gateway::fetch_all_project_pipeline_items(&with_url, &client).await;
|
||||
for (name, url) in &entries {
|
||||
if url.is_none() {
|
||||
items_by_project
|
||||
.entry(name.clone())
|
||||
.or_insert_with(|| serde_json::json!({ "error": "no URL configured" }));
|
||||
}
|
||||
}
|
||||
|
||||
crate::gateway::format_overview_compact(&items_by_project)
|
||||
}
|
||||
|
||||
/// Evaluate a `switch <arg>` command against the live project store.
|
||||
///
|
||||
/// Reads valid project names from the store at call time so newly added
|
||||
@@ -332,6 +382,7 @@ pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
|
||||
"upgrade",
|
||||
"health",
|
||||
"projects",
|
||||
"overview",
|
||||
];
|
||||
|
||||
let stripped = crate::chat::util::strip_bot_mention(
|
||||
@@ -371,6 +422,30 @@ pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
|
||||
return;
|
||||
}
|
||||
|
||||
// `overview` (bare) is special-cased ahead of the generic per-command
|
||||
// proxy below so it can render a cross-sled overview locally instead
|
||||
// of proxying to a single project (story 1193). `overview <arg>`
|
||||
// still proxies, preserving the existing per-story overview handler.
|
||||
if cmd == "overview" {
|
||||
slog!("[matrix-bot] Handling 'overview {args}' from {sender}");
|
||||
let response = match (&ctx.gateway_active_project, &ctx.gateway_projects_store) {
|
||||
(Some(active_project), Some(store)) => {
|
||||
eval_gateway_overview_command(&args, active_project, store).await
|
||||
}
|
||||
_ => "Gateway projects store unavailable.".to_string(),
|
||||
};
|
||||
let html = markdown_to_html(&response);
|
||||
if let Ok(msg_id) = ctx
|
||||
.transport
|
||||
.send_message(&room_id_str, &response, &html)
|
||||
.await
|
||||
&& let Ok(event_id) = msg_id.parse()
|
||||
{
|
||||
ctx.bot_sent_event_ids.lock().await.insert(event_id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Only proxy if the first word is a known bot command (sync or async).
|
||||
let is_known_command = !cmd.is_empty()
|
||||
&& !GATEWAY_LOCAL_COMMANDS.contains(&cmd.as_str())
|
||||
@@ -1285,7 +1360,7 @@ pub(in crate::chat::transport::matrix::bot) async fn on_room_message(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{eval_gateway_status_command, eval_switch_command};
|
||||
use super::{eval_gateway_overview_command, eval_gateway_status_command, eval_switch_command};
|
||||
use crate::service::gateway::config::ProjectEntry;
|
||||
use std::collections::BTreeMap;
|
||||
use tokio::sync::RwLock;
|
||||
@@ -1434,4 +1509,45 @@ mod tests {
|
||||
"error should list valid projects: {resp}"
|
||||
);
|
||||
}
|
||||
|
||||
// ── eval_gateway_overview_command (story 1193) ───────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn overview_empty_arg_marks_no_url_project_unreachable_not_omitted() {
|
||||
let active = RwLock::new("huskies".to_string());
|
||||
let store: RwLock<BTreeMap<String, ProjectEntry>> = RwLock::new(BTreeMap::from([(
|
||||
"huskies".to_string(),
|
||||
project_without_url(),
|
||||
)]));
|
||||
|
||||
let resp = eval_gateway_overview_command("", &active, &store).await;
|
||||
assert!(
|
||||
resp.contains("huskies") && resp.contains("unreachable"),
|
||||
"a project with no URL must be reported unreachable, not silently dropped: {resp}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn overview_empty_arg_empty_store_reports_no_projects() {
|
||||
let active = RwLock::new("huskies".to_string());
|
||||
let store: RwLock<BTreeMap<String, ProjectEntry>> = RwLock::new(BTreeMap::new());
|
||||
|
||||
let resp = eval_gateway_overview_command("", &active, &store).await;
|
||||
assert_eq!(resp, "No projects registered.");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn overview_nonempty_arg_no_active_url_reports_no_active_project() {
|
||||
let active = RwLock::new("huskies".to_string());
|
||||
let store: RwLock<BTreeMap<String, ProjectEntry>> = RwLock::new(BTreeMap::from([(
|
||||
"huskies".to_string(),
|
||||
project_without_url(),
|
||||
)]));
|
||||
|
||||
let resp = eval_gateway_overview_command("42", &active, &store).await;
|
||||
assert!(
|
||||
resp.contains("No active project selected"),
|
||||
"numeric arg should proxy to the active project, reporting no URL: {resp}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user