Skip to content

Commit

Permalink
frontend: Allow multiple deletes on ResourceTable
Browse files Browse the repository at this point in the history
* allow row selection
* externalize logic

Signed-off-by: farodin91 <[email protected]>
  • Loading branch information
farodin91 committed Dec 2, 2024
1 parent 4ddc587 commit bff6644
Show file tree
Hide file tree
Showing 41 changed files with 4,147 additions and 647 deletions.
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 @@ -45,7 +45,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 @@ -19,6 +19,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 @@ -83,6 +84,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 @@ -252,6 +255,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 @@ -458,6 +462,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 @@ -490,6 +510,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 bff6644

Please sign in to comment.