[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:
Julia Rechkunova 2023-03-02 21:07:55 +01:00 committed by GitHub
parent 4e3cc20ca0
commit 2ceaa64744
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 511 additions and 36 deletions

View file

@ -13,6 +13,7 @@
],
"requiredBundles": [
"kibanaReact",
"kibanaUtils",
"esUiShared"
]
}

View file

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

View file

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

View file

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

View file

@ -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',

View file

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

View file

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

View file

@ -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) => {

View file

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

View file

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

View file

@ -17,6 +17,7 @@
"@kbn/i18n",
"@kbn/test-jest-helpers",
"@kbn/ui-theme",
"@kbn/kibana-utils-plugin",
],
"exclude": [
"target/**/*",

View file

@ -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 \"*\" nest 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 , /, ?, \", &lt;, >, | 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}.",

View file

@ -3876,7 +3876,6 @@
"indexPatternEditor.typeSelect.standardDescription": "すべてのデータに完全アグリゲーションを実行",
"indexPatternEditor.typeSelect.standardTitle": "標準データビュー",
"indexPatternEditor.validations.noSingleAstriskPattern": "単一の「*」は許可されたパターンではありません",
"indexPatternEditor.validations.titleHelpText": "1つ以上のデータソースと一致するインデックスパターンを入力します。複数の文字の一致にアスタリスク*)を使用します。ペースと / ? , \" &lt; > | 文字は使用できません。",
"indexPatternEditor.validations.titleIsRequiredErrorMessage": "インデックスパターンが必要です。",
"indexPatternFieldEditor.date.momentLabel": "Moment.jsのフォーマットパターンデフォルト{defaultPattern}",
"indexPatternFieldEditor.defaultErrorMessage": "このフォーマット構成の使用を試みた際にエラーが発生しました:{message}",

View file

@ -3881,7 +3881,6 @@
"indexPatternEditor.typeSelect.standardDescription": "对任何数据执行完全聚合",
"indexPatternEditor.typeSelect.standardTitle": "标准数据视图",
"indexPatternEditor.validations.noSingleAstriskPattern": "不允许以单个“*”作为索引模式",
"indexPatternEditor.validations.titleHelpText": "输入与一个或多个数据源匹配的索引模式。使用星号 (*) 匹配多个字符。不允许使用空格和字符 /、?、\"、&lt;、>、|。",
"indexPatternEditor.validations.titleIsRequiredErrorMessage": "“索引模式”必填。",
"indexPatternFieldEditor.date.momentLabel": "Moment.js 格式模式(默认值:{defaultPattern}",
"indexPatternFieldEditor.defaultErrorMessage": "尝试使用此格式配置时发生错误:{message}",