From bb78c69303cfd69a76811c43098f5fe87fba302d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 22:44:07 +0000 Subject: [PATCH] feat: add duration formatting and retry countdowns for better CLI UX - Replaces silent `time.sleep()` with a visual countdown timer during API retries and folder polling. - Adds `format_duration` helper to display time in `Xm Ys` format in the summary table. - Improves perceived responsiveness and readability of long-running sync operations. - Includes unit tests for new functionality in `tests/test_ux.py`. Co-authored-by: abhimehro <84992105+abhimehro@users.noreply.github.com> --- main.py | 18 ++++++++++++++---- tests/test_ux.py | 14 ++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index 4b766144..fa96a48e 100644 --- a/main.py +++ b/main.py @@ -256,6 +256,16 @@ def print_plan_details(plan_entry: Dict[str, Any]) -> None: print("") +def format_duration(seconds: float) -> str: + """Formats duration in a human-readable way (e.g., 2m 05s).""" + if seconds < 60: + return f"{seconds:.1f}s" + + minutes = int(seconds // 60) + rem_seconds = int(seconds % 60) + return f"{minutes}m {rem_seconds:02d}s" + + def countdown_timer(seconds: int, message: str = "Waiting") -> None: """Shows a countdown timer if strictly in a TTY, otherwise just sleeps.""" if not USE_COLORS: @@ -836,7 +846,7 @@ def verify_access_and_get_folders( MAX_RETRIES, wait_time, ) - time.sleep(wait_time) + countdown_timer(int(wait_time), "Retrying") def get_all_existing_rules( @@ -1044,7 +1054,7 @@ def create_folder( log.info( f"Folder '{sanitize_for_log(name)}' not found yet. Retrying in {wait_time}s..." ) - time.sleep(wait_time) + countdown_timer(int(wait_time), "Waiting for folder") log.error( f"Folder {sanitize_for_log(name)} was not found after creation and retries." @@ -1651,7 +1661,7 @@ def validate_profile_input(value: str) -> bool: f"{res['profile']:<{profile_col_width}} | " f"{res['folders']:>10} | " f"{res['rules']:>10,} | " - f"{res['duration']:>9.1f}s | " + f"{format_duration(res['duration']):>10} | " f"{status_color}{res['status_label']:<15}{Colors.ENDC}" ) total_folders += res["folders"] @@ -1682,7 +1692,7 @@ def validate_profile_input(value: str) -> bool: f"{'TOTAL':<{profile_col_width}} | " f"{total_folders:>10} | " f"{total_rules:>10,} | " - f"{total_duration:>9.1f}s | " + f"{format_duration(total_duration):>10} | " f"{total_status_color}{total_status_text:<15}{Colors.ENDC}" ) print("=" * table_width + "\n") diff --git a/tests/test_ux.py b/tests/test_ux.py index 311b7e91..0e74d8e5 100644 --- a/tests/test_ux.py +++ b/tests/test_ux.py @@ -2,6 +2,7 @@ import sys from unittest.mock import MagicMock import main +import pytest def test_countdown_timer_visuals(monkeypatch): """Verify that countdown_timer writes a progress bar to stderr.""" @@ -44,3 +45,16 @@ def test_countdown_timer_no_colors(monkeypatch): mock_stderr.write.assert_not_called() # Should call sleep exactly once with full seconds mock_sleep.assert_called_once_with(3) + +@pytest.mark.parametrize("seconds, expected", [ + (5.2, "5.2s"), + (0.0, "0.0s"), + (59.9, "59.9s"), + (60.0, "1m 00s"), + (65.5, "1m 05s"), + (125.0, "2m 05s"), + (3600.0, "60m 00s"), +]) +def test_format_duration(seconds, expected): + """Verify format_duration output for various inputs.""" + assert main.format_duration(seconds) == expected