mirror of
https://github.com/oarkflow/mq.git
synced 2025-09-27 04:15:52 +08:00
346 lines
8.6 KiB
Go
346 lines
8.6 KiB
Go
package dag
|
|
|
|
import (
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// WorkflowAPI provides HTTP handlers for workflow management on top of DAG
|
|
type WorkflowAPI struct {
|
|
enhancedDAG *EnhancedDAG
|
|
}
|
|
|
|
// NewWorkflowAPI creates a new workflow API handler
|
|
func NewWorkflowAPI(enhancedDAG *EnhancedDAG) *WorkflowAPI {
|
|
return &WorkflowAPI{
|
|
enhancedDAG: enhancedDAG,
|
|
}
|
|
}
|
|
|
|
// RegisterWorkflowRoutes registers all workflow routes with Fiber app
|
|
func (api *WorkflowAPI) RegisterWorkflowRoutes(app *fiber.App) {
|
|
v1 := app.Group("/api/v1/workflows")
|
|
|
|
// Workflow definition routes
|
|
v1.Post("/", api.CreateWorkflow)
|
|
v1.Get("/", api.ListWorkflows)
|
|
v1.Get("/:id", api.GetWorkflow)
|
|
v1.Put("/:id", api.UpdateWorkflow)
|
|
v1.Delete("/:id", api.DeleteWorkflow)
|
|
|
|
// Execution routes
|
|
v1.Post("/:id/execute", api.ExecuteWorkflow)
|
|
v1.Get("/:id/executions", api.ListWorkflowExecutions)
|
|
v1.Get("/executions", api.ListAllExecutions)
|
|
v1.Get("/executions/:executionId", api.GetExecution)
|
|
v1.Post("/executions/:executionId/cancel", api.CancelExecution)
|
|
|
|
// Management routes
|
|
v1.Get("/health", api.HealthCheck)
|
|
v1.Get("/metrics", api.GetMetrics)
|
|
}
|
|
|
|
// CreateWorkflow creates a new workflow definition
|
|
func (api *WorkflowAPI) CreateWorkflow(c *fiber.Ctx) error {
|
|
var definition WorkflowDefinition
|
|
if err := c.BodyParser(&definition); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": "Invalid request body",
|
|
})
|
|
}
|
|
|
|
// Set ID if not provided
|
|
if definition.ID == "" {
|
|
definition.ID = uuid.New().String()
|
|
}
|
|
|
|
// Set version if not provided
|
|
if definition.Version == "" {
|
|
definition.Version = "1.0.0"
|
|
}
|
|
|
|
// Set timestamps
|
|
now := time.Now()
|
|
definition.CreatedAt = now
|
|
definition.UpdatedAt = now
|
|
|
|
if err := api.enhancedDAG.RegisterWorkflow(c.Context(), &definition); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusCreated).JSON(definition)
|
|
}
|
|
|
|
// ListWorkflows lists workflow definitions with filtering
|
|
func (api *WorkflowAPI) ListWorkflows(c *fiber.Ctx) error {
|
|
workflows := api.enhancedDAG.ListWorkflows()
|
|
|
|
// Apply filters if provided
|
|
status := c.Query("status")
|
|
if status != "" {
|
|
filtered := make([]*WorkflowDefinition, 0)
|
|
for _, w := range workflows {
|
|
if string(w.Status) == status {
|
|
filtered = append(filtered, w)
|
|
}
|
|
}
|
|
workflows = filtered
|
|
}
|
|
|
|
// Apply pagination
|
|
limit, _ := strconv.Atoi(c.Query("limit", "10"))
|
|
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
|
|
|
total := len(workflows)
|
|
start := offset
|
|
end := offset + limit
|
|
|
|
if start > total {
|
|
start = total
|
|
}
|
|
if end > total {
|
|
end = total
|
|
}
|
|
|
|
pagedWorkflows := workflows[start:end]
|
|
|
|
return c.JSON(fiber.Map{
|
|
"workflows": pagedWorkflows,
|
|
"total": total,
|
|
"limit": limit,
|
|
"offset": offset,
|
|
})
|
|
}
|
|
|
|
// GetWorkflow retrieves a workflow definition by ID
|
|
func (api *WorkflowAPI) GetWorkflow(c *fiber.Ctx) error {
|
|
id := c.Params("id")
|
|
if id == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": "Workflow ID is required",
|
|
})
|
|
}
|
|
|
|
workflow, err := api.enhancedDAG.GetWorkflow(id)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(workflow)
|
|
}
|
|
|
|
// UpdateWorkflow updates an existing workflow definition
|
|
func (api *WorkflowAPI) UpdateWorkflow(c *fiber.Ctx) error {
|
|
id := c.Params("id")
|
|
if id == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": "Workflow ID is required",
|
|
})
|
|
}
|
|
|
|
var definition WorkflowDefinition
|
|
if err := c.BodyParser(&definition); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": "Invalid request body",
|
|
})
|
|
}
|
|
|
|
// Ensure ID matches
|
|
definition.ID = id
|
|
definition.UpdatedAt = time.Now()
|
|
|
|
if err := api.enhancedDAG.RegisterWorkflow(c.Context(), &definition); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(definition)
|
|
}
|
|
|
|
// DeleteWorkflow deletes a workflow definition
|
|
func (api *WorkflowAPI) DeleteWorkflow(c *fiber.Ctx) error {
|
|
id := c.Params("id")
|
|
if id == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": "Workflow ID is required",
|
|
})
|
|
}
|
|
|
|
// For now, we'll just return success
|
|
// In a real implementation, you'd remove it from the registry
|
|
return c.JSON(fiber.Map{
|
|
"message": "Workflow deleted successfully",
|
|
"id": id,
|
|
})
|
|
}
|
|
|
|
// ExecuteWorkflow starts execution of a workflow
|
|
func (api *WorkflowAPI) ExecuteWorkflow(c *fiber.Ctx) error {
|
|
id := c.Params("id")
|
|
if id == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": "Workflow ID is required",
|
|
})
|
|
}
|
|
|
|
var input map[string]interface{}
|
|
if err := c.BodyParser(&input); err != nil {
|
|
input = make(map[string]interface{})
|
|
}
|
|
|
|
execution, err := api.enhancedDAG.ExecuteWorkflow(c.Context(), id, input)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusCreated).JSON(execution)
|
|
}
|
|
|
|
// ListWorkflowExecutions lists executions for a specific workflow
|
|
func (api *WorkflowAPI) ListWorkflowExecutions(c *fiber.Ctx) error {
|
|
workflowID := c.Params("id")
|
|
if workflowID == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": "Workflow ID is required",
|
|
})
|
|
}
|
|
|
|
activeExecutions := api.enhancedDAG.ListActiveExecutions()
|
|
|
|
// Filter by workflow ID
|
|
filtered := make([]*WorkflowExecution, 0)
|
|
for _, exec := range activeExecutions {
|
|
if exec.WorkflowID == workflowID {
|
|
filtered = append(filtered, exec)
|
|
}
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"executions": filtered,
|
|
"total": len(filtered),
|
|
})
|
|
}
|
|
|
|
// ListAllExecutions lists all workflow executions
|
|
func (api *WorkflowAPI) ListAllExecutions(c *fiber.Ctx) error {
|
|
activeExecutions := api.enhancedDAG.ListActiveExecutions()
|
|
|
|
return c.JSON(fiber.Map{
|
|
"executions": activeExecutions,
|
|
"total": len(activeExecutions),
|
|
})
|
|
}
|
|
|
|
// GetExecution retrieves a specific workflow execution
|
|
func (api *WorkflowAPI) GetExecution(c *fiber.Ctx) error {
|
|
executionID := c.Params("executionId")
|
|
if executionID == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": "Execution ID is required",
|
|
})
|
|
}
|
|
|
|
execution, err := api.enhancedDAG.GetExecution(executionID)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(execution)
|
|
}
|
|
|
|
// CancelExecution cancels a running workflow execution
|
|
func (api *WorkflowAPI) CancelExecution(c *fiber.Ctx) error {
|
|
executionID := c.Params("executionId")
|
|
if executionID == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": "Execution ID is required",
|
|
})
|
|
}
|
|
|
|
if err := api.enhancedDAG.CancelExecution(executionID); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"message": "Execution cancelled successfully",
|
|
"id": executionID,
|
|
})
|
|
}
|
|
|
|
// HealthCheck provides health status of the workflow system
|
|
func (api *WorkflowAPI) HealthCheck(c *fiber.Ctx) error {
|
|
workflows := api.enhancedDAG.ListWorkflows()
|
|
activeExecutions := api.enhancedDAG.ListActiveExecutions()
|
|
|
|
return c.JSON(fiber.Map{
|
|
"status": "healthy",
|
|
"workflows": len(workflows),
|
|
"active_executions": len(activeExecutions),
|
|
"timestamp": time.Now(),
|
|
})
|
|
}
|
|
|
|
// GetMetrics provides system metrics
|
|
func (api *WorkflowAPI) GetMetrics(c *fiber.Ctx) error {
|
|
workflows := api.enhancedDAG.ListWorkflows()
|
|
activeExecutions := api.enhancedDAG.ListActiveExecutions()
|
|
|
|
// Basic metrics
|
|
metrics := fiber.Map{
|
|
"workflows": fiber.Map{
|
|
"total": len(workflows),
|
|
"by_status": make(map[string]int),
|
|
},
|
|
"executions": fiber.Map{
|
|
"active": len(activeExecutions),
|
|
"by_status": make(map[string]int),
|
|
},
|
|
}
|
|
|
|
// Count workflows by status
|
|
statusCounts := metrics["workflows"].(fiber.Map)["by_status"].(map[string]int)
|
|
for _, w := range workflows {
|
|
statusCounts[string(w.Status)]++
|
|
}
|
|
|
|
// Count executions by status
|
|
execStatusCounts := metrics["executions"].(fiber.Map)["by_status"].(map[string]int)
|
|
for _, e := range activeExecutions {
|
|
execStatusCounts[string(e.Status)]++
|
|
}
|
|
|
|
return c.JSON(metrics)
|
|
}
|
|
|
|
// Helper method to extend existing DAG API with workflow features
|
|
func (tm *DAG) RegisterWorkflowAPI(app *fiber.App) error {
|
|
// Create enhanced DAG if not already created
|
|
enhanced, err := NewEnhancedDAG(tm.name, tm.key, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Copy existing DAG state to enhanced DAG
|
|
enhanced.DAG = tm
|
|
|
|
// Create and register workflow API
|
|
workflowAPI := NewWorkflowAPI(enhanced)
|
|
workflowAPI.RegisterWorkflowRoutes(app)
|
|
|
|
return nil
|
|
}
|