Repository: skypilot-org/skypilot
Pull request: [SPOT/UX] Add color for status
Fixed issue #1413 by adding color for cluster & job statuses:

<img width="577" alt="image" src="https://user-images.githubusercontent.com/8518898/204893332-44788015-1da7-45ed-a093-120d1dd0ddd6.png">

<img width="414" alt="image" src="https://user-images.githubusercontent.com/8518898/204893408-0c1fdfba-2774-4fef-a8ef-a0d1d8f7a276.png">



Problem statement:
[Spot/UX] Color for different job status
From our user:
> Suggestion: show the spot job statuses (or maybe the entire row) in different colors (e.g. green for running; red for failed; yellow for canceled; blue for starting). Right now when there are many jobs it can be a bit inconvenient to quickly locate the running / failed ones.

Hints from issue discussion:

Commit message: moved method + made stop lighter

Patch to review:
diff --git a/sky/global_user_state.py b/sky/global_user_state.py
index b8fe3369e9c..ac969a4b77d 100644
--- a/sky/global_user_state.py
+++ b/sky/global_user_state.py
@@ -15,6 +15,8 @@
 import typing
 from typing import Any, Dict, List, Optional
 
+import colorama
+
 from sky import clouds
 from sky.utils import db_utils
 from sky.utils import common_utils
@@ -82,15 +84,27 @@ class ClusterStatus(enum.Enum):
     # Initializing.  This means a backend.provision() call has started but has
     # not successfully finished. The cluster may be undergoing setup, may have
     # failed setup, may be live or down.
+
     INIT = 'INIT'
 
     # The cluster is recorded as up.  This means a backend.provision() has
     # previously succeeded.
     UP = 'UP'
 
-    # Stopped.  This means a `sky stop` call has previously succeeded.
+    # Stopped.  This means a `sky stop` _call has previously succeeded.
     STOPPED = 'STOPPED'
 
+    def colored_str(self):
+        color = STATUS_TO_COLOR[self]
+        return f'{color}{self.value}{colorama.Style.RESET_ALL}'
+
+
+STATUS_TO_COLOR = {
+    ClusterStatus.INIT: colorama.Fore.BLUE,
+    ClusterStatus.UP: colorama.Fore.GREEN,
+    ClusterStatus.STOPPED: colorama.Fore.LIGHTWHITE_EX,
+}
+
 
 class StorageStatus(enum.Enum):
     """Storage status as recorded in table 'storage'."""
diff --git a/sky/skylet/job_lib.py b/sky/skylet/job_lib.py
index 241fb0a278e..2616e8e24ae 100644
--- a/sky/skylet/job_lib.py
+++ b/sky/skylet/job_lib.py
@@ -10,6 +10,7 @@
 import typing
 from typing import Any, Dict, List, Optional
 
+import colorama
 import filelock
 
 from sky import sky_logging
@@ -73,6 +74,7 @@ def create_table(cursor, conn):
 
 class JobStatus(enum.Enum):
     """Job status"""
+
     # 3 in-flux states: each can transition to any state below it.
     # The `job_id` has been generated, but the generated ray program has
     # not started yet. skylet can transit the state from INIT to FAILED
@@ -114,6 +116,21 @@ def is_terminal(self):
     def __lt__(self, other):
         return list(JobStatus).index(self) < list(JobStatus).index(other)
 
+    def colored_str(self):
+        color = JOB_STATUS_TO_COLOR[self]
+        return f'{color}{self.value}{colorama.Style.RESET_ALL}'
+
+
+JOB_STATUS_TO_COLOR = {
+    JobStatus.INIT: colorama.Fore.BLUE,
+    JobStatus.SETTING_UP: colorama.Fore.BLUE,
+    JobStatus.PENDING: colorama.Fore.BLUE,
+    JobStatus.RUNNING: colorama.Fore.GREEN,
+    JobStatus.SUCCEEDED: colorama.Fore.GREEN,
+    JobStatus.FAILED: colorama.Fore.RED,
+    JobStatus.FAILED_SETUP: colorama.Fore.RED,
+    JobStatus.CANCELLED: colorama.Fore.YELLOW,
+}
 
 _RAY_TO_JOB_STATUS_MAP = {
     # These are intentionally set to one status before, because:
@@ -488,7 +505,7 @@ def format_job_queue(jobs: List[Dict[str, Any]]):
                                              job['end_at'],
                                              absolute=True),
             job['resources'],
