[Lens] [Unified Search] [Maps] [Dashboard] Adds wildcard matching to field pickers across Kibana (#182631)

## Summary

Users can now utilize '*' or spaces to effectively search through
various comboboxes or selectables. The change has been done for field
pickers across Lens, Unified Search, Dashboard controls, Maps and
Discover.

Fixes https://github.com/elastic/kibana/issues/179898

Places where it is added:
1. All field pickers in lens
<img width="282" alt="Screenshot 2024-05-05 at 23 25 41"
src="17c309ca-758b-49a2-8658-5f0a00aab19d">

2. Field picker in Unified Search
<img width="965" alt="Screenshot 2024-05-05 at 23 26 00"
src="d10a4cc0-f469-4e23-9890-a9632f05b16a">

3. Maps
<img width="352" alt="Screenshot 2024-05-05 at 23 27 13"
src="a988e3ff-f08f-4e6e-ba6d-da28bf8c6e96">


4. presentations
<img width="531" alt="Screenshot 2024-05-05 at 23 28 03"
src="61ef675e-e580-4c0a-aaf3-982ae1a59258">


5. Discover breakdown field
<img width="323" alt="Screenshot 2024-05-05 at 23 29 06"
src="8c5c46ab-f896-4556-a25f-1fdfd7f30028">
This commit is contained in:
Marta Bondyra 2024-05-07 20:24:45 +02:00 committed by GitHub
parent 915f0d2475
commit 43db7a6e55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 45 additions and 3 deletions

View file

@ -21,6 +21,7 @@ export { getFieldTypeDescription } from './src/utils/get_field_type_description'
export { getFieldTypeName, UNKNOWN_FIELD_TYPE_MESSAGE } from './src/utils/get_field_type_name';
export {
fieldNameWildcardMatcher,
comboBoxFieldOptionMatcher,
getFieldSearchMatchingHighlight,
} from './src/utils/field_name_wildcard_matcher';

View file

@ -42,6 +42,19 @@ export const fieldNameWildcardMatcher = (
return (!!field.displayName && regExp.test(field.displayName)) || regExp.test(field.name);
};
/**
* Adapts fieldNameWildcardMatcher to combobox props.
* @param field
* @param fieldSearchHighlight
*/
export const comboBoxFieldOptionMatcher = ({
option: { name, label },
searchValue,
}: {
option: { name?: string; label: string };
searchValue: string;
}) => fieldNameWildcardMatcher({ name: name || label, displayName: label }, searchValue);
/**
* Get `highlight` string to be used together with `EuiHighlight`
* @param displayName

View file

@ -10,6 +10,7 @@ import './field_picker.scss';
import React from 'react';
import { i18n } from '@kbn/i18n';
import classNames from 'classnames';
import { comboBoxFieldOptionMatcher } from '@kbn/field-utils';
import { EuiComboBox, EuiComboBoxOptionOption, EuiComboBoxProps } from '@elastic/eui';
import { FieldIcon } from '@kbn/field-utils/src/components/field_icon';
import { calculateWidthFromCharCount } from '@kbn/calculate-width-from-char-count';
@ -91,6 +92,7 @@ export function FieldPicker<T extends FieldOptionValue = FieldOptionValue>(
placeholder={i18n.translate('visualizationUiComponents.fieldPicker.fieldPlaceholder', {
defaultMessage: 'Select a field',
})}
optionMatcher={comboBoxFieldOptionMatcher}
options={styledOptions}
isInvalid={fieldIsInvalid}
selectedOptions={selectedOption ? [selectedOption] : []}

View file

@ -8,6 +8,7 @@
import classNames from 'classnames';
import { sortBy, uniq } from 'lodash';
import { comboBoxFieldOptionMatcher } from '@kbn/field-utils';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { i18n } from '@kbn/i18n';
@ -63,6 +64,7 @@ export const FieldPicker = ({
const options: EuiSelectableOption[] = (availableFields ?? []).map((field) => {
return {
key: field.name,
name: field.name,
label: field.displayName ?? field.name,
className: 'presFieldPicker__fieldButton',
checked: field.name === selectedFieldName ? 'on' : undefined,
@ -129,6 +131,7 @@ export const FieldPicker = ({
const field = dataView.getFieldByName(changedOption.key);
if (field) onSelectField?.(field);
}}
optionMatcher={comboBoxFieldOptionMatcher}
searchProps={{
'data-test-subj': 'field-search-input',
placeholder: i18n.translate('presentationUtil.fieldSearch.searchPlaceHolder', {

View file

@ -8,7 +8,7 @@
import React, { useCallback, useMemo } from 'react';
import { EuiSelectableOption } from '@elastic/eui';
import { FieldIcon, getFieldIconProps } from '@kbn/field-utils';
import { FieldIcon, getFieldIconProps, comboBoxFieldOptionMatcher } from '@kbn/field-utils';
import { css } from '@emotion/react';
import type { DataView, DataViewField } from '@kbn/data-views-plugin/common';
import { i18n } from '@kbn/i18n';
@ -37,6 +37,7 @@ export const BreakdownFieldSelector = ({
.filter(fieldSupportsBreakdown)
.map((field) => ({
key: field.name,
name: field.name,
label: field.displayName,
value: field.name,
checked:
@ -102,6 +103,7 @@ export const BreakdownFieldSelector = ({
defaultMessage: 'Select breakdown field',
}
)}
optionMatcher={comboBoxFieldOptionMatcher}
options={fieldOptions}
onChange={onChange}
/>

View file

@ -35,6 +35,7 @@ export interface ToolbarSelectorProps {
options: SelectableEntry[];
searchable: boolean;
onChange?: (chosenOption: SelectableEntry | undefined) => void;
optionMatcher?: EuiSelectableProps['optionMatcher'];
}
export const ToolbarSelector: React.FC<ToolbarSelectorProps> = ({
@ -45,6 +46,7 @@ export const ToolbarSelector: React.FC<ToolbarSelectorProps> = ({
options,
searchable,
onChange,
optionMatcher,
}) => {
const { euiTheme } = useEuiTheme();
const [isOpen, setIsOpen] = useState<boolean>(false);
@ -144,6 +146,7 @@ export const ToolbarSelector: React.FC<ToolbarSelectorProps> = ({
data-test-subj={`${dataTestSubj}Selectable`}
options={options}
onChange={onSelectionChange}
optionMatcher={optionMatcher}
listProps={{
truncationProps: { truncation: 'middle' },
isVirtualized: searchable,

View file

@ -8,6 +8,7 @@
import React, { useCallback, useContext, useRef } from 'react';
import { i18n } from '@kbn/i18n';
import { comboBoxFieldOptionMatcher } from '@kbn/field-utils';
import { FieldIcon } from '@kbn/react-field';
import { KBN_FIELD_TYPES } from '@kbn/field-types';
import type { DataView, DataViewField } from '@kbn/data-views-plugin/common';
@ -54,6 +55,7 @@ export function FieldInput({ field, dataView, onHandleField }: FieldInputProps)
}
return {
label,
name: dataViewField.name,
value: dataViewField.type as KBN_FIELD_TYPES,
prepend: <FieldIcon type={dataViewField.type} fill="none" className="eui-alignMiddle" />,
};
@ -82,6 +84,7 @@ export function FieldInput({ field, dataView, onHandleField }: FieldInputProps)
inputRef={(ref) => {
inputRef.current = ref;
}}
optionMatcher={comboBoxFieldOptionMatcher}
options={euiOptions}
selectedOptions={selectedEuiOptions}
onChange={onComboBoxChange}

View file

@ -47,7 +47,8 @@
"@kbn/react-kibana-context-render",
"@kbn/data-view-utils",
"@kbn/esql-utils",
"@kbn/react-kibana-mount"
"@kbn/react-kibana-mount",
"@kbn/field-utils"
],
"exclude": [
"target/**/*",

View file

@ -10,6 +10,7 @@ import { calculateWidthFromEntries } from '@kbn/calculate-width-from-char-count'
import { EuiComboBox, EuiComboBoxProps, EuiComboBoxOptionOption } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FieldIcon } from '@kbn/react-field';
import { comboBoxFieldOptionMatcher } from '@kbn/field-utils';
import {
FIELD_ORIGIN,
MIDDLE_TRUNCATION_PROPS,
@ -35,6 +36,7 @@ function groupFieldsByOrigin(fields: StyleField[]) {
.map((field) => {
return {
value: field,
name: field.name,
label: field.label,
disabled: field.isUnsupported,
title: field.unsupportedMsg,
@ -132,6 +134,7 @@ export function FieldSelect({ fields, selectedFieldName, onChange, styleName, ..
singleSelection={SINGLE_SELECTION_AS_TEXT_PROPS}
truncationProps={MIDDLE_TRUNCATION_PROPS}
inputPopoverProps={{ panelMinWidth }}
optionMatcher={comboBoxFieldOptionMatcher}
{...rest}
/>
);

View file

@ -12,6 +12,7 @@ import { EuiComboBox, EuiComboBoxProps, EuiComboBoxOptionOption, EuiToolTip } fr
import { FieldIcon } from '@kbn/react-field';
import { DataViewField } from '@kbn/data-views-plugin/public';
import { calculateWidthFromEntries } from '@kbn/calculate-width-from-char-count';
import { comboBoxFieldOptionMatcher } from '@kbn/field-utils';
import { MIDDLE_TRUNCATION_PROPS } from '../../common/constants';
function fieldsToOptions(
@ -32,6 +33,7 @@ function fieldsToOptions(
value: field,
label: field.displayName ? field.displayName : field.name,
prepend: FieldTypeIcon,
name: field.name,
};
if (isFieldDisabled && isFieldDisabled(field)) {
option.disabled = true;
@ -119,6 +121,7 @@ export function SingleFieldSelect({
isDisabled={!fields || fields.length === 0}
truncationProps={MIDDLE_TRUNCATION_PROPS}
inputPopoverProps={{ panelMinWidth }}
optionMatcher={comboBoxFieldOptionMatcher}
{...rest}
/>
);

View file

@ -34,6 +34,7 @@ exports[`Should remove selected fields from selectable 1`] = `
Array [
Object {
"label": "@timestamp-label",
"name": "@timestamp",
"prepend": <FieldIcon
className="eui-alignMiddle"
fill="none"
@ -109,6 +110,7 @@ exports[`Should render 1`] = `
Array [
Object {
"label": "@timestamp-label",
"name": "@timestamp",
"prepend": <FieldIcon
className="eui-alignMiddle"
fill="none"
@ -118,6 +120,7 @@ exports[`Should render 1`] = `
},
Object {
"label": "custom label for prop1",
"name": "prop1",
"prepend": <FieldIcon
className="eui-alignMiddle"
fill="none"
@ -127,6 +130,7 @@ exports[`Should render 1`] = `
},
Object {
"label": "prop2-label",
"name": "prop2",
"prepend": <FieldIcon
className="eui-alignMiddle"
fill="none"

View file

@ -22,6 +22,7 @@ import {
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { FieldIcon } from '@kbn/react-field';
import { comboBoxFieldOptionMatcher } from '@kbn/field-utils';
export type FieldProps = {
label: string;
@ -51,6 +52,7 @@ function getOptions(fields: FieldProps[], selectedFields: FieldProps[]): EuiSele
.map((field) => {
return {
value: field.name,
name: field.name,
prepend:
'type' in field ? (
<FieldIcon className="eui-alignMiddle" type={field.type} fill="none" />
@ -161,6 +163,7 @@ export class AddTooltipFieldPopover extends Component<Props, State> {
searchProps={{ compressed: true }}
options={this.state.options}
onChange={this._onSelect}
optionMatcher={comboBoxFieldOptionMatcher}
>
{(list, search) => (
<div style={{ width: '300px' }}>

View file

@ -89,7 +89,8 @@
"@kbn/esql-utils",
"@kbn/apm-data-view",
"@kbn/shared-ux-utility",
"@kbn/react-kibana-context-render"
"@kbn/react-kibana-context-render",
"@kbn/field-utils"
],
"exclude": [
"target/**/*",