Skip to content

Commit fac3032

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 47ffbea commit fac3032

File tree

5 files changed

+157
-100
lines changed

5 files changed

+157
-100
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: imageConfigModel,
121121
PodConfig: podConfigModel,
122122
},
123+
EndPoints: buildEndpointsList(ws, wsk),
123124
},
124125
Activity: Activity{
125126
LastActivity: ws.Status.Activity.LastActivity,
@@ -133,6 +134,37 @@ func NewWorkspaceModelFromWorkspace(ws *kubefloworgv1beta1.Workspace, wsk *kubef
133134
return workspaceModel
134135
}
135136

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

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ type PodTemplate struct {
5959
PodMetadata PodMetadata `json:"podMetadata"`
6060
Volumes PodVolumes `json:"volumes"`
6161
Options PodTemplateOptions `json:"options"`
62+
EndPoints []EndPoints `json:"end_points"`
63+
}
64+
65+
type EndPoints struct {
66+
DisplayName string `json:"display_name"`
67+
Port string `json:"port"`
6268
}
6369

6470
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/Workspaces.tsx

Lines changed: 109 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@ 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
53+
import { formatRam } from 'shared/utilities/WorkspaceUtils';
5554
import { extractCpuValue, extractMemoryValue } from 'shared/utilities/WorkspaceUtils';
5655

5756
export enum ActionType {
@@ -486,102 +485,116 @@ export const Workspaces: React.FunctionComponent = () => {
486485
<Content style={{ display: 'flex', alignItems: 'flex-start', columnGap: '20px' }}>
487486
<Filter id="filter-workspaces" onFilter={onFilter} columnNames={filterableColumns} />
488487
</Content>
489-
<Table aria-label="Sortable table" ouiaId="SortableTable">
490-
<Thead>
491-
<Tr>
492-
<Th screenReaderText="expand-action" />
493-
{Object.values(columnNames).map((columnName, index) => (
494-
<Th
495-
key={`${columnName}-col-name`}
496-
sort={columnName !== 'Redirect Status' ? getSortParams(index) : undefined}
497-
>
498-
{columnName}
499-
</Th>
500-
))}
501-
<Th screenReaderText="Primary action" />
502-
</Tr>
503-
</Thead>
504-
{sortedWorkspaces.map((workspace, rowIndex) => (
505-
<Tbody
506-
id="workspaces-table-content"
507-
key={rowIndex}
508-
isExpanded={isWorkspaceExpanded(workspace)}
509-
data-testid="table-body"
510-
>
511-
<Tr id={`workspaces-table-row-${rowIndex + 1}`}>
512-
<Td
513-
expand={{
514-
rowIndex,
515-
isExpanded: isWorkspaceExpanded(workspace),
516-
onToggle: () =>
517-
setWorkspaceExpanded(workspace, !isWorkspaceExpanded(workspace)),
518-
}}
519-
/>
520-
<Td dataLabel={columnNames.redirectStatus}>
521-
{workspaceRedirectStatus[workspace.workspaceKind.name]
522-
? getRedirectStatusIcon(
523-
workspaceRedirectStatus[workspace.workspaceKind.name]?.message?.level,
524-
workspaceRedirectStatus[workspace.workspaceKind.name]?.message?.text ||
525-
'No API response available',
526-
)
527-
: getRedirectStatusIcon(undefined, 'No API response available')}
528-
</Td>
529-
<Td dataLabel={columnNames.name}>{workspace.name}</Td>
530-
<Td dataLabel={columnNames.kind}>
531-
{kindLogoDict[workspace.workspaceKind.name] ? (
532-
<Tooltip content={workspace.workspaceKind.name}>
533-
<Brand
534-
src={kindLogoDict[workspace.workspaceKind.name]}
535-
alt={workspace.workspaceKind.name}
536-
style={{ width: '20px', height: '20px', cursor: 'pointer' }}
537-
/>
538-
</Tooltip>
539-
) : (
540-
<Tooltip content={workspace.workspaceKind.name}>
541-
<CodeIcon />
542-
</Tooltip>
543-
)}
544-
</Td>
545-
<Td dataLabel={columnNames.image}>
546-
{workspace.podTemplate.options.imageConfig.current.displayName}
547-
</Td>
548-
<Td dataLabel={columnNames.podConfig}>
549-
{workspace.podTemplate.options.podConfig.current.displayName}
550-
</Td>
551-
<Td dataLabel={columnNames.state}>
552-
<Label color={extractStateColor(workspace.state)}>{workspace.state}</Label>
553-
</Td>
554-
<Td dataLabel={columnNames.homeVol}>
555-
{workspace.podTemplate.volumes.home?.pvcName ?? ''}
556-
</Td>
557-
<Td dataLabel={columnNames.cpu}>{extractCpuValue(workspace)}</Td>
558-
<Td dataLabel={columnNames.ram}>{extractMemoryValue(workspace)}</Td>
559-
<Td dataLabel={columnNames.lastActivity}>
560-
<Timestamp
561-
date={new Date(workspace.activity.lastActivity)}
562-
tooltip={{ variant: TimestampTooltipVariant.default }}
488+
{/* Show a loading spinner if data is still loading */}
489+
{!loaded ? (
490+
<Spinner size="xl" />
491+
) : (
492+
<Table
493+
data-testid="workspaces-table"
494+
aria-label="Sortable table"
495+
ouiaId="SortableTable"
496+
>
497+
<Thead>
498+
<Tr>
499+
<Th />
500+
{Object.values(columnNames).map((columnName, index) => (
501+
<Th
502+
key={`${columnName}-col-name`}
503+
sort={columnName !== 'Redirect Status' ? getSortParams(index) : undefined}
563504
>
564-
1 hour ago
565-
</Timestamp>
566-
</Td>
567-
<Td>
568-
<WorkspaceConnectAction workspace={workspace} />
569-
</Td>
570-
<Td isActionCell data-testid="action-column">
571-
<ActionsColumn
572-
items={workspaceDefaultActions(workspace).map((action) => ({
573-
...action,
574-
'data-testid': `action-${action.id || ''}`,
575-
}))}
576-
/>
577-
</Td>
505+
{columnName}
506+
</Th>
507+
))}
508+
<Th screenReaderText="Primary action" />
578509
</Tr>
579-
{isWorkspaceExpanded(workspace) && (
580-
<ExpandedWorkspaceRow workspace={workspace} columnNames={columnNames} />
581-
)}
582-
</Tbody>
583-
))}
584-
</Table>
510+
</Thead>
511+
{sortedWorkspaces.map((workspace, rowIndex) => (
512+
<Tbody
513+
id="workspaces-table-content"
514+
key={rowIndex}
515+
isExpanded={isWorkspaceExpanded(workspace)}
516+
data-testid="table-body"
517+
>
518+
<Tr
519+
id={`workspaces-table-row-${rowIndex + 1}`}
520+
data-testid={`workspace-row-${rowIndex}`}
521+
>
522+
<Td
523+
expand={{
524+
rowIndex,
525+
isExpanded: isWorkspaceExpanded(workspace),
526+
onToggle: () =>
527+
setWorkspaceExpanded(workspace, !isWorkspaceExpanded(workspace)),
528+
}}
529+
/>
530+
<Td dataLabel={columnNames.redirectStatus}>
531+
{workspaceRedirectStatus[workspace.kind]
532+
? getRedirectStatusIcon(
533+
workspaceRedirectStatus[workspace.kind]?.level,
534+
workspaceRedirectStatus[workspace.kind]?.message ||
535+
'No API response available',
536+
)
537+
: getRedirectStatusIcon(undefined, 'No API response available')}
538+
</Td>
539+
<Td data-testid="workspace-name" dataLabel={columnNames.name}>
540+
{workspace.name}
541+
</Td>
542+
<Td dataLabel={columnNames.kind}>
543+
{kindLogoDict[workspace.workspace_kind.name] ? (
544+
<Tooltip content={workspace.workspace_kind.name}>
545+
<Brand
546+
src={kindLogoDict[workspace.workspace_kind.name]}
547+
alt={workspace.workspace_kind.name}
548+
style={{ width: '20px', height: '20px', cursor: 'pointer' }}
549+
/>
550+
</Tooltip>
551+
) : (
552+
<Tooltip content={workspace.workspace_kind.name}>
553+
<CodeIcon />
554+
</Tooltip>
555+
)}
556+
</Td>
557+
<Td dataLabel={columnNames.image}>
558+
{workspace.pod_template.options.image_config.current.display_name}
559+
</Td>
560+
<Td data-testid="pod-config" dataLabel={columnNames.podConfig}>
561+
{workspace.pod_template.options.pod_config.current.display_name}
562+
</Td>
563+
<Td data-testid="state-label" dataLabel={columnNames.state}>
564+
<Label color={stateColors[workspace.state]}>{workspace.state}</Label>{' '}
565+
</Td>
566+
<Td dataLabel={columnNames.homeVol}>
567+
{workspace.pod_template.volumes.home.pvc_name}
568+
</Td>
569+
<Td dataLabel={columnNames.cpu}>{formatCPU(getCpuValue(workspace))}</Td>
570+
<Td dataLabel={columnNames.ram}>{formatRam(getRamValue(workspace))}</Td>
571+
<Td dataLabel={columnNames.lastActivity}>
572+
<Timestamp
573+
date={new Date(workspace.activity.last_activity)}
574+
tooltip={{ variant: TimestampTooltipVariant.default }}
575+
>
576+
1 hour ago
577+
</Timestamp>
578+
</Td>
579+
<Td>
580+
<WorkspaceConnectAction workspace={workspace} />
581+
</Td>
582+
<Td isActionCell data-testid="action-column">
583+
<ActionsColumn
584+
items={workspaceDefaultActions(workspace).map((action) => ({
585+
...action,
586+
'data-testid': `action-${typeof action.title === 'string' ? action.title.toLowerCase() : ''}`,
587+
}))}
588+
/>
589+
</Td>
590+
</Tr>
591+
{isWorkspaceExpanded(workspace) && (
592+
<ExpandedWorkspaceRow workspace={workspace} columnNames={columnNames} />
593+
)}
594+
</Tbody>
595+
))}
596+
</Table>
597+
)}
585598
{isActionAlertModalOpen && chooseAlertModal()}
586599
<DeleteModal
587600
isOpen={workspaceToDelete != null}

0 commit comments

Comments
 (0)