[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:
David Sánchez 2021-03-16 13:36:06 +01:00 committed by GitHub
parent e830e077d3
commit 638f166f71
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 180 additions and 2 deletions

View file

@ -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);
});
});

View file

@ -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,
},

View file

@ -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', () => {

View file

@ -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',