mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution][Exceptions] Complete List-Header's use-cases (#144383)
* kbn add more tests + enable user to export even when readonly is true * fix test * fix onblur one another field removes required of another * add tests * fix test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
92251b2567
commit
d2c5ce8274
28 changed files with 2374 additions and 512 deletions
|
@ -1,5 +1,66 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ExceptionItemCardComments should not render comments when the length is falsy 1`] = `
|
||||
Object {
|
||||
"asFragment": [Function],
|
||||
"baseElement": <body>
|
||||
<div />
|
||||
</body>,
|
||||
"container": <div />,
|
||||
"debug": [Function],
|
||||
"findAllByAltText": [Function],
|
||||
"findAllByDisplayValue": [Function],
|
||||
"findAllByLabelText": [Function],
|
||||
"findAllByPlaceholderText": [Function],
|
||||
"findAllByRole": [Function],
|
||||
"findAllByTestId": [Function],
|
||||
"findAllByText": [Function],
|
||||
"findAllByTitle": [Function],
|
||||
"findByAltText": [Function],
|
||||
"findByDisplayValue": [Function],
|
||||
"findByLabelText": [Function],
|
||||
"findByPlaceholderText": [Function],
|
||||
"findByRole": [Function],
|
||||
"findByTestId": [Function],
|
||||
"findByText": [Function],
|
||||
"findByTitle": [Function],
|
||||
"getAllByAltText": [Function],
|
||||
"getAllByDisplayValue": [Function],
|
||||
"getAllByLabelText": [Function],
|
||||
"getAllByPlaceholderText": [Function],
|
||||
"getAllByRole": [Function],
|
||||
"getAllByTestId": [Function],
|
||||
"getAllByText": [Function],
|
||||
"getAllByTitle": [Function],
|
||||
"getByAltText": [Function],
|
||||
"getByDisplayValue": [Function],
|
||||
"getByLabelText": [Function],
|
||||
"getByPlaceholderText": [Function],
|
||||
"getByRole": [Function],
|
||||
"getByTestId": [Function],
|
||||
"getByText": [Function],
|
||||
"getByTitle": [Function],
|
||||
"queryAllByAltText": [Function],
|
||||
"queryAllByDisplayValue": [Function],
|
||||
"queryAllByLabelText": [Function],
|
||||
"queryAllByPlaceholderText": [Function],
|
||||
"queryAllByRole": [Function],
|
||||
"queryAllByTestId": [Function],
|
||||
"queryAllByText": [Function],
|
||||
"queryAllByTitle": [Function],
|
||||
"queryByAltText": [Function],
|
||||
"queryByDisplayValue": [Function],
|
||||
"queryByLabelText": [Function],
|
||||
"queryByPlaceholderText": [Function],
|
||||
"queryByRole": [Function],
|
||||
"queryByTestId": [Function],
|
||||
"queryByText": [Function],
|
||||
"queryByTitle": [Function],
|
||||
"rerender": [Function],
|
||||
"unmount": [Function],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`ExceptionItemCardComments should render comments panel closed 1`] = `
|
||||
Object {
|
||||
"asFragment": [Function],
|
||||
|
|
|
@ -13,6 +13,15 @@ import * as i18n from '../translations';
|
|||
|
||||
const comments = mockGetFormattedComments();
|
||||
describe('ExceptionItemCardComments', () => {
|
||||
it('should not render comments when the length is falsy', () => {
|
||||
const wrapper = render(
|
||||
<ExceptionItemCardComments comments={[]} dataTestSubj="ExceptionItemCardCommentsContainer" />
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
expect(wrapper.queryByTestId('ExceptionItemCardCommentsContainer')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render comments panel closed', () => {
|
||||
const wrapper = render(
|
||||
<ExceptionItemCardComments
|
||||
|
|
|
@ -24,6 +24,7 @@ export interface ExceptionItemCardCommentsProps {
|
|||
|
||||
export const ExceptionItemCardComments = memo<ExceptionItemCardCommentsProps>(
|
||||
({ comments, dataTestSubj }) => {
|
||||
if (!comments.length) return null;
|
||||
return (
|
||||
<EuiFlexItem data-test-subj={dataTestSubj}>
|
||||
<EuiAccordion
|
||||
|
|
|
@ -79,7 +79,7 @@ describe('ExceptionItemCardMetaInfo', () => {
|
|||
item={getExceptionListItemSchemaMock()}
|
||||
rules={[
|
||||
{
|
||||
exception_lists: [
|
||||
exception_list: [
|
||||
{
|
||||
id: '123',
|
||||
list_id: 'i_exist',
|
||||
|
@ -98,7 +98,7 @@ describe('ExceptionItemCardMetaInfo', () => {
|
|||
rule_id: 'rule-2',
|
||||
},
|
||||
{
|
||||
exception_lists: [
|
||||
exception_list: [
|
||||
{
|
||||
id: '123',
|
||||
list_id: 'i_exist',
|
||||
|
|
|
@ -1,5 +1,201 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`HeaderMenu should render button icon disabled 1`] = `
|
||||
Object {
|
||||
"asFragment": [Function],
|
||||
"baseElement": <body>
|
||||
<div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiPopover emotion-euiPopover"
|
||||
data-test-subj="Items"
|
||||
>
|
||||
<div
|
||||
class="euiPopover__anchor css-16vtueo-render"
|
||||
>
|
||||
<button
|
||||
aria-label="Header menu Button Icon"
|
||||
class="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall"
|
||||
data-test-subj="ButtonIcon"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="euiButtonIcon__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="boxesHorizontal"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-euiportal="true"
|
||||
>
|
||||
<div
|
||||
data-eui="EuiFocusTrap"
|
||||
>
|
||||
<div
|
||||
aria-describedby="generated-id"
|
||||
aria-live="off"
|
||||
aria-modal="true"
|
||||
class="euiPanel euiPanel--plain euiPanel--paddingSmall euiPopover__panel emotion-euiPanel-grow-m-s-plain-euiPopover__panel"
|
||||
data-popover-panel="true"
|
||||
role="dialog"
|
||||
style="top: 16px; left: -22px; will-change: transform, opacity; z-index: 2000;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="emotion-euiPopoverArrow-bottom"
|
||||
data-popover-arrow="bottom"
|
||||
style="left: 10px; top: 0px;"
|
||||
/>
|
||||
<p
|
||||
class="emotion-euiScreenReaderOnly"
|
||||
id="generated-id"
|
||||
>
|
||||
You are in a dialog. To close this dialog, hit escape.
|
||||
</p>
|
||||
<div>
|
||||
<div
|
||||
class="euiContextMenuPanel"
|
||||
data-test-subj="MenuPanel"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<button
|
||||
class="euiContextMenuItem euiContextMenuItem--small"
|
||||
data-test-subj="ActionItemedit"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiContextMenu__itemLayout"
|
||||
>
|
||||
<span
|
||||
class="euiContextMenu__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="pencil"
|
||||
/>
|
||||
<span
|
||||
class="euiContextMenuItem__text"
|
||||
>
|
||||
Edit detection exception
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="euiContextMenuItem euiContextMenuItem--small euiContextMenuItem-isDisabled"
|
||||
data-test-subj="ActionItemdelete"
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiContextMenu__itemLayout"
|
||||
>
|
||||
<span
|
||||
class="euiContextMenu__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="trash"
|
||||
/>
|
||||
<span
|
||||
class="euiContextMenuItem__text"
|
||||
>
|
||||
Delete detection exception
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>,
|
||||
"container": <div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiPopover emotion-euiPopover"
|
||||
data-test-subj="Items"
|
||||
>
|
||||
<div
|
||||
class="euiPopover__anchor css-16vtueo-render"
|
||||
>
|
||||
<button
|
||||
aria-label="Header menu Button Icon"
|
||||
class="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall"
|
||||
data-test-subj="ButtonIcon"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="euiButtonIcon__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="boxesHorizontal"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
"debug": [Function],
|
||||
"findAllByAltText": [Function],
|
||||
"findAllByDisplayValue": [Function],
|
||||
"findAllByLabelText": [Function],
|
||||
"findAllByPlaceholderText": [Function],
|
||||
"findAllByRole": [Function],
|
||||
"findAllByTestId": [Function],
|
||||
"findAllByText": [Function],
|
||||
"findAllByTitle": [Function],
|
||||
"findByAltText": [Function],
|
||||
"findByDisplayValue": [Function],
|
||||
"findByLabelText": [Function],
|
||||
"findByPlaceholderText": [Function],
|
||||
"findByRole": [Function],
|
||||
"findByTestId": [Function],
|
||||
"findByText": [Function],
|
||||
"findByTitle": [Function],
|
||||
"getAllByAltText": [Function],
|
||||
"getAllByDisplayValue": [Function],
|
||||
"getAllByLabelText": [Function],
|
||||
"getAllByPlaceholderText": [Function],
|
||||
"getAllByRole": [Function],
|
||||
"getAllByTestId": [Function],
|
||||
"getAllByText": [Function],
|
||||
"getAllByTitle": [Function],
|
||||
"getByAltText": [Function],
|
||||
"getByDisplayValue": [Function],
|
||||
"getByLabelText": [Function],
|
||||
"getByPlaceholderText": [Function],
|
||||
"getByRole": [Function],
|
||||
"getByTestId": [Function],
|
||||
"getByText": [Function],
|
||||
"getByTitle": [Function],
|
||||
"queryAllByAltText": [Function],
|
||||
"queryAllByDisplayValue": [Function],
|
||||
"queryAllByLabelText": [Function],
|
||||
"queryAllByPlaceholderText": [Function],
|
||||
"queryAllByRole": [Function],
|
||||
"queryAllByTestId": [Function],
|
||||
"queryAllByText": [Function],
|
||||
"queryAllByTitle": [Function],
|
||||
"queryByAltText": [Function],
|
||||
"queryByDisplayValue": [Function],
|
||||
"queryByLabelText": [Function],
|
||||
"queryByPlaceholderText": [Function],
|
||||
"queryByRole": [Function],
|
||||
"queryByTestId": [Function],
|
||||
"queryByText": [Function],
|
||||
"queryByTitle": [Function],
|
||||
"rerender": [Function],
|
||||
"unmount": [Function],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`HeaderMenu should render button icon with default settings 1`] = `
|
||||
Object {
|
||||
"asFragment": [Function],
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { fireEvent, render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { HeaderMenu } from '.';
|
||||
import { actions } from '../mocks/header.mock';
|
||||
import { actions, actionsWithDisabledDelete } from '../mocks/header.mock';
|
||||
import { getSecurityLinkAction } from '../mocks/security_link_component.mock';
|
||||
|
||||
describe('HeaderMenu', () => {
|
||||
|
@ -21,6 +21,16 @@ describe('HeaderMenu', () => {
|
|||
expect(wrapper.queryByTestId('EmptyButton')).not.toBeInTheDocument();
|
||||
expect(wrapper.queryByTestId('MenuPanel')).not.toBeInTheDocument();
|
||||
});
|
||||
it('should render button icon disabled', () => {
|
||||
const wrapper = render(
|
||||
<HeaderMenu disableActions={false} actions={actionsWithDisabledDelete} />
|
||||
);
|
||||
|
||||
fireEvent.click(wrapper.getByTestId('ButtonIcon'));
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.getByTestId('ActionItemdelete')).toBeDisabled();
|
||||
expect(wrapper.getByTestId('ActionItemedit')).toBeEnabled();
|
||||
});
|
||||
|
||||
it('should render empty button icon with different icon settings', () => {
|
||||
const wrapper = render(
|
||||
|
|
|
@ -24,6 +24,7 @@ interface Action {
|
|||
key: string;
|
||||
icon: string;
|
||||
label: string | boolean;
|
||||
disabled?: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
interface HeaderMenuComponentProps {
|
||||
|
@ -63,6 +64,7 @@ const HeaderMenuComponent: FC<HeaderMenuComponentProps> = ({
|
|||
data-test-subj={`${dataTestSubj || ''}ActionItem${action.key}`}
|
||||
key={action.key}
|
||||
icon={action.icon}
|
||||
disabled={action.disabled}
|
||||
layoutAlign="center"
|
||||
onClick={() => {
|
||||
onClosePopover();
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -57,13 +57,14 @@ Object {
|
|||
id="modalForm_generated-id"
|
||||
>
|
||||
<div
|
||||
class="euiFormRow euiFormRow--hasLabel"
|
||||
class="euiFormRow euiFormRow--fullWidth euiFormRow--hasLabel"
|
||||
id="generated-id-row"
|
||||
>
|
||||
<div
|
||||
class="euiFormRow__labelWrapper"
|
||||
>
|
||||
<label
|
||||
aria-invalid="false"
|
||||
class="euiFormLabel euiFormRow__label"
|
||||
for="generated-id"
|
||||
id="generated-id-label"
|
||||
|
@ -75,13 +76,14 @@ Object {
|
|||
class="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<div
|
||||
class="euiFormControlLayout"
|
||||
class="euiFormControlLayout euiFormControlLayout--fullWidth"
|
||||
>
|
||||
<div
|
||||
class="euiFormControlLayout__childrenWrapper"
|
||||
>
|
||||
<input
|
||||
class="euiFieldText"
|
||||
aria-invalid="false"
|
||||
class="euiFieldText euiFieldText--fullWidth"
|
||||
data-test-subj="editModalNameTextField"
|
||||
id="generated-id"
|
||||
name="name"
|
||||
|
@ -93,7 +95,7 @@ Object {
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFormRow euiFormRow--hasLabel"
|
||||
class="euiFormRow euiFormRow--fullWidth euiFormRow--hasLabel"
|
||||
id="generated-id-row"
|
||||
>
|
||||
<div
|
||||
|
@ -110,22 +112,15 @@ Object {
|
|||
<div
|
||||
class="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<div
|
||||
class="euiFormControlLayout"
|
||||
<textarea
|
||||
class="euiTextArea euiTextArea--resizeVertical euiTextArea--fullWidth"
|
||||
data-test-subj="editModalDescriptionTextField"
|
||||
id="generated-id"
|
||||
name="description"
|
||||
rows="6"
|
||||
>
|
||||
<div
|
||||
class="euiFormControlLayout__childrenWrapper"
|
||||
>
|
||||
<input
|
||||
class="euiFieldText"
|
||||
data-test-subj="editModalDescriptionTextField"
|
||||
id="generated-id"
|
||||
name="description"
|
||||
type="text"
|
||||
value="list description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
list description
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -7,12 +7,14 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import * as i18n from '../../translations';
|
||||
import { EditModal } from '.';
|
||||
|
||||
const onSave = jest.fn();
|
||||
const onCancel = jest.fn();
|
||||
|
||||
describe('EditModal', () => {
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
it('should render the title and description from listDetails', () => {
|
||||
const wrapper = render(
|
||||
<EditModal
|
||||
|
@ -24,7 +26,7 @@ describe('EditModal', () => {
|
|||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.getByTestId('editModalTitle')).toHaveTextContent('list name');
|
||||
});
|
||||
it('should call onSave', () => {
|
||||
it('should call onSave when submitting the form', () => {
|
||||
const wrapper = render(
|
||||
<EditModal
|
||||
listDetails={{ name: 'list name', description: 'list description' }}
|
||||
|
@ -33,8 +35,10 @@ describe('EditModal', () => {
|
|||
/>
|
||||
);
|
||||
fireEvent.submit(wrapper.getByTestId('editModalForm'));
|
||||
expect(wrapper.getByTestId('editModalProgess')).toBeInTheDocument();
|
||||
expect(onSave).toBeCalled();
|
||||
});
|
||||
|
||||
it('should call onCancel', () => {
|
||||
const wrapper = render(
|
||||
<EditModal
|
||||
|
@ -47,7 +51,7 @@ describe('EditModal', () => {
|
|||
expect(onCancel).toBeCalled();
|
||||
});
|
||||
|
||||
it('should call change title, description and call onSave with the new props', () => {
|
||||
it('should change title, description and call onSave with the new props', () => {
|
||||
const wrapper = render(
|
||||
<EditModal
|
||||
listDetails={{ name: 'list name', description: 'list description' }}
|
||||
|
@ -68,4 +72,86 @@ describe('EditModal', () => {
|
|||
description: 'New description name',
|
||||
});
|
||||
});
|
||||
it('should trim title, description before calling onSave', () => {
|
||||
const wrapper = render(
|
||||
<EditModal
|
||||
listDetails={{ name: ' list name', description: 'list description ' }}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
);
|
||||
fireEvent.change(wrapper.getByTestId('editModalNameTextField'), {
|
||||
target: { value: 'New list name' },
|
||||
});
|
||||
fireEvent.change(wrapper.getByTestId('editModalDescriptionTextField'), {
|
||||
target: { value: 'New description name' },
|
||||
});
|
||||
fireEvent.submit(wrapper.getByTestId('editModalForm'));
|
||||
|
||||
expect(onSave).toBeCalledWith({
|
||||
name: 'New list name',
|
||||
description: 'New description name',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not call onSave when submitting the form with invalid name field', () => {
|
||||
const wrapper = render(
|
||||
<EditModal
|
||||
listDetails={{ name: 'list name', description: 'list description' }}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
);
|
||||
const nameField = wrapper.getByTestId('editModalNameTextField');
|
||||
fireEvent.change(nameField, {
|
||||
target: { value: '' },
|
||||
});
|
||||
fireEvent.blur(nameField);
|
||||
expect(nameField).toBeInvalid();
|
||||
expect(wrapper.queryByTestId('editModalProgess')).not.toBeInTheDocument();
|
||||
fireEvent.submit(wrapper.getByTestId('editModalForm'));
|
||||
expect(onSave).not.toBeCalled();
|
||||
expect(wrapper.getByText(i18n.LIST_NAME_REQUIRED_ERROR)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not call onSave when submitting the form when name field is invalid even after changing the description', () => {
|
||||
const wrapper = render(
|
||||
<EditModal
|
||||
listDetails={{ name: 'list name', description: 'list description' }}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
);
|
||||
const nameField = wrapper.getByTestId('editModalNameTextField');
|
||||
fireEvent.change(nameField, {
|
||||
target: { value: ' ' },
|
||||
});
|
||||
fireEvent.blur(nameField);
|
||||
expect(nameField).toBeInvalid();
|
||||
|
||||
const descriptionField = wrapper.getByTestId('editModalDescriptionTextField');
|
||||
fireEvent.change(descriptionField, {
|
||||
target: { value: 'new description' },
|
||||
});
|
||||
fireEvent.blur(descriptionField);
|
||||
expect(nameField).toBeInvalid();
|
||||
expect(descriptionField).toBeValid();
|
||||
|
||||
expect(wrapper.queryByTestId('editModalProgess')).not.toBeInTheDocument();
|
||||
fireEvent.submit(wrapper.getByTestId('editModalForm'));
|
||||
expect(onSave).not.toBeCalled();
|
||||
expect(wrapper.getByText(i18n.LIST_NAME_REQUIRED_ERROR)).toBeTruthy();
|
||||
});
|
||||
it('should call onCanel when clicking on close button', () => {
|
||||
const wrapper = render(
|
||||
<EditModal
|
||||
listDetails={{ name: 'list name', description: 'list description' }}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
);
|
||||
const closeButton = wrapper.getByLabelText('Closes this modal window');
|
||||
fireEvent.click(closeButton);
|
||||
expect(onCancel).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import React, { ChangeEvent, FC, useState } from 'react';
|
||||
import React, { FC } from 'react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
|
@ -17,10 +17,12 @@ import {
|
|||
EuiModalFooter,
|
||||
EuiModalHeader,
|
||||
EuiModalHeaderTitle,
|
||||
useGeneratedHtmlId,
|
||||
EuiTextArea,
|
||||
EuiProgress,
|
||||
} from '@elastic/eui';
|
||||
import * as i18n from '../../translations';
|
||||
import { ListDetails } from '../../types';
|
||||
import { useEditModal } from './use_edit_modal';
|
||||
|
||||
interface EditModalProps {
|
||||
listDetails: ListDetails;
|
||||
|
@ -29,18 +31,16 @@ interface EditModalProps {
|
|||
}
|
||||
|
||||
const EditModalComponent: FC<EditModalProps> = ({ listDetails, onSave, onCancel }) => {
|
||||
const modalFormId = useGeneratedHtmlId({ prefix: 'modalForm' });
|
||||
const [newListDetails, setNewListDetails] = useState(listDetails);
|
||||
|
||||
const onChange = ({ target }: ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = target;
|
||||
setNewListDetails({ ...newListDetails, [name]: value });
|
||||
};
|
||||
const onSubmit = () => {
|
||||
onSave(newListDetails);
|
||||
};
|
||||
const { error, modalFormId, newListDetails, showProgress, onBlur, onSubmit, onChange } =
|
||||
useEditModal({
|
||||
listDetails,
|
||||
onSave,
|
||||
});
|
||||
return (
|
||||
<EuiModal data-test-subj="EditModal" onClose={onSubmit} initialFocus="[name=popswitch]">
|
||||
<EuiModal data-test-subj="EditModal" onClose={onCancel} initialFocus="[name=popswitch]">
|
||||
{showProgress && (
|
||||
<EuiProgress data-test-subj="editModalProgess" size="xs" position="absolute" />
|
||||
)}
|
||||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle data-test-subj="editModalTitle">
|
||||
<h1>{i18n.EXCEPTION_LIST_HEADER_EDIT_MODAL_TITLE(listDetails.name)}</h1>
|
||||
|
@ -54,8 +54,16 @@ const EditModalComponent: FC<EditModalProps> = ({ listDetails, onSave, onCancel
|
|||
component="form"
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<EuiFormRow label={i18n.EXCEPTION_LIST_HEADER_NAME_TEXTBOX}>
|
||||
<EuiFormRow
|
||||
error={error}
|
||||
isInvalid={!!error}
|
||||
fullWidth
|
||||
label={i18n.EXCEPTION_LIST_HEADER_NAME_TEXTBOX}
|
||||
>
|
||||
<EuiFieldText
|
||||
fullWidth
|
||||
isInvalid={!!error}
|
||||
onBlur={onBlur}
|
||||
data-test-subj="editModalNameTextField"
|
||||
name="name"
|
||||
value={newListDetails.name}
|
||||
|
@ -63,12 +71,14 @@ const EditModalComponent: FC<EditModalProps> = ({ listDetails, onSave, onCancel
|
|||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiFormRow label={i18n.EXCEPTION_LIST_HEADER_DESCRIPTION_TEXTBOX}>
|
||||
<EuiFieldText
|
||||
<EuiFormRow fullWidth label={i18n.EXCEPTION_LIST_HEADER_DESCRIPTION_TEXTBOX}>
|
||||
<EuiTextArea
|
||||
fullWidth
|
||||
data-test-subj="editModalDescriptionTextField"
|
||||
name="description"
|
||||
value={newListDetails.description}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { ChangeEvent, SyntheticEvent } from 'react';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { useEditModal } from './use_edit_modal';
|
||||
|
||||
const listDetails = { name: 'test-name', description: 'test-description' };
|
||||
const onSave = jest.fn();
|
||||
describe('useEditModal', () => {
|
||||
it('should return default values based on input', () => {
|
||||
const { result } = renderHook(() => useEditModal({ listDetails, onSave }));
|
||||
const { error, newListDetails, showProgress } = result.current;
|
||||
expect(error).toBeFalsy();
|
||||
expect(newListDetails).toStrictEqual({ name: 'test-name', description: 'test-description' });
|
||||
expect(showProgress).toBeFalsy();
|
||||
});
|
||||
it('should set error when required field is empty', () => {
|
||||
const { result } = renderHook(() => useEditModal({ listDetails: { name: 'name' }, onSave }));
|
||||
const { showProgress, onBlur } = result.current;
|
||||
|
||||
act(() =>
|
||||
onBlur({ target: { name: 'name', value: '' } } as unknown as ChangeEvent<HTMLInputElement>)
|
||||
);
|
||||
expect(showProgress).toBeFalsy();
|
||||
expect(result.current.newListDetails).toStrictEqual({ name: '' });
|
||||
expect(result.current.error).toBeTruthy();
|
||||
});
|
||||
it('should call onSubmit if no errors and stop the event default', () => {
|
||||
const { result } = renderHook(() => useEditModal({ listDetails, onSave }));
|
||||
const { error, onSubmit } = result.current;
|
||||
|
||||
const preventDefault = jest.fn();
|
||||
act(() => onSubmit({ preventDefault } as unknown as SyntheticEvent));
|
||||
expect(error).toBeFalsy();
|
||||
expect(onSave).toBeCalled();
|
||||
expect(preventDefault).toBeCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { useGeneratedHtmlId } from '@elastic/eui';
|
||||
import { useState, useCallback, ChangeEvent, SyntheticEvent } from 'react';
|
||||
import * as i18n from '../../translations';
|
||||
import { ListDetails } from '../../types';
|
||||
|
||||
interface UseEditModal {
|
||||
listDetails: ListDetails;
|
||||
onSave: (newListDetails: ListDetails) => void;
|
||||
}
|
||||
|
||||
export const useEditModal = ({ listDetails, onSave }: UseEditModal) => {
|
||||
const modalFormId = useGeneratedHtmlId({ prefix: 'modalForm' });
|
||||
const [newListDetails, setNewListDetails] = useState<ListDetails>(listDetails);
|
||||
const [showProgress, setShowProgress] = useState(false);
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
|
||||
const onBlur = useCallback(
|
||||
({ target }: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
|
||||
const { name, value } = target;
|
||||
const trimmedValue = value.trim();
|
||||
setNewListDetails({ ...newListDetails, [name]: trimmedValue });
|
||||
if (name === 'name') setError(!trimmedValue ? i18n.LIST_NAME_REQUIRED_ERROR : undefined);
|
||||
},
|
||||
[newListDetails]
|
||||
);
|
||||
|
||||
const onChange = ({ target }: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const { name, value } = target;
|
||||
setNewListDetails({ ...newListDetails, [name]: value });
|
||||
};
|
||||
|
||||
const onSubmit = (e?: SyntheticEvent) => {
|
||||
if (error) return;
|
||||
setShowProgress(true);
|
||||
onSave(newListDetails);
|
||||
e?.preventDefault();
|
||||
};
|
||||
return {
|
||||
error,
|
||||
modalFormId,
|
||||
newListDetails,
|
||||
showProgress,
|
||||
onBlur,
|
||||
onChange,
|
||||
onSubmit,
|
||||
};
|
||||
};
|
|
@ -10,17 +10,13 @@ import React from 'react';
|
|||
import type { FC } from 'react';
|
||||
import { EuiIcon, EuiPageHeader, EuiText } from '@elastic/eui';
|
||||
import * as i18n from '../translations';
|
||||
import {
|
||||
textWithEditContainerCss,
|
||||
textCss,
|
||||
descriptionContainerCss,
|
||||
headerCss,
|
||||
} from './list_header.styles';
|
||||
import { textCss, descriptionContainerCss, headerCss } from './list_header.styles';
|
||||
import { MenuItems } from './menu_items';
|
||||
import { TextWithEdit } from '../text_with_edit';
|
||||
import { EditModal } from './edit_modal';
|
||||
import { ListDetails, Rule } from '../types';
|
||||
import { useExceptionListHeader } from './use_list_header';
|
||||
import { textWithEditContainerCss } from '../text_with_edit/text_with_edit.styles';
|
||||
|
||||
interface ExceptionListHeaderComponentProps {
|
||||
name: string;
|
||||
|
@ -30,6 +26,7 @@ interface ExceptionListHeaderComponentProps {
|
|||
linkedRules: Rule[];
|
||||
dataTestSubj?: string;
|
||||
breadcrumbLink?: string;
|
||||
canUserEditList?: boolean;
|
||||
securityLinkAnchorComponent: React.ElementType; // This property needs to be removed to avoid the Prop Drilling, once we move all the common components from x-pack/security-solution/common
|
||||
onEditListDetails: (listDetails: ListDetails) => void;
|
||||
onExportList: () => void;
|
||||
|
@ -46,6 +43,7 @@ const ExceptionListHeaderComponent: FC<ExceptionListHeaderComponentProps> = ({
|
|||
dataTestSubj,
|
||||
securityLinkAnchorComponent,
|
||||
breadcrumbLink,
|
||||
canUserEditList = true,
|
||||
onEditListDetails,
|
||||
onExportList,
|
||||
onDeleteList,
|
||||
|
@ -65,7 +63,7 @@ const ExceptionListHeaderComponent: FC<ExceptionListHeaderComponentProps> = ({
|
|||
<TextWithEdit
|
||||
dataTestSubj={`${dataTestSubj || ''}Title`}
|
||||
text={listDetails.name || i18n.EXCEPTION_LIST_HEADER_NAME}
|
||||
isReadonly={isReadonly}
|
||||
isReadonly={isReadonly || !canUserEditList}
|
||||
onEdit={onEdit}
|
||||
/>
|
||||
}
|
||||
|
@ -76,7 +74,7 @@ const ExceptionListHeaderComponent: FC<ExceptionListHeaderComponentProps> = ({
|
|||
<TextWithEdit
|
||||
dataTestSubj={`${dataTestSubj || ''}Description`}
|
||||
textCss={textCss}
|
||||
isReadonly={isReadonly}
|
||||
isReadonly={isReadonly || !canUserEditList}
|
||||
text={listDetails.description || i18n.EXCEPTION_LIST_HEADER_DESCRIPTION}
|
||||
onEdit={onEdit}
|
||||
/>
|
||||
|
@ -91,6 +89,7 @@ const ExceptionListHeaderComponent: FC<ExceptionListHeaderComponentProps> = ({
|
|||
dataTestSubj={`${dataTestSubj || ''}RightSideMenuItems`}
|
||||
linkedRules={linkedRules}
|
||||
isReadonly={isReadonly}
|
||||
canUserEditList={canUserEditList}
|
||||
securityLinkAnchorComponent={securityLinkAnchorComponent}
|
||||
onExportList={onExportList}
|
||||
onDeleteList={onDeleteList}
|
||||
|
|
|
@ -17,21 +17,17 @@ export const headerMenuCss = css`
|
|||
border-right: 1px solid #d3dae6;
|
||||
padding: ${euiThemeVars.euiSizeXS} ${euiThemeVars.euiSizeL} ${euiThemeVars.euiSizeXS} 0;
|
||||
`;
|
||||
export const textWithEditContainerCss = css`
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
align-items: baseline;
|
||||
margin-bottom: ${euiThemeVars.euiSizeS};
|
||||
h1 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
export const noLinkedRulesCss = css`
|
||||
width: max-content;
|
||||
`;
|
||||
|
||||
export const textCss = css`
|
||||
font-size: ${euiThemeVars.euiFontSize};
|
||||
color: ${euiThemeVars.euiTextSubduedColor};
|
||||
margin-left: ${euiThemeVars.euiSizeXS};
|
||||
`;
|
||||
export const descriptionContainerCss = css`
|
||||
margin-top: -${euiThemeVars.euiSizeL};
|
||||
margin-top: -${euiThemeVars.euiSizeXXL};
|
||||
margin-bottom: -${euiThemeVars.euiSizeL};
|
||||
`;
|
||||
|
|
|
@ -27,7 +27,7 @@ describe('ExceptionListHeader', () => {
|
|||
onCancel: jest.fn(),
|
||||
});
|
||||
});
|
||||
it('should render the List Header with name, default description and disabled actions because of the ReadOnly mode', () => {
|
||||
it('should render the List Header with name, default description and disabled actions because of the ReadOnly mode', () => {
|
||||
const wrapper = render(
|
||||
<ExceptionListHeader
|
||||
listId="List_Id"
|
||||
|
@ -42,8 +42,8 @@ describe('ExceptionListHeader', () => {
|
|||
/>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
fireEvent.click(wrapper.getByTestId('RightSideMenuItemsContainer'));
|
||||
expect(wrapper.queryByTestId('MenuActions')).not.toBeInTheDocument();
|
||||
fireEvent.click(wrapper.getByTestId('RightSideMenuItemsMenuActionsItems'));
|
||||
expect(wrapper.queryByTestId('RightSideMenuItemsMenuActionsButtonIcon')).toBeDisabled();
|
||||
expect(wrapper.getByTestId('DescriptionText')).toHaveTextContent(
|
||||
i18n.EXCEPTION_LIST_HEADER_DESCRIPTION
|
||||
);
|
||||
|
@ -55,6 +55,29 @@ describe('ExceptionListHeader', () => {
|
|||
i18n.EXCEPTION_LIST_HEADER_BREADCRUMB
|
||||
);
|
||||
});
|
||||
it('should render the List Header with name, default description and disabled actions because user can not edit details', () => {
|
||||
const wrapper = render(
|
||||
<ExceptionListHeader
|
||||
listId="List_Id"
|
||||
name="List Name"
|
||||
isReadonly={false}
|
||||
canUserEditList={false}
|
||||
linkedRules={[]}
|
||||
securityLinkAnchorComponent={securityLinkAnchorComponentMock}
|
||||
onEditListDetails={onEditListDetails}
|
||||
onExportList={onExportList}
|
||||
onDeleteList={onDeleteList}
|
||||
onManageRules={onManageRules}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.queryByTestId('RightSideMenuItemsMenuActionsButtonIcon')).toBeEnabled();
|
||||
fireEvent.click(wrapper.getByTestId('RightSideMenuItemsMenuActionsButtonIcon'));
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
expect(wrapper.queryByTestId('RightSideMenuItemsMenuActionsActionItem1')).toBeEnabled();
|
||||
expect(wrapper.queryByTestId('RightSideMenuItemsMenuActionsActionItem2')).toBeDisabled();
|
||||
expect(wrapper.queryByTestId('EditTitleIcon')).not.toBeInTheDocument();
|
||||
});
|
||||
it('should render the List Header with name, default description and actions', () => {
|
||||
const wrapper = render(
|
||||
<ExceptionListHeader
|
||||
|
|
|
@ -1,5 +1,795 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`MenuItems should not render linkedRules HeaderMenu component, instead should render a text 1`] = `
|
||||
Object {
|
||||
"asFragment": [Function],
|
||||
"baseElement": <body>
|
||||
<div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsBaseline euiFlexGroup--justifyContentCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="Container"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem emotion-EuiFlexItem"
|
||||
>
|
||||
<span
|
||||
class="emotion-EuiTextColor"
|
||||
data-test-subj="noLinkedRules"
|
||||
>
|
||||
Linked to 0 rules
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<button
|
||||
class="euiButton euiButton--primary euiButton--fill"
|
||||
data-test-subj="ManageRulesButton"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButton__content"
|
||||
>
|
||||
<span
|
||||
class="euiButton__text"
|
||||
>
|
||||
Manage rules
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiPopover emotion-euiPopover"
|
||||
data-test-subj="MenuActionsItems"
|
||||
>
|
||||
<div
|
||||
class="euiPopover__anchor css-16vtueo-render"
|
||||
>
|
||||
<button
|
||||
aria-label="Header menu Button Icon"
|
||||
class="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall"
|
||||
data-test-subj="MenuActionsButtonIcon"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="euiButtonIcon__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="boxesHorizontal"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>,
|
||||
"container": <div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsBaseline euiFlexGroup--justifyContentCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="Container"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem emotion-EuiFlexItem"
|
||||
>
|
||||
<span
|
||||
class="emotion-EuiTextColor"
|
||||
data-test-subj="noLinkedRules"
|
||||
>
|
||||
Linked to 0 rules
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<button
|
||||
class="euiButton euiButton--primary euiButton--fill"
|
||||
data-test-subj="ManageRulesButton"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButton__content"
|
||||
>
|
||||
<span
|
||||
class="euiButton__text"
|
||||
>
|
||||
Manage rules
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiPopover emotion-euiPopover"
|
||||
data-test-subj="MenuActionsItems"
|
||||
>
|
||||
<div
|
||||
class="euiPopover__anchor css-16vtueo-render"
|
||||
>
|
||||
<button
|
||||
aria-label="Header menu Button Icon"
|
||||
class="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall"
|
||||
data-test-subj="MenuActionsButtonIcon"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="euiButtonIcon__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="boxesHorizontal"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
"debug": [Function],
|
||||
"findAllByAltText": [Function],
|
||||
"findAllByDisplayValue": [Function],
|
||||
"findAllByLabelText": [Function],
|
||||
"findAllByPlaceholderText": [Function],
|
||||
"findAllByRole": [Function],
|
||||
"findAllByTestId": [Function],
|
||||
"findAllByText": [Function],
|
||||
"findAllByTitle": [Function],
|
||||
"findByAltText": [Function],
|
||||
"findByDisplayValue": [Function],
|
||||
"findByLabelText": [Function],
|
||||
"findByPlaceholderText": [Function],
|
||||
"findByRole": [Function],
|
||||
"findByTestId": [Function],
|
||||
"findByText": [Function],
|
||||
"findByTitle": [Function],
|
||||
"getAllByAltText": [Function],
|
||||
"getAllByDisplayValue": [Function],
|
||||
"getAllByLabelText": [Function],
|
||||
"getAllByPlaceholderText": [Function],
|
||||
"getAllByRole": [Function],
|
||||
"getAllByTestId": [Function],
|
||||
"getAllByText": [Function],
|
||||
"getAllByTitle": [Function],
|
||||
"getByAltText": [Function],
|
||||
"getByDisplayValue": [Function],
|
||||
"getByLabelText": [Function],
|
||||
"getByPlaceholderText": [Function],
|
||||
"getByRole": [Function],
|
||||
"getByTestId": [Function],
|
||||
"getByText": [Function],
|
||||
"getByTitle": [Function],
|
||||
"queryAllByAltText": [Function],
|
||||
"queryAllByDisplayValue": [Function],
|
||||
"queryAllByLabelText": [Function],
|
||||
"queryAllByPlaceholderText": [Function],
|
||||
"queryAllByRole": [Function],
|
||||
"queryAllByTestId": [Function],
|
||||
"queryAllByText": [Function],
|
||||
"queryAllByTitle": [Function],
|
||||
"queryByAltText": [Function],
|
||||
"queryByDisplayValue": [Function],
|
||||
"queryByLabelText": [Function],
|
||||
"queryByPlaceholderText": [Function],
|
||||
"queryByRole": [Function],
|
||||
"queryByTestId": [Function],
|
||||
"queryByText": [Function],
|
||||
"queryByTitle": [Function],
|
||||
"rerender": [Function],
|
||||
"unmount": [Function],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`MenuItems should render all menu actions enabled 1`] = `
|
||||
Object {
|
||||
"asFragment": [Function],
|
||||
"baseElement": <body>
|
||||
<div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsBaseline euiFlexGroup--justifyContentCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="Container"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem emotion-EuiFlexItem"
|
||||
>
|
||||
<span
|
||||
class="emotion-EuiTextColor"
|
||||
data-test-subj="noLinkedRules"
|
||||
>
|
||||
Linked to 0 rules
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<button
|
||||
class="euiButton euiButton--primary euiButton--fill"
|
||||
data-test-subj="ManageRulesButton"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButton__content"
|
||||
>
|
||||
<span
|
||||
class="euiButton__text"
|
||||
>
|
||||
Manage rules
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiPopover emotion-euiPopover"
|
||||
data-test-subj="MenuActionsItems"
|
||||
>
|
||||
<div
|
||||
class="euiPopover__anchor css-16vtueo-render"
|
||||
>
|
||||
<button
|
||||
aria-label="Header menu Button Icon"
|
||||
class="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall"
|
||||
data-test-subj="MenuActionsButtonIcon"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="euiButtonIcon__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="boxesHorizontal"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-euiportal="true"
|
||||
>
|
||||
<div
|
||||
data-eui="EuiFocusTrap"
|
||||
>
|
||||
<div
|
||||
aria-describedby="generated-id"
|
||||
aria-live="off"
|
||||
aria-modal="true"
|
||||
class="euiPanel euiPanel--plain euiPanel--paddingSmall euiPopover__panel emotion-euiPanel-grow-m-s-plain-euiPopover__panel"
|
||||
data-popover-panel="true"
|
||||
role="dialog"
|
||||
style="top: 16px; left: -22px; will-change: transform, opacity; z-index: 2000;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="emotion-euiPopoverArrow-bottom"
|
||||
data-popover-arrow="bottom"
|
||||
style="left: 10px; top: 0px;"
|
||||
/>
|
||||
<p
|
||||
class="emotion-euiScreenReaderOnly"
|
||||
id="generated-id"
|
||||
>
|
||||
You are in a dialog. To close this dialog, hit escape.
|
||||
</p>
|
||||
<div>
|
||||
<div
|
||||
class="euiContextMenuPanel"
|
||||
data-test-subj="MenuActionsMenuPanel"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<button
|
||||
class="euiContextMenuItem euiContextMenuItem--small"
|
||||
data-test-subj="MenuActionsActionItem1"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiContextMenu__itemLayout"
|
||||
>
|
||||
<span
|
||||
class="euiContextMenu__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="exportAction"
|
||||
/>
|
||||
<span
|
||||
class="euiContextMenuItem__text"
|
||||
>
|
||||
Export exception list
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="euiContextMenuItem euiContextMenuItem--small"
|
||||
data-test-subj="MenuActionsActionItem2"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiContextMenu__itemLayout"
|
||||
>
|
||||
<span
|
||||
class="euiContextMenu__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="trash"
|
||||
/>
|
||||
<span
|
||||
class="euiContextMenuItem__text"
|
||||
>
|
||||
Delete exception list
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>,
|
||||
"container": <div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsBaseline euiFlexGroup--justifyContentCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="Container"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem emotion-EuiFlexItem"
|
||||
>
|
||||
<span
|
||||
class="emotion-EuiTextColor"
|
||||
data-test-subj="noLinkedRules"
|
||||
>
|
||||
Linked to 0 rules
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<button
|
||||
class="euiButton euiButton--primary euiButton--fill"
|
||||
data-test-subj="ManageRulesButton"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButton__content"
|
||||
>
|
||||
<span
|
||||
class="euiButton__text"
|
||||
>
|
||||
Manage rules
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiPopover emotion-euiPopover"
|
||||
data-test-subj="MenuActionsItems"
|
||||
>
|
||||
<div
|
||||
class="euiPopover__anchor css-16vtueo-render"
|
||||
>
|
||||
<button
|
||||
aria-label="Header menu Button Icon"
|
||||
class="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall"
|
||||
data-test-subj="MenuActionsButtonIcon"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="euiButtonIcon__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="boxesHorizontal"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
"debug": [Function],
|
||||
"findAllByAltText": [Function],
|
||||
"findAllByDisplayValue": [Function],
|
||||
"findAllByLabelText": [Function],
|
||||
"findAllByPlaceholderText": [Function],
|
||||
"findAllByRole": [Function],
|
||||
"findAllByTestId": [Function],
|
||||
"findAllByText": [Function],
|
||||
"findAllByTitle": [Function],
|
||||
"findByAltText": [Function],
|
||||
"findByDisplayValue": [Function],
|
||||
"findByLabelText": [Function],
|
||||
"findByPlaceholderText": [Function],
|
||||
"findByRole": [Function],
|
||||
"findByTestId": [Function],
|
||||
"findByText": [Function],
|
||||
"findByTitle": [Function],
|
||||
"getAllByAltText": [Function],
|
||||
"getAllByDisplayValue": [Function],
|
||||
"getAllByLabelText": [Function],
|
||||
"getAllByPlaceholderText": [Function],
|
||||
"getAllByRole": [Function],
|
||||
"getAllByTestId": [Function],
|
||||
"getAllByText": [Function],
|
||||
"getAllByTitle": [Function],
|
||||
"getByAltText": [Function],
|
||||
"getByDisplayValue": [Function],
|
||||
"getByLabelText": [Function],
|
||||
"getByPlaceholderText": [Function],
|
||||
"getByRole": [Function],
|
||||
"getByTestId": [Function],
|
||||
"getByText": [Function],
|
||||
"getByTitle": [Function],
|
||||
"queryAllByAltText": [Function],
|
||||
"queryAllByDisplayValue": [Function],
|
||||
"queryAllByLabelText": [Function],
|
||||
"queryAllByPlaceholderText": [Function],
|
||||
"queryAllByRole": [Function],
|
||||
"queryAllByTestId": [Function],
|
||||
"queryAllByText": [Function],
|
||||
"queryAllByTitle": [Function],
|
||||
"queryByAltText": [Function],
|
||||
"queryByDisplayValue": [Function],
|
||||
"queryByLabelText": [Function],
|
||||
"queryByPlaceholderText": [Function],
|
||||
"queryByRole": [Function],
|
||||
"queryByTestId": [Function],
|
||||
"queryByText": [Function],
|
||||
"queryByTitle": [Function],
|
||||
"rerender": [Function],
|
||||
"unmount": [Function],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`MenuItems should render delete action disabled 1`] = `
|
||||
Object {
|
||||
"asFragment": [Function],
|
||||
"baseElement": <body>
|
||||
<div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsBaseline euiFlexGroup--justifyContentCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="Container"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem emotion-EuiFlexItem"
|
||||
>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiPopover emotion-euiPopover"
|
||||
data-test-subj="LinkedRulesMenuItems"
|
||||
>
|
||||
<div
|
||||
class="euiPopover__anchor css-16vtueo-render"
|
||||
>
|
||||
<button
|
||||
aria-label="Header menu Button Empty"
|
||||
class="euiButtonEmpty euiButtonEmpty--primary"
|
||||
data-test-subj="LinkedRulesMenuEmptyButton"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButtonContent--iconRight euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="arrowDown"
|
||||
/>
|
||||
<span
|
||||
class="euiButtonEmpty__text"
|
||||
>
|
||||
Linked to 1 rules
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<button
|
||||
class="euiButton euiButton--primary euiButton--fill"
|
||||
data-test-subj="ManageRulesButton"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButton__content"
|
||||
>
|
||||
<span
|
||||
class="euiButton__text"
|
||||
>
|
||||
Manage rules
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiPopover emotion-euiPopover"
|
||||
data-test-subj="MenuActionsItems"
|
||||
>
|
||||
<div
|
||||
class="euiPopover__anchor css-16vtueo-render"
|
||||
>
|
||||
<button
|
||||
aria-label="Header menu Button Icon"
|
||||
class="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall"
|
||||
data-test-subj="MenuActionsButtonIcon"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="euiButtonIcon__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="boxesHorizontal"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-euiportal="true"
|
||||
>
|
||||
<div
|
||||
data-eui="EuiFocusTrap"
|
||||
>
|
||||
<div
|
||||
aria-describedby="generated-id"
|
||||
aria-live="off"
|
||||
aria-modal="true"
|
||||
class="euiPanel euiPanel--plain euiPanel--paddingSmall euiPopover__panel emotion-euiPanel-grow-m-s-plain-euiPopover__panel"
|
||||
data-popover-panel="true"
|
||||
role="dialog"
|
||||
style="top: 16px; left: -22px; will-change: transform, opacity; z-index: 2000;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="emotion-euiPopoverArrow-bottom"
|
||||
data-popover-arrow="bottom"
|
||||
style="left: 10px; top: 0px;"
|
||||
/>
|
||||
<p
|
||||
class="emotion-euiScreenReaderOnly"
|
||||
id="generated-id"
|
||||
>
|
||||
You are in a dialog. To close this dialog, hit escape.
|
||||
</p>
|
||||
<div>
|
||||
<div
|
||||
class="euiContextMenuPanel"
|
||||
data-test-subj="MenuActionsMenuPanel"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<button
|
||||
class="euiContextMenuItem euiContextMenuItem--small"
|
||||
data-test-subj="MenuActionsActionItem1"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiContextMenu__itemLayout"
|
||||
>
|
||||
<span
|
||||
class="euiContextMenu__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="exportAction"
|
||||
/>
|
||||
<span
|
||||
class="euiContextMenuItem__text"
|
||||
>
|
||||
Export exception list
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="euiContextMenuItem euiContextMenuItem--small euiContextMenuItem-isDisabled"
|
||||
data-test-subj="MenuActionsActionItem2"
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiContextMenu__itemLayout"
|
||||
>
|
||||
<span
|
||||
class="euiContextMenu__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="trash"
|
||||
/>
|
||||
<span
|
||||
class="euiContextMenuItem__text"
|
||||
>
|
||||
Delete exception list
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>,
|
||||
"container": <div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsBaseline euiFlexGroup--justifyContentCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
data-test-subj="Container"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem emotion-EuiFlexItem"
|
||||
>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiPopover emotion-euiPopover"
|
||||
data-test-subj="LinkedRulesMenuItems"
|
||||
>
|
||||
<div
|
||||
class="euiPopover__anchor css-16vtueo-render"
|
||||
>
|
||||
<button
|
||||
aria-label="Header menu Button Empty"
|
||||
class="euiButtonEmpty euiButtonEmpty--primary"
|
||||
data-test-subj="LinkedRulesMenuEmptyButton"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButtonContent--iconRight euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="arrowDown"
|
||||
/>
|
||||
<span
|
||||
class="euiButtonEmpty__text"
|
||||
>
|
||||
Linked to 1 rules
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<button
|
||||
class="euiButton euiButton--primary euiButton--fill"
|
||||
data-test-subj="ManageRulesButton"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="euiButtonContent euiButton__content"
|
||||
>
|
||||
<span
|
||||
class="euiButton__text"
|
||||
>
|
||||
Manage rules
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
class="euiPopover emotion-euiPopover"
|
||||
data-test-subj="MenuActionsItems"
|
||||
>
|
||||
<div
|
||||
class="euiPopover__anchor css-16vtueo-render"
|
||||
>
|
||||
<button
|
||||
aria-label="Header menu Button Icon"
|
||||
class="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall"
|
||||
data-test-subj="MenuActionsButtonIcon"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="euiButtonIcon__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="boxesHorizontal"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
"debug": [Function],
|
||||
"findAllByAltText": [Function],
|
||||
"findAllByDisplayValue": [Function],
|
||||
"findAllByLabelText": [Function],
|
||||
"findAllByPlaceholderText": [Function],
|
||||
"findAllByRole": [Function],
|
||||
"findAllByTestId": [Function],
|
||||
"findAllByText": [Function],
|
||||
"findAllByTitle": [Function],
|
||||
"findByAltText": [Function],
|
||||
"findByDisplayValue": [Function],
|
||||
"findByLabelText": [Function],
|
||||
"findByPlaceholderText": [Function],
|
||||
"findByRole": [Function],
|
||||
"findByTestId": [Function],
|
||||
"findByText": [Function],
|
||||
"findByTitle": [Function],
|
||||
"getAllByAltText": [Function],
|
||||
"getAllByDisplayValue": [Function],
|
||||
"getAllByLabelText": [Function],
|
||||
"getAllByPlaceholderText": [Function],
|
||||
"getAllByRole": [Function],
|
||||
"getAllByTestId": [Function],
|
||||
"getAllByText": [Function],
|
||||
"getAllByTitle": [Function],
|
||||
"getByAltText": [Function],
|
||||
"getByDisplayValue": [Function],
|
||||
"getByLabelText": [Function],
|
||||
"getByPlaceholderText": [Function],
|
||||
"getByRole": [Function],
|
||||
"getByTestId": [Function],
|
||||
"getByText": [Function],
|
||||
"getByTitle": [Function],
|
||||
"queryAllByAltText": [Function],
|
||||
"queryAllByDisplayValue": [Function],
|
||||
"queryAllByLabelText": [Function],
|
||||
"queryAllByPlaceholderText": [Function],
|
||||
"queryAllByRole": [Function],
|
||||
"queryAllByTestId": [Function],
|
||||
"queryAllByText": [Function],
|
||||
"queryAllByTitle": [Function],
|
||||
"queryByAltText": [Function],
|
||||
"queryByDisplayValue": [Function],
|
||||
"queryByLabelText": [Function],
|
||||
"queryByPlaceholderText": [Function],
|
||||
"queryByRole": [Function],
|
||||
"queryByTestId": [Function],
|
||||
"queryByText": [Function],
|
||||
"queryByTitle": [Function],
|
||||
"rerender": [Function],
|
||||
"unmount": [Function],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`MenuItems should render linkedRules, manageRules and menuActions 1`] = `
|
||||
Object {
|
||||
"asFragment": [Function],
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiTextColor } from '@elastic/eui';
|
||||
import React, { FC, useMemo } from 'react';
|
||||
import { HeaderMenu } from '../../header_menu';
|
||||
import { headerMenuCss } from '../list_header.styles';
|
||||
import { headerMenuCss, noLinkedRulesCss } from '../list_header.styles';
|
||||
import * as i18n from '../../translations';
|
||||
import { Rule } from '../../types';
|
||||
import { generateLinkedRulesMenuItems } from '../../generate_linked_rules_menu_item';
|
||||
|
@ -16,6 +16,7 @@ interface MenuItemsProps {
|
|||
isReadonly: boolean;
|
||||
dataTestSubj?: string;
|
||||
linkedRules: Rule[];
|
||||
canUserEditList?: boolean;
|
||||
securityLinkAnchorComponent: React.ElementType; // This property needs to be removed to avoid the Prop Drilling, once we move all the common components from x-pack/security-solution/common
|
||||
onExportList: () => void;
|
||||
onDeleteList: () => void;
|
||||
|
@ -27,6 +28,7 @@ const MenuItemsComponent: FC<MenuItemsProps> = ({
|
|||
linkedRules,
|
||||
securityLinkAnchorComponent,
|
||||
isReadonly,
|
||||
canUserEditList = true,
|
||||
onExportList,
|
||||
onDeleteList,
|
||||
onManageRules,
|
||||
|
@ -51,17 +53,23 @@ const MenuItemsComponent: FC<MenuItemsProps> = ({
|
|||
gutterSize="l"
|
||||
>
|
||||
<EuiFlexItem css={headerMenuCss}>
|
||||
<HeaderMenu
|
||||
dataTestSubj={`${dataTestSubj || ''}LinkedRulesMenu`}
|
||||
emptyButton
|
||||
useCustomActions
|
||||
text={i18n.EXCEPTION_LIST_HEADER_LINKED_RULES(linkedRules.length)}
|
||||
actions={referencedLinks}
|
||||
disableActions={isReadonly}
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
panelPaddingSize="none"
|
||||
/>
|
||||
{linkedRules.length ? (
|
||||
<HeaderMenu
|
||||
dataTestSubj={`${dataTestSubj || ''}LinkedRulesMenu`}
|
||||
emptyButton
|
||||
useCustomActions
|
||||
text={i18n.EXCEPTION_LIST_HEADER_LINKED_RULES(linkedRules.length)}
|
||||
actions={referencedLinks}
|
||||
disableActions={false}
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
panelPaddingSize="none"
|
||||
/>
|
||||
) : (
|
||||
<EuiTextColor data-test-subj="noLinkedRules" css={noLinkedRulesCss} color="subdued">
|
||||
{i18n.EXCEPTION_LIST_HEADER_LINKED_RULES(linkedRules.length)}
|
||||
</EuiTextColor>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
|
@ -95,6 +103,7 @@ const MenuItemsComponent: FC<MenuItemsProps> = ({
|
|||
onClick: () => {
|
||||
if (typeof onDeleteList === 'function') onDeleteList();
|
||||
},
|
||||
disabled: !canUserEditList,
|
||||
},
|
||||
]}
|
||||
disableActions={isReadonly}
|
||||
|
|
|
@ -32,6 +32,53 @@ describe('MenuItems', () => {
|
|||
expect(wrapper.getByTestId('ManageRulesButton')).toBeInTheDocument();
|
||||
expect(wrapper.getByTestId('MenuActionsButtonIcon')).toBeInTheDocument();
|
||||
});
|
||||
it('should not render linkedRules HeaderMenu component, instead should render a text', () => {
|
||||
const wrapper = render(
|
||||
<MenuItems
|
||||
isReadonly={false}
|
||||
linkedRules={[]}
|
||||
securityLinkAnchorComponent={securityLinkAnchorComponentMock}
|
||||
onExportList={onExportList}
|
||||
onDeleteList={onDeleteList}
|
||||
onManageRules={onManageRules}
|
||||
/>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.queryByTestId('LinkedRulesMenuItems')).not.toBeInTheDocument();
|
||||
expect(wrapper.getByTestId('noLinkedRules')).toBeInTheDocument();
|
||||
});
|
||||
it('should render all menu actions enabled', () => {
|
||||
const wrapper = render(
|
||||
<MenuItems
|
||||
isReadonly={false}
|
||||
linkedRules={[]}
|
||||
securityLinkAnchorComponent={securityLinkAnchorComponentMock}
|
||||
onExportList={onExportList}
|
||||
onDeleteList={onDeleteList}
|
||||
onManageRules={onManageRules}
|
||||
/>
|
||||
);
|
||||
fireEvent.click(wrapper.getByTestId('MenuActionsButtonIcon'));
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.getByTestId('MenuActionsActionItem1')).toBeEnabled();
|
||||
expect(wrapper.getByTestId('MenuActionsActionItem2')).toBeEnabled();
|
||||
});
|
||||
it('should render delete action disabled', () => {
|
||||
const wrapper = render(
|
||||
<MenuItems
|
||||
isReadonly={false}
|
||||
canUserEditList={false}
|
||||
linkedRules={rules}
|
||||
securityLinkAnchorComponent={securityLinkAnchorComponentMock}
|
||||
onExportList={onExportList}
|
||||
onDeleteList={onDeleteList}
|
||||
onManageRules={onManageRules}
|
||||
/>
|
||||
);
|
||||
fireEvent.click(wrapper.getByTestId('MenuActionsButtonIcon'));
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper.getByTestId('MenuActionsActionItem2')).toBeDisabled();
|
||||
});
|
||||
it('should call onManageRules', () => {
|
||||
const wrapper = render(
|
||||
<MenuItems
|
||||
|
|
|
@ -24,9 +24,11 @@ export const useExceptionListHeader = ({
|
|||
setIsModalVisible(true);
|
||||
};
|
||||
const onSave = (newListDetails: ListDetails) => {
|
||||
setIsModalVisible(false);
|
||||
setListDetails(newListDetails);
|
||||
if (typeof onEditListDetails === 'function') onEditListDetails(newListDetails);
|
||||
setTimeout(() => {
|
||||
setIsModalVisible(false);
|
||||
}, 200);
|
||||
};
|
||||
const onCancel = () => {
|
||||
setIsModalVisible(false);
|
||||
|
|
|
@ -21,3 +21,18 @@ export const actions = [
|
|||
onClick: handleDelete,
|
||||
},
|
||||
];
|
||||
export const actionsWithDisabledDelete = [
|
||||
{
|
||||
key: 'edit',
|
||||
icon: 'pencil',
|
||||
label: 'Edit detection exception',
|
||||
onClick: handleEdit,
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
icon: 'trash',
|
||||
disabled: true,
|
||||
label: 'Delete detection exception',
|
||||
onClick: handleDelete,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -10,7 +10,7 @@ import { Rule, RuleReference } from '../types';
|
|||
|
||||
export const rules: Rule[] = [
|
||||
{
|
||||
exception_lists: [
|
||||
exception_list: [
|
||||
{
|
||||
id: '123',
|
||||
list_id: 'i_exist',
|
||||
|
|
|
@ -26,7 +26,7 @@ export const getSecurityLinkAction = (dataTestSubj: string) =>
|
|||
linkedRules: [
|
||||
...rules,
|
||||
{
|
||||
exception_lists: [],
|
||||
exception_list: [],
|
||||
id: '2a2b3c',
|
||||
name: 'Simple Rule Query 2',
|
||||
rule_id: 'rule-2',
|
||||
|
|
|
@ -6,7 +6,29 @@ Object {
|
|||
"baseElement": <body>
|
||||
<div>
|
||||
<div
|
||||
css="You have tried to stringify object returned from \`css\` function. It isn't supposed to be used directly (e.g. as value of the \`className\` prop), but rather handed to emotion so it can handle it (e.g. as value of \`css\` prop)."
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive css-nhr7ke"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrow10"
|
||||
>
|
||||
<span
|
||||
data-test-subj="TextWithEditTestText"
|
||||
>
|
||||
Test
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero emotion-EuiFlexItem"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</body>,
|
||||
"container": <div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive css-nhr7ke"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrow10"
|
||||
>
|
||||
<span
|
||||
data-test-subj="TextWithEditTestText"
|
||||
|
@ -14,17 +36,9 @@ Object {
|
|||
Test
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>,
|
||||
"container": <div>
|
||||
<div
|
||||
css="You have tried to stringify object returned from \`css\` function. It isn't supposed to be used directly (e.g. as value of the \`className\` prop), but rather handed to emotion so it can handle it (e.g. as value of \`css\` prop)."
|
||||
>
|
||||
<span
|
||||
data-test-subj="TextWithEditTestText"
|
||||
>
|
||||
Test
|
||||
</span>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero emotion-EuiFlexItem"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
"debug": [Function],
|
||||
|
@ -87,13 +101,53 @@ Object {
|
|||
"baseElement": <body>
|
||||
<div>
|
||||
<div
|
||||
css="You have tried to stringify object returned from \`css\` function. It isn't supposed to be used directly (e.g. as value of the \`className\` prop), but rather handed to emotion so it can handle it (e.g. as value of \`css\` prop)."
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive css-nhr7ke"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrow10"
|
||||
>
|
||||
<span
|
||||
data-test-subj="TextWithEditTestText"
|
||||
>
|
||||
Test
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero emotion-EuiFlexItem"
|
||||
>
|
||||
<button
|
||||
aria-label="Edit Text List Header"
|
||||
class="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall"
|
||||
data-test-subj="TextWithEditTestEditIcon"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="euiButtonIcon__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="pencil"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>,
|
||||
"container": <div>
|
||||
<div
|
||||
class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive css-nhr7ke"
|
||||
>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrow10"
|
||||
>
|
||||
<span
|
||||
data-test-subj="TextWithEditTestText"
|
||||
>
|
||||
Test
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="euiFlexItem euiFlexItem--flexGrowZero emotion-EuiFlexItem"
|
||||
>
|
||||
<button
|
||||
aria-label="Edit Text List Header"
|
||||
class="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall"
|
||||
|
@ -109,30 +163,6 @@ Object {
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>,
|
||||
"container": <div>
|
||||
<div
|
||||
css="You have tried to stringify object returned from \`css\` function. It isn't supposed to be used directly (e.g. as value of the \`className\` prop), but rather handed to emotion so it can handle it (e.g. as value of \`css\` prop)."
|
||||
>
|
||||
<span
|
||||
data-test-subj="TextWithEditTestText"
|
||||
>
|
||||
Test
|
||||
</span>
|
||||
<button
|
||||
aria-label="Edit Text List Header"
|
||||
class="euiButtonIcon euiButtonIcon--primary euiButtonIcon--empty euiButtonIcon--xSmall"
|
||||
data-test-subj="TextWithEditTestEditIcon"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="euiButtonIcon__icon"
|
||||
color="inherit"
|
||||
data-euiicon-type="pencil"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>,
|
||||
"debug": [Function],
|
||||
"findAllByAltText": [Function],
|
||||
|
|
|
@ -7,10 +7,9 @@
|
|||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { EuiButtonIcon } from '@elastic/eui';
|
||||
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { Interpolation, Theme } from '@emotion/react';
|
||||
import { textWithEditContainerCss } from '../list_header/list_header.styles';
|
||||
|
||||
import { textWithEditContainerCss, editIconCss } from './text_with_edit.styles';
|
||||
interface TextWithEditProps {
|
||||
isReadonly: boolean;
|
||||
dataTestSubj?: string;
|
||||
|
@ -27,19 +26,23 @@ const TextWithEditComponent: FC<TextWithEditProps> = ({
|
|||
textCss,
|
||||
}) => {
|
||||
return (
|
||||
<div css={textWithEditContainerCss}>
|
||||
<span css={textCss} data-test-subj={`${dataTestSubj || ''}Text`}>
|
||||
{text}
|
||||
</span>
|
||||
{isReadonly ? null : (
|
||||
<EuiButtonIcon
|
||||
data-test-subj={`${dataTestSubj || ''}EditIcon`}
|
||||
aria-label="Edit Text List Header"
|
||||
iconType="pencil"
|
||||
onClick={() => (typeof onEdit === 'function' ? onEdit() : null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<EuiFlexGroup css={textWithEditContainerCss}>
|
||||
<EuiFlexItem grow={10}>
|
||||
<span css={textCss} data-test-subj={`${dataTestSubj || ''}Text`}>
|
||||
{text}
|
||||
</span>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} css={editIconCss}>
|
||||
{isReadonly ? null : (
|
||||
<EuiButtonIcon
|
||||
data-test-subj={`${dataTestSubj || ''}EditIcon`}
|
||||
aria-label="Edit Text List Header"
|
||||
iconType="pencil"
|
||||
onClick={() => (typeof onEdit === 'function' ? onEdit() : null)}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
TextWithEditComponent.displayName = 'TextWithEditComponent';
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { css } from '@emotion/react';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
|
||||
export const textWithEditContainerCss = css`
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
align-items: baseline;
|
||||
padding-bottom: ${euiThemeVars.euiSizeS};
|
||||
h1 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
`;
|
||||
export const editIconCss = css`
|
||||
button {
|
||||
margin-left: -${euiThemeVars.euiSizeM};
|
||||
}
|
||||
`;
|
|
@ -140,3 +140,10 @@ export const EXCEPTION_LIST_HEADER_DESCRIPTION_TEXTBOX = i18n.translate(
|
|||
defaultMessage: 'Description',
|
||||
}
|
||||
);
|
||||
|
||||
export const LIST_NAME_REQUIRED_ERROR = i18n.translate(
|
||||
'exceptionList-components.exception_list_header_description_textboxexceptionList-components.exception_list_header_name_required_eror',
|
||||
{
|
||||
defaultMessage: 'List name cannot be empty',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -60,7 +60,7 @@ export interface Rule {
|
|||
name: string;
|
||||
id: string;
|
||||
rule_id: string;
|
||||
exception_lists: ListArray;
|
||||
exception_list?: ListArray;
|
||||
}
|
||||
|
||||
export interface RuleReference {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue