from cloudos_cli.clos import Cloudos
import cloudos_cli.jobs.job as jb
import json
import click
from rich.console import Console
from rich.table import Table
from rich.text import Text
from datetime import datetime
def save_as_json(data, filename):
with open(filename, 'w') as f:
json.dump(data, f, indent=4)
def save_as_stdout(data, j_workdir_parent, cloudos_url="https://cloudos.lifebit.ai"):
"""Display related analyses in a formatted table with pagination.
Parameters
----------
data : dict
Dictionary where keys are job IDs and values are dictionaries
containing job details (status, name, user_name, user_surname,
_id, createdAt, runTime, computeCostSpent).
"""
console = Console(markup=True)
# Helper function to format timestamp
def format_timestamp(timestamp_str):
if not timestamp_str:
return "N/A"
try:
dt = datetime.fromisoformat(timestamp_str.replace('Z', '+00:00'))
return dt.strftime('%Y-%m-%d %H:%M:%S')
except (ValueError, AttributeError):
return timestamp_str
# Helper function to format runtime
def format_runtime(runtime_seconds):
if runtime_seconds is None:
return "N/A"
try:
total_seconds = int(runtime_seconds)
hours = total_seconds // 3600
minutes = (total_seconds % 3600) // 60
seconds = total_seconds % 60
if hours > 0:
return f"{hours}h {minutes}m {seconds}s"
elif minutes > 0:
return f"{minutes}m {seconds}s"
else:
return f"{seconds}s"
except (ValueError, TypeError):
return "N/A"
# Helper function to format cost
def format_cost(cost):
if cost is None or cost == "":
return "N/A"
try:
# Cost is stored in cents, divide by 100 to get dollars
return f"${float(cost) / 100:.4f}"
except (ValueError, TypeError):
return "N/A"
# Prepare all rows data
rows = []
for job_id, job_info in data.items():
status = job_info.get('status', 'N/A')
name = job_info.get('name', 'N/A')
user_name = job_info.get('user_name', '')
user_surname = job_info.get('user_surname', '')
owner = f"{user_name} {user_surname}".strip() if user_name or user_surname else "N/A"
job_id_display = job_info.get('_id', job_id)
submit_time = format_timestamp(job_info.get('createdAt'))
run_time = format_runtime(job_info.get('runTime'))
cost = job_info.get('computeCostSpent')
total_cost_formatted = format_cost(cost)
# Add hyperlink to job_id_display
job_url = f"{cloudos_url}/app/advanced-analytics/analyses/{job_id_display}"
job_id_with_link = f"[link={job_url}]{job_id_display}[/link]"
rows.append([
status,
name,
owner,
job_id_with_link,
submit_time,
run_time,
total_cost_formatted
])
# Pagination setup
limit = 10 # Display 10 rows per page
current_page = 0
total_pages = (len(rows) + limit - 1) // limit if len(rows) > 0 else 1
# Display with pagination
show_error = None # Track error messages to display
while True:
start = current_page * limit
end = start + limit
# Clear console first
console.clear()
# Display parent job information (reprinted each iteration after clear)
if "Intermediate results of this job were deleted by" in str(j_workdir_parent):
console.print(f"[white on #fff08a]🗑️ {j_workdir_parent}[/white on #fff08a]")
elif j_workdir_parent is not None:
link = f"{cloudos_url}/app/advanced-analytics/analyses/{j_workdir_parent}"
console.print(f"Parent job link: [link={link}]{j_workdir_parent}[/link]")
else:
console.print("[dim]No parent job found[/dim]")
console.print(f"\nTotal related analyses found: {len(data)}")
# Create and display table
table = Table(title="Related Analyses")
# Add columns
table.add_column("Status", style="cyan", no_wrap=True)
table.add_column("Name", style="green", overflow="fold")
table.add_column("Owner", style="blue", overflow="fold")
table.add_column("ID", style="magenta", overflow="fold", no_wrap=True)
table.add_column("Submit time", style="yellow", overflow="fold")
table.add_column("Run time", style="white", overflow="fold")
table.add_column("Total Cost", style="red", no_wrap=True)
# Get rows for current page
page_rows = rows[start:end]
# Add rows to table
for row in page_rows:
table.add_row(*row)
# Print table
console.print(table)
# Show error message if any (before clearing for next iteration)
if show_error:
console.print(show_error)
show_error = None # Reset error after displaying
# Show pagination info and controls
if total_pages > 1:
console.print(f"\nOn page {current_page + 1}/{total_pages}: [bold cyan]n[/] = next, [bold cyan]p[/] = prev, [bold cyan]q[/] = quit")
# Get user input for navigation
choice = input(">>> ").strip().lower()
if choice in ("q", "quit"):
break
elif choice in ("n", "next"):
if current_page < total_pages - 1:
current_page += 1
else:
show_error = "[red]Invalid choice. Already on the last page.[/red]"
elif choice in ("p", "prev"):
if current_page > 0:
current_page -= 1
else:
show_error = "[red]Invalid choice. Already on the first page.[/red]"
else:
show_error = "[red]Invalid choice. Please enter 'n' (next), 'p' (prev), or 'q' (quit).[/red]"
else:
# Only one page, no need for input, just exit
break