fix(#1643): filter destroyed-agent targets from dashboard schedule view
This commit is contained in:
parent
8b991b2cc5
commit
e08122a206
4 changed files with 117 additions and 8 deletions
|
|
@ -831,6 +831,27 @@ pub fn schedule_to_wire_public(s: crate::scheduled_prompts::Schedule) -> hive_sh
|
|||
schedule_to_wire(s)
|
||||
}
|
||||
|
||||
/// Drop schedule targets that point at agents which no longer exist, so
|
||||
/// the dashboard's schedule table doesn't render ghost columns for
|
||||
/// destroyed agents. `live` is the set of logical agent names from the
|
||||
/// last `nixos-container list` scan (stopped agents included, destroyed
|
||||
/// ones absent); the `operator` pseudo-target is always retained since
|
||||
/// it isn't a container. Applied only to the dashboard wire paths
|
||||
/// (`api_schedules` + the `SchedulesChanged` SSE emit) — the
|
||||
/// manager-facing `list_schedules` stays unfiltered so agents can still
|
||||
/// see and cancel stale targets. This is a view filter: the underlying
|
||||
/// schedule rows keep every target, so a re-spawned agent's targets
|
||||
/// reappear on their own.
|
||||
pub(crate) fn filter_ghost_schedule_targets(
|
||||
schedules: &mut [hive_sh4re::WireSchedule],
|
||||
live: &std::collections::HashSet<String>,
|
||||
) {
|
||||
for s in schedules.iter_mut() {
|
||||
s.targets
|
||||
.retain(|t| t.target == hive_sh4re::OPERATOR_RECIPIENT || live.contains(&t.target));
|
||||
}
|
||||
}
|
||||
|
||||
fn schedule_to_wire(s: crate::scheduled_prompts::Schedule) -> hive_sh4re::WireSchedule {
|
||||
hive_sh4re::WireSchedule {
|
||||
id: s.id,
|
||||
|
|
@ -906,7 +927,57 @@ pub fn spawn_question_watchdog(coord: &Arc<Coordinator>, id: i64, ttl_secs: u64)
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::validate_commit_ref;
|
||||
use super::{filter_ghost_schedule_targets, validate_commit_ref};
|
||||
|
||||
fn target(name: &str) -> hive_sh4re::WireScheduleTarget {
|
||||
hive_sh4re::WireScheduleTarget {
|
||||
target: name.to_owned(),
|
||||
cancelled_at_unix: None,
|
||||
last_fired_at_unix: None,
|
||||
last_result: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn schedule(targets: &[&str]) -> hive_sh4re::WireSchedule {
|
||||
hive_sh4re::WireSchedule {
|
||||
id: 1,
|
||||
owner: "operator".to_owned(),
|
||||
body: "ping".to_owned(),
|
||||
interval_seconds: None,
|
||||
next_fire_at_unix: 0,
|
||||
created_at_unix: 0,
|
||||
source: hive_sh4re::WireScheduleSource::Operator,
|
||||
cancelled_at_unix: None,
|
||||
description: None,
|
||||
targets: targets.iter().map(|t| target(t)).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ghost_filter_drops_dead_agents_keeps_live_and_operator() {
|
||||
let live: std::collections::HashSet<String> = ["iris".to_owned(), "damocles".to_owned()]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let mut schedules = vec![schedule(&["iris", "ghost", "operator", "damocles"])];
|
||||
filter_ghost_schedule_targets(&mut schedules, &live);
|
||||
let kept: Vec<&str> = schedules[0]
|
||||
.targets
|
||||
.iter()
|
||||
.map(|t| t.target.as_str())
|
||||
.collect();
|
||||
// `ghost` (destroyed) dropped; live agents + operator pseudo-target kept.
|
||||
assert_eq!(kept, vec!["iris", "operator", "damocles"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ghost_filter_can_empty_targets_when_all_dead() {
|
||||
let live: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||
let mut schedules = vec![schedule(&["gone1", "gone2"])];
|
||||
filter_ghost_schedule_targets(&mut schedules, &live);
|
||||
// operator is never in the live set but is always retained; here
|
||||
// there's no operator target, so everything drops.
|
||||
assert!(schedules[0].targets.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_short_and_full_sha() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue