Skip to content

Commit a8ba6f7

Browse files
Pre-select cluster if exists, and suppress widgets and outputs on creation of Cluster Object, and bug fixes
1 parent 7158242 commit a8ba6f7

File tree

2 files changed

+71
-52
lines changed

2 files changed

+71
-52
lines changed

src/codeflare_sdk/cluster/widgets.py

+39-29
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
"""
1616
The widgets sub-module contains the ui widgets created using the ipywidgets package.
1717
"""
18+
import contextlib
19+
import io
1820
import os
1921
from time import sleep
2022
import time
@@ -113,38 +115,42 @@ def view_clusters(namespace: str = None):
113115
df = _fetch_cluster_data(namespace)
114116
if df.empty:
115117
print(f"No clusters found in the {namespace} namespace.")
116-
else:
117-
classification_widget = widgets.ToggleButtons(
118-
options=df["Name"].tolist(), value=None,
119-
description='Select an existing cluster:',
120-
)
118+
return
121119

122-
classification_widget.observe(lambda selection_change: _on_cluster_click(selection_change, raycluster_data_output, namespace, classification_widget), names="value")
120+
classification_widget = widgets.ToggleButtons(
121+
options=df["Name"].tolist(), value=df["Name"].tolist()[0],
122+
description='Select an existing cluster:',
123+
)
124+
# Setting the initial value to trigger the event handler to display the cluster details.
125+
initial_value = classification_widget.value
126+
_on_cluster_click({"new": initial_value}, raycluster_data_output, namespace, classification_widget)
127+
classification_widget.observe(lambda selection_change: _on_cluster_click(selection_change, raycluster_data_output, namespace, classification_widget), names="value")
123128

124-
delete_button = widgets.Button(
125-
description='Delete Cluster',
126-
icon='trash',
127-
tooltip="Delete the selected cluster"
128-
)
129-
delete_button.on_click(lambda b: _on_delete_button_click(b, classification_widget, df, raycluster_data_output, user_output, delete_button, list_jobs_button, ray_dashboard_button))
129+
# UI table buttons
130+
delete_button = widgets.Button(
131+
description='Delete Cluster',
132+
icon='trash',
133+
tooltip="Delete the selected cluster"
134+
)
135+
delete_button.on_click(lambda b: _on_delete_button_click(b, classification_widget, df, raycluster_data_output, user_output, delete_button, list_jobs_button, ray_dashboard_button))
130136

131-
list_jobs_button = widgets.Button(
132-
description='View Jobs',
133-
icon='suitcase',
134-
tooltip="Open the Ray Job Dashboard"
135-
)
136-
list_jobs_button.on_click(lambda b: _on_list_jobs_button_click(b, classification_widget, df, user_output, url_output))
137+
list_jobs_button = widgets.Button(
138+
description='View Jobs',
139+
icon='suitcase',
140+
tooltip="Open the Ray Job Dashboard"
141+
)
142+
list_jobs_button.on_click(lambda b: _on_list_jobs_button_click(b, classification_widget, df, user_output, url_output))
137143

138-
ray_dashboard_button = widgets.Button(
139-
description='Open Ray Dashboard',
140-
icon='dashboard',
141-
tooltip="Open the Ray Dashboard in a new tab",
142-
layout=widgets.Layout(width='auto'),
143-
)
144-
ray_dashboard_button.on_click(lambda b: _on_ray_dashboard_button_click(b, classification_widget, df, user_output, url_output))
144+
ray_dashboard_button = widgets.Button(
145+
description='Open Ray Dashboard',
146+
icon='dashboard',
147+
tooltip="Open the Ray Dashboard in a new tab",
148+
layout=widgets.Layout(width='auto'),
149+
)
150+
ray_dashboard_button.on_click(lambda b: _on_ray_dashboard_button_click(b, classification_widget, df, user_output, url_output))
145151

146-
display(widgets.VBox([classification_widget, raycluster_data_output]))
147-
display(widgets.HBox([delete_button, list_jobs_button, ray_dashboard_button]), url_output, user_output)
152+
display(widgets.VBox([classification_widget, raycluster_data_output]))
153+
display(widgets.HBox([delete_button, list_jobs_button, ray_dashboard_button]), url_output, user_output)
148154

149155
# Handles the event when a cluster is selected from the toggle buttons, updating the output with cluster details.
150156
def _on_cluster_click(selection_change, raycluster_data_output: widgets.Output, namespace: str, classification_widget: widgets.ToggleButtons):
@@ -184,7 +190,9 @@ def _on_ray_dashboard_button_click(b, classification_widget: widgets.ToggleButto
184190
cluster_name = classification_widget.value
185191
namespace = df[df["Name"]==classification_widget.value]["Namespace"].values[0]
186192

187-
cluster = Cluster(ClusterConfiguration(cluster_name, namespace))
193+
# Suppress from Cluster Object initialisation widgets and outputs
194+
with widgets.Output(), contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
195+
cluster = Cluster(ClusterConfiguration(cluster_name, namespace))
188196
dashboard_url = cluster.cluster_dashboard_uri()
189197

190198
with user_output:
@@ -198,7 +206,9 @@ def _on_list_jobs_button_click(b, classification_widget: widgets.ToggleButtons,
198206
cluster_name = classification_widget.value
199207
namespace = df[df["Name"]==classification_widget.value]["Namespace"].values[0]
200208

201-
cluster = Cluster(ClusterConfiguration(cluster_name, namespace))
209+
# Suppress from Cluster Object initialisation widgets and outputs
210+
with widgets.Output(), contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
211+
cluster = Cluster(ClusterConfiguration(cluster_name, namespace))
202212
dashboard_url = cluster.cluster_dashboard_uri()
203213

204214
with user_output:

tests/unit_test.py

+32-23
Original file line numberDiff line numberDiff line change
@@ -2953,7 +2953,6 @@ def test_cluster_up_down_buttons(mocker):
29532953
@patch.dict("os.environ", {}, clear=True) # Mock environment with no variables
29542954
def test_is_notebook_false():
29552955
from codeflare_sdk.cluster.widgets import is_notebook
2956-
29572956
assert is_notebook() is False
29582957

29592958

@@ -2962,9 +2961,9 @@ def test_is_notebook_false():
29622961
) # Mock Jupyter environment variable
29632962
def test_is_notebook_true():
29642963
from codeflare_sdk.cluster.widgets import is_notebook
2965-
29662964
assert is_notebook() is True
29672965

2966+
29682967
@patch.dict("os.environ", {"JPY_SESSION_NAME": "example-test"}) # Mock Jupyter environment variable
29692968
def test_view_clusters(mocker):
29702969
# Mock Kubernetes API responses
@@ -2974,6 +2973,10 @@ def test_view_clusters(mocker):
29742973
return_value={"items": []}
29752974
)
29762975

