[7.x] [Discover] Refactor discover index pattern selector to L… (#53758)

This commit is contained in:
Marta Bondyra 2019-12-23 15:29:36 +01:00 committed by GitHub
parent 1b949a49de
commit 90928977e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 219 additions and 97 deletions

View file

@ -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`] = `""`;

View file

@ -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>
);
}

View file

@ -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');
});
});

View file

@ -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>
);
}

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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} に一致するインデックスパターンがありません",

View file

@ -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}",