From a1d74a2ecaaf9c4c8b156681f416477d29e0dfba Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Fri, 21 Apr 2023 18:52:15 +0200 Subject: [PATCH 1/7] [UnifiedFieldList] Allow wildcards in field search --- .../public/hooks/use_field_filters.ts | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/plugins/unified_field_list/public/hooks/use_field_filters.ts b/src/plugins/unified_field_list/public/hooks/use_field_filters.ts index 803739caba7c6..07578613e5107 100644 --- a/src/plugins/unified_field_list/public/hooks/use_field_filters.ts +++ b/src/plugins/unified_field_list/public/hooks/use_field_filters.ts @@ -7,6 +7,7 @@ */ import { useMemo, useState } from 'react'; +import { escapeRegExp, memoize } from 'lodash'; import { htmlIdGenerator } from '@elastic/eui'; import { type DataViewField } from '@kbn/data-views-plugin/common'; import type { CoreStart } from '@kbn/core-lifecycle-browser'; @@ -16,6 +17,22 @@ import { getFieldIconType } from '../utils/field_types'; const htmlId = htmlIdGenerator('fieldList'); +export const makeRegEx = memoize(function makeRegEx(glob: string) { + const globRegex = glob.split('*').filter(Boolean).map(escapeRegExp).join('.*'); + return new RegExp(globRegex, 'i'); +}); + +export const fieldNameMatcher = (field: FieldListItem, fieldSearchHighlight: string): boolean => { + if (!fieldSearchHighlight) { + return false; + } + + return ( + (!!field.displayName && makeRegEx(fieldSearchHighlight).test(field.displayName)) || + makeRegEx(fieldSearchHighlight).test(field.name) + ); +}; + /** * Input params for useFieldFilters hook */ @@ -74,12 +91,10 @@ export function useFieldFilters({ onFilterField: fieldSearchHighlight?.length || selectedFieldTypes.length > 0 ? (field: T) => { - if ( - fieldSearchHighlight?.length && - !field.name?.toLowerCase().includes(fieldSearchHighlight) && - !field.displayName?.toLowerCase().includes(fieldSearchHighlight) - ) { - return false; + if (fieldSearchHighlight) { + if (!fieldNameMatcher(field, fieldSearchHighlight)) { + return false; + } } if (selectedFieldTypes.length > 0) { return selectedFieldTypes.includes(getFieldIconType(field, getCustomFieldType)); From 1cd1a4693757734b4383d5e571398346791e1c67 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Fri, 21 Apr 2023 19:01:25 +0200 Subject: [PATCH 2/7] [UnifiedFieldList] Add tests --- .../public/hooks/use_field_filters.test.tsx | 27 +++++++++++++++++++ .../public/hooks/use_field_filters.ts | 18 +------------ .../public/utils/field_name_matcher.ts | 26 ++++++++++++++++++ 3 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 src/plugins/unified_field_list/public/utils/field_name_matcher.ts diff --git a/src/plugins/unified_field_list/public/hooks/use_field_filters.test.tsx b/src/plugins/unified_field_list/public/hooks/use_field_filters.test.tsx index 77327fcbee402..2ce8f38d7f289 100644 --- a/src/plugins/unified_field_list/public/hooks/use_field_filters.test.tsx +++ b/src/plugins/unified_field_list/public/hooks/use_field_filters.test.tsx @@ -68,6 +68,33 @@ describe('UnifiedFieldList useFieldFilters()', () => { expect(result.current.onFilterField!(dataView.getFieldByName('bytes')!)).toBe(false); }); + it('should update correctly on search by name which has a wildcard', async () => { + const props: FieldFiltersParams = { + allFields: dataView.fields, + services: mockedServices, + }; + const { result } = renderHook(useFieldFilters, { + initialProps: props, + }); + + expect(result.current.fieldSearchHighlight).toBe(''); + expect(result.current.onFilterField).toBeUndefined(); + + act(() => { + result.current.fieldListFiltersProps.onChangeNameFilter('message*me1'); + }); + + expect(result.current.fieldSearchHighlight).toBe('message*me1'); + expect(result.current.onFilterField).toBeDefined(); + expect(result.current.onFilterField!({ displayName: 'test' } as DataViewField)).toBe(false); + expect(result.current.onFilterField!({ displayName: 'message' } as DataViewField)).toBe(false); + expect(result.current.onFilterField!({ displayName: 'message.name1' } as DataViewField)).toBe( + true + ); + expect(result.current.onFilterField!({ name: 'messagename10' } as DataViewField)).toBe(true); + expect(result.current.onFilterField!({ name: 'message.test' } as DataViewField)).toBe(false); + }); + it('should update correctly on filter by type', async () => { const props: FieldFiltersParams = { allFields: dataView.fields, diff --git a/src/plugins/unified_field_list/public/hooks/use_field_filters.ts b/src/plugins/unified_field_list/public/hooks/use_field_filters.ts index 07578613e5107..ec4e741fc7159 100644 --- a/src/plugins/unified_field_list/public/hooks/use_field_filters.ts +++ b/src/plugins/unified_field_list/public/hooks/use_field_filters.ts @@ -7,32 +7,16 @@ */ import { useMemo, useState } from 'react'; -import { escapeRegExp, memoize } from 'lodash'; import { htmlIdGenerator } from '@elastic/eui'; import { type DataViewField } from '@kbn/data-views-plugin/common'; import type { CoreStart } from '@kbn/core-lifecycle-browser'; import { type FieldListFiltersProps } from '../components/field_list_filters'; import { type FieldListItem, type FieldTypeKnown, GetCustomFieldType } from '../types'; import { getFieldIconType } from '../utils/field_types'; +import { fieldNameMatcher } from '../utils/field_name_matcher'; const htmlId = htmlIdGenerator('fieldList'); -export const makeRegEx = memoize(function makeRegEx(glob: string) { - const globRegex = glob.split('*').filter(Boolean).map(escapeRegExp).join('.*'); - return new RegExp(globRegex, 'i'); -}); - -export const fieldNameMatcher = (field: FieldListItem, fieldSearchHighlight: string): boolean => { - if (!fieldSearchHighlight) { - return false; - } - - return ( - (!!field.displayName && makeRegEx(fieldSearchHighlight).test(field.displayName)) || - makeRegEx(fieldSearchHighlight).test(field.name) - ); -}; - /** * Input params for useFieldFilters hook */ diff --git a/src/plugins/unified_field_list/public/utils/field_name_matcher.ts b/src/plugins/unified_field_list/public/utils/field_name_matcher.ts new file mode 100644 index 0000000000000..5120ea9693c95 --- /dev/null +++ b/src/plugins/unified_field_list/public/utils/field_name_matcher.ts @@ -0,0 +1,26 @@ +/* + * 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 { escapeRegExp, memoize } from 'lodash'; +import { FieldListItem } from '../types'; + +const makeRegEx = memoize(function makeRegEx(glob: string) { + const globRegex = glob.split('*').filter(Boolean).map(escapeRegExp).join('.*'); + return new RegExp(globRegex, 'i'); +}); + +export const fieldNameMatcher = (field: FieldListItem, fieldSearchHighlight: string): boolean => { + if (!fieldSearchHighlight) { + return false; + } + + return ( + (!!field.displayName && makeRegEx(fieldSearchHighlight).test(field.displayName)) || + makeRegEx(fieldSearchHighlight).test(field.name) + ); +}; From e7ec180979784b2f6c0192592817f3c1d0df9fb9 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Fri, 21 Apr 2023 19:09:15 +0200 Subject: [PATCH 3/7] [UnifiedFieldList] Add more tests --- .../public/hooks/use_field_filters.ts | 8 ++--- .../field_name_wildcard_matcher.test.tsx | 32 +++++++++++++++++++ ...cher.ts => field_name_wildcard_matcher.ts} | 5 ++- 3 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.test.tsx rename src/plugins/unified_field_list/public/utils/{field_name_matcher.ts => field_name_wildcard_matcher.ts} (88%) diff --git a/src/plugins/unified_field_list/public/hooks/use_field_filters.ts b/src/plugins/unified_field_list/public/hooks/use_field_filters.ts index ec4e741fc7159..c3e08ff335602 100644 --- a/src/plugins/unified_field_list/public/hooks/use_field_filters.ts +++ b/src/plugins/unified_field_list/public/hooks/use_field_filters.ts @@ -13,7 +13,7 @@ import type { CoreStart } from '@kbn/core-lifecycle-browser'; import { type FieldListFiltersProps } from '../components/field_list_filters'; import { type FieldListItem, type FieldTypeKnown, GetCustomFieldType } from '../types'; import { getFieldIconType } from '../utils/field_types'; -import { fieldNameMatcher } from '../utils/field_name_matcher'; +import { fieldNameWildcardMatcher } from '../utils/field_name_wildcard_matcher'; const htmlId = htmlIdGenerator('fieldList'); @@ -75,10 +75,8 @@ export function useFieldFilters({ onFilterField: fieldSearchHighlight?.length || selectedFieldTypes.length > 0 ? (field: T) => { - if (fieldSearchHighlight) { - if (!fieldNameMatcher(field, fieldSearchHighlight)) { - return false; - } + if (fieldSearchHighlight && !fieldNameWildcardMatcher(field, fieldSearchHighlight)) { + return false; } if (selectedFieldTypes.length > 0) { return selectedFieldTypes.includes(getFieldIconType(field, getCustomFieldType)); diff --git a/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.test.tsx b/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.test.tsx new file mode 100644 index 0000000000000..af7ae55925514 --- /dev/null +++ b/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.test.tsx @@ -0,0 +1,32 @@ +/* + * 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 { type DataViewField } from '@kbn/data-views-plugin/common'; +import { fieldNameWildcardMatcher } from './field_name_wildcard_matcher'; + +describe('UnifiedFieldList fieldNameWildcardMatcher()', () => { + it('should work correctly', async () => { + expect(fieldNameWildcardMatcher({ displayName: 'test' } as DataViewField, 'no')).toBe(false); + expect( + fieldNameWildcardMatcher({ displayName: 'test', name: 'yes' } as DataViewField, 'yes') + ).toBe(true); + + const search = 'test*ue'; + expect(fieldNameWildcardMatcher({ displayName: 'test' } as DataViewField, search)).toBe(false); + expect(fieldNameWildcardMatcher({ displayName: 'test.value' } as DataViewField, search)).toBe( + true + ); + expect(fieldNameWildcardMatcher({ name: 'test.this_value' } as DataViewField, search)).toBe( + true + ); + expect( + fieldNameWildcardMatcher({ name: 'test.this_value.anyway' } as DataViewField, search) + ).toBe(true); + expect(fieldNameWildcardMatcher({ name: 'message.test' } as DataViewField, search)).toBe(false); + }); +}); diff --git a/src/plugins/unified_field_list/public/utils/field_name_matcher.ts b/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.ts similarity index 88% rename from src/plugins/unified_field_list/public/utils/field_name_matcher.ts rename to src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.ts index 5120ea9693c95..70b27e9ca3f47 100644 --- a/src/plugins/unified_field_list/public/utils/field_name_matcher.ts +++ b/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.ts @@ -14,7 +14,10 @@ const makeRegEx = memoize(function makeRegEx(glob: string) { return new RegExp(globRegex, 'i'); }); -export const fieldNameMatcher = (field: FieldListItem, fieldSearchHighlight: string): boolean => { +export const fieldNameWildcardMatcher = ( + field: FieldListItem, + fieldSearchHighlight: string +): boolean => { if (!fieldSearchHighlight) { return false; } From ddbb339f3499f749101d44dfd6315535357a2380 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Fri, 21 Apr 2023 19:19:28 +0200 Subject: [PATCH 4/7] [UnifiedFieldList] Update the logic --- .../public/utils/field_name_wildcard_matcher.test.tsx | 10 ++++++++-- .../public/utils/field_name_wildcard_matcher.ts | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.test.tsx b/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.test.tsx index af7ae55925514..2637ddf4046d2 100644 --- a/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.test.tsx +++ b/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.test.tsx @@ -24,9 +24,15 @@ describe('UnifiedFieldList fieldNameWildcardMatcher()', () => { expect(fieldNameWildcardMatcher({ name: 'test.this_value' } as DataViewField, search)).toBe( true ); + expect(fieldNameWildcardMatcher({ name: 'message.test' } as DataViewField, search)).toBe(false); + expect( + fieldNameWildcardMatcher({ name: 'test.this_value.maybe' } as DataViewField, search) + ).toBe(false); expect( - fieldNameWildcardMatcher({ name: 'test.this_value.anyway' } as DataViewField, search) + fieldNameWildcardMatcher({ name: 'test.this_value.maybe' } as DataViewField, `${search}*`) + ).toBe(true); + expect( + fieldNameWildcardMatcher({ name: 'test.this_value.maybe' } as DataViewField, '*value*') ).toBe(true); - expect(fieldNameWildcardMatcher({ name: 'message.test' } as DataViewField, search)).toBe(false); }); }); diff --git a/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.ts b/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.ts index 70b27e9ca3f47..57796b12e2175 100644 --- a/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.ts +++ b/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.ts @@ -10,8 +10,8 @@ import { escapeRegExp, memoize } from 'lodash'; import { FieldListItem } from '../types'; const makeRegEx = memoize(function makeRegEx(glob: string) { - const globRegex = glob.split('*').filter(Boolean).map(escapeRegExp).join('.*'); - return new RegExp(globRegex, 'i'); + const globRegex = glob.split('*').map(escapeRegExp).join('.*'); + return new RegExp(globRegex.includes('*') ? `^${globRegex}$` : globRegex, 'i'); }); export const fieldNameWildcardMatcher = ( From 1481331f13f244bad416a293b8e70e674a607c34 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Fri, 21 Apr 2023 20:10:09 +0200 Subject: [PATCH 5/7] [UnifiedFieldList] Fix tests --- .../unified_field_list/public/hooks/use_field_filters.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/unified_field_list/public/hooks/use_field_filters.test.tsx b/src/plugins/unified_field_list/public/hooks/use_field_filters.test.tsx index 2ce8f38d7f289..1295d14f374b2 100644 --- a/src/plugins/unified_field_list/public/hooks/use_field_filters.test.tsx +++ b/src/plugins/unified_field_list/public/hooks/use_field_filters.test.tsx @@ -91,7 +91,7 @@ describe('UnifiedFieldList useFieldFilters()', () => { expect(result.current.onFilterField!({ displayName: 'message.name1' } as DataViewField)).toBe( true ); - expect(result.current.onFilterField!({ name: 'messagename10' } as DataViewField)).toBe(true); + expect(result.current.onFilterField!({ name: 'messagename10' } as DataViewField)).toBe(false); expect(result.current.onFilterField!({ name: 'message.test' } as DataViewField)).toBe(false); }); From abd963cacc9054d40e62fdd378402c309dae78c9 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Mon, 24 Apr 2023 10:23:56 +0200 Subject: [PATCH 6/7] [UnifiedFieldList] Adjust search highlight --- .../field_item_button.test.tsx.snap | 29 +++++++++++++++++++ .../field_item_button.test.tsx | 14 +++++++++ .../field_item_button/field_item_button.tsx | 15 +++++++++- .../utils/field_name_wildcard_matcher.ts | 3 +- 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/plugins/unified_field_list/public/components/field_item_button/__snapshots__/field_item_button.test.tsx.snap b/src/plugins/unified_field_list/public/components/field_item_button/__snapshots__/field_item_button.test.tsx.snap index d1068660c1eaa..899d6a7579123 100644 --- a/src/plugins/unified_field_list/public/components/field_item_button/__snapshots__/field_item_button.test.tsx.snap +++ b/src/plugins/unified_field_list/public/components/field_item_button/__snapshots__/field_item_button.test.tsx.snap @@ -88,6 +88,35 @@ exports[`UnifiedFieldList renders properly for text-based co /> `; +exports[`UnifiedFieldList renders properly for wildcard search 1`] = ` + + } + fieldName={ + + script date + + } + isActive={false} + key="field-item-button-script date" + size="s" +/> +`; + exports[`UnifiedFieldList renders properly when a conflict field 1`] = ` ', () => { ); expect(component).toMatchSnapshot(); }); + + test('renders properly for wildcard search', () => { + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); }); diff --git a/src/plugins/unified_field_list/public/components/field_item_button/field_item_button.tsx b/src/plugins/unified_field_list/public/components/field_item_button/field_item_button.tsx index df24125027e2f..ec0078431a8bc 100644 --- a/src/plugins/unified_field_list/public/components/field_item_button/field_item_button.tsx +++ b/src/plugins/unified_field_list/public/components/field_item_button/field_item_button.tsx @@ -14,6 +14,7 @@ import { EuiButtonIcon, EuiButtonIconProps, EuiHighlight, EuiIcon, EuiToolTip } import type { DataViewField } from '@kbn/data-views-plugin/common'; import { type FieldListItem, type GetCustomFieldType } from '../../types'; import { FieldIcon, getFieldIconProps } from '../field_icon'; +import { fieldNameWildcardMatcher } from '../../utils/field_name_wildcard_matcher'; import './field_item_button.scss'; /** @@ -194,7 +195,7 @@ export function FieldItemButton({ fieldIcon={} fieldName={ @@ -230,3 +231,15 @@ function FieldConflictInfoIcon() { ); } + +function getSearchHighlight(displayName: string, fieldSearchHighlight?: string): string { + const searchHighlight = fieldSearchHighlight || ''; + if ( + searchHighlight.includes('*') && + fieldNameWildcardMatcher({ name: displayName }, searchHighlight) + ) { + return displayName; + } + + return searchHighlight; +} diff --git a/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.ts b/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.ts index 57796b12e2175..b90b86cef2e10 100644 --- a/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.ts +++ b/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.ts @@ -7,7 +7,6 @@ */ import { escapeRegExp, memoize } from 'lodash'; -import { FieldListItem } from '../types'; const makeRegEx = memoize(function makeRegEx(glob: string) { const globRegex = glob.split('*').map(escapeRegExp).join('.*'); @@ -15,7 +14,7 @@ const makeRegEx = memoize(function makeRegEx(glob: string) { }); export const fieldNameWildcardMatcher = ( - field: FieldListItem, + field: { name: string; displayName?: string }, fieldSearchHighlight: string ): boolean => { if (!fieldSearchHighlight) { From 638fb593893be74b4942284033e98cf089cab030 Mon Sep 17 00:00:00 2001 From: Julia Rechkunova Date: Mon, 24 Apr 2023 10:45:57 +0200 Subject: [PATCH 7/7] [UnifiedFieldList] Add a comment --- .../public/utils/field_name_wildcard_matcher.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.ts b/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.ts index b90b86cef2e10..98b0e64b7bc78 100644 --- a/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.ts +++ b/src/plugins/unified_field_list/public/utils/field_name_wildcard_matcher.ts @@ -13,6 +13,12 @@ const makeRegEx = memoize(function makeRegEx(glob: string) { return new RegExp(globRegex.includes('*') ? `^${globRegex}$` : globRegex, 'i'); }); +/** + * Checks if field displayName or name matches the provided search string. + * The search string can have wildcard. + * @param field + * @param fieldSearchHighlight + */ export const fieldNameWildcardMatcher = ( field: { name: string; displayName?: string }, fieldSearchHighlight: string