-            job['status'].value,
+            job['status'].colored_str(),
             job['log_path'],
         ])
     return job_table
diff --git a/sky/utils/cli_utils/status_utils.py b/sky/utils/cli_utils/status_utils.py
index 60d4ccc7432..5a64b7abc4e 100644
--- a/sky/utils/cli_utils/status_utils.py
+++ b/sky/utils/cli_utils/status_utils.py
@@ -215,10 +215,14 @@ def show_local_status_table(local_clusters: List[str]):
     cluster_status['launched_at']))
 _get_region = (
     lambda clusters_status: clusters_status['handle'].launched_resources.region)
-_get_status = (lambda cluster_status: cluster_status['status'].value)
 _get_command = (lambda cluster_status: cluster_status['last_use'])
 
 
+def _get_status(cluster_status):
+    status = cluster_status['status']
+    return status.colored_str()
+
+
 def _get_resources(cluster_status):
     handle = cluster_status['handle']
     resources_str = '<initializing>'


Code review comments:
diff_hunk: @@ -114,6 +116,21 @@ def is_terminal(self):
     def __lt__(self, other):
         return list(JobStatus).index(self) < list(JobStatus).index(other)
 
+    def colored_str(self):
+        color = JOB_STATUS_TO_COLOR[self]
+        return f'{color}{self.value}{colorama.Style.RESET_ALL}'
+
+
+JOB_STATUS_TO_COLOR = {
path: sky/skylet/job_lib.py
text: @user1:
nit: make this variable private
```suggestion
_JOB_STATUS_TO_COLOR = {
```

diff_hunk: @@ -82,15 +84,27 @@ class ClusterStatus(enum.Enum):
     # Initializing.  This means a backend.provision() call has started but has
     # not successfully finished. The cluster may be undergoing setup, may have
     # failed setup, may be live or down.
+
     INIT = 'INIT'
 
     # The cluster is recorded as up.  This means a backend.provision() has
     # previously succeeded.
     UP = 'UP'
 
-    # Stopped.  This means a `sky stop` call has previously succeeded.
+    # Stopped.  This means a `sky stop` _call has previously succeeded.
     STOPPED = 'STOPPED'
 
+    def colored_str(self):
+        color = STATUS_TO_COLOR[self]
+        return f'{color}{self.value}{colorama.Style.RESET_ALL}'
+
+
+STATUS_TO_COLOR = {
+    ClusterStatus.INIT: colorama.Fore.BLUE,
+    ClusterStatus.UP: colorama.Fore.GREEN,
+    ClusterStatus.STOPPED: colorama.Fore.LIGHTWHITE_EX,
path: sky/global_user_state.py
text: @user1:
nit: How about `colorama.Fore.YELLOW`? `LIGHTWHITE_EX` might be a bit too bright for dark background?

diff_hunk: @@ -82,15 +84,27 @@ class ClusterStatus(enum.Enum):
     # Initializing.  This means a backend.provision() call has started but has
     # not successfully finished. The cluster may be undergoing setup, may have
     # failed setup, may be live or down.
+
     INIT = 'INIT'
 
     # The cluster is recorded as up.  This means a backend.provision() has
     # previously succeeded.
     UP = 'UP'
 
-    # Stopped.  This means a `sky stop` call has previously succeeded.
+    # Stopped.  This means a `sky stop` _call has previously succeeded.
     STOPPED = 'STOPPED'
 
+    def colored_str(self):
+        color = STATUS_TO_COLOR[self]
+        return f'{color}{self.value}{colorama.Style.RESET_ALL}'
+
+
+STATUS_TO_COLOR = {
path: sky/global_user_state.py
text: @user1:
nit: make this variable private?
```suggestion
_STATUS_TO_COLOR = {
```