mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[DataView] UX improvements for index pattern input in data view flyout (#152138)
## Summary This PR adds some UX improvements to how an index pattern can be entered: - [x] Help text under the input was updated to mention that commas can be used - [x] Added in-product docs popover to Index pattern input - [x] Automatically show all sources again if a comma was entered - [x] It's possible to switch between all available sources and only matching ones via new tabs - [x] Index name will be fully highlighted if ES returns that it was an exact match for a wildcard - [x] Persist the selected "Rows per page" value in localStorage <img width="400" alt="Screenshot 2023-03-01 at 12 02 40" src="https://user-images.githubusercontent.com/1415710/222121556-b0288fdc-8095-4a66-b781-d3d389c7f54a.png"> <img width="400" alt="Screenshot 2023-03-01 at 12 02 46" src="https://user-images.githubusercontent.com/1415710/222121559-ede0ec65-e49f-4253-afaa-7c7980f714c8.png"> <img width="400" alt="Screenshot 2023-03-01 at 11 56 59" src="https://user-images.githubusercontent.com/1415710/222120704-a0b2c974-ca03-450a-9beb-6fe72b03c929.png"> ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
4e3cc20ca0
commit
2ceaa64744
14 changed files with 511 additions and 36 deletions
|
@ -13,6 +13,7 @@
|
|||
],
|
||||
"requiredBundles": [
|
||||
"kibanaReact",
|
||||
"kibanaUtils",
|
||||
"esUiShared"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { TitleDocsPopover } from './title_docs_popover';
|
||||
|
||||
describe('DataViewEditor TitleDocsPopover', () => {
|
||||
it('should render normally', async () => {
|
||||
const component = mountWithIntl(<TitleDocsPopover />);
|
||||
|
||||
expect(findTestSubject(component, 'indexPatternDocsButton').exists()).toBeTruthy();
|
||||
expect(findTestSubject(component, 'indexPatternDocsPopoverContent').exists()).toBeFalsy();
|
||||
|
||||
findTestSubject(component, 'indexPatternDocsButton').simulate('click');
|
||||
|
||||
await component.update();
|
||||
|
||||
expect(findTestSubject(component, 'indexPatternDocsPopoverContent').exists()).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiPanel,
|
||||
EuiPopover,
|
||||
EuiPopoverTitle,
|
||||
EuiText,
|
||||
EuiCode,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const TitleDocsPopover: React.FC = () => {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
|
||||
const helpButton = (
|
||||
<EuiButtonIcon
|
||||
onClick={() => setIsOpen((prev) => !prev)}
|
||||
iconType="documentation"
|
||||
data-test-subj="indexPatternDocsButton"
|
||||
aria-label={i18n.translate('indexPatternEditor.titleDocsPopover.ariaLabel', {
|
||||
defaultMessage: 'Index pattern examples',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
button={helpButton}
|
||||
isOpen={isOpen}
|
||||
display="inlineBlock"
|
||||
panelPaddingSize="none"
|
||||
closePopover={() => setIsOpen(false)}
|
||||
>
|
||||
<EuiPopoverTitle paddingSize="s">
|
||||
{i18n.translate('indexPatternEditor.titleDocsPopover.title', {
|
||||
defaultMessage: 'Index pattern',
|
||||
})}
|
||||
</EuiPopoverTitle>
|
||||
<EuiPanel
|
||||
className="eui-yScroll"
|
||||
css={css`
|
||||
max-height: 40vh;
|
||||
max-width: 500px;
|
||||
`}
|
||||
color="transparent"
|
||||
paddingSize="m"
|
||||
>
|
||||
<EuiText size="s" data-test-subj="indexPatternDocsPopoverContent">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="indexPatternEditor.titleDocsPopover.indexPatternDescription"
|
||||
defaultMessage="An index pattern is a string that you use to match one or more data streams, indices, or aliases."
|
||||
/>
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="indexPatternEditor.titleDocsPopover.useWildcardDescription"
|
||||
defaultMessage="Match multiple sources with a wildcard (*)."
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<EuiCode>filebeat-*</EuiCode>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="indexPatternEditor.titleDocsPopover.useCommasDescription"
|
||||
defaultMessage="Separate multiple single sources with a comma (,)."
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<EuiCode>filebeat-a,filebeat-b</EuiCode>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="indexPatternEditor.titleDocsPopover.useMinusDescription"
|
||||
defaultMessage="Exclude a source by preceding it with a minus sign (-)."
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<EuiCode>filebeat-*,-filebeat-c</EuiCode>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'indexPatternEditor.titleDocsPopover.dontUseSpecialCharactersDescription',
|
||||
{
|
||||
defaultMessage: 'Spaces and the characters /?"<>| are not allowed.',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</EuiText>
|
||||
</EuiPanel>
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
|
@ -22,6 +22,7 @@ import { canAppendWildcard } from '../../lib';
|
|||
import { schema } from '../form_schema';
|
||||
import { RollupIndicesCapsResponse, IndexPatternConfig, MatchedIndicesSet } from '../../types';
|
||||
import { matchedIndiciesDefault } from '../../data_view_editor_service';
|
||||
import { TitleDocsPopover } from './title_docs_popover';
|
||||
|
||||
interface TitleFieldProps {
|
||||
isRollup: boolean;
|
||||
|
@ -194,6 +195,8 @@ export const TitleField = ({
|
|||
isLoading={field.isValidating}
|
||||
fullWidth
|
||||
data-test-subj="createIndexPatternTitleInput"
|
||||
append={<TitleDocsPopover />}
|
||||
placeholder="example-*"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
|
|
|
@ -28,10 +28,6 @@ export const schema = {
|
|||
defaultMessage: 'Index pattern',
|
||||
}),
|
||||
defaultValue: '',
|
||||
helpText: i18n.translate('indexPatternEditor.validations.titleHelpText', {
|
||||
defaultMessage:
|
||||
'Enter an index pattern that matches one or more data sources. Use an asterisk (*) to match multiple characters. Spaces and the characters , /, ?, ", <, >, | are not allowed.',
|
||||
}),
|
||||
validations: [
|
||||
{
|
||||
validator: fieldValidators.emptyField(
|
||||
|
@ -84,7 +80,7 @@ export const schema = {
|
|||
},
|
||||
isAdHoc: {
|
||||
label: i18n.translate('indexPatternEditor.editor.form.IsAdHocLabel', {
|
||||
defaultMessage: 'Creeate AdHoc DataView',
|
||||
defaultMessage: 'Create AdHoc DataView',
|
||||
}),
|
||||
defaultValue: false,
|
||||
type: 'hidden',
|
||||
|
|
|
@ -201,6 +201,113 @@ exports[`IndicesList should change per page 1`] = `
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`IndicesList should highlight fully when an exact match 1`] = `
|
||||
<div>
|
||||
<EuiTable
|
||||
responsive={false}
|
||||
tableLayout="auto"
|
||||
>
|
||||
<EuiTableBody>
|
||||
<EuiTableRow
|
||||
key="0"
|
||||
>
|
||||
<EuiTableRowCell>
|
||||
<span>
|
||||
<strong>
|
||||
logs
|
||||
</strong>
|
||||
tash
|
||||
</span>
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell />
|
||||
</EuiTableRow>
|
||||
<EuiTableRow
|
||||
key="1"
|
||||
>
|
||||
<EuiTableRowCell>
|
||||
<strong>
|
||||
some_logs
|
||||
</strong>
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell />
|
||||
</EuiTableRow>
|
||||
</EuiTableBody>
|
||||
</EuiTable>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="spaceBetween"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiPopover
|
||||
anchorPosition="downCenter"
|
||||
button={
|
||||
<EuiButtonEmpty
|
||||
color="text"
|
||||
iconSide="right"
|
||||
iconType="arrowDown"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Rows per page: {perPage}"
|
||||
id="indexPatternEditor.pagingLabel"
|
||||
values={
|
||||
Object {
|
||||
"perPage": 10,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
}
|
||||
closePopover={[Function]}
|
||||
display="inline-block"
|
||||
hasArrow={true}
|
||||
id="customizablePagination"
|
||||
isOpen={false}
|
||||
ownFocus={true}
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
<EuiContextMenuPanel
|
||||
items={
|
||||
Array [
|
||||
<EuiContextMenuItem
|
||||
icon="empty"
|
||||
onClick={[Function]}
|
||||
>
|
||||
5
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
icon="empty"
|
||||
onClick={[Function]}
|
||||
>
|
||||
10
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
icon="empty"
|
||||
onClick={[Function]}
|
||||
>
|
||||
20
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
icon="empty"
|
||||
onClick={[Function]}
|
||||
>
|
||||
50
|
||||
</EuiContextMenuItem>,
|
||||
]
|
||||
}
|
||||
/>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`IndicesList should highlight the query in the matches 1`] = `
|
||||
<div>
|
||||
<EuiTable
|
||||
|
@ -225,7 +332,9 @@ exports[`IndicesList should highlight the query in the matches 1`] = `
|
|||
key="1"
|
||||
>
|
||||
<EuiTableRowCell>
|
||||
es
|
||||
<strong>
|
||||
es
|
||||
</strong>
|
||||
</EuiTableRowCell>
|
||||
<EuiTableRowCell />
|
||||
</EuiTableRow>
|
||||
|
|
|
@ -7,24 +7,39 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { IndicesList } from '.';
|
||||
import { IndicesList, IndicesListProps, PER_PAGE_STORAGE_KEY } from './indices_list';
|
||||
import { shallow } from 'enzyme';
|
||||
import { MatchedItem } from '@kbn/data-views-plugin/public';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
|
||||
const indices = [
|
||||
{ name: 'kibana', tags: [] },
|
||||
{ name: 'es', tags: [] },
|
||||
] as unknown as MatchedItem[];
|
||||
|
||||
const similarIndices = [
|
||||
{ name: 'logstash', tags: [] },
|
||||
{ name: 'some_logs', tags: [] },
|
||||
] as unknown as MatchedItem[];
|
||||
|
||||
describe('IndicesList', () => {
|
||||
const commonProps: Omit<IndicesListProps, 'query'> = {
|
||||
indices,
|
||||
isExactMatch: jest.fn(() => false),
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
new Storage(localStorage).remove(PER_PAGE_STORAGE_KEY);
|
||||
});
|
||||
|
||||
it('should render normally', () => {
|
||||
const component = shallow(<IndicesList indices={indices} query="" />);
|
||||
const component = shallow(<IndicesList {...commonProps} query="" />);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should change pages', () => {
|
||||
const component = shallow(<IndicesList indices={indices} query="" />);
|
||||
const component = shallow(<IndicesList {...commonProps} query="" />);
|
||||
|
||||
const instance = component.instance() as IndicesList;
|
||||
|
||||
|
@ -36,7 +51,7 @@ describe('IndicesList', () => {
|
|||
});
|
||||
|
||||
it('should change per page', () => {
|
||||
const component = shallow(<IndicesList indices={indices} query="" />);
|
||||
const component = shallow(<IndicesList {...commonProps} query="" />);
|
||||
|
||||
const instance = component.instance() as IndicesList;
|
||||
instance.onChangePerPage(1);
|
||||
|
@ -46,14 +61,33 @@ describe('IndicesList', () => {
|
|||
});
|
||||
|
||||
it('should highlight the query in the matches', () => {
|
||||
const component = shallow(<IndicesList indices={indices} query="ki" />);
|
||||
const component = shallow(
|
||||
<IndicesList
|
||||
{...commonProps}
|
||||
query="es,ki"
|
||||
isExactMatch={(indexName) => indexName === 'es'}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should highlight fully when an exact match', () => {
|
||||
const component = shallow(
|
||||
<IndicesList
|
||||
{...commonProps}
|
||||
indices={similarIndices}
|
||||
query="logs*"
|
||||
isExactMatch={(indexName) => indexName === 'some_logs'}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('updating props', () => {
|
||||
it('should render all new indices', () => {
|
||||
const component = shallow(<IndicesList indices={indices} query="" />);
|
||||
const component = shallow(<IndicesList {...commonProps} query="" />);
|
||||
|
||||
const moreIndices = [
|
||||
...indices,
|
||||
|
|
|
@ -25,13 +25,14 @@ import {
|
|||
} from '@elastic/eui';
|
||||
|
||||
import { Pager } from '@elastic/eui';
|
||||
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { MatchedItem, Tag } from '@kbn/data-views-plugin/public';
|
||||
|
||||
interface IndicesListProps {
|
||||
export interface IndicesListProps {
|
||||
indices: MatchedItem[];
|
||||
query: string;
|
||||
isExactMatch: (indexName: string) => boolean;
|
||||
}
|
||||
|
||||
interface IndicesListState {
|
||||
|
@ -41,15 +42,20 @@ interface IndicesListState {
|
|||
}
|
||||
|
||||
const PER_PAGE_INCREMENTS = [5, 10, 20, 50];
|
||||
export const PER_PAGE_STORAGE_KEY = 'dataViews.previewPanel.indicesPerPage';
|
||||
|
||||
export class IndicesList extends React.Component<IndicesListProps, IndicesListState> {
|
||||
pager: Pager;
|
||||
storage: Storage;
|
||||
|
||||
constructor(props: IndicesListProps) {
|
||||
super(props);
|
||||
|
||||
this.storage = new Storage(localStorage);
|
||||
|
||||
this.state = {
|
||||
page: 0,
|
||||
perPage: PER_PAGE_INCREMENTS[1],
|
||||
perPage: this.storage.get(PER_PAGE_STORAGE_KEY) || PER_PAGE_INCREMENTS[1],
|
||||
isPerPageControlOpen: false,
|
||||
};
|
||||
|
||||
|
@ -75,6 +81,7 @@ export class IndicesList extends React.Component<IndicesListProps, IndicesListSt
|
|||
this.setState({ perPage });
|
||||
this.resetPageTo0();
|
||||
this.closePerPageControl();
|
||||
this.storage.set(PER_PAGE_STORAGE_KEY, perPage);
|
||||
};
|
||||
|
||||
openPerPageControl = () => {
|
||||
|
@ -144,11 +151,20 @@ export class IndicesList extends React.Component<IndicesListProps, IndicesListSt
|
|||
}
|
||||
|
||||
highlightIndexName(indexName: string, query: string) {
|
||||
const { isExactMatch } = this.props;
|
||||
|
||||
if (!query) {
|
||||
return indexName;
|
||||
}
|
||||
|
||||
const queryAsArray = query.split(',').map((q) => q.trim());
|
||||
if (isExactMatch(indexName)) {
|
||||
return <strong>{indexName}</strong>;
|
||||
}
|
||||
|
||||
const queryAsArray = query
|
||||
.split(',')
|
||||
.map((q) => q.trim())
|
||||
.filter(Boolean);
|
||||
let queryIdx = -1;
|
||||
let queryWithoutWildcard = '';
|
||||
for (let i = 0; i < queryAsArray.length; i++) {
|
||||
|
@ -162,6 +178,7 @@ export class IndicesList extends React.Component<IndicesListProps, IndicesListSt
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (queryIdx === -1) {
|
||||
return indexName;
|
||||
}
|
||||
|
@ -179,7 +196,7 @@ export class IndicesList extends React.Component<IndicesListProps, IndicesListSt
|
|||
}
|
||||
|
||||
render() {
|
||||
const { indices, query, ...rest } = this.props;
|
||||
const { indices, query, isExactMatch, ...rest } = this.props;
|
||||
|
||||
const paginatedIndices = indices.slice(this.pager.firstItemIndex, this.pager.lastItemIndex + 1);
|
||||
const rows = paginatedIndices.map((index, key) => {
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
import { EuiTable, EuiButtonGroup } from '@elastic/eui';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { INDEX_PATTERN_TYPE, MatchedItem } from '@kbn/data-views-plugin/public';
|
||||
import { Props as PreviewPanelProps, PreviewPanel } from './preview_panel';
|
||||
import { from } from 'rxjs';
|
||||
|
||||
const indices = [
|
||||
{ name: 'kibana_1', tags: [] },
|
||||
{ name: 'kibana_2', tags: [] },
|
||||
{ name: 'es', tags: [] },
|
||||
] as unknown as MatchedItem[];
|
||||
|
||||
describe('DataViewEditor PreviewPanel', () => {
|
||||
const commonProps: Omit<PreviewPanelProps, 'matchedIndices$' | 'title'> = {
|
||||
type: INDEX_PATTERN_TYPE.DEFAULT,
|
||||
allowHidden: false,
|
||||
};
|
||||
|
||||
it('should render normally by default', async () => {
|
||||
const matchedIndices$: PreviewPanelProps['matchedIndices$'] = from([
|
||||
{
|
||||
allIndices: indices,
|
||||
exactMatchedIndices: [],
|
||||
partialMatchedIndices: [],
|
||||
visibleIndices: indices,
|
||||
},
|
||||
]);
|
||||
const component = await mountWithIntl(
|
||||
<PreviewPanel {...commonProps} title="" matchedIndices$={matchedIndices$} />
|
||||
);
|
||||
|
||||
expect(component.find(EuiTable).exists()).toBeTruthy();
|
||||
expect(component.find(EuiButtonGroup).exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should render matching indices and can switch to all indices', async () => {
|
||||
const matchedIndices$: PreviewPanelProps['matchedIndices$'] = from([
|
||||
{
|
||||
allIndices: indices,
|
||||
exactMatchedIndices: [indices[0], indices[1]],
|
||||
partialMatchedIndices: [],
|
||||
visibleIndices: [indices[0], indices[1]],
|
||||
},
|
||||
]);
|
||||
const component = await mountWithIntl(
|
||||
<PreviewPanel {...commonProps} title="kib*" matchedIndices$={matchedIndices$} />
|
||||
);
|
||||
|
||||
expect(component.find(EuiTable).exists()).toBeTruthy();
|
||||
expect(component.find(EuiButtonGroup).exists()).toBeTruthy();
|
||||
|
||||
expect(component.find('.euiButtonGroupButton-isSelected').first().text()).toBe(
|
||||
'Matching sources'
|
||||
);
|
||||
|
||||
findTestSubject(component, 'allIndices').simulate('change', {
|
||||
target: {
|
||||
value: true,
|
||||
},
|
||||
});
|
||||
|
||||
await component.update();
|
||||
|
||||
expect(component.find('.euiButtonGroupButton-isSelected').first().text()).toBe('All sources');
|
||||
});
|
||||
|
||||
it('should render matching indices with warnings', async () => {
|
||||
const matchedIndices$: PreviewPanelProps['matchedIndices$'] = from([
|
||||
{
|
||||
allIndices: indices,
|
||||
exactMatchedIndices: [],
|
||||
partialMatchedIndices: [indices[0], indices[1]],
|
||||
visibleIndices: [indices[0], indices[1]],
|
||||
},
|
||||
]);
|
||||
const component = await mountWithIntl(
|
||||
<PreviewPanel {...commonProps} title="kib*" matchedIndices$={matchedIndices$} />
|
||||
);
|
||||
|
||||
expect(component.find(EuiTable).exists()).toBeTruthy();
|
||||
expect(component.find(EuiButtonGroup).exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render all indices tab when ends with a comma and can switch to matching sources', async () => {
|
||||
const matchedIndices$: PreviewPanelProps['matchedIndices$'] = from([
|
||||
{
|
||||
allIndices: indices,
|
||||
exactMatchedIndices: [indices[0]],
|
||||
partialMatchedIndices: [],
|
||||
visibleIndices: [indices[0]],
|
||||
},
|
||||
]);
|
||||
const component = await mountWithIntl(
|
||||
<PreviewPanel {...commonProps} title="kibana_1," matchedIndices$={matchedIndices$} />
|
||||
);
|
||||
|
||||
expect(component.find(EuiTable).exists()).toBeTruthy();
|
||||
expect(component.find(EuiButtonGroup).exists()).toBeTruthy();
|
||||
|
||||
expect(component.find('.euiButtonGroupButton-isSelected').first().text()).toBe('All sources');
|
||||
|
||||
findTestSubject(component, 'onlyMatchingIndices').simulate('change', {
|
||||
target: {
|
||||
value: true,
|
||||
},
|
||||
});
|
||||
|
||||
await component.update();
|
||||
|
||||
expect(component.find('.euiButtonGroupButton-isSelected').first().text()).toBe(
|
||||
'Matching sources'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -6,8 +6,9 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import React, { useState } from 'react';
|
||||
import { EuiButtonGroup, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { Observable } from 'rxjs';
|
||||
import { INDEX_PATTERN_TYPE } from '@kbn/data-views-plugin/public';
|
||||
|
@ -17,7 +18,27 @@ import { matchedIndiciesDefault } from '../../data_view_editor_service';
|
|||
|
||||
import { MatchedIndicesSet } from '../../types';
|
||||
|
||||
interface Props {
|
||||
enum ViewMode {
|
||||
allIndices = 'allIndices',
|
||||
onlyMatchingIndices = 'onlyMatchingIndices',
|
||||
}
|
||||
|
||||
const viewModeButtons = [
|
||||
{
|
||||
id: ViewMode.allIndices,
|
||||
label: i18n.translate('indexPatternEditor.previewPanel.viewModeGroup.allSourcesButton', {
|
||||
defaultMessage: 'All sources',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: ViewMode.onlyMatchingIndices,
|
||||
label: i18n.translate('indexPatternEditor.previewPanel.viewModeGroup.matchingSourcesButton', {
|
||||
defaultMessage: 'Matching sources',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
export interface Props {
|
||||
type: INDEX_PATTERN_TYPE;
|
||||
allowHidden: boolean;
|
||||
title: string;
|
||||
|
@ -25,20 +46,35 @@ interface Props {
|
|||
}
|
||||
|
||||
export const PreviewPanel = ({ type, allowHidden, title = '', matchedIndices$ }: Props) => {
|
||||
const [viewMode, setViewMode] = useState<ViewMode>();
|
||||
const matched = useObservable(matchedIndices$, matchedIndiciesDefault);
|
||||
const indicesListContent =
|
||||
matched.visibleIndices.length || matched.allIndices.length ? (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<IndicesList
|
||||
data-test-subj="createIndexPatternStep1IndicesList"
|
||||
query={title}
|
||||
indices={title.length ? matched.visibleIndices : matched.allIndices}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
|
||||
let currentlyVisibleIndices;
|
||||
let currentViewMode;
|
||||
|
||||
if (
|
||||
(title.length && !isAboutToIncludeMoreIndices(title) && viewMode !== ViewMode.allIndices) ||
|
||||
viewMode === ViewMode.onlyMatchingIndices
|
||||
) {
|
||||
currentlyVisibleIndices = matched.visibleIndices;
|
||||
currentViewMode = ViewMode.onlyMatchingIndices;
|
||||
} else {
|
||||
currentlyVisibleIndices = matched.allIndices;
|
||||
currentViewMode = ViewMode.allIndices;
|
||||
}
|
||||
|
||||
const indicesListContent = currentlyVisibleIndices.length ? (
|
||||
<IndicesList
|
||||
data-test-subj="createIndexPatternStep1IndicesList"
|
||||
query={title}
|
||||
indices={currentlyVisibleIndices}
|
||||
isExactMatch={(indexName) =>
|
||||
title.length > 0 && matched.exactMatchedIndices.some((index) => index.name === indexName)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -48,7 +84,23 @@ export const PreviewPanel = ({ type, allowHidden, title = '', matchedIndices$ }:
|
|||
isIncludingSystemIndices={allowHidden}
|
||||
query={title}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
{Boolean(title) && currentlyVisibleIndices.length > 0 && (
|
||||
<EuiButtonGroup
|
||||
isFullWidth
|
||||
legend={i18n.translate('indexPatternEditor.previewPanel.viewModeGroup.legend', {
|
||||
defaultMessage: 'Visible sources',
|
||||
})}
|
||||
options={viewModeButtons}
|
||||
idSelected={currentViewMode}
|
||||
onChange={(id: string) => setViewMode(id as ViewMode)}
|
||||
/>
|
||||
)}
|
||||
{indicesListContent}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function isAboutToIncludeMoreIndices(query: string) {
|
||||
return query.trimEnd().endsWith(',');
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"@kbn/i18n",
|
||||
"@kbn/test-jest-helpers",
|
||||
"@kbn/ui-theme",
|
||||
"@kbn/kibana-utils-plugin",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -3879,7 +3879,6 @@
|
|||
"indexPatternEditor.typeSelect.standardDescription": "Effectuer des agrégations complètes à partir de n'importe quelles données",
|
||||
"indexPatternEditor.typeSelect.standardTitle": "Vue de données standard",
|
||||
"indexPatternEditor.validations.noSingleAstriskPattern": "Un seul astérisque \"*\" n’est pas un modèle d'indexation autorisé",
|
||||
"indexPatternEditor.validations.titleHelpText": "Entrez un modèle d'indexation qui correspond à une ou plusieurs sources de données. Utilisez un astérisque (*) pour faire correspondre plusieurs caractères. Les espaces et les caractères , /, ?, \", <, >, | ne sont pas autorisés.",
|
||||
"indexPatternEditor.validations.titleIsRequiredErrorMessage": "Un modèle d'indexation est requis.",
|
||||
"indexPatternFieldEditor.date.momentLabel": "Modèle de format Moment.js (par défaut : {defaultPattern})",
|
||||
"indexPatternFieldEditor.defaultErrorMessage": "Une erreur s'est produite lors de l'utilisation de cette configuration de format : {message}.",
|
||||
|
|
|
@ -3876,7 +3876,6 @@
|
|||
"indexPatternEditor.typeSelect.standardDescription": "すべてのデータに完全アグリゲーションを実行",
|
||||
"indexPatternEditor.typeSelect.standardTitle": "標準データビュー",
|
||||
"indexPatternEditor.validations.noSingleAstriskPattern": "単一の「*」は許可されたパターンではありません",
|
||||
"indexPatternEditor.validations.titleHelpText": "1つ以上のデータソースと一致するインデックスパターンを入力します。複数の文字の一致にアスタリスク(*)を使用します。ペースと / ? , \" < > | 文字は使用できません。",
|
||||
"indexPatternEditor.validations.titleIsRequiredErrorMessage": "インデックスパターンが必要です。",
|
||||
"indexPatternFieldEditor.date.momentLabel": "Moment.jsのフォーマットパターン(デフォルト:{defaultPattern})",
|
||||
"indexPatternFieldEditor.defaultErrorMessage": "このフォーマット構成の使用を試みた際にエラーが発生しました:{message}",
|
||||
|
|
|
@ -3881,7 +3881,6 @@
|
|||
"indexPatternEditor.typeSelect.standardDescription": "对任何数据执行完全聚合",
|
||||
"indexPatternEditor.typeSelect.standardTitle": "标准数据视图",
|
||||
"indexPatternEditor.validations.noSingleAstriskPattern": "不允许以单个“*”作为索引模式",
|
||||
"indexPatternEditor.validations.titleHelpText": "输入与一个或多个数据源匹配的索引模式。使用星号 (*) 匹配多个字符。不允许使用空格和字符 /、?、\"、<、>、|。",
|
||||
"indexPatternEditor.validations.titleIsRequiredErrorMessage": "“索引模式”必填。",
|
||||
"indexPatternFieldEditor.date.momentLabel": "Moment.js 格式模式(默认值:{defaultPattern})",
|
||||
"indexPatternFieldEditor.defaultErrorMessage": "尝试使用此格式配置时发生错误:{message}",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue