Skip to content

Commit f5645ee

Browse files
kibanamachinekertaleokoneyo
authored
[8.18] [DataViews] Improve fields matcher error handling (#233751) (#234762)
# Backport This will backport the following commits from `main` to `8.18`: - [[DataViews] Improve fields matcher error handling (#233751)](#233751) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Matthias Polman","email":"[email protected]"},"sourceCommit":{"committedDate":"2025-09-11T13:28:01Z","message":"[DataViews] Improve fields matcher error handling (#233751)\n\nImproving the robustness and user experience of the source filter management UI and its supporting utilities. The main focus is on better validation of filter patterns, enhanced error handling for invalid user input, and updating tests.\n\nCo-authored-by: Eyo Okon Eyo <[email protected]>","sha":"643d364dc6b8fc70b82f6cb136740a08eaed4713","branchLabelMapping":{"^v9.2.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:Data Views","release_note:skip","Team:DataDiscovery","Team:SharedUX","backport:all-open","v9.2.0"],"title":"[DataViews] Improve fields matcher error handling","number":233751,"url":"https://github.com/elastic/kibana/pull/233751","mergeCommit":{"message":"[DataViews] Improve fields matcher error handling (#233751)\n\nImproving the robustness and user experience of the source filter management UI and its supporting utilities. The main focus is on better validation of filter patterns, enhanced error handling for invalid user input, and updating tests.\n\nCo-authored-by: Eyo Okon Eyo <[email protected]>","sha":"643d364dc6b8fc70b82f6cb136740a08eaed4713"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.2.0","branchLabelMappingKey":"^v9.2.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/233751","number":233751,"mergeCommit":{"message":"[DataViews] Improve fields matcher error handling (#233751)\n\nImproving the robustness and user experience of the source filter management UI and its supporting utilities. The main focus is on better validation of filter patterns, enhanced error handling for invalid user input, and updating tests.\n\nCo-authored-by: Eyo Okon Eyo <[email protected]>","sha":"643d364dc6b8fc70b82f6cb136740a08eaed4713"}}]}] BACKPORT--> --------- Co-authored-by: Matthias Polman <[email protected]> Co-authored-by: Eyo Okon Eyo <[email protected]> Co-authored-by: Matthias Polman <[email protected]>
1 parent 27b7abc commit f5645ee

File tree

6 files changed

+123
-78
lines changed

6 files changed

+123
-78
lines changed

src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/source_filters_table/components/add_filter/__snapshots__/add_filter.test.tsx.snap

Lines changed: 0 additions & 57 deletions
This file was deleted.

src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/source_filters_table/components/add_filter/add_filter.test.tsx

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,89 @@
88
*/
99

1010
import React from 'react';
11-
import { shallow } from 'enzyme';
12-
11+
import { screen, render } from '@testing-library/react';
12+
import { userEvent } from '@testing-library/user-event';
13+
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
1314
import { AddFilter } from './add_filter';
1415

16+
type RenderAddFilterComponentProps = React.ComponentProps<typeof AddFilter>;
17+
18+
const mockMakeRegExTest = jest.fn(() => true);
19+
20+
jest.mock('@kbn/kibana-utils-plugin/common/field_wildcard', () => {
21+
const originalModule = jest.requireActual('@kbn/kibana-utils-plugin/common/field_wildcard');
22+
return {
23+
...originalModule,
24+
makeRegEx: () => {
25+
return {
26+
test: mockMakeRegExTest,
27+
} as unknown as RegExp;
28+
},
29+
};
30+
});
31+
32+
const renderAddFilterComponent = (
33+
{ onAddFilter }: RenderAddFilterComponentProps = { onAddFilter: jest.fn() }
34+
) => {
35+
return render(
36+
<IntlProvider locale="en">
37+
<AddFilter onAddFilter={onAddFilter} />
38+
</IntlProvider>
39+
);
40+
};
41+
1542
describe('AddFilter', () => {
16-
test('should render normally', () => {
17-
const component = shallow(<AddFilter onAddFilter={() => {}} />);
43+
test('should render normally', async () => {
44+
renderAddFilterComponent();
1845

19-
expect(component).toMatchSnapshot();
46+
expect(screen.getByTestId('fieldFilterInput')).toBeVisible();
2047
});
2148

2249
test('should allow adding a filter', async () => {
50+
const user = userEvent.setup();
2351
const onAddFilter = jest.fn();
24-
const component = shallow(<AddFilter onAddFilter={onAddFilter} />);
25-
26-
component.find('EuiFieldText').simulate('change', { target: { value: 'tim*' } });
27-
component.find('EuiButton').simulate('click');
28-
component.update();
52+
renderAddFilterComponent({ onAddFilter });
2953

54+
await user.type(screen.getByTestId('fieldFilterInput'), 'tim*');
55+
await user.click(screen.getByText('Add'));
3056
expect(onAddFilter).toBeCalledWith('tim*');
3157
});
3258

33-
test('should ignore strings with just spaces', () => {
34-
const component = shallow(<AddFilter onAddFilter={() => {}} />);
59+
test('should ignore strings with just spaces', async () => {
60+
const user = userEvent.setup();
61+
const onAddFilter = jest.fn();
62+
63+
renderAddFilterComponent({ onAddFilter });
3564

3665
// Set a value in the input field
37-
component.find('EuiFieldText').simulate('keypress', ' ');
38-
component.update();
66+
await user.type(screen.getByTestId('fieldFilterInput'), ' ');
67+
await user.click(screen.getByText('Add'));
68+
expect(onAddFilter).not.toBeCalled();
69+
});
70+
71+
test('should handle errors with invalid filter patterns', async () => {
72+
const user = userEvent.setup();
73+
74+
// Simulate makeRegEx throwing an error for invalid regex
75+
mockMakeRegExTest.mockImplementationOnce(() => {
76+
throw new Error('Invalid regex');
77+
});
78+
79+
renderAddFilterComponent();
80+
81+
// Set a value in the input field, we know this will be regarded as invalid because of the mock above
82+
await user.type(screen.getByTestId('fieldFilterInput'), '*//foo');
83+
// Trigger the blur event to validate the input
84+
await user.tab();
85+
86+
expect(await screen.findByTestId('fieldFilterInput')).toHaveAttribute('aria-invalid', 'true');
87+
expect(screen.getByTestId('addFieldFilterButton')).toBeDisabled();
88+
89+
// This would be regarded as a valid regex
90+
await user.type(screen.getByTestId('fieldFilterInput'), '*//foo');
91+
await user.tab();
3992

40-
expect(component).toMatchSnapshot();
93+
expect(await screen.findByTestId('fieldFilterInput')).not.toHaveAttribute('aria-invalid');
94+
expect(screen.getByTestId('addFieldFilterButton')).not.toBeDisabled();
4195
});
4296
});

src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/source_filters_table/components/add_filter/add_filter.tsx

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import React, { useState, useCallback } from 'react';
1111

1212
import { i18n } from '@kbn/i18n';
1313
import { FormattedMessage } from '@kbn/i18n-react';
14+
import { makeRegEx } from '@kbn/kibana-utils-plugin/common';
1415
import { EuiFlexGroup, EuiFlexItem, EuiFieldText, EuiButton } from '@elastic/eui';
1516

1617
interface AddFilterProps {
@@ -26,28 +27,59 @@ const sourcePlaceholder = i18n.translate(
2627
);
2728

2829
export const AddFilter = ({ onAddFilter }: AddFilterProps) => {
29-
const [filter, setFilter] = useState<string>('');
30+
const [filter, setFilter] = useState('');
31+
const [isInvalid, setIsInvalid] = useState(false);
32+
33+
const isAddButtonDisabled = filter.length === 0 || isInvalid;
3034

3135
const onAddButtonClick = useCallback(() => {
3236
onAddFilter(filter);
3337
setFilter('');
3438
}, [filter, onAddFilter]);
3539

40+
const onInputChange = useCallback(
41+
(e: React.ChangeEvent<HTMLInputElement>) => {
42+
const value = e.target.value.trim();
43+
setFilter(value);
44+
},
45+
[setFilter]
46+
);
47+
48+
const onInputBlur = useCallback((e: React.FocusEvent<HTMLInputElement>) => {
49+
const value = e.target.value.trim();
50+
51+
if (value.length === 0) {
52+
setIsInvalid(true);
53+
return;
54+
}
55+
56+
try {
57+
// test value is not important, just that the created regex is able to compile
58+
makeRegEx(value).test('');
59+
setIsInvalid(false);
60+
} catch (_) {
61+
setIsInvalid(true);
62+
return;
63+
}
64+
}, []);
65+
3666
return (
3767
<EuiFlexGroup>
3868
<EuiFlexItem grow={10}>
3969
<EuiFieldText
4070
fullWidth
4171
value={filter}
4272
data-test-subj="fieldFilterInput"
43-
onChange={(e) => setFilter(e.target.value.trim())}
73+
isInvalid={isInvalid}
74+
onBlur={onInputBlur}
75+
onChange={onInputChange}
4476
placeholder={sourcePlaceholder}
4577
/>
4678
</EuiFlexItem>
4779
<EuiFlexItem>
4880
<EuiButton
4981
data-test-subj="addFieldFilterButton"
50-
isDisabled={filter.length === 0}
82+
isDisabled={isAddButtonDisabled}
5183
onClick={onAddButtonClick}
5284
>
5385
<FormattedMessage

src/platform/plugins/shared/kibana_utils/common/field_wildcard.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import { fieldWildcardFilter, makeRegEx } from './field_wildcard';
11+
import { fieldWildcardMatcher } from './field_wildcard';
1112

1213
describe('fieldWildcard', () => {
1314
const metaFields = ['_id', '_type', '_source'];
@@ -72,5 +73,14 @@ describe('fieldWildcard', () => {
7273

7374
expect(original.filter(filter)).toEqual([null, 'bar', {}, [], 'baz']);
7475
});
76+
77+
it('logs and safely handles invalid glob inputs (non-string) without throwing', function () {
78+
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
79+
// Pass a non-string value to trigger the try/catch path inside fieldWildcardMatcher
80+
const matcher = fieldWildcardMatcher([null as unknown as string], metaFields);
81+
expect(() => matcher('foo')).not.toThrow();
82+
expect(consoleSpy).toHaveBeenCalled();
83+
consoleSpy.mockRestore();
84+
});
7585
});
7686
});

src/platform/plugins/shared/kibana_utils/common/field_wildcard.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,18 @@ export const makeRegEx = memoize(function makeRegEx(glob: string) {
1717

1818
// Note that this will return an essentially noop function if globs is undefined.
1919
export function fieldWildcardMatcher(globs: string[] = [], metaFields: unknown[] = []) {
20-
return function matcher(val: unknown) {
20+
return function matcher(val: unknown): boolean {
2121
// do not test metaFields or keyword
2222
if (metaFields.indexOf(val) !== -1) {
2323
return false;
2424
}
25-
return globs.some((p) => makeRegEx(p).test(`${val}`));
25+
try {
26+
return globs.some((p) => makeRegEx(p).test(String(val)));
27+
} catch (e) {
28+
// eslint-disable-next-line no-console
29+
console.error(e.toString());
30+
return false;
31+
}
2632
};
2733
}
2834

src/platform/plugins/shared/kibana_utils/common/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*/
99

1010
export { Defer, defer } from './defer';
11-
export { fieldWildcardMatcher, fieldWildcardFilter } from './field_wildcard';
11+
export { fieldWildcardMatcher, fieldWildcardFilter, makeRegEx } from './field_wildcard';
1212
export { of } from './of';
1313
export type {
1414
BaseState,

0 commit comments

Comments
 (0)