Skip to content

Commit 47ffbea

Browse files
author
Yehudit Kerido
committed
feat(ws): Notebooks 2.0 // Frontend // Fetch workspaces
Signed-off-by: Yehudit Kerido <yehudit.kerido@nokia.com>
1 parent c967ab6 commit 47ffbea

File tree

5 files changed

+144
-32
lines changed

5 files changed

+144
-32
lines changed
Binary file not shown.

workspaces/frontend/src/__tests__/cypress/cypress/tests/e2e/Workspaces.cy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const validateWorkspaceRow = (workspace: Workspace, index: number) => {
1616

1717
cy.findByTestId(`workspace-row-${index}`)
1818
.find('[data-testid="pod-config"]')
19-
.should('have.text', workspace.pod_template.pod_config.current);
19+
.should('have.text', workspace.pod_template.options.pod_config.current.display_name);
2020
};
2121

2222
// Test suite for workspace-related tests

workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspace.mock.ts

Lines changed: 90 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,57 @@ const generateMockWorkspace = (
66
namespace: string,
77
state: WorkspaceState,
88
paused: boolean,
9-
imageConfig: string,
10-
podConfig: string,
9+
imageConfigId: string,
10+
imageConfigDisplayName: string,
11+
podConfigId: string,
12+
podConfigDisplayName: string,
1113
pvcName: string,
12-
): Workspace => {
14+
): {
15+
name: string;
16+
namespace: string;
17+
workspace_kind: { name: string };
18+
defer_updates: boolean;
19+
paused: boolean;
20+
paused_time: number;
21+
state: WorkspaceState;
22+
state_message: string;
23+
pod_template: {
24+
pod_metadata: { labels: {}; annotations: {} };
25+
volumes: {
26+
home: { pvc_name: string; mount_path: string; readOnly: boolean };
27+
data: { pvc_name: string; mount_path: string; readOnly: boolean }[];
28+
};
29+
options: {
30+
image_config: {
31+
current: {
32+
id: string;
33+
display_name: string;
34+
description: string;
35+
labels: { key: string; value: string }[];
36+
};
37+
};
38+
pod_config: {
39+
current: {
40+
id: string;
41+
display_name: string;
42+
description: string;
43+
labels: ({ key: string; value: string } | { key: string; value: string })[];
44+
};
45+
};
46+
};
47+
image_config: { current: string; desired: string; redirect_chain: any[] };
48+
pod_config: { current: string; desired: string; redirect_chain: any[] };
49+
};
50+
activity: { last_activity: number; last_update: number };
51+
} => {
1352
const currentTime = Date.now();
14-
const lastActivityTime = currentTime - Math.floor(Math.random() * 1000000); // Random last activity time
15-
const lastUpdateTime = currentTime - Math.floor(Math.random() * 100000); // Random last update time
53+
const lastActivityTime = currentTime - Math.floor(Math.random() * 1000000);
54+
const lastUpdateTime = currentTime - Math.floor(Math.random() * 100000);
1655

1756
return {
1857
name,
1958
namespace,
20-
workspace_kind: {
21-
name: 'jupyterlab',
22-
},
59+
workspace_kind: { name: 'jupyterlab' },
2360
defer_updates: paused,
2461
paused,
2562
paused_time: paused ? currentTime - Math.floor(Math.random() * 1000000) : 0,
@@ -45,41 +82,60 @@ const generateMockWorkspace = (
4582
{
4683
pvc_name: pvcName,
4784
mount_path: '/data/my-data',
48-
readOnly: paused, // Set based on the paused state
85+
readOnly: paused,
4986
},
5087
],
5188
},
89+
options: {
90+
image_config: {
91+
current: {
92+
id: imageConfigId,
93+
display_name: imageConfigDisplayName,
94+
description: 'JupyterLab environment',
95+
labels: [{ key: 'python_version', value: '3.11' }],
96+
},
97+
},
98+
pod_config: {
99+
current: {
100+
id: podConfigId,
101+
display_name: podConfigDisplayName,
102+
description: 'Pod configuration with resource limits',
103+
labels: [
104+
{ key: 'cpu', value: '100m' },
105+
{ key: 'memory', value: '128Mi' },
106+
],
107+
},
108+
},
109+
},
52110
image_config: {
53-
current: imageConfig,
111+
current: imageConfigId,
54112
desired: '',
55113
redirect_chain: [],
56114
},
57115
pod_config: {
58-
current: podConfig,
59-
desired: podConfig,
116+
current: podConfigId,
117+
desired: podConfigId,
60118
redirect_chain: [],
61119
},
62120
},
63121
activity: {
64122
last_activity: lastActivityTime,
65123
last_update: lastUpdateTime,
66-
last_probe: {
67-
start_time_ms: lastUpdateTime - 1000, // Simulated probe timing
68-
end_time_ms: lastUpdateTime,
69-
result: 'default_result',
70-
message: 'default_message',
71-
},
72124
},
73125
};
74126
};
75127

76128
const generateMockWorkspaces = (numWorkspaces: number, byNamespace = false) => {
77129
const mockWorkspaces = [];
78-
const podConfigs = ['Small CPU', 'Medium CPU', 'Large CPU'];
130+
const podConfigs = [
131+
{ id: 'small-cpu', display_name: 'Small CPU' },
132+
{ id: 'medium-cpu', display_name: 'Medium CPU' },
133+
{ id: 'large-cpu', display_name: 'Large CPU' },
134+
];
79135
const imageConfigs = [
80-
'jupyterlab_scipy_180',
81-
'jupyterlab_tensorflow_230',
82-
'jupyterlab_pytorch_120',
136+
{ id: 'jupyterlab_scipy_180', display_name: 'JupyterLab SciPy 1.8.0' },
137+
{ id: 'jupyterlab_tensorflow_230', display_name: 'JupyterLab TensorFlow 2.3.0' },
138+
{ id: 'jupyterlab_pytorch_120', display_name: 'JupyterLab PyTorch 1.2.0' },
83139
];
84140
const namespaces = byNamespace ? ['kubeflow'] : ['kubeflow', 'system', 'user-example', 'default'];
85141

@@ -94,11 +150,22 @@ const generateMockWorkspaces = (numWorkspaces: number, byNamespace = false) => {
94150
const name = `workspace-${i}`;
95151
const namespace = namespaces[i % namespaces.length];
96152
const pvcName = `data-pvc-${i}`;
153+
97154
const imageConfig = imageConfigs[i % imageConfigs.length];
98155
const podConfig = podConfigs[i % podConfigs.length];
99156

100157
mockWorkspaces.push(
101-
generateMockWorkspace(name, namespace, state, paused, imageConfig, podConfig, pvcName),
158+
generateMockWorkspace(
159+
name,
160+
namespace,
161+
state,
162+
paused,
163+
imageConfig.id,
164+
imageConfig.display_name,
165+
podConfig.id,
166+
podConfig.display_name,
167+
pvcName,
168+
),
102169
);
103170
}
104171

workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ import { WorkspaceStopActionModal } from '~/app/pages/Workspaces/workspaceAction
5050
import { useNamespaceContext } from '~/app/context/NamespaceContextProvider';
5151
import { WorkspacesColumnNames } from '~/app/types';
5252
import Filter, { FilteredColumn } from 'shared/components/Filter';
53+
import { formatRam, formatCPU } from 'shared/utilities/WorkspaceResources';
54+
import { formatRam } from 'shared/utilities/WorkspaceUtils'; //check if still exist
5355
import { extractCpuValue, extractMemoryValue } from 'shared/utilities/WorkspaceUtils';
5456

5557
export enum ActionType {
@@ -460,6 +462,14 @@ export const Workspaces: React.FunctionComponent = () => {
460462
</>
461463
);
462464

465+
const getCpuValue = (workspace: Workspace): string =>
466+
workspace.pod_template.options.pod_config.current.labels.find((label) => label.key === 'cpu')
467+
?.value || 'N/A';
468+
469+
const getRamValue = (workspace: Workspace): string =>
470+
workspace.pod_template.options.pod_config.current.labels.find((label) => label.key === 'memory')
471+
?.value || 'N/A';
472+
463473
return (
464474
<Drawer
465475
isInline

workspaces/frontend/src/shared/utilities/WorkspaceUtils.ts

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,55 @@
11
import { Workspace } from '~/shared/api/backendApiTypes';
22

3-
export const formatRam = (valueInKb: number): string => {
4-
export const formatRam = (valueInKb: number | undefined | null): string => {
5-
if (valueInKb === undefined || valueInKb === null) {
3+
export const formatRam = (value: string | undefined | null): string => {
4+
if (value === undefined || value === null) {
65
return 'N/A';
76
}
8-
const units = ['KB', 'MB', 'GB', 'TB'];
7+
8+
// Extract numeric part and unit (e.g., "128Mi" → [128, "Mi"])
9+
const match = value.match(/^(\d+)([KMGTP]i?)$/);
10+
if (!match) {
11+
return 'Invalid';
12+
}
13+
14+
const size = parseInt(match[1], 10);
15+
const unit = match[2];
16+
17+
// Convert input to KB based on unit
18+
const unitMultiplier: Record<string, number> = {
19+
Ki: 1, // Kibibyte = 1 KB
20+
Mi: 1024, // Mebibyte → KB
21+
Gi: 1024 * 1024, // Gigibyte → KB
22+
Ti: 1024 * 1024 * 1024, // Terabyte → KB
23+
Pi: 1024 * 1024 * 1024 * 1024, // Petabyte → KB (unlikely but just in case)
24+
};
25+
26+
const valueInKB = size * (unitMultiplier[unit] || 1);
27+
return formatFromKB(valueInKB);
28+
};
29+
30+
// Helper function to format KB into readable units
31+
const formatFromKB = (valueInKb: number): string => {
32+
const units = ['KB', 'MB', 'GB', 'TB', 'PB'];
933
let index = 0;
10-
let value = valueInKb;
1134

12-
while (value >= 1024 && index < units.length - 1) {
13-
value /= 1024;
35+
while (valueInKb >= 1024 && index < units.length - 1) {
36+
// eslint-disable-next-line no-param-reassign
37+
valueInKb /= 1024;
1438
index += 1;
1539
}
1640

17-
return `${value.toFixed(2)} ${units[index]}`;
41+
return `${valueInKb.toFixed(2)} ${units[index]}`;
42+
};
43+
44+
export const formatCPU = (value: string): string => {
45+
// If value is in millicores (e.g., "100m"), convert it to vCPUs
46+
if (value.endsWith('m')) {
47+
const milliCores = parseInt(value.replace('m', ''), 10);
48+
return `${(milliCores / 1000).toFixed(2)} vCPU`;
49+
}
50+
51+
// Otherwise, assume it's in full cores
52+
return `${value} vCPU`;
1853
};
1954

2055
// Helper function to format UNIX timestamps

0 commit comments

Comments
 (0)