feat(ws): Notebooks 2.0 // Frontend // Workspaces table // Workspace redirect status column #147 (#191)

Signed-off-by: Asaad Balum <asaad.balum@gmail.com>
This commit is contained in:
asaadbalum 2025-02-12 14:14:29 +02:00 committed by GitHub
parent 6d04cffd61
commit f22890d307
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 127 additions and 16 deletions

View File

@ -19,3 +19,34 @@ export function buildKindLogoDictionary(workspaceKinds: WorkspaceKind[] | []): K
}
return kindLogoDict;
}
type WorkspaceRedirectStatus = Record<
string,
{ to: string; message: string; level: string } | null
>;
/**
* Builds a dictionary of workspace kinds to redirect statuses.
* @param {WorkspaceKind[]} workspaceKinds - The list of workspace kinds.
* @returns {WorkspaceRedirectStatus} A dictionary with kind names as keys and redirect status objects as values.
*/
export function buildWorkspaceRedirectStatus(
workspaceKinds: WorkspaceKind[] | [],
): WorkspaceRedirectStatus {
const workspaceRedirectStatus: WorkspaceRedirectStatus = {};
for (const workspaceKind of workspaceKinds) {
// Loop through the `values` array inside `imageConfig`
const redirect = workspaceKind.podTemplate.options.imageConfig.values.find(
(value) => value.redirect,
)?.redirect;
// If redirect exists, extract the necessary properties
workspaceRedirectStatus[workspaceKind.name] = redirect
? {
to: redirect.to,
message: redirect.message.text,
level: redirect.message.level,
}
: null;
}
return workspaceRedirectStatus;
}

View File

