From afe3a79ae4e4f40ea06fa92b79adef6f0d092110 Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Sat, 2 May 2026 19:03:50 +0300 Subject: [PATCH] feat: Add MVNO onboarding monitor with progress tracking and metrics collection - Add OnboardingMonitor struct with progress map and mutex for concurrent access - Add UpdateProgress and GetProgress methods for tracking individual MVNO onboarding state - Add GetAllProgress with map copy to prevent concurrent access issues - Add GetActiveOnboardingCount and GetCompletedOnboardingCount for status aggregation - Add GetAverageOnboardingTime calculation with completed onboarding duration tracking - Add GetStepSuccess --- .../internal/mvno/monitor.go | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 apps/carrier-connector/internal/mvno/monitor.go diff --git a/apps/carrier-connector/internal/mvno/monitor.go b/apps/carrier-connector/internal/mvno/monitor.go new file mode 100644 index 0000000..9b58deb --- /dev/null +++ b/apps/carrier-connector/internal/mvno/monitor.go @@ -0,0 +1,214 @@ +package mvno + +import ( + "maps" + "sync" + "time" + + "github.com/sirupsen/logrus" +) + +// OnboardingMonitor tracks onboarding progress and status +type OnboardingMonitor struct { + logger *logrus.Logger + progress map[string]*OnboardingProgress + mu sync.RWMutex +} + +// NewOnboardingMonitor creates a new monitor instance +func NewOnboardingMonitor(logger *logrus.Logger) *OnboardingMonitor { + return &OnboardingMonitor{ + logger: logger, + progress: make(map[string]*OnboardingProgress), + } +} + +// UpdateProgress updates the onboarding progress for an MVNO +func (m *OnboardingMonitor) UpdateProgress(mvnoID string, progress *OnboardingProgress) { + m.mu.Lock() + defer m.mu.Unlock() + + m.progress[mvnoID] = progress + + m.logger.WithFields(map[string]any{ + "mvno_id": mvnoID, + "progress": progress.Progress, + "status": m.getCurrentStatus(progress), + }).Info("Onboarding progress updated") +} + +// GetProgress retrieves current onboarding progress +func (m *OnboardingMonitor) GetProgress(mvnoID string) (*OnboardingProgress, bool) { + m.mu.RLock() + defer m.mu.RUnlock() + + progress, exists := m.progress[mvnoID] + return progress, exists +} + +// GetAllProgress retrieves progress for all MVNOs +func (m *OnboardingMonitor) GetAllProgress() map[string]*OnboardingProgress { + m.mu.RLock() + defer m.mu.RUnlock() + + // Return a copy to avoid concurrent access issues + result := make(map[string]*OnboardingProgress) + maps.Copy(result, m.progress) + return result +} + +// GetActiveOnboardingCount returns count of active onboarding processes +func (m *OnboardingMonitor) GetActiveOnboardingCount() int { + m.mu.RLock() + defer m.mu.RUnlock() + + count := 0 + for _, progress := range m.progress { + if progress.Progress < 100.0 && !progress.CompletedAt.IsZero() { + count++ + } + } + return count +} + +// GetCompletedOnboardingCount returns count of completed onboardings +func (m *OnboardingMonitor) GetCompletedOnboardingCount() int { + m.mu.RLock() + defer m.mu.RUnlock() + + count := 0 + for _, progress := range m.progress { + if progress.Progress == 100.0 && !progress.CompletedAt.IsZero() { + count++ + } + } + return count +} + +// GetAverageOnboardingTime calculates average onboarding duration +func (m *OnboardingMonitor) GetAverageOnboardingTime() time.Duration { + m.mu.RLock() + defer m.mu.RUnlock() + + var totalDuration time.Duration + completedCount := 0 + + for _, progress := range m.progress { + if progress.Progress == 100.0 && !progress.CompletedAt.IsZero() && !progress.StartedAt.IsZero() { + totalDuration += progress.CompletedAt.Sub(progress.StartedAt) + completedCount++ + } + } + + if completedCount == 0 { + return 0 + } + + return totalDuration / time.Duration(completedCount) +} + +// GetStepSuccessRate calculates success rate for each step +func (m *OnboardingMonitor) GetStepSuccessRate() map[string]float64 { + m.mu.RLock() + defer m.mu.RUnlock() + + stepStats := make(map[string]map[string]int) + + // Collect step statistics + for _, progress := range m.progress { + for _, step := range progress.Steps { + if _, exists := stepStats[step.Name]; !exists { + stepStats[step.Name] = map[string]int{"completed": 0, "failed": 0, "total": 0} + } + stepStats[step.Name]["total"]++ + if step.Status == "completed" { + stepStats[step.Name]["completed"]++ + } else if step.Status == "failed" { + stepStats[step.Name]["failed"]++ + } + } + } + + // Calculate success rates + successRates := make(map[string]float64) + for stepName, stats := range stepStats { + if stats["total"] > 0 { + successRates[stepName] = float64(stats["completed"]) / float64(stats["total"]) * 100 + } + } + + return successRates +} + +// CleanupOldProgress removes old completed progress records +func (m *OnboardingMonitor) CleanupOldProgress(maxAge time.Duration) { + m.mu.Lock() + defer m.mu.Unlock() + + cutoff := time.Now().Add(-maxAge) + for mvnoID, progress := range m.progress { + if progress.Progress == 100.0 && !progress.CompletedAt.IsZero() && progress.CompletedAt.Before(cutoff) { + delete(m.progress, mvnoID) + m.logger.WithFields(map[string]any{ + "mvno_id": mvnoID, + "completed_at": progress.CompletedAt, + }).Info("Cleaned up old onboarding progress") + } + } +} + +// GetFailedOnboardings returns list of failed onboardings +func (m *OnboardingMonitor) GetFailedOnboardings() []string { + m.mu.RLock() + defer m.mu.RUnlock() + + var failed []string + for mvnoID, progress := range m.progress { + hasFailed := false + for _, step := range progress.Steps { + if step.Status == "failed" { + hasFailed = true + break + } + } + if hasFailed { + failed = append(failed, mvnoID) + } + } + return failed +} + +// getCurrentStatus determines current status from progress +func (m *OnboardingMonitor) getCurrentStatus(progress *OnboardingProgress) string { + if progress.Progress == 100.0 { + return "completed" + } + + if progress.Progress == 0.0 { + return "pending" + } + + // Check for failed steps + for _, step := range progress.Steps { + if step.Status == "failed" { + return "failed" + } + } + + return "in_progress" +} + +// GetOnboardingMetrics returns comprehensive onboarding metrics +func (m *OnboardingMonitor) GetOnboardingMetrics() map[string]any { + m.mu.RLock() + defer m.mu.RUnlock() + + return map[string]any{ + "active_count": m.GetActiveOnboardingCount(), + "completed_count": m.GetCompletedOnboardingCount(), + "average_duration": m.GetAverageOnboardingTime().String(), + "step_success_rates": m.GetStepSuccessRate(), + "failed_count": len(m.GetFailedOnboardings()), + "total_onboardings": len(m.progress), + } +}