mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[7.x] [Discover] Refactor discover index pattern selector to L… (#53758)
This commit is contained in:
parent
1b949a49de
commit
90928977e6
8 changed files with 219 additions and 97 deletions
|
@ -1,11 +1,3 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DiscoverIndexPattern A single index pattern is just displayed 1`] = `
|
||||
<DiscoverIndexPatternTitle
|
||||
isChangeable={false}
|
||||
onChange={[Function]}
|
||||
title="test1"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`DiscoverIndexPattern Invalid props dont cause an exception: "" 1`] = `""`;
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiPopover,
|
||||
EuiPopoverTitle,
|
||||
EuiSelectable,
|
||||
EuiButtonEmptyProps,
|
||||
} from '@elastic/eui';
|
||||
import { EuiSelectableProps } from '@elastic/eui/src/components/selectable/selectable';
|
||||
import { IndexPatternRef } from './types';
|
||||
|
||||
export type ChangeIndexPatternTriggerProps = EuiButtonEmptyProps & {
|
||||
label: string;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
// TODO: refactor to shared component with ../../../../../../../../x-pack/legacy/plugins/lens/public/indexpattern_plugin/change_indexpattern
|
||||
|
||||
export function ChangeIndexPattern({
|
||||
indexPatternRefs,
|
||||
indexPatternId,
|
||||
onChangeIndexPattern,
|
||||
trigger,
|
||||
selectableProps,
|
||||
}: {
|
||||
trigger: ChangeIndexPatternTriggerProps;
|
||||
indexPatternRefs: IndexPatternRef[];
|
||||
onChangeIndexPattern: (newId: string) => void;
|
||||
indexPatternId?: string;
|
||||
selectableProps?: EuiSelectableProps;
|
||||
}) {
|
||||
const [isPopoverOpen, setPopoverIsOpen] = useState(false);
|
||||
|
||||
const createTrigger = function() {
|
||||
const { label, title, ...rest } = trigger;
|
||||
return (
|
||||
<EuiButtonEmpty
|
||||
className="eui-textTruncate"
|
||||
flush="left"
|
||||
color="text"
|
||||
iconSide="right"
|
||||
iconType="arrowDown"
|
||||
title={title}
|
||||
onClick={() => setPopoverIsOpen(!isPopoverOpen)}
|
||||
{...rest}
|
||||
>
|
||||
{label}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
button={createTrigger()}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={() => setPopoverIsOpen(false)}
|
||||
className="eui-textTruncate"
|
||||
anchorClassName="eui-textTruncate"
|
||||
display="block"
|
||||
panelPaddingSize="s"
|
||||
ownFocus
|
||||
>
|
||||
<div style={{ width: 320 }}>
|
||||
<EuiPopoverTitle>
|
||||
{i18n.translate('kbn.discover.fieldChooser.indexPattern.changeIndexPatternTitle', {
|
||||
defaultMessage: 'Change index pattern',
|
||||
})}
|
||||
</EuiPopoverTitle>
|
||||
<EuiSelectable
|
||||
data-test-subj="indexPattern-switcher"
|
||||
{...selectableProps}
|
||||
searchable
|
||||
singleSelection="always"
|
||||
options={indexPatternRefs.map(({ title, id }) => ({
|
||||
label: title,
|
||||
key: id,
|
||||
value: id,
|
||||
checked: id === indexPatternId ? 'on' : undefined,
|
||||
}))}
|
||||
onChange={choices => {
|
||||
const choice = (choices.find(({ checked }) => checked) as unknown) as {
|
||||
value: string;
|
||||
};
|
||||
onChangeIndexPattern(choice.value);
|
||||
setPopoverIsOpen(false);
|
||||
}}
|
||||
searchProps={{
|
||||
compressed: true,
|
||||
...(selectableProps ? selectableProps.searchProps : undefined),
|
||||
}}
|
||||
>
|
||||
{(list, search) => (
|
||||
<>
|
||||
{search}
|
||||
{list}
|
||||
</>
|
||||
)}
|
||||
</EuiSelectable>
|
||||
</div>
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
|
@ -17,27 +17,61 @@
|
|||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { shallowWithIntl as shallow } from 'test_utils/enzyme_helpers';
|
||||
|
||||
// @ts-ignore
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
import { ShallowWrapper } from 'enzyme';
|
||||
import { ChangeIndexPattern } from './change_indexpattern';
|
||||
import { SavedObject } from 'kibana/server';
|
||||
import { DiscoverIndexPattern, DiscoverIndexPatternProps } from './discover_index_pattern';
|
||||
import { comboBoxKeyCodes } from '@elastic/eui';
|
||||
import { DiscoverIndexPattern } from './discover_index_pattern';
|
||||
import { EuiSelectable, EuiSelectableList } from '@elastic/eui';
|
||||
|
||||
const indexPattern1 = {
|
||||
id: 'test1',
|
||||
attributes: {
|
||||
title: 'test1',
|
||||
title: 'test1 title',
|
||||
},
|
||||
} as SavedObject;
|
||||
|
||||
const indexPattern2 = {
|
||||
id: 'test2',
|
||||
attributes: {
|
||||
title: 'test2',
|
||||
title: 'test2 title',
|
||||
},
|
||||
} as SavedObject;
|
||||
|
||||
const defaultProps = {
|
||||
indexPatternList: [indexPattern1, indexPattern2],
|
||||
selectedIndexPattern: indexPattern1,
|
||||
setIndexPattern: jest.fn(async () => {}),
|
||||
};
|
||||
|
||||
function getIndexPatternPickerList(instance: ShallowWrapper) {
|
||||
return instance
|
||||
.find(ChangeIndexPattern)
|
||||
.first()
|
||||
.dive()
|
||||
.find(EuiSelectable);
|
||||
}
|
||||
|
||||
function getIndexPatternPickerOptions(instance: ShallowWrapper) {
|
||||
return getIndexPatternPickerList(instance)
|
||||
.dive()
|
||||
.find(EuiSelectableList)
|
||||
.prop('options');
|
||||
}
|
||||
|
||||
function selectIndexPatternPickerOption(instance: ShallowWrapper, selectedLabel: string) {
|
||||
const options: Array<{ label: string; checked?: 'on' | 'off' }> = getIndexPatternPickerOptions(
|
||||
instance
|
||||
).map((option: any) =>
|
||||
option.label === selectedLabel
|
||||
? { ...option, checked: 'on' }
|
||||
: { ...option, checked: undefined }
|
||||
);
|
||||
return getIndexPatternPickerList(instance).prop('onChange')!(options);
|
||||
}
|
||||
|
||||
describe('DiscoverIndexPattern', () => {
|
||||
test('Invalid props dont cause an exception', () => {
|
||||
const props = {
|
||||
|
@ -46,30 +80,21 @@ describe('DiscoverIndexPattern', () => {
|
|||
setIndexPattern: jest.fn(),
|
||||
} as any;
|
||||
|
||||
expect(shallowWithIntl(<DiscoverIndexPattern {...props} />)).toMatchSnapshot(`""`);
|
||||
expect(shallow(<DiscoverIndexPattern {...props} />)).toMatchSnapshot(`""`);
|
||||
});
|
||||
test('A single index pattern is just displayed', () => {
|
||||
const props = {
|
||||
indexPatternList: [indexPattern1],
|
||||
selectedIndexPattern: indexPattern1,
|
||||
setIndexPattern: jest.fn(),
|
||||
} as DiscoverIndexPatternProps;
|
||||
test('should list all index patterns', () => {
|
||||
const instance = shallow(<DiscoverIndexPattern {...defaultProps} />);
|
||||
|
||||
expect(shallowWithIntl(<DiscoverIndexPattern {...props} />)).toMatchSnapshot();
|
||||
expect(getIndexPatternPickerOptions(instance)!.map((option: any) => option.label)).toEqual([
|
||||
'test1 title',
|
||||
'test2 title',
|
||||
]);
|
||||
});
|
||||
|
||||
test('Multiple index patterns are selectable', () => {
|
||||
const props = {
|
||||
indexPatternList: [indexPattern1, indexPattern2],
|
||||
selectedIndexPattern: indexPattern2,
|
||||
setIndexPattern: jest.fn(),
|
||||
} as DiscoverIndexPatternProps;
|
||||
const component = mountWithIntl(<DiscoverIndexPattern {...props} />);
|
||||
findTestSubject(component, 'indexPattern-switch-link').simulate('click');
|
||||
test('should switch data panel to target index pattern', () => {
|
||||
const instance = shallow(<DiscoverIndexPattern {...defaultProps} />);
|
||||
|
||||
const searchInput = findTestSubject(component, 'comboBoxSearchInput');
|
||||
searchInput.simulate('change', { target: { value: 'test1' } });
|
||||
searchInput.simulate('keyDown', { keyCode: comboBoxKeyCodes.ENTER });
|
||||
expect(props.setIndexPattern).toBeCalledWith('test1');
|
||||
selectIndexPatternPickerOption(instance, 'test2 title');
|
||||
expect(defaultProps.setIndexPattern).toHaveBeenCalledWith('test2');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,10 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import { EuiComboBox } from '@elastic/eui';
|
||||
import { SavedObject } from 'kibana/server';
|
||||
import { DiscoverIndexPatternTitle } from './discover_index_pattern_title';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
|
||||
import { IndexPatternRef } from './types';
|
||||
import { ChangeIndexPattern } from './change_indexpattern';
|
||||
export interface DiscoverIndexPatternProps {
|
||||
/**
|
||||
* list of available index patterns, if length > 1, component offers a "change" link
|
||||
|
@ -48,60 +49,37 @@ export function DiscoverIndexPattern({
|
|||
// just in case, shouldn't happen
|
||||
return null;
|
||||
}
|
||||
const [selected, setSelected] = useState(selectedIndexPattern);
|
||||
const [showCombo, setShowCombo] = useState(false);
|
||||
const options = indexPatternList.map(entity => ({
|
||||
value: entity.id,
|
||||
label: entity.attributes!.title,
|
||||
const options: IndexPatternRef[] = indexPatternList.map(entity => ({
|
||||
id: entity.id,
|
||||
title: entity.attributes!.title,
|
||||
}));
|
||||
const selectedOptions = selected
|
||||
? [{ value: selected.id, label: selected.attributes.title }]
|
||||
: [];
|
||||
|
||||
const findIndexPattern = (id?: string) => indexPatternList.find(entity => entity.id === id);
|
||||
|
||||
if (!showCombo) {
|
||||
return (
|
||||
<DiscoverIndexPatternTitle
|
||||
isChangeable={indexPatternList.length > 1}
|
||||
onChange={() => setShowCombo(true)}
|
||||
title={selected.attributes ? selected.attributes.title : ''}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* catches a EuiComboBox related 'Can't perform a React state update on an unmounted component'
|
||||
* warning in console by delaying the hiding/removal of the EuiComboBox a bit
|
||||
*/
|
||||
function hideCombo() {
|
||||
setTimeout(() => setShowCombo(false), 50);
|
||||
}
|
||||
const [selected, setSelected] = useState({
|
||||
id: selectedIndexPattern.id,
|
||||
title: selectedIndexPattern.attributes!.title,
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiComboBox
|
||||
className="index-pattern-selection"
|
||||
data-test-subj="index-pattern-selection"
|
||||
fullWidth={true}
|
||||
isClearable={false}
|
||||
onBlur={() => hideCombo()}
|
||||
onChange={choices => {
|
||||
const newSelected = choices[0] && findIndexPattern(choices[0].value);
|
||||
if (newSelected) {
|
||||
setSelected(newSelected);
|
||||
setIndexPattern(newSelected.id);
|
||||
}
|
||||
hideCombo();
|
||||
}}
|
||||
inputRef={el => {
|
||||
// auto focus input element when combo box is displayed
|
||||
if (el) {
|
||||
el.focus();
|
||||
}
|
||||
}}
|
||||
options={options}
|
||||
selectedOptions={selectedOptions}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
/>
|
||||
<div className="indexPattern__container">
|
||||
<I18nProvider>
|
||||
<ChangeIndexPattern
|
||||
trigger={{
|
||||
label: selected.title,
|
||||
title: selected.title,
|
||||
'data-test-subj': 'indexPattern-switch-link',
|
||||
className: 'indexPattern__triggerButton',
|
||||
}}
|
||||
indexPatternId={selected.id}
|
||||
indexPatternRefs={options}
|
||||
onChangeIndexPattern={id => {
|
||||
const indexPattern = options.find(pattern => pattern.id === id);
|
||||
if (indexPattern) {
|
||||
setIndexPattern(id);
|
||||
setSelected(indexPattern);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</I18nProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -112,8 +112,16 @@
|
|||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.index-pattern-selection:not(.euiComboBox) {
|
||||
padding: $euiSizeS 0;
|
||||
}
|
||||
}
|
||||
|
||||
.indexPattern__container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: $euiSize * 3;
|
||||
margin-top: -$euiSizeS;
|
||||
}
|
||||
|
||||
.indexPattern__triggerButton {
|
||||
@include euiTitle('xs');
|
||||
line-height: $euiSizeXXL;
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ export function DiscoverPageProvider({ getService, getPageObjects }) {
|
|||
const globalNav = getService('globalNav');
|
||||
const config = getService('config');
|
||||
const defaultFindTimeout = config.get('timeouts.find');
|
||||
const comboBox = getService('comboBox');
|
||||
const elasticChart = getService('elasticChart');
|
||||
|
||||
class DiscoverPage {
|
||||
|
@ -279,7 +278,9 @@ export function DiscoverPageProvider({ getService, getPageObjects }) {
|
|||
|
||||
async selectIndexPattern(indexPattern) {
|
||||
await testSubjects.click('indexPattern-switch-link');
|
||||
await comboBox.set('index-pattern-selection', indexPattern);
|
||||
await find.clickByCssSelector(
|
||||
`[data-test-subj="indexPattern-switcher"] [title="${indexPattern}*"]`
|
||||
);
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
}
|
||||
|
||||
|
|
|
@ -2497,8 +2497,6 @@
|
|||
"kbn.context.olderDocumentsWarning": "アンカーよりも古いドキュメントは {docCount} 件しか見つかりませんでした。",
|
||||
"kbn.context.olderDocumentsWarningZero": "アンカーよりも古いドキュメントは見つかりませんでした。",
|
||||
"kbn.discover.fieldChooser.fieldFilterFacetButtonLabel": "フィルタリングされたフィールド",
|
||||
"kbn.discover.fieldChooser.indexPattern.changeLinkTooltip": "現在のインデックスパターンを変更",
|
||||
"kbn.discover.fieldChooser.indexPattern.changeLinkAriaLabel": "現在のインデックスパターンを変更",
|
||||
"kbn.discover.fieldChooser.searchPlaceHolder": "検索フィールド",
|
||||
"kbn.discover.histogram.partialData.bucketTooltipText": "選択された時間範囲にはこのバケット全体は含まれていませんが、一部データが含まれている可能性があります。",
|
||||
"kbn.doc.failedToLocateIndexPattern": "ID {indexPatternId} に一致するインデックスパターンがありません",
|
||||
|
|
|
@ -2498,8 +2498,6 @@
|
|||
"kbn.context.olderDocumentsWarning": "仅可以找到 {docCount} 个比定位标记旧的文档。",
|
||||
"kbn.context.olderDocumentsWarningZero": "找不到比定位标记旧的文档。",
|
||||
"kbn.discover.fieldChooser.fieldFilterFacetButtonLabel": "已筛选字段",
|
||||
"kbn.discover.fieldChooser.indexPattern.changeLinkTooltip": "更改当前索引模式",
|
||||
"kbn.discover.fieldChooser.indexPattern.changeLinkAriaLabel": "更改当前索引模式",
|
||||
"kbn.discover.fieldChooser.searchPlaceHolder": "搜索字段",
|
||||
"kbn.discover.histogram.partialData.bucketTooltipText": "选定的时间范围不包括此整个存储桶,其可能包含部分数据。",
|
||||
"kbn.doc.failedToLocateIndexPattern": "无索引模式匹配 ID {indexPatternId}",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue