Skip to content

Commit

Permalink
Add confirmation dialog for table bulk trashand delete (#4133)
Browse files Browse the repository at this point in the history
* ADD: confirmation dialog for bulk trashcan or delete

Update footer.jsx

Update footer.jsx

* add prop for genericDialog

* add translation

* fix test

* fix test and add eslint rules
  • Loading branch information
daniele-mng authored Aug 28, 2024
1 parent a8c2435 commit 653d6da
Show file tree
Hide file tree
Showing 18 changed files with 177 additions and 69 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module.exports = {
'plugin:react-hooks/recommended',
'plugin:vitest-globals/recommended',
],
plugins: ['react', 'react-hooks', 'header'],
plugins: ['react', 'react-hooks', 'header', 'vitest'],
settings: {
react: {
version: 'detect',
Expand Down
5 changes: 5 additions & 0 deletions public/locales/gsa-de.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@
"Apply to page contents": "Auf Seiteninhalt anwenden",
"Apply to selection": "Auf Auswahl anwenden",
"Apps": "Apps",
"Are you sure you want to delete all rows in the page of the table? This action cannot be undone.": "Möchten Sie wirklich alle Zeilen auf der Seite der Tabelle löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
"Are you sure you want to move all rows in the page of the table to the trashcan?": "Möchten Sie wirklich alle Zeilen auf der Seite der Tabelle in den Papierkorb verschieben?",
"As a short-cut the following steps will be done for you:": "Als Abkürzung wird folgendes durchgeführt:",
"As soon as the scan progress is beyond 1%, you can already jump to the scan report by clicking on the progress bar in the \"Status\" column and review the results collected so far.": "Sobald der Scanfortschritt 1 % überschritten hat, können Sie über die Statusanzeige in der Spalte \"Status\" auf der Seite \"Aufgaben\" die bereits gesammelten Ergebnisse einsehen.",
"Ascending": "Aufsteigend",
Expand Down Expand Up @@ -337,6 +339,8 @@
"Configs Filter": "Konfigurationen-Filter",
"Configuration": "Konfiguration",
"Confirm": "Bestätigen",
"Confirm Deletion": "Löschung bestätigen",
"Confirm Move to Trashcan": "Verschieben in den Papierkorb bestätigen",
"Confirm deletion of user {{name}}": "Löschen von Benutzer {{name}} bestätigen",
"Confirm deletion of users": "Löschen von Benutzern bestätigen",
"Confirmation does not match new password!": "Bestätigung entspricht nicht dem neuen Passwort!",
Expand Down Expand Up @@ -954,6 +958,7 @@
"Most Vulnerable Operating Systems": "Verwundbarste Betriebssysteme",
"Move all filtered to trashcan": "Gesamte Filterauswahl in den Papierkorb verschieben",
"Move page contents to trashcan": "Seiteninhalt in den Papierkorb verschieben",
"Move to Trashcan": "In den Papierkorb verschieben",
"Move permission to trashcan": "Berechtigung in Papierkorb verschieben",
"Move selection to trashcan": "Auswahl in den Papierkorb verschieben",
"Move {{entity}} to trashcan": "{{entity}} in Papierkorb verschieben",
Expand Down
24 changes: 24 additions & 0 deletions src/web/components/testing.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,27 @@ export const getFileInputs = element => {
element = getElementOrDocument(element);
return element.querySelectorAll('.mantine-FileInput-input');
};

export const testBulkTrashcanDialog = (screen, dialogAction) => {
const dialog = screen.getByRole('dialog');
expect(dialog).toBeVisible();

const buttons = dialog.querySelectorAll('button');
expect(buttons[2]).toHaveTextContent('Move to Trashcan');

fireEvent.click(buttons[2]);

expect(dialogAction).toHaveBeenCalled();
};

export const testBulkDeleteDialog = (screen, dialogAction) => {
const dialog = screen.getByRole('dialog');
expect(dialog).toBeVisible();

const buttons = dialog.querySelectorAll('button');
expect(buttons[2]).toHaveTextContent('Delete');

fireEvent.click(buttons[2]);

expect(dialogAction).toHaveBeenCalled();
};
9 changes: 5 additions & 4 deletions src/web/entities/container.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/


import React from 'react';

import {withRouter} from 'react-router-dom';
Expand Down Expand Up @@ -475,6 +474,7 @@ class EntitiesContainer extends React.Component {
const {
children,
isLoading,
isGenericBulkTrashcanDeleteDialog = true,
onDownload,
showErrorMessage,
showSuccessMessage,
Expand Down Expand Up @@ -519,6 +519,7 @@ class EntitiesContainer extends React.Component {
filter: loadedFilter,
isLoading,
isUpdating,
isGenericBulkTrashcanDeleteDialog,
selectionType: selectionType,
sortBy,
sortDir,
Expand Down Expand Up @@ -587,6 +588,7 @@ EntitiesContainer.propTypes = {
gmpname: PropTypes.string.isRequired,
history: PropTypes.object.isRequired,
isLoading: PropTypes.bool.isRequired,
isGenericBulkTrashcanDeleteDialog: PropTypes.bool,
listExportFileName: PropTypes.string,
loadSettings: PropTypes.func.isRequired,
loadedFilter: PropTypes.filter,
Expand All @@ -604,9 +606,8 @@ EntitiesContainer.propTypes = {
const mapStateToProps = rootState => {
const userDefaultsSelector = getUserSettingsDefaults(rootState);
const username = getUsername(rootState);
const listExportFileName = userDefaultsSelector.getValueByName(
'listexportfilename',
);
const listExportFileName =
userDefaultsSelector.getValueByName('listexportfilename');
return {
listExportFileName,
username,
Expand Down
131 changes: 97 additions & 34 deletions src/web/entities/footer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import React from 'react';

import _ from 'gmp/locale';
import {useState} from 'react';

import Divider from 'web/components/layout/divider';
import IconDivider from 'web/components/layout/icondivider';
import Layout from 'web/components/layout/layout';
import ConfirmationDialog from 'web/components/dialog/confirmationdialog';

import PropTypes from 'web/utils/proptypes';
import SelectionType from 'web/utils/selectiontype';
Expand All @@ -24,6 +23,13 @@ import Select from 'web/components/form/select';
import TableFooter from 'web/components/table/footer';
import TableRow from 'web/components/table/row';

import useTranslation from 'web/hooks/useTranslation';

const DIALOG_TYPES = {
TRASH: 'trash',
DELETE: 'delete',
};

export const EntitiesFooter = ({
actions = true,
children,
Expand All @@ -38,9 +44,46 @@ export const EntitiesFooter = ({
onSelectionTypeChange,
onTagsClick,
onTrashClick,
...props
isGenericBulkTrashcanDeleteDialog,
delete: deleteEntities,
}) => {
const deleteEntities = props.delete;
const [_] = useTranslation();
const [configDialog, setConfigDialog] = useState(undefined);
const [isDialogVisible, setIsDialogVisible] = useState(false);

const onIconClick = (type, propOnAction) => {
if (!isGenericBulkTrashcanDeleteDialog) {
propOnAction();
return;
}

const configMap = {
[DIALOG_TYPES.DELETE]: {
dialogText: _(
'Are you sure you want to delete all rows in the page of the table? This action cannot be undone.',
),
dialogTitle: _('Confirm Deletion'),
dialogButtonTitle: _('Delete'),
dialogFunction: propOnAction,
},
[DIALOG_TYPES.TRASH]: {
dialogText: _(
'Are you sure you want to move all rows in the page of the table to the trashcan?',
),
dialogTitle: _('Confirm Move to Trashcan'),
dialogButtonTitle: _('Move to Trashcan'),
dialogFunction: propOnAction,
},
};
setConfigDialog(configMap[type]);
setIsDialogVisible(true);
};

const closeDialog = () => {
setIsDialogVisible(false);
setConfigDialog(undefined);
};

const selectItems = [
{
value: SelectionType.SELECTION_PAGE_CONTENTS,
Expand All @@ -55,6 +98,7 @@ export const EntitiesFooter = ({
label: _('Apply to all filtered'),
},
];

return (
<TableFooter>
<TableRow>
Expand All @@ -78,13 +122,17 @@ export const EntitiesFooter = ({
)}
{trash && (
<TrashIcon
onClick={onTrashClick}
onClick={() =>
onIconClick(DIALOG_TYPES.TRASH, onTrashClick)
}
selectionType={selectionType}
/>
)}
{deleteEntities && (
<DeleteIcon
onClick={onDeleteClick}
onClick={() =>
onIconClick(DIALOG_TYPES.DELETE, onDeleteClick)
}
selectionType={selectionType}
/>
)}
Expand All @@ -104,6 +152,19 @@ export const EntitiesFooter = ({
)}
</td>
</TableRow>
{isDialogVisible && (
<ConfirmationDialog
onClose={closeDialog}
onResumeClick={() => {
configDialog.dialogFunction();
closeDialog();
}}
content={configDialog.dialogText}
title={configDialog.dialogTitle}
rightButtonTitle={configDialog.dialogButtonTitle}
width="500px"
/>
)}
</TableFooter>
);
};
Expand All @@ -122,39 +183,41 @@ EntitiesFooter.propTypes = {
onSelectionTypeChange: PropTypes.func,
onTagsClick: PropTypes.func,
onTrashClick: PropTypes.func,
children: PropTypes.node,
isGenericBulkTrashcanDeleteDialog: PropTypes.bool,
};

export const withEntitiesFooter = (options = {}) => Component => {
const EntitiesFooterWrapper = ({
onDownloadBulk,
onDeleteBulk,
onTagsBulk,
...props
}) => {
return (
<Component
{...options}
{...props}
onDownloadClick={onDownloadBulk}
onDeleteClick={onDeleteBulk}
onTagsClick={onTagsBulk}
onTrashClick={onDeleteBulk}
/>
);
};
export const withEntitiesFooter =
(options = {}) =>
Component => {
const EntitiesFooterWrapper = ({
onDownloadBulk,
onDeleteBulk,
onTagsBulk,
...props
}) => {
return (
<Component
{...options}
{...props}
onDownloadClick={onDownloadBulk}
onDeleteClick={onDeleteBulk}
onTagsClick={onTagsBulk}
onTrashClick={onDeleteBulk}
/>
);
};

EntitiesFooterWrapper.propTypes = {
onDeleteBulk: PropTypes.func,
onDownloadBulk: PropTypes.func,
onTagsBulk: PropTypes.func,
};
EntitiesFooterWrapper.propTypes = {
onDeleteBulk: PropTypes.func,
onDownloadBulk: PropTypes.func,
onTagsBulk: PropTypes.func,
};

return EntitiesFooterWrapper;
};
return EntitiesFooterWrapper;
};

export const createEntitiesFooter = options =>
withEntitiesFooter(options)(EntitiesFooter);

export default EntitiesFooter;

// vim: set ts=2 sw=2 tw=80:
7 changes: 4 additions & 3 deletions src/web/pages/alerts/__tests__/listpage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
getTableBody,
getTableFooter,
getTextInputs,
testBulkTrashcanDialog,
} from 'web/components/testing';

const caps = new Capabilities(['everything']);
Expand Down Expand Up @@ -261,7 +262,7 @@ describe('Alert listpage tests', () => {
expect(deleteByFilter).not.toHaveBeenCalled();
expect(icons[1]).toHaveAttribute('title', 'Move page contents to trashcan');
await clickElement(icons[1]);
expect(deleteByFilter).toHaveBeenCalled();
testBulkTrashcanDialog(screen, deleteByFilter);
});

test('should allow to bulk action on selected alerts', async () => {
Expand Down Expand Up @@ -342,7 +343,7 @@ describe('Alert listpage tests', () => {
expect(deleteByIds).not.toHaveBeenCalled();
expect(icons[1]).toHaveAttribute('title', 'Move selection to trashcan');
await clickElement(icons[1]);
expect(deleteByIds).toHaveBeenCalled();
testBulkTrashcanDialog(screen, deleteByIds);
});

test('should allow to bulk action on filtered alerts', async () => {
Expand Down Expand Up @@ -418,7 +419,7 @@ describe('Alert listpage tests', () => {
expect(deleteByFilter).not.toHaveBeenCalled();
expect(icons[1]).toHaveAttribute('title', 'Move all filtered to trashcan');
await clickElement(icons[1]);
expect(deleteByFilter).toHaveBeenCalled();
testBulkTrashcanDialog(screen, deleteByFilter);
});
});

Expand Down
6 changes: 4 additions & 2 deletions src/web/pages/audits/__tests__/listpage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ import {entitiesLoadingActions} from 'web/store/entities/audits';
import {loadingActions} from 'web/store/usersettings/defaults/actions';
import {defaultFilterLoadingActions} from 'web/store/usersettings/defaultfilters/actions';

import {rendererWith, fireEvent, wait} from 'web/utils/testing';
import {rendererWith, fireEvent, wait, screen} from 'web/utils/testing';

import AuditPage, {ToolBarIcons} from '../listpage';
import {
clickElement,
getActionItems,
getBulkActionItems,
getTableBody,
testBulkTrashcanDialog,
} from 'web/components/testing';

const lastReport = {
Expand Down Expand Up @@ -210,7 +211,8 @@ describe('AuditPage tests', () => {
expect(deleteByFilter).not.toHaveBeenCalled();
expect(icons[0]).toHaveAttribute('title', 'Move page contents to trashcan');
await clickElement(icons[0]);
expect(deleteByFilter).toHaveBeenCalled();

testBulkTrashcanDialog(screen, deleteByFilter);

expect(exportByFilter).not.toHaveBeenCalled();
expect(icons[1]).toHaveAttribute('title', 'Export page contents');
Expand Down
7 changes: 4 additions & 3 deletions src/web/pages/credentials/__tests__/listpage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
getTableBody,
getTableFooter,
getTextInputs,
testBulkTrashcanDialog,
} from 'web/components/testing';

const credential = Credential.fromElement({
Expand Down Expand Up @@ -223,7 +224,7 @@ describe('CredentialPage tests', () => {
'Move page contents to trashcan',
)[0];
await clickElement(deleteIcon);
expect(deleteByFilter).toHaveBeenCalled();
testBulkTrashcanDialog(screen, deleteByFilter);
});

test('should allow to bulk action on selected credentials', async () => {
Expand Down Expand Up @@ -289,7 +290,7 @@ describe('CredentialPage tests', () => {
// move selected credential to trashcan
const deleteIcon = screen.getAllByTitle('Move selection to trashcan')[0];
await clickElement(deleteIcon);
expect(deleteByIds).toHaveBeenCalled();
testBulkTrashcanDialog(screen, deleteByIds);
});

test('should allow to bulk action on filtered credentials', async () => {
Expand Down Expand Up @@ -350,7 +351,7 @@ describe('CredentialPage tests', () => {
// move all filtered credentials to trashcan
const deleteIcon = screen.getAllByTitle('Move all filtered to trashcan')[0];
await clickElement(deleteIcon);
expect(deleteByFilter).toHaveBeenCalled();
testBulkTrashcanDialog(screen, deleteByFilter);
});
});

Expand Down
Loading

0 comments on commit 653d6da

Please sign in to comment.