Skip to content

Commit ec06987

Browse files
author
Yehudit Kerido
committed
feat(ws): Notebooks 2.0 // Frontend // Fetch workspaces
Signed-off-by: Yehudit Kerido <[email protected]>
1 parent 86ef032 commit ec06987

File tree

7 files changed

+167
-101
lines changed

7 files changed

+167
-101
lines changed

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ func NewWorkspaceModelFromWorkspace(ws *kubefloworgv1beta1.Workspace, wsk *kubef
119119
ImageConfig: imageConfigModel,
120120
PodConfig: podConfigModel,
121121
},
122+
EndPoints: buildEndpointsList(ws, wsk),
122123
},
123124
Activity: Activity{
124125
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
@@ -58,6 +58,12 @@ type PodTemplate struct {
5858
PodMetadata PodMetadata `json:"podMetadata"`
5959
Volumes PodVolumes `json:"volumes"`
6060
Options PodTemplateOptions `json:"options"`
61+
EndPoints []EndPoints `json:"end_points"`
62+
}
63+
64+
type EndPoints struct {
65+
DisplayName string `json:"display_name"`
66+
Port string `json:"port"`
6167
}
6268

6369
type PodMetadata struct {
-240 KB
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: 111 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ import { WorkspaceRestartActionModal } from '~/app/pages/Workspaces/workspaceAct
5252
import { WorkspaceStopActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal';
5353
import Filter, { FilteredColumn } from 'shared/components/Filter';
5454
import { formatRam, formatCPU } from 'shared/utilities/WorkspaceResources';
55-
import { formatRam } from 'shared/utilities/WorkspaceUtils'; //check if still exist
55+
import { formatRam } from 'shared/utilities/WorkspaceUtils'; //check if still exist
5656

5757
export enum ActionType {
5858
ViewDetails,
@@ -189,7 +189,7 @@ export const Workspaces: React.FunctionComponent = () => {
189189

190190
const getSortableRowValues = (workspace: Workspace): (string | number | undefined)[] => {
191191
const { name, kind, image, podConfig, state, homeVol, cpu, ram, lastActivity } = {
192-
redirectStatus: '',
192+
redirectStatus: '',
193193
name: workspace.name,
194194
kind: workspace.workspace_kind.name,
195195
image: workspace.pod_template.options.image_config.current.display_name,
@@ -278,7 +278,7 @@ export const Workspaces: React.FunctionComponent = () => {
278278
};
279279

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