feat(dashboard): extract inline script to standalone dashboard.ts/js, add UI build/check recipes, and add route unit tests
This commit is contained in:
1 parent
4b307b55b9
commit
19f9c7ace7
24 files changed
+2343
-1819
No files matched your search
@@ -54,6 +54,40 @@ pub async fn handle_socket(socket: WebSocket, state: Arc<AppState>, _client_type
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.insert(session_id.clone(), tx.clone());
|
||||
|
||||
// Reconnection Catch-Up: Replay recent TASK_EVENT notifications so client receives missed Futures
|
||||
let recent_task_notifications: Vec<String> = state
|
||||
.handler
|
||||
.state
|
||||
.telemetry
|
||||
.recent_activities
|
||||
.read_with(|activities| {
|
||||
activities
|
||||
.iter()
|
||||
.filter_map(|act_val| {
|
||||
if act_val["category"] == "TASK_EVENT" {
|
||||
if let Some(details_str) = act_val["details"].as_str() {
|
||||
if let Ok(event_val) = serde_json::from_str::<serde_json::Value>(details_str) {
|
||||
return Some(
|
||||
serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/task/completed",
|
||||
"params": event_val
|
||||
})
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.take(5)
|
||||
.collect()
|
||||
});
|
||||
|
||||
for notif in recent_task_notifications.into_iter().rev() {
|
||||
let _ = tx.try_send(notif);
|
||||
}
|
||||
|
||||
let (mut sender, mut receiver) = socket.split();
|
||||
|
||||
let send_task = tokio::spawn(async move {
|
||||
@@ -185,4 +219,83 @@ mod tests {
|
||||
|
||||
assert!(app_state.clients.read().unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_task_event_broadcast_and_reconnection_replay() {
|
||||
let dir = tempdir().unwrap();
|
||||
let mem_state = Arc::new(MemoryState::new(dir.path().to_str().unwrap()));
|
||||
|
||||
let task_event = crate::models::TaskEvent {
|
||||
task_id: "task-999".to_string(),
|
||||
status: "completed".to_string(),
|
||||
action: Some("update".to_string()),
|
||||
result: Some(serde_json::json!({"status": "completed"})),
|
||||
error: None,
|
||||
timestamp: 1728129000,
|
||||
session_id: None,
|
||||
};
|
||||
|
||||
// Broadcast task event
|
||||
mem_state.broadcast_task_event(task_event.clone());
|
||||
|
||||
// Verify recent activities recorded the event
|
||||
let recorded = mem_state.telemetry.recent_activities.read_with(|act| act.clone());
|
||||
assert!(!recorded.is_empty());
|
||||
assert_eq!(recorded[0]["category"], "TASK_EVENT");
|
||||
|
||||
// Verify reconnection catch-up replay fetches the notification
|
||||
let (shutdown_tx, _) = tokio::sync::oneshot::channel();
|
||||
let app_state = Arc::new(AppState {
|
||||
handler: Arc::new(MemoryHandler::new(mem_state)),
|
||||
clients: RwLock::new(HashMap::new()),
|
||||
next_id: AtomicUsize::new(1),
|
||||
shutdown_tx: std::sync::Mutex::new(Some(shutdown_tx)),
|
||||
});
|
||||
|
||||
let (tx, mut rx) = mpsc::channel::<String>(10);
|
||||
app_state
|
||||
.clients
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert("session-1".to_string(), tx.clone());
|
||||
|
||||
let recent_notifications: Vec<String> = app_state
|
||||
.handler
|
||||
.state
|
||||
.telemetry
|
||||
.recent_activities
|
||||
.read_with(|activities| {
|
||||
activities
|
||||
.iter()
|
||||
.filter_map(|act_val| {
|
||||
if act_val["category"] == "TASK_EVENT" {
|
||||
if let Some(details_str) = act_val["details"].as_str() {
|
||||
if let Ok(event_val) = serde_json::from_str::<serde_json::Value>(details_str) {
|
||||
return Some(
|
||||
serde_json::json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/task/completed",
|
||||
"params": event_val
|
||||
})
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.take(5)
|
||||
.collect()
|
||||
});
|
||||
|
||||
for notif in recent_notifications {
|
||||
let _ = tx.try_send(notif);
|
||||
}
|
||||
|
||||
let replayed_msg = rx.recv().await.expect("Expected replayed task event notification");
|
||||
let parsed: serde_json::Value = serde_json::from_str(&replayed_msg).unwrap();
|
||||
assert_eq!(parsed["method"], "notifications/task/completed");
|
||||
assert_eq!(parsed["params"]["task_id"], "task-999");
|
||||
assert_eq!(parsed["params"]["status"], "completed");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user