mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Cases] Only show sub-category if there are available items in the cases form (#123948)
This commit is contained in:
parent
41e756c989
commit
a22b948cb5
10 changed files with 449 additions and 302 deletions
|
@ -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) => [
|
||||
{
|
||||
|
|
|
@ -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')!({
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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" />
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue