[Cases] Only show sub-category if there are available items in the cases form (#123948)

This commit is contained in:
Esteban Beltran 2022-02-01 10:29:18 +01:00 committed by GitHub
parent 41e756c989
commit a22b948cb5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 449 additions and 302 deletions

View file

@ -88,6 +88,12 @@ export const choices = [
value: 'os',
element: 'subcategory',
},
{
dependent_value: '',
label: 'Failed Login',
value: 'failed_login',
element: 'category',
},
...['severity', 'urgency', 'impact', 'priority']
.map((element) => [
{

View file

@ -47,6 +47,10 @@ describe('ServiceNowITSM Fields', () => {
it('all params fields are rendered - isEdit: true', () => {
const wrapper = mount(<Fields fields={fields} onChange={onChange} connector={connector} />);
act(() => {
onChoicesSuccess(mockChoices);
});
wrapper.update();
expect(wrapper.find('[data-test-subj="severitySelect"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="urgencySelect"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="impactSelect"]').exists()).toBeTruthy();
@ -73,7 +77,7 @@ describe('ServiceNowITSM Fields', () => {
);
});
test('it transforms the categories to options correctly', async () => {
it('transforms the categories to options correctly', async () => {
const wrapper = mount(<Fields fields={fields} onChange={onChange} connector={connector} />);
act(() => {
onChoicesSuccess(mockChoices);
@ -91,10 +95,14 @@ describe('ServiceNowITSM Fields', () => {
value: 'software',
text: 'Software',
},
{
text: 'Failed Login',
value: 'failed_login',
},
]);
});
test('it transforms the subcategories to options correctly', async () => {
it('transforms the subcategories to options correctly', async () => {
const wrapper = mount(<Fields fields={fields} onChange={onChange} connector={connector} />);
act(() => {
onChoicesSuccess(mockChoices);
@ -109,7 +117,7 @@ describe('ServiceNowITSM Fields', () => {
]);
});
it('it transforms the options correctly', async () => {
it('transforms the options correctly', async () => {
const wrapper = mount(<Fields fields={fields} onChange={onChange} connector={connector} />);
act(() => {
onChoicesSuccess(mockChoices);
@ -127,25 +135,43 @@ describe('ServiceNowITSM Fields', () => {
);
});
test('it shows the deprecated callout when the connector uses the table API', async () => {
it('shows the deprecated callout when the connector uses the table API', async () => {
const tableApiConnector = { ...connector, config: { usesTableApi: true } };
render(<Fields fields={fields} onChange={onChange} connector={tableApiConnector} />);
expect(screen.getByTestId('deprecated-connector-warning-callout')).toBeInTheDocument();
});
test('it does not show the deprecated callout when the connector does not uses the table API', async () => {
it('does not show the deprecated callout when the connector does not uses the table API', async () => {
render(<Fields fields={fields} onChange={onChange} connector={connector} />);
expect(screen.queryByTestId('deprecated-connector-warning-callout')).not.toBeInTheDocument();
});
it('should hide subcategory if selecting a category without subcategories', async () => {
// Failed Login doesn't have defined subcategories
const customFields = {
...fields,
category: 'Failed Login',
subcategory: '',
};
const wrapper = mount(
<Fields fields={customFields} onChange={onChange} connector={connector} />
);
expect(wrapper.find('[data-test-subj="subcategorySelect"]').exists()).toBeFalsy();
});
describe('onChange calls', () => {
const wrapper = mount(<Fields fields={fields} onChange={onChange} connector={connector} />);
act(() => {
onChoicesSuccess(mockChoices);
});
wrapper.update();
expect(onChange).toHaveBeenCalledWith(fields);
const testers = ['severity', 'urgency', 'impact', 'subcategory'];
testers.forEach((subj) =>
test(`${subj.toUpperCase()}`, async () => {
it(`${subj.toUpperCase()}`, async () => {
await waitFor(() => {
const select = wrapper.find(EuiSelect).filter(`[data-test-subj="${subj}Select"]`)!;
select.prop('onChange')!({
@ -162,7 +188,7 @@ describe('ServiceNowITSM Fields', () => {
})
);
test('it should set subcategory to null when changing category', async () => {
it('should set subcategory to null when changing category', async () => {
await waitFor(() => {
const select = wrapper.find(EuiSelect).filter(`[data-test-subj="categorySelect"]`)!;
select.prop('onChange')!({

View file

@ -237,19 +237,21 @@ const ServiceNowITSMFieldsComponent: React.FunctionComponent<
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow fullWidth label={i18n.SUBCATEGORY}>
<EuiSelect
fullWidth
data-test-subj="subcategorySelect"
options={subcategoryOptions}
// Needs an empty string instead of undefined to select the blank option when changing categories
value={subcategory ?? ''}
isLoading={isLoadingChoices}
disabled={isLoadingChoices}
hasNoInitialSelection
onChange={(e) => onChangeCb('subcategory', e.target.value)}
/>
</EuiFormRow>
{subcategoryOptions?.length > 0 ? (
<EuiFormRow fullWidth label={i18n.SUBCATEGORY}>
<EuiSelect
fullWidth
data-test-subj="subcategorySelect"
options={subcategoryOptions}
// Needs an empty string instead of undefined to select the blank option when changing categories
value={subcategory ?? ''}
isLoading={isLoadingChoices}
disabled={isLoadingChoices}
hasNoInitialSelection
onChange={(e) => onChangeCb('subcategory', e.target.value)}
/>
</EuiFormRow>
) : null}
</EuiFlexItem>
</EuiFlexGroup>
</div>

View file

@ -6,7 +6,7 @@
*/
import React from 'react';
import { mount } from 'enzyme';
import { mount, ReactWrapper } from 'enzyme';
import { waitFor, act, render, screen } from '@testing-library/react';
import { EuiSelect } from '@elastic/eui';
@ -15,7 +15,7 @@ import { connector, choices as mockChoices } from '../mock';
import { Choice } from './types';
import Fields from './servicenow_sir_case_fields';
let onChoicesSuccess = (c: Choice[]) => {};
let onChoicesSuccess = (_c: Choice[]) => {};
jest.mock('../../../common/lib/kibana');
jest.mock('./use_get_choices', () => ({
@ -49,6 +49,10 @@ describe('ServiceNowSIR Fields', () => {
it('all params fields are rendered - isEdit: true', () => {
const wrapper = mount(<Fields fields={fields} onChange={onChange} connector={connector} />);
act(() => {
onChoicesSuccess(mockChoices);
});
wrapper.update();
expect(wrapper.find('[data-test-subj="destIpCheckbox"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="sourceIpCheckbox"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="malwareUrlCheckbox"]').exists()).toBeTruthy();
@ -108,6 +112,10 @@ describe('ServiceNowSIR Fields', () => {
text: 'Software',
value: 'software',
},
{
text: 'Failed Login',
value: 'failed_login',
},
]);
});
@ -172,15 +180,31 @@ describe('ServiceNowSIR Fields', () => {
expect(screen.queryByTestId('deprecated-connector-warning-callout')).not.toBeInTheDocument();
});
test('it should hide subcategory if selecting a category without subcategories', async () => {
// Failed Login doesn't have defined subcategories
const customFields = {
...fields,
category: 'Failed Login',
subcategory: '',
};
const wrapper = mount(
<Fields fields={customFields} onChange={onChange} connector={connector} />
);
expect(wrapper.find('[data-test-subj="subcategorySelect"]').exists()).toBeFalsy();
});
describe('onChange calls', () => {
const wrapper = mount(<Fields fields={fields} onChange={onChange} connector={connector} />);
let wrapper: ReactWrapper;
act(() => {
onChoicesSuccess(mockChoices);
beforeEach(() => {
wrapper = mount(<Fields fields={fields} onChange={onChange} connector={connector} />);
act(() => {
onChoicesSuccess(mockChoices);
});
wrapper.update();
expect(onChange).toHaveBeenCalledWith(fields);
});
wrapper.update();
expect(onChange).toHaveBeenCalledWith(fields);
const checkbox = ['destIp', 'sourceIp', 'malwareHash', 'malwareUrl'];
checkbox.forEach((subj) =>

View file

@ -29,250 +29,243 @@ const defaultFields: Fields = {
const ServiceNowSIRFieldsComponent: React.FunctionComponent<
ConnectorFieldsProps<ServiceNowSIRFieldsType>
> = // TODO: Fix this manually. Issue #123375
// eslint-disable-next-line react/display-name
({ isEdit = true, fields, connector, onChange }) => {
const init = useRef(true);
const {
category = null,
destIp = true,
malwareHash = true,
malwareUrl = true,
priority = null,
sourceIp = true,
subcategory = null,
} = fields ?? {};
> = ({ isEdit = true, fields, connector, onChange }) => {
const init = useRef(true);
const {
category = null,
destIp = true,
malwareHash = true,
malwareUrl = true,
priority = null,
sourceIp = true,
subcategory = null,
} = fields ?? {};
const { http, notifications } = useKibana().services;
const [choices, setChoices] = useState<Fields>(defaultFields);
const showConnectorWarning = useMemo(() => connectorValidator(connector) != null, [connector]);
const { http, notifications } = useKibana().services;
const [choices, setChoices] = useState<Fields>(defaultFields);
const showConnectorWarning = useMemo(() => connectorValidator(connector) != null, [connector]);
const onChangeCb = useCallback(
(
key: keyof ServiceNowSIRFieldsType,
value: ServiceNowSIRFieldsType[keyof ServiceNowSIRFieldsType]
) => {
onChange({ ...fields, [key]: value });
},
[fields, onChange]
const onChangeCb = useCallback(
(
key: keyof ServiceNowSIRFieldsType,
value: ServiceNowSIRFieldsType[keyof ServiceNowSIRFieldsType]
) => {
onChange({ ...fields, [key]: value });
},
[fields, onChange]
);
const onChoicesSuccess = (values: Choice[]) => {
setChoices(
values.reduce(
(acc, value) => ({
...acc,
[value.element]: [...(acc[value.element] != null ? acc[value.element] : []), value],
}),
defaultFields
)
);
};
const onChoicesSuccess = (values: Choice[]) => {
setChoices(
values.reduce(
(acc, value) => ({
...acc,
[value.element]: [...(acc[value.element] != null ? acc[value.element] : []), value],
}),
defaultFields
)
);
};
const { isLoading: isLoadingChoices } = useGetChoices({
http,
toastNotifications: notifications.toasts,
connector,
fields: useGetChoicesFields,
onSuccess: onChoicesSuccess,
});
const { isLoading: isLoadingChoices } = useGetChoices({
http,
toastNotifications: notifications.toasts,
connector,
fields: useGetChoicesFields,
onSuccess: onChoicesSuccess,
});
const categoryOptions = useMemo(() => choicesToEuiOptions(choices.category), [choices.category]);
const priorityOptions = useMemo(() => choicesToEuiOptions(choices.priority), [choices.priority]);
const categoryOptions = useMemo(
() => choicesToEuiOptions(choices.category),
[choices.category]
);
const priorityOptions = useMemo(
() => choicesToEuiOptions(choices.priority),
[choices.priority]
);
const subcategoryOptions = useMemo(
() =>
choicesToEuiOptions(
choices.subcategory.filter((choice) => choice.dependent_value === category)
),
[choices.subcategory, category]
);
const subcategoryOptions = useMemo(
() =>
choicesToEuiOptions(
choices.subcategory.filter((choice) => choice.dependent_value === category)
),
[choices.subcategory, category]
);
const listItems = useMemo(
() => [
...(destIp != null && destIp
? [
{
title: i18n.DEST_IP,
description: i18n.ALERT_FIELD_ENABLED_TEXT,
},
]
: []),
...(sourceIp != null && sourceIp
? [
{
title: i18n.SOURCE_IP,
description: i18n.ALERT_FIELD_ENABLED_TEXT,
},
]
: []),
...(malwareUrl != null && malwareUrl
? [
{
title: i18n.MALWARE_URL,
description: i18n.ALERT_FIELD_ENABLED_TEXT,
},
]
: []),
...(malwareHash != null && malwareHash
? [
{
title: i18n.MALWARE_HASH,
description: i18n.ALERT_FIELD_ENABLED_TEXT,
},
]
: []),
...(priority != null && priority.length > 0
? [
{
title: i18n.PRIORITY,
description: priorityOptions.find((option) => `${option.value}` === priority)?.text,
},
]
: []),
...(category != null && category.length > 0
? [
{
title: i18n.CATEGORY,
description: categoryOptions.find((option) => `${option.value}` === category)?.text,
},
]
: []),
...(subcategory != null && subcategory.length > 0
? [
{
title: i18n.SUBCATEGORY,
description: subcategoryOptions.find((option) => `${option.value}` === subcategory)
?.text,
},
]
: []),
],
[
category,
categoryOptions,
destIp,
malwareHash,
malwareUrl,
priority,
priorityOptions,
sourceIp,
subcategory,
subcategoryOptions,
]
);
const listItems = useMemo(
() => [
...(destIp != null && destIp
? [
{
title: i18n.DEST_IP,
description: i18n.ALERT_FIELD_ENABLED_TEXT,
},
]
: []),
...(sourceIp != null && sourceIp
? [
{
title: i18n.SOURCE_IP,
description: i18n.ALERT_FIELD_ENABLED_TEXT,
},
]
: []),
...(malwareUrl != null && malwareUrl
? [
{
title: i18n.MALWARE_URL,
description: i18n.ALERT_FIELD_ENABLED_TEXT,
},
]
: []),
...(malwareHash != null && malwareHash
? [
{
title: i18n.MALWARE_HASH,
description: i18n.ALERT_FIELD_ENABLED_TEXT,
},
]
: []),
...(priority != null && priority.length > 0
? [
{
title: i18n.PRIORITY,
description: priorityOptions.find((option) => `${option.value}` === priority)?.text,
},
]
: []),
...(category != null && category.length > 0
? [
{
title: i18n.CATEGORY,
description: categoryOptions.find((option) => `${option.value}` === category)?.text,
},
]
: []),
...(subcategory != null && subcategory.length > 0
? [
{
title: i18n.SUBCATEGORY,
description: subcategoryOptions.find((option) => `${option.value}` === subcategory)
?.text,
},
]
: []),
],
[
category,
categoryOptions,
destIp,
malwareHash,
malwareUrl,
priority,
priorityOptions,
sourceIp,
subcategory,
subcategoryOptions,
]
);
// Set field at initialization
useEffect(() => {
if (init.current) {
init.current = false;
onChange({ category, destIp, malwareHash, malwareUrl, priority, sourceIp, subcategory });
}
}, [category, destIp, malwareHash, malwareUrl, onChange, priority, sourceIp, subcategory]);
// Set field at initialization
useEffect(() => {
if (init.current) {
init.current = false;
onChange({ category, destIp, malwareHash, malwareUrl, priority, sourceIp, subcategory });
}
}, [category, destIp, malwareHash, malwareUrl, onChange, priority, sourceIp, subcategory]);
return (
<>
{showConnectorWarning && (
return (
<>
{showConnectorWarning && (
<EuiFlexGroup>
<EuiFlexItem>
<DeprecatedCallout />
</EuiFlexItem>
</EuiFlexGroup>
)}
{isEdit ? (
<div data-test-subj="connector-fields-sn-sir">
<EuiFlexGroup>
<EuiFlexItem>
<DeprecatedCallout />
<EuiFormRow fullWidth label={i18n.ALERT_FIELDS_LABEL}>
<>
<EuiFlexGroup>
<EuiFlexItem>
<EuiCheckbox
id="destIpCheckbox"
data-test-subj="destIpCheckbox"
label={i18n.DEST_IP}
checked={destIp ?? false}
compressed
onChange={(e) => onChangeCb('destIp', e.target.checked)}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiCheckbox
id="sourceIpCheckbox"
data-test-subj="sourceIpCheckbox"
label={i18n.SOURCE_IP}
checked={sourceIp ?? false}
compressed
onChange={(e) => onChangeCb('sourceIp', e.target.checked)}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup>
<EuiFlexItem>
<EuiCheckbox
id="malwareUrlCheckbox"
data-test-subj="malwareUrlCheckbox"
label={i18n.MALWARE_URL}
checked={malwareUrl ?? false}
compressed
onChange={(e) => onChangeCb('malwareUrl', e.target.checked)}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiCheckbox
id="malwareHashCheckbox"
data-test-subj="malwareHashCheckbox"
label={i18n.MALWARE_HASH}
checked={malwareHash ?? false}
compressed
onChange={(e) => onChangeCb('malwareHash', e.target.checked)}
/>
</EuiFlexItem>
</EuiFlexGroup>
</>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
)}
{isEdit ? (
<div data-test-subj="connector-fields-sn-sir">
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow fullWidth label={i18n.ALERT_FIELDS_LABEL}>
<>
<EuiFlexGroup>
<EuiFlexItem>
<EuiCheckbox
id="destIpCheckbox"
data-test-subj="destIpCheckbox"
label={i18n.DEST_IP}
checked={destIp ?? false}
compressed
onChange={(e) => onChangeCb('destIp', e.target.checked)}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiCheckbox
id="sourceIpCheckbox"
data-test-subj="sourceIpCheckbox"
label={i18n.SOURCE_IP}
checked={sourceIp ?? false}
compressed
onChange={(e) => onChangeCb('sourceIp', e.target.checked)}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup>
<EuiFlexItem>
<EuiCheckbox
id="malwareUrlCheckbox"
data-test-subj="malwareUrlCheckbox"
label={i18n.MALWARE_URL}
checked={malwareUrl ?? false}
compressed
onChange={(e) => onChangeCb('malwareUrl', e.target.checked)}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiCheckbox
id="malwareHashCheckbox"
data-test-subj="malwareHashCheckbox"
label={i18n.MALWARE_HASH}
checked={malwareHash ?? false}
compressed
onChange={(e) => onChangeCb('malwareHash', e.target.checked)}
/>
</EuiFlexItem>
</EuiFlexGroup>
</>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow fullWidth label={i18n.PRIORITY}>
<EuiSelect
fullWidth
data-test-subj="prioritySelect"
hasNoInitialSelection
isLoading={isLoadingChoices}
disabled={isLoadingChoices}
options={priorityOptions}
value={priority ?? undefined}
onChange={(e) => onChangeCb('priority', e.target.value)}
/>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow fullWidth label={i18n.CATEGORY}>
<EuiSelect
fullWidth
data-test-subj="categorySelect"
options={categoryOptions}
value={category ?? undefined}
isLoading={isLoadingChoices}
disabled={isLoadingChoices}
hasNoInitialSelection
onChange={(e) =>
onChange({ ...fields, category: e.target.value, subcategory: null })
}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow fullWidth label={i18n.PRIORITY}>
<EuiSelect
fullWidth
data-test-subj="prioritySelect"
hasNoInitialSelection
isLoading={isLoadingChoices}
disabled={isLoadingChoices}
options={priorityOptions}
value={priority ?? undefined}
onChange={(e) => onChangeCb('priority', e.target.value)}
/>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow fullWidth label={i18n.CATEGORY}>
<EuiSelect
fullWidth
data-test-subj="categorySelect"
options={categoryOptions}
value={category ?? undefined}
isLoading={isLoadingChoices}
disabled={isLoadingChoices}
hasNoInitialSelection
onChange={(e) =>
onChange({ ...fields, category: e.target.value, subcategory: null })
}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
{subcategoryOptions?.length > 0 ? (
<EuiFormRow fullWidth label={i18n.SUBCATEGORY}>
<EuiSelect
fullWidth
@ -286,24 +279,26 @@ const ServiceNowSIRFieldsComponent: React.FunctionComponent<
onChange={(e) => onChangeCb('subcategory', e.target.value)}
/>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
</div>
) : (
<EuiFlexGroup>
<EuiFlexItem>
<ConnectorCard
connectorType={ConnectorTypes.serviceNowSIR}
title={connector.name}
listItems={listItems}
isLoading={false}
/>
) : null}
</EuiFlexItem>
</EuiFlexGroup>
)}
</>
);
};
</div>
) : (
<EuiFlexGroup>
<EuiFlexItem>
<ConnectorCard
connectorType={ConnectorTypes.serviceNowSIR}
title={connector.name}
listItems={listItems}
isLoading={false}
/>
</EuiFlexItem>
</EuiFlexGroup>
)}
</>
);
};
ServiceNowSIRFieldsComponent.displayName = 'ServiceNowSIRFieldsComponent';
// eslint-disable-next-line import/no-default-export
export { ServiceNowSIRFieldsComponent as default };

View file

@ -39,6 +39,7 @@ import { FormContext } from './form_context';
import { CreateCaseFormFields, CreateCaseFormFieldsProps } from './form';
import { SubmitCaseButton } from './submit_button';
import { usePostPushToService } from '../../containers/use_post_push_to_service';
import { Choice } from '../connectors/servicenow/types';
const sampleId = 'case-id';
@ -114,6 +115,7 @@ describe('Create case', () => {
const onFormSubmitSuccess = jest.fn();
const afterCaseCreated = jest.fn();
const postComment = jest.fn();
let onChoicesSuccess: (values: Choice[]) => void;
beforeAll(() => {
postCase.mockResolvedValue({
@ -129,7 +131,12 @@ describe('Create case', () => {
useGetSeverityMock.mockReturnValue(useGetSeverityResponse);
useGetIssueTypesMock.mockReturnValue(useGetIssueTypesResponse);
useGetFieldsByIssueTypeMock.mockReturnValue(useGetFieldsByIssueTypeResponse);
useGetChoicesMock.mockReturnValue(useGetChoicesResponse);
useGetChoicesMock.mockImplementation(
({ onSuccess }: { onSuccess: (values: Choice[]) => void }) => {
onChoicesSuccess = onSuccess;
return useGetChoicesResponse;
}
);
(useGetTags as jest.Mock).mockImplementation(() => ({
tags: sampleTags,
@ -512,6 +519,11 @@ describe('Create case', () => {
expect(wrapper.find(`[data-test-subj="connector-fields-sn-itsm"]`).exists()).toBeTruthy();
});
// we need the choices response to conditionally show the subcategory select
act(() => {
onChoicesSuccess(useGetChoicesResponse.choices);
});
['severitySelect', 'urgencySelect', 'impactSelect'].forEach((subj) => {
wrapper
.find(`select[data-test-subj="${subj}"]`)
@ -602,6 +614,11 @@ describe('Create case', () => {
expect(wrapper.find(`[data-test-subj="connector-fields-sn-sir"]`).exists()).toBeTruthy();
});
// we need the choices response to conditionally show the subcategory select
act(() => {
onChoicesSuccess(useGetChoicesResponse.choices);
});
wrapper
.find('[data-test-subj="destIpCheckbox"] input')
.first()

View file

@ -13,6 +13,7 @@ import { ActionConnector } from '../../../../types';
import { useGetChoices } from './use_get_choices';
import ServiceNowITSMParamsFields from './servicenow_itsm_params';
import { Choice } from './types';
import { merge } from 'lodash';
jest.mock('./use_get_choices');
jest.mock('../../../../common/lib/kibana');
@ -72,6 +73,12 @@ const useGetChoicesResponse = {
value: 'os',
element: 'subcategory',
},
{
dependent_value: '',
label: 'Failed Login',
value: 'failed_login',
element: 'category',
},
...['severity', 'urgency', 'impact']
.map((element) => [
{
@ -116,6 +123,10 @@ describe('ServiceNowITSMParamsFields renders', () => {
test('all params fields is rendered', () => {
const wrapper = mountWithIntl(<ServiceNowITSMParamsFields {...defaultProps} />);
act(() => {
onChoices(useGetChoicesResponse.choices);
});
wrapper.update();
expect(wrapper.find('[data-test-subj="urgencySelect"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="severitySelect"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="impactSelect"]').exists()).toBeTruthy();
@ -191,6 +202,10 @@ describe('ServiceNowITSMParamsFields renders', () => {
value: 'software',
text: 'Software',
},
{
value: 'failed_login',
text: 'Failed Login',
},
]);
});
@ -227,6 +242,26 @@ describe('ServiceNowITSMParamsFields renders', () => {
);
});
it('should hide subcategory if selecting a category without subcategories', async () => {
const newProps = merge({}, defaultProps, {
actionParams: {
subActionParams: {
incident: {
category: 'failed_login',
subcategory: null,
},
},
},
});
const wrapper = mountWithIntl(<ServiceNowITSMParamsFields {...newProps} />);
act(() => {
onChoices(useGetChoicesResponse.choices);
});
wrapper.update();
expect(wrapper.find('[data-test-subj="subcategorySelect"]').exists()).toBeFalsy();
});
describe('UI updates', () => {
const changeEvent = { target: { value: 'Bug' } } as React.ChangeEvent<HTMLSelectElement>;
const simpleFields = [
@ -247,6 +282,10 @@ describe('ServiceNowITSMParamsFields renders', () => {
simpleFields.forEach((field) =>
test(`${field.key} update triggers editAction :D`, () => {
const wrapper = mountWithIntl(<ServiceNowITSMParamsFields {...defaultProps} />);
act(() => {
onChoices(useGetChoicesResponse.choices);
});
wrapper.update();
const theField = wrapper.find(field.dataTestSubj).first();
theField.prop('onChange')!(changeEvent);
expect(editAction.mock.calls[0][1].incident[field.key]).toEqual(changeEvent.target.value);

View file

@ -225,19 +225,21 @@ const ServiceNowParamsFields: React.FunctionComponent<
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow fullWidth label={i18n.SUBCATEGORY_LABEL}>
<EuiSelect
fullWidth
data-test-subj="subcategorySelect"
hasNoInitialSelection
isLoading={isLoadingChoices}
disabled={isLoadingChoices}
options={subcategoryOptions}
// Needs an empty string instead of undefined to select the blank option when changing categories
value={incident.subcategory ?? ''}
onChange={(e) => editSubActionProperty('subcategory', e.target.value)}
/>
</EuiFormRow>
{subcategoryOptions?.length > 0 ? (
<EuiFormRow fullWidth label={i18n.SUBCATEGORY_LABEL}>
<EuiSelect
fullWidth
data-test-subj="subcategorySelect"
hasNoInitialSelection
isLoading={isLoadingChoices}
disabled={isLoadingChoices}
options={subcategoryOptions}
// Needs an empty string instead of undefined to select the blank option when changing categories
value={incident.subcategory ?? ''}
onChange={(e) => editSubActionProperty('subcategory', e.target.value)}
/>
</EuiFormRow>
) : null}
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />

View file

@ -13,6 +13,7 @@ import { ActionConnector } from '../../../../types';
import { useGetChoices } from './use_get_choices';
import ServiceNowSIRParamsFields from './servicenow_sir_params';
import { Choice } from './types';
import { merge } from 'lodash';
jest.mock('./use_get_choices');
jest.mock('../../../../common/lib/kibana');
@ -80,6 +81,12 @@ const choicesResponse = {
value: 'Denial of Service',
element: 'category',
},
{
dependent_value: '',
label: 'Failed Login',
value: 'failed_login',
element: 'category',
},
{
dependent_value: 'Denial of Service',
label: 'Inbound or outbound',
@ -144,6 +151,10 @@ describe('ServiceNowSIRParamsFields renders', () => {
test('all params fields is rendered', () => {
const wrapper = mountWithIntl(<ServiceNowSIRParamsFields {...defaultProps} />);
act(() => {
onChoicesSuccess(choicesResponse.choices);
});
wrapper.update();
expect(wrapper.find('[data-test-subj="short_descriptionInput"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="correlation_idInput"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="correlation_displayInput"]').exists()).toBeTruthy();
@ -219,6 +230,7 @@ describe('ServiceNowSIRParamsFields renders', () => {
text: 'Criminal activity/investigation',
},
{ value: 'Denial of Service', text: 'Denial of Service' },
{ value: 'failed_login', text: 'Failed Login' },
]);
});
@ -275,6 +287,24 @@ describe('ServiceNowSIRParamsFields renders', () => {
},
]);
});
it('should hide subcategory if selecting a category without subcategories', async () => {
const newProps = merge({}, defaultProps, {
actionParams: {
subActionParams: {
incident: {
category: 'failed_login',
subcategory: null,
},
},
},
});
const wrapper = mountWithIntl(<ServiceNowSIRParamsFields {...newProps} />);
act(() => {
onChoicesSuccess(choicesResponse.choices);
});
wrapper.update();
expect(wrapper.find('[data-test-subj="subcategorySelect"]').exists()).toBeFalsy();
});
describe('UI updates', () => {
const changeEvent = { target: { value: 'Bug' } } as React.ChangeEvent<HTMLSelectElement>;
@ -294,6 +324,10 @@ describe('ServiceNowSIRParamsFields renders', () => {
simpleFields.forEach((field) =>
test(`${field.key} update triggers editAction :D`, () => {
const wrapper = mountWithIntl(<ServiceNowSIRParamsFields {...defaultProps} />);
act(() => {
onChoicesSuccess(choicesResponse.choices);
});
wrapper.update();
const theField = wrapper.find(field.dataTestSubj).first();
theField.prop('onChange')!(changeEvent);
expect(editAction.mock.calls[0][1].incident[field.key]).toEqual(changeEvent.target.value);

View file

@ -212,19 +212,21 @@ const ServiceNowSIRParamsFields: React.FunctionComponent<
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow fullWidth label={i18n.SUBCATEGORY_LABEL}>
<EuiSelect
fullWidth
data-test-subj="subcategorySelect"
hasNoInitialSelection
isLoading={isLoadingChoices}
disabled={isLoadingChoices}
options={subcategoryOptions}
// Needs an empty string instead of undefined to select the blank option when changing categories
value={incident.subcategory ?? ''}
onChange={(e) => editSubActionProperty('subcategory', e.target.value)}
/>
</EuiFormRow>
{subcategoryOptions?.length > 0 ? (
<EuiFormRow fullWidth label={i18n.SUBCATEGORY_LABEL}>
<EuiSelect
fullWidth
data-test-subj="subcategorySelect"
hasNoInitialSelection
isLoading={isLoadingChoices}
disabled={isLoadingChoices}
options={subcategoryOptions}
// Needs an empty string instead of undefined to select the blank option when changing categories
value={incident.subcategory ?? ''}
onChange={(e) => editSubActionProperty('subcategory', e.target.value)}
/>
</EuiFormRow>
) : null}
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />