Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

Lease progress details while users wait#688

Merged
kirkbrauer merged 1 commit intojumpstarter-dev:mainfrom
mangelajo:lease-waiting
Oct 6, 2025
Merged

Lease progress details while users wait#688
kirkbrauer merged 1 commit intojumpstarter-dev:mainfrom
mangelajo:lease-waiting

Conversation

@mangelajo
Copy link
Copy Markdown
Member

@mangelajo mangelajo commented Oct 3, 2025

image

when no tty:
image

Summary by CodeRabbit

  • New Features

    • Improved CLI experience during lease acquisition with an interactive spinner and clear status messages.
    • Shows progress updates and elapsed time; automatically falls back to plain logging when no terminal is available.
    • Provides immediate success indication when ready and informative messages for other states.
  • Tests

    • Added comprehensive unit and async tests covering terminal detection, context lifecycle, status updates, ticking behavior, and elapsed-time formatting.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Oct 3, 2025

Walkthrough

Adds LeaseAcquisitionSpinner and integrates it into the lease acquisition flow to provide terminal-aware spinner/status updates, per-tick progress, and state-driven messages; includes a new comprehensive test suite for the spinner behavior and async interactions. (47 words)

Changes

Cohort / File(s) Summary
Client lease acquisition UX
packages/jumpstarter/jumpstarter/client/lease.py
Added LeaseAcquisitionSpinner class (uses rich.Console) and integrated it into _acquire to drive terminal-aware spinner/status updates, per-second ticks, elapsed-time display, and state-specific messages for Pending, Ready, Unsatisfiable, Invalid, Released, etc.; added helper to map lease states to spinner messages.
Spinner tests
packages/jumpstarter/jumpstarter/client/lease_test.py
New unit tests covering initialization (with/without lease name), terminal availability detection, context manager enter/exit behavior, update_status() formatting and logging paths, tick() semantics, elapsed-time formatting, message preservation across ticks, and async/context integration.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client/_acquire
  participant LAS as LeaseAcquisitionSpinner
  participant LS as Lease Service

  Note over C,LAS: Start acquisition context
  C->>LAS: enter() (creates Console if TTY)
  LAS-->>C: ready to update

  loop every 1s (up to N ticks)
    C->>LS: get_lease_status()
    LS-->>C: status
    C->>LAS: update_status(status)
    alt status == Ready
      LAS-->>C: display success message (stop spinner)
      C-->>C: break loop
    else status == Unsatisfiable/Invalid/Released
      C-->>C: raise appropriate error / handle
    else
      LAS-->>LAS: set message (Pending/... with progress)
      C->>LAS: tick() (spinner.tick or log elapsed)
    end
  end

  C->>LAS: exit() (stop spinner/cleanup)
  Note over C,LAS: Acquisition finished
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I spin a soft blue line in the burrowed night,
Counting ticks while leases inch toward light.
Pending hops, Ready gleams like a carrot true,
Logs kept tidy — a bunny's little queue.
Spin, tick, succeed — a hop and a chew 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title accurately summarizes the main change—introducing lease progress details while users wait—and is concise and clear, directly corresponding to the new spinner-based UX added to the lease acquisition flow.
Docstring Coverage ✅ Passed Docstring coverage is 82.76% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@netlify
Copy link
Copy Markdown

netlify bot commented Oct 3, 2025

Deploy Preview for jumpstarter-docs ready!

Name Link
🔨 Latest commit 0e2be15
🔍 Latest deploy log https://app.netlify.com/projects/jumpstarter-docs/deploys/68e3e344b74e9b0008949525
😎 Deploy Preview https://deploy-preview-688--jumpstarter-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Comment thread packages/jumpstarter/jumpstarter/client/lease.py Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6266878 and d5a26bd.

📒 Files selected for processing (2)
  • packages/jumpstarter/jumpstarter/client/lease.py (4 hunks)
  • packages/jumpstarter/jumpstarter/client/lease_test.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/jumpstarter/jumpstarter/client/lease.py (1)
