Skip to content

Commit

Permalink
[SIEM] [Cases] Tags suggestions (elastic#63878)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephmilovic committed Apr 21, 2020
1 parent 90cd648 commit 6b70fe4
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { useForm } from '../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks';
jest.mock(
'../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'
);
export const mockFormHook = {
isSubmitted: false,
isSubmitting: false,
Expand Down Expand Up @@ -35,3 +39,5 @@ export const getFormMock = (sampleData: any) => ({
}),
getFormData: () => sampleData,
});

export const useFormMock = useForm as jest.Mock;
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router

import { useInsertTimeline } from '../../../../components/timeline/insert_timeline_popover/use_insert_timeline';
import { usePostCase } from '../../../../containers/case/use_post_case';
import { useGetTags } from '../../../../containers/case/use_get_tags';

jest.mock('../../../../components/timeline/insert_timeline_popover/use_insert_timeline');
jest.mock('../../../../containers/case/use_post_case');
import { useForm } from '../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks';
Expand All @@ -22,6 +24,14 @@ import { SiemPageName } from '../../../home/types';
jest.mock(
'../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'
);
jest.mock('../../../../containers/case/use_get_tags');
jest.mock(
'../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider',
() => ({
FormDataProvider: ({ children }: { children: ({ tags }: { tags: string[] }) => void }) =>
children({ tags: ['rad', 'dude'] }),
})
);

export const useFormMock = useForm as jest.Mock;

Expand All @@ -40,9 +50,11 @@ const defaultInsertTimeline = {
handleCursorChange,
handleOnTimelineChange,
};

