Skip to content

Commit

Permalink
Merge pull request #2594 from farodin91/row-selection
Browse files Browse the repository at this point in the history
frontend: allow multiple deletes on ResourceTable
  • Loading branch information
joaquimrocha authored Dec 11, 2024
2 parents aae7687 + 9f374bc commit 950b977
Show file tree
Hide file tree
Showing 41 changed files with 3,476 additions and 32 deletions.
4 changes: 2 additions & 2 deletions frontend/src/components/common/ConfirmDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import MuiDialog, { DialogProps as MuiDialogProps } from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import React from 'react';
import React, { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import { DialogTitle } from './Dialog';

export interface ConfirmDialogProps extends MuiDialogProps {
title: string;
description: string;
description: ReactNode;
onConfirm: () => void;
handleClose: () => void;
}
Expand Down
109 changes: 109 additions & 0 deletions frontend/src/components/common/Resource/DeleteMultipleButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import _ from 'lodash';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { KubeObject } from '../../../lib/k8s/KubeObject';
import { CallbackActionOptions, clusterAction } from '../../../redux/clusterActionSlice';
import {
EventStatus,
HeadlampEventType,
useEventCallback,
} from '../../../redux/headlampEventSlice';
import { AppDispatch } from '../../../redux/stores/store';
import ActionButton, { ButtonStyle } from '../ActionButton';
import { ConfirmDialog } from '../Dialog';

interface DeleteMultipleButtonProps {
items?: KubeObject[];
options?: CallbackActionOptions;
buttonStyle?: ButtonStyle;
afterConfirm?: () => void;
}

interface DeleteMultipleButtonDescriptionProps {
items?: KubeObject[];
}

function DeleteMultipleButtonDescription(props: DeleteMultipleButtonDescriptionProps) {
const { t } = useTranslation(['translation']);
return (
<p>
{t('Are you sure you want to delete following items?')}
<ul>
{props.items?.map(item => (
<li key={item.metadata.uid}>{item.metadata.name}</li>
))}
</ul>
</p>
);
}

export default function DeleteMultipleButton(props: DeleteMultipleButtonProps) {
const dispatch: AppDispatch = useDispatch();
const { items, options, afterConfirm, buttonStyle } = props;
const [openAlert, setOpenAlert] = React.useState(false);
const { t } = useTranslation(['translation']);
const dispatchDeleteEvent = useEventCallback(HeadlampEventType.DELETE_RESOURCES);

const deleteFunc = React.useCallback(
(items: KubeObject[]) => {
if (!items || items.length === 0) {
return;
}
const clonedItems = _.cloneDeep(items);
const itemsLength = clonedItems.length;

dispatch(
clusterAction(
async () => {
await Promise.all(items.map(item => item.delete()));
},
{
startMessage: t('Deleting {{ itemsLength }} items…', { itemsLength }),
cancelledMessage: t('Cancelled deletion of {{ itemsLength }} items.', { itemsLength }),
successMessage: t('Deleted {{ itemsLength }} items.', { itemsLength }),
errorMessage: t('Error deleting {{ itemsLength }} items.', { itemsLength }),
cancelUrl: location.pathname,
startUrl: location.pathname,
errorUrl: location.pathname,
...options,
}
)
);
},
[options]
);

if (!items || items.length === 0) {
return null;
}

return (
<>
<ActionButton
description={t('translation|Delete items')}
buttonStyle={buttonStyle}
onClick={() => {
setOpenAlert(true);
}}
icon="mdi:delete"
/>
<ConfirmDialog
open={openAlert}
title={t('translation|Delete items')}
description={<DeleteMultipleButtonDescription items={items} />}
handleClose={() => setOpenAlert(false)}
onConfirm={() => {
deleteFunc(items);
dispatchDeleteEvent({
resources: items,
status: EventStatus.CONFIRMED,
});
if (afterConfirm) {
afterConfirm();
}
}}
/>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default function ResourceListView(
)
}
>
<ResourceTable enableRowActions {...tableProps} />
<ResourceTable enableRowActions enableRowSelection {...tableProps} />
{children}
</SectionBox>
);
Expand Down
24 changes: 23 additions & 1 deletion frontend/src/components/common/Resource/ResourceTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MenuItem, TableCellProps } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { MRT_FilterFns, MRT_Row, MRT_SortingFn } from 'material-react-table';
import { MRT_FilterFns, MRT_Row, MRT_SortingFn, MRT_TableInstance } from 'material-react-table';
import { ComponentProps, ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import helpers from '../../../helpers';
Expand All @@ -20,6 +20,7 @@ import Link from '../Link';
import Table, { TableColumn } from '../Table';
import DeleteButton from './DeleteButton';
import EditButton from './EditButton';
import ResourceTableMultiActions from './ResourceTableMultiActions';
import { RestartButton } from './RestartButton';
import ScaleButton from './ScaleButton';
import ViewButton from './ViewButton';
Expand Down Expand Up @@ -84,6 +85,8 @@ export interface ResourceTableProps<RowItem> {
columns: (ResourceTableColumn<RowItem> | ColumnType)[];
/** Show or hide row actions @default false*/
enableRowActions?: boolean;
/** Show or hide row selections and actions @default false*/
enableRowSelection?: boolean;
actions?: null | RowAction[];
/** Provide a list of columns that won't be shown and cannot be turned on */
hideColumns?: string[] | null;
Expand Down Expand Up @@ -253,6 +256,7 @@ function ResourceTableContent<RowItem extends KubeObject>(props: ResourceTablePr
defaultGlobalFilter,
actions,
enableRowActions = false,
enableRowSelection = false,
} = props;
const { t } = useTranslation(['glossary', 'translation']);
const theme = useTheme();
Expand Down Expand Up @@ -459,6 +463,22 @@ function ResourceTableContent<RowItem extends KubeObject>(props: ResourceTablePr
};
}, [actionsProcessed]);

