mirror of
https://github.com/ludo-technologies/pyscn.git
synced 2025-10-06 00:59:45 +03:00
feat: implement unified progress bar with time-based estimation
Replaced multiple task-specific progress bars with a single unified progress bar that shows overall analysis progress. Changes: - Added calculateEstimatedTime() to estimate total analysis time based on file count and enabled analyses - Implemented startFakeProgressUpdater() that updates progress based on elapsed time rather than actual file processing - Used conservative time estimates: O(n) for fast analyses, O(n²) for clone detection - Single "Analysis" progress bar replaces individual task progress bars - Progress updates every 100ms, capping at 99% until completion Benefits: - Provides meaningful progress feedback during long-running analyses - Works with --select flag to adjust estimates for selected analyses - Conservative estimates (7min estimated vs 3min44s actual for 207 files) provide better UX than overly optimistic estimates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -191,9 +191,14 @@ func (uc *AnalyzeUseCase) Execute(ctx context.Context, config AnalyzeUseCaseConf
|
||||
return nil, fmt.Errorf("no Python files found in the specified paths")
|
||||
}
|
||||
|
||||
// Initialize progress tracking
|
||||
// Calculate estimated time based on file count and enabled analyses
|
||||
estimatedTime := uc.calculateEstimatedTime(len(files), config)
|
||||
|
||||
// Start unified progress tracking with fake progress based on time
|
||||
var progressDone chan struct{}
|
||||
if uc.progressManager != nil {
|
||||
uc.progressManager.Initialize(len(files))
|
||||
uc.progressManager.Initialize(100) // 100% based progress
|
||||
progressDone = uc.startFakeProgressUpdater(estimatedTime)
|
||||
}
|
||||
|
||||
// Create analysis tasks
|
||||
@@ -209,24 +214,25 @@ func (uc *AnalyzeUseCase) Execute(ctx context.Context, config AnalyzeUseCaseConf
|
||||
wg.Add(1)
|
||||
go func(t *AnalysisTask) {
|
||||
defer wg.Done()
|
||||
|
||||
if uc.progressManager != nil {
|
||||
uc.progressManager.StartTask(t.Name)
|
||||
}
|
||||
|
||||
result, err := t.Execute(ctx)
|
||||
t.Result = result
|
||||
t.Error = err
|
||||
|
||||
if uc.progressManager != nil {
|
||||
uc.progressManager.CompleteTask(t.Name, err == nil)
|
||||
}
|
||||
}(task)
|
||||
}
|
||||
|
||||
// Wait for all tasks to complete
|
||||
wg.Wait()
|
||||
|
||||
// Stop progress updater
|
||||
if progressDone != nil {
|
||||
close(progressDone)
|
||||
// Ensure progress bar reaches 100%
|
||||
if uc.progressManager != nil {
|
||||
uc.progressManager.UpdateProgress("Analysis", 100, 100)
|
||||
uc.progressManager.CompleteTask("Analysis", true)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for errors
|
||||
var errors []error
|
||||
for _, task := range tasks {
|
||||
@@ -561,3 +567,70 @@ func (uc *AnalyzeUseCase) getFilePatterns(configPath string, paths []string) ([]
|
||||
|
||||
return includePatterns, excludePatterns, nil
|
||||
}
|
||||
|
||||
// calculateEstimatedTime estimates the total analysis time based on file count and enabled analyses
|
||||
func (uc *AnalyzeUseCase) calculateEstimatedTime(fileCount int, config AnalyzeUseCaseConfig) float64 {
|
||||
n := float64(fileCount)
|
||||
totalTime := 0.0
|
||||
|
||||
// Linear analyses (fast)
|
||||
if !config.SkipComplexity {
|
||||
totalTime += 0.01 * n // Complexity: ~0.01s per file
|
||||
}
|
||||
if !config.SkipDeadCode {
|
||||
totalTime += 0.01 * n // Dead Code: ~0.01s per file
|
||||
}
|
||||
if !config.SkipCBO {
|
||||
totalTime += 0.01 * n // CBO: ~0.01s per file
|
||||
}
|
||||
if !config.SkipSystem {
|
||||
totalTime += 0.02 * n // System: ~0.02s per file (slightly heavier)
|
||||
}
|
||||
|
||||
// Clone detection (quadratic - the dominant factor)
|
||||
if !config.SkipClones {
|
||||
// More conservative estimate: O(n²) is the bottleneck
|
||||
// For 207 files: 0.01 * 207² ≈ 428 seconds
|
||||
totalTime += 0.01 * n * n
|
||||
}
|
||||
|
||||
// Minimum time to avoid division by zero
|
||||
if totalTime < 0.1 {
|
||||
totalTime = 0.1
|
||||
}
|
||||
|
||||
return totalTime
|
||||
}
|
||||
|
||||
// startFakeProgressUpdater starts a background goroutine that updates progress based on elapsed time
|
||||
func (uc *AnalyzeUseCase) startFakeProgressUpdater(estimatedTime float64) chan struct{} {
|
||||
done := make(chan struct{})
|
||||
startTime := time.Now()
|
||||
|
||||
// Start task with unified name
|
||||
uc.progressManager.StartTask("Analysis")
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(100 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
elapsed := time.Since(startTime).Seconds()
|
||||
// Progress increases with elapsed time, but caps at 99%
|
||||
// (we'll set it to 100% when tasks actually complete)
|
||||
progress := int((elapsed / estimatedTime) * 100)
|
||||
if progress > 99 {
|
||||
progress = 99
|
||||
}
|
||||
uc.progressManager.UpdateProgress("Analysis", progress, 100)
|
||||
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return done
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user