packages/jumpstarter/jumpstarter/common/condition.py (4)
  • condition_true (26-27)
  • condition_message (16-23)
  • condition_false (30-31)
  • condition_present_and_equal (6-13)
packages/jumpstarter/jumpstarter/client/lease_test.py (1)
packages/jumpstarter/jumpstarter/client/lease.py (4)
  • LeaseAcquisitionSpinner (300-354)
  • _is_terminal_available (311-318)
  • update_status (335-346)
  • tick (348-354)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Redirect rules - jumpstarter-docs
  • GitHub Check: Header rules - jumpstarter-docs
  • GitHub Check: Pages changed - jumpstarter-docs
  • GitHub Check: build
  • GitHub Check: e2e
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: pytest-matrix (macos-15, 3.13)
  • GitHub Check: pytest-matrix (macos-15, 3.12)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
  • GitHub Check: pytest-matrix (macos-15, 3.11)

Comment thread packages/jumpstarter/jumpstarter/client/lease.py Outdated
Comment thread packages/jumpstarter/jumpstarter/client/lease.py Outdated
Comment thread packages/jumpstarter/jumpstarter/client/lease.py
@mangelajo
Copy link
Copy Markdown
Member Author

so for colors, there is https://no-color.org/ that rich obeys, but for spinners, etc, asking chatgpt:

Disable interactivity
CI=1, TERM=dumb, NONINTERACTIVE=1
Signals that output is not a TTY and interactive features (progress bars, prompts, spinners) should be suppressed.

Disable progress bars
DISABLE_PROGRESS_BAR=1, NO_PROGRESS=1, NO_SPINNER=1
Non-standard but used by several tools (e.g. some Python, Rust, and Node CLIs).

Force plain output
FORCE_PLAIN_OUTPUT=1
Used by some CI/CD tools to disable all fancy terminal control sequences.
Disable any TTY decoration
CLICOLOR=0 or CLICOLOR_FORCE=0
Related to color output, but some tools (e.g. cargo, brew) extend this to progress bars as well.

I'd propose we use NONINTERACTIVE, WDYT?

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/jumpstarter/jumpstarter/client/lease.py (1)

316-323: Consider using Rich Console's built-in terminal detection.

The manual TTY check reimplements what Rich's Console already provides. Rich Console has built-in methods and respects its own environment variables (e.g., TTY_INTERACTIVE, TTY_COMPATIBLE from Rich 14.1.0).

Based on learnings about Rich 14.1.0, consider using Rich's built-in detection:

    def _is_terminal_available(self) -> bool:
-       """Check if we're running in a terminal/TTY."""
-       return (
-           hasattr(sys.stdout, 'isatty') and
-           sys.stdout.isatty() and
-           hasattr(sys.stderr, 'isatty') and
-           sys.stderr.isatty()
-       )
+       """Check if we're running in a terminal/TTY using Rich's detection."""
+       return self.console.is_terminal

This leverages Rich's sophisticated terminal detection which handles edge cases and respects Rich's own environment variables. Note: You'll need to move this check to after Console initialization.

Based on learnings

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d5a26bd and 8316398.

📒 Files selected for processing (2)
  • packages/jumpstarter/jumpstarter/client/lease.py (4 hunks)
  • packages/jumpstarter/jumpstarter/client/lease_test.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/jumpstarter/jumpstarter/client/lease_test.py
🧰 Additional context used
🧬 Code graph analysis (1)
packages/jumpstarter/jumpstarter/client/lease.py (2)
packages/jumpstarter/jumpstarter/common/condition.py (4)
  • condition_true (26-27)
  • condition_message (16-23)
  • condition_false (30-31)
  • condition_present_and_equal (6-13)
packages/jumpstarter/jumpstarter/client/exceptions.py (1)
  • LeaseError (4-5)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: e2e
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
🔇 Additional comments (5)
packages/jumpstarter/jumpstarter/client/lease.py (5)

2-3: LGTM!

The new imports (os, sys, rich.console.Console) are necessary for the spinner functionality and terminal detection.

Also applies to: 25-25


118-128: LGTM!

The status update logic correctly handles different lease conditions and provides appropriate user feedback. The use of condition_true and condition_message helpers ensures consistency with the codebase's condition-checking patterns.

Note: Line 127 has a typo ("server to" with extra space) that was already flagged in a previous review.


312-314: Document environment variable handling and consider Rich's TTY_INTERACTIVE.

The NONINTERACTIVE environment variable is a sensible choice for controlling interactive features. However, Rich 14.1.0 introduced TTY_INTERACTIVE for this exact purpose. You should either:

  1. Document that NONINTERACTIVE is the preferred way to disable interactivity in this codebase
  2. Consider also checking TTY_INTERACTIVE=0 for consistency with Rich's conventions
  3. Note that Rich's Console will independently respect NO_COLOR, FORCE_COLOR, etc.

From PR comments, you're investigating standard environment variables that Rich follows. The learnings confirm Rich 14.1.0 supports TTY_INTERACTIVE to force interactive mode on/off. Consider checking both:

    def _is_non_interactive(self) -> bool:
-       """Check if the user desires a NONINTERACTIVE environment."""
-       return os.environ.get("NONINTERACTIVE", "false").lower() in ["true", "1"]
+       """Check if the user desires a NONINTERACTIVE environment.
+       
+       Respects both NONINTERACTIVE (jumpstarter convention) and TTY_INTERACTIVE (Rich convention).
+       """
+       noninteractive = os.environ.get("NONINTERACTIVE", "false").lower() in ["true", "1"]
+       tty_interactive = os.environ.get("TTY_INTERACTIVE", "1") == "0"
+       return noninteractive or tty_interactive

Based on learnings


325-359: LGTM!

The context manager implementation is clean with proper resource management. The update_status method provides good fallback behavior by logging to logger.info when no terminal is available, and the tick method maintains a consistent UX by updating the elapsed time without changing the message.


136-178: Add fallback logging for non-interactive environments

update_status only updates the spinner when _should_show_spinner is true, so on non-TTY runs the success message (with or without emoji) is never emitted. Introduce a fallback—e.g. call logger.info(success_msg) (stripped of emoji if preferred) when _should_show_spinner is false—to ensure the “Lease … acquired” message appears in CI or non-Unicode logs.

Likely an incorrect or invalid review comment.

@mangelajo
Copy link
Copy Markdown
Member Author

oops, now it's really updated, I had pushed branch to the wrong repo.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/jumpstarter/jumpstarter/client/lease.py (2)

312-314: Consider additional environment variables for broader compatibility.

The current implementation checks only NONINTERACTIVE, which aligns with the PR proposal. However, the PR comments mention several other widely-used environment variables that indicate non-interactive environments:

  • CI=1 (standard CI environment indicator)
  • TERM=dumb (non-interactive terminal)
  • NO_PROGRESS=1 (suppress progress bars)
  • NO_SPINNER=1 (suppress spinners)

Consider expanding the check to support these additional variables for better compatibility with various CI/CD systems and tools.

Example implementation:

 def _is_non_interactive(self) -> bool:
-    """Check if the user desires a NONINTERACTIVE environment."""
-    return os.environ.get("NONINTERACTIVE", "false").lower() in ["true", "1"]
+    """Check if the environment indicates non-interactive mode."""
+    # Check various environment variables that indicate non-interactive mode
+    if os.environ.get("NONINTERACTIVE", "false").lower() in ["true", "1"]:
+        return True
+    if os.environ.get("CI", "false").lower() in ["true", "1"]:
+        return True
+    if os.environ.get("TERM", "") == "dumb":
+        return True
+    if os.environ.get("NO_PROGRESS", "false").lower() in ["true", "1"]:
+        return True
+    if os.environ.get("NO_SPINNER", "false").lower() in ["true", "1"]:
+        return True
+    return False

316-323: Consider leveraging Rich's built-in TTY detection.

Rich 14.1.0 introduced the TTY_INTERACTIVE environment variable specifically for controlling interactive mode. Instead of implementing custom TTY detection, you could use Rich's Console.is_interactive property which respects TTY_INTERACTIVE and other Rich-specific environment variables.

