Skip to content

Commit 8004ea2

Browse files
committed
feat(ws): Notebooks 2.0 // Frontend // Workspaces table // Selectable rows
Signed-off-by: paulovmr <[email protected]>
1 parent a5bf4ee commit 8004ea2

File tree

4 files changed

+164
-71
lines changed

4 files changed

+164
-71
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from 'react';
2+
import {
3+
Button,
4+
DrawerActions,
5+
DrawerHead,
6+
DrawerPanelBody,
7+
DrawerPanelContent,
8+
Title,
9+
} from '@patternfly/react-core';
10+
import { WorkspaceAggregatedDetailsActions } from '~/app/pages/Workspaces/DetailsAggregated/WorkspaceAggregatedDetailsActions';
11+
12+
type WorkspaceAggregatedDetailsProps = {
13+
workspaceNames: string[];
14+
onCloseClick: React.MouseEventHandler;
15+
onDeleteClick: React.MouseEventHandler;
16+
};
17+
18+
export const WorkspaceAggregatedDetails: React.FunctionComponent<
19+
WorkspaceAggregatedDetailsProps
20+
> = ({ workspaceNames, onCloseClick, onDeleteClick }) => (
21+
<DrawerPanelContent>
22+
<DrawerHead>
23+
<Title headingLevel="h6">Multiple selected workspaces</Title>
24+
<WorkspaceAggregatedDetailsActions onDeleteClick={onDeleteClick} />
25+
<DrawerActions>
26+
<Button onClick={onCloseClick} aria-label="Clear workspoaces selection" variant="link">
27+
Clear selection
28+
</Button>
29+
</DrawerActions>
30+
</DrawerHead>
31+
<DrawerPanelBody>
32+
<Title headingLevel="h6" size="md">
33+
{'Selected workspaces: '}
34+
</Title>
35+
{workspaceNames.join(', ')}
36+
</DrawerPanelBody>
37+
</DrawerPanelContent>
38+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import * as React from 'react';
2+
import {
3+
Dropdown,
4+
DropdownList,
5+
MenuToggle,
6+
DropdownItem,
7+
Flex,
8+
FlexItem,
9+
} from '@patternfly/react-core';
10+
11+
interface WorkspaceAggregatedDetailsActionsProps {
12+
onDeleteClick: React.MouseEventHandler;
13+
}
14+
15+
export const WorkspaceAggregatedDetailsActions: React.FC<
16+
WorkspaceAggregatedDetailsActionsProps
17+
> = ({ onDeleteClick }) => {
18+
const [isOpen, setOpen] = React.useState(false);
19+
20+
return (
21+
<Flex>
22+
<FlexItem>
23+
<Dropdown
24+
isOpen={isOpen}
25+
onSelect={() => setOpen(false)}
26+
onOpenChange={(open) => setOpen(open)}
27+
popperProps={{ position: 'end' }}
28+
toggle={(toggleRef) => (
29+
<MenuToggle
30+
variant="primary"
31+
ref={toggleRef}
32+
onClick={() => setOpen(!isOpen)}
33+
isExpanded={isOpen}
34+
aria-label="Workspace aggregated details action toggle"
35+
data-testid="workspace-aggregated-details-action-toggle"
36+
>
37+
Actions
38+
</MenuToggle>
39+
)}
40+
>
41+
<DropdownList>
42+
<DropdownItem
43+
id="workspace-aggregated-details-action-delete-button"
44+
aria-label="Delete selected workspace"
45+
key="delete-aggregated-workspace-button"
46+
onClick={onDeleteClick}
47+
>
48+
Delete selected
49+
</DropdownItem>
50+
</DropdownList>
51+
</Dropdown>
52+
</FlexItem>
53+
</Flex>
54+
);
55+
};
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,21 @@
11
import * as React from 'react';
22
import { ExpandableRowContent, Td, Tr } from '@patternfly/react-table';
3-
import { Workspace, WorkspacesColumnNames } from '~/shared/types';
3+
import { Workspace } from '~/shared/types';
44
import { DataVolumesList } from '~/app/pages/Workspaces/DataVolumesList';
55

66
interface ExpandedWorkspaceRowProps {
77
workspace: Workspace;
8-
columnNames: WorkspacesColumnNames;
98
}
109

11-
export const ExpandedWorkspaceRow: React.FC<ExpandedWorkspaceRowProps> = ({
12-
workspace,
13-
columnNames,
14-
}) => {
15-
const renderExpandedData = () =>
16-
Object.keys(columnNames).map((colName) => {
17-
switch (colName) {
18-
case 'name':
19-
return (
20-
<Td noPadding colSpan={1}>
21-
<ExpandableRowContent>
22-
<DataVolumesList workspace={workspace} />
23-
</ExpandableRowContent>
24-
</Td>
25-
);
26-
default:
27-
return <Td />;
28-
}
29-
});
30-
31-
return (
32-
<Tr>
33-
<Td />
34-
{renderExpandedData()}
35-
</Tr>
36-
);
37-
};
10+
export const ExpandedWorkspaceRow: React.FC<ExpandedWorkspaceRowProps> = ({ workspace }) => (
11+
<Tr>
12+
<Td />
13+
<Td />
14+
<Td noPadding colSpan={3}>
15+
<ExpandableRowContent>
16+
<DataVolumesList workspace={workspace} />
17+
</ExpandableRowContent>
18+
</Td>
19+
<Td colSpan={8} />
20+
</Tr>
21+
);

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

+58-42
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ import { WorkspaceConnectAction } from '~/app/pages/Workspaces/WorkspaceConnectA
4646
import { WorkspaceStartActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal';
4747
import { WorkspaceRestartActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRestartActionModal';
4848
import { WorkspaceStopActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal';
49+
import { WorkspaceAggregatedDetails } from '~/app/pages/Workspaces/DetailsAggregated/WorkspaceAggregatedDetails';
4950
import Filter, { FilteredColumn } from 'shared/components/Filter';
5051
import { formatRam } from 'shared/utilities/WorkspaceUtils';
5152

5253
export enum ActionType {
53-
ViewDetails,
5454
Edit,
5555
Delete,
5656
Start,
@@ -225,17 +225,6 @@ export const Workspaces: React.FunctionComponent = () => {
225225
const [isActionAlertModalOpen, setIsActionAlertModalOpen] = React.useState(false);
226226
const [activeActionType, setActiveActionType] = React.useState<ActionType | null>(null);
227227

228-
const selectWorkspace = React.useCallback(
229-
(newSelectedWorkspace) => {
230-
if (selectedWorkspace?.name === newSelectedWorkspace?.name) {
231-
setSelectedWorkspace(null);
232-
} else {
233-
setSelectedWorkspace(newSelectedWorkspace);
234-
}
235-
},
236-
[selectedWorkspace],
237-
);
238-
239228
const setWorkspaceExpanded = (workspace: Workspace, isExpanding = true) =>
240229
setExpandedWorkspacesNames((prevExpanded) => {
241230
const newExpandedWorkspacesNames = prevExpanded.filter((wsName) => wsName !== workspace.name);
@@ -341,11 +330,6 @@ export const Workspaces: React.FunctionComponent = () => {
341330

342331
// Actions
343332

344-
const viewDetailsClick = React.useCallback((workspace: Workspace) => {
345-
setSelectedWorkspace(workspace);
346-
setActiveActionType(ActionType.ViewDetails);
347-
}, []);
348-
349333
const editAction = React.useCallback((workspace: Workspace) => {
350334
setSelectedWorkspace(workspace);
351335
setActiveActionType(ActionType.Edit);
@@ -383,11 +367,6 @@ export const Workspaces: React.FunctionComponent = () => {
383367
const workspaceDefaultActions = (workspace: Workspace): IActions => {
384368
const workspaceState = workspace.status.state;
385369
const workspaceActions = [
386-
{
387-
id: 'view-details',
388-
title: 'View Details',
389-
onClick: () => viewDetailsClick(workspace),
390-
},
391370
{
392371
id: 'edit',
393372
title: 'Edit',
@@ -526,25 +505,49 @@ export const Workspaces: React.FunctionComponent = () => {
526505
setPage(newPage);
527506
};
528507

529-
const workspaceDetailsContent = (
530-
<>
531-
{selectedWorkspace && (
532-
<WorkspaceDetails
533-
workspace={selectedWorkspace}
534-
onCloseClick={() => selectWorkspace(null)}
535-
onEditClick={() => editAction(selectedWorkspace)}
536-
onDeleteClick={() => handleDeleteClick(selectedWorkspace)}
537-
/>
538-
)}
539-
</>
540-
);
508+
const [selectedWorkspaceNames, setSelectedWorkspaceNames] = React.useState<string[]>([]);
509+
const setWorkspaceSelected = (workspace: Workspace, isSelecting = true) =>
510+
setSelectedWorkspaceNames((prevSelected) => {
511+
const otherSelectedWorkspaceNames = prevSelected.filter((w) => w !== workspace.name);
512+
return isSelecting
513+
? [...otherSelectedWorkspaceNames, workspace.name]
514+
: otherSelectedWorkspaceNames;
515+
});
516+
const selectAllWorkspaces = (isSelecting = true) =>
517+
setSelectedWorkspaceNames(isSelecting ? sortedWorkspaces.map((r) => r.name) : []);
518+
const areAllWorkspacesSelected = selectedWorkspaceNames.length === sortedWorkspaces.length;
519+
const isWorkspaceSelected = (workspace: Workspace) =>
520+
selectedWorkspaceNames.includes(workspace.name);
521+
522+
const workspaceDetailsContent = () => {
523+
const selectedWorkspaceForDetails =
524+
selectedWorkspaceNames.length === 1
525+
? sortedWorkspaces.find((w) => w.name === selectedWorkspaceNames[0])
526+
: undefined;
527+
return (
528+
<>
529+
{selectedWorkspaceForDetails && (
530+
<WorkspaceDetails
531+
workspace={selectedWorkspaceForDetails}
532+
onCloseClick={() => selectAllWorkspaces(false)}
533+
onEditClick={() => editAction(selectedWorkspaceForDetails)}
534+
onDeleteClick={() => handleDeleteClick(selectedWorkspaceForDetails)}
535+
/>
536+
)}
537+
{selectedWorkspaceNames.length > 1 && (
538+
<WorkspaceAggregatedDetails
539+
workspaceNames={selectedWorkspaceNames}
540+
onCloseClick={() => selectAllWorkspaces(false)}
541+
onDeleteClick={() => console.log('Delete selected workspaces')}
542+
/>
543+
)}
544+
</>
545+
);
546+
};
541547

542548
return (
543-
<Drawer
544-
isInline
545-
isExpanded={selectedWorkspace != null && activeActionType === ActionType.ViewDetails}
546-
>
547-
<DrawerContent panelContent={workspaceDetailsContent}>
549+
<Drawer isExpanded={selectedWorkspaceNames.length >= 1}>
550+
<DrawerContent panelContent={workspaceDetailsContent()}>
548551
<DrawerContentBody>
549552
<PageSection isFilled>
550553
<Content>
@@ -562,6 +565,13 @@ export const Workspaces: React.FunctionComponent = () => {
562565
<Thead>
563566
<Tr>
564567
<Th />
568+
<Th
569+
select={{
570+
onSelect: (_event, isSelecting) => selectAllWorkspaces(isSelecting),
571+
isSelected: areAllWorkspacesSelected,
572+
}}
573+
aria-label="Row select"
574+
/>
565575
{Object.values(columnNames).map((columnName, index) => (
566576
<Th
567577
key={`${columnName}-col-name`}
@@ -589,6 +599,14 @@ export const Workspaces: React.FunctionComponent = () => {
589599
setWorkspaceExpanded(workspace, !isWorkspaceExpanded(workspace)),
590600
}}
591601
/>
602+
<Td
603+
select={{
604+
rowIndex,
605+
onSelect: (_event, isSelecting) =>
606+
setWorkspaceSelected(workspace, isSelecting),
607+
isSelected: isWorkspaceSelected(workspace),
608+
}}
609+
/>
592610
<Td dataLabel={columnNames.redirectStatus}>
593611
{workspaceRedirectStatus[workspace.kind]
594612
? getRedirectStatusIcon(
@@ -644,9 +662,7 @@ export const Workspaces: React.FunctionComponent = () => {
644662
/>
645663
</Td>
646664
</Tr>
647-
{isWorkspaceExpanded(workspace) && (
648-
<ExpandedWorkspaceRow workspace={workspace} columnNames={columnNames} />
649-
)}
665+
{isWorkspaceExpanded(workspace) && <ExpandedWorkspaceRow workspace={workspace} />}
650666
</Tbody>
651667
))}
652668
</Table>

0 commit comments

Comments
 (0)