Skip to content

Commit

Permalink
[7.x] [Security Solution][Case] Case connector alert UI (#82405) (#84712
Browse files Browse the repository at this point in the history
)

Co-authored-by: Patryk Kopycinski <contact@patrykkopycinski.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
# Conflicts:
#	x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx
  • Loading branch information
cnasikas authored Dec 2, 2020
1 parent b4738be commit 7fb5ec1
Show file tree
Hide file tree
Showing 49 changed files with 2,019 additions and 607 deletions.
6 changes: 6 additions & 0 deletions x-pack/plugins/security_solution/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export const ML_GROUP_IDS = [ML_GROUP_ID, LEGACY_ML_GROUP_ID];
/*
Rule notifications options
*/
export const ENABLE_CASE_CONNECTOR = false;
export const NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS = [
'.email',
'.slack',
Expand All @@ -169,6 +170,11 @@ export const NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS = [
'.jira',
'.resilient',
];

if (ENABLE_CASE_CONNECTOR) {
NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS.push('.case');
}

export const NOTIFICATION_THROTTLE_NO_ACTIONS = 'no_actions';
export const NOTIFICATION_THROTTLE_RULE = 'rule';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,24 @@

import React from 'react';
import { mount } from 'enzyme';
import { waitFor, act } from '@testing-library/react';

import { AddComment, AddCommentRefObject } from '.';
import { TestProviders } from '../../../common/mock';
import { getFormMock } from '../__mock__/form';
import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router';

import { CommentRequest, CommentType } from '../../../../../case/common/api';
import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline';
import { useInsertTimeline } from '../use_insert_timeline';
import { usePostComment } from '../../containers/use_post_comment';
import { useForm } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form';
import { useFormData } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data';

import { waitFor } from '@testing-library/react';

jest.mock(
'../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'
);

jest.mock(
'../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data'
);

jest.mock('../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline');
jest.mock('../../containers/use_post_comment');
jest.mock('../use_insert_timeline');

const useFormMock = useForm as jest.Mock;
const useFormDataMock = useFormData as jest.Mock;

const useInsertTimelineMock = useInsertTimeline as jest.Mock;
const usePostCommentMock = usePostComment as jest.Mock;

const useInsertTimelineMock = useInsertTimeline as jest.Mock;
const onCommentSaving = jest.fn();
const onCommentPosted = jest.fn();
const postComment = jest.fn();
const handleCursorChange = jest.fn();
const handleOnTimelineChange = jest.fn();

const addCommentProps = {
caseId: '1234',
Expand All @@ -52,15 +34,6 @@ const addCommentProps = {
showLoading: false,
};

const defaultInsertTimeline = {
cursorPosition: {
start: 0,
end: 0,
},
handleCursorChange,
handleOnTimelineChange,
};

const defaultPostCommment = {
isLoading: false,
isError: false,
Expand All @@ -73,14 +46,9 @@ const sampleData: CommentRequest = {
};

describe('AddComment ', () => {
const formHookMock = getFormMock(sampleData);

beforeEach(() => {
jest.resetAllMocks();
useInsertTimelineMock.mockImplementation(() => defaultInsertTimeline);
usePostCommentMock.mockImplementation(() => defaultPostCommment);
useFormMock.mockImplementation(() => ({ form: formHookMock }));
useFormDataMock.mockImplementation(() => [{ comment: sampleData.comment }]);
jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);
});

Expand All @@ -92,14 +60,25 @@ describe('AddComment ', () => {
</Router>
</TestProviders>
);

await act(async () => {
wrapper
.find(`[data-test-subj="add-comment"] textarea`)
.first()
.simulate('change', { target: { value: sampleData.comment } });
});

expect(wrapper.find(`[data-test-subj="add-comment"]`).exists()).toBeTruthy();
expect(wrapper.find(`[data-test-subj="loading-spinner"]`).exists()).toBeFalsy();

wrapper.find(`[data-test-subj="submit-comment"]`).first().simulate('click');
await act(async () => {
wrapper.find(`[data-test-subj="submit-comment"]`).first().simulate('click');
});

await waitFor(() => {
expect(onCommentSaving).toBeCalled();
expect(postComment).toBeCalledWith(sampleData, onCommentPosted);
expect(formHookMock.reset).toBeCalled();
expect(wrapper.find(`[data-test-subj="add-comment"] textarea`).text()).toBe('');
});
});

Expand All @@ -112,6 +91,7 @@ describe('AddComment ', () => {
</Router>
</TestProviders>
);

expect(wrapper.find(`[data-test-subj="loading-spinner"]`).exists()).toBeTruthy();
expect(
wrapper.find(`[data-test-subj="submit-comment"]`).first().prop('isDisabled')
Expand All @@ -127,26 +107,54 @@ describe('AddComment ', () => {
</Router>
</TestProviders>
);

expect(
wrapper.find(`[data-test-subj="submit-comment"]`).first().prop('isDisabled')
).toBeTruthy();
});

it('should insert a quote', () => {
it('should insert a quote', async () => {
const sampleQuote = 'what a cool quote';
const ref = React.createRef<AddCommentRefObject>();
mount(
const wrapper = mount(
<TestProviders>
<Router history={mockHistory}>
<AddComment {...{ ...addCommentProps }} ref={ref} />
</Router>
</TestProviders>
);

ref.current!.addQuote(sampleQuote);
expect(formHookMock.setFieldValue).toBeCalledWith(
'comment',
await act(async () => {
wrapper
.find(`[data-test-subj="add-comment"] textarea`)
.first()
.simulate('change', { target: { value: sampleData.comment } });
});

await act(async () => {
ref.current!.addQuote(sampleQuote);
});

expect(wrapper.find(`[data-test-subj="add-comment"] textarea`).text()).toBe(
`${sampleData.comment}\n\n${sampleQuote}`
);
});

it('it should insert a timeline', async () => {
useInsertTimelineMock.mockImplementation((comment, onTimelineAttached) => {
onTimelineAttached(`[title](url)`);
});

const wrapper = mount(
<TestProviders>
<Router history={mockHistory}>
<AddComment {...{ ...addCommentProps }} />
</Router>
</TestProviders>
);

await waitFor(() => {
expect(wrapper.find(`[data-test-subj="add-comment"] textarea`).text()).toBe('[title](url)');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@ import { CommentType } from '../../../../../case/common/api';
import { usePostComment } from '../../containers/use_post_comment';
import { Case } from '../../containers/types';
import { MarkdownEditorForm } from '../../../common/components/markdown_editor/eui_form';
import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline';
import { Form, useForm, UseField, useFormData } from '../../../shared_imports';

import * as i18n from './translations';
import { schema, AddCommentFormSchema } from './schema';
import { useTimelineClick } from '../../../common/utils/timeline/use_timeline_click';
import { useInsertTimeline } from '../use_insert_timeline';

const MySpinner = styled(EuiLoadingSpinner)`
position: absolute;
Expand Down Expand Up @@ -56,12 +55,6 @@ export const AddComment = React.memo(
const { setFieldValue, reset, submit } = form;
const [{ comment }] = useFormData<{ comment: string }>({ form, watch: [fieldName] });

const onCommentChange = useCallback((newValue) => setFieldValue(fieldName, newValue), [
setFieldValue,
]);

const { handleCursorChange } = useInsertTimeline(comment, onCommentChange);

const addQuote = useCallback(
(quote) => {
setFieldValue(fieldName, `${comment}${comment.length > 0 ? '\n\n' : ''}${quote}`);
Expand All @@ -73,7 +66,12 @@ export const AddComment = React.memo(
addQuote,
}));

const handleTimelineClick = useTimelineClick();
const onTimelineAttached = useCallback(
(newValue: string) => setFieldValue(fieldName, newValue),
[setFieldValue]
);

useInsertTimeline(comment ?? '', onTimelineAttached);

const onSubmit = useCallback(async () => {
const { isValid, data } = await submit();
Expand All @@ -98,8 +96,6 @@ export const AddComment = React.memo(
isDisabled: isLoading,
dataTestSubj: 'add-comment',
placeholder: i18n.ADD_COMMENT_HELP_TEXT,
onCursorPositionUpdate: handleCursorChange,
onClickTimeline: handleTimelineClick,
bottomRightContent: (
<EuiButton
data-test-subj="submit-comment"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { TestProviders } from '../../../common/mock';
import { useGetTags } from '../../containers/use_get_tags';
import { useGetReporters } from '../../containers/use_get_reporters';
import { DEFAULT_FILTER_OPTIONS } from '../../containers/use_get_cases';
jest.mock('../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline');
jest.mock('../../containers/use_get_reporters');
jest.mock('../../containers/use_get_tags');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSuperSelect } from '@elastic/eui
import styled from 'styled-components';

import { ActionConnector } from '../../containers/configure/types';
import { connectorsConfiguration } from '../../../common/lib/connectors/config';
import { connectorsConfiguration } from '../connectors';
import * as i18n from './translations';

export interface Props {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
import React from 'react';
import { mount, ReactWrapper } from 'enzyme';

import { connectorsConfiguration } from '../../../common/lib/connectors/config';
import { createDefaultMapping } from '../../../common/lib/connectors/utils';
import { connectorsConfiguration, createDefaultMapping } from '../connectors';

import { FieldMapping, FieldMappingProps } from './field_mapping';
import { mapping } from './__mock__';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ import {
ActionType,
ThirdPartyField,
} from '../../containers/configure/types';
import { FieldMappingRow } from './field_mapping_row';
import * as i18n from './translations';

import { connectorsConfiguration } from '../../../common/lib/connectors/config';
import { setActionTypeToMapping, setThirdPartyToMapping } from './utils';
import {
ThirdPartyField as ConnectorConfigurationThirdPartyField,
AllThirdPartyFields,
} from '../../../common/lib/connectors/types';
import { createDefaultMapping } from '../../../common/lib/connectors/utils';
createDefaultMapping,
connectorsConfiguration,
} from '../connectors';

import { FieldMappingRow } from './field_mapping_row';
import * as i18n from './translations';
import { setActionTypeToMapping, setThirdPartyToMapping } from './utils';

const FieldRowWrapper = styled.div`
margin-top: 8px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {

import { capitalize } from 'lodash/fp';
import { CaseField, ActionType, ThirdPartyField } from '../../containers/configure/types';
import { AllThirdPartyFields } from '../../../common/lib/connectors/types';
import { AllThirdPartyFields } from '../connectors';

export interface RowProps {
id: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { ClosureType } from '../../containers/configure/types';

// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { ActionConnectorTableItem } from '../../../../../triggers_actions_ui/public/types';
import { connectorsConfiguration } from '../../../common/lib/connectors/config';
import { connectorsConfiguration } from '../connectors';

import { SectionWrapper } from '../wrappers';
import { Connectors } from './connectors';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import { mount } from 'enzyme';
import { UseField, Form, useForm, FormHook } from '../../../shared_imports';
import { ConnectorSelector } from './form';
import { connectorsMock } from '../../containers/mock';
import { getFormMock } from '../__mock__/form';

jest.mock(
'../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'
);

const useFormMock = useForm as jest.Mock;

describe('ConnectorSelector', () => {
const formHookMock = getFormMock({ connectorId: connectorsMock[0].id });

beforeEach(() => {
jest.resetAllMocks();
useFormMock.mockImplementation(() => ({ form: formHookMock }));
});

it('it should render', async () => {
const wrapper = mount(
<Form form={(formHookMock as unknown) as FormHook}>
<UseField
path="connectorId"
component={ConnectorSelector}
componentProps={{
connectors: connectorsMock,
dataTestSubj: 'caseConnectors',
disabled: false,
idAria: 'caseConnectors',
isLoading: false,
}}
/>
</Form>
);

expect(wrapper.find(`[data-test-subj="caseConnectors"]`).exists()).toBeTruthy();
});

it('it should not render when is not in edit mode', async () => {
const wrapper = mount(
<Form form={(formHookMock as unknown) as FormHook}>
<UseField
path="connectorId"
component={ConnectorSelector}
componentProps={{
connectors: connectorsMock,
dataTestSubj: 'caseConnectors',
disabled: false,
idAria: 'caseConnectors',
isLoading: false,
isEdit: false,
}}
/>
</Form>
);

expect(wrapper.find(`[data-test-subj="caseConnectors"]`).exists()).toBeFalsy();
});
});
Loading

0 comments on commit 7fb5ec1

Please sign in to comment.