Skip to content

Commit a485d0f

Browse files
adding rich output for rayjobs - widget and console output
1 parent 4c2f265 commit a485d0f

File tree

6 files changed

+1337
-7
lines changed

6 files changed

+1337
-7
lines changed

src/codeflare_sdk/ray/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
RayJobDeploymentStatus,
1111
CodeflareRayJobStatus,
1212
RayJobInfo,
13+
KueueWorkloadInfo,
1314
)
1415

1516
from .cluster import (
@@ -21,4 +22,4 @@
2122
RayClusterStatus,
2223
CodeFlareClusterStatus,
2324
RayCluster,
24-
)
25+
)
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
from .rayjob import RayJob, ManagedClusterConfig
2-
from .status import RayJobDeploymentStatus, CodeflareRayJobStatus, RayJobInfo
3-
from .config import ManagedClusterConfig
2+
from .status import RayJobDeploymentStatus, CodeflareRayJobStatus, RayJobInfo, KueueWorkloadInfo
3+
from .config import ManagedClusterConfig

src/codeflare_sdk/ray/rayjobs/pretty_print.py

Lines changed: 179 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
from rich.console import Console
2121
from rich.table import Table
2222
from rich.panel import Panel
23-
from typing import Tuple, Optional
23+
from typing import Tuple, Optional, List
2424

25-
from .status import RayJobDeploymentStatus, RayJobInfo
25+
from .status import RayJobDeploymentStatus, RayJobInfo, KueueWorkloadInfo
2626

2727

2828
def print_job_status(job_info: RayJobInfo):
@@ -37,6 +37,10 @@ def print_job_status(job_info: RayJobInfo):
3737
table.add_row(f"[bold]Status:[/bold] {job_info.status.value}")
3838
table.add_row(f"[bold]RayCluster:[/bold] {job_info.cluster_name}")
3939
table.add_row(f"[bold]Namespace:[/bold] {job_info.namespace}")
40+
41+
# Add cluster management info
42+
managed_text = "[bold green]Yes (Job-managed)[/bold green]" if job_info.is_managed_cluster else "[dim]No (Existing cluster)[/dim]"
43+
table.add_row(f"[bold]Managed Cluster:[/bold] {managed_text}")
4044

4145
# Add timing information if available
4246
if job_info.start_time:
@@ -47,6 +51,23 @@ def print_job_status(job_info: RayJobInfo):
4751
if job_info.failed_attempts > 0:
4852
table.add_row(f"[bold]Failed Attempts:[/bold] {job_info.failed_attempts}")
4953

54+
# Add Kueue information if available
55+
if job_info.kueue_workload:
56+
table.add_row()
57+
table.add_row("[bold blue]🎯 Kueue Integration[/bold blue]")
58+
table.add_row(f"[bold]Local Queue:[/bold] {job_info.local_queue}")
59+
table.add_row(f"[bold]Workload Status:[/bold] [bold green]{job_info.kueue_workload.status}[/bold green]")
60+
table.add_row(f"[bold]Workload Name:[/bold] {job_info.kueue_workload.name}")
61+
if job_info.kueue_workload.priority is not None:
62+
table.add_row(f"[bold]Priority:[/bold] {job_info.kueue_workload.priority}")
63+
if job_info.kueue_workload.admission_time:
64+
table.add_row(f"[bold]Admitted:[/bold] {job_info.kueue_workload.admission_time}")
65+
elif job_info.local_queue:
66+
table.add_row()
67+
table.add_row("[bold blue]🎯 Kueue Integration[/bold blue]")
68+
table.add_row(f"[bold]Local Queue:[/bold] {job_info.local_queue}")
69+
table.add_row("[dim]Workload information not available[/dim]")
70+
5071
_print_table_in_panel(table)
5172

5273

@@ -66,6 +87,162 @@ def print_no_job_found(job_name: str, namespace: str):
6687
_print_table_in_panel(table)
6788

6889

90+
def print_jobs_list(job_list: List[RayJobInfo], namespace: str, pagination_info: Optional[dict] = None):
91+
"""
92+
Pretty print a list of RayJobs using Rich formatting with pagination support.
93+
94+
Args:
95+
job_list: List of RayJobInfo objects (for current page)
96+
namespace: Kubernetes namespace
97+
pagination_info: Optional pagination information dict
98+
"""
99+
if not job_list:
100+
# Create table for no jobs found
101+
table = _create_info_table(
102+
"[white on yellow][bold]Namespace", namespace, "[bold yellow]No RayJobs found"
103+
)
104+
table.add_row()
105+
table.add_row("No RayJobs found in this namespace.")
106+
table.add_row("Jobs may have been deleted or completed with TTL cleanup.")
107+
_print_table_in_panel(table)
108+
return
109+
110+
# Create main table for job list
111+
console = Console()
112+
113+
# Create title with pagination info
114+
title = f"[bold blue]🚀 RayJobs in namespace: {namespace}[/bold blue]"
115+
if pagination_info and pagination_info["total_pages"] > 1:
116+
title += f" [dim](Page {pagination_info['current_page']} of {pagination_info['total_pages']})[/dim]"
117+
118+
# Create jobs table with responsive width
119+
jobs_table = Table(
120+
title=title,
121+
show_header=True,
122+
header_style="bold magenta",
123+
border_style="blue",
124+
expand=True, # Allow table to expand to terminal width
125+
min_width=120, # Minimum width for readability
126+
)
127+
128+
# Add columns with flexible width allocation and wrapping
129+
jobs_table.add_column("Status", style="bold", min_width=12, max_width=16)
130+
jobs_table.add_column("Job Name", style="bold cyan", min_width=15, max_width=30)
131+
jobs_table.add_column("Job ID", style="dim", min_width=12, max_width=25)
132+
jobs_table.add_column("Cluster", min_width=12, max_width=25)
133+
jobs_table.add_column("Managed", min_width=8, max_width=12)
134+
jobs_table.add_column("Queue", min_width=10, max_width=18)
135+
jobs_table.add_column("Kueue Status", min_width=8, max_width=15)
136+
jobs_table.add_column("Start Time", style="dim", min_width=8, max_width=12)
137+
138+
# Add rows for each job
139+
for job_info in job_list:
140+
status_display, _ = _get_status_display(job_info.status)
141+
142+
# Format start time more compactly
143+
if job_info.start_time:
144+
try:
145+
# Extract just time portion and make it compact
146+
start_time = job_info.start_time.split('T')[1][:8] # HH:MM:SS
147+
except (IndexError, AttributeError):
148+
start_time = "N/A"
149+
else:
150+
start_time = "N/A"
151+
152+
# Truncate long values intelligently (Rich will handle further truncation if needed)
153+
job_name = _truncate_text(job_info.name, 28)
154+
job_id = _truncate_text(job_info.job_id, 23)
155+
cluster_name = _truncate_text(job_info.cluster_name, 23)
156+
157+
# Cluster management info
158+
managed_display = "✅ Yes" if job_info.is_managed_cluster else "❌ No"
159+
managed_style = "bold green" if job_info.is_managed_cluster else "dim"
160+
161+
# Kueue information
162+
queue_display = _truncate_text(job_info.local_queue or "N/A", 16)
163+
kueue_status_display = "N/A"
164+
kueue_status_style = "dim"
165+
166+
if job_info.kueue_workload:
167+
kueue_status_display = job_info.kueue_workload.status
168+
if job_info.kueue_workload.status == "Admitted":
169+
kueue_status_style = "bold green"
170+
elif job_info.kueue_workload.status == "Pending":
171+
kueue_status_style = "bold yellow"
172+
elif job_info.kueue_workload.status == "Finished":
173+
kueue_status_style = "bold blue"
174+
175+
jobs_table.add_row(
176+
status_display,
177+
job_name,
178+
job_id,
179+
cluster_name,
180+
f"[{managed_style}]{managed_display}[/{managed_style}]",
181+
queue_display,
182+
f"[{kueue_status_style}]{kueue_status_display}[/{kueue_status_style}]",
183+
start_time,
184+
)
185+
186+
# Print the table
187+
console.print(jobs_table)
188+
189+
# Add pagination information
190+
if pagination_info:
191+
console.print() # Add spacing
192+
if pagination_info["total_pages"] > 1:
193+
console.print(
194+
f"[dim]Showing {pagination_info['showing_start']}-{pagination_info['showing_end']} "
195+
f"of {pagination_info['total_jobs']} jobs "
196+
f"(Page {pagination_info['current_page']} of {pagination_info['total_pages']})[/dim]"
197+
)
198+
199+
# Navigation hints
200+
nav_hints = []
201+
if pagination_info['current_page'] > 1:
202+
nav_hints.append(f"Previous: RayJob.List(page={pagination_info['current_page'] - 1})")
203+
if pagination_info['current_page'] < pagination_info['total_pages']:
204+
nav_hints.append(f"Next: RayJob.List(page={pagination_info['current_page'] + 1})")
205+
206+
if nav_hints:
207+
console.print(f"[dim]Navigation: {' | '.join(nav_hints)}[/dim]")
208+
209+
# Add summary information
210+
kueue_jobs = [job for job in job_list if job.local_queue]
211+
managed_jobs = [job for job in job_list if job.is_managed_cluster]
212+
213+
if kueue_jobs or managed_jobs:
214+
console.print() # Add spacing
215+
if managed_jobs:
216+
total_managed = len(managed_jobs)
217+
total_jobs = pagination_info["total_jobs"] if pagination_info else len(job_list)
218+
console.print(f"[bold green]🏗️ {total_managed} of {total_jobs} jobs use job-managed clusters[/bold green]")
219+
if kueue_jobs:
220+
total_kueue = len(kueue_jobs)
221+
total_jobs = pagination_info["total_jobs"] if pagination_info else len(job_list)
222+
console.print(f"[bold blue]🎯 {total_kueue} of {total_jobs} jobs are managed by Kueue[/bold blue]")
223+
224+
225+
def _truncate_text(text: str, max_length: int) -> str:
226+
"""
227+
Truncate text intelligently with ellipsis if needed.
228+
229+
Args:
230+
text: Text to truncate
231+
max_length: Maximum length including ellipsis
232+
233+
Returns:
234+
Truncated text with ellipsis if needed
235+
"""
236+
if len(text) <= max_length:
237+
return text
238+
239+
# Leave room for ellipsis
240+
if max_length <= 3:
241+
return text[:max_length]
242+
243+
return text[:max_length-3] + "..."
244+
245+
69246
def _get_status_display(status: RayJobDeploymentStatus) -> Tuple[str, str]:
70247
"""
71248
Get the display string and header color for a given status.

0 commit comments

Comments
 (0)