mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Unified field list] debounce search (#187143)
## Summary Updates to unified field list on typing are debounced - this way we don't get so many updates when typing in the search input. Flaky test runner: https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6424 ## Performance comparison Test: typing the string: activem for metricbeat data (~6000 fields) before (costly update on every keystroke): <img width="669" alt="Screenshot 2024-06-28 at 17 28 38" src="7075f7bc
-2d90-4177-acac-69ac101b2ef1"> after (only one costly update when user stops typing): <img width="269" alt="Screenshot 2024-06-28 at 17 24 43" src="8c0ce4a3
-7c1a-428b-a482-f6b4d87911e0">
This commit is contained in:
parent
4504088b9a
commit
e6f17e7c06
38 changed files with 164 additions and 80 deletions
|
@ -6,10 +6,10 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import React, { useState } from 'react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { FieldNameSearch, type FieldNameSearchProps } from './field_name_search';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
|
||||
describe('UnifiedFieldList <FieldNameSearch />', () => {
|
||||
it('should render correctly', async () => {
|
||||
|
@ -19,35 +19,34 @@ describe('UnifiedFieldList <FieldNameSearch />', () => {
|
|||
screenReaderDescriptionId: 'htmlId',
|
||||
'data-test-subj': 'searchInput',
|
||||
};
|
||||
const wrapper = mountWithIntl(<FieldNameSearch {...props} />);
|
||||
expect(wrapper.find('input').prop('aria-describedby')).toBe('htmlId');
|
||||
|
||||
act(() => {
|
||||
wrapper.find('input').simulate('change', {
|
||||
target: { value: 'hi' },
|
||||
});
|
||||
});
|
||||
|
||||
expect(props.onChange).toBeCalledWith('hi');
|
||||
render(<FieldNameSearch {...props} />);
|
||||
const input = screen.getByRole('searchbox', { name: 'Search field names' });
|
||||
expect(input).toHaveAttribute('aria-describedby', 'htmlId');
|
||||
userEvent.type(input, 'hey');
|
||||
await waitFor(() => expect(props.onChange).toHaveBeenCalledWith('hey'), { timeout: 256 });
|
||||
expect(props.onChange).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should update correctly', async () => {
|
||||
const props: FieldNameSearchProps = {
|
||||
nameFilter: 'this',
|
||||
onChange: jest.fn(),
|
||||
screenReaderDescriptionId: 'htmlId',
|
||||
'data-test-subj': 'searchInput',
|
||||
it('should accept the updates from the top', async () => {
|
||||
const FieldNameSearchWithWrapper = ({ defaultNameFilter = '' }) => {
|
||||
const [nameFilter, setNameFilter] = useState(defaultNameFilter);
|
||||
const props: FieldNameSearchProps = {
|
||||
nameFilter,
|
||||
onChange: jest.fn(),
|
||||
screenReaderDescriptionId: 'htmlId',
|
||||
'data-test-subj': 'searchInput',
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => setNameFilter('that')}>update nameFilter</button>
|
||||
<FieldNameSearch {...props} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const wrapper = mountWithIntl(<FieldNameSearch {...props} />);
|
||||
|
||||
expect(wrapper.find('input').prop('value')).toBe('this');
|
||||
|
||||
wrapper.setProps({
|
||||
nameFilter: 'that',
|
||||
});
|
||||
|
||||
expect(wrapper.find('input').prop('value')).toBe('that');
|
||||
|
||||
expect(props.onChange).not.toBeCalled();
|
||||
render(<FieldNameSearchWithWrapper defaultNameFilter="this" />);
|
||||
expect(screen.getByRole('searchbox')).toHaveValue('this');
|
||||
const button = screen.getByRole('button', { name: 'update nameFilter' });
|
||||
userEvent.click(button);
|
||||
expect(screen.getByRole('searchbox')).toHaveValue('that');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFieldSearch, type EuiFieldSearchProps } from '@elastic/eui';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
|
||||
/**
|
||||
* Props for FieldNameSearch component
|
||||
|
@ -45,15 +46,22 @@ export const FieldNameSearch: React.FC<FieldNameSearchProps> = ({
|
|||
description: 'Search the list of fields in the data view for the provided text',
|
||||
});
|
||||
|
||||
const { inputValue, handleInputChange } = useDebouncedValue({
|
||||
onChange,
|
||||
value: nameFilter,
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiFieldSearch
|
||||
aria-describedby={screenReaderDescriptionId}
|
||||
aria-label={searchPlaceholder}
|
||||
data-test-subj={`${dataTestSubject}FieldSearch`}
|
||||
fullWidth
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
onChange={(e) => {
|
||||
handleInputChange(e.target.value);
|
||||
}}
|
||||
placeholder={searchPlaceholder}
|
||||
value={nameFilter}
|
||||
value={inputValue}
|
||||
append={append}
|
||||
compressed={compressed}
|
||||
/>
|
||||
|
|
|
@ -22,6 +22,15 @@ import { FieldsAccordion } from './fields_accordion';
|
|||
import { NoFieldsCallout } from './no_fields_callout';
|
||||
import { useGroupedFields, type GroupedFieldsParams } from '../../hooks/use_grouped_fields';
|
||||
|
||||
jest.mock('lodash', () => {
|
||||
const original = jest.requireActual('lodash');
|
||||
|
||||
return {
|
||||
...original,
|
||||
debounce: (fn: unknown) => fn,
|
||||
};
|
||||
});
|
||||
|
||||
describe('UnifiedFieldList FieldListGrouped + useGroupedFields()', () => {
|
||||
let defaultProps: FieldListGroupedProps<DataViewField>;
|
||||
let mockedServices: GroupedFieldsParams<DataViewField>['services'];
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiFieldText, EuiFieldTextProps } from '@elastic/eui';
|
||||
import { useDebouncedValue } from './debounced_value';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
|
||||
type Props = {
|
||||
value: string;
|
||||
|
|
|
@ -12,8 +12,6 @@ export * from './name_input';
|
|||
|
||||
export * from './debounced_input';
|
||||
|
||||
export * from './debounced_value';
|
||||
|
||||
export * from './color_picker';
|
||||
|
||||
export * from './icon_select';
|
||||
|
|
|
@ -21,7 +21,7 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
|||
import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
|
||||
import type { NotificationsStart } from '@kbn/core-notifications-browser';
|
||||
import type { DocLinksStart } from '@kbn/core-doc-links-browser';
|
||||
import { useDebouncedValue } from '../debounced_value';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
|
||||
export interface QueryInputServices {
|
||||
http: HttpStart;
|
||||
|
|
|
@ -10,7 +10,6 @@ export {
|
|||
FieldPicker,
|
||||
NameInput,
|
||||
DebouncedInput,
|
||||
useDebouncedValue,
|
||||
ColorPicker,
|
||||
IconSelect,
|
||||
IconSelectSetting,
|
||||
|
|
|
@ -9,3 +9,4 @@
|
|||
export { getTimeZone } from './src/get_timezone';
|
||||
export { getLensAttributesFromSuggestion } from './src/get_lens_attributes';
|
||||
export { TooltipWrapper } from './src/tooltip_wrapper';
|
||||
export { useDebouncedValue } from './src/debounced_value';
|
||||
|
|
|
@ -9,16 +9,15 @@
|
|||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { useDebouncedValue } from './debounced_value';
|
||||
|
||||
jest.mock('lodash', () => {
|
||||
const original = jest.requireActual('lodash');
|
||||
|
||||
return {
|
||||
...original,
|
||||
debounce: (fn: unknown) => fn,
|
||||
};
|
||||
});
|
||||
|
||||
describe('useDebouncedValue', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should update upstream value changes', () => {
|
||||
const onChangeMock = jest.fn();
|
||||
const { result } = renderHook(() => useDebouncedValue({ value: 'a', onChange: onChangeMock }));
|
||||
|
@ -26,6 +25,8 @@ describe('useDebouncedValue', () => {
|
|||
act(() => {
|
||||
result.current.handleInputChange('b');
|
||||
});
|
||||
expect(onChangeMock).not.toHaveBeenCalled();
|
||||
jest.advanceTimersByTime(256);
|
||||
|
||||
expect(onChangeMock).toHaveBeenCalledWith('b');
|
||||
});
|
||||
|
@ -37,7 +38,8 @@ describe('useDebouncedValue', () => {
|
|||
act(() => {
|
||||
result.current.handleInputChange('');
|
||||
});
|
||||
|
||||
expect(onChangeMock).not.toHaveBeenCalled();
|
||||
jest.advanceTimersByTime(256);
|
||||
expect(onChangeMock).toHaveBeenCalledWith('a');
|
||||
});
|
||||
|
||||
|
@ -50,7 +52,23 @@ describe('useDebouncedValue', () => {
|
|||
act(() => {
|
||||
result.current.handleInputChange('');
|
||||
});
|
||||
|
||||
expect(onChangeMock).not.toHaveBeenCalled();
|
||||
jest.advanceTimersByTime(256);
|
||||
expect(onChangeMock).toHaveBeenCalledWith('');
|
||||
});
|
||||
it('custom wait time is respected', () => {
|
||||
const onChangeMock = jest.fn();
|
||||
const { result } = renderHook(() =>
|
||||
useDebouncedValue({ value: 'a', onChange: onChangeMock }, { wait: 500 })
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.handleInputChange('b');
|
||||
});
|
||||
expect(onChangeMock).not.toHaveBeenCalled();
|
||||
jest.advanceTimersByTime(256);
|
||||
expect(onChangeMock).not.toHaveBeenCalled();
|
||||
jest.advanceTimersByTime(244); // sums to 500
|
||||
expect(onChangeMock).toHaveBeenCalledWith('b');
|
||||
});
|
||||
});
|
|
@ -9,13 +9,13 @@
|
|||
import { useState, useMemo, useEffect, useRef } from 'react';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
const DEFAULT_TIMEOUT = 256;
|
||||
/**
|
||||
* Debounces value changes and updates inputValue on root state changes if no debounced changes
|
||||
* are in flight because the user is currently modifying the value.
|
||||
*
|
||||
* * allowFalsyValue: update upstream with all falsy values but null or undefined
|
||||
*
|
||||
* When testing this function mock the "debounce" function in lodash (see this module test for an example)
|
||||
* * wait: debounce timeout
|
||||
*/
|
||||
|
||||
export const useDebouncedValue = <T>(
|
||||
|
@ -28,7 +28,9 @@ export const useDebouncedValue = <T>(
|
|||
value: T;
|
||||
defaultValue?: T;
|
||||
},
|
||||
{ allowFalsyValue }: { allowFalsyValue?: boolean } = {}
|
||||
{ allowFalsyValue, wait = DEFAULT_TIMEOUT }: { allowFalsyValue?: boolean; wait?: number } = {
|
||||
wait: DEFAULT_TIMEOUT,
|
||||
}
|
||||
) => {
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
const unflushedChanges = useRef(false);
|
||||
|
@ -45,8 +47,8 @@ export const useDebouncedValue = <T>(
|
|||
// do not reset unflushed flag right away, wait a bit for upstream to pick it up
|
||||
flushChangesTimeout.current = setTimeout(() => {
|
||||
unflushedChanges.current = false;
|
||||
}, 256);
|
||||
}, 256);
|
||||
}, wait);
|
||||
}, wait);
|
||||
return (val: T) => {
|
||||
if (flushChangesTimeout.current) {
|
||||
clearTimeout(flushChangesTimeout.current);
|
||||
|
@ -54,7 +56,7 @@ export const useDebouncedValue = <T>(
|
|||
unflushedChanges.current = true;
|
||||
callback(val);
|
||||
};
|
||||
}, [onChange]);
|
||||
}, [onChange, wait]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!unflushedChanges.current && value !== inputValue) {
|
|
@ -66,6 +66,15 @@ jest.mock('../../../../customizations', () => ({
|
|||
}),
|
||||
}));
|
||||
|
||||
jest.mock('lodash', () => {
|
||||
const original = jest.requireActual('lodash');
|
||||
|
||||
return {
|
||||
...original,
|
||||
debounce: (fn: unknown) => fn,
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('@kbn/unified-field-list/src/services/field_stats', () => ({
|
||||
loadFieldStats: jest.fn().mockResolvedValue({
|
||||
totalDocuments: 1624,
|
||||
|
|
|
@ -230,7 +230,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
describe('add and remove columns', async function () {
|
||||
const extraColumns = ['phpmemory', 'ip'];
|
||||
|
||||
const expectedFieldLength: Record<string, number> = {
|
||||
phpmemory: 1,
|
||||
ip: 4,
|
||||
};
|
||||
afterEach(async function () {
|
||||
for (const column of extraColumns) {
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemRemove(column);
|
||||
|
@ -242,6 +245,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
for (const column of extraColumns) {
|
||||
await PageObjects.unifiedFieldList.clearFieldSearchInput();
|
||||
await PageObjects.unifiedFieldList.findFieldByName(column);
|
||||
await PageObjects.unifiedFieldList.waitUntilFieldlistHasCountOfFields(
|
||||
expectedFieldLength[column]
|
||||
);
|
||||
await retry.waitFor('field to appear', async function () {
|
||||
return await testSubjects.exists(`field-${column}`);
|
||||
});
|
||||
|
@ -258,9 +264,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
for (const column of extraColumns) {
|
||||
await PageObjects.unifiedFieldList.clearFieldSearchInput();
|
||||
await PageObjects.unifiedFieldList.findFieldByName(column);
|
||||
await retry.waitFor('field to appear', async function () {
|
||||
return await testSubjects.exists(`field-${column}`);
|
||||
});
|
||||
await PageObjects.unifiedFieldList.waitUntilFieldlistHasCountOfFields(
|
||||
expectedFieldLength[column]
|
||||
);
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd(column);
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
}
|
||||
|
|
|
@ -220,6 +220,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
describe('add and remove columns', function () {
|
||||
const extraColumns = ['phpmemory', 'ip'];
|
||||
const expectedFieldLength: Record<string, number> = {
|
||||
phpmemory: 1,
|
||||
ip: 4,
|
||||
};
|
||||
|
||||
afterEach(async function () {
|
||||
for (const column of extraColumns) {
|
||||
|
@ -232,6 +236,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
for (const column of extraColumns) {
|
||||
await PageObjects.unifiedFieldList.clearFieldSearchInput();
|
||||
await PageObjects.unifiedFieldList.findFieldByName(column);
|
||||
await PageObjects.unifiedFieldList.waitUntilFieldlistHasCountOfFields(
|
||||
expectedFieldLength[column]
|
||||
);
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd(column);
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
// test the header now
|
||||
|
@ -244,6 +251,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
for (const column of extraColumns) {
|
||||
await PageObjects.unifiedFieldList.clearFieldSearchInput();
|
||||
await PageObjects.unifiedFieldList.findFieldByName(column);
|
||||
await PageObjects.unifiedFieldList.waitUntilFieldlistHasCountOfFields(
|
||||
expectedFieldLength[column]
|
||||
);
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd(column);
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
}
|
||||
|
|
|
@ -53,6 +53,15 @@ export class UnifiedFieldListPageObject extends FtrService {
|
|||
});
|
||||
}
|
||||
|
||||
public async waitUntilFieldlistHasCountOfFields(count: number) {
|
||||
await this.retry.waitFor('wait until fieldlist has updated number of fields', async () => {
|
||||
return (
|
||||
(await this.find.allByCssSelector('#fieldListGroupedAvailableFields .kbnFieldButton'))
|
||||
.length === count
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public async doesSidebarShowFields() {
|
||||
return await this.testSubjects.exists('fieldListGroupedFieldGroups');
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
EuiLink,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { useDebouncedValue } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import {
|
||||
DEFAULT_DURATION_INPUT_FORMAT,
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
ExpressionAstExpressionBuilder,
|
||||
ExpressionAstFunctionBuilder,
|
||||
} from '@kbn/expressions-plugin/public';
|
||||
import { useDebouncedValue } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import { PERCENTILE_ID, PERCENTILE_NAME } from '@kbn/lens-formula-docs';
|
||||
import { OperationDefinition } from '.';
|
||||
import {
|
||||
|
|
|
@ -10,7 +10,7 @@ import React, { useCallback } from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { AggFunctionsMapping } from '@kbn/data-plugin/public';
|
||||
import { buildExpressionFunction } from '@kbn/expressions-plugin/public';
|
||||
import { useDebouncedValue } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import { PERCENTILE_RANK_ID, PERCENTILE_RANK_NAME } from '@kbn/lens-formula-docs';
|
||||
import { OperationDefinition } from '.';
|
||||
import {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React, { useRef } from 'react';
|
||||
import { EuiFieldText, keys } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useDebouncedValue } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
|
||||
export const LabelInput = ({
|
||||
value,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFieldNumber, EuiFormRow } from '@elastic/eui';
|
||||
import { useDebouncedValue } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import { OperationDefinition } from '.';
|
||||
import {
|
||||
ReferenceBasedIndexPatternColumn,
|
||||
|
|
|
@ -9,12 +9,12 @@ import React, { useCallback, useMemo } from 'react';
|
|||
import { htmlIdGenerator } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
useDebouncedValue,
|
||||
DragDropBuckets,
|
||||
FieldsBucketContainer,
|
||||
NewBucketButton,
|
||||
DraggableBucketContainer,
|
||||
} from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import { IndexPattern } from '../../../../../types';
|
||||
import { FieldSelect } from '../../../dimension_panel/field_select';
|
||||
import type { TermsIndexPatternColumn } from './types';
|
||||
|
|
|
@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { uniq } from 'lodash';
|
||||
import { EuiComboBox, EuiFormRow, EuiSpacer, EuiSwitch, EuiFieldText, EuiText } from '@elastic/eui';
|
||||
import type { DatatableRow } from '@kbn/expressions-plugin/common';
|
||||
import { useDebouncedValue } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
|
||||
export interface IncludeExcludeOptions {
|
||||
label: string;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React, { useCallback, useMemo } from 'react';
|
||||
import { EuiSpacer, EuiFormRow } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useDebouncedValue } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import type { AxesSettingsConfig } from '../../../visualizations/xy/types';
|
||||
import { type LabelMode, VisLabel } from '../..';
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFieldNumber, EuiFormRow } from '@elastic/eui';
|
||||
import { useDebouncedValue } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
|
||||
export const DEFAULT_FLOATING_COLUMNS = 1;
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { Position, VerticalAlignment, HorizontalAlignment, LegendValue } from '@elastic/charts';
|
||||
import { LegendSize } from '@kbn/visualizations-plugin/public';
|
||||
import { useDebouncedValue } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import { XYLegendValue } from '@kbn/visualizations-plugin/common/constants';
|
||||
import { ToolbarPopover, type ToolbarPopoverProps } from '../toolbar_popover';
|
||||
import { LegendLocationSettings } from './location/legend_location_settings';
|
||||
|
|
|
@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { css } from '@emotion/react';
|
||||
import { EuiFormRow, EuiFieldText, EuiText, useEuiTheme, EuiComboBox } from '@elastic/eui';
|
||||
import { PaletteRegistry } from '@kbn/coloring';
|
||||
import { useDebouncedValue } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import type { VisualizationDimensionEditorProps } from '../../../types';
|
||||
import type { DatatableVisualizationState } from '../visualization';
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import React, { memo, useState } from 'react';
|
|||
import { EuiFlexGroup, EuiFlexItem, EuiFormRow } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { GaugeLabelMajorMode } from '@kbn/expression-gauge-plugin/common';
|
||||
import { useDebouncedValue } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import type { VisualizationToolbarProps } from '../../../types';
|
||||
import { ToolbarPopover, VisLabel } from '../../../shared_components';
|
||||
import './gauge_config_panel.scss';
|
||||
|
|
|
@ -28,7 +28,8 @@ import {
|
|||
import { getDataBoundsForPalette } from '@kbn/expression-metric-vis-plugin/public';
|
||||
import { getColumnByAccessor } from '@kbn/visualizations-plugin/common/utils';
|
||||
import { css } from '@emotion/react';
|
||||
import { DebouncedInput, useDebouncedValue, IconSelect } from '@kbn/visualization-ui-components';
|
||||
import { DebouncedInput, IconSelect } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import { isNumericFieldForDatatable } from '../../../common/expressions/datatable/utils';
|
||||
import { applyPaletteParams, PalettePanelContainer } from '../../shared_components';
|
||||
import type { VisualizationDimensionEditorProps } from '../../types';
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFlexGroup, EuiFormRow, EuiFieldText } from '@elastic/eui';
|
||||
import { useDebouncedValue } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import { VisualizationToolbarProps } from '../../types';
|
||||
import { ToolbarPopover } from '../../shared_components';
|
||||
import { MetricVisualizationState } from './visualization';
|
||||
|
|
|
@ -17,7 +17,8 @@ import {
|
|||
AVAILABLE_PALETTES,
|
||||
getColorsFromMapping,
|
||||
} from '@kbn/coloring';
|
||||
import { ColorPicker, useDebouncedValue } from '@kbn/visualization-ui-components';
|
||||
import { ColorPicker } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import { EuiFormRow, EuiFlexGroup, EuiFlexItem, EuiSwitch, EuiText, EuiBadge } from '@elastic/eui';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { getColorCategories } from '@kbn/chart-expressions-common';
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { LegendValue, Position } from '@elastic/charts';
|
||||
import { LegendSize } from '@kbn/visualizations-plugin/public';
|
||||
import { useDebouncedValue } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import { type PartitionLegendValue } from '@kbn/visualizations-plugin/common/constants';
|
||||
import { DEFAULT_PERCENT_DECIMALS } from './constants';
|
||||
import { PartitionChartsMeta } from './partition_charts_meta';
|
||||
|
|
|
@ -19,7 +19,7 @@ import {
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSwitch, EuiFormRow, EuiText, EuiBadge } from '@elastic/eui';
|
||||
import { useState, MutableRefObject, useCallback } from 'react';
|
||||
import { useDebouncedValue } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import { getColorCategories } from '@kbn/chart-expressions-common';
|
||||
import type { TagcloudState } from './types';
|
||||
import { PalettePanelContainer, PalettePicker } from '../../shared_components';
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
EuiIconAxisRight,
|
||||
EuiIconAxisTop,
|
||||
} from '@kbn/chart-icons';
|
||||
import { useDebouncedValue } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import { isHorizontalChart } from '../state_helpers';
|
||||
import {
|
||||
ToolbarPopover,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useDebouncedValue } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import { ColorPicker } from '@kbn/visualization-ui-components';
|
||||
|
||||
import {
|
||||
|
|
|
@ -11,12 +11,12 @@ import { EuiButtonGroup, EuiFormRow } from '@elastic/eui';
|
|||
import type { PaletteRegistry } from '@kbn/coloring';
|
||||
import { FillStyle } from '@kbn/expression-xy-plugin/common';
|
||||
import {
|
||||
useDebouncedValue,
|
||||
IconSelectSetting,
|
||||
ColorPicker,
|
||||
LineStyleSettings,
|
||||
TextDecorationSetting,
|
||||
} from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
import type { VisualizationDimensionEditorProps } from '../../../../types';
|
||||
import { State, XYState, XYReferenceLineLayerConfig, YConfig } from '../../types';
|
||||
import { FormatFactory } from '../../../../../common/types';
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiFormRow, EuiRange } from '@elastic/eui';
|
||||
import { useDebouncedValue } from '@kbn/visualization-ui-components';
|
||||
import { useDebouncedValue } from '@kbn/visualization-utils';
|
||||
|
||||
export interface FillOpacityOptionProps {
|
||||
/**
|
||||
|
|
|
@ -125,6 +125,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('should show stats for a numeric runtime field', async () => {
|
||||
await PageObjects.lens.searchField('runtime');
|
||||
await PageObjects.lens.waitForMissingField('Records');
|
||||
await PageObjects.lens.waitForField('runtime_number');
|
||||
const [fieldId] = await PageObjects.lens.findFieldIdsByType('number');
|
||||
await log.debug(`Opening field stats for ${fieldId}`);
|
||||
|
|
|
@ -319,6 +319,10 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
|
|||
await testSubjects.existOrFail(`lnsFieldListPanelField-${field}`);
|
||||
},
|
||||
|
||||
async waitForMissingField(field: string) {
|
||||
await testSubjects.missingOrFail(`lnsFieldListPanelField-${field}`);
|
||||
},
|
||||
|
||||
async waitForMissingDataViewWarning() {
|
||||
await retry.try(async () => {
|
||||
await testSubjects.existOrFail(`missing-refs-failure`);
|
||||
|
|
|
@ -221,7 +221,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
describe('add and remove columns', function () {
|
||||
const extraColumns = ['phpmemory', 'ip'];
|
||||
|
||||
const expectedFieldLength: Record<string, number> = {
|
||||
phpmemory: 1,
|
||||
ip: 4,
|
||||
};
|
||||
afterEach(async function () {
|
||||
for (const column of extraColumns) {
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemRemove(column);
|
||||
|
@ -233,6 +236,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
for (const column of extraColumns) {
|
||||
await PageObjects.unifiedFieldList.clearFieldSearchInput();
|
||||
await PageObjects.unifiedFieldList.findFieldByName(column);
|
||||
await PageObjects.unifiedFieldList.waitUntilFieldlistHasCountOfFields(
|
||||
expectedFieldLength[column]
|
||||
);
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd(column);
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
// test the header now
|
||||
|
@ -245,6 +251,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
for (const column of extraColumns) {
|
||||
await PageObjects.unifiedFieldList.clearFieldSearchInput();
|
||||
await PageObjects.unifiedFieldList.findFieldByName(column);
|
||||
await PageObjects.unifiedFieldList.waitUntilFieldlistHasCountOfFields(
|
||||
expectedFieldLength[column]
|
||||
);
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd(column);
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue