Add progress bars and spinners for long-running CLI operations#149
Add progress bars and spinners for long-running CLI operations#149Copilot wants to merge 3 commits into
Conversation
- New cmd/progress.go: progressBar struct (in-place fill bar with \r), countdown() for deterministic sleeps, spinWhile() for indeterminate waits (braille spinner). Constants progressLineWidth/progressBarWidth ensure consistent rendering across all callers. - cmd/helpers.go: waitForReady, waitForReadyAny, waitForMigration now show an animated [████░░░░] progress bar instead of printing a new line per attempt. - cmd/deploy_local.go: 30s MySQL init sleep replaced with countdown bar. - cmd/deploy_azure.go: DeployBicep wrapped with spinWhile(); MySQL 30s sleep replaced with countdown(). - cmd/configure_projects.go: triggerAndPoll uses in-place \r updates with renderBar for task progress. Agent-Logs-Url: https://github.com/DevExpGbb/gh-devlake/sessions/5882daaa-5962-4400-851a-c131c3cb23f9 Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces a small, zero-dependency progress UI helper for the gh-devlake Cobra CLI and wires it into several long-running operations (container readiness polling, fixed sleeps, Azure Bicep deployment, and pipeline monitoring) to provide continuous terminal feedback instead of static messages.
Changes:
- Added
cmd/progress.gowith an in-place progress bar, deterministic countdown bar, and a spinner wrapper for indeterminate waits. - Updated DevLake readiness + migration polling to render a single in-place progress bar instead of printing a new line per attempt.
- Wrapped long sleeps and Azure deployment / pipeline monitoring with countdown/spinner/in-place progress output.
Show a summary per file
| File | Description |
|---|---|
cmd/progress.go |
Adds progress bar, countdown, and spinner primitives for in-place terminal feedback. |
cmd/helpers.go |
Uses the new progress bar for /ping readiness and migration polling loops. |
cmd/deploy_local.go |
Replaces a fixed MySQL initialization sleep with a countdown bar during local deploy. |
cmd/deploy_azure.go |
Adds countdown for MySQL start wait and wraps Bicep deployment in a spinner. |
cmd/configure_projects.go |
Switches pipeline polling output to an in-place progress bar/status line. |
Copilot's findings
- Files reviewed: 5/5 changed files
- Comments generated: 3
| const progressLineWidth = 72 | ||
|
|
||
| // progressBarWidth is the number of block characters in every progress bar. | ||
| const progressBarWidth = 24 | ||
|
|
||
| // progressBar renders an in-place terminal progress bar using \r. | ||
| // Create with newProgressBar, call update to redraw, and done to finish. | ||
| type progressBar struct { | ||
| total int | ||
| width int | ||
| start time.Time | ||
| } | ||
|
|
||
| func newProgressBar(total int) *progressBar { | ||
| return &progressBar{total: total, width: progressBarWidth, start: time.Now()} | ||
| } | ||
|
|
||
| // renderBar returns a [████░░░░] string representing current/total progress. | ||
| // Rounding is applied so early progress is visible even at low percentages. | ||
| func renderBar(current, total, width int) string { | ||
| if total <= 0 { | ||
| return "[" + strings.Repeat("░", width) + "]" | ||
| } | ||
| // Use rounding division to avoid premature completion or invisible early progress. | ||
| filled := (width*current + total/2) / total | ||
| if filled > width { | ||
| filled = width | ||
| } | ||
| return "[" + strings.Repeat("█", filled) + strings.Repeat("░", width-filled) + "]" | ||
| } | ||
|
|
||
| // update redraws the progress bar at position current. | ||
| // It uses \r to overwrite the current line in the terminal. | ||
| func (p *progressBar) update(current int, label string) { | ||
| bar := renderBar(current, p.total, p.width) | ||
| elapsed := time.Since(p.start).Truncate(time.Second) | ||
| fmt.Printf("\r %s %2d/%-2d %s (%s elapsed) ", bar, current, p.total, label, elapsed) | ||
| } | ||
|
|
||
| // clear erases the progress bar line and returns the cursor to column 0. | ||
| func (p *progressBar) clear() { | ||
| fmt.Printf("\r%s\r", strings.Repeat(" ", progressLineWidth)) | ||
| } |
| var deployment *azure.DeploymentOutput | ||
| err = spinWhile("Deploying Azure resources via Bicep (this takes several minutes)", func() error { | ||
| var innerErr error | ||
| deployment, innerErr = azure.DeployBicep(azureRG, templatePath, params) | ||
| return innerErr | ||
| }) |
| if err != nil { | ||
| elapsed := time.Since(deadline.Add(-timeout)).Truncate(time.Second) | ||
| fmt.Printf(" [%s] Could not check status...\n", elapsed) | ||
| fmt.Printf("\r %-*s", progressLineWidth-3, fmt.Sprintf("⚠️ Could not check status (%s elapsed)", elapsed)) | ||
| } else { | ||
| elapsed := time.Since(deadline.Add(-timeout)).Truncate(time.Second) | ||
| fmt.Printf(" [%s] Status: %s | Tasks: %d/%d\n", elapsed, p.Status, p.FinishedTasks, p.TotalTasks) | ||
| bar := renderBar(p.FinishedTasks, p.TotalTasks, progressBarWidth) | ||
| fmt.Printf("\r %-*s", progressLineWidth-3, fmt.Sprintf("%s %d/%d tasks — %s (%s elapsed)", bar, p.FinishedTasks, p.TotalTasks, p.Status, elapsed)) | ||
|
|
|
@copilot Can you address the comments in this PR |
…width spaces
Replace strings.Repeat(" ", progressLineWidth) clears with \033[2K (ANSI
erase entire line) in progress.go and configure_projects.go. This correctly
erases lines of any length regardless of label width, fixing garbled output
for long labels like the Azure Bicep spinner. Also removes the now-unneeded
progressLineWidth constant and trailing-space padding from all update/spinner
print calls.
Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
Addressed all three review comments in commit The root cause was the same across all three: relying on overwriting with a fixed number of spaces (72) to "clear" a line, which fails for longer content. Fix: replaced every
The now-unneeded |
Any operation taking >30s (container startup, Azure Bicep deployment, DB migration, pipeline sync) gave no visual feedback beyond a static message. Users had no way to gauge progress or distinguish a slow operation from a hang.
New:
cmd/progress.goZero-dependency terminal progress UI with three primitives:
progressBar— in-place[████████░░░░░░░░] 8/36 waiting for DevLake (1m20s elapsed)via\rcountdown(n, label)— deterministic per-second fill bar for fixed-duration sleepsspinWhile(label, fn)— runsfnin a goroutine, shows a braille spinner (⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏) until it returnsConstants
progressLineWidth = 72andprogressBarWidth = 24keep rendering consistent across all callers.Applied to long-running operations
waitForReady/waitForReadyAny/waitForMigrationstartLocalContainers— MySQL init sleeptime.Sleep(30s)countdown(30, "MySQL initializing")DeployBicepindeploy_azure.gospinWhile("Deploying Azure resources via Bicep", ...)time.Sleep(30s)countdown(30, "waiting for MySQL")triggerAndPollinconfigure_projects.go[████░░░░] 3/8 tasks — TASK_RUNNING (45s elapsed)Example — spinner wrapping Bicep deployment
On failure,
spinWhileclears the spinner line before returning so the caller's error message lands on a clean line.Original prompt
Created from VS Code.