Skip to content

Commit c332481

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

File tree

4 files changed

+160
-50
lines changed

4 files changed

+160
-50
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

+58-29
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,
@@ -341,11 +341,6 @@ export const Workspaces: React.FunctionComponent = () => {
341341

342342
// Actions
343343

344-
const viewDetailsClick = React.useCallback((workspace: Workspace) => {
345-
setSelectedWorkspace(workspace);
346-
setActiveActionType(ActionType.ViewDetails);
347-
}, []);
348-
349344
const editAction = React.useCallback((workspace: Workspace) => {
350345
setSelectedWorkspace(workspace);
351346
setActiveActionType(ActionType.Edit);
@@ -383,11 +378,6 @@ export const Workspaces: React.FunctionComponent = () => {
383378
const workspaceDefaultActions = (workspace: Workspace): IActions => {
384379
const workspaceState = workspace.status.state;
385380
const workspaceActions = [
386-
{
387-
id: 'view-details',
388-
title: 'View Details',
389-
onClick: () => viewDetailsClick(workspace),
390-
},
391381
{
392382
id: 'edit',
393383
title: 'Edit',
@@ -526,25 +516,49 @@ export const Workspaces: React.FunctionComponent = () => {
526516
setPage(newPage);
527517
};
528518

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-
);
519+
const [selectedWorkspaceNames, setSelectedWorkspaceNames] = React.useState<string[]>([]);
520+
const setWorkspaceSelected = (workspace: Workspace, isSelecting = true) =>
521+
setSelectedWorkspaceNames((prevSelected) => {
522+
const otherSelectedWorkspaceNames = prevSelected.filter((w) => w !== workspace.name);
523+
return isSelecting
524+
? [...otherSelectedWorkspaceNames, workspace.name]
525+
: otherSelectedWorkspaceNames;
526+
});
527+
const selectAllWorkspaces = (isSelecting = true) =>
528+
setSelectedWorkspaceNames(isSelecting ? sortedWorkspaces.map((r) => r.name) : []);
529+
const areAllWorkspacesSelected = selectedWorkspaceNames.length === sortedWorkspaces.length;
530+
const isWorkspaceSelected = (workspace: Workspace) =>
531+
selectedWorkspaceNames.includes(workspace.name);
532+
533+
const workspaceDetailsContent = () => {
534+
const selectedWorkspaceForDetails =
535+
selectedWorkspaceNames.length === 1
536+
? sortedWorkspaces.find((w) => w.name === selectedWorkspaceNames[0])
537+
: undefined;
538+
return (
539+
<>
540+
{selectedWorkspaceForDetails && (
541+
<WorkspaceDetails
542+
workspace={selectedWorkspaceForDetails}
543+
onCloseClick={() => selectAllWorkspaces(false)}
544+
onEditClick={() => editAction(selectedWorkspaceForDetails)}
545+
onDeleteClick={() => handleDeleteClick(selectedWorkspaceForDetails)}
546+
/>
547+
)}
548+
{selectedWorkspaceNames.length > 1 && (
549+
<WorkspaceAggregatedDetails
550+
workspaceNames={selectedWorkspaceNames}
551+
onCloseClick={() => selectAllWorkspaces(false)}
552+
onDeleteClick={() => console.log('Delete selected workspaces')}
553+
/>
554+
)}
555+
</>
556+
);
557+
};
541558

542559
return (
543-
<Drawer
544-
isInline
545-
isExpanded={selectedWorkspace != null && activeActionType === ActionType.ViewDetails}
546-
>
547-
<DrawerContent panelContent={workspaceDetailsContent}>
560+
<Drawer isExpanded={selectedWorkspaceNames.length >= 1}>
561+
<DrawerContent panelContent={workspaceDetailsContent()}>
548562
<DrawerContentBody>
549563
<PageSection isFilled>
550564
<Content>
@@ -562,6 +576,13 @@ export const Workspaces: React.FunctionComponent = () => {
562576
<Thead>
563577
<Tr>
564578
<Th />
579+
<Th
580+
select={{
581+
onSelect: (_event, isSelecting) => selectAllWorkspaces(isSelecting),
582+
isSelected: areAllWorkspacesSelected,
583+
}}
584+
aria-label="Row select"
585+
/>
565586
{Object.values(columnNames).map((columnName, index) => (
566587
<Th
567588
key={`${columnName}-col-name`}
@@ -589,6 +610,14 @@ export const Workspaces: React.FunctionComponent = () => {
589610
setWorkspaceExpanded(workspace, !isWorkspaceExpanded(workspace)),
590611
}}
591612
/>
613+
<Td
614+
select={{
615+
rowIndex,
616+
onSelect: (_event, isSelecting) =>
617+
setWorkspaceSelected(workspace, isSelecting),
618+
isSelected: isWorkspaceSelected(workspace),
619+
}}
620+
/>
592621
<Td dataLabel={columnNames.redirectStatus}>
593622
{workspaceRedirectStatus[workspace.kind]
594623
? getRedirectStatusIcon(
@@ -645,7 +674,7 @@ export const Workspaces: React.FunctionComponent = () => {
645674
</Td>
646675
</Tr>
647676
{isWorkspaceExpanded(workspace) && (
648-
<ExpandedWorkspaceRow workspace={workspace} columnNames={columnNames} />
677+
<ExpandedWorkspaceRow workspace={workspace} />
649678
)}
650679
</Tbody>
651680
))}

0 commit comments

Comments
 (0)