[User Experience App] Fixed searching filter values (#105582)

This commit is contained in:
Shahzad 2021-07-15 15:42:00 +02:00 committed by GitHub
parent 8ccf88e3e1
commit 84d4652b14
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 161 additions and 15 deletions

View file

@ -15,6 +15,7 @@ import {
EuiPopoverTitle,
EuiSelectable,
EuiSelectableOption,
EuiLoadingSpinner,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import styled from 'styled-components';
@ -34,7 +35,13 @@ const formatOptions = (
excludedValues?: string[],
showCount?: boolean
): EuiSelectableOption[] => {
return (values ?? []).map(({ label, count }) => ({
const uniqueValues: Record<string, number> = {};
values?.forEach(({ label, count }) => {
uniqueValues[label] = count;
});
return Object.entries(uniqueValues).map(([label, count]) => ({
label,
append: showCount ? (
<Counter>
@ -50,6 +57,7 @@ export function FieldValueSelection({
fullWidth,
label,
loading,
query,
setQuery,
button,
width,
@ -118,7 +126,7 @@ export function FieldValueSelection({
hasActiveFilters={numOfFilters > 0}
iconType="arrowDown"
numActiveFilters={numOfFilters}
numFilters={values.length}
numFilters={options.length}
onClick={onButtonClick}
>
{label}
@ -158,19 +166,28 @@ export function FieldValueSelection({
}),
compressed,
onInput: onValueChange,
'data-test-subj': 'suggestionInputField',
}}
listProps={{
onFocusBadge: false,
}}
options={options}
onChange={onChange}
isLoading={loading}
isLoading={loading && !query && options.length === 0}
allowExclusions={true}
>
{(list, search) => (
<div style={{ width: 240 }}>
<EuiPopoverTitle paddingSize="s">{search}</EuiPopoverTitle>
{list}
{loading && query && (
<EuiText className="eui-textCenter" color="subdued">
{i18n.translate('xpack.observability.fieldValueSelection.loading', {
defaultMessage: 'Loading',
})}{' '}
<EuiLoadingSpinner size="m" />
</EuiText>
)}
<EuiPopoverFooter paddingSize="s">
<EuiButton
fill

View file

@ -0,0 +1,131 @@
/*
* 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 { FieldValueSuggestions } from './index';
import { render, screen, fireEvent } from '@testing-library/react';
import * as searchHook from '../../../hooks/use_es_search';
import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common';
jest.setTimeout(30000);
describe('FieldValueSuggestions', () => {
jest.spyOn(HTMLElement.prototype, 'offsetHeight', 'get').mockReturnValue(1500);
jest.spyOn(HTMLElement.prototype, 'offsetWidth', 'get').mockReturnValue(1500);
function setupSearch(data: any) {
// @ts-ignore
jest.spyOn(searchHook, 'useEsSearch').mockReturnValue({
data: {
took: 17,
timed_out: false,
_shards: { total: 35, successful: 35, skipped: 31, failed: 0 },
hits: { total: { value: 15299, relation: 'eq' }, hits: [] },
aggregations: {
values: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: data,
},
},
},
loading: false,
});
}
it('renders a list', async () => {
setupSearch([
{ key: 'US', doc_count: 14132 },
{ key: 'Pak', doc_count: 200 },
{ key: 'Japan', doc_count: 100 },
]);
render(
<EuiThemeProvider darkMode={false}>
<FieldValueSuggestions
label="Service name"
sourceField={'service'}
onChange={() => {}}
selectedValue={[]}
filters={[]}
asCombobox={false}
/>
</EuiThemeProvider>
);
fireEvent.click(screen.getByText('Service name'));
expect(await screen.findByPlaceholderText('Filter Service name')).toBeInTheDocument();
expect(await screen.findByText('Apply')).toBeInTheDocument();
expect(await screen.findByText('US')).toBeInTheDocument();
expect(await screen.findByText('Pak')).toBeInTheDocument();
expect(await screen.findByText('Japan')).toBeInTheDocument();
expect(await screen.findByText('14132')).toBeInTheDocument();
expect(await screen.findByText('200')).toBeInTheDocument();
expect(await screen.findByText('100')).toBeInTheDocument();
setupSearch([{ key: 'US', doc_count: 14132 }]);
fireEvent.input(screen.getByTestId('suggestionInputField'), {
target: { value: 'u' },
});
expect(await screen.findByDisplayValue('u')).toBeInTheDocument();
});
it('calls oncChange when applied', async () => {
setupSearch([
{ key: 'US', doc_count: 14132 },
{ key: 'Pak', doc_count: 200 },
{ key: 'Japan', doc_count: 100 },
]);
const onChange = jest.fn();
const { rerender } = render(
<EuiThemeProvider darkMode={false}>
<FieldValueSuggestions
label="Service name"
sourceField={'service'}
onChange={onChange}
selectedValue={[]}
filters={[]}
asCombobox={false}
/>
</EuiThemeProvider>
);
fireEvent.click(screen.getByText('Service name'));
fireEvent.click(await screen.findByText('US'));
fireEvent.click(await screen.findByText('Apply'));
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange).toHaveBeenCalledWith(['US'], []);
rerender(
<EuiThemeProvider darkMode={false}>
<FieldValueSuggestions
label="Service name"
sourceField={'service'}
onChange={onChange}
selectedValue={['US']}
excludedValue={['Pak']}
filters={[]}
asCombobox={false}
/>
</EuiThemeProvider>
);
fireEvent.click(await screen.findByText('US'));
fireEvent.click(await screen.findByText('Pak'));
fireEvent.click(await screen.findByText('Apply'));
expect(onChange).toHaveBeenCalledTimes(2);
expect(onChange).toHaveBeenLastCalledWith([], ['US']);
});
});

View file

@ -6,8 +6,6 @@
*/
import React, { useState } from 'react';
import useDebounce from 'react-use/lib/useDebounce';
import { useValuesList } from '../../../hooks/use_values_list';
import { FieldValueSelection } from './field_value_selection';
import { FieldValueSuggestionsProps } from './types';
@ -35,7 +33,6 @@ export function FieldValueSuggestions({
onChange: onSelectionChange,
}: FieldValueSuggestionsProps) {
const [query, setQuery] = useState('');
const [debouncedValue, setDebouncedValue] = useState('');
const { values, loading } = useValuesList({
indexPatternTitle,
@ -46,14 +43,6 @@ export function FieldValueSuggestions({
keepHistory: true,
});
useDebounce(
() => {
setQuery(debouncedValue);
},
400,
[debouncedValue]
);
const SelectionComponent = asCombobox ? FieldValueCombobox : FieldValueSelection;
return (
@ -63,7 +52,8 @@ export function FieldValueSuggestions({
values={values}
label={label}
onChange={onSelectionChange}
setQuery={setDebouncedValue}
query={query}
setQuery={setQuery}
loading={loading}
selectedValue={selectedValue}
excludedValue={excludedValue}

View file

@ -39,6 +39,7 @@ export type FieldValueSelectionProps = CommonProps & {
loading?: boolean;
onChange: (val?: string[], excludedValue?: string[]) => void;
values?: ListItem[];
query?: string;
setQuery: Dispatch<SetStateAction<string>>;
};

View file

@ -58,6 +58,13 @@ export const useValuesList = ({
[query]
);
useEffect(() => {
if (!query) {
// in case query is cleared, we don't wait for debounce
setDebounceQuery(query);
}
}, [query]);
const { data, loading } = useEsSearch(
createEsParams({
index: indexPatternTitle!,