Skip to content

Commit 3c8c8e0

Browse files
authored
feat(ws): call delete Workspace API from the frontend (#383)
Signed-off-by: Guilherme Caponetto <[email protected]>
1 parent c40a2e0 commit 3c8c8e0

File tree

10 files changed

+80
-35
lines changed

10 files changed

+80
-35
lines changed

workspaces/frontend/package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

workspaces/frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
"@patternfly/react-styles": "^6.2.0",
105105
"@patternfly/react-table": "^6.2.0",
106106
"@types/js-yaml": "^4.0.9",
107+
"date-fns": "^4.1.0",
107108
"js-yaml": "^4.1.0",
108109
"npm-run-all": "^4.1.5",
109110
"react": "^18",

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

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
CodeIcon,
3535
} from '@patternfly/react-icons';
3636
import { useState } from 'react';
37+
import { formatDistanceToNow } from 'date-fns';
3738
import { Workspace, WorkspaceState } from '~/shared/api/backendApiTypes';
3839
import { WorkspaceDetails } from '~/app/pages/Workspaces/Details/WorkspaceDetails';
3940
import { ExpandedWorkspaceRow } from '~/app/pages/Workspaces/ExpandedWorkspaceRow';
@@ -115,7 +116,6 @@ export const Workspaces: React.FunctionComponent = () => {
115116
const [workspaces, setWorkspaces] = useState<Workspace[]>([]);
116117
const [expandedWorkspacesNames, setExpandedWorkspacesNames] = React.useState<string[]>([]);
117118
const [selectedWorkspace, setSelectedWorkspace] = React.useState<Workspace | null>(null);
118-
const [workspaceToDelete, setWorkspaceToDelete] = React.useState<Workspace | null>(null);
119119
const [isActionAlertModalOpen, setIsActionAlertModalOpen] = React.useState(false);
120120
const [activeActionType, setActiveActionType] = React.useState<ActionType | null>(null);
121121
const filterRef = React.useRef<FilterRef>(null);
@@ -285,10 +285,21 @@ export const Workspaces: React.FunctionComponent = () => {
285285
// setActiveActionType(ActionType.Edit);
286286
// }, []);
287287

288-
const deleteAction = React.useCallback((workspace: Workspace) => {
289-
setSelectedWorkspace(workspace);
290-
setActiveActionType(ActionType.Delete);
291-
}, []);
288+
const deleteAction = React.useCallback(async () => {
289+
if (!selectedWorkspace) {
290+
return;
291+
}
292+
293+
try {
294+
await api.deleteWorkspace({}, selectedNamespace, selectedWorkspace.name);
295+
// TODO: alert user about success
296+
console.info(`Workspace '${selectedWorkspace.name}' deleted successfully`);
297+
await initialWorkspacesRefresh();
298+
} catch (err) {
299+
// TODO: alert user about error
300+
console.error(`Error deleting workspace '${selectedWorkspace.name}': ${err}`);
301+
}
302+
}, [api, initialWorkspacesRefresh, selectedNamespace, selectedWorkspace]);
292303

293304
const startRestartAction = React.useCallback((workspace: Workspace, action: ActionType) => {
294305
setSelectedWorkspace(workspace);
@@ -305,7 +316,8 @@ export const Workspaces: React.FunctionComponent = () => {
305316
const handleDeleteClick = React.useCallback((workspace: Workspace) => {
306317
const buttonElement = document.activeElement as HTMLElement;
307318
buttonElement.blur(); // Remove focus from the currently focused button
308-
setWorkspaceToDelete(workspace); // Open the modal and set workspace to delete
319+
setSelectedWorkspace(workspace);
320+
setActiveActionType(ActionType.Delete);
309321
}, []);
310322

311323
const onCloseActionAlertDialog = () => {
@@ -610,7 +622,9 @@ export const Workspaces: React.FunctionComponent = () => {
610622
date={new Date(workspace.activity.lastActivity)}
611623
tooltip={{ variant: TimestampTooltipVariant.default }}
612624
>
613-
1 hour ago
625+
{formatDistanceToNow(new Date(workspace.activity.lastActivity), {
626+
addSuffix: true,
627+
})}
614628
</Timestamp>
615629
</Td>
616630
<Td>
@@ -641,14 +655,19 @@ export const Workspaces: React.FunctionComponent = () => {
641655
)}
642656
</Table>
643657
{isActionAlertModalOpen && chooseAlertModal()}
644-
<DeleteModal
645-
isOpen={workspaceToDelete != null}
646-
resourceName={workspaceToDelete?.name || ''}
647-
namespace={workspaceToDelete?.namespace || ''}
648-
title="Delete Workspace?"
649-
onClose={() => setWorkspaceToDelete(null)}
650-
onDelete={() => workspaceToDelete && deleteAction(workspaceToDelete)}
651-
/>
658+
{selectedWorkspace && (
659+
<DeleteModal
660+
isOpen={activeActionType === ActionType.Delete}
661+
resourceName={selectedWorkspace.name}
662+
namespace={selectedWorkspace.namespace}
663+
title="Delete Workspace?"
664+
onClose={() => {
665+
setSelectedWorkspace(null);
666+
setActiveActionType(null);
667+
}}
668+
onDelete={() => deleteAction()}
669+
/>
670+
)}
652671
<Pagination
653672
itemCount={333}
654673
widgetId="bottom-example"

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import {
77
ModalHeader,
88
TabTitleText,
99
} from '@patternfly/react-core';
10-
import { Workspace } from '~/shared/api/backendApiTypes';
1110
import { WorkspaceRedirectInformationView } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView';
12-
import { ActionButton } from '~/app/pages/Workspaces/workspaceActions/ActionButton';
11+
import { Workspace } from '~/shared/api/backendApiTypes';
12+
import { ActionButton } from '~/shared/components/ActionButton';
1313

1414
interface StartActionAlertProps {
1515
onClose: () => void;

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import * as React from 'react';
22
import {
33
Button,
4+
Content,
45
Modal,
56
ModalBody,
67
ModalFooter,
78
ModalHeader,
89
TabTitleText,
9-
Content,
1010
} from '@patternfly/react-core';
11-
import { Workspace } from '~/shared/api/backendApiTypes';
1211
import { WorkspaceRedirectInformationView } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRedirectInformationView';
13-
import { ActionButton } from '~/app/pages/Workspaces/workspaceActions/ActionButton';
12+
import { Workspace } from '~/shared/api/backendApiTypes';
13+
import { ActionButton } from '~/shared/components/ActionButton';
1414

1515
interface StopActionAlertProps {
1616
onClose: () => void;

workspaces/frontend/src/shared/api/notebookService.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,7 @@ export const patchWorkspace: PatchWorkspaceAPI = (hostPath) => (opts, namespace,
6868
);
6969

7070
export const deleteWorkspace: DeleteWorkspaceAPI = (hostPath) => (opts, namespace, workspace) =>
71-
handleRestFailures(
72-
restDELETE(hostPath, `/workspaces/${namespace}/${workspace}`, {}, {}, opts),
73-
).then((response) => extractNotebookResponse<void>(response));
71+
handleRestFailures(restDELETE(hostPath, `/workspaces/${namespace}/${workspace}`, {}, {}, opts));
7472

7573
export const pauseWorkspace: PauseWorkspaceAPI = (hostPath) => (opts, namespace, workspace) =>
7674
handleRestFailures(

workspaces/frontend/src/shared/components/DeleteModal.tsx

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect } from 'react';
1+
import React, { useState, useEffect, useCallback } from 'react';
22
import {
33
Modal,
44
ModalBody,
@@ -14,13 +14,14 @@ import {
1414
HelperTextItem,
1515
} from '@patternfly/react-core';
1616
import { default as ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon';
17+
import { ActionButton } from '~/shared/components/ActionButton';
1718

1819
interface DeleteModalProps {
1920
isOpen: boolean;
2021
resourceName: string;
2122
namespace: string;
2223
onClose: () => void;
23-
onDelete: (resourceName: string) => void;
24+
onDelete: (resourceName: string) => Promise<void>;
2425
title: string;
2526
}
2627

@@ -33,21 +34,25 @@ const DeleteModal: React.FC<DeleteModalProps> = ({
3334
onDelete,
3435
}) => {
3536
const [inputValue, setInputValue] = useState('');
37+
const [isDeleting, setIsDeleting] = React.useState(false);
3638

3739
useEffect(() => {
3840
if (!isOpen) {
3941
setInputValue('');
4042
}
4143
}, [isOpen]);
4244

43-
const handleDelete = () => {
45+
const handleDelete = useCallback(async () => {
4446
if (inputValue === resourceName) {
45-
onDelete(resourceName);
47+
setIsDeleting(true);
48+
await onDelete(resourceName);
49+
setIsDeleting(false);
50+
4651
onClose();
4752
} else {
4853
alert('Resource name does not match.');
4954
}
50-
};
55+
}, [inputValue, onClose, onDelete, resourceName]);
5156

5257
const handleInputChange = (event: React.FormEvent<HTMLInputElement>, value: string) => {
5358
setInputValue(value);
@@ -94,17 +99,21 @@ const DeleteModal: React.FC<DeleteModalProps> = ({
9499
</ModalBody>
95100
<ModalFooter>
96101
<div style={{ marginTop: '1rem' }}>
97-
<Button
102+
<ActionButton
103+
action="Delete"
104+
titleOnLoading="Deleting ..."
98105
onClick={handleDelete}
99106
variant="danger"
100107
isDisabled={inputValue !== resourceName}
101108
aria-disabled={inputValue !== resourceName}
102109
>
103110
Delete
104-
</Button>
105-
<Button onClick={onClose} variant="link" style={{ marginLeft: '1rem' }}>
106-
Cancel
107-
</Button>
111+
</ActionButton>
112+
{!isDeleting && (
113+
<Button onClick={onClose} variant="link" style={{ marginLeft: '1rem' }}>
114+
Cancel
115+
</Button>
116+
)}
108117
</div>
109118
</ModalFooter>
110119
</Modal>

workspaces/frontend/src/shared/mock/mockBuilder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ export const buildMockWorkspace = (workspace?: Partial<Workspace>): Workspace =>
103103
},
104104
},
105105
activity: {
106-
lastActivity: 1746551485113,
107-
lastUpdate: 1746551485113,
106+
lastActivity: new Date().getTime(),
107+
lastUpdate: new Date().getTime(),
108108
},
109109
pendingRestart: false,
110110
services: [

workspaces/frontend/src/shared/mock/mockNotebookServiceData.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ export const mockWorkspace2: Workspace = buildMockWorkspace({
6161
state: WorkspaceState.WorkspaceStatePaused,
6262
paused: false,
6363
deferUpdates: false,
64+
activity: {
65+
lastActivity: 1735603200000,
66+
lastUpdate: 1735603200000,
67+
},
6468
podTemplate: {
6569
podMetadata: {
6670
labels: { labelKey1: 'labelValue1', labelKey2: 'labelValue2' },
@@ -121,6 +125,10 @@ export const mockWorkspace3: Workspace = buildMockWorkspace({
121125
workspaceKind: mockWorkspaceKindInfo1,
122126
state: WorkspaceState.WorkspaceStateRunning,
123127
pendingRestart: true,
128+
activity: {
129+
lastActivity: 1744857600000,
130+
lastUpdate: 1744857600000,
131+
},
124132
});
125133

126134
export const mockWorkspace4 = buildMockWorkspace({

0 commit comments

Comments
 (0)