Skip to content

Commit

Permalink
[Security Solution][Exceptions] - Fixes builder overflow and updates …
Browse files Browse the repository at this point in the history
…ux for nested entries (#74262)

## Summary

- updates the builder nested entries so that the children do not display the parent path - so instead of `parent.child` it just shows `child`
- updates the builder to fix overflow issue
  • Loading branch information
yctercero authored Aug 5, 2020
1 parent 549c256 commit e23c5ea
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ export const FieldComponent: React.FC<OperatorProps> = ({
onChange,
}): JSX.Element => {
const [touched, setIsTouched] = useState(false);
const getLabel = useCallback((field): string => field.name, []);
const getLabel = useCallback(({ name }): string => name, []);
const optionsMemo = useMemo((): IFieldType[] => {
if (indexPattern != null) {
if (fieldTypeFilter.length > 0) {
return indexPattern.fields.filter((f) => fieldTypeFilter.includes(f.type));
return indexPattern.fields.filter(({ type }) => fieldTypeFilter.includes(type));
} else {
return indexPattern.fields;
}
Expand Down Expand Up @@ -68,6 +68,10 @@ export const FieldComponent: React.FC<OperatorProps> = ({
onChange(newValues);
};

const handleTouch = useCallback((): void => {
setIsTouched(true);
}, [setIsTouched]);

return (
<EuiComboBox
placeholder={placeholder}
Expand All @@ -78,7 +82,7 @@ export const FieldComponent: React.FC<OperatorProps> = ({
isDisabled={isDisabled}
isClearable={isClearable}
isInvalid={isRequired ? touched && selectedField == null : false}
onFocus={() => setIsTouched(true)}
onFocus={handleTouch}
singleSelection={{ asPlainText: true }}
data-test-subj="fieldAutocompleteComboBox"
style={{ width: `${fieldInputWidth}px` }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,12 @@ export function getGenericComboBoxProps<T>({
const newLabels = options.map(getLabel);
const newComboOptions: EuiComboBoxOptionOption[] = newLabels.map((label) => ({ label }));
const newSelectedComboOptions = selectedOptions
.map(getLabel)
.filter((option) => {
return options.indexOf(option) !== -1;
return newLabels.indexOf(option) !== -1;
})
.map((option) => {
return newComboOptions[options.indexOf(option)];
return newComboOptions[newLabels.indexOf(option)];
});

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
import React, { useCallback } from 'react';
import { EuiFormRow, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import styled from 'styled-components';

import { IFieldType, IIndexPattern } from '../../../../../../../../src/plugins/data/common';
import { FieldComponent } from '../../autocomplete/field';
Expand All @@ -29,6 +30,10 @@ import {
} from './helpers';
import { EXCEPTION_OPERATORS_ONLY_LISTS } from '../../autocomplete/operators';

const MyValuesInput = styled(EuiFlexItem)`
overflow: hidden;
`;

interface EntryItemProps {
entry: FormattedBuilderEntry;
indexPattern: IIndexPattern;
Expand Down Expand Up @@ -257,12 +262,12 @@ export const BuilderEntryItem: React.FC<EntryItemProps> = ({
>
<EuiFlexItem grow={false}>{renderFieldInput(showLabel)}</EuiFlexItem>
<EuiFlexItem grow={false}>{renderOperatorInput(showLabel)}</EuiFlexItem>
<EuiFlexItem grow={6}>
<MyValuesInput grow={6}>
{renderFieldValueInput(
showLabel,
entry.nested === 'parent' ? OperatorTypeEnum.EXISTS : entry.operator.type
)}
</EuiFlexItem>
</MyValuesInput>
</EuiFlexGroup>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ const MyBeautifulLine = styled(EuiFlexItem)`
}
`;

const MyOverflowContainer = styled(EuiFlexItem)`
overflow: hidden;
width: 100%;
`;

interface BuilderExceptionListItemProps {
exceptionItem: ExceptionsBuilderExceptionItem;
exceptionId: string;
Expand Down Expand Up @@ -98,13 +103,13 @@ export const BuilderExceptionListItemComponent = React.memo<BuilderExceptionList
exceptionItemIndex={exceptionItemIndex}
/>
)}
<EuiFlexItem grow={6}>
<MyOverflowContainer grow={6}>
<EuiFlexGroup gutterSize="s" direction="column">
{entries.map((item, index) => (
<EuiFlexItem key={`${exceptionId}-${index}`} grow={1}>
<EuiFlexGroup gutterSize="xs" alignItems="center" direction="row">
{item.nested === 'child' && <MyBeautifulLine grow={false} />}
<EuiFlexItem grow={1}>
<MyOverflowContainer grow={1}>
<BuilderEntryItem
entry={item}
indexPattern={indexPattern}
Expand All @@ -115,7 +120,7 @@ export const BuilderExceptionListItemComponent = React.memo<BuilderExceptionList
onChange={handleEntryChange}
onlyShowListOperators={onlyShowListOperators}
/>
</EuiFlexItem>
</MyOverflowContainer>
<BuilderEntryDeleteButtonComponent
entries={exceptionItem.entries}
isOnlyItem={isOnlyItem}
Expand All @@ -128,7 +133,7 @@ export const BuilderExceptionListItemComponent = React.memo<BuilderExceptionList
</EuiFlexItem>
))}
</EuiFlexGroup>
</EuiFlexItem>
</MyOverflowContainer>
</EuiFlexGroup>
</EuiFlexItem>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,7 @@ describe('Exception builder helpers', () => {
const payloadItem: FormattedBuilderEntry = getMockNestedBuilderEntry();
const output = getFilteredIndexPatterns(payloadIndexPattern, payloadItem, 'detection');
const expected: IIndexPattern = {
fields: [
{ ...getField('nestedField.child') },
{ ...getField('nestedField.nestedChild.doublyNestedChild') },
],
fields: [{ ...getField('nestedField.child'), name: 'child' }],
id: '1234',
title: 'logstash-*',
};
Expand Down Expand Up @@ -243,7 +240,7 @@ describe('Exception builder helpers', () => {
};
const output = getFilteredIndexPatterns(payloadIndexPattern, payloadItem, 'endpoint');
const expected: IIndexPattern = {
fields: [getEndpointField('file.Ext.code_signature.status')],
fields: [{ ...getEndpointField('file.Ext.code_signature.status'), name: 'status' }],
id: '1234',
title: 'logstash-*',
};
Expand Down Expand Up @@ -405,7 +402,7 @@ describe('Exception builder helpers', () => {
aggregatable: false,
count: 0,
esTypes: ['text'],
name: 'nestedField.child',
name: 'child',
readFromDocValues: false,
scripted: false,
searchable: true,
Expand Down Expand Up @@ -600,7 +597,7 @@ describe('Exception builder helpers', () => {
aggregatable: false,
count: 0,
esTypes: ['text'],
name: 'nestedField.child',
name: 'child',
readFromDocValues: false,
scripted: false,
searchable: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,20 @@ export const getFilteredIndexPatterns = (
// when user has selected a nested entry, only fields with the common parent are shown
return {
...indexPatterns,
fields: indexPatterns.fields.filter(
(field) =>
field.subType != null &&
field.subType.nested != null &&
item.parent != null &&
field.subType.nested.path.startsWith(item.parent.parent.field)
),
fields: indexPatterns.fields
.filter((indexField) => {
const fieldHasCommonParentPath =
indexField.subType != null &&
indexField.subType.nested != null &&
item.parent != null &&
indexField.subType.nested.path === item.parent.parent.field;

return fieldHasCommonParentPath;
})
.map((f) => {
const fieldNameWithoutParentPath = f.name.split('.').slice(-1)[0];
return { ...f, name: fieldNameWithoutParentPath };
}),
};
} else if (item.nested === 'parent' && item.field != null) {
// when user has selected a nested entry, right above it we show the common parent
Expand Down Expand Up @@ -145,7 +152,10 @@ export const getFormattedBuilderEntry = (

if (parent != null && parentIndex != null) {
return {
field: foundField,
field:
foundField != null
? { ...foundField, name: foundField.name.split('.').slice(-1)[0] }
: foundField,
correspondingKeywordField,
operator: getExceptionOperatorSelect(item),
value: getEntryValue(item),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
useApi,
} from '../../../../../public/lists_plugin_deps';
import { getExceptionListSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_schema.mock';
import { getFoundExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/found_exception_list_item_schema.mock';

jest.mock('../../../../common/lib/kibana');
jest.mock('../../../../../public/lists_plugin_deps');
Expand All @@ -36,6 +37,7 @@ describe('ExceptionsViewer', () => {

(useApi as jest.Mock).mockReturnValue({
deleteExceptionItem: jest.fn().mockResolvedValue(true),
getExceptionListsItems: jest.fn().mockResolvedValue(getFoundExceptionListItemSchemaMock()),
});

(useExceptionList as jest.Mock).mockReturnValue([
Expand Down

0 comments on commit e23c5ea

Please sign in to comment.