2976+
# Return empty dataframe when no clusters are found
2977+
mocker.patch("codeflare_sdk.cluster.cluster.list_all_clusters", return_value=[])
2978+
df = _fetch_cluster_data(namespace="default")
2979+
assert df.empty
29772980

29782981
test_df=pd.DataFrame({
29792982
"Name": ["test-cluster"],
@@ -3029,10 +3032,6 @@ def test_view_clusters(mocker):
30293032
# Assert that the toggle options are set correctly
30303033
mock_toggle.observe.assert_called()
30313034

3032-
# Simulate clicking the delete button
3033-
_on_delete_button_click(None, mock_toggle, test_df, mock_output, mock_output,
3034-
mock_delete_button, mock_list_jobs_button, mock_ray_dashboard_button)
3035-
30363035
# Simulate clicking the list jobs button
30373036
_on_list_jobs_button_click(None, mock_toggle, test_df, mock_output, mock_output)
30383037
mock_javascript.assert_called()
@@ -3041,26 +3040,17 @@ def test_view_clusters(mocker):
30413040
_on_ray_dashboard_button_click(None, mock_toggle, test_df, mock_output, mock_output)
30423041
mock_javascript.assert_called()
30433042

3043+
# Simulate clicking the delete button
3044+
_on_delete_button_click(None, mock_toggle, test_df, mock_output, mock_output,
3045+
mock_delete_button, mock_list_jobs_button, mock_ray_dashboard_button)
30443046

30453047

3046-
def test_format_status():
3047-
# Test each possible status
3048-
test_cases = [
3049-
(RayClusterStatus.READY, '<span style="color: green;">Ready ✓</span>'),
3050-
(RayClusterStatus.SUSPENDED, '<span style="color: #007BFF;">Suspended ❄️</span>'),
3051-
(RayClusterStatus.FAILED, '<span style="color: red;">Failed ✗</span>'),
3052-
(RayClusterStatus.UNHEALTHY, '<span style="color: purple;">Unhealthy</span>'),
3053-
(RayClusterStatus.UNKNOWN, '<span style="color: purple;">Unknown</span>'),
3054-
]
3055-
3056-
for status, expected_output in test_cases:
3057-
assert _format_status(status) == expected_output, f"Failed for status: {status}"
3058-
3059-
# Test an unrecognized status
3060-
unrecognized_status = 'NotAStatus'
3061-
assert _format_status(unrecognized_status) == 'NotAStatus', "Failed for unrecognized status"
3062-
30633048
def test_fetch_cluster_data(mocker):
3049+
# Return empty dataframe when no clusters are found
3050+
mocker.patch("codeflare_sdk.cluster.cluster.list_all_clusters", return_value=[])
3051+
df = _fetch_cluster_data(namespace="default")
3052+
assert df.empty
3053+
30643054
# Create mock RayCluster objects
30653055
mock_raycluster1 = MagicMock(spec=RayCluster)
30663056
mock_raycluster1.name = 'test-cluster-1'
@@ -3121,6 +3111,25 @@ def test_fetch_cluster_data(mocker):
31213111
# Assert that the DataFrame matches expected
31223112
pd.testing.assert_frame_equal(df.reset_index(drop=True), expected_df.reset_index(drop=True))
31233113

3114+
3115+
def test_format_status():
3116+
# Test each possible status
3117+
test_cases = [
3118+
(RayClusterStatus.READY, '<span style="color: green;">Ready ✓</span>'),
3119+
(RayClusterStatus.SUSPENDED, '<span style="color: #007BFF;">Suspended ❄️</span>'),
3120+
(RayClusterStatus.FAILED, '<span style="color: red;">Failed ✗</span>'),
3121+
(RayClusterStatus.UNHEALTHY, '<span style="color: purple;">Unhealthy</span>'),
3122+
(RayClusterStatus.UNKNOWN, '<span style="color: purple;">Unknown</span>'),
3123+
]
3124+
3125+
for status, expected_output in test_cases:
3126+
assert _format_status(status) == expected_output, f"Failed for status: {status}"
3127+
3128+
# Test an unrecognized status
3129+
unrecognized_status = 'NotAStatus'
3130+
assert _format_status(unrecognized_status) == 'NotAStatus', "Failed for unrecognized status"
3131+
3132+
31243133
# Make sure to always keep this function last
31253134
def test_cleanup():
31263135
os.remove(f"{aw_dir}unit-test-no-kueue.yaml")

0 commit comments

Comments
 (0)