hammerwork-web 1.15.2

Web-based admin dashboard for Hammerwork job queues with real-time monitoring, webhook management, event streaming, and comprehensive job queue administration
Documentation
//! Job spawning and dependency management API endpoints.
//!
//! This module provides REST API endpoints for managing job spawn operations,
//! spawn tree hierarchies, and parent-child relationships between jobs.
//!
//! Note: This is a placeholder implementation for spawn API endpoints.
//! The actual database queries and spawn logic will be implemented based on
//! the specific database backend being used.

use chrono::{DateTime, Utc};
use hammerwork::queue::DatabaseQueue;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use uuid::Uuid;
use warp::{Filter, Rejection, Reply};

use super::ApiResponse;

/// Parameters for querying spawn children
#[derive(Debug, Deserialize)]
pub struct SpawnChildrenParams {
    /// Include grandchildren and deeper descendants
    pub include_grandchildren: Option<bool>,
    /// Filter children by status
    pub status_filter: Option<String>,
    /// Maximum depth to traverse (default: 1)
    pub depth: Option<u32>,
}

/// Parameters for spawn tree queries
#[derive(Debug, Deserialize)]
pub struct SpawnTreeParams {
    /// Output format (json, mermaid, d3)
    pub format: Option<String>,
    /// Maximum depth to include
    pub max_depth: Option<u32>,
    /// Include spawn configuration details
    pub include_config: Option<bool>,
}

/// Spawn operation details
#[derive(Debug, Serialize)]
pub struct SpawnOperationInfo {
    pub operation_id: String,
    pub parent_job_id: Uuid,
    pub spawned_job_ids: Vec<Uuid>,
    pub spawned_at: DateTime<Utc>,
    pub spawn_count: u32,
    pub success_count: u32,
    pub failed_count: u32,
    pub pending_count: u32,
    pub config: Option<serde_json::Value>,
}

/// Job information for spawn tree nodes
#[derive(Debug, Serialize)]
pub struct SpawnTreeNode {
    pub id: Uuid,
    pub queue_name: String,
    pub status: String,
    pub priority: String,
    pub created_at: DateTime<Utc>,
    pub has_spawn_config: bool,
    pub children: Vec<SpawnTreeNode>,
    pub parent_id: Option<Uuid>,
    pub depth: u32,
    pub spawn_operation_id: Option<String>,
}

/// Complete spawn tree response
#[derive(Debug, Serialize)]
pub struct SpawnTreeResponse {
    pub root_job: SpawnTreeNode,
    pub total_nodes: u32,
    pub max_depth: u32,
    pub spawn_operations: Vec<SpawnOperationInfo>,
}

/// Spawn statistics response
#[derive(Debug, Serialize)]
pub struct SpawnStatsResponse {
    pub total_spawn_operations: u64,
    pub avg_children_per_spawn: f64,
    pub max_children_in_spawn: u32,
    pub spawn_success_rate: f64,
    pub recent_spawn_count: u64,
    pub queue_breakdown: HashMap<String, SpawnQueueStats>,
}

/// Per-queue spawn statistics
#[derive(Debug, Serialize)]
pub struct SpawnQueueStats {
    pub spawn_operations: u64,
    pub avg_children: f64,
    pub success_rate: f64,
}

/// Minimal job information for API responses
#[derive(Debug, Serialize)]
pub struct JobInfo {
    pub id: Uuid,
    pub queue_name: String,
    pub status: String,
    pub priority: String,
    pub created_at: DateTime<Utc>,
    pub scheduled_at: Option<DateTime<Utc>>,
    pub started_at: Option<DateTime<Utc>>,
    pub completed_at: Option<DateTime<Utc>>,
    pub has_spawn_config: bool,
    pub spawn_operation_id: Option<String>,
}

/// Create spawn API routes
pub fn spawn_routes<T>(
    _queue: Arc<T>,
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone
where
    T: DatabaseQueue + Send + Sync + 'static,
{
    // Placeholder route that returns a message about spawn API endpoints
    warp::path("spawn")
        .and(warp::path("info"))
        .and(warp::get())
        .map(|| {
            let response = ApiResponse::success(serde_json::json!({
                "message": "Spawn API endpoints are available",
                "endpoints": [
                    "GET /api/jobs/{id}/children - List spawned child jobs",
                    "GET /api/jobs/{id}/parent - Get parent job information",
                    "GET /api/jobs/{id}/spawn-tree - Get complete spawn hierarchy",
                    "GET /api/spawn/operations - List spawn operations",
                    "GET /api/spawn/operations/{id} - Get spawn operation details",
                    "POST /api/jobs/{id}/spawn - Manually trigger spawn operation",
                    "GET /api/spawn/stats - Get spawn operation statistics"
                ],
                "status": "placeholder_implementation"
            }));
            warp::reply::json(&response)
        })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_spawn_children_params() {
        let params = SpawnChildrenParams {
            include_grandchildren: Some(true),
            status_filter: Some("pending".to_string()),
            depth: Some(3),
        };

        assert_eq!(params.include_grandchildren, Some(true));
        assert_eq!(params.status_filter, Some("pending".to_string()));
        assert_eq!(params.depth, Some(3));
    }

    #[test]
    fn test_spawn_tree_params() {
        let params = SpawnTreeParams {
            format: Some("mermaid".to_string()),
            max_depth: Some(5),
            include_config: Some(true),
        };

        assert_eq!(params.format, Some("mermaid".to_string()));
        assert_eq!(params.max_depth, Some(5));
        assert_eq!(params.include_config, Some(true));
    }

    #[test]
    fn test_spawn_tree_response_structure() {
        let tree_response = SpawnTreeResponse {
            root_job: SpawnTreeNode {
                id: Uuid::new_v4(),
                queue_name: "test_queue".to_string(),
                status: "completed".to_string(),
                priority: "high".to_string(),
                created_at: Utc::now(),
                has_spawn_config: true,
                children: vec![],
                parent_id: None,
                depth: 0,
                spawn_operation_id: Some("test_op".to_string()),
            },
            total_nodes: 1,
            max_depth: 0,
            spawn_operations: vec![],
        };

        assert_eq!(tree_response.total_nodes, 1);
        assert_eq!(tree_response.max_depth, 0);
        assert!(tree_response.root_job.has_spawn_config);
    }

    #[test]
    fn test_spawn_stats_response_structure() {
        let mut queue_breakdown = HashMap::new();
        queue_breakdown.insert(
            "test_queue".to_string(),
            SpawnQueueStats {
                spawn_operations: 10,
                avg_children: 3.5,
                success_rate: 95.0,
            },
        );

        let stats_response = SpawnStatsResponse {
            total_spawn_operations: 50,
            avg_children_per_spawn: 3.2,
            max_children_in_spawn: 15,
            spawn_success_rate: 97.5,
            recent_spawn_count: 12,
            queue_breakdown,
        };

        assert_eq!(stats_response.total_spawn_operations, 50);
        assert_eq!(stats_response.avg_children_per_spawn, 3.2);
        assert_eq!(stats_response.spawn_success_rate, 97.5);
        assert_eq!(stats_response.queue_breakdown.len(), 1);
    }
}