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:
DaisukeYoda
2025-10-04 22:32:50 +09:00
parent af4ede15e1
commit 1abeb9341b

View File

@@ -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
}