mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[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:
parent
915f0d2475
commit
43db7a6e55
13 changed files with 45 additions and 3 deletions
|
@ -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';
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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] : []}
|
||||
|
|
|
@ -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', {
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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/**/*",
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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' }}>
|
||||
|
|
|
@ -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/**/*",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue