-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[SIEM][Exceptions] - Exception builder component (#67013)
### Summary This PR creates the bulk functionality of the exception builder. The exception builder is the component that will be used to create exception list items. It does not deal with the actual API creation/deletion/update of exceptions, it does contain an `onChange` handler that can be used to access the exceptions. The builder is able to: - accept `ExceptionListItem` and render them correctly - allow user to add exception list item and exception list item entries - accept an `indexPattern` and use it to fetch relevant field and autocomplete field values - disable `Or` button if user is only allowed to edit/add to exception list item (not add additional exception list items) - displays `Add new exception` button if no exception items exist - An exception item can be created without entries, the `add new exception` button will show in the case that an exception list contains exception list item(s) with an empty `entries` array (as long as there is one exception list item with an item in `entries`, button does not show) - debounces field value autocomplete searches - bubble up exceptions to parent component, stripping out any empty entries
- Loading branch information
Showing
48 changed files
with
3,623 additions
and
980 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
156 changes: 156 additions & 0 deletions
156
x-pack/plugins/security_solution/public/common/components/autocomplete/field.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
import React from 'react'; | ||
import { ThemeProvider } from 'styled-components'; | ||
import { mount } from 'enzyme'; | ||
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; | ||
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; | ||
|
||
import { | ||
fields, | ||
getField, | ||
} from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks.ts'; | ||
import { FieldComponent } from './field'; | ||
|
||
describe('FieldComponent', () => { | ||
test('it renders disabled if "isDisabled" is true', () => { | ||
const wrapper = mount( | ||
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}> | ||
<FieldComponent | ||
placeholder="Placeholder text" | ||
indexPattern={{ | ||
id: '1234', | ||
title: 'logstash-*', | ||
fields, | ||
}} | ||
selectedField={getField('machine.os.raw')} | ||
isLoading={false} | ||
isClearable={false} | ||
isDisabled={true} | ||
onChange={jest.fn()} | ||
/> | ||
</ThemeProvider> | ||
); | ||
|
||
expect( | ||
wrapper.find(`[data-test-subj="fieldAutocompleteComboBox"] input`).prop('disabled') | ||
).toBeTruthy(); | ||
}); | ||
|
||
test('it renders loading if "isLoading" is true', () => { | ||
const wrapper = mount( | ||
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}> | ||
<FieldComponent | ||
placeholder="Placeholder text" | ||
indexPattern={{ | ||
id: '1234', | ||
title: 'logstash-*', | ||
fields, | ||
}} | ||
selectedField={getField('machine.os.raw')} | ||
isLoading={true} | ||
isClearable={false} | ||
isDisabled={false} | ||
onChange={jest.fn()} | ||
/> | ||
</ThemeProvider> | ||
); | ||
wrapper.find(`[data-test-subj="fieldAutocompleteComboBox"] button`).at(0).simulate('click'); | ||
expect( | ||
wrapper | ||
.find(`EuiComboBoxOptionsList[data-test-subj="fieldAutocompleteComboBox-optionsList"]`) | ||
.prop('isLoading') | ||
).toBeTruthy(); | ||
}); | ||
|
||
test('it allows user to clear values if "isClearable" is true', () => { | ||
const wrapper = mount( | ||
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}> | ||
<FieldComponent | ||
placeholder="Placeholder text" | ||
indexPattern={{ | ||
id: '1234', | ||
title: 'logstash-*', | ||
fields, | ||
}} | ||
selectedField={getField('machine.os.raw')} | ||
isLoading={false} | ||
isClearable={true} | ||
isDisabled={false} | ||
onChange={jest.fn()} | ||
/> | ||
</ThemeProvider> | ||
); | ||
|
||
expect( | ||
wrapper | ||
.find(`[data-test-subj="comboBoxInput"]`) | ||
.hasClass('euiComboBox__inputWrap-isClearable') | ||
).toBeTruthy(); | ||
}); | ||
|
||
test('it correctly displays selected field', () => { | ||
const wrapper = mount( | ||
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}> | ||
<FieldComponent | ||
placeholder="Placeholder text" | ||
indexPattern={{ | ||
id: '1234', | ||
title: 'logstash-*', | ||
fields, | ||
}} | ||
selectedField={getField('machine.os.raw')} | ||
isLoading={false} | ||
isClearable={false} | ||
isDisabled={false} | ||
onChange={jest.fn()} | ||
/> | ||
</ThemeProvider> | ||
); | ||
|
||
expect( | ||
wrapper.find(`[data-test-subj="fieldAutocompleteComboBox"] EuiComboBoxPill`).at(0).text() | ||
).toEqual('machine.os.raw'); | ||
}); | ||
|
||
test('it invokes "onChange" when option selected', () => { | ||
const mockOnChange = jest.fn(); | ||
const wrapper = mount( | ||
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}> | ||
<FieldComponent | ||
placeholder="Placeholder text" | ||
indexPattern={{ | ||
id: '1234', | ||
title: 'logstash-*', | ||
fields, | ||
}} | ||
selectedField={getField('machine.os.raw')} | ||
isLoading={false} | ||
isClearable={false} | ||
isDisabled={false} | ||
onChange={mockOnChange} | ||
/> | ||
</ThemeProvider> | ||
); | ||
|
||
((wrapper.find(EuiComboBox).props() as unknown) as { | ||
onChange: (a: EuiComboBoxOptionOption[]) => void; | ||
}).onChange([{ label: 'machine.os' }]); | ||
|
||
expect(mockOnChange).toHaveBeenCalledWith([ | ||
{ | ||
aggregatable: true, | ||
count: 0, | ||
esTypes: ['text'], | ||
name: 'machine.os', | ||
readFromDocValues: false, | ||
scripted: false, | ||
searchable: true, | ||
type: 'string', | ||
}, | ||
]); | ||
}); | ||
}); |
74 changes: 74 additions & 0 deletions
74
x-pack/plugins/security_solution/public/common/components/autocomplete/field.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
import React, { useMemo, useCallback } from 'react'; | ||
import { EuiComboBoxOptionOption, EuiComboBox } from '@elastic/eui'; | ||
|
||
import { IFieldType, IIndexPattern } from '../../../../../../../src/plugins/data/common'; | ||
import { getGenericComboBoxProps } from './helpers'; | ||
import { GetGenericComboBoxPropsReturn } from './types'; | ||
|
||
interface OperatorProps { | ||
placeholder: string; | ||
selectedField: IFieldType | undefined; | ||
indexPattern: IIndexPattern | undefined; | ||
isLoading: boolean; | ||
isDisabled: boolean; | ||
isClearable: boolean; | ||
fieldInputWidth?: number; | ||
onChange: (a: IFieldType[]) => void; | ||
} | ||
|
||
export const FieldComponent: React.FC<OperatorProps> = ({ | ||
placeholder, | ||
selectedField, | ||
indexPattern, | ||
isLoading = false, | ||
isDisabled = false, | ||
isClearable = false, | ||
fieldInputWidth = 190, | ||
onChange, | ||
}): JSX.Element => { | ||
const getLabel = useCallback((field): string => field.name, []); | ||
const optionsMemo = useMemo((): IFieldType[] => (indexPattern ? indexPattern.fields : []), [ | ||
indexPattern, | ||
]); | ||
const selectedOptionsMemo = useMemo((): IFieldType[] => (selectedField ? [selectedField] : []), [ | ||
selectedField, | ||
]); | ||
const { comboOptions, labels, selectedComboOptions } = useMemo( | ||
(): GetGenericComboBoxPropsReturn => | ||
getGenericComboBoxProps<IFieldType>({ | ||
options: optionsMemo, | ||
selectedOptions: selectedOptionsMemo, | ||
getLabel, | ||
}), | ||
[optionsMemo, selectedOptionsMemo, getLabel] | ||
); | ||
|
||
const handleValuesChange = (newOptions: EuiComboBoxOptionOption[]): void => { | ||
const newValues: IFieldType[] = newOptions.map( | ||
({ label }) => optionsMemo[labels.indexOf(label)] | ||
); | ||
onChange(newValues); | ||
}; | ||
|
||
return ( | ||
<EuiComboBox | ||
placeholder={placeholder} | ||
options={comboOptions} | ||
selectedOptions={selectedComboOptions} | ||
onChange={handleValuesChange} | ||
isLoading={isLoading} | ||
isDisabled={isDisabled} | ||
isClearable={isClearable} | ||
singleSelection={{ asPlainText: true }} | ||
data-test-subj="fieldAutocompleteComboBox" | ||
style={{ width: `${fieldInputWidth}px` }} | ||
/> | ||
); | ||
}; | ||
|
||
FieldComponent.displayName = 'Field'; |
27 changes: 27 additions & 0 deletions
27
...ugins/security_solution/public/common/components/autocomplete/field_value_exists.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
import React from 'react'; | ||
import { ThemeProvider } from 'styled-components'; | ||
import { mount } from 'enzyme'; | ||
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; | ||
|
||
import { AutocompleteFieldExistsComponent } from './field_value_exists'; | ||
|
||
describe('AutocompleteFieldExistsComponent', () => { | ||
test('it renders field disabled', () => { | ||
const wrapper = mount( | ||
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}> | ||
<AutocompleteFieldExistsComponent placeholder="Placeholder text" /> | ||
</ThemeProvider> | ||
); | ||
|
||
expect( | ||
wrapper | ||
.find(`[data-test-subj="valuesAutocompleteComboBox existsComboxBox"] input`) | ||
.prop('disabled') | ||
).toBeTruthy(); | ||
}); | ||
}); |
27 changes: 27 additions & 0 deletions
27
...ck/plugins/security_solution/public/common/components/autocomplete/field_value_exists.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
import React from 'react'; | ||
import { EuiComboBox } from '@elastic/eui'; | ||
|
||
interface AutocompleteFieldExistsProps { | ||
placeholder: string; | ||
} | ||
|
||
export const AutocompleteFieldExistsComponent: React.FC<AutocompleteFieldExistsProps> = ({ | ||
placeholder, | ||
}): JSX.Element => ( | ||
<EuiComboBox | ||
placeholder={placeholder} | ||
options={[]} | ||
selectedOptions={[]} | ||
onChange={undefined} | ||
isDisabled | ||
data-test-subj="valuesAutocompleteComboBox existsComboxBox" | ||
fullWidth | ||
/> | ||
); | ||
|
||
AutocompleteFieldExistsComponent.displayName = 'AutocompleteFieldExists'; |
Oops, something went wrong.