Skip to content

Commit 9ab07f3

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 a065e2f commit 9ab07f3

File tree

7 files changed

+165
-99
lines changed

7 files changed

+165
-99
lines changed

workspaces/backend/internal/models/workspaces/funcs.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ func NewWorkspaceModelFromWorkspace(ws *kubefloworgv1beta1.Workspace, wsk *kubef
120120
ImageConfig: buildImageConfig(ws, wsk),
121121
PodConfig: buildPodConfig(ws, wsk),
122122
},
123+
EndPoints: buildEndpointsList(ws, wsk),
123124
},
124125
Activity: Activity{
125126
LastActivity: ws.Status.Activity.LastActivity,
@@ -132,6 +133,37 @@ func NewWorkspaceModelFromWorkspace(ws *kubefloworgv1beta1.Workspace, wsk *kubef
132133
return workspaceModel
133134
}
134135

136+
func buildEndpointsList(ws *kubefloworgv1beta1.Workspace, wsk *kubefloworgv1beta1.WorkspaceKind) []EndPoints {
137+
var endpoints []EndPoints
138+
139+
// Return an empty list if wsk is nil.
140+
if wsk == nil {
141+
return endpoints
142+
}
143+
144+
// Get the image configuration from the WorkspaceKind's PodTemplate options.
145+
imageConfig := wsk.Spec.PodTemplate.Options.ImageConfig
146+
147+
for _, val := range imageConfig.Values {
148+
if len(val.Spec.Ports) == 0 {
149+
continue
150+
}
151+
firstPort := val.Spec.Ports[0]
152+
portStr := fmt.Sprintf("%d", firstPort.Port)
153+
displayName := firstPort.DisplayName
154+
if displayName == "" {
155+
displayName = val.Id
156+
}
157+
ep := EndPoints{
158+
DisplayName: displayName,
159+
Port: portStr,
160+
}
161+
endpoints = append(endpoints, ep)
162+
}
163+
164+
return endpoints
165+
}
166+
135167
func wskExists(wsk *kubefloworgv1beta1.WorkspaceKind) bool {
136168
return wsk != nil && wsk.UID != ""
137169
}

workspaces/backend/internal/models/workspaces/types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ type PodTemplate struct {
5555
PodMetadata PodMetadata `json:"pod_metadata"`
5656
Volumes PodVolumes `json:"volumes"`
5757
Options PodTemplateOptions `json:"options"`
58+
EndPoints []EndPoints `json:"end_points"`
59+
}
60+
61+
type EndPoints struct {
62+
DisplayName string `json:"display_name"`
63+
Port string `json:"port"`
5864
}
5965

6066
type PodMetadata struct {
Binary file not shown.

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { Workspace } from '~/shared/types';
21
import { WorkspaceState } from '~/shared/types';
32

43
const generateMockWorkspace = (
@@ -21,7 +20,7 @@ const generateMockWorkspace = (
2120
state: WorkspaceState;
2221
state_message: string;
2322
pod_template: {
24-
pod_metadata: { labels: {}; annotations: {} };
23+
pod_metadata: { labels: object; annotations: object };
2524
volumes: {
2625
home: { pvc_name: string; mount_path: string; readOnly: boolean };
2726
data: { pvc_name: string; mount_path: string; readOnly: boolean }[];
@@ -44,8 +43,9 @@ const generateMockWorkspace = (
4443
};
4544
};
4645
};
47-
image_config: { current: string; desired: string; redirect_chain: any[] };
48-
pod_config: { current: string; desired: string; redirect_chain: any[] };
46+
image_config: { current: string; desired: string; redirect_chain: never[] };
47+
pod_config: { current: string; desired: string; redirect_chain: never[] };
48+
end_points: { display_name: string; port: string }[];
4949
};
5050
activity: { last_activity: number; last_update: number };
5151
} => {
@@ -68,6 +68,12 @@ const generateMockWorkspace = (
6868
? 'Workspace is paused.'
6969
: 'Workspace is operational.',
7070
pod_template: {
71+
end_points: [
72+
{
73+
display_name: 'Jupyter-lab',
74+
port: '8888',
75+
},
76+
],
7177
pod_metadata: {
7278
labels: {},
7379
annotations: {},

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const WorkspaceConnectAction: React.FunctionComponent<WorkspaceConnectAct
3333
};
3434

3535
const onClickConnect = () => {
36-
openEndpoint(workspace.podTemplate.endpoints[0].port);
36+
openEndpoint(workspace.pod_template.end_points[0].port);
3737
};
3838

3939
const openEndpoint = (port: string) => {
@@ -51,7 +51,7 @@ export const WorkspaceConnectAction: React.FunctionComponent<WorkspaceConnectAct
5151
onClick={onToggleClick}
5252
isExpanded={open}
5353
isFullWidth
54-
isDisabled={workspace.status.state !== WorkspaceState.Running}
54+
isDisabled={workspace.state !== WorkspaceState.Running}
5555
splitButtonItems={[
5656
<MenuToggleAction
5757
id="connect-endpoint-button"
@@ -67,9 +67,9 @@ export const WorkspaceConnectAction: React.FunctionComponent<WorkspaceConnectAct
6767
shouldFocusToggleOnSelect
6868
>
6969
<DropdownList>
70-
{workspace.podTemplate.endpoints.map((endpoint) => (
70+
{workspace.pod_template.end_points.map((endpoint) => (
7171
<DropdownItem value={endpoint.port} key={`${workspace.name}-${endpoint.port}`}>
72-
{endpoint.displayName}
72+
{endpoint.display_name}
7373
</DropdownItem>
7474
))}
7575
</DropdownList>

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

Lines changed: 109 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ export const Workspaces: React.FunctionComponent = () => {
277277
};
278278

279279
const workspaceDefaultActions = (workspace: Workspace): IActions => {
280-
const workspaceState = workspace.status.state;
280+
const workspaceState = workspace.state;
281281
const workspaceActions = [
282282
{
283283
title: 'View Details',
@@ -456,98 +456,116 @@ export const Workspaces: React.FunctionComponent = () => {
456456
Create Workspace
457457
</Button>
458458
</Content>
459-
<Table aria-label="Sortable table" ouiaId="SortableTable">
460-
<Thead>
461-
<Tr>
462-
<Th />
463-
{Object.values(columnNames).map((columnName, index) => (
464-
<Th
465-
key={`${columnName}-col-name`}
466-
sort={columnName !== 'Redirect Status' ? getSortParams(index) : undefined}
459+
{/* Show a loading spinner if data is still loading */}
460+
{!loaded ? (
461+
<Spinner size="xl" />
462+
) : (
463+
<Table
464+
data-testid="workspaces-table"
465+
aria-label="Sortable table"
466+
ouiaId="SortableTable"
467+
>
468+
<Thead>
469+
<Tr>
470+
<Th />
471+
{Object.values(columnNames).map((columnName, index) => (
472+
<Th
473+
key={`${columnName}-col-name`}
474+
sort={columnName !== 'Redirect Status' ? getSortParams(index) : undefined}
475+
>
476+
{columnName}
477+
</Th>
478+
))}
479+
<Th screenReaderText="Primary action" />
480+
</Tr>
481+
</Thead>
482+
{sortedWorkspaces.map((workspace, rowIndex) => (
483+
<Tbody
484+
id="workspaces-table-content"
485+
key={rowIndex}
486+
isExpanded={isWorkspaceExpanded(workspace)}
487+
data-testid="table-body"
488+
>
489+
<Tr
490+
id={`workspaces-table-row-${rowIndex + 1}`}
491+
data-testid={`workspace-row-${rowIndex}`}
467492
>
468-
{columnName}
469-
</Th>
470-
))}
471-
<Th screenReaderText="Primary action" />
472-
</Tr>
473-
</Thead>
474-
{sortedWorkspaces.map((workspace, rowIndex) => (
475-
<Tbody
476-
id="workspaces-table-content"
477-
key={rowIndex}
478-
isExpanded={isWorkspaceExpanded(workspace)}
479-
data-testid="table-body"
480-
>
481-
<Tr id={`workspaces-table-row-${rowIndex + 1}`}>
482-
<Td
483-
expand={{
484-
rowIndex,
485-
isExpanded: isWorkspaceExpanded(workspace),
486-
onToggle: () =>
487-
setWorkspaceExpanded(workspace, !isWorkspaceExpanded(workspace)),
488-
}}
489-
/>
490-
<Td dataLabel={columnNames.redirectStatus}>
491-
{workspaceRedirectStatus[workspace.kind]
492-
? getRedirectStatusIcon(
493-
workspaceRedirectStatus[workspace.kind]?.level,
494-
workspaceRedirectStatus[workspace.kind]?.message ||
495-
'No API response available',
496-
)
497-
: getRedirectStatusIcon(undefined, 'No API response available')}
498-
</Td>
499-
<Td dataLabel={columnNames.name}>{workspace.name}</Td>
500-
<Td dataLabel={columnNames.kind}>
501-
{kindLogoDict[workspace.kind] ? (
502-
<Tooltip content={workspace.kind}>
503-
<Brand
504-
src={kindLogoDict[workspace.kind]}
505-
alt={workspace.kind}
506-
style={{ width: '20px', height: '20px', cursor: 'pointer' }}
507-
/>
508-
</Tooltip>
509-
) : (
510-
<Tooltip content={workspace.kind}>
511-
<CodeIcon />
512-
</Tooltip>
513-
)}
514-
</Td>
515-
<Td dataLabel={columnNames.image}>{workspace.options.imageConfig}</Td>
516-
<Td dataLabel={columnNames.podConfig}>{workspace.options.podConfig}</Td>
517-
<Td dataLabel={columnNames.state}>
518-
<Label color={stateColors[workspace.status.state]}>
519-
{WorkspaceState[workspace.status.state]}
520-
</Label>
521-
</Td>
522-
<Td dataLabel={columnNames.homeVol}>{workspace.podTemplate.volumes.home}</Td>
523-
<Td dataLabel={columnNames.cpu}>{`${workspace.cpu}%`}</Td>
524-
<Td dataLabel={columnNames.ram}>{formatRam(workspace.ram)}</Td>
525-
<Td dataLabel={columnNames.lastActivity}>
526-
<Timestamp
527-
date={new Date(workspace.status.activity.lastActivity)}
528-
tooltip={{ variant: TimestampTooltipVariant.default }}
529-
>
530-
1 hour ago
531-
</Timestamp>
532-
</Td>
533-
<Td>
534-
<WorkspaceConnectAction workspace={workspace} />
535-
</Td>
536-
<Td isActionCell data-testid="action-column">
537-
<ActionsColumn
538-
items={workspaceDefaultActions(workspace).map((action) => ({
539-
...action,
540-
'data-testid': `action-${typeof action.title === 'string' ? action.title.toLowerCase() : ''}`,
541-
}))}
493+
<Td
494+
expand={{
495+
rowIndex,
496+
isExpanded: isWorkspaceExpanded(workspace),
497+
onToggle: () =>
498+
setWorkspaceExpanded(workspace, !isWorkspaceExpanded(workspace)),
499+
}}
542500
/>
543-
</Td>
544-
</Tr>
545-
{isWorkspaceExpanded(workspace) && (
546-
<ExpandedWorkspaceRow workspace={workspace} columnNames={columnNames} />
547-
)}
548-
</Tbody>
549-
))}
550-
</Table>
501+
<Td dataLabel={columnNames.redirectStatus}>
502+
{workspaceRedirectStatus[workspace.kind]
503+
? getRedirectStatusIcon(
504+
workspaceRedirectStatus[workspace.kind]?.level,
505+
workspaceRedirectStatus[workspace.kind]?.message ||
506+
'No API response available',
507+
)
508+
: getRedirectStatusIcon(undefined, 'No API response available')}
509+
</Td>
510+
<Td data-testid="workspace-name" dataLabel={columnNames.name}>
511+
{workspace.name}
512+
</Td>
513+
<Td dataLabel={columnNames.kind}>
514+
{kindLogoDict[workspace.workspace_kind.name] ? (
515+
<Tooltip content={workspace.workspace_kind.name}>
516+
<Brand
517+
src={kindLogoDict[workspace.workspace_kind.name]}
518+
alt={workspace.workspace_kind.name}
519+
style={{ width: '20px', height: '20px', cursor: 'pointer' }}
520+
/>
521+
</Tooltip>
522+
) : (
523+
<Tooltip content={workspace.workspace_kind.name}>
524+
<CodeIcon />
525+
</Tooltip>
526+
)}
527+
</Td>
528+
<Td dataLabel={columnNames.image}>
529+
{workspace.pod_template.options.image_config.current.display_name}
530+
</Td>
531+
<Td data-testid="pod-config" dataLabel={columnNames.podConfig}>
532+
{workspace.pod_template.options.pod_config.current.display_name}
533+
</Td>
534+
<Td data-testid="state-label" dataLabel={columnNames.state}>
535+
<Label color={stateColors[workspace.state]}>{workspace.state}</Label>{' '}
536+
</Td>
537+
<Td dataLabel={columnNames.homeVol}>
538+
{workspace.pod_template.volumes.home.pvc_name}
539+
</Td>
540+
<Td dataLabel={columnNames.cpu}>{formatCPU(getCpuValue(workspace))}</Td>
541+
<Td dataLabel={columnNames.ram}>{formatRam(getRamValue(workspace))}</Td>
542+
<Td dataLabel={columnNames.lastActivity}>
543+
<Timestamp
544+
date={new Date(workspace.activity.last_activity)}
545+
tooltip={{ variant: TimestampTooltipVariant.default }}
546+
>
547+
1 hour ago
548+
</Timestamp>
549+
</Td>
550+
<Td>
551+
<WorkspaceConnectAction workspace={workspace} />
552+
</Td>
553+
<Td isActionCell data-testid="action-column">
554+
<ActionsColumn
555+
items={workspaceDefaultActions(workspace).map((action) => ({
556+
...action,
557+
'data-testid': `action-${typeof action.title === 'string' ? action.title.toLowerCase() : ''}`,
558+
}))}
559+
/>
560+
</Td>
561+
</Tr>
562+
{isWorkspaceExpanded(workspace) && (
563+
<ExpandedWorkspaceRow workspace={workspace} columnNames={columnNames} />
564+
)}
565+
</Tbody>
566+
))}
567+
</Table>
568+
)}
551569
{isActionAlertModalOpen && chooseAlertModal()}
552570
<DeleteModal
553571
isOpen={workspaceToDelete != null}

workspaces/frontend/src/shared/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ export interface Workspace {
8585
state: WorkspaceState;
8686
state_message: string;
8787
pod_template: {
88+
end_points: {
89+
display_name: string;
90+
port: string;
91+
}[];
8892
pod_metadata: {
8993
labels: Record<string, string>;
9094
annotations: Record<string, string>;

0 commit comments

Comments
 (0)