Skip to content

Commit 0c4bebc

Browse files
Add refresh button bug fixes and test case to UI e2e test
1 parent 742cdeb commit 0c4bebc

File tree

8 files changed

+87
-39
lines changed

8 files changed

+87
-39
lines changed

docs/sphinx/user-docs/ui-widgets.rst

+5-3
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,15 @@ requests and limits along with the clusters status.
3939

4040
Above is a list of two Ray Clusters ``raytest`` and ``raytest2`` each of
4141
those headings is clickable and will update the table to view the
42-
selected Cluster's information. There are three buttons under the table
43-
``Cluster Down``, ``View Jobs`` and ``Open Ray Dashboard``. \* The
42+
selected Cluster's information. There are four buttons under the table
43+
``Cluster Down``, ``View Jobs``, ``Open Ray Dashboard``, and ``Refresh Data``. \* The
4444
``Cluster Down`` button will delete the selected Cluster. \* The
4545
``View Jobs`` button will try to open the Ray Dashboard's Jobs view in a
4646
Web Browser. The link will also be printed to the console. \* The
4747
``Open Ray Dashboard`` button will try to open the Ray Dashboard view in
48-
a Web Browser. The link will also be printed to the console.
48+
a Web Browser. The link will also be printed to the console. \* The
49+
``Refresh Data`` button will refresh the list of RayClusters, the spec, and
50+
the status of the Ray Cluster.
4951

5052
The UI Table can be viewed by calling the following function.
5153

src/codeflare_sdk/common/widgets/widgets.py

+52-26
Original file line numberDiff line numberDiff line change
@@ -119,28 +119,8 @@ def _on_cluster_click(self, selection_change):
119119
_on_cluster_click handles the event when a cluster is selected from the toggle buttons, updating the output with cluster details.
120120
"""
121121
new_value = selection_change["new"]
122-
self.raycluster_data_output.clear_output()
123-
ray_clusters_df = _fetch_cluster_data(self.namespace)
124-
self.classification_widget.options = ray_clusters_df["Name"].tolist()
125-
with self.raycluster_data_output:
126-
display(
127-
HTML(
128-
ray_clusters_df[ray_clusters_df["Name"] == new_value][
129-
[
130-
"Name",
131-
"Namespace",
132-
"Num Workers",
133-
"Head GPUs",
134-
"Head CPU Req~Lim",
135-
"Head Memory Req~Lim",
136-
"Worker GPUs",
137-
"Worker CPU Req~Lim",
138-
"Worker Memory Req~Lim",
139-
"status",
140-
]
141-
].to_html(escape=False, index=False, border=2)
142-
)
143-
)
122+
self.classification_widget.value = new_value
123+
self._refresh_dataframe()
144124

145125
def _on_delete_button_click(self, b):
146126
"""
@@ -215,9 +195,8 @@ def _refresh_dataframe(self):
215195
"""
216196
_refresh_data function refreshes the list of Ray Clusters.
217197
"""
218-
new_df = _fetch_cluster_data(self.namespace)
219-
self.ray_clusters_df = new_df
220-
if new_df.empty:
198+
self.ray_clusters_df = _fetch_cluster_data(self.namespace)
199+
if self.ray_clusters_df.empty:
221200
self.classification_widget.close()
222201
self.delete_button.close()
223202
self.list_jobs_button.close()
@@ -227,7 +206,54 @@ def _refresh_dataframe(self):
227206
self.raycluster_data_output.clear_output()
228207
print(f"No clusters found in the {self.namespace} namespace.")
229208
else:
230-
self.classification_widget.options = new_df["Name"].tolist()
209+
# Store the current selection if it still exists (Was not previously deleted).
210+
selected_cluster = (
211+
self.classification_widget.value
212+
if self.classification_widget.value
213+
in self.ray_clusters_df["Name"].tolist()
214+
else None
215+
)
216+
217+
# Update list of Ray Clusters.
218+
self.classification_widget.options = self.ray_clusters_df["Name"].tolist()
219+
220+
# If the selected cluster exists, preserve the selection to remain viewing the currently selected cluster.
221+
# If it does not exist, default to the first available cluster.
222+
if selected_cluster:
223+
self.classification_widget.value = selected_cluster
224+
else:
225+
self.classification_widget.value = self.ray_clusters_df["Name"].iloc[0]
226+
227+
# Update the output with the current Ray Cluster details.
228+
self._display_cluster_details()
229+
230+
def _display_cluster_details(self):
231+
"""
232+
_display_cluster_details function displays the selected cluster details in the output widget.
233+
"""
234+
self.raycluster_data_output.clear_output()
235+
selected_cluster = self.ray_clusters_df[
236+
self.ray_clusters_df["Name"] == self.classification_widget.value
237+
]
238+
with self.raycluster_data_output:
239+
display(
240+
HTML(
241+
selected_cluster[
242+
[
243+
"Name",
244+
"Namespace",
245+
"Num Workers",
246+
"Head GPUs",
247+
"Head CPU Req~Lim",
248+
"Head Memory Req~Lim",
249+
"Worker GPUs",
250+
"Worker CPU Req~Lim",
251+
"Worker Memory Req~Lim",
252+
"status",
253+
]
254+
].to_html(escape=False, index=False, border=2)
255+
)
256+
)
231257

232258
def display_widgets(self):
233259
display(widgets.VBox([self.classification_widget, self.raycluster_data_output]))

ui-tests/tests/widget_notebook_example.test.ts

+30-10
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ test.describe("Visual Regression", () => {
3636

3737
// Hide the cell toolbar before capturing the screenshots
3838
await page.addStyleTag({ content: '.jp-cell-toolbar { display: none !important; }' });
39+
// Hide the file explorer
40+
await page.keyboard.press('Control+Shift+F');
3941

4042
const captures: (Buffer | null)[] = []; // Array to store cell screenshots
4143
const cellCount = await page.notebook.getCellCount();
@@ -109,30 +111,48 @@ test.describe("Visual Regression", () => {
109111

110112
await runPreviousCell(page, cellCount, '(<CodeFlareClusterStatus.UNKNOWN: 6>, False)');
111113

112-
// view_clusters table with buttons
113-
await interactWithWidget(page, upDownWidgetCellIndex, 'input[type="checkbox"]', async (checkbox) => {
114-
await checkbox.click();
115-
const isChecked = await checkbox.isChecked();
116-
expect(isChecked).toBe(false);
117-
});
114+
// Replace text in ClusterConfiguration to run a new RayCluster
115+
const cell = page.getByText('raytest').first();
116+
await cell.fill('"raytest-1"');
117+
await page.notebook.runCell(cellCount - 3, true); // Run ClusterConfiguration cell
118118

119119
await interactWithWidget(page, upDownWidgetCellIndex, 'button:has-text("Cluster Up")', async (button) => {
120120
await button.click();
121-
const successMessage = await page.waitForSelector('text=Ray Cluster: \'raytest\' has successfully been created', { timeout: 10000 });
121+
const successMessage = await page.waitForSelector('text=Ray Cluster: \'raytest-1\' has successfully been created', { timeout: 10000 });
122122
expect(successMessage).not.toBeNull();
123123
});
124124

125125
const viewClustersCellIndex = 4; // 5 on OpenShift
126126
await page.notebook.runCell(cellCount - 2, true);
127+
128+
// Wait until the RayCluster status in the table updates to "Ready"
129+
await interactWithWidget(page, viewClustersCellIndex, 'button:has-text("Refresh Data")', async (button) => {
130+
let clusterReady = false;
131+
const maxRefreshRetries = 24; // 24 retries * 5 seconds = 120 seconds
132+
let numRefreshRetries = 0;
133+
while (!clusterReady && numRefreshRetries < maxRefreshRetries) {
134+
await button.click();
135+
try {
136+
await page.waitForSelector('text=Ready ✓', { timeout: 5000 });
137+
clusterReady = true;
138+
}
139+
catch (e) {
140+
console.log(`Cluster not ready yet. Retrying...`);
141+
numRefreshRetries++;
142+
}
143+
}
144+
expect(clusterReady).toBe(true);
145+
});
146+
127147
await interactWithWidget(page, viewClustersCellIndex, 'button:has-text("Open Ray Dashboard")', async (button) => {
128148
await button.click();
129-
const successMessage = await page.waitForSelector('text=Opening Ray Dashboard for raytest cluster', { timeout: 5000 });
149+
const successMessage = await page.waitForSelector('text=Opening Ray Dashboard for raytest-1 cluster', { timeout: 5000 });
130150
expect(successMessage).not.toBeNull();
131151
});
132152

133153
await interactWithWidget(page, viewClustersCellIndex, 'button:has-text("View Jobs")', async (button) => {
134154
await button.click();
135-
const successMessage = await page.waitForSelector('text=Opening Ray Jobs Dashboard for raytest cluster', { timeout: 5000 });
155+
const successMessage = await page.waitForSelector('text=Opening Ray Jobs Dashboard for raytest-1 cluster', { timeout: 5000 });
136156
expect(successMessage).not.toBeNull();
137157
});
138158

@@ -141,7 +161,7 @@ test.describe("Visual Regression", () => {
141161

142162
const noClustersMessage = await page.waitForSelector(`text=No clusters found in the ${namespace} namespace.`, { timeout: 5000 });
143163
expect(noClustersMessage).not.toBeNull();
144-
const successMessage = await page.waitForSelector(`text=Cluster raytest in the ${namespace} namespace was deleted successfully.`, { timeout: 5000 });
164+
const successMessage = await page.waitForSelector(`text=Cluster raytest-1 in the ${namespace} namespace was deleted successfully.`, { timeout: 5000 });
145165
expect(successMessage).not.toBeNull();
146166
});
147167

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)