Index Patterns Management - use /_resolve endpoint for data streams support (#70271) (#71105)

* Index Patterns Management - use `/_resolve` endpoint for data streams support
This commit is contained in:
Matthew Kime 2020-07-08 11:41:23 -05:00 committed by GitHub
parent eff820bd69
commit 7423de8b77
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 1417 additions and 1158 deletions

View file

@ -1,7 +1,7 @@
{
"id": "indexPatternManagement",
"version": "kibana",
"server": false,
"server": true,
"ui": true,
"requiredPlugins": ["management", "data", "kibanaLegacy"]
}

View file

@ -1,41 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CreateIndexPatternWizard defaults to the loading state 1`] = `
<EuiPanel
paddingSize="l"
>
<div>
<Header
indexPatternName="name"
isBeta={false}
isIncludingSystemIndices={false}
onChangeIncludingSystemIndices={[Function]}
prompt={null}
showSystemIndices={true}
/>
<LoadingState />
</div>
<Fragment>
<LoadingState />
<EuiGlobalToastList
dismissToast={[Function]}
toastLifeTimeMs={6000}
toasts={Array []}
/>
</EuiPanel>
</Fragment>
`;
exports[`CreateIndexPatternWizard renders index pattern step when there are indices 1`] = `
<EuiPanel
paddingSize="l"
>
<div>
<Fragment>
<EuiPageContent>
<Header
docLinks={
Object {
"links": Object {
"indexPatterns": Object {},
"scriptedFields": Object {},
},
}
}
indexPatternName="name"
isBeta={false}
isIncludingSystemIndices={false}
onChangeIncludingSystemIndices={[Function]}
prompt={null}
showSystemIndices={true}
/>
<EuiHorizontalRule />
<StepIndexPattern
allIndices={
Array [
@ -55,56 +47,48 @@ exports[`CreateIndexPatternWizard renders index pattern step when there are indi
"type": "default",
}
}
isIncludingSystemIndices={false}
showSystemIndices={true}
/>
</div>
</EuiPageContent>
<EuiGlobalToastList
dismissToast={[Function]}
toastLifeTimeMs={6000}
toasts={Array []}
/>
</EuiPanel>
</Fragment>
`;
exports[`CreateIndexPatternWizard renders the empty state when there are no indices 1`] = `
<EuiPanel
paddingSize="l"
>
<div>
<Header
indexPatternName="name"
isBeta={false}
isIncludingSystemIndices={false}
onChangeIncludingSystemIndices={[Function]}
prompt={null}
showSystemIndices={true}
/>
<EmptyState
onRefresh={[Function]}
prependBasePath={[Function]}
/>
</div>
<Fragment>
<EmptyState
onRefresh={[Function]}
prependBasePath={[Function]}
/>
<EuiGlobalToastList
dismissToast={[Function]}
toastLifeTimeMs={6000}
toasts={Array []}
/>
</EuiPanel>
</Fragment>
`;
exports[`CreateIndexPatternWizard renders time field step when step is set to 2 1`] = `
<EuiPanel
paddingSize="l"
>
<div>
<Fragment>
<EuiPageContent>
<Header
docLinks={
Object {
"links": Object {
"indexPatterns": Object {},
"scriptedFields": Object {},
},
}
}
indexPatternName="name"
isBeta={false}
isIncludingSystemIndices={false}
onChangeIncludingSystemIndices={[Function]}
prompt={null}
showSystemIndices={true}
/>
<EuiHorizontalRule />
<StepTimeField
createIndexPattern={[Function]}
goToPreviousStep={[Function]}
@ -120,28 +104,32 @@ exports[`CreateIndexPatternWizard renders time field step when step is set to 2
}
}
/>
</div>
</EuiPageContent>
<EuiGlobalToastList
dismissToast={[Function]}
toastLifeTimeMs={6000}
toasts={Array []}
/>
</EuiPanel>
</Fragment>
`;
exports[`CreateIndexPatternWizard renders when there are no indices but there are remote clusters 1`] = `
<EuiPanel
paddingSize="l"
>
<div>
<Fragment>
<EuiPageContent>
<Header
docLinks={
Object {
"links": Object {
"indexPatterns": Object {},
"scriptedFields": Object {},
},
}
}
indexPatternName="name"
isBeta={false}
isIncludingSystemIndices={false}
onChangeIncludingSystemIndices={[Function]}
prompt={null}
showSystemIndices={true}
/>
<EuiHorizontalRule />
<StepIndexPattern
allIndices={Array []}
goToNextStep={[Function]}
@ -155,56 +143,27 @@ exports[`CreateIndexPatternWizard renders when there are no indices but there ar
"type": "default",
}
}
isIncludingSystemIndices={false}
showSystemIndices={true}
/>
</div>
</EuiPageContent>
<EuiGlobalToastList
dismissToast={[Function]}
toastLifeTimeMs={6000}
toasts={Array []}
/>
</EuiPanel>
</Fragment>
`;
exports[`CreateIndexPatternWizard shows system indices even if there are no other indices if the include system indices is toggled 1`] = `
<EuiPanel
paddingSize="l"
>
<div>
<Header
indexPatternName="name"
isBeta={false}
isIncludingSystemIndices={true}
onChangeIncludingSystemIndices={[Function]}
prompt={null}
showSystemIndices={true}
/>
<StepIndexPattern
allIndices={
Array [
Object {
"name": ".kibana ",
},
]
}
goToNextStep={[Function]}
indexPatternCreationType={
IndexPatternCreationConfig {
"httpClient": null,
"isBeta": false,
"key": "default",
"name": "name",
"showSystemIndices": true,
"type": "default",
}
}
isIncludingSystemIndices={true}
/>
</div>
<Fragment>
<EmptyState
onRefresh={[Function]}
prependBasePath={[Function]}
/>
<EuiGlobalToastList
dismissToast={[Function]}
toastLifeTimeMs={6000}
toasts={Array []}
/>
</EuiPanel>
</Fragment>
`;

View file

@ -2,10 +2,15 @@
exports[`Header should render a different name, prompt, and beta tag if provided 1`] = `
<Header
docLinks={
Object {
"links": Object {
"indexPatterns": Object {},
},
}
}
indexPatternName="test index pattern"
isBeta={true}
isIncludingSystemIndices={false}
onChangeIncludingSystemIndices={[Function]}
prompt={
<div>
Test prompt
@ -31,50 +36,6 @@ exports[`Header should render a different name, prompt, and beta tag if provided
</EuiBetaBadge>
</h1>
</EuiTitle>
<EuiFlexGroup
alignItems="flexEnd"
justifyContent="spaceBetween"
>
<div
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsFlexEnd euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive"
>
<EuiFlexItem
grow={false}
>
<div
className="euiFlexItem euiFlexItem--flexGrowZero"
>
<EuiText
size="s"
>
<div
className="euiText euiText--small"
>
<p>
<EuiTextColor
color="subdued"
>
<span
className="euiTextColor euiTextColor--subdued"
>
<FormattedMessage
defaultMessage="Kibana uses index patterns to retrieve data from Elasticsearch indices for things like visualizations."
id="indexPatternManagement.createIndexPatternLabel"
values={Object {}}
>
<span>
Kibana uses index patterns to retrieve data from Elasticsearch indices for things like visualizations.
</span>
</FormattedMessage>
</span>
</EuiTextColor>
</p>
</div>
</EuiText>
</div>
</EuiFlexItem>
</div>
</EuiFlexGroup>
<EuiSpacer
size="s"
>
@ -82,9 +43,83 @@ exports[`Header should render a different name, prompt, and beta tag if provided
className="euiSpacer euiSpacer--s"
/>
</EuiSpacer>
<div>
Test prompt
</div>
<EuiText>
<div
className="euiText euiText--medium"
>
<p>
<FormattedMessage
defaultMessage="An index pattern can match a single source, for example, {single}, or {multiple} data souces, {star}."
id="indexPatternManagement.createIndexPattern.description"
values={
Object {
"multiple": <strong>
multiple
</strong>,
"single": <EuiCode>
filebeat-4-3-22
</EuiCode>,
"star": <EuiCode>
filebeat-*
</EuiCode>,
}
}
>
<span>
An index pattern can match a single source, for example,
<EuiCode>
<EuiCodeBlockImpl
inline={true}
>
<span>
<code>
filebeat-4-3-22
</code>
</span>
</EuiCodeBlockImpl>
</EuiCode>
, or
<strong>
multiple
</strong>
data souces,
<EuiCode>
<EuiCodeBlockImpl
inline={true}
>
<span>
<code>
filebeat-*
</code>
</span>
</EuiCodeBlockImpl>
</EuiCode>
.
</span>
</FormattedMessage>
<br />
<EuiLink
external={true}
target="_blank"
>
<button
className="euiLink euiLink--primary"
type="button"
>
<FormattedMessage
defaultMessage="Read documentation"
id="indexPatternManagement.createIndexPattern.documentation"
values={Object {}}
>
<span>
Read documentation
</span>
</FormattedMessage>
</button>
</EuiLink>
</p>
</div>
</EuiText>
<EuiSpacer
size="m"
>
@ -92,15 +127,23 @@ exports[`Header should render a different name, prompt, and beta tag if provided
className="euiSpacer euiSpacer--m"
/>
</EuiSpacer>
<div>
Test prompt
</div>
</div>
</Header>
`;
exports[`Header should render normally 1`] = `
<Header
docLinks={
Object {
"links": Object {
"indexPatterns": Object {},
},
}
}
indexPatternName="test index pattern"
isIncludingSystemIndices={true}
onChangeIncludingSystemIndices={[Function]}
>
<div>
<EuiTitle>
@ -110,66 +153,104 @@ exports[`Header should render normally 1`] = `
Create test index pattern
</h1>
</EuiTitle>
<EuiFlexGroup
alignItems="flexEnd"
justifyContent="spaceBetween"
>
<div
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsFlexEnd euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive"
>
<EuiFlexItem
grow={false}
>
<div
className="euiFlexItem euiFlexItem--flexGrowZero"
>
<EuiText
size="s"
>
<div
className="euiText euiText--small"
>
<p>
<EuiTextColor
color="subdued"
>
<span
className="euiTextColor euiTextColor--subdued"
>
<FormattedMessage
defaultMessage="Kibana uses index patterns to retrieve data from Elasticsearch indices for things like visualizations."
id="indexPatternManagement.createIndexPatternLabel"
values={Object {}}
>
<span>
Kibana uses index patterns to retrieve data from Elasticsearch indices for things like visualizations.
</span>
</FormattedMessage>
</span>
</EuiTextColor>
</p>
</div>
</EuiText>
</div>
</EuiFlexItem>
</div>
</EuiFlexGroup>
<EuiSpacer
size="m"
size="s"
>
<div
className="euiSpacer euiSpacer--m"
className="euiSpacer euiSpacer--s"
/>
</EuiSpacer>
<EuiText>
<div
className="euiText euiText--medium"
>
<p>
<FormattedMessage
defaultMessage="An index pattern can match a single source, for example, {single}, or {multiple} data souces, {star}."
id="indexPatternManagement.createIndexPattern.description"
values={
Object {
"multiple": <strong>
multiple
</strong>,
"single": <EuiCode>
filebeat-4-3-22
</EuiCode>,
"star": <EuiCode>
filebeat-*
</EuiCode>,
}
}
>
<span>
An index pattern can match a single source, for example,
<EuiCode>
<EuiCodeBlockImpl
inline={true}
>
<span>
<code>
filebeat-4-3-22
</code>
</span>
</EuiCodeBlockImpl>
</EuiCode>
, or
<strong>
multiple
</strong>
data souces,
<EuiCode>
<EuiCodeBlockImpl
inline={true}
>
<span>
<code>
filebeat-*
</code>
</span>
</EuiCodeBlockImpl>
</EuiCode>
.
</span>
</FormattedMessage>
<br />
<EuiLink
external={true}
target="_blank"
>
<button
className="euiLink euiLink--primary"
type="button"
>
<FormattedMessage
defaultMessage="Read documentation"
id="indexPatternManagement.createIndexPattern.documentation"
values={Object {}}
>
<span>
Read documentation
</span>
</FormattedMessage>
</button>
</EuiLink>
</p>
</div>
</EuiText>
</div>
</Header>
`;
exports[`Header should render without including system indices 1`] = `
<Header
docLinks={
Object {
"links": Object {
"indexPatterns": Object {},
},
}
}
indexPatternName="test index pattern"
isIncludingSystemIndices={false}
onChangeIncludingSystemIndices={[Function]}
>
<div>
<EuiTitle>
@ -179,57 +260,90 @@ exports[`Header should render without including system indices 1`] = `
Create test index pattern
</h1>
</EuiTitle>
<EuiFlexGroup
alignItems="flexEnd"
justifyContent="spaceBetween"
>
<div
className="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--alignItemsFlexEnd euiFlexGroup--justifyContentSpaceBetween euiFlexGroup--directionRow euiFlexGroup--responsive"
>
<EuiFlexItem
grow={false}
>
<div
className="euiFlexItem euiFlexItem--flexGrowZero"
>
<EuiText
size="s"
>
<div
className="euiText euiText--small"
>
<p>
<EuiTextColor
color="subdued"
>
<span
className="euiTextColor euiTextColor--subdued"
>
<FormattedMessage
defaultMessage="Kibana uses index patterns to retrieve data from Elasticsearch indices for things like visualizations."
id="indexPatternManagement.createIndexPatternLabel"
values={Object {}}
>
<span>
Kibana uses index patterns to retrieve data from Elasticsearch indices for things like visualizations.
</span>
</FormattedMessage>
</span>
</EuiTextColor>
</p>
</div>
</EuiText>
</div>
</EuiFlexItem>
</div>
</EuiFlexGroup>
<EuiSpacer
size="m"
size="s"
>
<div
className="euiSpacer euiSpacer--m"
className="euiSpacer euiSpacer--s"
/>
</EuiSpacer>
<EuiText>
<div
className="euiText euiText--medium"
>
<p>
<FormattedMessage
defaultMessage="An index pattern can match a single source, for example, {single}, or {multiple} data souces, {star}."
id="indexPatternManagement.createIndexPattern.description"
values={
Object {
"multiple": <strong>
multiple
</strong>,
"single": <EuiCode>
filebeat-4-3-22
</EuiCode>,
"star": <EuiCode>
filebeat-*
</EuiCode>,
}
}
>
<span>
An index pattern can match a single source, for example,
<EuiCode>
<EuiCodeBlockImpl
inline={true}
>
<span>
<code>
filebeat-4-3-22
</code>
</span>
</EuiCodeBlockImpl>
</EuiCode>
, or
<strong>
multiple
</strong>
data souces,
<EuiCode>
<EuiCodeBlockImpl
inline={true}
>
<span>
<code>
filebeat-*
</code>
</span>
</EuiCodeBlockImpl>
</EuiCode>
.
</span>
</FormattedMessage>
<br />
<EuiLink
external={true}
target="_blank"
>
<button
className="euiLink euiLink--primary"
type="button"
>
<FormattedMessage
defaultMessage="Read documentation"
id="indexPatternManagement.createIndexPattern.documentation"
values={Object {}}
>
<span>
Read documentation
</span>
</FormattedMessage>
</button>
</EuiLink>
</p>
</div>
</EuiText>
</div>
</Header>
`;

View file

@ -22,18 +22,20 @@ import { Header } from '../header';
import { mount } from 'enzyme';
import { KibanaContextProvider } from 'src/plugins/kibana_react/public';
import { mockManagementPlugin } from '../../../../mocks';
import { DocLinksStart } from 'kibana/public';
describe('Header', () => {
const indexPatternName = 'test index pattern';
const mockedContext = mockManagementPlugin.createIndexPatternManagmentContext();
const mockedDocLinks = {
links: {
indexPatterns: {},
},
} as DocLinksStart;
it('should render normally', () => {
const component = mount(
<Header
indexPatternName={indexPatternName}
isIncludingSystemIndices={true}
onChangeIncludingSystemIndices={() => {}}
/>,
<Header indexPatternName={indexPatternName} docLinks={mockedDocLinks} />,
{
wrappingComponent: KibanaContextProvider,
wrappingComponentProps: {
@ -47,11 +49,7 @@ describe('Header', () => {
it('should render without including system indices', () => {
const component = mount(
<Header
indexPatternName={indexPatternName}
isIncludingSystemIndices={false}
onChangeIncludingSystemIndices={() => {}}
/>,
<Header indexPatternName={indexPatternName} docLinks={mockedDocLinks} />,
{
wrappingComponent: KibanaContextProvider,
wrappingComponentProps: {
@ -66,11 +64,10 @@ describe('Header', () => {
it('should render a different name, prompt, and beta tag if provided', () => {
const component = mount(
<Header
isIncludingSystemIndices={false}
onChangeIncludingSystemIndices={() => {}}
prompt={<div>Test prompt</div>}
indexPatternName={indexPatternName}
isBeta={true}
docLinks={mockedDocLinks}
/>,
{
wrappingComponent: KibanaContextProvider,

View file

@ -17,38 +17,26 @@
* under the License.
*/
import React, { Fragment } from 'react';
import React from 'react';
import {
EuiBetaBadge,
EuiSpacer,
EuiTitle,
EuiFlexGroup,
EuiFlexItem,
EuiText,
EuiTextColor,
EuiSwitch,
} from '@elastic/eui';
import { EuiBetaBadge, EuiSpacer, EuiTitle, EuiText, EuiCode, EuiLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { DocLinksStart } from 'kibana/public';
import { useKibana } from '../../../../../../../plugins/kibana_react/public';
import { IndexPatternManagmentContext } from '../../../../types';
export const Header = ({
prompt,
indexPatternName,
showSystemIndices = false,
isIncludingSystemIndices,
onChangeIncludingSystemIndices,
isBeta = false,
docLinks,
}: {
prompt?: React.ReactNode;
indexPatternName: string;
showSystemIndices?: boolean;
isIncludingSystemIndices: boolean;
onChangeIncludingSystemIndices: () => void;
isBeta?: boolean;
docLinks: DocLinksStart;
}) => {
const changeTitle = useKibana<IndexPatternManagmentContext>().services.chrome.docTitle.change;
const createIndexPatternHeader = i18n.translate(
@ -67,53 +55,44 @@ export const Header = ({
<h1>
{createIndexPatternHeader}
{isBeta ? (
<Fragment>
<>
{' '}
<EuiBetaBadge
label={i18n.translate('indexPatternManagement.createIndexPattern.betaLabel', {
defaultMessage: 'Beta',
})}
/>
</Fragment>
</>
) : null}
</h1>
</EuiTitle>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="flexEnd">
<EuiFlexItem grow={false}>
<EuiText size="s">
<p>
<EuiTextColor color="subdued">
<FormattedMessage
id="indexPatternManagement.createIndexPatternLabel"
defaultMessage="Kibana uses index patterns to retrieve data from Elasticsearch indices for things like visualizations."
/>
</EuiTextColor>
</p>
</EuiText>
</EuiFlexItem>
{showSystemIndices ? (
<EuiFlexItem grow={false}>
<EuiSwitch
label={
<FormattedMessage
id="indexPatternManagement.createIndexPattern.includeSystemIndicesToggleSwitchLabel"
defaultMessage="Include system indices"
/>
}
id="checkboxShowSystemIndices"
checked={isIncludingSystemIndices}
onChange={onChangeIncludingSystemIndices}
<EuiSpacer size="s" />
<EuiText>
<p>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.description"
defaultMessage="An index pattern can match a single source, for example, {single}, or {multiple} data souces, {star}."
values={{
multiple: <strong>multiple</strong>,
single: <EuiCode>filebeat-4-3-22</EuiCode>,
star: <EuiCode>filebeat-*</EuiCode>,
}}
/>
<br />
<EuiLink href={docLinks.links.indexPatterns.introduction} target="_blank" external>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.documentation"
defaultMessage="Read documentation"
/>
</EuiFlexItem>
) : null}
</EuiFlexGroup>
</EuiLink>
</p>
</EuiText>
{prompt ? (
<Fragment>
<EuiSpacer size="s" />
<>
<EuiSpacer size="m" />
{prompt}
</Fragment>
</>
) : null}
<EuiSpacer size="m" />
</div>
);
};

View file

@ -11,8 +11,10 @@ Object {
]
}
goToNextStep={[Function]}
isIncludingSystemIndices={false}
isInputInvalid={true}
isNextStepDisabled={true}
onChangeIncludingSystemIndices={[Function]}
onQueryChanged={[Function]}
query="?"
/>,
@ -25,6 +27,7 @@ exports[`StepIndexPattern renders indices which match the initial query 1`] = `
indices={
Array [
Object {
"item": Object {},
"name": "kibana",
},
]
@ -39,6 +42,7 @@ exports[`StepIndexPattern renders matching indices when input is valid 1`] = `
indices={
Array [
Object {
"item": Object {},
"name": "kibana",
},
]

View file

@ -16,13 +16,8 @@ exports[`Header should mark the input as invalid 1`] = `
<EuiSpacer
size="m"
/>
<EuiFlexGroup
alignItems="flexEnd"
justifyContent="spaceBetween"
>
<EuiFlexItem
grow={false}
>
<EuiFlexGroup>
<EuiFlexItem>
<EuiForm
isInvalid={true}
>
@ -34,43 +29,40 @@ exports[`Header should mark the input as invalid 1`] = `
"Input is invalid",
]
}
fullWidth={false}
fullWidth={true}
hasChildLabel={true}
hasEmptyLabelSpace={false}
helpText={
<div>
<p>
<FormattedMessage
defaultMessage="You can use a {asterisk} as a wildcard in your index pattern."
id="indexPatternManagement.createIndexPattern.step.indexPattern.allowLabel"
values={
Object {
"asterisk": <strong>
*
</strong>,
}
<React.Fragment>
<FormattedMessage
defaultMessage="Use an asterisk ({asterisk}) to match multiple indices."
id="indexPatternManagement.createIndexPattern.step.indexPattern.allowLabel"
values={
Object {
"asterisk": <strong>
*
</strong>,
}
/>
</p>
<p>
<FormattedMessage
defaultMessage="You can't use spaces or the characters {characterList}."
id="indexPatternManagement.createIndexPattern.step.indexPattern.disallowLabel"
values={
Object {
"characterList": <strong>
%
</strong>,
}
}
/>
<FormattedMessage
defaultMessage="Spaces and the characters {characterList} are not allowed."
id="indexPatternManagement.createIndexPattern.step.indexPattern.disallowLabel"
values={
Object {
"characterList": <strong>
%
</strong>,
}
/>
</p>
</div>
}
/>
</React.Fragment>
}
isInvalid={true}
label={
<FormattedMessage
defaultMessage="Index pattern"
defaultMessage="Index pattern name"
id="indexPatternManagement.createIndexPattern.step.indexPatternLabel"
values={Object {}}
/>
@ -79,6 +71,7 @@ exports[`Header should mark the input as invalid 1`] = `
>
<EuiFieldText
data-test-subj="createIndexPatternNameInput"
fullWidth={true}
isInvalid={true}
name="indexPattern"
onChange={[Function]}
@ -91,18 +84,29 @@ exports[`Header should mark the input as invalid 1`] = `
<EuiFlexItem
grow={false}
>
<EuiButton
data-test-subj="createIndexPatternGoToStep2Button"
iconType="arrowRight"
isDisabled={true}
onClick={[Function]}
<EuiFormRow
describedByIds={Array []}
display="row"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={true}
labelType="label"
>
<FormattedMessage
defaultMessage="Next step"
id="indexPatternManagement.createIndexPattern.step.nextStepButton"
values={Object {}}
/>
</EuiButton>
<EuiButton
data-test-subj="createIndexPatternGoToStep2Button"
fill={true}
iconSide="right"
iconType="arrowRight"
isDisabled={true}
onClick={[Function]}
>
<FormattedMessage
defaultMessage="Next step"
id="indexPatternManagement.createIndexPattern.step.nextStepButton"
values={Object {}}
/>
</EuiButton>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
</div>
@ -124,13 +128,8 @@ exports[`Header should render normally 1`] = `
<EuiSpacer
size="m"
/>
<EuiFlexGroup
alignItems="flexEnd"
justifyContent="spaceBetween"
>
<EuiFlexItem
grow={false}
>
<EuiFlexGroup>
<EuiFlexItem>
<EuiForm
isInvalid={false}
>
@ -138,43 +137,40 @@ exports[`Header should render normally 1`] = `
describedByIds={Array []}
display="row"
error={Array []}
fullWidth={false}
fullWidth={true}
hasChildLabel={true}
hasEmptyLabelSpace={false}
helpText={
<div>
<p>
<FormattedMessage
defaultMessage="You can use a {asterisk} as a wildcard in your index pattern."
id="indexPatternManagement.createIndexPattern.step.indexPattern.allowLabel"
values={
Object {
"asterisk": <strong>
*
</strong>,
}
<React.Fragment>
<FormattedMessage
defaultMessage="Use an asterisk ({asterisk}) to match multiple indices."
id="indexPatternManagement.createIndexPattern.step.indexPattern.allowLabel"
values={
Object {
"asterisk": <strong>
*
</strong>,
}
/>
</p>
<p>
<FormattedMessage
defaultMessage="You can't use spaces or the characters {characterList}."
id="indexPatternManagement.createIndexPattern.step.indexPattern.disallowLabel"
values={
Object {
"characterList": <strong>
%
</strong>,
}
}
/>
<FormattedMessage
defaultMessage="Spaces and the characters {characterList} are not allowed."
id="indexPatternManagement.createIndexPattern.step.indexPattern.disallowLabel"
values={
Object {
"characterList": <strong>
%
</strong>,
}
/>
</p>
</div>
}
/>
</React.Fragment>
}
isInvalid={false}
label={
<FormattedMessage
defaultMessage="Index pattern"
defaultMessage="Index pattern name"
id="indexPatternManagement.createIndexPattern.step.indexPatternLabel"
values={Object {}}
/>
@ -183,6 +179,7 @@ exports[`Header should render normally 1`] = `
>
<EuiFieldText
data-test-subj="createIndexPatternNameInput"
fullWidth={true}
isInvalid={false}
name="indexPattern"
onChange={[Function]}
@ -195,18 +192,29 @@ exports[`Header should render normally 1`] = `
<EuiFlexItem
grow={false}
>
<EuiButton
data-test-subj="createIndexPatternGoToStep2Button"
iconType="arrowRight"
isDisabled={false}
onClick={[Function]}
<EuiFormRow
describedByIds={Array []}
display="row"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={true}
labelType="label"
>
<FormattedMessage
defaultMessage="Next step"
id="indexPatternManagement.createIndexPattern.step.nextStepButton"
values={Object {}}
/>
</EuiButton>
<EuiButton
data-test-subj="createIndexPatternGoToStep2Button"
fill={true}
iconSide="right"
iconType="arrowRight"
isDisabled={false}
onClick={[Function]}
>
<FormattedMessage
defaultMessage="Next step"
id="indexPatternManagement.createIndexPattern.step.nextStepButton"
values={Object {}}
/>
</EuiButton>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
</div>

View file

@ -32,6 +32,8 @@ describe('Header', () => {
onQueryChanged={() => {}}
goToNextStep={() => {}}
isNextStepDisabled={false}
onChangeIncludingSystemIndices={() => {}}
isIncludingSystemIndices={false}
/>
);
@ -48,6 +50,8 @@ describe('Header', () => {
onQueryChanged={() => {}}
goToNextStep={() => {}}
isNextStepDisabled={true}
onChangeIncludingSystemIndices={() => {}}
isIncludingSystemIndices={false}
/>
);

View file

@ -28,6 +28,8 @@ import {
EuiForm,
EuiFormRow,
EuiFieldText,
EuiSwitchEvent,
EuiSwitch,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@ -41,6 +43,9 @@ interface HeaderProps {
onQueryChanged: (e: React.ChangeEvent<HTMLInputElement>) => void;
goToNextStep: (query: string) => void;
isNextStepDisabled: boolean;
showSystemIndices?: boolean;
onChangeIncludingSystemIndices: (event: EuiSwitchEvent) => void;
isIncludingSystemIndices: boolean;
}
export const Header: React.FC<HeaderProps> = ({
@ -51,6 +56,9 @@ export const Header: React.FC<HeaderProps> = ({
onQueryChanged,
goToNextStep,
isNextStepDisabled,
showSystemIndices = false,
onChangeIncludingSystemIndices,
isIncludingSystemIndices,
...rest
}) => (
<div {...rest}>
@ -63,35 +71,32 @@ export const Header: React.FC<HeaderProps> = ({
</h2>
</EuiTitle>
<EuiSpacer size="m" />
<EuiFlexGroup justifyContent="spaceBetween" alignItems="flexEnd">
<EuiFlexItem grow={false}>
<EuiFlexGroup>
<EuiFlexItem>
<EuiForm isInvalid={isInputInvalid}>
<EuiFormRow
fullWidth
label={
<FormattedMessage
id="indexPatternManagement.createIndexPattern.step.indexPatternLabel"
defaultMessage="Index pattern"
defaultMessage="Index pattern name"
/>
}
isInvalid={isInputInvalid}
error={errors}
helpText={
<div>
<p>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.step.indexPattern.allowLabel"
defaultMessage="You can use a {asterisk} as a wildcard in your index pattern."
values={{ asterisk: <strong>*</strong> }}
/>
</p>
<p>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.step.indexPattern.disallowLabel"
defaultMessage="You can't use spaces or the characters {characterList}."
values={{ characterList: <strong>{characterList}</strong> }}
/>
</p>
</div>
<>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.step.indexPattern.allowLabel"
defaultMessage="Use an asterisk ({asterisk}) to match multiple indices."
values={{ asterisk: <strong>*</strong> }}
/>{' '}
<FormattedMessage
id="indexPatternManagement.createIndexPattern.step.indexPattern.disallowLabel"
defaultMessage="Spaces and the characters {characterList} are not allowed."
values={{ characterList: <strong>{characterList}</strong> }}
/>
</>
}
>
<EuiFieldText
@ -106,22 +111,43 @@ export const Header: React.FC<HeaderProps> = ({
isInvalid={isInputInvalid}
onChange={onQueryChanged}
data-test-subj="createIndexPatternNameInput"
fullWidth
/>
</EuiFormRow>
{showSystemIndices ? (
<EuiFormRow>
<EuiSwitch
label={
<FormattedMessage
id="indexPatternManagement.createIndexPattern.includeSystemIndicesToggleSwitchLabel"
defaultMessage="Include system and hidden indices"
/>
}
id="checkboxShowSystemIndices"
checked={isIncludingSystemIndices}
onChange={onChangeIncludingSystemIndices}
/>
</EuiFormRow>
) : null}
</EuiForm>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
iconType="arrowRight"
onClick={() => goToNextStep(query)}
isDisabled={isNextStepDisabled}
data-test-subj="createIndexPatternGoToStep2Button"
>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.step.nextStepButton"
defaultMessage="Next step"
/>
</EuiButton>
<EuiFormRow hasEmptyLabelSpace>
<EuiButton
fill
iconSide="right"
iconType="arrowRight"
onClick={() => goToNextStep(query)}
isDisabled={isNextStepDisabled}
data-test-subj="createIndexPatternGoToStep2Button"
>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.step.nextStepButton"
defaultMessage="Next step"
/>
</EuiButton>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
</div>

View file

@ -20,11 +20,12 @@
import React from 'react';
import { IndicesList } from '../indices_list';
import { shallow } from 'enzyme';
import { MatchedItem } from '../../../../types';
const indices = [
const indices = ([
{ name: 'kibana', tags: [] },
{ name: 'es', tags: [] },
];
] as unknown) as MatchedItem[];
describe('IndicesList', () => {
it('should render normally', () => {

View file

@ -39,10 +39,10 @@ import { Pager } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { PER_PAGE_INCREMENTS } from '../../../../constants';
import { MatchedIndex, Tag } from '../../../../types';
import { MatchedItem, Tag } from '../../../../types';
interface IndicesListProps {
indices: MatchedIndex[];
indices: MatchedItem[];
query: string;
}
@ -187,7 +187,7 @@ export class IndicesList extends React.Component<IndicesListProps, IndicesListSt
<EuiTableRowCell>
{index.tags.map((tag: Tag) => {
return (
<EuiBadge key={`index_${key}_tag_${tag.key}`} color="primary">
<EuiBadge key={`index_${key}_tag_${tag.key}`} color={tag.color}>
{tag.name}
</EuiBadge>
);

View file

@ -1,67 +1,44 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`StatusMessage should render with exact matches 1`] = `
<EuiText
<EuiCallOut
color="success"
data-test-subj="createIndexPatternStatusMessage"
iconType="check"
size="s"
>
<EuiTextColor
color="secondary"
>
<EuiIcon
type="check"
/>
title={
<span>
 
<FormattedMessage
defaultMessage="{strongSuccess} Your index pattern matches {strongIndices}."
defaultMessage="Your index pattern matches {sourceCount} {sourceCount, plural, one {source} other {sources} }."
id="indexPatternManagement.createIndexPattern.step.status.successLabel.successDetail"
values={
Object {
"strongIndices": <strong>
<FormattedMessage
defaultMessage="{indicesLength, plural, one {# index} other {# indices}}"
id="indexPatternManagement.createIndexPattern.step.status.successLabel.strongIndicesLabel"
values={
Object {
"indicesLength": 1,
}
}
/>
</strong>,
"strongSuccess": <strong>
<FormattedMessage
defaultMessage="Success!"
id="indexPatternManagement.createIndexPattern.step.status.successLabel.strongSuccessLabel"
values={Object {}}
/>
</strong>,
"sourceCount": 1,
}
}
/>
</span>
</EuiTextColor>
</EuiText>
}
/>
`;
exports[`StatusMessage should render with no partial matches 1`] = `
<EuiText
<EuiCallOut
color="warning"
data-test-subj="createIndexPatternStatusMessage"
size="s"
>
<EuiTextColor
color="default"
>
title={
<span>
<FormattedMessage
defaultMessage="The index pattern you've entered doesn't match any indices. You can match {indicesLength, plural, one {your} other {any of your}} {strongIndices}, below."
defaultMessage="The index pattern you've entered doesn't match any indices. You can match {indicesLength, plural, one {your} other {any of your} } {strongIndices}, below."
id="indexPatternManagement.createIndexPattern.step.status.notMatchLabel.notMatchDetail"
values={
Object {
"indicesLength": 2,
"strongIndices": <strong>
<FormattedMessage
defaultMessage="{indicesLength, plural, one {# index} other {# indices}}"
defaultMessage="{indicesLength, plural, one {# index} other {# indices} }"
id="indexPatternManagement.createIndexPattern.step.status.notMatchLabel.allIndicesLabel"
values={
Object {
@ -74,28 +51,26 @@ exports[`StatusMessage should render with no partial matches 1`] = `
}
/>
</span>
</EuiTextColor>
</EuiText>
}
/>
`;
exports[`StatusMessage should render with partial matches 1`] = `
<EuiText
<EuiCallOut
color="primary"
data-test-subj="createIndexPatternStatusMessage"
size="s"
>
<EuiTextColor
color="default"
>
title={
<span>
<FormattedMessage
defaultMessage="Your index pattern doesn't match any indices, but you have {strongIndices} which {matchedIndicesLength, plural, one {looks} other {look}} similar."
defaultMessage="Your index pattern doesn't match any indices, but you have {strongIndices} which {matchedIndicesLength, plural, one {looks} other {look} } similar."
id="indexPatternManagement.createIndexPattern.step.status.partialMatchLabel.partialMatchDetail"
values={
Object {
"matchedIndicesLength": 1,
"strongIndices": <strong>
<FormattedMessage
defaultMessage="{matchedIndicesLength, plural, one {# index} other {# indices}}"
defaultMessage="{matchedIndicesLength, plural, one {index} other {# indices} }"
id="indexPatternManagement.createIndexPattern.step.status.partialMatchLabel.strongIndicesLabel"
values={
Object {
@ -108,44 +83,37 @@ exports[`StatusMessage should render with partial matches 1`] = `
}
/>
</span>
</EuiTextColor>
</EuiText>
}
/>
`;
exports[`StatusMessage should render without a query 1`] = `
<EuiText
<EuiCallOut
color="primary"
data-test-subj="createIndexPatternStatusMessage"
size="s"
>
<EuiTextColor
color="default"
>
title={
<span>
<FormattedMessage
defaultMessage="Your index pattern can match any of your {strongIndices}, below."
defaultMessage="Your index pattern can match {sourceCount, plural, one {your # source} other {any of your # sources} }."
id="indexPatternManagement.createIndexPattern.step.status.matchAnyLabel.matchAnyDetail"
values={
Object {
"strongIndices": <strong>
2
indices
</strong>,
"sourceCount": 2,
}
}
/>
</span>
</EuiTextColor>
</EuiText>
}
/>
`;
exports[`StatusMessage should show that no indices exist 1`] = `
<EuiText
<EuiCallOut
color="primary"
data-test-subj="createIndexPatternStatusMessage"
size="s"
>
<EuiTextColor
color="default"
>
title={
<span>
<FormattedMessage
defaultMessage="No Elasticsearch indices match your pattern."
@ -153,18 +121,16 @@ exports[`StatusMessage should show that no indices exist 1`] = `
values={Object {}}
/>
</span>
</EuiTextColor>
</EuiText>
}
/>
`;
exports[`StatusMessage should show that system indices exist 1`] = `
<EuiText
<EuiCallOut
color="primary"
data-test-subj="createIndexPatternStatusMessage"
size="s"
>
<EuiTextColor
color="default"
>
title={
<span>
<FormattedMessage
defaultMessage="No Elasticsearch indices match your pattern."
@ -172,6 +138,6 @@ exports[`StatusMessage should show that system indices exist 1`] = `
values={Object {}}
/>
</span>
</EuiTextColor>
</EuiText>
}
/>
`;

View file

@ -20,18 +20,19 @@
import React from 'react';
import { StatusMessage } from '../status_message';
import { shallow } from 'enzyme';
import { MatchedItem } from '../../../../types';
const tagsPartial = {
tags: [],
};
const matchedIndices = {
allIndices: [
allIndices: ([
{ name: 'kibana', ...tagsPartial },
{ name: 'es', ...tagsPartial },
],
exactMatchedIndices: [],
partialMatchedIndices: [{ name: 'kibana', ...tagsPartial }],
] as unknown) as MatchedItem[],
exactMatchedIndices: [] as MatchedItem[],
partialMatchedIndices: ([{ name: 'kibana', ...tagsPartial }] as unknown) as MatchedItem[],
};
describe('StatusMessage', () => {
@ -51,7 +52,7 @@ describe('StatusMessage', () => {
it('should render with exact matches', () => {
const localMatchedIndices = {
...matchedIndices,
exactMatchedIndices: [{ name: 'kibana', ...tagsPartial }],
exactMatchedIndices: ([{ name: 'kibana', ...tagsPartial }] as unknown) as MatchedItem[],
};
const component = shallow(

View file

@ -19,16 +19,17 @@
import React from 'react';
import { EuiText, EuiTextColor, EuiIcon } from '@elastic/eui';
import { EuiCallOut } from '@elastic/eui';
import { EuiIconType } from '@elastic/eui/src/components/icon/icon';
import { FormattedMessage } from '@kbn/i18n/react';
import { MatchedIndex } from '../../../../types';
import { MatchedItem } from '../../../../types';
interface StatusMessageProps {
matchedIndices: {
allIndices: MatchedIndex[];
exactMatchedIndices: MatchedIndex[];
partialMatchedIndices: MatchedIndex[];
allIndices: MatchedItem[];
exactMatchedIndices: MatchedItem[];
partialMatchedIndices: MatchedItem[];
};
isIncludingSystemIndices: boolean;
query: string;
@ -41,23 +42,26 @@ export const StatusMessage: React.FC<StatusMessageProps> = ({
query,
showSystemIndices,
}) => {
let statusIcon;
let statusIcon: EuiIconType | undefined;
let statusMessage;
let statusColor: 'default' | 'secondary' | undefined;
let statusColor: 'primary' | 'success' | 'warning' | undefined;
const allIndicesLength = allIndices.length;
if (query.length === 0) {
statusIcon = null;
statusColor = 'default';
statusIcon = undefined;
statusColor = 'primary';
if (allIndicesLength > 1) {
if (allIndicesLength >= 1) {
statusMessage = (
<span>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.step.status.matchAnyLabel.matchAnyDetail"
defaultMessage="Your index pattern can match any of your {strongIndices}, below."
values={{ strongIndices: <strong>{allIndicesLength} indices</strong> }}
defaultMessage="Your index pattern can match {sourceCount, plural,
one {your # source}
other {any of your # sources}
}."
values={{ sourceCount: allIndicesLength }}
/>
</span>
);
@ -66,8 +70,7 @@ export const StatusMessage: React.FC<StatusMessageProps> = ({
<span>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.step.status.noSystemIndicesWithPromptLabel"
defaultMessage="No Elasticsearch indices match your pattern. To view the matching system indices, toggle the switch in
the upper right."
defaultMessage="No Elasticsearch indices match your pattern. To view the matching system indices, toggle the switch above."
/>
</span>
);
@ -83,51 +86,44 @@ export const StatusMessage: React.FC<StatusMessageProps> = ({
}
} else if (exactMatchedIndices.length) {
statusIcon = 'check';
statusColor = 'secondary';
statusColor = 'success';
statusMessage = (
<span>
&nbsp;
<FormattedMessage
id="indexPatternManagement.createIndexPattern.step.status.successLabel.successDetail"
defaultMessage="{strongSuccess} Your index pattern matches {strongIndices}."
defaultMessage="Your index pattern matches {sourceCount} {sourceCount, plural,
one {source}
other {sources}
}."
values={{
strongSuccess: (
<strong>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.step.status.successLabel.strongSuccessLabel"
defaultMessage="Success!"
/>
</strong>
),
strongIndices: (
<strong>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.step.status.successLabel.strongIndicesLabel"
defaultMessage="{indicesLength, plural, one {# index} other {# indices}}"
values={{ indicesLength: exactMatchedIndices.length }}
/>
</strong>
),
sourceCount: exactMatchedIndices.length,
}}
/>
</span>
);
} else if (partialMatchedIndices.length) {
statusIcon = null;
statusColor = 'default';
statusIcon = undefined;
statusColor = 'primary';
statusMessage = (
<span>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.step.status.partialMatchLabel.partialMatchDetail"
defaultMessage="Your index pattern doesn't match any indices, but you have {strongIndices} which
{matchedIndicesLength, plural, one {looks} other {look}} similar."
{matchedIndicesLength, plural,
one {looks}
other {look}
} similar."
values={{
matchedIndicesLength: partialMatchedIndices.length,
strongIndices: (
<strong>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.step.status.partialMatchLabel.strongIndicesLabel"
defaultMessage="{matchedIndicesLength, plural, one {# index} other {# indices}}"
defaultMessage="{matchedIndicesLength, plural,
one {index}
other {# indices}
}"
values={{ matchedIndicesLength: partialMatchedIndices.length }}
/>
</strong>
@ -137,20 +133,26 @@ export const StatusMessage: React.FC<StatusMessageProps> = ({
</span>
);
} else if (allIndicesLength) {
statusIcon = null;
statusColor = 'default';
statusIcon = undefined;
statusColor = 'warning';
statusMessage = (
<span>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.step.status.notMatchLabel.notMatchDetail"
defaultMessage="The index pattern you've entered doesn't match any indices.
You can match {indicesLength, plural, one {your} other {any of your}} {strongIndices}, below."
You can match {indicesLength, plural,
one {your}
other {any of your}
} {strongIndices}, below."
values={{
strongIndices: (
<strong>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.step.status.notMatchLabel.allIndicesLabel"
defaultMessage="{indicesLength, plural, one {# index} other {# indices}}"
defaultMessage="{indicesLength, plural,
one {# index}
other {# indices}
}"
values={{ indicesLength: allIndicesLength }}
/>
</strong>
@ -163,11 +165,12 @@ export const StatusMessage: React.FC<StatusMessageProps> = ({
}
return (
<EuiText size="s" data-test-subj="createIndexPatternStatusMessage">
<EuiTextColor color={statusColor}>
{statusIcon ? <EuiIcon type={statusIcon} /> : null}
{statusMessage}
</EuiTextColor>
</EuiText>
<EuiCallOut
size="s"
color={statusColor}
data-test-subj="createIndexPatternStatusMessage"
iconType={statusIcon}
title={statusMessage}
/>
);
};

View file

@ -19,7 +19,7 @@
import React from 'react';
import { SavedObjectsFindResponsePublic } from 'kibana/public';
import { StepIndexPattern } from '../step_index_pattern';
import { StepIndexPattern, canPreselectTimeField } from './step_index_pattern';
import { Header } from './components/header';
import { IndexPatternCreationConfig } from '../../../../../../../plugins/index_pattern_management/public';
import { mockManagementPlugin } from '../../../../mocks';
@ -38,16 +38,16 @@ const mockIndexPatternCreationType = new IndexPatternCreationConfig({
jest.mock('../../lib/get_indices', () => ({
getIndices: ({}, {}, query: string) => {
if (query.startsWith('e')) {
return [{ name: 'es' }];
return [{ name: 'es', item: {} }];
}
return [{ name: 'kibana' }];
return [{ name: 'kibana', item: {} }];
},
}));
const allIndices = [
{ name: 'kibana', tags: [] },
{ name: 'es', tags: [] },
{ name: 'kibana', tags: [], item: {} },
{ name: 'es', tags: [], item: {} },
];
const goToNextStep = () => {};
@ -205,4 +205,53 @@ describe('StepIndexPattern', () => {
await new Promise((resolve) => process.nextTick(resolve));
expect(component.state('exactMatchedIndices')).toEqual([]);
});
it('it can preselect time field', async () => {
const dataStream1 = {
name: 'data stream 1',
tags: [],
item: { name: 'data stream 1', backing_indices: [], timestamp_field: 'timestamp_field' },
};
const dataStream2 = {
name: 'data stream 2',
tags: [],
item: { name: 'data stream 2', backing_indices: [], timestamp_field: 'timestamp_field' },
};
const differentDataStream = {
name: 'different data stream',
tags: [],
item: { name: 'different data stream 2', backing_indices: [], timestamp_field: 'x' },
};
const index = {
name: 'index',
tags: [],
item: {
name: 'index',
},
};
const alias = {
name: 'alias',
tags: [],
item: {
name: 'alias',
indices: [],
},
};
expect(canPreselectTimeField([index])).toEqual(undefined);
expect(canPreselectTimeField([alias])).toEqual(undefined);
expect(canPreselectTimeField([index, alias, dataStream1])).toEqual(undefined);
expect(canPreselectTimeField([dataStream1])).toEqual('timestamp_field');
expect(canPreselectTimeField([dataStream1, dataStream2])).toEqual('timestamp_field');
expect(canPreselectTimeField([dataStream1, dataStream2, differentDataStream])).toEqual(
undefined
);
});
});

View file

@ -18,7 +18,7 @@
*/
import React, { Component } from 'react';
import { EuiPanel, EuiSpacer, EuiCallOut } from '@elastic/eui';
import { EuiSpacer, EuiCallOut, EuiSwitchEvent } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
@ -26,7 +26,6 @@ import {
IndexPatternAttributes,
UI_SETTINGS,
} from '../../../../../../../plugins/data/public';
import { MAX_SEARCH_SIZE } from '../../constants';
import {
getIndices,
containsIllegalCharacters,
@ -40,20 +39,20 @@ import { IndicesList } from './components/indices_list';
import { Header } from './components/header';
import { context as contextType } from '../../../../../../kibana_react/public';
import { IndexPatternCreationConfig } from '../../../../../../../plugins/index_pattern_management/public';
import { MatchedIndex } from '../../types';
import { MatchedItem } from '../../types';
import { IndexPatternManagmentContextValue } from '../../../../types';
interface StepIndexPatternProps {
allIndices: MatchedIndex[];
isIncludingSystemIndices: boolean;
allIndices: MatchedItem[];
indexPatternCreationType: IndexPatternCreationConfig;
goToNextStep: (query: string) => void;
goToNextStep: (query: string, timestampField?: string) => void;
initialQuery?: string;
showSystemIndices: boolean;
}
interface StepIndexPatternState {
partialMatchedIndices: MatchedIndex[];
exactMatchedIndices: MatchedIndex[];
partialMatchedIndices: MatchedItem[];
exactMatchedIndices: MatchedItem[];
isLoadingIndices: boolean;
existingIndexPatterns: string[];
indexPatternExists: boolean;
@ -61,8 +60,35 @@ interface StepIndexPatternState {
appendedWildcard: boolean;
showingIndexPatternQueryErrors: boolean;
indexPatternName: string;
isIncludingSystemIndices: boolean;
}
export const canPreselectTimeField = (indices: MatchedItem[]) => {
const preselectStatus = indices.reduce(
(
{ canPreselect, timeFieldName }: { canPreselect: boolean; timeFieldName?: string },
matchedItem
) => {
const dataStreamItem = matchedItem.item;
const dataStreamTimestampField = dataStreamItem.timestamp_field;
const isDataStream = !!dataStreamItem.timestamp_field;
const timestampFieldMatches =
timeFieldName === undefined || timeFieldName === dataStreamTimestampField;
return {
canPreselect: canPreselect && isDataStream && timestampFieldMatches,
timeFieldName: dataStreamTimestampField || timeFieldName,
};
},
{
canPreselect: true,
timeFieldName: undefined,
}
);
return preselectStatus.canPreselect ? preselectStatus.timeFieldName : undefined;
};
export class StepIndexPattern extends Component<StepIndexPatternProps, StepIndexPatternState> {
static contextType = contextType;
@ -78,9 +104,9 @@ export class StepIndexPattern extends Component<StepIndexPatternProps, StepIndex
appendedWildcard: false,
showingIndexPatternQueryErrors: false,
indexPatternName: '',
isIncludingSystemIndices: false,
};
ILLEGAL_CHARACTERS = [...indexPatterns.ILLEGAL_CHARACTERS];
lastQuery: string | undefined;
constructor(props: StepIndexPatternProps, context: IndexPatternManagmentContextValue) {
super(props, context);
@ -91,6 +117,8 @@ export class StepIndexPattern extends Component<StepIndexPatternProps, StepIndex
this.state.indexPatternName = indexPatternCreationType.getIndexPatternName();
}
lastQuery = '';
async UNSAFE_componentWillMount() {
this.fetchExistingIndexPatterns();
if (this.state.query) {
@ -129,10 +157,10 @@ export class StepIndexPattern extends Component<StepIndexPatternProps, StepIndex
if (query.endsWith('*')) {
const exactMatchedIndices = await ensureMinimumTime(
getIndices(
this.context.services.data.search.__LEGACY.esClient,
this.context.services.http,
indexPatternCreationType,
query,
MAX_SEARCH_SIZE
this.state.isIncludingSystemIndices
)
);
// If the search changed, discard this state
@ -145,16 +173,16 @@ export class StepIndexPattern extends Component<StepIndexPatternProps, StepIndex
const [partialMatchedIndices, exactMatchedIndices] = await ensureMinimumTime([
getIndices(
this.context.services.data.search.__LEGACY.esClient,
this.context.services.http,
indexPatternCreationType,
`${query}*`,
MAX_SEARCH_SIZE
this.state.isIncludingSystemIndices
),
getIndices(
this.context.services.data.search.__LEGACY.esClient,
this.context.services.http,
indexPatternCreationType,
query,
MAX_SEARCH_SIZE
this.state.isIncludingSystemIndices
),
]);
@ -202,12 +230,12 @@ export class StepIndexPattern extends Component<StepIndexPatternProps, StepIndex
}
renderStatusMessage(matchedIndices: {
allIndices: MatchedIndex[];
exactMatchedIndices: MatchedIndex[];
partialMatchedIndices: MatchedIndex[];
allIndices: MatchedItem[];
exactMatchedIndices: MatchedItem[];
partialMatchedIndices: MatchedItem[];
}) {
const { indexPatternCreationType, isIncludingSystemIndices } = this.props;
const { query, isLoadingIndices, indexPatternExists } = this.state;
const { indexPatternCreationType } = this.props;
const { query, isLoadingIndices, indexPatternExists, isIncludingSystemIndices } = this.state;
if (isLoadingIndices || indexPatternExists) {
return null;
@ -227,8 +255,8 @@ export class StepIndexPattern extends Component<StepIndexPatternProps, StepIndex
visibleIndices,
allIndices,
}: {
visibleIndices: MatchedIndex[];
allIndices: MatchedIndex[];
visibleIndices: MatchedItem[];
allIndices: MatchedItem[];
}) {
const { query, isLoadingIndices, indexPatternExists } = this.state;
@ -268,13 +296,14 @@ export class StepIndexPattern extends Component<StepIndexPatternProps, StepIndex
);
}
renderHeader({ exactMatchedIndices: indices }: { exactMatchedIndices: MatchedIndex[] }) {
renderHeader({ exactMatchedIndices: indices }: { exactMatchedIndices: MatchedItem[] }) {
const { goToNextStep, indexPatternCreationType } = this.props;
const {
query,
showingIndexPatternQueryErrors,
indexPatternExists,
indexPatternName,
isIncludingSystemIndices,
} = this.state;
let containsErrors = false;
@ -316,15 +345,24 @@ export class StepIndexPattern extends Component<StepIndexPatternProps, StepIndex
characterList={characterList}
query={query}
onQueryChanged={this.onQueryChanged}
goToNextStep={goToNextStep}
goToNextStep={() => goToNextStep(query, canPreselectTimeField(indices))}
isNextStepDisabled={isNextStepDisabled}
onChangeIncludingSystemIndices={this.onChangeIncludingSystemIndices}
isIncludingSystemIndices={isIncludingSystemIndices}
showSystemIndices={this.props.showSystemIndices}
/>
);
}
onChangeIncludingSystemIndices = (event: EuiSwitchEvent) => {
this.setState({ isIncludingSystemIndices: event.target.checked }, () =>
this.fetchIndices(this.state.query)
);
};
render() {
const { isIncludingSystemIndices, allIndices } = this.props;
const { partialMatchedIndices, exactMatchedIndices } = this.state;
const { allIndices } = this.props;
const { partialMatchedIndices, exactMatchedIndices, isIncludingSystemIndices } = this.state;
const matchedIndices = getMatchedIndices(
allIndices,
@ -334,15 +372,15 @@ export class StepIndexPattern extends Component<StepIndexPatternProps, StepIndex
);
return (
<EuiPanel paddingSize="l">
<>
{this.renderHeader(matchedIndices)}
<EuiSpacer size="s" />
<EuiSpacer />
{this.renderLoadingState()}
{this.renderIndexPatternExists()}
{this.renderStatusMessage(matchedIndices)}
<EuiSpacer size="s" />
<EuiSpacer />
{this.renderList(matchedIndices)}
</EuiPanel>
</>
);
}
}

View file

@ -17,9 +17,7 @@ exports[`StepTimeField should enable the action button if the user decides to no
`;
exports[`StepTimeField should render "Custom index pattern ID already exists" when error is "Conflict" 1`] = `
<EuiPanel
paddingSize="l"
>
<Fragment>
<Header
indexPattern="ki*"
indexPatternName="name"
@ -34,9 +32,7 @@ exports[`StepTimeField should render "Custom index pattern ID already exists" wh
onTimeFieldChanged={[Function]}
timeFieldOptions={Array []}
/>
<EuiSpacer
size="s"
/>
<EuiHorizontalRule />
<AdvancedOptions
indexPatternId=""
isVisible={false}
@ -73,38 +69,45 @@ exports[`StepTimeField should render "Custom index pattern ID already exists" wh
goToPreviousStep={[Function]}
submittable={true}
/>
</EuiPanel>
</Fragment>
`;
exports[`StepTimeField should render a loading state when creating the index pattern 1`] = `
<EuiPanel>
<EuiFlexGroup
alignItems="center"
<EuiFlexGroup
alignItems="center"
direction="column"
gutterSize="s"
justifyContent="center"
>
<EuiFlexItem
grow={false}
>
<EuiFlexItem
grow={false}
<EuiTitle
size="s"
>
<EuiLoadingSpinner />
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiText>
<h3
className="eui-textCenter"
>
<FormattedMessage
defaultMessage="Creating index pattern…"
id="indexPatternManagement.createIndexPattern.stepTime.creatingLabel"
values={Object {}}
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiLoadingSpinner
size="l"
/>
</EuiFlexItem>
</EuiFlexGroup>
`;
exports[`StepTimeField should render a selected timeField 1`] = `
<EuiPanel
paddingSize="l"
>
<Fragment>
<Header
indexPattern="ki*"
indexPatternName="name"
@ -137,9 +140,7 @@ exports[`StepTimeField should render a selected timeField 1`] = `
]
}
/>
<EuiSpacer
size="s"
/>
<EuiHorizontalRule />
<AdvancedOptions
indexPatternId=""
isVisible={false}
@ -154,13 +155,11 @@ exports[`StepTimeField should render a selected timeField 1`] = `
goToPreviousStep={[Function]}
submittable={true}
/>
</EuiPanel>
</Fragment>
`;
exports[`StepTimeField should render advanced options 1`] = `
<EuiPanel
paddingSize="l"
>
<Fragment>
<Header
indexPattern="ki*"
indexPatternName="name"
@ -175,9 +174,7 @@ exports[`StepTimeField should render advanced options 1`] = `
onTimeFieldChanged={[Function]}
timeFieldOptions={Array []}
/>
<EuiSpacer
size="s"
/>
<EuiHorizontalRule />
<AdvancedOptions
indexPatternId=""
isVisible={false}
@ -192,13 +189,11 @@ exports[`StepTimeField should render advanced options 1`] = `
goToPreviousStep={[Function]}
submittable={true}
/>
</EuiPanel>
</Fragment>
`;
exports[`StepTimeField should render advanced options with an index pattern id 1`] = `
<EuiPanel
paddingSize="l"
>
<Fragment>
<Header
indexPattern="ki*"
indexPatternName="name"
@ -213,9 +208,7 @@ exports[`StepTimeField should render advanced options with an index pattern id 1
onTimeFieldChanged={[Function]}
timeFieldOptions={Array []}
/>
<EuiSpacer
size="s"
/>
<EuiHorizontalRule />
<AdvancedOptions
indexPatternId="foobar"
isVisible={false}
@ -230,13 +223,11 @@ exports[`StepTimeField should render advanced options with an index pattern id 1
goToPreviousStep={[Function]}
submittable={true}
/>
</EuiPanel>
</Fragment>
`;
exports[`StepTimeField should render any error message 1`] = `
<EuiPanel
paddingSize="l"
>
<Fragment>
<Header
indexPattern="ki*"
indexPatternName="name"
@ -251,9 +242,7 @@ exports[`StepTimeField should render any error message 1`] = `
onTimeFieldChanged={[Function]}
timeFieldOptions={Array []}
/>
<EuiSpacer
size="s"
/>
<EuiHorizontalRule />
<AdvancedOptions
indexPatternId=""
isVisible={false}
@ -286,13 +275,11 @@ exports[`StepTimeField should render any error message 1`] = `
goToPreviousStep={[Function]}
submittable={true}
/>
</EuiPanel>
</Fragment>
`;
exports[`StepTimeField should render normally 1`] = `
<EuiPanel
paddingSize="l"
>
<Fragment>
<Header
indexPattern="ki*"
indexPatternName="name"
@ -307,9 +294,7 @@ exports[`StepTimeField should render normally 1`] = `
onTimeFieldChanged={[Function]}
timeFieldOptions={Array []}
/>
<EuiSpacer
size="s"
/>
<EuiHorizontalRule />
<AdvancedOptions
indexPatternId=""
isVisible={false}
@ -324,13 +309,11 @@ exports[`StepTimeField should render normally 1`] = `
goToPreviousStep={[Function]}
submittable={true}
/>
</EuiPanel>
</Fragment>
`;
exports[`StepTimeField should render timeFields 1`] = `
<EuiPanel
paddingSize="l"
>
<Fragment>
<Header
indexPattern="ki*"
indexPatternName="name"
@ -362,9 +345,7 @@ exports[`StepTimeField should render timeFields 1`] = `
]
}
/>
<EuiSpacer
size="s"
/>
<EuiHorizontalRule />
<AdvancedOptions
indexPatternId=""
isVisible={false}
@ -379,5 +360,5 @@ exports[`StepTimeField should render timeFields 1`] = `
goToPreviousStep={[Function]}
submittable={false}
/>
</EuiPanel>
</Fragment>
`;

View file

@ -16,21 +16,10 @@ exports[`Header should render normally 1`] = `
<EuiSpacer
size="m"
/>
<EuiText
color="subdued"
>
<FormattedMessage
defaultMessage="You've defined {indexPattern} as your {indexPatternName}. Now you can specify some settings before we create it."
id="indexPatternManagement.createIndexPattern.stepTimeLabel"
values={
Object {
"indexPattern": <strong>
ki*
</strong>,
"indexPatternName": "ki*",
}
}
/>
<EuiText>
<strong>
ki*
</strong>
</EuiText>
</div>
`;

View file

@ -39,15 +39,8 @@ export const Header: React.FC<HeaderProps> = ({ indexPattern, indexPatternName }
</h2>
</EuiTitle>
<EuiSpacer size="m" />
<EuiText color="subdued">
<FormattedMessage
id="indexPatternManagement.createIndexPattern.stepTimeLabel"
defaultMessage="You've defined {indexPattern} as your {indexPatternName}. Now you can specify some settings before we create it."
values={{
indexPattern: <strong>{indexPattern}</strong>,
indexPatternName,
}}
/>
<EuiText>
<strong>{indexPattern}</strong>
</EuiText>
</div>
);

View file

@ -2,55 +2,33 @@
exports[`TimeField should render a loading state 1`] = `
<EuiForm>
<EuiText>
<p>
<FormattedMessage
defaultMessage="Select a primary time field for use with the global time filter."
id="indexPatternManagement.createIndexPattern.stepTime.timeDescription"
values={Object {}}
/>
</p>
</EuiText>
<EuiSpacer />
<EuiFormRow
describedByIds={Array []}
display="row"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
helpText={
<div>
<p>
<FormattedMessage
defaultMessage="The Time Filter will use this field to filter your data by time."
id="indexPatternManagement.createIndexPattern.stepTime.fieldLabel"
values={Object {}}
/>
</p>
<p>
<FormattedMessage
defaultMessage="You can choose not to have a time field, but you will not be able to narrow down your data by a time range."
id="indexPatternManagement.createIndexPattern.stepTime.fieldWarningLabel"
values={Object {}}
/>
</p>
</div>
}
label={
<EuiFlexGroup
alignItems="center"
gutterSize="xs"
justifyContent="spaceBetween"
>
<EuiFlexItem
grow={false}
>
<span>
<FormattedMessage
defaultMessage="Time Filter field name"
id="indexPatternManagement.createIndexPattern.stepTime.fieldHeader"
values={Object {}}
/>
</span>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiLoadingSpinner
size="s"
/>
</EuiFlexItem>
</EuiFlexGroup>
<FormattedMessage
defaultMessage="Time field"
id="indexPatternManagement.createIndexPattern.stepTime.fieldLabel"
values={Object {}}
/>
}
labelAppend={
<EuiLoadingSpinner
size="s"
/>
}
labelType="label"
>
@ -73,62 +51,43 @@ exports[`TimeField should render a loading state 1`] = `
exports[`TimeField should render a selected time field 1`] = `
<EuiForm>
<EuiText>
<p>
<FormattedMessage
defaultMessage="Select a primary time field for use with the global time filter."
id="indexPatternManagement.createIndexPattern.stepTime.timeDescription"
values={Object {}}
/>
</p>
</EuiText>
<EuiSpacer />
<EuiFormRow
describedByIds={Array []}
display="row"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
helpText={
<div>
<p>
<FormattedMessage
defaultMessage="The Time Filter will use this field to filter your data by time."
id="indexPatternManagement.createIndexPattern.stepTime.fieldLabel"
values={Object {}}
/>
</p>
<p>
<FormattedMessage
defaultMessage="You can choose not to have a time field, but you will not be able to narrow down your data by a time range."
id="indexPatternManagement.createIndexPattern.stepTime.fieldWarningLabel"
values={Object {}}
/>
</p>
</div>
}
label={
<EuiFlexGroup
alignItems="center"
gutterSize="xs"
justifyContent="spaceBetween"
<FormattedMessage
defaultMessage="Time field"
id="indexPatternManagement.createIndexPattern.stepTime.fieldLabel"
values={Object {}}
/>
}
labelAppend={
<EuiText
size="xs"
>
<EuiFlexItem
grow={false}
<EuiLink
onClick={[Function]}
>
<span>
<FormattedMessage
defaultMessage="Time Filter field name"
id="indexPatternManagement.createIndexPattern.stepTime.fieldHeader"
values={Object {}}
/>
</span>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiLink
className="timeFieldRefreshButton"
onClick={[Function]}
>
<FormattedMessage
defaultMessage="Refresh"
id="indexPatternManagement.createIndexPattern.stepTime.refreshButton"
values={Object {}}
/>
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>
<FormattedMessage
defaultMessage="Refresh"
id="indexPatternManagement.createIndexPattern.stepTime.refreshButton"
values={Object {}}
/>
</EuiLink>
</EuiText>
}
labelType="label"
>
@ -154,62 +113,43 @@ exports[`TimeField should render a selected time field 1`] = `
exports[`TimeField should render normally 1`] = `
<EuiForm>
<EuiText>
<p>
<FormattedMessage
defaultMessage="Select a primary time field for use with the global time filter."
id="indexPatternManagement.createIndexPattern.stepTime.timeDescription"
values={Object {}}
/>
</p>
</EuiText>
<EuiSpacer />
<EuiFormRow
describedByIds={Array []}
display="row"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
helpText={
<div>
<p>
<FormattedMessage
defaultMessage="The Time Filter will use this field to filter your data by time."
id="indexPatternManagement.createIndexPattern.stepTime.fieldLabel"
values={Object {}}
/>
</p>
<p>
<FormattedMessage
defaultMessage="You can choose not to have a time field, but you will not be able to narrow down your data by a time range."
id="indexPatternManagement.createIndexPattern.stepTime.fieldWarningLabel"
values={Object {}}
/>
</p>
</div>
}
label={
<EuiFlexGroup
alignItems="center"
gutterSize="xs"
justifyContent="spaceBetween"
<FormattedMessage
defaultMessage="Time field"
id="indexPatternManagement.createIndexPattern.stepTime.fieldLabel"
values={Object {}}
/>
}
labelAppend={
<EuiText
size="xs"
>
<EuiFlexItem
grow={false}
<EuiLink
onClick={[Function]}
>
<span>
<FormattedMessage
defaultMessage="Time Filter field name"
id="indexPatternManagement.createIndexPattern.stepTime.fieldHeader"
values={Object {}}
/>
</span>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiLink
className="timeFieldRefreshButton"
onClick={[Function]}
>
<FormattedMessage
defaultMessage="Refresh"
id="indexPatternManagement.createIndexPattern.stepTime.refreshButton"
values={Object {}}
/>
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>
<FormattedMessage
defaultMessage="Refresh"
id="indexPatternManagement.createIndexPattern.stepTime.refreshButton"
values={Object {}}
/>
</EuiLink>
</EuiText>
}
labelType="label"
>

View file

@ -24,8 +24,7 @@ import React from 'react';
import {
EuiForm,
EuiFormRow,
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
EuiLink,
EuiSelect,
EuiText,
@ -54,77 +53,68 @@ export const TimeField: React.FC<TimeFieldProps> = ({
}) => (
<EuiForm>
{isVisible ? (
<EuiFormRow
label={
<EuiFlexGroup gutterSize="xs" justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem grow={false}>
<span>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.stepTime.fieldHeader"
defaultMessage="Time Filter field name"
/>
</span>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{isLoading ? (
<EuiLoadingSpinner size="s" />
) : (
<EuiLink className="timeFieldRefreshButton" onClick={fetchTimeFields}>
<>
<EuiText>
<p>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.stepTime.timeDescription"
defaultMessage="Select a primary time field for use with the global time filter."
/>
</p>
</EuiText>
<EuiSpacer />
<EuiFormRow
label={
<FormattedMessage
id="indexPatternManagement.createIndexPattern.stepTime.fieldLabel"
defaultMessage="Time field"
/>
}
labelAppend={
isLoading ? (
<EuiLoadingSpinner size="s" />
) : (
<EuiText size="xs">
<EuiLink onClick={fetchTimeFields}>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.stepTime.refreshButton"
defaultMessage="Refresh"
/>
</EuiLink>
)}
</EuiFlexItem>
</EuiFlexGroup>
}
helpText={
<div>
<p>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.stepTime.fieldLabel"
defaultMessage="The Time Filter will use this field to filter your data by time."
/>
</p>
<p>
<FormattedMessage
id="indexPatternManagement.createIndexPattern.stepTime.fieldWarningLabel"
defaultMessage="You can choose not to have a time field, but you will not be able to narrow down your data by a time range."
/>
</p>
</div>
}
>
{isLoading ? (
<EuiSelect
name="timeField"
data-test-subj="createIndexPatternTimeFieldSelect"
options={[
{
text: i18n.translate(
'indexPatternManagement.createIndexPattern.stepTime.field.loadingDropDown',
{
defaultMessage: 'Loading…',
}
),
value: '',
},
]}
disabled={true}
/>
) : (
<EuiSelect
name="timeField"
data-test-subj="createIndexPatternTimeFieldSelect"
options={timeFieldOptions}
isLoading={isLoading}
disabled={isLoading}
value={selectedTimeField}
onChange={onTimeFieldChanged}
/>
)}
</EuiFormRow>
</EuiText>
)
}
>
{isLoading ? (
<EuiSelect
name="timeField"
data-test-subj="createIndexPatternTimeFieldSelect"
options={[
{
text: i18n.translate(
'indexPatternManagement.createIndexPattern.stepTime.field.loadingDropDown',
{
defaultMessage: 'Loading…',
}
),
value: '',
},
]}
disabled={true}
/>
) : (
<EuiSelect
name="timeField"
data-test-subj="createIndexPatternTimeFieldSelect"
options={timeFieldOptions}
isLoading={isLoading}
disabled={isLoading}
value={selectedTimeField}
onChange={onTimeFieldChanged}
/>
)}
</EuiFormRow>
</>
) : (
<EuiText>
<p>

View file

@ -22,10 +22,10 @@ import {
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiText,
EuiTitle,
EuiSpacer,
EuiLoadingSpinner,
EuiHorizontalRule,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { ensureMinimumTime, extractTimeFields } from '../../lib';
@ -43,6 +43,7 @@ interface StepTimeFieldProps {
goToPreviousStep: () => void;
createIndexPattern: (selectedTimeField: string | undefined, indexPatternId: string) => void;
indexPatternCreationType: IndexPatternCreationConfig;
selectedTimeField?: string;
}
interface StepTimeFieldState {
@ -69,7 +70,7 @@ export class StepTimeField extends Component<StepTimeFieldProps, StepTimeFieldSt
public readonly context!: IndexPatternManagmentContextValue;
state = {
state: StepTimeFieldState = {
error: '',
timeFields: [],
selectedTimeField: undefined,
@ -86,6 +87,10 @@ export class StepTimeField extends Component<StepTimeFieldProps, StepTimeFieldSt
super(props);
this.state.indexPatternType = props.indexPatternCreationType.getIndexPatternType() || '';
this.state.indexPatternName = props.indexPatternCreationType.getIndexPatternName();
this.state.selectedTimeField = props.selectedTimeField;
if (props.selectedTimeField) {
this.state.timeFieldSet = true;
}
}
mounted = false;
@ -183,21 +188,22 @@ export class StepTimeField extends Component<StepTimeFieldProps, StepTimeFieldSt
if (isCreating) {
return (
<EuiPanel>
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText>
<EuiFlexGroup justifyContent="center" alignItems="center" direction="column" gutterSize="s">
<EuiFlexItem grow={false}>
<EuiTitle size="s">
<h3 className="eui-textCenter">
<FormattedMessage
id="indexPatternManagement.createIndexPattern.stepTime.creatingLabel"
defaultMessage="Creating index pattern…"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="l" />
</EuiFlexItem>
</EuiFlexGroup>
);
}
@ -236,7 +242,7 @@ export class StepTimeField extends Component<StepTimeFieldProps, StepTimeFieldSt
) : null;
return (
<EuiPanel paddingSize="l">
<>
<Header indexPattern={indexPattern} indexPatternName={indexPatternName} />
<EuiSpacer size="m" />
<TimeField
@ -247,7 +253,7 @@ export class StepTimeField extends Component<StepTimeFieldProps, StepTimeFieldSt
selectedTimeField={selectedTimeField}
onTimeFieldChanged={this.onTimeFieldChanged}
/>
<EuiSpacer size="s" />
<EuiHorizontalRule />
<AdvancedOptions
isVisible={isAdvancedOptionsVisible}
indexPatternId={indexPatternId}
@ -261,7 +267,7 @@ export class StepTimeField extends Component<StepTimeFieldProps, StepTimeFieldSt
submittable={submittable}
createIndexPattern={this.createIndexPattern}
/>
</EuiPanel>
</>
);
}
}

View file

@ -19,10 +19,16 @@
import React, { ReactElement, Component } from 'react';
import { EuiGlobalToastList, EuiGlobalToastListToast, EuiPanel } from '@elastic/eui';
import {
EuiGlobalToastList,
EuiGlobalToastListToast,
EuiPageContent,
EuiHorizontalRule,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { DocLinksStart } from 'src/core/public';
import { StepIndexPattern } from './components/step_index_pattern';
import { StepTimeField } from './components/step_time_field';
import { Header } from './components/header';
@ -31,21 +37,21 @@ import { EmptyState } from './components/empty_state';
import { context as contextType } from '../../../../kibana_react/public';
import { getCreateBreadcrumbs } from '../breadcrumbs';
import { MAX_SEARCH_SIZE } from './constants';
import { ensureMinimumTime, getIndices } from './lib';
import { IndexPatternCreationConfig } from '../..';
import { IndexPatternManagmentContextValue } from '../../types';
import { MatchedIndex } from './types';
import { MatchedItem } from './types';
interface CreateIndexPatternWizardState {
step: number;
indexPattern: string;
allIndices: MatchedIndex[];
allIndices: MatchedItem[];
remoteClustersExist: boolean;
isInitiallyLoadingIndices: boolean;
isIncludingSystemIndices: boolean;
toasts: EuiGlobalToastListToast[];
indexPatternCreationType: IndexPatternCreationConfig;
selectedTimeField?: string;
docLinks: DocLinksStart;
}
export class CreateIndexPatternWizard extends Component<
@ -69,9 +75,9 @@ export class CreateIndexPatternWizard extends Component<
allIndices: [],
remoteClustersExist: false,
isInitiallyLoadingIndices: true,
isIncludingSystemIndices: false,
toasts: [],
indexPatternCreationType: context.services.indexPatternManagementStart.creation.getType(type),
docLinks: context.services.docLinks,
};
}
@ -80,7 +86,7 @@ export class CreateIndexPatternWizard extends Component<
}
catchAndWarn = async (
asyncFn: Promise<MatchedIndex[]>,
asyncFn: Promise<MatchedItem[]>,
errorValue: [] | string[],
errorMsg: ReactElement
) => {
@ -102,12 +108,6 @@ export class CreateIndexPatternWizard extends Component<
};
fetchData = async () => {
this.setState({
allIndices: [],
isInitiallyLoadingIndices: true,
remoteClustersExist: false,
});
const indicesFailMsg = (
<FormattedMessage
id="indexPatternManagement.createIndexPattern.loadIndicesFailMsg"
@ -125,31 +125,21 @@ export class CreateIndexPatternWizard extends Component<
// query local and remote indices, updating state independently
ensureMinimumTime(
this.catchAndWarn(
getIndices(
this.context.services.data.search.__LEGACY.esClient,
this.state.indexPatternCreationType,
`*`,
MAX_SEARCH_SIZE
),
getIndices(this.context.services.http, this.state.indexPatternCreationType, `*`, false),
[],
indicesFailMsg
)
).then((allIndices: MatchedIndex[]) =>
).then((allIndices: MatchedItem[]) =>
this.setState({ allIndices, isInitiallyLoadingIndices: false })
);
this.catchAndWarn(
// if we get an error from remote cluster query, supply fallback value that allows user entry.
// ['a'] is fallback value
getIndices(
this.context.services.data.search.__LEGACY.esClient,
this.state.indexPatternCreationType,
`*:*`,
1
),
getIndices(this.context.services.http, this.state.indexPatternCreationType, `*:*`, false),
['a'],
clustersFailMsg
).then((remoteIndices: string[] | MatchedIndex[]) =>
).then((remoteIndices: string[] | MatchedItem[]) =>
this.setState({ remoteClustersExist: !!remoteIndices.length })
);
};
@ -189,7 +179,7 @@ export class CreateIndexPatternWizard extends Component<
if (isConfirmed) {
return history.push(`/patterns/${indexPatternId}`);
} else {
return false;
return;
}
}
@ -201,31 +191,21 @@ export class CreateIndexPatternWizard extends Component<
history.push(`/patterns/${createdId}`);
};
goToTimeFieldStep = (indexPattern: string) => {
this.setState({ step: 2, indexPattern });
goToTimeFieldStep = (indexPattern: string, selectedTimeField?: string) => {
this.setState({ step: 2, indexPattern, selectedTimeField });
};
goToIndexPatternStep = () => {
this.setState({ step: 1 });
};
onChangeIncludingSystemIndices = () => {
this.setState((prevState) => ({
isIncludingSystemIndices: !prevState.isIncludingSystemIndices,
}));
};
renderHeader() {
const { isIncludingSystemIndices } = this.state;
return (
<Header
prompt={this.state.indexPatternCreationType.renderPrompt()}
showSystemIndices={this.state.indexPatternCreationType.getShowSystemIndices()}
isIncludingSystemIndices={isIncludingSystemIndices}
onChangeIncludingSystemIndices={this.onChangeIncludingSystemIndices}
indexPatternName={this.state.indexPatternCreationType.getIndexPatternName()}
isBeta={this.state.indexPatternCreationType.getIsBeta()}
docLinks={this.state.docLinks}
/>
);
}
@ -234,7 +214,6 @@ export class CreateIndexPatternWizard extends Component<
const {
allIndices,
isInitiallyLoadingIndices,
isIncludingSystemIndices,
step,
indexPattern,
remoteClustersExist,
@ -244,8 +223,8 @@ export class CreateIndexPatternWizard extends Component<
return <LoadingState />;
}
const hasDataIndices = allIndices.some(({ name }: MatchedIndex) => !name.startsWith('.'));
if (!hasDataIndices && !isIncludingSystemIndices && !remoteClustersExist) {
const hasDataIndices = allIndices.some(({ name }: MatchedItem) => !name.startsWith('.'));
if (!hasDataIndices && !remoteClustersExist) {
return (
<EmptyState
onRefresh={this.fetchData}
@ -254,29 +233,42 @@ export class CreateIndexPatternWizard extends Component<
);
}
const header = this.renderHeader();
if (step === 1) {
const { location } = this.props;
const initialQuery = new URLSearchParams(location.search).get('id') || undefined;
return (
<StepIndexPattern
allIndices={allIndices}
initialQuery={indexPattern || initialQuery}
isIncludingSystemIndices={isIncludingSystemIndices}
indexPatternCreationType={this.state.indexPatternCreationType}
goToNextStep={this.goToTimeFieldStep}
/>
<EuiPageContent>
{header}
<EuiHorizontalRule />
<StepIndexPattern
allIndices={allIndices}
initialQuery={indexPattern || initialQuery}
indexPatternCreationType={this.state.indexPatternCreationType}
goToNextStep={this.goToTimeFieldStep}
showSystemIndices={
this.state.indexPatternCreationType.getShowSystemIndices() && this.state.step === 1
}
/>
</EuiPageContent>
);
}
if (step === 2) {
return (
<StepTimeField
indexPattern={indexPattern}
goToPreviousStep={this.goToIndexPatternStep}
createIndexPattern={this.createIndexPattern}
indexPatternCreationType={this.state.indexPatternCreationType}
/>
<EuiPageContent>
{header}
<EuiHorizontalRule />
<StepTimeField
indexPattern={indexPattern}
goToPreviousStep={this.goToIndexPatternStep}
createIndexPattern={this.createIndexPattern}
indexPatternCreationType={this.state.indexPatternCreationType}
selectedTimeField={this.state.selectedTimeField}
/>
</EuiPageContent>
);
}
@ -290,15 +282,11 @@ export class CreateIndexPatternWizard extends Component<
};
render() {
const header = this.renderHeader();
const content = this.renderContent();
return (
<EuiPanel paddingSize={'l'}>
<div>
{header}
{content}
</div>
<>
{content}
<EuiGlobalToastList
toasts={this.state.toasts}
dismissToast={({ id }) => {
@ -306,7 +294,7 @@ export class CreateIndexPatternWizard extends Component<
}}
toastLifeTimeMs={6000}
/>
</EuiPanel>
</>
);
}
}

View file

@ -0,0 +1,69 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`getIndices response object to item array 1`] = `
Array [
Object {
"item": Object {
"attributes": Array [
"frozen",
],
"name": "frozen_index",
},
"name": "frozen_index",
"tags": Array [
Object {
"color": "default",
"key": "index",
"name": "Index",
},
Object {
"color": "danger",
"key": "frozen",
"name": "Frozen",
},
],
},
Object {
"item": Object {
"indices": Array [],
"name": "test_alias",
},
"name": "test_alias",
"tags": Array [
Object {
"color": "default",
"key": "alias",
"name": "Alias",
},
],
},
Object {
"item": Object {
"backing_indices": Array [],
"name": "test_data_stream",
"timestamp_field": "test_timestamp_field",
},
"name": "test_data_stream",
"tags": Array [
Object {
"color": "primary",
"key": "data_stream",
"name": "Data stream",
},
],
},
Object {
"item": Object {
"name": "test_index",
},
"name": "test_index",
"tags": Array [
Object {
"color": "default",
"key": "index",
"name": "Index",
},
],
},
]
`;

View file

@ -17,66 +17,31 @@
* under the License.
*/
import { getIndices } from './get_indices';
import { getIndices, responseToItemArray } from './get_indices';
import { IndexPatternCreationConfig } from '../../../../../index_pattern_management/public';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { LegacyApiCaller } from '../../../../../data/public/search/legacy';
import { httpServiceMock } from '../../../../../../core/public/mocks';
import { ResolveIndexResponseItemIndexAttrs } from '../types';
export const successfulResponse = {
hits: {
total: 1,
max_score: 0.0,
hits: [],
},
aggregations: {
indices: {
doc_count_error_upper_bound: 0,
sum_other_doc_count: 0,
buckets: [
{
key: '1',
doc_count: 1,
},
{
key: '2',
doc_count: 1,
},
],
indices: [
{
name: 'remoteCluster1:bar-01',
attributes: ['open'],
},
},
};
export const exceptionResponse = {
body: {
error: {
root_cause: [
{
type: 'index_not_found_exception',
reason: 'no such index',
index_uuid: '_na_',
'resource.type': 'index_or_alias',
'resource.id': 't',
index: 't',
},
],
type: 'transport_exception',
reason: 'unable to communicate with remote cluster [cluster_one]',
caused_by: {
type: 'index_not_found_exception',
reason: 'no such index',
index_uuid: '_na_',
'resource.type': 'index_or_alias',
'resource.id': 't',
index: 't',
},
],
aliases: [
{
name: 'f-alias',
indices: ['freeze-index', 'my-index'],
},
},
status: 500,
};
export const errorResponse = {
statusCode: 400,
error: 'Bad Request',
],
data_streams: [
{
name: 'foo',
backing_indices: ['foo-000001'],
timestamp_field: '@timestamp',
},
],
};
const mockIndexPatternCreationType = new IndexPatternCreationConfig({
@ -87,81 +52,62 @@ const mockIndexPatternCreationType = new IndexPatternCreationConfig({
isBeta: false,
});
function esClientFactory(search: (params: any) => any): LegacyApiCaller {
return {
search,
msearch: () => ({
abort: () => {},
...new Promise((resolve) => resolve({})),
}),
};
}
const es = esClientFactory(() => successfulResponse);
const http = httpServiceMock.createStartContract();
http.get.mockResolvedValue(successfulResponse);
describe('getIndices', () => {
it('should work in a basic case', async () => {
const result = await getIndices(es, mockIndexPatternCreationType, 'kibana', 1);
expect(result.length).toBe(2);
expect(result[0].name).toBe('1');
expect(result[1].name).toBe('2');
const result = await getIndices(http, mockIndexPatternCreationType, 'kibana', false);
expect(result.length).toBe(3);
expect(result[0].name).toBe('f-alias');
expect(result[1].name).toBe('foo');
});
it('should ignore ccs query-all', async () => {
expect((await getIndices(es, mockIndexPatternCreationType, '*:', 10)).length).toBe(0);
expect((await getIndices(http, mockIndexPatternCreationType, '*:', false)).length).toBe(0);
});
it('should ignore a single comma', async () => {
expect((await getIndices(es, mockIndexPatternCreationType, ',', 10)).length).toBe(0);
expect((await getIndices(es, mockIndexPatternCreationType, ',*', 10)).length).toBe(0);
expect((await getIndices(es, mockIndexPatternCreationType, ',foobar', 10)).length).toBe(0);
expect((await getIndices(http, mockIndexPatternCreationType, ',', false)).length).toBe(0);
expect((await getIndices(http, mockIndexPatternCreationType, ',*', false)).length).toBe(0);
expect((await getIndices(http, mockIndexPatternCreationType, ',foobar', false)).length).toBe(0);
});
it('should trim the input', async () => {
let index;
const esClient = esClientFactory(
jest.fn().mockImplementation((params) => {
index = params.index;
})
);
await getIndices(esClient, mockIndexPatternCreationType, 'kibana ', 1);
expect(index).toBe('kibana');
});
it('should use the limit', async () => {
let limit;
const esClient = esClientFactory(
jest.fn().mockImplementation((params) => {
limit = params.body.aggs.indices.terms.size;
})
);
await getIndices(esClient, mockIndexPatternCreationType, 'kibana', 10);
expect(limit).toBe(10);
it('response object to item array', () => {
const result = {
indices: [
{
name: 'test_index',
},
{
name: 'frozen_index',
attributes: ['frozen' as ResolveIndexResponseItemIndexAttrs],
},
],
aliases: [
{
name: 'test_alias',
indices: [],
},
],
data_streams: [
{
name: 'test_data_stream',
backing_indices: [],
timestamp_field: 'test_timestamp_field',
},
],
};
expect(responseToItemArray(result, mockIndexPatternCreationType)).toMatchSnapshot();
expect(responseToItemArray({}, mockIndexPatternCreationType)).toEqual([]);
});
describe('errors', () => {
it('should handle errors gracefully', async () => {
const esClient = esClientFactory(() => errorResponse);
const result = await getIndices(esClient, mockIndexPatternCreationType, 'kibana', 1);
expect(result.length).toBe(0);
});
it('should throw exceptions', async () => {
const esClient = esClientFactory(() => {
throw new Error('Fail');
http.get.mockImplementationOnce(() => {
throw new Error('Test error');
});
await expect(
getIndices(esClient, mockIndexPatternCreationType, 'kibana', 1)
).rejects.toThrow();
});
it('should handle index_not_found_exception errors gracefully', async () => {
const esClient = esClientFactory(
() => new Promise((resolve, reject) => reject(exceptionResponse))
);
const result = await getIndices(esClient, mockIndexPatternCreationType, 'kibana', 1);
const result = await getIndices(http, mockIndexPatternCreationType, 'kibana', false);
expect(result.length).toBe(0);
});
});

View file

@ -17,17 +17,31 @@
* under the License.
*/
import { get, sortBy } from 'lodash';
import { sortBy } from 'lodash';
import { HttpStart } from 'kibana/public';
import { i18n } from '@kbn/i18n';
import { IndexPatternCreationConfig } from '../../../../../index_pattern_management/public';
import { DataPublicPluginStart } from '../../../../../data/public';
import { MatchedIndex } from '../types';
import { MatchedItem, ResolveIndexResponse, ResolveIndexResponseItemIndexAttrs } from '../types';
const aliasLabel = i18n.translate('indexPatternManagement.aliasLabel', { defaultMessage: 'Alias' });
const dataStreamLabel = i18n.translate('indexPatternManagement.dataStreamLabel', {
defaultMessage: 'Data stream',
});
const indexLabel = i18n.translate('indexPatternManagement.indexLabel', {
defaultMessage: 'Index',
});
const frozenLabel = i18n.translate('indexPatternManagement.frozenLabel', {
defaultMessage: 'Frozen',
});
export async function getIndices(
es: DataPublicPluginStart['search']['__LEGACY']['esClient'],
http: HttpStart,
indexPatternCreationType: IndexPatternCreationConfig,
rawPattern: string,
limit: number
): Promise<MatchedIndex[]> {
showAllIndices: boolean
): Promise<MatchedItem[]> {
const pattern = rawPattern.trim();
// Searching for `*:` fails for CCS environments. The search request
@ -48,54 +62,58 @@ export async function getIndices(
return [];
}
// We need to always provide a limit and not rely on the default
if (!limit) {
throw new Error('`getIndices()` was called without the required `limit` parameter.');
}
const params = {
ignoreUnavailable: true,
index: pattern,
ignore: [404],
body: {
size: 0, // no hits
aggs: {
indices: {
terms: {
field: '_index',
size: limit,
},
},
},
},
};
const query = showAllIndices ? { expand_wildcards: 'all' } : undefined;
try {
const response = await es.search(params);
if (!response || response.error || !response.aggregations) {
const response = await http.get<ResolveIndexResponse>(
`/internal/index-pattern-management/resolve_index/${pattern}`,
{ query }
);
if (!response) {
return [];
}
return sortBy(
response.aggregations.indices.buckets
.map((bucket: { key: string; doc_count: number }) => {
return bucket.key;
})
.map((indexName: string) => {
return {
name: indexName,
tags: indexPatternCreationType.getIndexTags(indexName),
};
}),
'name'
);
} catch (err) {
const type = get(err, 'body.error.caused_by.type');
if (type === 'index_not_found_exception') {
// This happens in a CSS environment when the controlling node returns a 500 even though the data
// nodes returned a 404. Remove this when/if this is handled: https://github.com/elastic/elasticsearch/issues/27461
return [];
}
throw err;
return responseToItemArray(response, indexPatternCreationType);
} catch {
return [];
}
}
export const responseToItemArray = (
response: ResolveIndexResponse,
indexPatternCreationType: IndexPatternCreationConfig
): MatchedItem[] => {
const source: MatchedItem[] = [];
(response.indices || []).forEach((index) => {
const tags: MatchedItem['tags'] = [{ key: 'index', name: indexLabel, color: 'default' }];
const isFrozen = (index.attributes || []).includes(ResolveIndexResponseItemIndexAttrs.FROZEN);
tags.push(...indexPatternCreationType.getIndexTags(index.name));
if (isFrozen) {
tags.push({ name: frozenLabel, key: 'frozen', color: 'danger' });
}
source.push({
name: index.name,
tags,
item: index,
});
});
(response.aliases || []).forEach((alias) => {
source.push({
name: alias.name,
tags: [{ key: 'alias', name: aliasLabel, color: 'default' }],
item: alias,
});
});
(response.data_streams || []).forEach((dataStream) => {
source.push({
name: dataStream.name,
tags: [{ key: 'data_stream', name: dataStreamLabel, color: 'primary' }],
item: dataStream,
});
});
return sortBy(source, 'name');
};

View file

@ -18,7 +18,7 @@
*/
import { getMatchedIndices } from './get_matched_indices';
import { Tag } from '../types';
import { Tag, MatchedItem } from '../types';
jest.mock('./../constants', () => ({
MAX_NUMBER_OF_MATCHING_INDICES: 6,
@ -32,18 +32,18 @@ const indices = [
{ name: 'packetbeat', tags },
{ name: 'metricbeat', tags },
{ name: '.kibana', tags },
];
] as MatchedItem[];
const partialIndices = [
{ name: 'kibana', tags },
{ name: 'es', tags },
{ name: '.kibana', tags },
];
] as MatchedItem[];
const exactIndices = [
{ name: 'kibana', tags },
{ name: '.kibana', tags },
];
] as MatchedItem[];
describe('getMatchedIndices', () => {
it('should return all indices', () => {

View file

@ -33,7 +33,7 @@ function isSystemIndex(index: string): boolean {
return false;
}
function filterSystemIndices(indices: MatchedIndex[], isIncludingSystemIndices: boolean) {
function filterSystemIndices(indices: MatchedItem[], isIncludingSystemIndices: boolean) {
if (!indices) {
return indices;
}
@ -65,12 +65,12 @@ function filterSystemIndices(indices: MatchedIndex[], isIncludingSystemIndices:
We call this `exact` matches because ES is telling us exactly what it matches
*/
import { MatchedIndex } from '../types';
import { MatchedItem } from '../types';
export function getMatchedIndices(
unfilteredAllIndices: MatchedIndex[],
unfilteredPartialMatchedIndices: MatchedIndex[],
unfilteredExactMatchedIndices: MatchedIndex[],
unfilteredAllIndices: MatchedItem[],
unfilteredPartialMatchedIndices: MatchedItem[],
unfilteredExactMatchedIndices: MatchedItem[],
isIncludingSystemIndices: boolean = false
) {
const allIndices = filterSystemIndices(unfilteredAllIndices, isIncludingSystemIndices);

View file

@ -17,12 +17,54 @@
* under the License.
*/
export interface MatchedIndex {
export interface MatchedItem {
name: string;
tags: Tag[];
item: {
name: string;
backing_indices?: string[];
timestamp_field?: string;
indices?: string[];
aliases?: string[];
attributes?: ResolveIndexResponseItemIndexAttrs[];
data_stream?: string;
};
}
export interface ResolveIndexResponse {
indices?: ResolveIndexResponseItemIndex[];
aliases?: ResolveIndexResponseItemAlias[];
data_streams?: ResolveIndexResponseItemDataStream[];
}
export interface ResolveIndexResponseItem {
name: string;
}
export interface ResolveIndexResponseItemDataStream extends ResolveIndexResponseItem {
backing_indices: string[];
timestamp_field: string;
}
export interface ResolveIndexResponseItemAlias extends ResolveIndexResponseItem {
indices: string[];
}
export interface ResolveIndexResponseItemIndex extends ResolveIndexResponseItem {
aliases?: string[];
attributes?: ResolveIndexResponseItemIndexAttrs[];
data_stream?: string;
}
export enum ResolveIndexResponseItemIndexAttrs {
OPEN = 'open',
CLOSED = 'closed',
HIDDEN = 'hidden',
FROZEN = 'frozen',
}
export interface Tag {
name: string;
key: string;
color: string;
}

View file

@ -836,7 +836,6 @@ exports[`FieldEditor should show deprecated lang warning 1`] = `
testlang
</eui-code>,
"painlessLink": <eui-link
href="https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/modules-scripting-painless.html"
target="_blank"
>
<FormattedMessage

View file

@ -61,7 +61,6 @@ exports[`ScriptingWarningCallOut should render normally 1`] = `
values={
Object {
"scripFields": <EuiLink
href="https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-request-script-fields.html"
target="_blank"
>
<FormattedMessage
@ -75,7 +74,6 @@ exports[`ScriptingWarningCallOut should render normally 1`] = `
/>
</EuiLink>,
"scriptsInAggregation": <EuiLink
href="https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-aggregations.html#_values_source"
target="_blank"
>
<FormattedMessage
@ -94,14 +92,11 @@ exports[`ScriptingWarningCallOut should render normally 1`] = `
<span>
Please familiarize yourself with
<EuiLink
href="https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-request-script-fields.html"
target="_blank"
>
<a
<button
className="euiLink euiLink--primary"
href="https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-request-script-fields.html"
rel="noopener"
target="_blank"
type="button"
>
<FormattedMessage
defaultMessage="script fields"
@ -120,18 +115,15 @@ exports[`ScriptingWarningCallOut should render normally 1`] = `
data-euiicon-type="link"
/>
</EuiIcon>
</a>
</button>
</EuiLink>
and with
<EuiLink
href="https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-aggregations.html#_values_source"
target="_blank"
>
<a
<button
className="euiLink euiLink--primary"
href="https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-aggregations.html#_values_source"
rel="noopener"
target="_blank"
type="button"
>
<FormattedMessage
defaultMessage="scripts in aggregations"
@ -150,7 +142,7 @@ exports[`ScriptingWarningCallOut should render normally 1`] = `
data-euiicon-type="link"
/>
</EuiIcon>
</a>
</button>
</EuiLink>
before using scripted fields.
</span>

View file

@ -76,6 +76,13 @@ const createInstance = async () => {
};
};
const docLinks = {
links: {
indexPatterns: {},
scriptedFields: {},
},
};
const createIndexPatternManagmentContext = () => {
const {
chrome,
@ -84,7 +91,6 @@ const createIndexPatternManagmentContext = () => {
uiSettings,
notifications,
overlays,
docLinks,
} = coreMock.createStart();
const { http } = coreMock.createSetup();
const data = dataPluginMock.createStartContract();

View file

@ -18,7 +18,7 @@
*/
import { i18n } from '@kbn/i18n';
import { MatchedIndex } from '../../components/create_index_pattern_wizard/types';
import { MatchedItem } from '../../components/create_index_pattern_wizard/types';
const indexPatternTypeName = i18n.translate(
'indexPatternManagement.editIndexPattern.createIndex.defaultTypeName',
@ -105,7 +105,7 @@ export class IndexPatternCreationConfig {
return [];
}
public checkIndicesForErrors(indices: MatchedIndex[]) {
public checkIndicesForErrors(indices: MatchedItem[]) {
return undefined;
}

View file

@ -0,0 +1,25 @@
/*
* 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 { PluginInitializerContext } from 'src/core/server';
import { IndexPatternManagementPlugin } from './plugin';
export function plugin(initializerContext: PluginInitializerContext) {
return new IndexPatternManagementPlugin(initializerContext);
}

View file

@ -0,0 +1,70 @@
/*
* 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 { PluginInitializerContext, CoreSetup, Plugin } from 'src/core/server';
import { schema } from '@kbn/config-schema';
export class IndexPatternManagementPlugin implements Plugin<void, void> {
constructor(initializerContext: PluginInitializerContext) {}
public setup(core: CoreSetup) {
const router = core.http.createRouter();
router.get(
{
path: '/internal/index-pattern-management/resolve_index/{query}',
validate: {
params: schema.object({
query: schema.string(),
}),
query: schema.object({
expand_wildcards: schema.maybe(
schema.oneOf([
schema.literal('all'),
schema.literal('open'),
schema.literal('closed'),
schema.literal('hidden'),
schema.literal('none'),
])
),
}),
},
},
async (context, req, res) => {
const queryString = req.query.expand_wildcards
? { expand_wildcards: req.query.expand_wildcards }
: null;
const result = await context.core.elasticsearch.legacy.client.callAsCurrentUser(
'transport.request',
{
method: 'GET',
path: `/_resolve/index/${encodeURIComponent(req.params.query)}${
queryString ? '?' + new URLSearchParams(queryString).toString() : ''
}`,
}
);
return res.ok({ body: result });
}
);
}
public start() {}
public stop() {}
}

View file

@ -22,6 +22,7 @@ import expect from '@kbn/expect';
export default function ({ getService, getPageObjects }) {
const kibanaServer = getService('kibanaServer');
const testSubjects = getService('testSubjects');
const es = getService('legacyEs');
const PageObjects = getPageObjects(['settings', 'common']);
describe('"Create Index Pattern" wizard', function () {
@ -48,5 +49,59 @@ export default function ({ getService, getPageObjects }) {
expect(isEnabled).to.be.ok();
});
});
describe('data streams', () => {
it('can be an index pattern', async () => {
await es.transport.request({
path: '/_index_template/generic-logs',
method: 'PUT',
body: {
index_patterns: ['logs-*', 'test_data_stream'],
template: {
mappings: {
properties: {
'@timestamp': {
type: 'date',
},
},
},
},
data_stream: {
timestamp_field: '@timestamp',
},
},
});
await es.transport.request({
path: '/_data_stream/test_data_stream',
method: 'PUT',
});
await PageObjects.settings.createIndexPattern('test_data_stream', false);
await es.transport.request({
path: '/_data_stream/test_data_stream',
method: 'DELETE',
});
});
});
describe('index alias', () => {
it('can be an index pattern', async () => {
await es.transport.request({
path: '/blogs/_doc',
method: 'POST',
body: { user: 'matt', message: 20 },
});
await es.transport.request({
path: '/_aliases',
method: 'POST',
body: { actions: [{ add: { index: 'blogs', alias: 'alias1' } }] },
});
await PageObjects.settings.createIndexPattern('alias1', false);
});
});
});
}

View file

@ -100,6 +100,7 @@ export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig
{
key: this.type,
name: rollupIndexPatternIndexLabel,
color: 'primary',
},
]
: [];