From 638f166f71e9fbb59bfe286d807aec587696be37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=A1nchez?= Date: Tue, 16 Mar 2021 13:36:06 +0100 Subject: [PATCH] [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 --- .../condition_entry_input/index.test.tsx | 141 ++++++++++++++++++ .../condition_entry_input/index.tsx | 20 ++- .../create_trusted_app_form.test.tsx | 6 +- .../pages/trusted_apps/view/translations.ts | 15 ++ 4 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/condition_entry_input/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/condition_entry_input/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/condition_entry_input/index.test.tsx new file mode 100644 index 000000000000..4e9ec3a0883a --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/condition_entry_input/index.test.tsx @@ -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 = { + 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 + ) => ( + + ); + + 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; + 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; + 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; + expect(superSelectProps.options.length).toBe(2); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/condition_entry_input/index.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/condition_entry_input/index.tsx index 72467cf28ec5..f85f00810bc7 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/condition_entry_input/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/condition_entry_input/index.tsx @@ -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( ]); const fieldOptions = useMemo>>(() => { + const getDropdownDisplay = (field: ConditionEntryField) => ( + <> + {CONDITION_FIELD_TITLE[field]} + + {CONDITION_FIELD_DESCRIPTION[field]} + + + ); + 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, }, diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.test.tsx index 441847bd88bb..7d056ae6999e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.test.tsx @@ -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', () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts index 3b9db3f8a1c0..b594c355a698 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts @@ -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',