Skip to content

feat(ws): Workspace list: Selectable rows #196

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: notebooks-v2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import {
Button,
DrawerActions,
DrawerHead,
DrawerPanelBody,
DrawerPanelContent,
Title,
} from '@patternfly/react-core';
import { WorkspaceAggregatedDetailsActions } from '~/app/pages/Workspaces/DetailsAggregated/WorkspaceAggregatedDetailsActions';

type WorkspaceAggregatedDetailsProps = {
workspaceNames: string[];
onCloseClick: React.MouseEventHandler;
onDeleteClick: React.MouseEventHandler;
};

export const WorkspaceAggregatedDetails: React.FunctionComponent<
WorkspaceAggregatedDetailsProps
> = ({ workspaceNames, onCloseClick, onDeleteClick }) => (
<DrawerPanelContent>
<DrawerHead>
<Title headingLevel="h6">Multiple selected workspaces</Title>
<WorkspaceAggregatedDetailsActions onDeleteClick={onDeleteClick} />
<DrawerActions>
<Button onClick={onCloseClick} aria-label="Clear workspoaces selection" variant="link">
Clear selection
</Button>
</DrawerActions>
</DrawerHead>
<DrawerPanelBody>
<Title headingLevel="h6" size="md">
{'Selected workspaces: '}
</Title>
{workspaceNames.join(', ')}
</DrawerPanelBody>
</DrawerPanelContent>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as React from 'react';
import {
Dropdown,
DropdownList,
MenuToggle,
DropdownItem,
Flex,
FlexItem,
} from '@patternfly/react-core';

interface WorkspaceAggregatedDetailsActionsProps {
onDeleteClick: React.MouseEventHandler;
}

export const WorkspaceAggregatedDetailsActions: React.FC<
WorkspaceAggregatedDetailsActionsProps
> = ({ onDeleteClick }) => {
const [isOpen, setOpen] = React.useState(false);

return (
<Flex>
<FlexItem>
<Dropdown
isOpen={isOpen}
onSelect={() => setOpen(false)}
onOpenChange={(open) => setOpen(open)}
popperProps={{ position: 'end' }}
toggle={(toggleRef) => (
<MenuToggle
variant="primary"
ref={toggleRef}
onClick={() => setOpen(!isOpen)}
isExpanded={isOpen}
aria-label="Workspace aggregated details action toggle"
data-testid="workspace-aggregated-details-action-toggle"
>
Actions
</MenuToggle>
)}
>
<DropdownList>
<DropdownItem
id="workspace-aggregated-details-action-delete-button"
aria-label="Delete selected workspace"
key="delete-aggregated-workspace-button"
onClick={onDeleteClick}
>
Delete selected
</DropdownItem>
</DropdownList>
</Dropdown>
</FlexItem>
</Flex>
);
};
Original file line number Diff line number Diff line change
@@ -1,37 +1,21 @@
import * as React from 'react';
import { ExpandableRowContent, Td, Tr } from '@patternfly/react-table';
import { Workspace, WorkspacesColumnNames } from '~/shared/types';
import { Workspace } from '~/shared/types';
import { DataVolumesList } from '~/app/pages/Workspaces/DataVolumesList';

interface ExpandedWorkspaceRowProps {
workspace: Workspace;
columnNames: WorkspacesColumnNames;
}

export const ExpandedWorkspaceRow: React.FC<ExpandedWorkspaceRowProps> = ({
workspace,
columnNames,
}) => {
const renderExpandedData = () =>
Object.keys(columnNames).map((colName) => {
switch (colName) {
case 'name':
return (
<Td noPadding colSpan={1}>
<ExpandableRowContent>
<DataVolumesList workspace={workspace} />
</ExpandableRowContent>
</Td>
);
default:
return <Td />;
}
});

return (
<Tr>
<Td />
{renderExpandedData()}
</Tr>
);
};
export const ExpandedWorkspaceRow: React.FC<ExpandedWorkspaceRowProps> = ({ workspace }) => (
<Tr>
<Td />
<Td />
<Td noPadding colSpan={3}>
<ExpandableRowContent>
<DataVolumesList workspace={workspace} />
</ExpandableRowContent>
</Td>
<Td colSpan={8} />
</Tr>
);
100 changes: 58 additions & 42 deletions workspaces/frontend/src/app/pages/Workspaces/Workspaces.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ import { WorkspaceConnectAction } from '~/app/pages/Workspaces/WorkspaceConnectA
import { WorkspaceStartActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceStartActionModal';
import { WorkspaceRestartActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceRestartActionModal';
import { WorkspaceStopActionModal } from '~/app/pages/Workspaces/workspaceActions/WorkspaceStopActionModal';
import { WorkspaceAggregatedDetails } from '~/app/pages/Workspaces/DetailsAggregated/WorkspaceAggregatedDetails';
import Filter, { FilteredColumn } from 'shared/components/Filter';
import { formatRam } from 'shared/utilities/WorkspaceUtils';

export enum ActionType {
ViewDetails,
Edit,
Delete,
Start,
Expand Down Expand Up @@ -225,17 +225,6 @@ export const Workspaces: React.FunctionComponent = () => {
const [isActionAlertModalOpen, setIsActionAlertModalOpen] = React.useState(false);
const [activeActionType, setActiveActionType] = React.useState<ActionType | null>(null);

const selectWorkspace = React.useCallback(
(newSelectedWorkspace) => {
if (selectedWorkspace?.name === newSelectedWorkspace?.name) {
setSelectedWorkspace(null);
} else {
setSelectedWorkspace(newSelectedWorkspace);
}
},
[selectedWorkspace],
);

const setWorkspaceExpanded = (workspace: Workspace, isExpanding = true) =>
setExpandedWorkspacesNames((prevExpanded) => {
const newExpandedWorkspacesNames = prevExpanded.filter((wsName) => wsName !== workspace.name);
Expand Down Expand Up @@ -341,11 +330,6 @@ export const Workspaces: React.FunctionComponent = () => {

// Actions

const viewDetailsClick = React.useCallback((workspace: Workspace) => {
setSelectedWorkspace(workspace);
setActiveActionType(ActionType.ViewDetails);
}, []);

const editAction = React.useCallback((workspace: Workspace) => {
setSelectedWorkspace(workspace);
setActiveActionType(ActionType.Edit);
Expand Down Expand Up @@ -383,11 +367,6 @@ export const Workspaces: React.FunctionComponent = () => {
const workspaceDefaultActions = (workspace: Workspace): IActions => {
const workspaceState = workspace.status.state;
const workspaceActions = [
{
id: 'view-details',
title: 'View Details',
onClick: () => viewDetailsClick(workspace),
},
{
id: 'edit',
title: 'Edit',
Expand Down Expand Up @@ -526,25 +505,49 @@ export const Workspaces: React.FunctionComponent = () => {
setPage(newPage);
};

const workspaceDetailsContent = (
<>
{selectedWorkspace && (
<WorkspaceDetails
workspace={selectedWorkspace}
onCloseClick={() => selectWorkspace(null)}
onEditClick={() => editAction(selectedWorkspace)}
onDeleteClick={() => handleDeleteClick(selectedWorkspace)}
/>
)}
</>
);
const [selectedWorkspaceNames, setSelectedWorkspaceNames] = React.useState<string[]>([]);
const setWorkspaceSelected = (workspace: Workspace, isSelecting = true) =>
setSelectedWorkspaceNames((prevSelected) => {
const otherSelectedWorkspaceNames = prevSelected.filter((w) => w !== workspace.name);
return isSelecting
? [...otherSelectedWorkspaceNames, workspace.name]
: otherSelectedWorkspaceNames;
});
const selectAllWorkspaces = (isSelecting = true) =>
setSelectedWorkspaceNames(isSelecting ? sortedWorkspaces.map((r) => r.name) : []);
const areAllWorkspacesSelected = selectedWorkspaceNames.length === sortedWorkspaces.length;
const isWorkspaceSelected = (workspace: Workspace) =>
selectedWorkspaceNames.includes(workspace.name);

const workspaceDetailsContent = () => {
const selectedWorkspaceForDetails =
selectedWorkspaceNames.length === 1
? sortedWorkspaces.find((w) => w.name === selectedWorkspaceNames[0])
: undefined;
return (
<>
{selectedWorkspaceForDetails && (
<WorkspaceDetails
workspace={selectedWorkspaceForDetails}
onCloseClick={() => selectAllWorkspaces(false)}
onEditClick={() => editAction(selectedWorkspaceForDetails)}
onDeleteClick={() => handleDeleteClick(selectedWorkspaceForDetails)}
/>
)}
{selectedWorkspaceNames.length > 1 && (
<WorkspaceAggregatedDetails
workspaceNames={selectedWorkspaceNames}
onCloseClick={() => selectAllWorkspaces(false)}
onDeleteClick={() => console.log('Delete selected workspaces')}
/>
)}
</>
);
};

return (
<Drawer
isInline
isExpanded={selectedWorkspace != null && activeActionType === ActionType.ViewDetails}
>
<DrawerContent panelContent={workspaceDetailsContent}>
<Drawer isExpanded={selectedWorkspaceNames.length >= 1}>
<DrawerContent panelContent={workspaceDetailsContent()}>
<DrawerContentBody>
<PageSection isFilled>
<Content>
Expand All @@ -562,6 +565,13 @@ export const Workspaces: React.FunctionComponent = () => {
<Thead>
<Tr>
<Th />
<Th
select={{
onSelect: (_event, isSelecting) => selectAllWorkspaces(isSelecting),
isSelected: areAllWorkspacesSelected,
}}
aria-label="Row select"
/>
{Object.values(columnNames).map((columnName, index) => (
<Th
key={`${columnName}-col-name`}
Expand Down Expand Up @@ -589,6 +599,14 @@ export const Workspaces: React.FunctionComponent = () => {
setWorkspaceExpanded(workspace, !isWorkspaceExpanded(workspace)),
}}
/>
<Td
select={{
rowIndex,
onSelect: (_event, isSelecting) =>
setWorkspaceSelected(workspace, isSelecting),
isSelected: isWorkspaceSelected(workspace),
}}
/>
<Td dataLabel={columnNames.redirectStatus}>
{workspaceRedirectStatus[workspace.kind]
? getRedirectStatusIcon(
Expand Down Expand Up @@ -644,9 +662,7 @@ export const Workspaces: React.FunctionComponent = () => {
/>
</Td>
</Tr>
{isWorkspaceExpanded(workspace) && (
<ExpandedWorkspaceRow workspace={workspace} columnNames={columnNames} />
)}
{isWorkspaceExpanded(workspace) && <ExpandedWorkspaceRow workspace={workspace} />}
</Tbody>
))}
</Table>
Expand Down