Based on learnings

Example implementation:

-def _is_terminal_available(self) -> bool:
-    """Check if we're running in a terminal/TTY."""
-    return (
-        hasattr(sys.stdout, 'isatty') and
-        sys.stdout.isatty() and
-        hasattr(sys.stderr, 'isatty') and
-        sys.stderr.isatty()
-    )
+def _is_terminal_available(self) -> bool:
+    """Check if we're running in a terminal/TTY."""
+    # Rich's is_interactive respects TTY_INTERACTIVE and other env vars
+    return self.console.is_interactive

Then simplify line 309:

-self._should_show_spinner = self._is_terminal_available() and not self._is_non_interactive()
+self._should_show_spinner = self._is_terminal_available() and not self._is_non_interactive()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8316398 and 0e2be15.

📒 Files selected for processing (2)
  • packages/jumpstarter/jumpstarter/client/lease.py (4 hunks)
  • packages/jumpstarter/jumpstarter/client/lease_test.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/jumpstarter/jumpstarter/client/lease.py (1)
packages/jumpstarter/jumpstarter/common/condition.py (4)
  • condition_true (26-27)
  • condition_message (16-23)
  • condition_false (30-31)
  • condition_present_and_equal (6-13)
packages/jumpstarter/jumpstarter/client/lease_test.py (1)
packages/jumpstarter/jumpstarter/client/lease.py (4)
  • LeaseAcquisitionSpinner (301-359)
  • _is_terminal_available (316-323)
  • update_status (340-351)
  • tick (353-359)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Redirect rules - jumpstarter-docs
  • GitHub Check: Header rules - jumpstarter-docs
  • GitHub Check: Pages changed - jumpstarter-docs
  • GitHub Check: build
  • GitHub Check: pytest-matrix (macos-15, 3.13)
  • GitHub Check: pytest-matrix (macos-15, 3.12)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: pytest-matrix (macos-15, 3.11)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: e2e
🔇 Additional comments (5)
packages/jumpstarter/jumpstarter/client/lease.py (4)

2-3: LGTM!

The new imports (os, sys, Console) are necessary for environment variable checking, TTY detection, and spinner display functionality.

Also applies to: 25-25


118-127: LGTM!

The status update logic correctly interprets lease conditions and provides appropriate user feedback. The use of existing condition helper functions ensures consistency with the rest of the codebase.


134-179: LGTM!

The refactored acquisition flow correctly integrates the spinner, handles all lease states appropriately, and provides tick-based progress updates for better UX. The previous timeout concern has been addressed by breaking immediately after success message.


325-359: LGTM!

The context manager and update methods are well-implemented:

  • __enter__ and __exit__ properly manage spinner lifecycle
  • update_status provides appropriate fallback to logging when no console
  • tick correctly preserves the message while updating elapsed time
  • The hardcoded color styling ([blue], [dim]) is fine—Rich automatically respects NO_COLOR and related environment variables for color output control
packages/jumpstarter/jumpstarter/client/lease_test.py (1)

1-230: Excellent test coverage!

The test suite is comprehensive and well-structured:

  • Covers initialization with and without lease name
  • Validates TTY detection across various scenarios (all-tty, no-tty, partial-tty)
  • Tests context manager behavior with and without console
  • Verifies status updates and tick behavior in different modes
  • Tests elapsed time formatting and message preservation
  • Includes async integration test
  • Uses appropriate mocking techniques

All test cases are relevant and correctly validate the LeaseAcquisitionSpinner behavior.

Copy link
Copy Markdown
Member

@kirkbrauer kirkbrauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

@kirkbrauer kirkbrauer merged commit 5f3a046 into jumpstarter-dev:main Oct 6, 2025
19 checks passed
jumpstarter-backport-bot bot pushed a commit that referenced this pull request Oct 6, 2025
@jumpstarter-backport-bot
Copy link
Copy Markdown

Successfully created backport PR for release-0.7:

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants