Skip to content

Commit

Permalink
change: add confirmation dialog for trashcan
Browse files Browse the repository at this point in the history
  • Loading branch information
daniele-mng committed Sep 25, 2024
1 parent b63396f commit c823047
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 25 deletions.
2 changes: 2 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 empty the trash?": "Sind Sie sicher, dass Sie den Pap,ierkorb leeren möchten?",
"An error occurred while emptying the trash, please try again.": "Beim Leeren des Papierkorbs ist ein Fehler aufgetreten, bitte versuchen Sie es erneut.",
"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
7 changes: 6 additions & 1 deletion src/web/components/dialog/confirmationdialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const ConfirmationDialogContent = props => {
}
};

const {content, moveprops, title, rightButtonTitle} = props;
const {content, moveprops, title, rightButtonTitle, loading} = props;

return (
<DialogContent>
Expand All @@ -38,6 +38,7 @@ const ConfirmationDialogContent = props => {
rightButtonTitle={rightButtonTitle}
onLeftButtonClick={props.close}
onRightButtonClick={handleResume}
loading={loading}
/>
</DialogContent>
);
Expand All @@ -50,6 +51,7 @@ ConfirmationDialogContent.propTypes = {
rightButtonTitle: PropTypes.string,
title: PropTypes.string.isRequired,
onResumeClick: PropTypes.func.isRequired,
loading: PropTypes.bool,
};

const ConfirmationDialog = ({
Expand All @@ -59,6 +61,7 @@ const ConfirmationDialog = ({
rightButtonTitle = _('OK'),
onClose,
onResumeClick,
loading,
}) => {
return (
<Dialog width={width} onClose={onClose} resizable={false}>
Expand All @@ -70,6 +73,7 @@ const ConfirmationDialog = ({
title={title}
rightButtonTitle={rightButtonTitle}
onResumeClick={onResumeClick}
loading={loading}
/>
)}
</Dialog>
Expand All @@ -83,6 +87,7 @@ ConfirmationDialog.propTypes = {
width: PropTypes.string,
onClose: PropTypes.func.isRequired,
onResumeClick: PropTypes.func.isRequired,
loading: PropTypes.bool,
};

export default ConfirmationDialog;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ import _ from 'gmp/locale';

import {isDefined} from 'gmp/utils/identity';

import ErrorDialog from 'web/components/dialog/errordialog';

import LoadingButton from 'web/components/form/loadingbutton';
import Button from 'web/components/form/button';

import ManualIcon from 'web/components/icon/manualicon';
import TrashcanIcon from 'web/components/icon/trashcanicon';
Expand Down Expand Up @@ -65,6 +63,7 @@ import TasksTable from '../tasks/table';
import TicketsTable from '../tickets/table';

import TrashActions from './trashactions';
import ConfirmationDialog from 'web/components/dialog/confirmationdialog';

const Col = styled.col`
width: 50%;
Expand All @@ -78,23 +77,20 @@ const ToolBarIcons = () => (
/>
);

const EmptyTrashButton = ({onClick, isLoading}) => {
const EmptyTrashButton = ({onClick}) => {
const capabilities = useCapabilities();

if (!capabilities.mayOp('empty_trashcan')) {
return null;
}
return (
<Layout align="end">
<LoadingButton onClick={onClick} isLoading={isLoading}>
{_('Empty Trash')}
</LoadingButton>
<Button onClick={onClick}>{_('Empty Trash')}</Button>
</Layout>
);
};

EmptyTrashButton.propTypes = {
isLoading: PropTypes.bool.isRequired,
onClick: PropTypes.func.isRequired,
};

Expand All @@ -119,6 +115,8 @@ class Trashcan extends React.Component {
this.state = {
trash: undefined,
isLoading: false,
isEmptyTrashDialogVisible: false,
isEmptyingTrash: false,
};

this.createContentRow = this.createContentRow.bind(this);
Expand All @@ -128,6 +126,8 @@ class Trashcan extends React.Component {
this.handleDelete = this.handleDelete.bind(this);
this.handleRestore = this.handleRestore.bind(this);
this.handleErrorClose = this.handleErrorClose.bind(this);
this.closeEmptyTrashDialog = this.closeEmptyTrashDialog.bind(this);
this.openEmptyTrashDialog = this.openEmptyTrashDialog.bind(this);
}

componentDidMount() {
Expand Down Expand Up @@ -187,19 +187,27 @@ class Trashcan extends React.Component {
});
}

handleEmpty() {
async handleEmpty() {
const {gmp} = this.props;

this.handleInteraction();

gmp.trashcan
.empty()
.then(this.getTrash)
.catch(error => {
this.setState({
error,
this.setState({isEmptyingTrash: true});

try {
await gmp.trashcan.empty();
this.getTrash();
} catch (error) {
this.setState({error});
} finally {
setTimeout(() => {
this.setState({isEmptyingTrash: false}, () => {
if (!this.state.isLoading && !this.state.error) {
this.closeEmptyTrashDialog();
}
});
});
}, 1000);
}
}

handleErrorClose() {
Expand All @@ -219,6 +227,14 @@ class Trashcan extends React.Component {
);
}

openEmptyTrashDialog = () => {
this.setState({isEmptyTrashDialogVisible: true});
};

closeEmptyTrashDialog = () => {
this.setState({isEmptyTrashDialogVisible: false});
};

createContentsTable(trash) {
const render_alerts = isDefined(trash.alert_list);
const render_credentials = isDefined(trash.credential_list);
Expand Down Expand Up @@ -375,15 +391,26 @@ class Trashcan extends React.Component {
{/* span prevents Toolbar from growing */}
<ToolBarIcons />
</span>
{error && (
<ErrorDialog
text={error.message}
title={_('Error')}
onClose={this.handleErrorClose}

<Section img={<TrashcanIcon size="large" />} title={_('Trashcan')} />
<EmptyTrashButton onClick={this.openEmptyTrashDialog} />
{this.state.isEmptyTrashDialogVisible && (
<ConfirmationDialog
onClose={this.closeEmptyTrashDialog}
onResumeClick={this.handleEmpty}
content={
error
? _(
'An error occurred while emptying the trash, please try again.',
)
: _('Are you sure you want to empty the trash?')
}
title={_('Empty Trash')}
rightButtonTitle={_('Confirm')}
loading={this.state.isEmptyingTrash || isLoading}
width="500px"
/>
)}
<Section img={<TrashcanIcon size="large" />} title={_('Trashcan')} />
<EmptyTrashButton onClick={this.handleEmpty} isLoading={isLoading} />
<LinkTarget id="Contents" />
<h1>{_('Contents')}</h1>
<Table>
Expand Down
153 changes: 153 additions & 0 deletions src/web/pages/extras/__tests__/TrashcanPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/* SPDX-FileCopyrightText: 2024 Greenbone AG
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import {describe, test, expect, testing} from '@gsa/testing';

import {userEvent, rendererWith, waitFor} from 'web/utils/testing';

import Trashcanpage from 'web/pages/extras/TrashcanPage';
import Capabilities from 'gmp/capabilities/capabilities';

const gmp = {
trashcan: {
empty: testing.fn().mockResolvedValueOnce(),
get: testing.fn().mockReturnValue(
Promise.resolve({
data: [],
}),
),
},
settings: {
manualUrl: 'http://docs.greenbone.net/GSM-Manual/gos-5/',
},
user: {
renewSession: testing.fn().mockReturnValue(
Promise.resolve({
data: 'foo',
}),
),
},
};

const capabilities = new Capabilities(['everything']);

describe('Trashcan page tests', () => {
test('Should render with empty trashcan button and empty out trash', async () => {
const {render} = rendererWith({
gmp,
capabilities,
store: true,
});

const {getByText, queryByTestId, getByRole} = render(<Trashcanpage />);
expect(queryByTestId('loading')).toBeVisible();
await waitFor(() => {
expect(queryByTestId('loading')).not.toBeInTheDocument();
});
const emptyTrashcanButton = getByRole('button', {
name: /Empty Trash/i,
});

userEvent.click(emptyTrashcanButton);
await waitFor(() => {
expect(
getByText('Are you sure you want to empty the trash?'),
).toBeVisible();
});

const confirmButton = getByRole('button', {name: /Confirm/i});

userEvent.click(confirmButton);

await waitFor(() => {
expect(gmp.trashcan.empty).toHaveBeenCalled();
});

await waitFor(() => {
expect(confirmButton).not.toBeVisible();
});
});

test('Should render with empty trashcan button and handle error case', async () => {
const errorGmp = {
...gmp,
trashcan: {
...gmp.trashcan,
empty: testing
.fn()
.mockRejectedValue(new Error('Failed to empty trash')),
},
};
const {render} = rendererWith({
gmp: errorGmp,
capabilities,
store: true,
});

const {getByText, queryByTestId, getByRole} = render(<Trashcanpage />);
expect(queryByTestId('loading')).toBeVisible();
await waitFor(() => {
expect(queryByTestId('loading')).not.toBeInTheDocument();
});
const emptyTrashcanButton = getByRole('button', {
name: /Empty Trash/i,
});

userEvent.click(emptyTrashcanButton);
await waitFor(() => {
expect(
getByText('Are you sure you want to empty the trash?'),
).toBeVisible();
});

const confirmButton = getByRole('button', {name: /Confirm/i});

userEvent.click(confirmButton);

await waitFor(() => {
expect(errorGmp.trashcan.empty).toHaveBeenCalled();
});

await waitFor(() => {
expect(
getByText(
'An error occurred while emptying the trash, please try again.',
),
).toBeVisible();
});
});

test('Should render open and close dialog', async () => {
const {render} = rendererWith({
gmp,
capabilities,
store: true,
});

const {getByText, queryByTestId, getByRole} = render(<Trashcanpage />);
expect(queryByTestId('loading')).toBeVisible();
await waitFor(() => {
expect(queryByTestId('loading')).not.toBeInTheDocument();
});
const emptyTrashcanButton = getByRole('button', {
name: /Empty Trash/i,
});

userEvent.click(emptyTrashcanButton);
await waitFor(() => {
expect(
getByText('Are you sure you want to empty the trash?'),
).toBeVisible();
});

const cancelButton = getByRole('button', {name: /Cancel/i});

userEvent.click(cancelButton);

await waitFor(() => {
expect(cancelButton).not.toBeVisible();
});
});
});
2 changes: 1 addition & 1 deletion src/web/routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ import TicketsPage from './pages/tickets/listpage';
import TicketDetailsPage from './pages/tickets/detailspage';
import TlsCertificatesPage from './pages/tlscertificates/listpage';
import TlsCertificateDetailsPage from './pages/tlscertificates/detailspage';
import TrashcanPage from './pages/extras/trashcanpage';
import TrashcanPage from './pages/extras/TrashcanPage';
import UserDetailsPage from './pages/users/detailspage';
import UserSettingsPage from './pages/usersettings/usersettingspage';
import UsersPage from './pages/users/listpage';
Expand Down

0 comments on commit c823047

Please sign in to comment.