[SECURITY_SOLUTION] Add helper text on entry input for trusted applications (#94563)
* Added text help for each entry input option * Add new unit test * Fix wrong import on test file * Change entry variable to readonly. Use it.each instead of a for loop * Move function inside useMemo since it is only used there * Remove old commented code * Update failing test
This commit is contained in:
parent
e830e077d3
commit
638f166f71
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* 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 { shallow, mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import { keys } from 'lodash';
|
||||
import {
|
||||
ConditionEntry,
|
||||
ConditionEntryField,
|
||||
OperatingSystem,
|
||||
} from '../../../../../../../common/endpoint/types';
|
||||
|
||||
import { ConditionEntryInput } from '.';
|
||||
import { EuiSuperSelectProps } from '@elastic/eui';
|
||||
|
||||
let onRemoveMock: jest.Mock;
|
||||
let onChangeMock: jest.Mock;
|
||||
let onVisitedMock: jest.Mock;
|
||||
|
||||
const entry: Readonly<ConditionEntry> = {
|
||||
field: ConditionEntryField.HASH,
|
||||
type: 'match',
|
||||
operator: 'included',
|
||||
value: 'trustedApp',
|
||||
};
|
||||
|
||||
describe('Condition entry input', () => {
|
||||
beforeEach(() => {
|
||||
onRemoveMock = jest.fn();
|
||||
onChangeMock = jest.fn();
|
||||
onVisitedMock = jest.fn();
|
||||
});
|
||||
|
||||
const getElement = (
|
||||
subject: string,
|
||||
os: OperatingSystem = OperatingSystem.WINDOWS,
|
||||
isRemoveDisabled: boolean = false
|
||||
) => (
|
||||
<ConditionEntryInput
|
||||
os={os}
|
||||
entry={entry}
|
||||
showLabels
|
||||
onRemove={onRemoveMock}
|
||||
onChange={onChangeMock}
|
||||
onVisited={onVisitedMock}
|
||||
data-test-subj={subject}
|
||||
isRemoveDisabled={isRemoveDisabled}
|
||||
/>
|
||||
);
|
||||
|
||||
it.each(keys(ConditionEntryField).map((k) => [k]))(
|
||||
'should call on change for field input with value %s',
|
||||
(field) => {
|
||||
const element = shallow(getElement('testOnChange'));
|
||||
expect(onChangeMock).toHaveBeenCalledTimes(0);
|
||||
element
|
||||
.find('[data-test-subj="testOnChange-field"]')
|
||||
.first()
|
||||
.simulate('change', { target: { value: field } });
|
||||
expect(onChangeMock).toHaveBeenCalledTimes(1);
|
||||
expect(onChangeMock).toHaveBeenCalledWith(
|
||||
{
|
||||
...entry,
|
||||
field: { target: { value: field } },
|
||||
},
|
||||
entry
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it('should call on remove for field input', () => {
|
||||
const element = mount(getElement('testOnRemove'));
|
||||
expect(onRemoveMock).toHaveBeenCalledTimes(0);
|
||||
element.find('[data-test-subj="testOnRemove-remove"]').first().simulate('click');
|
||||
expect(onRemoveMock).toHaveBeenCalledTimes(1);
|
||||
expect(onRemoveMock).toHaveBeenCalledWith(entry);
|
||||
});
|
||||
|
||||
it('should not be able to call on remove for field input because disabled', () => {
|
||||
const element = mount(getElement('testOnRemove', OperatingSystem.WINDOWS, true));
|
||||
expect(onRemoveMock).toHaveBeenCalledTimes(0);
|
||||
element.find('[data-test-subj="testOnRemove-remove"]').first().simulate('click');
|
||||
expect(onRemoveMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should call on visited for field input', () => {
|
||||
const element = shallow(getElement('testOnVisited'));
|
||||
expect(onVisitedMock).toHaveBeenCalledTimes(0);
|
||||
element.find('[data-test-subj="testOnVisited-value"]').first().simulate('blur');
|
||||
expect(onVisitedMock).toHaveBeenCalledTimes(1);
|
||||
expect(onVisitedMock).toHaveBeenCalledWith(entry);
|
||||
});
|
||||
|
||||
it('should change value for field input', () => {
|
||||
const element = shallow(getElement('testOnChange'));
|
||||
expect(onChangeMock).toHaveBeenCalledTimes(0);
|
||||
element
|
||||
.find('[data-test-subj="testOnChange-value"]')
|
||||
.first()
|
||||
.simulate('change', { target: { value: 'new value' } });
|
||||
expect(onChangeMock).toHaveBeenCalledTimes(1);
|
||||
expect(onChangeMock).toHaveBeenCalledWith(
|
||||
{
|
||||
...entry,
|
||||
value: 'new value',
|
||||
},
|
||||
entry
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to select three options when WINDOWS OS', () => {
|
||||
const element = mount(getElement('testCheckSignatureOption'));
|
||||
const superSelectProps = element
|
||||
.find('[data-test-subj="testCheckSignatureOption-field"]')
|
||||
.first()
|
||||
.props() as EuiSuperSelectProps<string>;
|
||||
expect(superSelectProps.options.length).toBe(3);
|
||||
});
|
||||
|
||||
it('should be able to select two options when LINUX OS', () => {
|
||||
const element = mount(getElement('testCheckSignatureOption', OperatingSystem.LINUX));
|
||||
const superSelectProps = element
|
||||
.find('[data-test-subj="testCheckSignatureOption-field"]')
|
||||
.first()
|
||||
.props() as EuiSuperSelectProps<string>;
|
||||
expect(superSelectProps.options.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should be able to select two options when MAC OS', () => {
|
||||
const element = mount(getElement('testCheckSignatureOption', OperatingSystem.MAC));
|
||||
const superSelectProps = element
|
||||
.find('[data-test-subj="testCheckSignatureOption-field"]')
|
||||
.first()
|
||||
.props() as EuiSuperSelectProps<string>;
|
||||
expect(superSelectProps.options.length).toBe(2);
|
||||
});
|
||||
});
|
|
@ -15,6 +15,7 @@ import {
|
|||
EuiFormRow,
|
||||
EuiSuperSelect,
|
||||
EuiSuperSelectOption,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import {
|
||||
|
@ -23,7 +24,12 @@ import {
|
|||
OperatingSystem,
|
||||
} from '../../../../../../../common/endpoint/types';
|
||||
|
||||
import { CONDITION_FIELD_TITLE, ENTRY_PROPERTY_TITLES, OPERATOR_TITLE } from '../../translations';
|
||||
import {
|
||||
CONDITION_FIELD_DESCRIPTION,
|
||||
CONDITION_FIELD_TITLE,
|
||||
ENTRY_PROPERTY_TITLES,
|
||||
OPERATOR_TITLE,
|
||||
} from '../../translations';
|
||||
|
||||
const ConditionEntryCell = memo<{
|
||||
showLabel: boolean;
|
||||
|
@ -75,18 +81,30 @@ export const ConditionEntryInput = memo<ConditionEntryInputProps>(
|
|||
]);
|
||||
|
||||
const fieldOptions = useMemo<Array<EuiSuperSelectOption<string>>>(() => {
|
||||
const getDropdownDisplay = (field: ConditionEntryField) => (
|
||||
<>
|
||||
{CONDITION_FIELD_TITLE[field]}
|
||||
<EuiText size="xs" color="subdued">
|
||||
{CONDITION_FIELD_DESCRIPTION[field]}
|
||||
</EuiText>
|
||||
</>
|
||||
);
|
||||
|
||||
return [
|
||||
{
|
||||
dropdownDisplay: getDropdownDisplay(ConditionEntryField.HASH),
|
||||
inputDisplay: CONDITION_FIELD_TITLE[ConditionEntryField.HASH],
|
||||
value: ConditionEntryField.HASH,
|
||||
},
|
||||
{
|
||||
dropdownDisplay: getDropdownDisplay(ConditionEntryField.PATH),
|
||||
inputDisplay: CONDITION_FIELD_TITLE[ConditionEntryField.PATH],
|
||||
value: ConditionEntryField.PATH,
|
||||
},
|
||||
...(os === OperatingSystem.WINDOWS
|
||||
? [
|
||||
{
|
||||
dropdownDisplay: getDropdownDisplay(ConditionEntryField.SIGNER),
|
||||
inputDisplay: CONDITION_FIELD_TITLE[ConditionEntryField.SIGNER],
|
||||
value: ConditionEntryField.SIGNER,
|
||||
},
|
||||
|
|
|
@ -165,7 +165,11 @@ describe('When showing the Trusted App Create Form', () => {
|
|||
'.euiSuperSelect__listbox button.euiSuperSelect__item'
|
||||
)
|
||||
).map((button) => button.textContent);
|
||||
expect(options).toEqual(['Hash', 'Path', 'Signature']);
|
||||
expect(options).toEqual([
|
||||
'Hashmd5, sha1, or sha256',
|
||||
'PathThe full path of the application',
|
||||
'SignatureThe signer of the application',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should show the value field as required', () => {
|
||||
|
|
|
@ -37,6 +37,21 @@ export const CONDITION_FIELD_TITLE: { [K in ConditionEntryField]: string } = {
|
|||
),
|
||||
};
|
||||
|
||||
export const CONDITION_FIELD_DESCRIPTION: { [K in ConditionEntryField]: string } = {
|
||||
[ConditionEntryField.HASH]: i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.description.hash',
|
||||
{ defaultMessage: 'md5, sha1, or sha256' }
|
||||
),
|
||||
[ConditionEntryField.PATH]: i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.description.path',
|
||||
{ defaultMessage: 'The full path of the application' }
|
||||
),
|
||||
[ConditionEntryField.SIGNER]: i18n.translate(
|
||||
'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.description.signature',
|
||||
{ defaultMessage: 'The signer of the application' }
|
||||
),
|
||||
};
|
||||
|
||||
export const OPERATOR_TITLE: { [K in ConditionEntry['operator']]: string } = {
|
||||
included: i18n.translate('xpack.securitySolution.trustedapps.card.operator.includes', {
|
||||
defaultMessage: 'is',
|
||||
|
|
Loading…
Reference in a new issue