const sampleTags = ['coke', 'pepsi'];
const sampleData = {
description: 'what a great description',
tags: ['coke', 'pepsi'],
tags: sampleTags,
title: 'what a cool title',
};
const defaultPostCase = {
Expand All @@ -52,14 +64,28 @@ const defaultPostCase = {
postCase,
};
describe('Create case', () => {
// Suppress warnings about "noSuggestions" prop
/* eslint-disable no-console */
const originalError = console.error;
beforeAll(() => {
console.error = jest.fn();
});
afterAll(() => {
console.error = originalError;
});
/* eslint-enable no-console */
const fetchTags = jest.fn();
const formHookMock = getFormMock(sampleData);

beforeEach(() => {
jest.resetAllMocks();
useInsertTimelineMock.mockImplementation(() => defaultInsertTimeline);
usePostCaseMock.mockImplementation(() => defaultPostCase);
useFormMock.mockImplementation(() => ({ form: formHookMock }));
jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);
(useGetTags as jest.Mock).mockImplementation(() => ({
tags: sampleTags,
fetchTags,
}));
});

it('should post case on submit click', async () => {
Expand Down Expand Up @@ -118,4 +144,19 @@ describe('Create case', () => {
);
expect(wrapper.find(`[data-test-subj="create-case-loading-spinner"]`).exists()).toBeTruthy();
});
it('Tag options render with new tags added', () => {
const wrapper = mount(
<TestProviders>
<Router history={mockHistory}>
<Create />
</Router>
</TestProviders>
);
expect(
wrapper
.find(`[data-test-subj="caseTags"] [data-test-subj="input"]`)
.first()
.prop('options')
).toEqual([{ label: 'coke' }, { label: 'pepsi' }, { label: 'rad' }, { label: 'dude' }]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* 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, { useCallback, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import {
EuiButton,
EuiButtonEmpty,
Expand All @@ -15,15 +15,24 @@ import {
import styled, { css } from 'styled-components';
import { Redirect } from 'react-router-dom';

import { isEqual } from 'lodash/fp';
import { CasePostRequest } from '../../../../../../../../plugins/case/common/api';
import { Field, Form, getUseField, useForm, UseField } from '../../../../shared_imports';
import {
Field,
Form,
getUseField,
useForm,
UseField,
FormDataProvider,
} from '../../../../shared_imports';
import { usePostCase } from '../../../../containers/case/use_post_case';
import { schema } from './schema';
import { InsertTimelinePopover } from '../../../../components/timeline/insert_timeline_popover';
import { useInsertTimeline } from '../../../../components/timeline/insert_timeline_popover/use_insert_timeline';
import * as i18n from '../../translations';
import { SiemPageName } from '../../../home/types';
import { MarkdownEditorForm } from '../../../../components/markdown_editor/form';
import { useGetTags } from '../../../../containers/case/use_get_tags';

export const CommonUseField = getUseField({ component: Field });

Expand Down Expand Up @@ -59,6 +68,21 @@ export const Create = React.memo(() => {
options: { stripEmptyFields: false },
schema,
});
const { tags: tagOptions } = useGetTags();
const [options, setOptions] = useState(
tagOptions.map(label => ({
label,
}))
);
useEffect(
() =>
setOptions(
tagOptions.map(label => ({
label,
}))
),
[tagOptions]
);
const { handleCursorChange, handleOnTimelineChange } = useInsertTimeline<CasePostRequest>(
form,
'description'
Expand Down Expand Up @@ -108,6 +132,8 @@ export const Create = React.memo(() => {
fullWidth: true,
placeholder: '',
disabled: isLoading,
options,
noSuggestions: false,
},
}}
/>
Expand All @@ -131,6 +157,25 @@ export const Create = React.memo(() => {
}}
/>
</ContainerBig>
<FormDataProvider pathsToWatch="tags">
{({ tags: anotherTags }) => {
const current: string[] = options.map(opt => opt.label);
const newOptions = anotherTags.reduce((acc: string[], item: string) => {
if (!acc.includes(item)) {
return [...acc, item];
}
return acc;
}, current);
if (!isEqual(current, newOptions)) {
setOptions(
newOptions.map((label: string) => ({
label,
}))
);
}
return null;
}}
</FormDataProvider>
</Form>
<Container>
<EuiFlexGroup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,26 @@

import React from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';

import { TagList } from './';
import { getFormMock } from '../__mock__/form';
import { TestProviders } from '../../../../mock';
import { wait } from '../../../../lib/helpers';
import { useForm } from '../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks';
import { act } from 'react-dom/test-utils';
import { useGetTags } from '../../../../containers/case/use_get_tags';

jest.mock(
'../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'
);
jest.mock('../../../../containers/case/use_get_tags');
jest.mock(
'../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider',
() => ({
FormDataProvider: ({ children }: { children: ({ tags }: { tags: string[] }) => void }) =>
children({ tags: ['rad', 'dude'] }),
})
);
const onSubmit = jest.fn();
const defaultProps = {
disabled: false,
Expand All @@ -26,11 +35,27 @@ const defaultProps = {
};

describe('TagList ', () => {
// Suppress warnings about "noSuggestions" prop
/* eslint-disable no-console */
const originalError = console.error;
beforeAll(() => {
console.error = jest.fn();
});
afterAll(() => {
console.error = originalError;
});
/* eslint-enable no-console */
const sampleTags = ['coke', 'pepsi'];
const fetchTags = jest.fn();
const formHookMock = getFormMock({ tags: sampleTags });
beforeEach(() => {
jest.resetAllMocks();
(useForm as jest.Mock).mockImplementation(() => ({ form: formHookMock }));

(useGetTags as jest.Mock).mockImplementation(() => ({
tags: sampleTags,
fetchTags,
}));
});
it('Renders no tags, and then edit', () => {
const wrapper = mount(
Expand Down Expand Up @@ -80,6 +105,23 @@ describe('TagList ', () => {
expect(onSubmit).toBeCalledWith(sampleTags);
});
});
it('Tag options render with new tags added', () => {
const wrapper = mount(
<TestProviders>
<TagList {...defaultProps} />
</TestProviders>
);
wrapper
.find(`[data-test-subj="tag-list-edit-button"]`)
.last()
.simulate('click');
expect(
wrapper
.find(`[data-test-subj="caseTags"] [data-test-subj="input"]`)
.first()
.prop('options')
).toEqual([{ label: 'coke' }, { label: 'pepsi' }, { label: 'rad' }, { label: 'dude' }]);
});
it('Cancels on cancel', async () => {
const props = {
...defaultProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { useCallback, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import {
EuiText,
EuiHorizontalRule,
Expand All @@ -17,10 +17,12 @@ import {
EuiLoadingSpinner,
} from '@elastic/eui';
import styled, { css } from 'styled-components';
import { isEqual } from 'lodash/fp';
import * as i18n from './translations';
import { Form, useForm } from '../../../../shared_imports';
import { Form, FormDataProvider, useForm } from '../../../../shared_imports';
import { schema } from './schema';
import { CommonUseField } from '../create';
import { useGetTags } from '../../../../containers/case/use_get_tags';

interface TagListProps {
disabled?: boolean;
Expand Down Expand Up @@ -54,6 +56,22 @@ export const TagList = React.memo(
setIsEditTags(false);
}
}, [form, onSubmit]);
const { tags: tagOptions } = useGetTags();
const [options, setOptions] = useState(
tagOptions.map(label => ({
label,
}))
);

useEffect(
() =>
setOptions(
tagOptions.map(label => ({
label,
}))
),
[tagOptions]
);

return (
<EuiText>
Expand All @@ -75,7 +93,7 @@ export const TagList = React.memo(
)}
</EuiFlexGroup>
<EuiHorizontalRule margin="xs" />
<MyFlexGroup gutterSize="xs" data-test-subj="grr">
<MyFlexGroup gutterSize="xs" wrap>
{tags.length === 0 && !isEditTags && <p data-test-subj="no-tags">{i18n.NO_TAGS}</p>}
{tags.length > 0 &&
!isEditTags &&
Expand All @@ -98,9 +116,30 @@ export const TagList = React.memo(
euiFieldProps: {
fullWidth: true,
placeholder: '',
options,
noSuggestions: false,
},
}}
/>
<FormDataProvider pathsToWatch="tags">
{({ tags: anotherTags }) => {
const current: string[] = options.map(opt => opt.label);
const newOptions = anotherTags.reduce((acc: string[], item: string) => {
if (!acc.includes(item)) {
return [...acc, item];
}
return acc;
}, current);
if (!isEqual(current, newOptions)) {
setOptions(
newOptions.map((label: string) => ({
label,
}))
);
}
return null;
}}
</FormDataProvider>
</Form>
</EuiFlexItem>
<EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,13 @@ import React from 'react';
import { mount } from 'enzyme';

import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router';
import { getFormMock } from '../__mock__/form';
import { getFormMock, useFormMock } from '../__mock__/form';
import { useUpdateComment } from '../../../../containers/case/use_update_comment';
import { basicCase, getUserAction } from '../../../../containers/case/mock';
import { UserActionTree } from './';
import { TestProviders } from '../../../../mock';
import { useFormMock } from '../create/index.test';
import { wait } from '../../../../lib/helpers';
import { act } from 'react-dom/test-utils';
jest.mock(
'../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'
);

const fetchUserActions = jest.fn();
const onUpdateField = jest.fn();
Expand Down
2 changes: 2 additions & 0 deletions x-pack/legacy/plugins/siem/public/shared_imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export {
getUseField,
getFieldValidityAndErrorMessage,
FieldHook,
FieldValidateResponse,
FIELD_TYPES,
Form,
FormData,
Expand All @@ -17,6 +18,7 @@ export {
UseField,
useForm,
ValidationFunc,
VALIDATION_TYPES,
} from '../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
export {
Field,
Expand Down

0 comments on commit 6b70fe4

Please sign in to comment.