const wrappedEnableRowSelection = useMemo(() => {
if (import.meta.env.REACT_APP_HEADLAMP_ENABLE_ROW_SELECTION === 'false') {
return false;
}
return enableRowSelection;
}, [enableRowSelection]);

const renderRowSelectionToolbar = useMemo(() => {
if (!wrappedEnableRowSelection) {
return undefined;
}
return ({ table }: { table: MRT_TableInstance<Record<string, any>> }) => (
<ResourceTableMultiActions table={table} />
);
}, [wrappedEnableRowSelection]);

function onColumnsVisibilityChange(updater: any): void {
setColumnVisibility(oldCols => {
const newCols = updater(oldCols);
Expand Down Expand Up @@ -491,6 +511,8 @@ function ResourceTableContent<RowItem extends KubeObject>(props: ResourceTablePr
<Table
enableFullScreenToggle={false}
enableFacetedValues
enableRowSelection={wrappedEnableRowSelection}
renderRowSelectionToolbar={renderRowSelectionToolbar}
errorMessage={errorMessage}
// @todo: once KubeObject is not any we can remove this casting
columns={allColumns as TableColumn<Record<string, any>>[]}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Grid } from '@mui/material';
import { MRT_TableInstance } from 'material-react-table';
import { useCallback } from 'react';
import { KubeObject } from '../../../lib/k8s/KubeObject';
import DeleteMultipleButton from './DeleteMultipleButton';

export interface ResourceTableMultiActionsProps<RowItem extends Record<string, any>> {
table: MRT_TableInstance<RowItem>;
}

export default function ResourceTableMultiActions<RowItem extends Record<string, any>>(
props: ResourceTableMultiActionsProps<RowItem>
) {
const { table } = props;
const items = table.getSelectedRowModel().rows.map(t => t.original as unknown as KubeObject);

const afterConfirm = useCallback(() => {
table.resetRowSelection();
}, [table]);
return (
<Grid item>
<Grid item container alignItems="center" justifyContent="flex-end">
<DeleteMultipleButton items={items} afterConfirm={afterConfirm} />
</Grid>
</Grid>
);
}
Loading

0 comments on commit 950b977

Please sign in to comment.