Skip to content

Commit 5494b44

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

File tree

4 files changed

+161
-37
lines changed

4 files changed

+161
-37
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,25 @@
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

1110
export const ExpandedWorkspaceRow: React.FC<ExpandedWorkspaceRowProps> = ({
12-
workspace,
13-
columnNames,
11+
workspace
1412
}) => {
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-
3113
return (
3214
<Tr>
3315
<Td />
34-
{renderExpandedData()}
16+
<Td />
17+
<Td noPadding colSpan={3}>
18+
<ExpandableRowContent>
19+
<DataVolumesList workspace={workspace} />
20+
</ExpandableRowContent>
21+
</Td>
22+
<Td colSpan={8} />
3523
</Tr>
3624
);
3725
};

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

+59-16
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import DeleteModal from '~/shared/components/DeleteModal';
3434
import { buildKindLogoDictionary } from '~/app/actions/WorkspaceKindsActions';
3535
import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds';
3636
import { WorkspaceConnectAction } from '~/app/pages/Workspaces/WorkspaceConnectAction';
37+
import { WorkspaceAggregatedDetails } from '~/app/pages/Workspaces/DetailsAggregated/WorkspaceAggregatedDetails';
3738
import Filter, { FilteredColumn } from 'shared/components/Filter';
3839
import { formatRam } from 'shared/utilities/WorkspaceResources';
3940

@@ -170,7 +171,7 @@ export const Workspaces: React.FunctionComponent = () => {
170171
lastActivity: 'Last Activity',
171172
};
172173

173-
const filterableColumns: WorkspacesColumnNames = {
174+
const filterableColumns = {
174175
name: 'Name',
175176
kind: 'Kind',
176177
image: 'Image',
@@ -387,22 +388,49 @@ export const Workspaces: React.FunctionComponent = () => {
387388
setPage(newPage);
388389
};
389390

390-
const workspaceDetailsContent = (
391-
<>
392-
{selectedWorkspace && (
393-
<WorkspaceDetails
394-
workspace={selectedWorkspace}
395-
onCloseClick={() => selectWorkspace(null)}
396-
onEditClick={() => editAction(selectedWorkspace)}
397-
onDeleteClick={() => handleDeleteClick(selectedWorkspace)}
398-
/>
399-
)}
400-
</>
401-
);
391+
const [selectedWorkspaceNames, setSelectedWorkspaceNames] = React.useState<string[]>([]);
392+
const setWorkspaceSelected = (workspace: Workspace, isSelecting = true) =>
393+
setSelectedWorkspaceNames((prevSelected) => {
394+
const otherSelectedWorkspaceNames = prevSelected.filter((w) => w !== workspace.name);
395+
return isSelecting
396+
? [...otherSelectedWorkspaceNames, workspace.name]
397+
: otherSelectedWorkspaceNames;
398+
});
399+
const selectAllWorkspaces = (isSelecting = true) =>
400+
setSelectedWorkspaceNames(isSelecting ? sortedWorkspaces.map((r) => r.name) : []);
401+
const areAllWorkspacesSelected = selectedWorkspaceNames.length === sortedWorkspaces.length;
402+
const isWorkspaceSelected = (workspace: Workspace) =>
403+
selectedWorkspaceNames.includes(workspace.name);
404+
405+
const workspaceDetailsContent = () => {
406+
const selectedWorkspaceForDetails =
407+
selectedWorkspaceNames.length === 1
408+
? sortedWorkspaces.find((w) => w.name === selectedWorkspaceNames[0])
409+
: undefined;
410+
return (
411+
<>
412+
{selectedWorkspaceForDetails && (
413+
<WorkspaceDetails
414+
workspace={selectedWorkspaceForDetails}
415+
onCloseClick={() => selectAllWorkspaces(false)}
416+
onEditClick={() => editAction(selectedWorkspaceForDetails)}
417+
onDeleteClick={() => handleDeleteClick(selectedWorkspaceForDetails)}
418+
/>
419+
)}
420+
{selectedWorkspaceNames.length > 1 && (
421+
<WorkspaceAggregatedDetails
422+
workspaceNames={selectedWorkspaceNames}
423+
onCloseClick={() => selectAllWorkspaces(false)}
424+
onDeleteClick={() => console.log('Delete selected workspaces')}
425+
/>
426+
)}
427+
</>
428+
);
429+
};
402430

403431
return (
404-
<Drawer isInline isExpanded={selectedWorkspace != null}>
405-
<DrawerContent panelContent={workspaceDetailsContent}>
432+
<Drawer isInline isExpanded={selectedWorkspaceNames.length >= 1}>
433+
<DrawerContent panelContent={workspaceDetailsContent()}>
406434
<DrawerContentBody>
407435
<PageSection isFilled>
408436
<Content>
@@ -420,6 +448,13 @@ export const Workspaces: React.FunctionComponent = () => {
420448
<Thead>
421449
<Tr>
422450
<Th />
451+
<Th
452+
select={{
453+
onSelect: (_event, isSelecting) => selectAllWorkspaces(isSelecting),
454+
isSelected: areAllWorkspacesSelected,
455+
}}
456+
aria-label="Row select"
457+
/>
423458
{Object.values(columnNames).map((columnName, index) => (
424459
<Th key={`${columnName}-col-name`} sort={getSortParams(index)}>
425460
{columnName}
@@ -444,6 +479,14 @@ export const Workspaces: React.FunctionComponent = () => {
444479
setWorkspaceExpanded(workspace, !isWorkspaceExpanded(workspace)),
445480
}}
446481
/>
482+
<Td
483+
select={{
484+
rowIndex,
485+
onSelect: (_event, isSelecting) =>
486+
setWorkspaceSelected(workspace, isSelecting),
487+
isSelected: isWorkspaceSelected(workspace),
488+
}}
489+
/>
447490
<Td dataLabel={columnNames.name}>{workspace.name}</Td>
448491
<Td dataLabel={columnNames.kind}>
449492
{kindLogoDict[workspace.kind] ? (
@@ -491,7 +534,7 @@ export const Workspaces: React.FunctionComponent = () => {
491534
</Td>
492535
</Tr>
493536
{isWorkspaceExpanded(workspace) && (
494-
<ExpandedWorkspaceRow workspace={workspace} columnNames={columnNames} />
537+
<ExpandedWorkspaceRow workspace={workspace} />
495538
)}
496539
</Tbody>
497540
))}

0 commit comments

Comments
 (0)