diff --git a/x-pack/plugins/cases/public/components/create/form.tsx b/x-pack/plugins/cases/public/components/create/form.tsx index ac2729564b387..50a3c69f2073e 100644 --- a/x-pack/plugins/cases/public/components/create/form.tsx +++ b/x-pack/plugins/cases/public/components/create/form.tsx @@ -35,6 +35,7 @@ import { CreateCaseOwnerSelector } from './owner_selector'; import { useCasesContext } from '../cases_context/use_cases_context'; import { useAvailableCasesOwners } from '../app/use_available_owners'; import { CaseAttachments } from '../../types'; +import { Severity } from './severity'; interface ContainerProps { big?: boolean; @@ -88,6 +89,9 @@ export const CreateCaseFormFields: React.FC = React.m + + + {canShowCaseSolutionSelection && ( = React.m + ), }), diff --git a/x-pack/plugins/cases/public/components/create/form_context.test.tsx b/x-pack/plugins/cases/public/components/create/form_context.test.tsx index 634f518ae5ebd..bfa4f391458da 100644 --- a/x-pack/plugins/cases/public/components/create/form_context.test.tsx +++ b/x-pack/plugins/cases/public/components/create/form_context.test.tsx @@ -10,7 +10,7 @@ import { mount, ReactWrapper } from 'enzyme'; import { act, RenderResult, waitFor, within } from '@testing-library/react'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; -import { CommentType, ConnectorTypes } from '../../../common/api'; +import { CaseSeverity, CommentType, ConnectorTypes } from '../../../common/api'; import { useKibana } from '../../common/lib/kibana'; import { AppMockRenderer, createAppMockRenderer, TestProviders } from '../../common/mock'; import { usePostCase } from '../../containers/use_post_case'; @@ -182,6 +182,7 @@ describe('Create case', () => { ); expect(renderResult.getByTestId('caseTitle')).toBeTruthy(); + expect(renderResult.getByTestId('caseSeverity')).toBeTruthy(); expect(renderResult.getByTestId('caseDescription')).toBeTruthy(); expect(renderResult.getByTestId('caseTags')).toBeTruthy(); expect(renderResult.getByTestId('caseConnectors')).toBeTruthy(); @@ -208,6 +209,34 @@ describe('Create case', () => { }); }); + it('should post a case on submit click with the selected severity', async () => { + useConnectorsMock.mockReturnValue({ + ...sampleConnectorData, + connectors: connectorsMock, + }); + + const renderResult = mockedContext.render( + + + + + ); + + await fillFormReactTestingLib(renderResult); + + userEvent.click(renderResult.getByTestId('case-severity-selection')); + expect(renderResult.getByTestId('case-severity-selection-high')).toBeTruthy(); + userEvent.click(renderResult.getByTestId('case-severity-selection-high')); + + userEvent.click(renderResult.getByTestId('create-case-submit')); + await waitFor(() => { + expect(postCase).toBeCalledWith({ + ...sampleData, + severity: CaseSeverity.HIGH, + }); + }); + }); + it('does not submits the title when the length is longer than 64 characters', async () => { const longTitle = 'This is a title that should not be saved as it is longer than 64 characters.'; @@ -285,6 +314,18 @@ describe('Create case', () => { ); }); + it('should select LOW as the default severity', async () => { + const renderResult = mockedContext.render( + + + + + ); + expect(renderResult.getByTestId('caseSeverity')).toBeTruthy(); + // there should be 2 low elements. one for the options popover and one for the displayed one. + expect(renderResult.getAllByTestId('case-severity-selection-low').length).toBe(2); + }); + it('should select the default connector set in the configuration', async () => { useCaseConfigureMock.mockImplementation(() => ({ ...useCaseConfigureResponse, diff --git a/x-pack/plugins/cases/public/components/create/form_context.tsx b/x-pack/plugins/cases/public/components/create/form_context.tsx index 4385053a8c8c0..a65e9f5960e9d 100644 --- a/x-pack/plugins/cases/public/components/create/form_context.tsx +++ b/x-pack/plugins/cases/public/components/create/form_context.tsx @@ -14,7 +14,7 @@ import { usePostPushToService } from '../../containers/use_post_push_to_service' import { useConnectors } from '../../containers/configure/use_connectors'; import { Case } from '../../containers/types'; -import { NONE_CONNECTOR_ID } from '../../../common/api'; +import { CaseSeverity, NONE_CONNECTOR_ID } from '../../../common/api'; import { UseCreateAttachments, useCreateAttachments, @@ -28,6 +28,7 @@ const initialCaseValue: FormProps = { description: '', tags: [], title: '', + severity: CaseSeverity.LOW, connectorId: NONE_CONNECTOR_ID, fields: null, syncAlerts: true, diff --git a/x-pack/plugins/cases/public/components/create/mock.ts b/x-pack/plugins/cases/public/components/create/mock.ts index 8ab515c79f67e..38d57bf24781e 100644 --- a/x-pack/plugins/cases/public/components/create/mock.ts +++ b/x-pack/plugins/cases/public/components/create/mock.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { CasePostRequest, ConnectorTypes } from '../../../common/api'; +import { CasePostRequest, CaseSeverity, ConnectorTypes } from '../../../common/api'; import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { choices } from '../connectors/mock'; @@ -13,6 +13,7 @@ export const sampleTags = ['coke', 'pepsi']; export const sampleData: CasePostRequest = { description: 'what a great description', tags: sampleTags, + severity: CaseSeverity.LOW, title: 'what a cool title', connector: { fields: null, diff --git a/x-pack/plugins/cases/public/components/create/schema.tsx b/x-pack/plugins/cases/public/components/create/schema.tsx index b7c363b263998..d72b1cc523f0d 100644 --- a/x-pack/plugins/cases/public/components/create/schema.tsx +++ b/x-pack/plugins/cases/public/components/create/schema.tsx @@ -17,6 +17,7 @@ import { import * as i18n from './translations'; import { OptionalFieldLabel } from './optional_field_label'; +import { SEVERITY_TITLE } from '../severity/translations'; const { emptyField, maxLengthField } = fieldValidators; export const schemaTags = { @@ -83,6 +84,9 @@ export const schema: FormSchema = { ], }, tags: schemaTags, + severity: { + label: SEVERITY_TITLE, + }, connectorId: { type: FIELD_TYPES.SUPER_SELECT, label: i18n.CONNECTORS, diff --git a/x-pack/plugins/cases/public/components/create/severity.test.tsx b/x-pack/plugins/cases/public/components/create/severity.test.tsx new file mode 100644 index 0000000000000..d2434a37a4392 --- /dev/null +++ b/x-pack/plugins/cases/public/components/create/severity.test.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CaseSeverity } from '../../../common/api'; +import React from 'react'; +import { AppMockRenderer, createAppMockRenderer } from '../../common/mock'; +import { Form, FormHook, useForm } from '../../common/shared_imports'; +import { Severity } from './severity'; +import { FormProps, schema } from './schema'; +import userEvent from '@testing-library/user-event'; +import { waitFor } from '@testing-library/dom'; + +let globalForm: FormHook; +const MockHookWrapperComponent: React.FC = ({ children }) => { + const { form } = useForm({ + defaultValue: { severity: CaseSeverity.LOW }, + schema: { + severity: schema.severity, + }, + }); + + globalForm = form; + + return
{children}
; +}; +describe('Severity form field', () => { + let appMockRender: AppMockRenderer; + beforeEach(() => { + appMockRender = createAppMockRenderer(); + }); + it('renders', () => { + const result = appMockRender.render( + + + + ); + expect(result.getByTestId('caseSeverity')).toBeTruthy(); + expect(result.getByTestId('case-severity-selection')).not.toHaveAttribute('disabled'); + }); + + // default to LOW in this test configuration + it('defaults to the correct value', () => { + const result = appMockRender.render( + + + + ); + expect(result.getByTestId('caseSeverity')).toBeTruthy(); + // two items. one for the popover one for the selected field + expect(result.getAllByTestId('case-severity-selection-low').length).toBe(2); + }); + + it('selects the correct value when changed', async () => { + const result = appMockRender.render( + + + + ); + expect(result.getByTestId('caseSeverity')).toBeTruthy(); + userEvent.click(result.getByTestId('case-severity-selection')); + userEvent.click(result.getByTestId('case-severity-selection-high')); + await waitFor(() => { + expect(globalForm.getFormData()).toEqual({ severity: 'high' }); + }); + }); + + it('disables when loading data', () => { + const result = appMockRender.render( + + + + ); + expect(result.getByTestId('case-severity-selection')).toHaveAttribute('disabled'); + }); +}); diff --git a/x-pack/plugins/cases/public/components/create/severity.tsx b/x-pack/plugins/cases/public/components/create/severity.tsx new file mode 100644 index 0000000000000..730eab5d77ac6 --- /dev/null +++ b/x-pack/plugins/cases/public/components/create/severity.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFormRow } from '@elastic/eui'; +import React, { memo } from 'react'; +import { CaseSeverity } from '../../../common/api'; +import { UseField, useFormContext, useFormData } from '../../common/shared_imports'; +import { SeveritySelector } from '../severity/selector'; +import { SEVERITY_TITLE } from '../severity/translations'; + +interface Props { + isLoading: boolean; +} + +const SeverityFieldFormComponent = ({ isLoading }: { isLoading: boolean }) => { + const { setFieldValue } = useFormContext(); + const [{ severity }] = useFormData({ watch: ['severity'] }); + const onSeverityChange = (newSeverity: CaseSeverity) => { + setFieldValue('severity', newSeverity); + }; + return ( + + + + ); +}; +SeverityFieldFormComponent.displayName = 'SeverityFieldForm'; + +const SeverityComponent: React.FC = ({ isLoading }) => ( + +); + +SeverityComponent.displayName = 'SeverityComponent'; + +export const Severity = memo(SeverityComponent);