Skip to content

Commit e23c5ea

Browse files
authored
[Security Solution][Exceptions] - Fixes builder overflow and updates 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
1 parent 549c256 commit e23c5ea

File tree

7 files changed

+50
-26
lines changed

7 files changed

+50
-26
lines changed

x-pack/plugins/security_solution/public/common/components/autocomplete/field.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ export const FieldComponent: React.FC<OperatorProps> = ({
3636
onChange,
3737
}): JSX.Element => {
3838
const [touched, setIsTouched] = useState(false);
39-
const getLabel = useCallback((field): string => field.name, []);
39+
const getLabel = useCallback(({ name }): string => name, []);
4040
const optionsMemo = useMemo((): IFieldType[] => {
4141
if (indexPattern != null) {
4242
if (fieldTypeFilter.length > 0) {
43-
return indexPattern.fields.filter((f) => fieldTypeFilter.includes(f.type));
43+
return indexPattern.fields.filter(({ type }) => fieldTypeFilter.includes(type));
4444
} else {
4545
return indexPattern.fields;
4646
}
@@ -68,6 +68,10 @@ export const FieldComponent: React.FC<OperatorProps> = ({
6868
onChange(newValues);
6969
};
7070

71+
const handleTouch = useCallback((): void => {
72+
setIsTouched(true);
73+
}, [setIsTouched]);
74+
7175
return (
7276
<EuiComboBox
7377
placeholder={placeholder}
@@ -78,7 +82,7 @@ export const FieldComponent: React.FC<OperatorProps> = ({
7882
isDisabled={isDisabled}
7983
isClearable={isClearable}
8084
isInvalid={isRequired ? touched && selectedField == null : false}
81-
onFocus={() => setIsTouched(true)}
85+
onFocus={handleTouch}
8286
singleSelection={{ asPlainText: true }}
8387
data-test-subj="fieldAutocompleteComboBox"
8488
style={{ width: `${fieldInputWidth}px` }}

x-pack/plugins/security_solution/public/common/components/autocomplete/helpers.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,12 @@ export function getGenericComboBoxProps<T>({
6969
const newLabels = options.map(getLabel);
7070
const newComboOptions: EuiComboBoxOptionOption[] = newLabels.map((label) => ({ label }));
7171
const newSelectedComboOptions = selectedOptions
72+
.map(getLabel)
7273
.filter((option) => {
73-
return options.indexOf(option) !== -1;
74+
return newLabels.indexOf(option) !== -1;
7475
})
7576
.map((option) => {
76-
return newComboOptions[options.indexOf(option)];
77+
return newComboOptions[newLabels.indexOf(option)];
7778
});
7879

7980
return {

x-pack/plugins/security_solution/public/common/components/exceptions/builder/entry_item.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
import React, { useCallback } from 'react';
77
import { EuiFormRow, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
8+
import styled from 'styled-components';
89

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

33+
const MyValuesInput = styled(EuiFlexItem)`
34+
overflow: hidden;
35+
`;
36+
3237
interface EntryItemProps {
3338
entry: FormattedBuilderEntry;
3439
indexPattern: IIndexPattern;
@@ -257,12 +262,12 @@ export const BuilderEntryItem: React.FC<EntryItemProps> = ({
257262
>
258263
<EuiFlexItem grow={false}>{renderFieldInput(showLabel)}</EuiFlexItem>
259264
<EuiFlexItem grow={false}>{renderOperatorInput(showLabel)}</EuiFlexItem>
260-
<EuiFlexItem grow={6}>
265+
<MyValuesInput grow={6}>
261266
{renderFieldValueInput(
262267
showLabel,
263268
entry.nested === 'parent' ? OperatorTypeEnum.EXISTS : entry.operator.type
264269
)}
265-
</EuiFlexItem>
270+
</MyValuesInput>
266271
</EuiFlexGroup>
267272
);
268273
};

x-pack/plugins/security_solution/public/common/components/exceptions/builder/exception_item.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ const MyBeautifulLine = styled(EuiFlexItem)`
2626
}
2727
`;
2828

29+
const MyOverflowContainer = styled(EuiFlexItem)`
30+
overflow: hidden;
31+
width: 100%;
32+
`;
33+
2934
interface BuilderExceptionListItemProps {
3035
exceptionItem: ExceptionsBuilderExceptionItem;
3136
exceptionId: string;
@@ -98,13 +103,13 @@ export const BuilderExceptionListItemComponent = React.memo<BuilderExceptionList
98103
exceptionItemIndex={exceptionItemIndex}
99104
/>
100105
)}
101-
<EuiFlexItem grow={6}>
106+
<MyOverflowContainer grow={6}>
102107
<EuiFlexGroup gutterSize="s" direction="column">
103108
{entries.map((item, index) => (
104109
<EuiFlexItem key={`${exceptionId}-${index}`} grow={1}>
105110
<EuiFlexGroup gutterSize="xs" alignItems="center" direction="row">
106111
{item.nested === 'child' && <MyBeautifulLine grow={false} />}
107-
<EuiFlexItem grow={1}>
112+
<MyOverflowContainer grow={1}>
108113
<BuilderEntryItem
109114
entry={item}
110115
indexPattern={indexPattern}
@@ -115,7 +120,7 @@ export const BuilderExceptionListItemComponent = React.memo<BuilderExceptionList
115120
onChange={handleEntryChange}
116121
onlyShowListOperators={onlyShowListOperators}
117122
/>
118-
</EuiFlexItem>
123+
</MyOverflowContainer>
119124
<BuilderEntryDeleteButtonComponent
120125
entries={exceptionItem.entries}
121126
isOnlyItem={isOnlyItem}
@@ -128,7 +133,7 @@ export const BuilderExceptionListItemComponent = React.memo<BuilderExceptionList
128133
</EuiFlexItem>
129134
))}
130135
</EuiFlexGroup>
131-
</EuiFlexItem>
136+
</MyOverflowContainer>
132137
</EuiFlexGroup>
133138
</EuiFlexItem>
134139
);

x-pack/plugins/security_solution/public/common/components/exceptions/builder/helpers.test.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,7 @@ describe('Exception builder helpers', () => {
161161
const payloadItem: FormattedBuilderEntry = getMockNestedBuilderEntry();
162162
const output = getFilteredIndexPatterns(payloadIndexPattern, payloadItem, 'detection');
163163
const expected: IIndexPattern = {
164-
fields: [
165-
{ ...getField('nestedField.child') },
166-
{ ...getField('nestedField.nestedChild.doublyNestedChild') },
167-
],
164+
fields: [{ ...getField('nestedField.child'), name: 'child' }],
168165
id: '1234',
169166
title: 'logstash-*',
170167
};
@@ -243,7 +240,7 @@ describe('Exception builder helpers', () => {
243240
};
244241
const output = getFilteredIndexPatterns(payloadIndexPattern, payloadItem, 'endpoint');
245242
const expected: IIndexPattern = {
246-
fields: [getEndpointField('file.Ext.code_signature.status')],
243+
fields: [{ ...getEndpointField('file.Ext.code_signature.status'), name: 'status' }],
247244
id: '1234',
248245
title: 'logstash-*',
249246
};
@@ -405,7 +402,7 @@ describe('Exception builder helpers', () => {
405402
aggregatable: false,
406403
count: 0,
407404
esTypes: ['text'],
408-
name: 'nestedField.child',
405+
name: 'child',
409406
readFromDocValues: false,
410407
scripted: false,
411408
searchable: true,
@@ -600,7 +597,7 @@ describe('Exception builder helpers', () => {
600597
aggregatable: false,
601598
count: 0,
602599
esTypes: ['text'],
603-
name: 'nestedField.child',
600+
name: 'child',
604601
readFromDocValues: false,
605602
scripted: false,
606603
searchable: true,

x-pack/plugins/security_solution/public/common/components/exceptions/builder/helpers.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,20 @@ export const getFilteredIndexPatterns = (
6060
// when user has selected a nested entry, only fields with the common parent are shown
6161
return {
6262
...indexPatterns,
63-
fields: indexPatterns.fields.filter(
64-
(field) =>
65-
field.subType != null &&
66-
field.subType.nested != null &&
67-
item.parent != null &&
68-
field.subType.nested.path.startsWith(item.parent.parent.field)
69-
),
63+
fields: indexPatterns.fields
64+
.filter((indexField) => {
65+
const fieldHasCommonParentPath =
66+
indexField.subType != null &&
67+
indexField.subType.nested != null &&
68+
item.parent != null &&
69+
indexField.subType.nested.path === item.parent.parent.field;
70+
71+
return fieldHasCommonParentPath;
72+
})
73+
.map((f) => {
74+
const fieldNameWithoutParentPath = f.name.split('.').slice(-1)[0];
75+
return { ...f, name: fieldNameWithoutParentPath };
76+
}),
7077
};
7178
} else if (item.nested === 'parent' && item.field != null) {
7279
// when user has selected a nested entry, right above it we show the common parent
@@ -145,7 +152,10 @@ export const getFormattedBuilderEntry = (
145152

146153
if (parent != null && parentIndex != null) {
147154
return {
148-
field: foundField,
155+
field:
156+
foundField != null
157+
? { ...foundField, name: foundField.name.split('.').slice(-1)[0] }
158+
: foundField,
149159
correspondingKeywordField,
150160
operator: getExceptionOperatorSelect(item),
151161
value: getEntryValue(item),

x-pack/plugins/security_solution/public/common/components/exceptions/viewer/index.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
useApi,
1818
} from '../../../../../public/lists_plugin_deps';
1919
import { getExceptionListSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_schema.mock';
20+
import { getFoundExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/found_exception_list_item_schema.mock';
2021

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

3738
(useApi as jest.Mock).mockReturnValue({
3839
deleteExceptionItem: jest.fn().mockResolvedValue(true),
40+
getExceptionListsItems: jest.fn().mockResolvedValue(getFoundExceptionListItemSchemaMock()),
3941
});
4042

4143
(useExceptionList as jest.Mock).mockReturnValue([

0 commit comments

Comments
 (0)