@ -25,13 +25,22 @@ import {
ActionsColumn,
IActions,
} from '@patternfly/react-table';
import {
InfoCircleIcon,
ExclamationTriangleIcon,
TimesCircleIcon,
QuestionCircleIcon,
CodeIcon,
} from '@patternfly/react-icons';
import { useState } from 'react';
import { CodeIcon } from '@patternfly/react-icons';
import { Workspace, WorkspacesColumnNames, WorkspaceState } from '~/shared/types';
import { WorkspaceDetails } from '~/app/pages/Workspaces/Details/WorkspaceDetails';
import { ExpandedWorkspaceRow } from '~/app/pages/Workspaces/ExpandedWorkspaceRow';
import DeleteModal from '~/shared/components/DeleteModal';
import { buildKindLogoDictionary } from '~/app/actions/WorkspaceKindsActions';
import {
buildKindLogoDictionary,
buildWorkspaceRedirectStatus,
} from '~/app/actions/WorkspaceKindsActions';
import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds';
import { WorkspaceConnectAction } from '~/app/pages/Workspaces/WorkspaceConnectAction';
import { WorkspaceStartActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal';
@ -107,6 +116,10 @@ export const Workspaces: React.FunctionComponent = () => {
state: WorkspaceState.Paused,
stateMessage: 'It is paused.',
},
redirectStatus: {
level: 'Info',
text: 'This is informational', // Tooltip text
},
},
{
name: 'My Other Jupyter Notebook',
@ -162,6 +175,10 @@ export const Workspaces: React.FunctionComponent = () => {
state: WorkspaceState.Running,
stateMessage: 'It is running.',
},
redirectStatus: {
level: 'Danger',
text: 'This is dangerous',
},
},
];
@ -169,8 +186,15 @@ export const Workspaces: React.FunctionComponent = () => {
let kindLogoDict: Record<string, string> = {};
kindLogoDict = buildKindLogoDictionary(workspaceKinds);
let workspaceRedirectStatus: Record<
string,
{ to: string; message: string; level: string } | null
> = {}; // Initialize the redirect status dictionary
workspaceRedirectStatus = buildWorkspaceRedirectStatus(workspaceKinds); // Populate the dictionary
// Table columns
const columnNames: WorkspacesColumnNames = {
redirectStatus: 'Redirect Status',
name: 'Name',
kind: 'Kind',
image: 'Image',
@ -266,18 +290,20 @@ export const Workspaces: React.FunctionComponent = () => {
const [activeSortDirection, setActiveSortDirection] = React.useState<'asc' | 'desc' | null>(null);
const getSortableRowValues = (workspace: Workspace): (string | number)[] => {
const { name, kind, image, podConfig, state, homeVol, cpu, ram, lastActivity } = {
name: workspace.name,
kind: workspace.kind,
image: workspace.options.imageConfig,
podConfig: workspace.options.podConfig,
state: WorkspaceState[workspace.status.state],
homeVol: workspace.podTemplate.volumes.home,
cpu: workspace.cpu,
ram: workspace.ram,
lastActivity: workspace.status.activity.lastActivity,
};
return [name, kind, image, podConfig, state, homeVol, cpu, ram, lastActivity];
const { redirectStatus, name, kind, image, podConfig, state, homeVol, cpu, ram, lastActivity } =
{
redirectStatus: '',
name: workspace.name,
kind: workspace.kind,
image: workspace.options.imageConfig,
podConfig: workspace.options.podConfig,
state: WorkspaceState[workspace.status.state],
homeVol: workspace.podTemplate.volumes.home,
cpu: workspace.cpu,
ram: workspace.ram,
lastActivity: workspace.status.activity.lastActivity,
};
return [redirectStatus, name, kind, image, podConfig, state, homeVol, cpu, ram, lastActivity];
};
let sortedWorkspaces = workspaces;
@ -436,6 +462,43 @@ export const Workspaces: React.FunctionComponent = () => {
| 'yellow'
)[] = ['green', 'orange', 'yellow', 'blue', 'red', 'purple'];
// Redirect Status Icons
const getRedirectStatusIcon = (level: string | undefined, message: string) => {
switch (level) {
case 'Info':
return (
<Tooltip content={message}>
<InfoCircleIcon color="blue" aria-hidden="true" />
</Tooltip>
);
case 'Warning':
return (
<Tooltip content={message}>
<ExclamationTriangleIcon color="orange" aria-hidden="true" />
</Tooltip>
);
case 'Danger':
return (
<Tooltip content={message}>
<TimesCircleIcon color="red" aria-hidden="true" />
</Tooltip>
);
case undefined:
return (
<Tooltip content={message}>
<QuestionCircleIcon color="gray" aria-hidden="true" />
</Tooltip>
);
default:
return (
<Tooltip content={`Invalid level: ${level}`}>
<QuestionCircleIcon color="gray" aria-hidden="true" />
</Tooltip>
);
}
};
// Pagination
const [page, setPage] = React.useState(1);
@ -494,7 +557,10 @@ export const Workspaces: React.FunctionComponent = () => {
<Tr>
<Th />
{Object.values(columnNames).map((columnName, index) => (
<Th key={`${columnName}-col-name`} sort={getSortParams(index)}>
<Th
key={`${columnName}-col-name`}
sort={columnName !== 'Redirect Status' ? getSortParams(index) : undefined}
>
{columnName}
</Th>
))}
@ -517,6 +583,15 @@ export const Workspaces: React.FunctionComponent = () => {
setWorkspaceExpanded(workspace, !isWorkspaceExpanded(workspace)),
}}
/>
<Td dataLabel={columnNames.redirectStatus}>
{workspaceRedirectStatus[workspace.kind]
? getRedirectStatusIcon(
workspaceRedirectStatus[workspace.kind]?.level,
workspaceRedirectStatus[workspace.kind]?.message ||
'No API response available',
)
: getRedirectStatusIcon(undefined, 'No API response available')}
</Td>
<Td dataLabel={columnNames.name}>{workspace.name}</Td>
<Td dataLabel={columnNames.kind}>
{kindLogoDict[workspace.kind] ? (

View File

@ -38,7 +38,7 @@ export interface WorkspaceKind {
pythonVersion: string;
};
hidden: true;
redirect: {
redirect?: {
to: string;
message: {
text: string;
@ -126,6 +126,10 @@ export interface Workspace {
podConfig: string;
};
status: WorkspaceStatus;
redirectStatus: {
level: 'Info' | 'Warning' | 'Danger';
text: string;
};
}
export type WorkspacesColumnNames = {
@ -138,4 +142,5 @@ export type WorkspacesColumnNames = {
cpu: string;
ram: string;
lastActivity: string;
redirectStatus: string;
};