Refresh activity list when change in time modifies it
This commit is contained in:
parent
8e754d7c77
commit
a17fb5104d
3 changed files with 55 additions and 14 deletions
|
|
@ -9,6 +9,7 @@ use shepherd_config::{Entry, Policy};
|
|||
use shepherd_host_api::{HostCapabilities, HostSessionHandle};
|
||||
use shepherd_store::{AuditEvent, AuditEventType, Store};
|
||||
use shepherd_util::{EntryId, MonotonicInstant, SessionId};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tracing::{debug, info};
|
||||
|
|
@ -35,6 +36,8 @@ pub struct CoreEngine {
|
|||
store: Arc<dyn Store>,
|
||||
capabilities: HostCapabilities,
|
||||
current_session: Option<ActiveSession>,
|
||||
/// Tracks which entries were enabled on the last tick, to detect availability changes
|
||||
last_availability_set: HashSet<EntryId>,
|
||||
}
|
||||
|
||||
impl CoreEngine {
|
||||
|
|
@ -59,6 +62,7 @@ impl CoreEngine {
|
|||
store,
|
||||
capabilities,
|
||||
current_session: None,
|
||||
last_availability_set: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -300,10 +304,29 @@ impl CoreEngine {
|
|||
}
|
||||
}
|
||||
|
||||
/// Tick the engine - check for warnings and expiry
|
||||
pub fn tick(&mut self, now_mono: MonotonicInstant) -> Vec<CoreEvent> {
|
||||
/// Tick the engine - check for warnings, expiry, and availability changes
|
||||
pub fn tick(&mut self, now_mono: MonotonicInstant, now: DateTime<Local>) -> Vec<CoreEvent> {
|
||||
let mut events = Vec::new();
|
||||
|
||||
// Check if the set of available entries has changed
|
||||
let current_availability: HashSet<EntryId> = self
|
||||
.policy
|
||||
.entries
|
||||
.iter()
|
||||
.filter(|e| self.evaluate_entry(e, now).enabled)
|
||||
.map(|e| e.id.clone())
|
||||
.collect();
|
||||
|
||||
if current_availability != self.last_availability_set {
|
||||
debug!(
|
||||
previous = ?self.last_availability_set,
|
||||
current = ?current_availability,
|
||||
"Entry availability set changed"
|
||||
);
|
||||
self.last_availability_set = current_availability;
|
||||
events.push(CoreEvent::AvailabilitySetChanged);
|
||||
}
|
||||
|
||||
let session = match &mut self.current_session {
|
||||
Some(s) => s,
|
||||
None => return events,
|
||||
|
|
@ -676,19 +699,23 @@ mod tests {
|
|||
engine.start_session(plan, now, now_mono);
|
||||
}
|
||||
|
||||
// No warnings initially
|
||||
let events = engine.tick(now_mono);
|
||||
assert!(events.is_empty());
|
||||
// No warnings initially (first tick may emit AvailabilitySetChanged)
|
||||
let events = engine.tick(now_mono, now);
|
||||
// Filter to just warning events for this test
|
||||
let warning_events: Vec<_> = events.iter().filter(|e| matches!(e, CoreEvent::Warning { .. })).collect();
|
||||
assert!(warning_events.is_empty());
|
||||
|
||||
// At 70 seconds (10 seconds past warning threshold), warning should fire
|
||||
let later = now_mono + Duration::from_secs(70);
|
||||
let events = engine.tick(later);
|
||||
assert_eq!(events.len(), 1);
|
||||
assert!(matches!(events[0], CoreEvent::Warning { threshold_seconds: 60, .. }));
|
||||
let events = engine.tick(later, now);
|
||||
let warning_events: Vec<_> = events.iter().filter(|e| matches!(e, CoreEvent::Warning { .. })).collect();
|
||||
assert_eq!(warning_events.len(), 1);
|
||||
assert!(matches!(warning_events[0], CoreEvent::Warning { threshold_seconds: 60, .. }));
|
||||
|
||||
// Warning shouldn't fire twice
|
||||
let events = engine.tick(later);
|
||||
assert!(events.is_empty());
|
||||
let events = engine.tick(later, now);
|
||||
let warning_events: Vec<_> = events.iter().filter(|e| matches!(e, CoreEvent::Warning { .. })).collect();
|
||||
assert!(warning_events.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -739,8 +766,10 @@ mod tests {
|
|||
|
||||
// At 61 seconds, should be expired
|
||||
let later = now_mono + Duration::from_secs(61);
|
||||
let events = engine.tick(later);
|
||||
assert_eq!(events.len(), 1);
|
||||
assert!(matches!(events[0], CoreEvent::ExpireDue { .. }));
|
||||
let events = engine.tick(later, now);
|
||||
// Filter to just expiry events for this test
|
||||
let expiry_events: Vec<_> = events.iter().filter(|e| matches!(e, CoreEvent::ExpireDue { .. })).collect();
|
||||
assert_eq!(expiry_events.len(), 1);
|
||||
assert!(matches!(expiry_events[0], CoreEvent::ExpireDue { .. }));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ pub enum CoreEvent {
|
|||
deadline: Option<DateTime<Local>>,
|
||||
},
|
||||
|
||||
/// The set of available entries has changed (e.g., due to time window boundaries)
|
||||
AvailabilitySetChanged,
|
||||
|
||||
/// Warning threshold reached
|
||||
Warning {
|
||||
session_id: SessionId,
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ impl Service {
|
|||
|
||||
let events = {
|
||||
let mut engine = engine.lock().await;
|
||||
engine.tick(now_mono)
|
||||
engine.tick(now_mono, now)
|
||||
};
|
||||
|
||||
for event in events {
|
||||
|
|
@ -308,6 +308,15 @@ impl Service {
|
|||
enabled: *enabled,
|
||||
}));
|
||||
}
|
||||
|
||||
CoreEvent::AvailabilitySetChanged => {
|
||||
// Time-based availability change - broadcast updated state
|
||||
let state = {
|
||||
let engine = engine.lock().await;
|
||||
engine.get_state()
|
||||
};
|
||||
ipc.broadcast_event(Event::new(EventPayload::StateChanged(state)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue