mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
Fixing leading or trailing whitespace in exception entries (#139617)
* add param_contains_space + handle spaces in match, match_any and field_value_wildcard + translations * add warning text to onChange events as well + add test for match with snanpshot * add warning text to onChange events as well + add test for match with snanpshotcd * add tests and snapshots for match_any and value_wildcard * remove snapshots * show warning in nested condition * add param_contains_space + handle spaces in match, match_any and field_value_wildcard + translations * add warning text to onChange events as well + add test for match with snanpshot * add tests and snapshots for match_any and value_wildcard * remove snapshots * show warning in nested condition * add ValueWithSpaceWarning component + tests in Exeption viewer * add ValueWithSpaceWarning component + tests in Exeption viewer * return </EuiFlexItemNested> * apply ux comments * use themeprovider * apply docs-team comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
8291e1c7d0
commit
2a3cf660ab
17 changed files with 949 additions and 41 deletions
|
@ -18,3 +18,6 @@ export * from './src/get_operators';
|
|||
export * from './src/hooks';
|
||||
export * from './src/operator';
|
||||
export * from './src/param_is_valid';
|
||||
export * from './src/param_contains_space';
|
||||
|
||||
export { default as autoCompletei18n } from './src/translations';
|
||||
|
|
|
@ -8,15 +8,22 @@
|
|||
|
||||
import React from 'react';
|
||||
import { ReactWrapper, mount } from 'enzyme';
|
||||
import { EuiComboBox, EuiComboBoxOptionOption, EuiSuperSelect } from '@elastic/eui';
|
||||
import { act } from '@testing-library/react';
|
||||
import {
|
||||
EuiComboBox,
|
||||
EuiComboBoxOptionOption,
|
||||
EuiFormHelpText,
|
||||
EuiSuperSelect,
|
||||
} from '@elastic/eui';
|
||||
import { act, waitFor } from '@testing-library/react';
|
||||
import { AutocompleteFieldMatchComponent } from '.';
|
||||
import { useFieldValueAutocomplete } from '../hooks/use_field_value_autocomplete';
|
||||
import { fields, getField } from '../fields/index.mock';
|
||||
import { autocompleteStartMock } from '../autocomplete/index.mock';
|
||||
|
||||
jest.mock('../hooks/use_field_value_autocomplete');
|
||||
|
||||
jest.mock('../translations', () => ({
|
||||
FIELD_SPACE_WARNING: 'Warning: there is a space',
|
||||
}));
|
||||
describe('AutocompleteFieldMatchComponent', () => {
|
||||
let wrapper: ReactWrapper;
|
||||
|
||||
|
@ -227,6 +234,48 @@ describe('AutocompleteFieldMatchComponent', () => {
|
|||
expect(mockOnChange).toHaveBeenCalledWith('value 1');
|
||||
});
|
||||
|
||||
test('should show the warning helper text if the new value contains spaces when change', async () => {
|
||||
(useFieldValueAutocomplete as jest.Mock).mockReturnValue([
|
||||
false,
|
||||
true,
|
||||
[' value 1 ', 'value 2'],
|
||||
getValueSuggestionsMock,
|
||||
]);
|
||||
const mockOnChange = jest.fn();
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldMatchComponent
|
||||
autocompleteService={autocompleteStartMock}
|
||||
rowLabel="Test"
|
||||
indexPattern={{
|
||||
fields,
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
}}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
onChange={mockOnChange}
|
||||
onError={jest.fn()}
|
||||
placeholder="Placeholder text"
|
||||
selectedField={getField('machine.os.raw')}
|
||||
selectedValue=""
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
(
|
||||
wrapper.find(EuiComboBox).props() as unknown as {
|
||||
onChange: (a: EuiComboBoxOptionOption[]) => void;
|
||||
}
|
||||
).onChange([{ label: ' value 1 ' }])
|
||||
);
|
||||
wrapper.update();
|
||||
expect(mockOnChange).toHaveBeenCalledWith(' value 1 ');
|
||||
|
||||
const euiFormHelptext = wrapper.find(EuiFormHelpText);
|
||||
expect(euiFormHelptext.length).toBeTruthy();
|
||||
});
|
||||
|
||||
test('it refreshes autocomplete with search query when new value searched', () => {
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldMatchComponent
|
||||
|
@ -267,6 +316,83 @@ describe('AutocompleteFieldMatchComponent', () => {
|
|||
selectedField: getField('machine.os.raw'),
|
||||
});
|
||||
});
|
||||
test('should show the warning helper text if the new value contains spaces when searching a new query', () => {
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldMatchComponent
|
||||
autocompleteService={autocompleteStartMock}
|
||||
indexPattern={{
|
||||
fields,
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
}}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
onChange={jest.fn()}
|
||||
onError={jest.fn()}
|
||||
placeholder="Placeholder text"
|
||||
selectedField={getField('machine.os.raw')}
|
||||
selectedValue=""
|
||||
/>
|
||||
);
|
||||
act(() => {
|
||||
(
|
||||
wrapper.find(EuiComboBox).props() as unknown as {
|
||||
onSearchChange: (a: string) => void;
|
||||
}
|
||||
).onSearchChange(' value 1');
|
||||
});
|
||||
|
||||
wrapper.update();
|
||||
const euiFormHelptext = wrapper.find(EuiFormHelpText);
|
||||
expect(euiFormHelptext.length).toBeTruthy();
|
||||
expect(euiFormHelptext.text()).toEqual('Warning: there is a space');
|
||||
});
|
||||
test('should show the warning helper text if selectedValue contains spaces when editing', () => {
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldMatchComponent
|
||||
autocompleteService={autocompleteStartMock}
|
||||
indexPattern={{
|
||||
fields,
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
}}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
onChange={jest.fn()}
|
||||
onError={jest.fn()}
|
||||
placeholder="Placeholder text"
|
||||
selectedField={getField('machine.os.raw')}
|
||||
selectedValue=" leading and trailing space "
|
||||
/>
|
||||
);
|
||||
const euiFormHelptext = wrapper.find(EuiFormHelpText);
|
||||
expect(euiFormHelptext.length).toBeTruthy();
|
||||
expect(euiFormHelptext.text()).toEqual('Warning: there is a space');
|
||||
});
|
||||
test('should not show the warning helper text if selectedValue is falsy', () => {
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldMatchComponent
|
||||
autocompleteService={autocompleteStartMock}
|
||||
indexPattern={{
|
||||
fields,
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
}}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
onChange={jest.fn()}
|
||||
onError={jest.fn()}
|
||||
placeholder="Placeholder text"
|
||||
selectedField={getField('machine.os.raw')}
|
||||
selectedValue=""
|
||||
/>
|
||||
);
|
||||
const euiFormHelptext = wrapper.find(EuiFormHelpText);
|
||||
expect(euiFormHelptext.length).toBeFalsy();
|
||||
});
|
||||
|
||||
describe('boolean type', () => {
|
||||
const valueSuggestionsMock = jest.fn().mockResolvedValue([false, false, [], jest.fn()]);
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
GetGenericComboBoxPropsReturn,
|
||||
} from '../get_generic_combo_box_props';
|
||||
import { paramIsValid } from '../param_is_valid';
|
||||
import { paramContainsSpace } from '../param_contains_space';
|
||||
|
||||
const BOOLEAN_OPTIONS = [
|
||||
{ inputDisplay: 'true', value: 'true' },
|
||||
|
@ -66,13 +67,14 @@ export const AutocompleteFieldMatchComponent: React.FC<AutocompleteFieldMatchPro
|
|||
isClearable = false,
|
||||
isRequired = false,
|
||||
fieldInputWidth,
|
||||
autocompleteService,
|
||||
onChange,
|
||||
onError,
|
||||
autocompleteService,
|
||||
}): JSX.Element => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [touched, setIsTouched] = useState(false);
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
const [showSpacesWarning, setShowSpacesWarning] = useState<boolean>(false);
|
||||
const [isLoadingSuggestions, isSuggestingValues, suggestions] = useFieldValueAutocomplete({
|
||||
autocompleteService,
|
||||
fieldValue: selectedValue,
|
||||
|
@ -82,17 +84,27 @@ export const AutocompleteFieldMatchComponent: React.FC<AutocompleteFieldMatchPro
|
|||
selectedField,
|
||||
});
|
||||
const getLabel = useCallback((option: string): string => option, []);
|
||||
|
||||
const optionsMemo = useMemo((): string[] => {
|
||||
const valueAsStr = String(selectedValue);
|
||||
return selectedValue != null && selectedValue.trim() !== ''
|
||||
? uniq([valueAsStr, ...suggestions])
|
||||
: suggestions;
|
||||
}, [suggestions, selectedValue]);
|
||||
|
||||
const selectedOptionsMemo = useMemo((): string[] => {
|
||||
const valueAsStr = String(selectedValue);
|
||||
return selectedValue ? [valueAsStr] : [];
|
||||
}, [selectedValue]);
|
||||
|
||||
const handleSpacesWarning = useCallback(
|
||||
(param: string | undefined) => {
|
||||
if (!param) return setShowSpacesWarning(false);
|
||||
setShowSpacesWarning(!!paramContainsSpace(param));
|
||||
},
|
||||
[setShowSpacesWarning]
|
||||
);
|
||||
|
||||
const handleError = useCallback(
|
||||
(err: string | undefined): void => {
|
||||
setError((existingErr): string | undefined => {
|
||||
|
@ -121,10 +133,12 @@ export const AutocompleteFieldMatchComponent: React.FC<AutocompleteFieldMatchPro
|
|||
const handleValuesChange = useCallback(
|
||||
(newOptions: EuiComboBoxOptionOption[]): void => {
|
||||
const [newValue] = newOptions.map(({ label }) => optionsMemo[labels.indexOf(label)]);
|
||||
|
||||
handleSpacesWarning(newValue);
|
||||
handleError(undefined);
|
||||
onChange(newValue ?? '');
|
||||
},
|
||||
[handleError, labels, onChange, optionsMemo]
|
||||
[handleError, handleSpacesWarning, labels, onChange, optionsMemo]
|
||||
);
|
||||
|
||||
const handleSearchChange = useCallback(
|
||||
|
@ -133,10 +147,11 @@ export const AutocompleteFieldMatchComponent: React.FC<AutocompleteFieldMatchPro
|
|||
const err = paramIsValid(searchVal, selectedField, isRequired, touched);
|
||||
handleError(err);
|
||||
|
||||
if (!err) handleSpacesWarning(searchVal);
|
||||
setSearchQuery(searchVal);
|
||||
}
|
||||
},
|
||||
[handleError, isRequired, selectedField, touched]
|
||||
[handleError, handleSpacesWarning, isRequired, selectedField, touched]
|
||||
);
|
||||
|
||||
const handleCreateOption = useCallback(
|
||||
|
@ -146,13 +161,15 @@ export const AutocompleteFieldMatchComponent: React.FC<AutocompleteFieldMatchPro
|
|||
|
||||
if (err != null) {
|
||||
// Explicitly reject the user's input
|
||||
setShowSpacesWarning(false);
|
||||
return false;
|
||||
} else {
|
||||
onChange(option);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
handleSpacesWarning(option);
|
||||
onChange(option);
|
||||
return undefined;
|
||||
},
|
||||
[isRequired, onChange, selectedField, touched, handleError]
|
||||
[isRequired, onChange, selectedField, touched, handleError, handleSpacesWarning]
|
||||
);
|
||||
|
||||
const handleNonComboBoxInputChange = useCallback(
|
||||
|
@ -194,10 +211,10 @@ export const AutocompleteFieldMatchComponent: React.FC<AutocompleteFieldMatchPro
|
|||
|
||||
useEffect((): void => {
|
||||
setError(undefined);
|
||||
if (onError != null) {
|
||||
onError(false);
|
||||
}
|
||||
}, [selectedField, onError]);
|
||||
if (onError != null) onError(false);
|
||||
|
||||
handleSpacesWarning(selectedValue);
|
||||
}, [selectedField, selectedValue, handleSpacesWarning, onError]);
|
||||
|
||||
const defaultInput = useMemo((): JSX.Element => {
|
||||
return (
|
||||
|
@ -207,6 +224,7 @@ export const AutocompleteFieldMatchComponent: React.FC<AutocompleteFieldMatchPro
|
|||
isInvalid={selectedField != null && error != null}
|
||||
data-test-subj="valuesAutocompleteMatchLabel"
|
||||
fullWidth
|
||||
helpText={showSpacesWarning && i18n.FIELD_SPACE_WARNING}
|
||||
>
|
||||
<EuiComboBox
|
||||
placeholder={inputPlaceholder}
|
||||
|
@ -233,9 +251,6 @@ export const AutocompleteFieldMatchComponent: React.FC<AutocompleteFieldMatchPro
|
|||
comboOptions,
|
||||
error,
|
||||
fieldInputWidth,
|
||||
handleCreateOption,
|
||||
handleSearchChange,
|
||||
handleValuesChange,
|
||||
inputPlaceholder,
|
||||
isClearable,
|
||||
isDisabled,
|
||||
|
@ -243,6 +258,10 @@ export const AutocompleteFieldMatchComponent: React.FC<AutocompleteFieldMatchPro
|
|||
rowLabel,
|
||||
selectedComboOptions,
|
||||
selectedField,
|
||||
showSpacesWarning,
|
||||
handleCreateOption,
|
||||
handleSearchChange,
|
||||
handleValuesChange,
|
||||
setIsTouchedValue,
|
||||
]);
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
import React from 'react';
|
||||
import { ReactWrapper, mount } from 'enzyme';
|
||||
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
|
||||
import { act } from '@testing-library/react';
|
||||
import { EuiComboBox, EuiComboBoxOptionOption, EuiFormHelpText } from '@elastic/eui';
|
||||
import { act, waitFor } from '@testing-library/react';
|
||||
|
||||
import { AutocompleteFieldMatchAnyComponent } from '.';
|
||||
import { getField, fields } from '../fields/index.mock';
|
||||
|
@ -23,6 +23,9 @@ jest.mock('../hooks/use_field_value_autocomplete', () => {
|
|||
useFieldValueAutocomplete: jest.fn(),
|
||||
};
|
||||
});
|
||||
jest.mock('../translations', () => ({
|
||||
FIELD_SPACE_WARNING: 'Warning: there is a space',
|
||||
}));
|
||||
|
||||
describe('AutocompleteFieldMatchAnyComponent', () => {
|
||||
let wrapper: ReactWrapper;
|
||||
|
@ -273,4 +276,128 @@ describe('AutocompleteFieldMatchAnyComponent', () => {
|
|||
selectedField: getField('machine.os.raw'),
|
||||
});
|
||||
});
|
||||
test('should show the warning helper text if the new value contains spaces when change', async () => {
|
||||
(useFieldValueAutocomplete as jest.Mock).mockReturnValue([
|
||||
false,
|
||||
true,
|
||||
[' value 1 ', 'value 2'],
|
||||
getValueSuggestionsMock,
|
||||
]);
|
||||
const mockOnChange = jest.fn();
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldMatchAnyComponent
|
||||
autocompleteService={{
|
||||
...autocompleteStartMock,
|
||||
}}
|
||||
indexPattern={{
|
||||
fields,
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
}}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
onChange={mockOnChange}
|
||||
placeholder="Placeholder text"
|
||||
rowLabel={'Row Label'}
|
||||
selectedField={getField('machine.os.raw')}
|
||||
selectedValue={[]}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
(
|
||||
wrapper.find(EuiComboBox).props() as unknown as {
|
||||
onChange: (a: EuiComboBoxOptionOption[]) => void;
|
||||
}
|
||||
).onChange([{ label: ' value 1 ' }])
|
||||
);
|
||||
wrapper.update();
|
||||
expect(mockOnChange).toHaveBeenCalledWith([' value 1 ']);
|
||||
const euiFormHelptext = wrapper.find(EuiFormHelpText);
|
||||
expect(euiFormHelptext.length).toBeTruthy();
|
||||
});
|
||||
test('should show the warning helper text if the new value contains spaces when searching a new query', () => {
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldMatchAnyComponent
|
||||
autocompleteService={{
|
||||
...autocompleteStartMock,
|
||||
}}
|
||||
indexPattern={{
|
||||
fields,
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
}}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
onChange={jest.fn()}
|
||||
placeholder="Placeholder text"
|
||||
rowLabel={'Row Label'}
|
||||
selectedField={getField('machine.os.raw')}
|
||||
selectedValue={[]}
|
||||
/>
|
||||
);
|
||||
act(() => {
|
||||
(
|
||||
wrapper.find(EuiComboBox).props() as unknown as {
|
||||
onSearchChange: (a: string) => void;
|
||||
}
|
||||
).onSearchChange(' value 1');
|
||||
});
|
||||
|
||||
wrapper.update();
|
||||
const euiFormHelptext = wrapper.find(EuiFormHelpText);
|
||||
expect(euiFormHelptext.length).toBeTruthy();
|
||||
expect(euiFormHelptext.text()).toEqual('Warning: there is a space');
|
||||
});
|
||||
test('should show the warning helper text if selectedValue contains spaces when editing', () => {
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldMatchAnyComponent
|
||||
autocompleteService={{
|
||||
...autocompleteStartMock,
|
||||
}}
|
||||
indexPattern={{
|
||||
fields,
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
}}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
onChange={jest.fn()}
|
||||
placeholder="Placeholder text"
|
||||
rowLabel={'Row Label'}
|
||||
selectedField={getField('machine.os.raw')}
|
||||
selectedValue={['value with trailing space ', 'value 1']}
|
||||
/>
|
||||
);
|
||||
const euiFormHelptext = wrapper.find(EuiFormHelpText);
|
||||
expect(euiFormHelptext.length).toBeTruthy();
|
||||
expect(euiFormHelptext.text()).toEqual('Warning: there is a space');
|
||||
});
|
||||
test('should not show the warning helper text if selectedValue is falsy', () => {
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldMatchAnyComponent
|
||||
autocompleteService={{
|
||||
...autocompleteStartMock,
|
||||
}}
|
||||
indexPattern={{
|
||||
fields,
|
||||
id: '1234',
|
||||
title: 'logstash-*',
|
||||
}}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
onChange={jest.fn()}
|
||||
placeholder="Placeholder text"
|
||||
rowLabel={'Row Label'}
|
||||
selectedField={getField('machine.os.raw')}
|
||||
selectedValue={[]}
|
||||
/>
|
||||
);
|
||||
const euiFormHelptext = wrapper.find(EuiFormHelpText);
|
||||
expect(euiFormHelptext.length).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui';
|
||||
import { uniq } from 'lodash';
|
||||
import { ListOperatorTypeEnum as OperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
|
||||
|
@ -23,6 +23,7 @@ import {
|
|||
} from '../get_generic_combo_box_props';
|
||||
import { useFieldValueAutocomplete } from '../hooks/use_field_value_autocomplete';
|
||||
import { paramIsValid } from '../param_is_valid';
|
||||
import { paramContainsSpace } from '../param_contains_space';
|
||||
|
||||
interface AutocompleteFieldMatchAnyProps {
|
||||
placeholder: string;
|
||||
|
@ -56,6 +57,7 @@ export const AutocompleteFieldMatchAnyComponent: React.FC<AutocompleteFieldMatch
|
|||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [touched, setIsTouched] = useState(false);
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
const [showSpacesWarning, setShowSpacesWarning] = useState<boolean>(false);
|
||||
const [isLoadingSuggestions, isSuggestingValues, suggestions] = useFieldValueAutocomplete({
|
||||
autocompleteService,
|
||||
fieldValue: selectedValue,
|
||||
|
@ -78,7 +80,11 @@ export const AutocompleteFieldMatchAnyComponent: React.FC<AutocompleteFieldMatch
|
|||
}),
|
||||
[optionsMemo, selectedValue, getLabel]
|
||||
);
|
||||
|
||||
const handleSpacesWarning = useCallback(
|
||||
(params: string[]) =>
|
||||
setShowSpacesWarning(!!params.find((param: string) => paramContainsSpace(param))),
|
||||
[setShowSpacesWarning]
|
||||
);
|
||||
const handleError = useCallback(
|
||||
(err: string | undefined): void => {
|
||||
setError((existingErr): string | undefined => {
|
||||
|
@ -98,9 +104,10 @@ export const AutocompleteFieldMatchAnyComponent: React.FC<AutocompleteFieldMatch
|
|||
(newOptions: EuiComboBoxOptionOption[]): void => {
|
||||
const newValues: string[] = newOptions.map(({ label }) => optionsMemo[labels.indexOf(label)]);
|
||||
handleError(undefined);
|
||||
handleSpacesWarning(newValues);
|
||||
onChange(newValues);
|
||||
},
|
||||
[handleError, labels, onChange, optionsMemo]
|
||||
[handleError, handleSpacesWarning, labels, onChange, optionsMemo]
|
||||
);
|
||||
|
||||
const handleSearchChange = useCallback(
|
||||
|
@ -113,10 +120,12 @@ export const AutocompleteFieldMatchAnyComponent: React.FC<AutocompleteFieldMatch
|
|||
const err = paramIsValid(searchVal, selectedField, isRequired, touched);
|
||||
handleError(err);
|
||||
|
||||
if (!err) handleSpacesWarning([searchVal]);
|
||||
|
||||
setSearchQuery(searchVal);
|
||||
}
|
||||
},
|
||||
[handleError, isRequired, selectedField, touched]
|
||||
[handleError, handleSpacesWarning, isRequired, selectedField, touched]
|
||||
);
|
||||
|
||||
const handleCreateOption = useCallback(
|
||||
|
@ -126,13 +135,15 @@ export const AutocompleteFieldMatchAnyComponent: React.FC<AutocompleteFieldMatch
|
|||
|
||||
if (err != null) {
|
||||
// Explicitly reject the user's input
|
||||
setShowSpacesWarning(false);
|
||||
return false;
|
||||
} else {
|
||||
onChange([...(selectedValue || []), option]);
|
||||
return true;
|
||||
}
|
||||
|
||||
onChange([...(selectedValue || []), option]);
|
||||
handleSpacesWarning([option]);
|
||||
return true;
|
||||
},
|
||||
[handleError, isRequired, onChange, selectedField, selectedValue, touched]
|
||||
[handleError, handleSpacesWarning, isRequired, onChange, selectedField, selectedValue, touched]
|
||||
);
|
||||
|
||||
const setIsTouchedValue = useCallback((): void => {
|
||||
|
@ -149,6 +160,9 @@ export const AutocompleteFieldMatchAnyComponent: React.FC<AutocompleteFieldMatch
|
|||
(): boolean => isLoading || isLoadingSuggestions,
|
||||
[isLoading, isLoadingSuggestions]
|
||||
);
|
||||
useEffect((): void => {
|
||||
handleSpacesWarning(selectedValue);
|
||||
}, [selectedField, selectedValue, handleSpacesWarning]);
|
||||
|
||||
const defaultInput = useMemo((): JSX.Element => {
|
||||
return (
|
||||
|
@ -156,6 +170,7 @@ export const AutocompleteFieldMatchAnyComponent: React.FC<AutocompleteFieldMatch
|
|||
label={rowLabel}
|
||||
error={error}
|
||||
isInvalid={selectedField != null && error != null}
|
||||
helpText={showSpacesWarning && i18n.FIELD_SPACE_WARNING}
|
||||
fullWidth
|
||||
>
|
||||
<EuiComboBox
|
||||
|
@ -189,6 +204,7 @@ export const AutocompleteFieldMatchAnyComponent: React.FC<AutocompleteFieldMatch
|
|||
rowLabel,
|
||||
selectedComboOptions,
|
||||
selectedField,
|
||||
showSpacesWarning,
|
||||
setIsTouchedValue,
|
||||
]);
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
import React from 'react';
|
||||
import { ReactWrapper, mount } from 'enzyme';
|
||||
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
|
||||
import { act } from '@testing-library/react';
|
||||
import { EuiComboBox, EuiComboBoxOptionOption, EuiFormHelpText } from '@elastic/eui';
|
||||
import { act, waitFor } from '@testing-library/react';
|
||||
import { AutocompleteFieldWildcardComponent } from '.';
|
||||
import { useFieldValueAutocomplete } from '../hooks/use_field_value_autocomplete';
|
||||
import { fields, getField } from '../fields/index.mock';
|
||||
|
@ -17,7 +17,9 @@ import { autocompleteStartMock } from '../autocomplete/index.mock';
|
|||
import { FILENAME_WILDCARD_WARNING, FILEPATH_WARNING } from '@kbn/securitysolution-utils';
|
||||
|
||||
jest.mock('../hooks/use_field_value_autocomplete');
|
||||
|
||||
jest.mock('../translations', () => ({
|
||||
FIELD_SPACE_WARNING: 'Warning: there is a space',
|
||||
}));
|
||||
describe('AutocompleteFieldWildcardComponent', () => {
|
||||
let wrapper: ReactWrapper;
|
||||
|
||||
|
@ -389,4 +391,129 @@ describe('AutocompleteFieldWildcardComponent', () => {
|
|||
expect(helpText.text()).toEqual(FILENAME_WILDCARD_WARNING);
|
||||
expect(helpText.find('.euiToolTipAnchor')).toBeTruthy();
|
||||
});
|
||||
test('should show the warning helper text if the new value contains spaces when change', async () => {
|
||||
(useFieldValueAutocomplete as jest.Mock).mockReturnValue([
|
||||
false,
|
||||
true,
|
||||
[' value 1 ', 'value 2'],
|
||||
getValueSuggestionsMock,
|
||||
]);
|
||||
const mockOnChange = jest.fn();
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldWildcardComponent
|
||||
autocompleteService={autocompleteStartMock}
|
||||
indexPattern={{
|
||||
fields,
|
||||
id: '1234',
|
||||
title: 'logs-endpoint.events.*',
|
||||
}}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
onChange={mockOnChange}
|
||||
onError={jest.fn()}
|
||||
onWarning={jest.fn()}
|
||||
placeholder="Placeholder text"
|
||||
selectedField={getField('file.path.text')}
|
||||
selectedValue="invalid path"
|
||||
warning={FILENAME_WILDCARD_WARNING}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
(
|
||||
wrapper.find(EuiComboBox).props() as unknown as {
|
||||
onChange: (a: EuiComboBoxOptionOption[]) => void;
|
||||
}
|
||||
).onChange([{ label: ' value 1 ' }])
|
||||
);
|
||||
wrapper.update();
|
||||
expect(mockOnChange).toHaveBeenCalledWith(' value 1 ');
|
||||
|
||||
const euiFormHelptext = wrapper.find(EuiFormHelpText);
|
||||
expect(euiFormHelptext.length).toBeTruthy();
|
||||
});
|
||||
test('should show the warning helper text if the new value contains spaces when searching a new query', () => {
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldWildcardComponent
|
||||
autocompleteService={autocompleteStartMock}
|
||||
indexPattern={{
|
||||
fields,
|
||||
id: '1234',
|
||||
title: 'logs-endpoint.events.*',
|
||||
}}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
onChange={jest.fn()}
|
||||
onError={jest.fn()}
|
||||
onWarning={jest.fn()}
|
||||
placeholder="Placeholder text"
|
||||
selectedField={getField('file.path.text')}
|
||||
selectedValue="invalid path"
|
||||
warning={''}
|
||||
/>
|
||||
);
|
||||
act(() => {
|
||||
(
|
||||
wrapper.find(EuiComboBox).props() as unknown as {
|
||||
onSearchChange: (a: string) => void;
|
||||
}
|
||||
).onSearchChange(' value 1');
|
||||
});
|
||||
|
||||
wrapper.update();
|
||||
const euiFormHelptext = wrapper.find(EuiFormHelpText);
|
||||
expect(euiFormHelptext.length).toBeTruthy();
|
||||
expect(euiFormHelptext.text()).toEqual('Warning: there is a space');
|
||||
});
|
||||
test('should show the warning helper text if selectedValue contains spaces when editing', () => {
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldWildcardComponent
|
||||
autocompleteService={autocompleteStartMock}
|
||||
indexPattern={{
|
||||
fields,
|
||||
id: '1234',
|
||||
title: 'logs-endpoint.events.*',
|
||||
}}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
onChange={jest.fn()}
|
||||
onError={jest.fn()}
|
||||
onWarning={jest.fn()}
|
||||
placeholder="Placeholder text"
|
||||
selectedField={getField('file.path.text')}
|
||||
selectedValue=" leading space"
|
||||
warning={''}
|
||||
/>
|
||||
);
|
||||
const euiFormHelptext = wrapper.find(EuiFormHelpText);
|
||||
expect(euiFormHelptext.length).toBeTruthy();
|
||||
expect(euiFormHelptext.text()).toEqual('Warning: there is a space');
|
||||
});
|
||||
test('should not show the warning helper text if selectedValue is falsy', () => {
|
||||
wrapper = mount(
|
||||
<AutocompleteFieldWildcardComponent
|
||||
autocompleteService={autocompleteStartMock}
|
||||
indexPattern={{
|
||||
fields,
|
||||
id: '1234',
|
||||
title: 'logs-endpoint.events.*',
|
||||
}}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
onChange={jest.fn()}
|
||||
onError={jest.fn()}
|
||||
onWarning={jest.fn()}
|
||||
placeholder="Placeholder text"
|
||||
selectedField={getField('file.path.text')}
|
||||
selectedValue=""
|
||||
warning={''}
|
||||
/>
|
||||
);
|
||||
const euiFormHelptext = wrapper.find(EuiFormHelpText);
|
||||
expect(euiFormHelptext.length).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
GetGenericComboBoxPropsReturn,
|
||||
} from '../get_generic_combo_box_props';
|
||||
import { paramIsValid } from '../param_is_valid';
|
||||
import { paramContainsSpace } from '../param_contains_space';
|
||||
|
||||
const SINGLE_SELECTION = { asPlainText: true };
|
||||
|
||||
|
@ -68,6 +69,7 @@ export const AutocompleteFieldWildcardComponent: React.FC<AutocompleteFieldWildc
|
|||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [touched, setIsTouched] = useState(false);
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
const [showSpacesWarning, setShowSpacesWarning] = useState<boolean>(false);
|
||||
const [isLoadingSuggestions, , suggestions] = useFieldValueAutocomplete({
|
||||
autocompleteService,
|
||||
fieldValue: selectedValue,
|
||||
|
@ -88,6 +90,13 @@ export const AutocompleteFieldWildcardComponent: React.FC<AutocompleteFieldWildc
|
|||
return selectedValue ? [valueAsStr] : [];
|
||||
}, [selectedValue]);
|
||||
|
||||
const handleSpacesWarning = useCallback(
|
||||
(param: string | undefined) => {
|
||||
if (!param) return setShowSpacesWarning(false);
|
||||
setShowSpacesWarning(!!paramContainsSpace(param));
|
||||
},
|
||||
[setShowSpacesWarning]
|
||||
);
|
||||
const handleError = useCallback(
|
||||
(err: string | undefined): void => {
|
||||
setError((existingErr): string | undefined => {
|
||||
|
@ -124,10 +133,12 @@ export const AutocompleteFieldWildcardComponent: React.FC<AutocompleteFieldWildc
|
|||
(newOptions: EuiComboBoxOptionOption[]): void => {
|
||||
const [newValue] = newOptions.map(({ label }) => optionsMemo[labels.indexOf(label)]);
|
||||
handleError(undefined);
|
||||
handleWarning(undefined);
|
||||
handleSpacesWarning(newValue);
|
||||
setShowSpacesWarning(false);
|
||||
|
||||
onChange(newValue ?? '');
|
||||
},
|
||||
[handleError, handleWarning, labels, onChange, optionsMemo]
|
||||
[handleError, handleSpacesWarning, labels, onChange, optionsMemo]
|
||||
);
|
||||
|
||||
const handleSearchChange = useCallback(
|
||||
|
@ -136,10 +147,12 @@ export const AutocompleteFieldWildcardComponent: React.FC<AutocompleteFieldWildc
|
|||
const err = paramIsValid(searchVal, selectedField, isRequired, touched);
|
||||
handleError(err);
|
||||
handleWarning(warning);
|
||||
if (!err) handleSpacesWarning(searchVal);
|
||||
|
||||
setSearchQuery(searchVal);
|
||||
}
|
||||
},
|
||||
[handleError, isRequired, selectedField, touched, warning, handleWarning]
|
||||
[handleError, handleSpacesWarning, isRequired, selectedField, touched, warning, handleWarning]
|
||||
);
|
||||
|
||||
const handleCreateOption = useCallback(
|
||||
|
@ -150,13 +163,24 @@ export const AutocompleteFieldWildcardComponent: React.FC<AutocompleteFieldWildc
|
|||
|
||||
if (err != null) {
|
||||
// Explicitly reject the user's input
|
||||
setShowSpacesWarning(false);
|
||||
return false;
|
||||
} else {
|
||||
onChange(option);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
handleSpacesWarning(option);
|
||||
onChange(option);
|
||||
return undefined;
|
||||
},
|
||||
[isRequired, onChange, selectedField, touched, handleError, handleWarning, warning]
|
||||
[
|
||||
isRequired,
|
||||
handleSpacesWarning,
|
||||
onChange,
|
||||
selectedField,
|
||||
touched,
|
||||
handleError,
|
||||
handleWarning,
|
||||
warning,
|
||||
]
|
||||
);
|
||||
|
||||
const setIsTouchedValue = useCallback((): void => {
|
||||
|
@ -195,15 +219,17 @@ export const AutocompleteFieldWildcardComponent: React.FC<AutocompleteFieldWildc
|
|||
if (onError != null) {
|
||||
onError(false);
|
||||
}
|
||||
handleSpacesWarning(selectedValue);
|
||||
|
||||
onWarning(false);
|
||||
}, [selectedField, onError, onWarning]);
|
||||
}, [selectedField, selectedValue, onError, onWarning, handleSpacesWarning]);
|
||||
|
||||
const defaultInput = useMemo((): JSX.Element => {
|
||||
return (
|
||||
<EuiFormRow
|
||||
label={rowLabel}
|
||||
error={error}
|
||||
helpText={warning}
|
||||
helpText={warning || (showSpacesWarning && i18n.FIELD_SPACE_WARNING)}
|
||||
isInvalid={selectedField != null && error != null}
|
||||
data-test-subj="valuesAutocompleteWildcardLabel"
|
||||
fullWidth
|
||||
|
@ -245,6 +271,7 @@ export const AutocompleteFieldWildcardComponent: React.FC<AutocompleteFieldWildc
|
|||
selectedField,
|
||||
setIsTouchedValue,
|
||||
warning,
|
||||
showSpacesWarning,
|
||||
]);
|
||||
|
||||
return defaultInput;
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { paramContainsSpace } from '.';
|
||||
|
||||
describe('param_contains_space', () => {
|
||||
test('should return true if leading spaces were found', () => {
|
||||
expect(paramContainsSpace(' test')).toBeTruthy();
|
||||
});
|
||||
test('should return true if trailing spaces were found', () => {
|
||||
expect(paramContainsSpace('test ')).toBeTruthy();
|
||||
});
|
||||
test('should return true if both trailing and leading spaces were found', () => {
|
||||
expect(paramContainsSpace(' test ')).toBeTruthy();
|
||||
});
|
||||
test('should return true if tabs was found', () => {
|
||||
expect(paramContainsSpace('\ttest')).toBeTruthy();
|
||||
});
|
||||
test('should return false if no spaces were found', () => {
|
||||
expect(paramContainsSpace('test test')).toBeFalsy();
|
||||
});
|
||||
test('should return false if param is falsy', () => {
|
||||
expect(paramContainsSpace('')).toBeFalsy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export const paramContainsSpace = (param: string) => param && param.trim().length !== param.length;
|
|
@ -27,3 +27,17 @@ export const NUMBER_ERR = i18n.translate('autocomplete.invalidNumberError', {
|
|||
export const DATE_ERR = i18n.translate('autocomplete.invalidDateError', {
|
||||
defaultMessage: 'Not a valid date',
|
||||
});
|
||||
|
||||
export const FIELD_SPACE_WARNING = i18n.translate('autocomplete.fieldSpaceWarning', {
|
||||
defaultMessage: "Warning: Spaces at the start or end of this value aren't being displayed.",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default {
|
||||
LOADING,
|
||||
SELECT_FIELD_FIRST,
|
||||
FIELD_REQUIRED_ERR,
|
||||
NUMBER_ERR,
|
||||
DATE_ERR,
|
||||
FIELD_SPACE_WARNING,
|
||||
};
|
||||
|
|
|
@ -21,6 +21,7 @@ import type {
|
|||
import { ListOperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { ValueWithSpaceWarning } from '../value_with_space_warning/value_with_space_warning';
|
||||
|
||||
const OS_LABELS = Object.freeze({
|
||||
linux: i18n.OS_LINUX,
|
||||
|
@ -97,7 +98,6 @@ export const ExceptionItemCardConditions = memo<CriteriaConditionsProps>(
|
|||
return nestedEntries.map((entry) => {
|
||||
const { field: nestedField, type: nestedType, operator: nestedOperator } = entry;
|
||||
const nestedValue = 'value' in entry ? entry.value : '';
|
||||
|
||||
return (
|
||||
<EuiFlexGroupNested
|
||||
data-test-subj={`${dataTestSubj}-nestedCondition`}
|
||||
|
@ -119,6 +119,7 @@ export const ExceptionItemCardConditions = memo<CriteriaConditionsProps>(
|
|||
value={getEntryValue(nestedType, nestedValue)}
|
||||
/>
|
||||
</EuiFlexItemNested>
|
||||
<ValueWithSpaceWarning value={nestedValue} />
|
||||
</EuiFlexGroupNested>
|
||||
);
|
||||
});
|
||||
|
@ -159,9 +160,9 @@ export const ExceptionItemCardConditions = memo<CriteriaConditionsProps>(
|
|||
{entries.map((entry, index) => {
|
||||
const { field, type } = entry;
|
||||
const value = getValue(entry);
|
||||
|
||||
const nestedEntries = 'entries' in entry ? entry.entries : [];
|
||||
const operator = 'operator' in entry ? entry.operator : '';
|
||||
|
||||
return (
|
||||
<div data-test-subj={`${dataTestSubj}-condition`} key={field + type + value + index}>
|
||||
<div className="eui-xScroll">
|
||||
|
@ -176,6 +177,7 @@ export const ExceptionItemCardConditions = memo<CriteriaConditionsProps>(
|
|||
description={getEntryOperator(type, operator)}
|
||||
value={getEntryValue(type, value)}
|
||||
/>
|
||||
<ValueWithSpaceWarning value={value} />
|
||||
</div>
|
||||
{nestedEntries != null && getNestedEntriesContent(type, nestedEntries)}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ValueWithSpaceWarning should not render if showSpaceWarning is falsy 1`] = `
|
||||
Object {
|
||||
"asFragment": [Function],
|
||||
"baseElement": <body>
|
||||
<div />
|
||||
</body>,
|
||||
"container": <div />,
|
||||
"debug": [Function],
|
||||
"findAllByAltText": [Function],
|
||||
"findAllByDisplayValue": [Function],
|
||||
"findAllByLabelText": [Function],
|
||||
"findAllByPlaceholderText": [Function],
|
||||
"findAllByRole": [Function],
|
||||
"findAllByTestId": [Function],
|
||||
"findAllByText": [Function],
|
||||
"findAllByTitle": [Function],
|
||||
"findByAltText": [Function],
|
||||
"findByDisplayValue": [Function],
|
||||
"findByLabelText": [Function],
|
||||
"findByPlaceholderText": [Function],
|
||||
"findByRole": [Function],
|
||||
"findByTestId": [Function],
|
||||
"findByText": [Function],
|
||||
"findByTitle": [Function],
|
||||
"getAllByAltText": [Function],
|
||||
"getAllByDisplayValue": [Function],
|
||||
"getAllByLabelText": [Function],
|
||||
"getAllByPlaceholderText": [Function],
|
||||
"getAllByRole": [Function],
|
||||
"getAllByTestId": [Function],
|
||||
"getAllByText": [Function],
|
||||
"getAllByTitle": [Function],
|
||||
"getByAltText": [Function],
|
||||
"getByDisplayValue": [Function],
|
||||
"getByLabelText": [Function],
|
||||
"getByPlaceholderText": [Function],
|
||||
"getByRole": [Function],
|
||||
"getByTestId": [Function],
|
||||
"getByText": [Function],
|
||||
"getByTitle": [Function],
|
||||
"queryAllByAltText": [Function],
|
||||
"queryAllByDisplayValue": [Function],
|
||||
"queryAllByLabelText": [Function],
|
||||
"queryAllByPlaceholderText": [Function],
|
||||
"queryAllByRole": [Function],
|
||||
"queryAllByTestId": [Function],
|
||||
"queryAllByText": [Function],
|
||||
"queryAllByTitle": [Function],
|
||||
"queryByAltText": [Function],
|
||||
"queryByDisplayValue": [Function],
|
||||
"queryByLabelText": [Function],
|
||||
"queryByPlaceholderText": [Function],
|
||||
"queryByRole": [Function],
|
||||
"queryByTestId": [Function],
|
||||
"queryByText": [Function],
|
||||
"queryByTitle": [Function],
|
||||
"rerender": [Function],
|
||||
"unmount": [Function],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`ValueWithSpaceWarning should not render if value is falsy 1`] = `
|
||||
Object {
|
||||
"asFragment": [Function],
|
||||
"baseElement": <body>
|
||||
<div />
|
||||
</body>,
|
||||
"container": <div />,
|
||||
"debug": [Function],
|
||||
"findAllByAltText": [Function],
|
||||
"findAllByDisplayValue": [Function],
|
||||
"findAllByLabelText": [Function],
|
||||
"findAllByPlaceholderText": [Function],
|
||||
"findAllByRole": [Function],
|
||||
"findAllByTestId": [Function],
|
||||
"findAllByText": [Function],
|
||||
"findAllByTitle": [Function],
|
||||
"findByAltText": [Function],
|
||||
"findByDisplayValue": [Function],
|
||||
"findByLabelText": [Function],
|
||||
"findByPlaceholderText": [Function],
|
||||
"findByRole": [Function],
|
||||
"findByTestId": [Function],
|
||||
"findByText": [Function],
|
||||
"findByTitle": [Function],
|
||||
"getAllByAltText": [Function],
|
||||
"getAllByDisplayValue": [Function],
|
||||
"getAllByLabelText": [Function],
|
||||
"getAllByPlaceholderText": [Function],
|
||||
"getAllByRole": [Function],
|
||||
"getAllByTestId": [Function],
|
||||
"getAllByText": [Function],
|
||||
"getAllByTitle": [Function],
|
||||
"getByAltText": [Function],
|
||||
"getByDisplayValue": [Function],
|
||||
"getByLabelText": [Function],
|
||||
"getByPlaceholderText": [Function],
|
||||
"getByRole": [Function],
|
||||
"getByTestId": [Function],
|
||||
"getByText": [Function],
|
||||
"getByTitle": [Function],
|
||||
"queryAllByAltText": [Function],
|
||||
"queryAllByDisplayValue": [Function],
|
||||
"queryAllByLabelText": [Function],
|
||||
"queryAllByPlaceholderText": [Function],
|
||||
"queryAllByRole": [Function],
|
||||
"queryAllByTestId": [Function],
|
||||
"queryAllByText": [Function],
|
||||
"queryAllByTitle": [Function],
|
||||
"queryByAltText": [Function],
|
||||
"queryByDisplayValue": [Function],
|
||||
"queryByLabelText": [Function],
|
||||
"queryByPlaceholderText": [Function],
|
||||
"queryByRole": [Function],
|
||||
"queryByTestId": [Function],
|
||||
"queryByText": [Function],
|
||||
"queryByTitle": [Function],
|
||||
"rerender": [Function],
|
||||
"unmount": [Function],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`ValueWithSpaceWarning should render if showSpaceWarning is truthy 1`] = `
|
||||
Object {
|
||||
"asFragment": [Function],
|
||||
"baseElement": <body>
|
||||
.c0 {
|
||||
display: inline;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
<div>
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<span
|
||||
class="euiToolTipAnchor"
|
||||
>
|
||||
<span
|
||||
color="warning"
|
||||
data-euiicon-type="iInCircle"
|
||||
data-test-subj="value_with_space_warning_tooltip"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>,
|
||||
"container": <div>
|
||||
<div
|
||||
class="sc-AxjAm dmOCRD"
|
||||
>
|
||||
<span
|
||||
class="euiToolTipAnchor"
|
||||
>
|
||||
<span
|
||||
color="warning"
|
||||
data-euiicon-type="iInCircle"
|
||||
data-test-subj="value_with_space_warning_tooltip"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>,
|
||||
"debug": [Function],
|
||||
"findAllByAltText": [Function],
|
||||
"findAllByDisplayValue": [Function],
|
||||
"findAllByLabelText": [Function],
|
||||
"findAllByPlaceholderText": [Function],
|
||||
"findAllByRole": [Function],
|
||||
"findAllByTestId": [Function],
|
||||
"findAllByText": [Function],
|
||||
"findAllByTitle": [Function],
|
||||
"findByAltText": [Function],
|
||||
"findByDisplayValue": [Function],
|
||||
"findByLabelText": [Function],
|
||||
"findByPlaceholderText": [Function],
|
||||
"findByRole": [Function],
|
||||
"findByTestId": [Function],
|
||||
"findByText": [Function],
|
||||
"findByTitle": [Function],
|
||||
"getAllByAltText": [Function],
|
||||
"getAllByDisplayValue": [Function],
|
||||
"getAllByLabelText": [Function],
|
||||
"getAllByPlaceholderText": [Function],
|
||||
"getAllByRole": [Function],
|
||||
"getAllByTestId": [Function],
|
||||
"getAllByText": [Function],
|
||||
"getAllByTitle": [Function],
|
||||
"getByAltText": [Function],
|
||||
"getByDisplayValue": [Function],
|
||||
"getByLabelText": [Function],
|
||||
"getByPlaceholderText": [Function],
|
||||
"getByRole": [Function],
|
||||
"getByTestId": [Function],
|
||||
"getByText": [Function],
|
||||
"getByTitle": [Function],
|
||||
"queryAllByAltText": [Function],
|
||||
"queryAllByDisplayValue": [Function],
|
||||
"queryAllByLabelText": [Function],
|
||||
"queryAllByPlaceholderText": [Function],
|
||||
"queryAllByRole": [Function],
|
||||
"queryAllByTestId": [Function],
|
||||
"queryAllByText": [Function],
|
||||
"queryAllByTitle": [Function],
|
||||
"queryByAltText": [Function],
|
||||
"queryByDisplayValue": [Function],
|
||||
"queryByLabelText": [Function],
|
||||
"queryByPlaceholderText": [Function],
|
||||
"queryByRole": [Function],
|
||||
"queryByTestId": [Function],
|
||||
"queryByText": [Function],
|
||||
"queryByTitle": [Function],
|
||||
"rerender": [Function],
|
||||
"unmount": [Function],
|
||||
}
|
||||
`;
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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 { renderHook } from '@testing-library/react-hooks';
|
||||
import { useValueWithSpaceWarning } from '../use_value_with_space_warning';
|
||||
|
||||
describe('useValueWithSpaceWarning', () => {
|
||||
it('should return true when value is string and contains space', () => {
|
||||
const { result } = renderHook(() => useValueWithSpaceWarning({ value: ' space before' }));
|
||||
|
||||
const { showSpaceWarningIcon, warningText } = result.current;
|
||||
expect(showSpaceWarningIcon).toBeTruthy();
|
||||
expect(warningText).toBeTruthy();
|
||||
});
|
||||
it('should return true when value is string and does not contain space', () => {
|
||||
const { result } = renderHook(() => useValueWithSpaceWarning({ value: 'no space' }));
|
||||
|
||||
const { showSpaceWarningIcon, warningText } = result.current;
|
||||
expect(showSpaceWarningIcon).toBeFalsy();
|
||||
expect(warningText).toBeTruthy();
|
||||
});
|
||||
it('should return true when value is array and one of the elements contains space', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useValueWithSpaceWarning({ value: [' space before', 'no space'] })
|
||||
);
|
||||
|
||||
const { showSpaceWarningIcon, warningText } = result.current;
|
||||
expect(showSpaceWarningIcon).toBeTruthy();
|
||||
expect(warningText).toBeTruthy();
|
||||
});
|
||||
it('should return true when value is array and none contains space', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useValueWithSpaceWarning({ value: ['no space', 'no space'] })
|
||||
);
|
||||
|
||||
const { showSpaceWarningIcon, warningText } = result.current;
|
||||
expect(showSpaceWarningIcon).toBeFalsy();
|
||||
expect(warningText).toBeTruthy();
|
||||
});
|
||||
it('should return the tooltipIconText', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useValueWithSpaceWarning({ value: ' space before', tooltipIconText: 'Warning Text' })
|
||||
);
|
||||
|
||||
const { showSpaceWarningIcon, warningText } = result.current;
|
||||
expect(showSpaceWarningIcon).toBeTruthy();
|
||||
expect(warningText).toEqual('Warning Text');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import { ValueWithSpaceWarning } from '..';
|
||||
|
||||
import * as useValueWithSpaceWarningMock from '../use_value_with_space_warning';
|
||||
|
||||
jest.mock('../use_value_with_space_warning');
|
||||
|
||||
describe('ValueWithSpaceWarning', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
useValueWithSpaceWarningMock.useValueWithSpaceWarning = jest
|
||||
.fn()
|
||||
.mockReturnValue({ showSpaceWarningIcon: true, warningText: 'Warning Text' });
|
||||
});
|
||||
it('should not render if value is falsy', () => {
|
||||
const container = render(<ValueWithSpaceWarning value="" />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
it('should not render if showSpaceWarning is falsy', () => {
|
||||
// @ts-ignore
|
||||
useValueWithSpaceWarningMock.useValueWithSpaceWarning = jest
|
||||
.fn()
|
||||
.mockReturnValue({ showSpaceWarningIcon: false, warningText: '' });
|
||||
|
||||
const container = render(<ValueWithSpaceWarning value="Test" />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
it('should render if showSpaceWarning is truthy', () => {
|
||||
const container = render(
|
||||
<ThemeProvider theme={() => ({ eui: { euiSizeXS: '4px' } })}>
|
||||
<ValueWithSpaceWarning value="Test" />
|
||||
</ThemeProvider>
|
||||
);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
it('should show the tooltip when the icon is clicked', async () => {
|
||||
const container = render(
|
||||
<ThemeProvider theme={() => ({ eui: { euiSizeXS: '4px' } })}>
|
||||
<ValueWithSpaceWarning value="Test" />
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
fireEvent.mouseOver(container.getByTestId('value_with_space_warning_tooltip'));
|
||||
expect(await container.findByText('Warning Text')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { ValueWithSpaceWarning } from './value_with_space_warning';
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { paramContainsSpace, autoCompletei18n } from '@kbn/securitysolution-autocomplete';
|
||||
|
||||
interface UseValueWithSpaceWarningResult {
|
||||
showSpaceWarningIcon: boolean;
|
||||
warningText: string;
|
||||
}
|
||||
interface UseValueWithSpaceWarningProps {
|
||||
value: string | string[];
|
||||
tooltipIconText?: string;
|
||||
}
|
||||
|
||||
export const useValueWithSpaceWarning = ({
|
||||
value,
|
||||
tooltipIconText,
|
||||
}: UseValueWithSpaceWarningProps): UseValueWithSpaceWarningResult => {
|
||||
const showSpaceWarningIcon = Array.isArray(value)
|
||||
? value.find(paramContainsSpace)
|
||||
: paramContainsSpace(value);
|
||||
|
||||
return {
|
||||
showSpaceWarningIcon: !!showSpaceWarningIcon,
|
||||
warningText: tooltipIconText || autoCompletei18n.FIELD_SPACE_WARNING,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import type { FC } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { EuiIcon, EuiToolTip } from '@elastic/eui';
|
||||
import { useValueWithSpaceWarning } from './use_value_with_space_warning';
|
||||
interface ValueWithSpaceWarningProps {
|
||||
value: string[] | string;
|
||||
tooltipIconType?: string;
|
||||
tooltipIconText?: string;
|
||||
}
|
||||
const Container = styled.div`
|
||||
display: inline;
|
||||
margin-left: ${({ theme }) => `${theme.eui.euiSizeXS}`};
|
||||
`;
|
||||
export const ValueWithSpaceWarning: FC<ValueWithSpaceWarningProps> = ({
|
||||
value,
|
||||
tooltipIconType = 'iInCircle',
|
||||
tooltipIconText,
|
||||
}) => {
|
||||
const { showSpaceWarningIcon, warningText } = useValueWithSpaceWarning({
|
||||
value,
|
||||
tooltipIconText,
|
||||
});
|
||||
if (!showSpaceWarningIcon || !value) return null;
|
||||
return (
|
||||
<Container>
|
||||
<EuiToolTip position="top" content={warningText}>
|
||||
<EuiIcon
|
||||
data-test-subj="value_with_space_warning_tooltip"
|
||||
type={tooltipIconType}
|
||||
color="warning"
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</Container>
|
||||
);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue