forked from elastic/kibana
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Security Solution] Allow to remove a host isolation exception from a…
… policy (elastic#121280)
- Loading branch information
Showing
4 changed files
with
351 additions
and
6 deletions.
There are no files selected for viewing
120 changes: 120 additions & 0 deletions
120
...c/management/pages/policy/view/host_isolation_exceptions/components/delete_modal.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; | ||
import { act, waitFor } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
import React from 'react'; | ||
import uuid from 'uuid'; | ||
import { getExceptionListItemSchemaMock } from '../../../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; | ||
import { | ||
AppContextTestRender, | ||
createAppRootMockRenderer, | ||
} from '../../../../../../common/mock/endpoint'; | ||
import { getPolicyHostIsolationExceptionsPath } from '../../../../../common/routing'; | ||
import { updateOneHostIsolationExceptionItem } from '../../../../host_isolation_exceptions/service'; | ||
import { PolicyHostIsolationExceptionsDeleteModal } from './delete_modal'; | ||
|
||
jest.mock('../../../../host_isolation_exceptions/service'); | ||
|
||
const updateOneHostIsolationExceptionItemMock = updateOneHostIsolationExceptionItem as jest.Mock; | ||
|
||
describe('Policy details host isolation exceptions delete modal', () => { | ||
let policyId: string; | ||
let render: () => ReturnType<AppContextTestRender['render']>; | ||
let renderResult: ReturnType<typeof render>; | ||
let history: AppContextTestRender['history']; | ||
let mockedContext: AppContextTestRender; | ||
let exception: ExceptionListItemSchema; | ||
let onCancel: () => void; | ||
|
||
beforeEach(() => { | ||
policyId = uuid.v4(); | ||
mockedContext = createAppRootMockRenderer(); | ||
exception = getExceptionListItemSchemaMock(); | ||
onCancel = jest.fn(); | ||
updateOneHostIsolationExceptionItemMock.mockClear(); | ||
({ history } = mockedContext); | ||
render = () => | ||
(renderResult = mockedContext.render( | ||
<PolicyHostIsolationExceptionsDeleteModal | ||
policyId={policyId} | ||
exception={exception} | ||
onCancel={onCancel} | ||
/> | ||
)); | ||
|
||
act(() => { | ||
history.push(getPolicyHostIsolationExceptionsPath(policyId)); | ||
}); | ||
}); | ||
|
||
it('should render with enabled buttons', () => { | ||
render(); | ||
expect(renderResult.getByTestId('confirmModalCancelButton')).toBeEnabled(); | ||
expect(renderResult.getByTestId('confirmModalConfirmButton')).toBeEnabled(); | ||
}); | ||
|
||
it('should disable the submit button while deleting ', async () => { | ||
updateOneHostIsolationExceptionItemMock.mockImplementation(() => { | ||
return new Promise((resolve) => setImmediate(resolve)); | ||
}); | ||
render(); | ||
const confirmButton = renderResult.getByTestId('confirmModalConfirmButton'); | ||
userEvent.click(confirmButton); | ||
|
||
await waitFor(() => { | ||
expect(confirmButton).toBeDisabled(); | ||
}); | ||
}); | ||
|
||
it('should call the API with the removed policy from the exception tags', async () => { | ||
exception.tags = ['policy:1234', 'policy:4321', `policy:${policyId}`, 'not-a-policy-tag']; | ||
render(); | ||
const confirmButton = renderResult.getByTestId('confirmModalConfirmButton'); | ||
userEvent.click(confirmButton); | ||
|
||
await waitFor(() => { | ||
expect(updateOneHostIsolationExceptionItemMock).toHaveBeenCalledWith( | ||
mockedContext.coreStart.http, | ||
expect.objectContaining({ | ||
id: exception.id, | ||
tags: ['policy:1234', 'policy:4321', 'not-a-policy-tag'], | ||
}) | ||
); | ||
}); | ||
}); | ||
|
||
it('should show a success toast if the operation was success', async () => { | ||
updateOneHostIsolationExceptionItemMock.mockReturnValue('all good'); | ||
render(); | ||
const confirmButton = renderResult.getByTestId('confirmModalConfirmButton'); | ||
userEvent.click(confirmButton); | ||
|
||
await waitFor(() => { | ||
expect(updateOneHostIsolationExceptionItemMock).toHaveBeenCalled(); | ||
}); | ||
|
||
expect(mockedContext.coreStart.notifications.toasts.addSuccess).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should show an error toast if the operation failed', async () => { | ||
const error = new Error('the server is too far away'); | ||
updateOneHostIsolationExceptionItemMock.mockRejectedValue(error); | ||
render(); | ||
const confirmButton = renderResult.getByTestId('confirmModalConfirmButton'); | ||
userEvent.click(confirmButton); | ||
|
||
await waitFor(() => { | ||
expect(updateOneHostIsolationExceptionItemMock).toHaveBeenCalled(); | ||
}); | ||
|
||
expect(mockedContext.coreStart.notifications.toasts.addError).toHaveBeenCalledWith(error, { | ||
title: 'Error while attempt to remove host isolation exception', | ||
}); | ||
}); | ||
}); |
128 changes: 128 additions & 0 deletions
128
...public/management/pages/policy/view/host_isolation_exceptions/components/delete_modal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { EuiCallOut, EuiConfirmModal, EuiSpacer, EuiText } from '@elastic/eui'; | ||
import { i18n } from '@kbn/i18n'; | ||
import { FormattedMessage } from '@kbn/i18n-react'; | ||
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; | ||
import React from 'react'; | ||
import { useMutation, useQueryClient } from 'react-query'; | ||
import { useHttp, useToasts } from '../../../../../../common/lib/kibana'; | ||
import { ServerApiError } from '../../../../../../common/types'; | ||
import { updateOneHostIsolationExceptionItem } from '../../../../host_isolation_exceptions/service'; | ||
|
||
export const PolicyHostIsolationExceptionsDeleteModal = ({ | ||
policyId, | ||
exception, | ||
onCancel, | ||
}: { | ||
policyId: string; | ||
exception: ExceptionListItemSchema; | ||
onCancel: () => void; | ||
}) => { | ||
const toasts = useToasts(); | ||
const http = useHttp(); | ||
const queryClient = useQueryClient(); | ||
|
||
const onDeleteError = (error: ServerApiError) => { | ||
toasts.addError(error as unknown as Error, { | ||
title: i18n.translate( | ||
'xpack.securitySolution.endpoint.policy.hostIsolationExceptions.list.removeDialog.errorToastTitle', | ||
{ | ||
defaultMessage: 'Error while attempt to remove host isolation exception', | ||
} | ||
), | ||
}); | ||
onCancel(); | ||
}; | ||
|
||
const onDeleteSuccess = () => { | ||
queryClient.invalidateQueries(['endpointSpecificPolicies']); | ||
queryClient.invalidateQueries(['hostIsolationExceptions']); | ||
toasts.addSuccess({ | ||
title: i18n.translate( | ||
'xpack.securitySolution.endpoint.policy.hostIsolationExceptions.list.removeDialog.successToastTitle', | ||
{ defaultMessage: 'Successfully removed' } | ||
), | ||
text: i18n.translate( | ||
'xpack.securitySolution.endpoint.policy.hostIsolationExceptions.list.removeDialog.successToastText', | ||
{ | ||
defaultMessage: '"{exception}" has been removed from policy', | ||
values: { exception: exception.name }, | ||
} | ||
), | ||
}); | ||
onCancel(); | ||
}; | ||
|
||
const mutation = useMutation( | ||
async () => { | ||
const modifiedException = { | ||
...exception, | ||
tags: exception.tags.filter((tag) => tag !== `policy:${policyId}`), | ||
}; | ||
return updateOneHostIsolationExceptionItem(http, modifiedException); | ||
}, | ||
{ | ||
onSuccess: onDeleteSuccess, | ||
onError: onDeleteError, | ||
} | ||
); | ||
|
||
const handleModalConfirm = () => { | ||
mutation.mutate(); | ||
}; | ||
|
||
const handleCancel = () => { | ||
if (!mutation.isLoading) { | ||
onCancel(); | ||
} | ||
}; | ||
|
||
return ( | ||
<EuiConfirmModal | ||
onCancel={handleCancel} | ||
onConfirm={handleModalConfirm} | ||
title={i18n.translate( | ||
'xpack.securitySolution.endpoint.policy.hostIsolationExceptions.list.removeDialog.title', | ||
{ defaultMessage: 'Remove host isolation exception from policy' } | ||
)} | ||
cancelButtonText={i18n.translate( | ||
'xpack.securitySolution.endpoint.policy.hostIsolationExceptions.list.removeDialog.cancelLabel', | ||
{ defaultMessage: 'Cancel' } | ||
)} | ||
confirmButtonText={i18n.translate( | ||
'xpack.securitySolution.endpoint.policy.hostIsolationExceptions.list.removeDialog.confirmLabel', | ||
{ | ||
defaultMessage: 'Remove from policy', | ||
} | ||
)} | ||
isLoading={mutation.isLoading} | ||
data-test-subj={'remove-from-policy-dialog'} | ||
> | ||
<EuiCallOut color="warning" iconType="help"> | ||
<p> | ||
<FormattedMessage | ||
id="xpack.securitySolution.endpoint.policy.hostIsolationExceptions.list.removeDialog.messageCallout" | ||
defaultMessage="This host isolation exception will be removed only from this policy and can still be found and managed from the host isolation exceptions page." | ||
/> | ||
</p> | ||
</EuiCallOut> | ||
|
||
<EuiSpacer /> | ||
|
||
<EuiText size="s"> | ||
<p> | ||
<FormattedMessage | ||
id="xpack.securitySolution.endpoint.policy.hostIsolationExceptions.list.removeDialog.message" | ||
defaultMessage="Are you sure you wish to continue?" | ||
/> | ||
</p> | ||
</EuiText> | ||
</EuiConfirmModal> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.