mirror of
https://github.com/oarkflow/mq.git
synced 2025-09-27 04:15:52 +08:00
533 lines
12 KiB
Go
533 lines
12 KiB
Go
package workflow
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// InMemoryRegistry - In-memory implementation of WorkflowRegistry
|
|
type InMemoryRegistry struct {
|
|
workflows map[string]*WorkflowDefinition
|
|
versions map[string][]string // workflow_id -> list of versions
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewInMemoryRegistry creates a new in-memory workflow registry
|
|
func NewInMemoryRegistry() WorkflowRegistry {
|
|
return &InMemoryRegistry{
|
|
workflows: make(map[string]*WorkflowDefinition),
|
|
versions: make(map[string][]string),
|
|
}
|
|
}
|
|
|
|
func (r *InMemoryRegistry) Store(ctx context.Context, definition *WorkflowDefinition) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
// Create a unique key for this version
|
|
key := fmt.Sprintf("%s:%s", definition.ID, definition.Version)
|
|
|
|
// Store the workflow
|
|
r.workflows[key] = definition
|
|
|
|
// Track versions
|
|
if versions, exists := r.versions[definition.ID]; exists {
|
|
// Check if version already exists
|
|
found := false
|
|
for _, v := range versions {
|
|
if v == definition.Version {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
r.versions[definition.ID] = append(versions, definition.Version)
|
|
}
|
|
} else {
|
|
r.versions[definition.ID] = []string{definition.Version}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *InMemoryRegistry) Get(ctx context.Context, id string, version string) (*WorkflowDefinition, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
var key string
|
|
if version == "" {
|
|
// Get latest version
|
|
versions, exists := r.versions[id]
|
|
if !exists || len(versions) == 0 {
|
|
return nil, fmt.Errorf("workflow not found: %s", id)
|
|
}
|
|
|
|
// Sort versions and get the latest
|
|
sort.Slice(versions, func(i, j int) bool {
|
|
return versions[i] > versions[j] // Assuming version strings are sortable
|
|
})
|
|
key = fmt.Sprintf("%s:%s", id, versions[0])
|
|
} else {
|
|
key = fmt.Sprintf("%s:%s", id, version)
|
|
}
|
|
|
|
definition, exists := r.workflows[key]
|
|
if !exists {
|
|
return nil, fmt.Errorf("workflow not found: %s (version: %s)", id, version)
|
|
}
|
|
|
|
return definition, nil
|
|
}
|
|
|
|
func (r *InMemoryRegistry) List(ctx context.Context, filter *WorkflowFilter) ([]*WorkflowDefinition, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
var results []*WorkflowDefinition
|
|
|
|
for _, definition := range r.workflows {
|
|
if r.matchesFilter(definition, filter) {
|
|
results = append(results, definition)
|
|
}
|
|
}
|
|
|
|
// Apply sorting
|
|
if filter != nil && filter.SortBy != "" {
|
|
r.sortResults(results, filter.SortBy, filter.SortOrder)
|
|
}
|
|
|
|
// Apply pagination
|
|
if filter != nil {
|
|
start := filter.Offset
|
|
end := start + filter.Limit
|
|
|
|
if start >= len(results) {
|
|
return []*WorkflowDefinition{}, nil
|
|
}
|
|
|
|
if end > len(results) {
|
|
end = len(results)
|
|
}
|
|
|
|
if filter.Limit > 0 {
|
|
results = results[start:end]
|
|
}
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
func (r *InMemoryRegistry) Delete(ctx context.Context, id string) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
// Get all versions for this workflow
|
|
versions, exists := r.versions[id]
|
|
if !exists {
|
|
return fmt.Errorf("workflow not found: %s", id)
|
|
}
|
|
|
|
// Delete all versions
|
|
for _, version := range versions {
|
|
key := fmt.Sprintf("%s:%s", id, version)
|
|
delete(r.workflows, key)
|
|
}
|
|
|
|
// Remove from versions map
|
|
delete(r.versions, id)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *InMemoryRegistry) GetVersions(ctx context.Context, id string) ([]string, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
versions, exists := r.versions[id]
|
|
if !exists {
|
|
return nil, fmt.Errorf("workflow not found: %s", id)
|
|
}
|
|
|
|
// Return a copy to avoid modification
|
|
result := make([]string, len(versions))
|
|
copy(result, versions)
|
|
|
|
// Sort versions
|
|
sort.Slice(result, func(i, j int) bool {
|
|
return result[i] > result[j]
|
|
})
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (r *InMemoryRegistry) matchesFilter(definition *WorkflowDefinition, filter *WorkflowFilter) bool {
|
|
if filter == nil {
|
|
return true
|
|
}
|
|
|
|
// Filter by status
|
|
if len(filter.Status) > 0 {
|
|
found := false
|
|
for _, status := range filter.Status {
|
|
if definition.Status == status {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Filter by category
|
|
if len(filter.Category) > 0 {
|
|
found := false
|
|
for _, category := range filter.Category {
|
|
if definition.Category == category {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Filter by owner
|
|
if len(filter.Owner) > 0 {
|
|
found := false
|
|
for _, owner := range filter.Owner {
|
|
if definition.Owner == owner {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Filter by tags
|
|
if len(filter.Tags) > 0 {
|
|
for _, filterTag := range filter.Tags {
|
|
found := false
|
|
for _, defTag := range definition.Tags {
|
|
if defTag == filterTag {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
// Filter by creation date
|
|
if filter.CreatedFrom != nil && definition.CreatedAt.Before(*filter.CreatedFrom) {
|
|
return false
|
|
}
|
|
|
|
if filter.CreatedTo != nil && definition.CreatedAt.After(*filter.CreatedTo) {
|
|
return false
|
|
}
|
|
|
|
// Filter by search term
|
|
if filter.Search != "" {
|
|
searchTerm := strings.ToLower(filter.Search)
|
|
if !strings.Contains(strings.ToLower(definition.Name), searchTerm) &&
|
|
!strings.Contains(strings.ToLower(definition.Description), searchTerm) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (r *InMemoryRegistry) sortResults(results []*WorkflowDefinition, sortBy, sortOrder string) {
|
|
ascending := sortOrder != "desc"
|
|
|
|
switch sortBy {
|
|
case "name":
|
|
sort.Slice(results, func(i, j int) bool {
|
|
if ascending {
|
|
return results[i].Name < results[j].Name
|
|
}
|
|
return results[i].Name > results[j].Name
|
|
})
|
|
case "created_at":
|
|
sort.Slice(results, func(i, j int) bool {
|
|
if ascending {
|
|
return results[i].CreatedAt.Before(results[j].CreatedAt)
|
|
}
|
|
return results[i].CreatedAt.After(results[j].CreatedAt)
|
|
})
|
|
case "updated_at":
|
|
sort.Slice(results, func(i, j int) bool {
|
|
if ascending {
|
|
return results[i].UpdatedAt.Before(results[j].UpdatedAt)
|
|
}
|
|
return results[i].UpdatedAt.After(results[j].UpdatedAt)
|
|
})
|
|
default:
|
|
// Default sort by name
|
|
sort.Slice(results, func(i, j int) bool {
|
|
if ascending {
|
|
return results[i].Name < results[j].Name
|
|
}
|
|
return results[i].Name > results[j].Name
|
|
})
|
|
}
|
|
}
|
|
|
|
// InMemoryStateManager - In-memory implementation of StateManager
|
|
type InMemoryStateManager struct {
|
|
executions map[string]*Execution
|
|
checkpoints map[string][]*Checkpoint // execution_id -> checkpoints
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewInMemoryStateManager creates a new in-memory state manager
|
|
func NewInMemoryStateManager() StateManager {
|
|
return &InMemoryStateManager{
|
|
executions: make(map[string]*Execution),
|
|
checkpoints: make(map[string][]*Checkpoint),
|
|
}
|
|
}
|
|
|
|
func (s *InMemoryStateManager) CreateExecution(ctx context.Context, execution *Execution) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if execution.ID == "" {
|
|
return fmt.Errorf("execution ID cannot be empty")
|
|
}
|
|
|
|
s.executions[execution.ID] = execution
|
|
return nil
|
|
}
|
|
|
|
func (s *InMemoryStateManager) UpdateExecution(ctx context.Context, execution *Execution) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if _, exists := s.executions[execution.ID]; !exists {
|
|
return fmt.Errorf("execution not found: %s", execution.ID)
|
|
}
|
|
|
|
execution.UpdatedAt = time.Now()
|
|
s.executions[execution.ID] = execution
|
|
return nil
|
|
}
|
|
|
|
func (s *InMemoryStateManager) GetExecution(ctx context.Context, executionID string) (*Execution, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
execution, exists := s.executions[executionID]
|
|
if !exists {
|
|
return nil, fmt.Errorf("execution not found: %s", executionID)
|
|
}
|
|
|
|
return execution, nil
|
|
}
|
|
|
|
func (s *InMemoryStateManager) ListExecutions(ctx context.Context, filter *ExecutionFilter) ([]*Execution, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
var results []*Execution
|
|
|
|
for _, execution := range s.executions {
|
|
if s.matchesExecutionFilter(execution, filter) {
|
|
results = append(results, execution)
|
|
}
|
|
}
|
|
|
|
// Apply sorting
|
|
if filter != nil && filter.SortBy != "" {
|
|
s.sortExecutionResults(results, filter.SortBy, filter.SortOrder)
|
|
}
|
|
|
|
// Apply pagination
|
|
if filter != nil {
|
|
start := filter.Offset
|
|
end := start + filter.Limit
|
|
|
|
if start >= len(results) {
|
|
return []*Execution{}, nil
|
|
}
|
|
|
|
if end > len(results) {
|
|
end = len(results)
|
|
}
|
|
|
|
if filter.Limit > 0 {
|
|
results = results[start:end]
|
|
}
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
func (s *InMemoryStateManager) DeleteExecution(ctx context.Context, executionID string) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
delete(s.executions, executionID)
|
|
delete(s.checkpoints, executionID)
|
|
return nil
|
|
}
|
|
|
|
func (s *InMemoryStateManager) SaveCheckpoint(ctx context.Context, executionID string, checkpoint *Checkpoint) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if checkpoints, exists := s.checkpoints[executionID]; exists {
|
|
s.checkpoints[executionID] = append(checkpoints, checkpoint)
|
|
} else {
|
|
s.checkpoints[executionID] = []*Checkpoint{checkpoint}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *InMemoryStateManager) GetCheckpoints(ctx context.Context, executionID string) ([]*Checkpoint, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
checkpoints, exists := s.checkpoints[executionID]
|
|
if !exists {
|
|
return []*Checkpoint{}, nil
|
|
}
|
|
|
|
// Return a copy
|
|
result := make([]*Checkpoint, len(checkpoints))
|
|
copy(result, checkpoints)
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (s *InMemoryStateManager) matchesExecutionFilter(execution *Execution, filter *ExecutionFilter) bool {
|
|
if filter == nil {
|
|
return true
|
|
}
|
|
|
|
// Filter by workflow ID
|
|
if len(filter.WorkflowID) > 0 {
|
|
found := false
|
|
for _, workflowID := range filter.WorkflowID {
|
|
if execution.WorkflowID == workflowID {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Filter by status
|
|
if len(filter.Status) > 0 {
|
|
found := false
|
|
for _, status := range filter.Status {
|
|
if execution.Status == status {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Filter by owner
|
|
if len(filter.Owner) > 0 {
|
|
found := false
|
|
for _, owner := range filter.Owner {
|
|
if execution.Owner == owner {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Filter by priority
|
|
if len(filter.Priority) > 0 {
|
|
found := false
|
|
for _, priority := range filter.Priority {
|
|
if execution.Priority == priority {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Filter by start date
|
|
if filter.StartedFrom != nil && execution.StartedAt.Before(*filter.StartedFrom) {
|
|
return false
|
|
}
|
|
|
|
if filter.StartedTo != nil && execution.StartedAt.After(*filter.StartedTo) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (s *InMemoryStateManager) sortExecutionResults(results []*Execution, sortBy, sortOrder string) {
|
|
ascending := sortOrder != "desc"
|
|
|
|
switch sortBy {
|
|
case "started_at":
|
|
sort.Slice(results, func(i, j int) bool {
|
|
if ascending {
|
|
return results[i].StartedAt.Before(results[j].StartedAt)
|
|
}
|
|
return results[i].StartedAt.After(results[j].StartedAt)
|
|
})
|
|
case "updated_at":
|
|
sort.Slice(results, func(i, j int) bool {
|
|
if ascending {
|
|
return results[i].UpdatedAt.Before(results[j].UpdatedAt)
|
|
}
|
|
return results[i].UpdatedAt.After(results[j].UpdatedAt)
|
|
})
|
|
case "priority":
|
|
sort.Slice(results, func(i, j int) bool {
|
|
priorityOrder := map[Priority]int{
|
|
PriorityLow: 1,
|
|
PriorityMedium: 2,
|
|
PriorityHigh: 3,
|
|
PriorityCritical: 4,
|
|
}
|
|
|
|
pi := priorityOrder[results[i].Priority]
|
|
pj := priorityOrder[results[j].Priority]
|
|
|
|
if ascending {
|
|
return pi < pj
|
|
}
|
|
return pi > pj
|
|
})
|
|
default:
|
|
// Default sort by started_at
|
|
sort.Slice(results, func(i, j int) bool {
|
|
if ascending {
|
|
return results[i].StartedAt.Before(results[j].StartedAt)
|
|
}
|
|
return results[i].StartedAt.After(results[j].StartedAt)
|
|
})
|
|
}
|
|
}
|