From ea7012ebb1732d2011e10ee658d75ba0909c79fe Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Wed, 8 Jul 2020 09:58:32 -0500 Subject: [PATCH] Index Patterns Management - use `/_resolve` endpoint for data streams support (#70271) * Index Patterns Management - use `/_resolve` endpoint for data streams support --- .../index_pattern_management/kibana.json | 2 +- .../create_index_pattern_wizard.test.tsx.snap | 153 +++---- .../header/__snapshots__/header.test.tsx.snap | 394 +++++++++++------- .../components/header/header.test.tsx | 21 +- .../components/header/header.tsx | 81 ++-- .../step_index_pattern.test.tsx.snap | 4 + .../header/__snapshots__/header.test.tsx.snap | 192 +++++---- .../components/header/header.test.tsx | 4 + .../components/header/header.tsx | 86 ++-- .../indices_list/indices_list.test.tsx | 5 +- .../components/indices_list/indices_list.tsx | 6 +- .../status_message.test.tsx.snap | 112 ++--- .../status_message/status_message.test.tsx | 11 +- .../status_message/status_message.tsx | 97 ++--- .../step_index_pattern.test.tsx | 59 ++- .../step_index_pattern/step_index_pattern.tsx | 98 +++-- .../step_time_field.test.tsx.snap | 109 ++--- .../header/__snapshots__/header.test.tsx.snap | 19 +- .../components/header/header.tsx | 11 +- .../__snapshots__/time_field.test.tsx.snap | 212 ++++------ .../components/time_field/time_field.tsx | 126 +++--- .../step_time_field/step_time_field.tsx | 40 +- .../create_index_pattern_wizard.tsx | 116 +++--- .../__snapshots__/get_indices.test.ts.snap | 69 +++ .../lib/get_indices.test.ts | 174 +++----- .../lib/get_indices.ts | 118 +++--- .../lib/get_matched_indices.test.ts | 8 +- .../lib/get_matched_indices.ts | 10 +- .../create_index_pattern_wizard/types.ts | 44 +- .../__snapshots__/field_editor.test.tsx.snap | 1 - .../warning_call_out.test.tsx.snap | 20 +- .../index_pattern_management/public/mocks.ts | 8 +- .../public/service/creation/config.ts | 4 +- .../index_pattern_management/server/index.ts | 25 ++ .../index_pattern_management/server/plugin.ts | 70 ++++ .../_create_index_pattern_wizard.js | 55 +++ .../rollup_index_pattern_creation_config.js | 1 + 37 files changed, 1412 insertions(+), 1153 deletions(-) create mode 100644 src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/__snapshots__/get_indices.test.ts.snap create mode 100644 src/plugins/index_pattern_management/server/index.ts create mode 100644 src/plugins/index_pattern_management/server/plugin.ts diff --git a/src/plugins/index_pattern_management/kibana.json b/src/plugins/index_pattern_management/kibana.json index 364edbb030dc99..23adef2626a72b 100644 --- a/src/plugins/index_pattern_management/kibana.json +++ b/src/plugins/index_pattern_management/kibana.json @@ -1,7 +1,7 @@ { "id": "indexPatternManagement", "version": "kibana", - "server": false, + "server": true, "ui": true, "requiredPlugins": ["management", "data", "kibanaLegacy"] } diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap index 5c955bbd3283ec..70200e03c0dbeb 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap @@ -1,41 +1,33 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`CreateIndexPatternWizard defaults to the loading state 1`] = ` - -
-
- -
+ + -
+ `; exports[`CreateIndexPatternWizard renders index pattern step when there are indices 1`] = ` - -
+ +
+ -
+ -
+ `; exports[`CreateIndexPatternWizard renders the empty state when there are no indices 1`] = ` - -
-
- -
+ + -
+ `; exports[`CreateIndexPatternWizard renders time field step when step is set to 2 1`] = ` - -
+ +
+ -
+ -
+ `; exports[`CreateIndexPatternWizard renders when there are no indices but there are remote clusters 1`] = ` - -
+ +
+ -
+ -
+ `; exports[`CreateIndexPatternWizard shows system indices even if there are no other indices if the include system indices is toggled 1`] = ` - -
-
- -
+ + -
+ `; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/__snapshots__/header.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/__snapshots__/header.test.tsx.snap index 81ca3e644d3ce3..6a2fd1000e6b4f 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/__snapshots__/header.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/__snapshots__/header.test.tsx.snap @@ -2,10 +2,15 @@ exports[`Header should render a different name, prompt, and beta tag if provided 1`] = `
Test prompt @@ -31,76 +36,114 @@ exports[`Header should render a different name, prompt, and beta tag if provided -
+ + +
- -
+ + multiple + , + "single": + filebeat-4-3-22 + , + "star": + filebeat-* + , + } + } + > + + An index pattern can match a single source, for example, + + + + + filebeat-4-3-22 + + + + + , or + + multiple + + data souces, + + + + + filebeat-* + + + + + . + + +
+ - -
-

- - - - - Kibana uses index patterns to retrieve data from Elasticsearch indices for things like visualizations. - - - - -

-
-
-
-
+ + Read documentation + + + + +

- +
Test prompt
- -
-
`; exports[`Header should render normally 1`] = `
@@ -110,66 +153,104 @@ exports[`Header should render normally 1`] = ` Create test index pattern -
+ + +
- -
+ + multiple + , + "single": + filebeat-4-3-22 + , + "star": + filebeat-* + , + } + } > - + An index pattern can match a single source, for example, + + + + + filebeat-4-3-22 + + + + + , or + + multiple + + data souces, + + + + + filebeat-* + + + + + . + + +
+ +
-
+ + Read documentation + + + + +

- - -
- +
`; exports[`Header should render without including system indices 1`] = `
@@ -179,57 +260,90 @@ exports[`Header should render without including system indices 1`] = ` Create test index pattern -
+ + +
- -
+ + multiple + , + "single": + filebeat-4-3-22 + , + "star": + filebeat-* + , + } + } + > + + An index pattern can match a single source, for example, + + + + + filebeat-4-3-22 + + + + + , or + + multiple + + data souces, + + + + + filebeat-* + + + + + . + + +
+ - -
-

- - - - - Kibana uses index patterns to retrieve data from Elasticsearch indices for things like visualizations. - - - - -

-
-
-
-
+ + Read documentation + + + + +

- - -
- +
`; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.test.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.test.tsx index d12e0401380b9e..865b3ec353f76e 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.test.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.test.tsx @@ -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( -
{}} - />, +
, { wrappingComponent: KibanaContextProvider, wrappingComponentProps: { @@ -47,11 +49,7 @@ describe('Header', () => { it('should render without including system indices', () => { const component = mount( -
{}} - />, +
, { wrappingComponent: KibanaContextProvider, wrappingComponentProps: { @@ -66,11 +64,10 @@ describe('Header', () => { it('should render a different name, prompt, and beta tag if provided', () => { const component = mount(
{}} prompt={
Test prompt
} indexPatternName={indexPatternName} isBeta={true} + docLinks={mockedDocLinks} />, { wrappingComponent: KibanaContextProvider, diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.tsx index 35c6e67d0ea0e7..f90425311142d9 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/header/header.tsx @@ -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().services.chrome.docTitle.change; const createIndexPatternHeader = i18n.translate( @@ -67,53 +55,44 @@ export const Header = ({

{createIndexPatternHeader} {isBeta ? ( - + <> {' '} - + ) : null}

- - - -

- - - -

-
-
- {showSystemIndices ? ( - - - } - id="checkboxShowSystemIndices" - checked={isIncludingSystemIndices} - onChange={onChangeIncludingSystemIndices} + + +

+ multiple, + single: filebeat-4-3-22, + star: filebeat-*, + }} + /> +
+ + - - ) : null} - + +

+
{prompt ? ( - - + <> + {prompt} - + ) : null} - ); }; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/__snapshots__/step_index_pattern.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/__snapshots__/step_index_pattern.test.tsx.snap index b68ba4720b9352..813a0c61c08294 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/__snapshots__/step_index_pattern.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/__snapshots__/step_index_pattern.test.tsx.snap @@ -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", }, ] diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/__snapshots__/header.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/__snapshots__/header.test.tsx.snap index 3021292953ff5c..c4f735558b1f21 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/__snapshots__/header.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/__snapshots__/header.test.tsx.snap @@ -16,13 +16,8 @@ exports[`Header should mark the input as invalid 1`] = ` - - + + @@ -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={ -
-

- - * - , - } + + + * + , } - /> -

-

- - % - , - } + } + /> + + + % + , } - /> -

-
+ } + /> + } isInvalid={true} label={ @@ -79,6 +71,7 @@ exports[`Header should mark the input as invalid 1`] = ` > - - - + + + +
@@ -124,13 +128,8 @@ exports[`Header should render normally 1`] = ` - - + + @@ -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={ -
-

- - * - , - } + + + * + , } - /> -

-

- - % - , - } + } + /> + + + % + , } - /> -

-
+ } + /> + } isInvalid={false} label={ @@ -183,6 +179,7 @@ exports[`Header should render normally 1`] = ` > - - - + + + +
diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.test.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.test.tsx index f56340d0009be5..acc133a4dd6499 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.test.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.test.tsx @@ -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} /> ); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.tsx index 9ce72aeeea6e37..f1bf0d54a1cbf7 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.tsx @@ -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) => void; goToNextStep: (query: string) => void; isNextStepDisabled: boolean; + showSystemIndices?: boolean; + onChangeIncludingSystemIndices: (event: EuiSwitchEvent) => void; + isIncludingSystemIndices: boolean; } export const Header: React.FC = ({ @@ -51,6 +56,9 @@ export const Header: React.FC = ({ onQueryChanged, goToNextStep, isNextStepDisabled, + showSystemIndices = false, + onChangeIncludingSystemIndices, + isIncludingSystemIndices, ...rest }) => (
@@ -63,35 +71,32 @@ export const Header: React.FC = ({ - - + + } isInvalid={isInputInvalid} error={errors} helpText={ -
-

- * }} - /> -

-

- {characterList} }} - /> -

-
+ <> + * }} + />{' '} + {characterList} }} + /> + } > = ({ isInvalid={isInputInvalid} onChange={onQueryChanged} data-test-subj="createIndexPatternNameInput" + fullWidth />
+ + {showSystemIndices ? ( + + + } + id="checkboxShowSystemIndices" + checked={isIncludingSystemIndices} + onChange={onChangeIncludingSystemIndices} + /> + + ) : null}
- goToNextStep(query)} - isDisabled={isNextStepDisabled} - data-test-subj="createIndexPatternGoToStep2Button" - > - - + + goToNextStep(query)} + isDisabled={isNextStepDisabled} + data-test-subj="createIndexPatternGoToStep2Button" + > + + +
diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.test.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.test.tsx index d8a1d1a0ab72ff..fbd60cbe3d131a 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.test.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.test.tsx @@ -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', () => { diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.tsx index c590d2a7ddfe2d..4a051ee698209a 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.tsx @@ -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 {index.tags.map((tag: Tag) => { return ( - + {tag.name} ); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/__snapshots__/status_message.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/__snapshots__/status_message.test.tsx.snap index 4a063f1430d1c0..44b753c4738038 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/__snapshots__/status_message.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/__snapshots__/status_message.test.tsx.snap @@ -1,67 +1,44 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`StatusMessage should render with exact matches 1`] = ` - - - + title={   - - , - "strongSuccess": - - , + "sourceCount": 1, } } /> - - + } +/> `; exports[`StatusMessage should render with no partial matches 1`] = ` - - + title={ - - + } +/> `; exports[`StatusMessage should render with partial matches 1`] = ` - - + title={ - - + } +/> `; exports[`StatusMessage should render without a query 1`] = ` - - + title={ - 2 - indices - , + "sourceCount": 2, } } /> - - + } +/> `; exports[`StatusMessage should show that no indices exist 1`] = ` - - + title={ - - + } +/> `; exports[`StatusMessage should show that system indices exist 1`] = ` - - + title={ - - + } +/> `; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/status_message.test.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/status_message.test.tsx index 899c21d59c5bc3..f97c9ffe8a364f 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/status_message.test.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/status_message.test.tsx @@ -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( diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/status_message.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/status_message.tsx index ccdd1833ea9bfb..22b75071b93bb9 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/status_message.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/status_message/status_message.tsx @@ -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 = ({ 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 = ( {allIndicesLength} indices }} + defaultMessage="Your index pattern can match {sourceCount, plural, + one {your # source} + other {any of your # sources} + }." + values={{ sourceCount: allIndicesLength }} /> ); @@ -66,8 +70,7 @@ export const StatusMessage: React.FC = ({ ); @@ -83,51 +86,44 @@ export const StatusMessage: React.FC = ({ } } else if (exactMatchedIndices.length) { statusIcon = 'check'; - statusColor = 'secondary'; + statusColor = 'success'; statusMessage = (   - - - ), - strongIndices: ( - - - - ), + sourceCount: exactMatchedIndices.length, }} /> ); } else if (partialMatchedIndices.length) { - statusIcon = null; - statusColor = 'default'; + statusIcon = undefined; + statusColor = 'primary'; statusMessage = ( @@ -137,20 +133,26 @@ export const StatusMessage: React.FC = ({ ); } else if (allIndicesLength) { - statusIcon = null; - statusColor = 'default'; + statusIcon = undefined; + statusColor = 'warning'; statusMessage = ( @@ -163,11 +165,12 @@ export const StatusMessage: React.FC = ({ } return ( - - - {statusIcon ? : null} - {statusMessage} - - + ); }; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx index 053940270c2b6d..c88918041ca810 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx @@ -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 + ); + }); }); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx index b6205a8731dfa9..5797149a51aeac 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx @@ -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 { static contextType = contextType; @@ -78,9 +104,9 @@ export class StepIndexPattern extends Component 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 + <> {this.renderHeader(matchedIndices)} - + {this.renderLoadingState()} {this.renderIndexPatternExists()} {this.renderStatusMessage(matchedIndices)} - + {this.renderList(matchedIndices)} - + ); } } diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/__snapshots__/step_time_field.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/__snapshots__/step_time_field.test.tsx.snap index f865a1ddfd2233..6cc92d20cfdcc1 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/__snapshots__/step_time_field.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/__snapshots__/step_time_field.test.tsx.snap @@ -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`] = ` - +
- + - + `; exports[`StepTimeField should render a loading state when creating the index pattern 1`] = ` - - + - - - - - +

- - - - +

+ +
+ + + +
`; exports[`StepTimeField should render a selected timeField 1`] = ` - +
- + - + `; exports[`StepTimeField should render advanced options 1`] = ` - +
- + - + `; exports[`StepTimeField should render advanced options with an index pattern id 1`] = ` - +
- + - + `; exports[`StepTimeField should render any error message 1`] = ` - +
- + - + `; exports[`StepTimeField should render normally 1`] = ` - +
- + - + `; exports[`StepTimeField should render timeFields 1`] = ` - +
- + - + `; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/__snapshots__/header.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/__snapshots__/header.test.tsx.snap index 63008ec5b52e79..2ac243780b31db 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/__snapshots__/header.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/__snapshots__/header.test.tsx.snap @@ -16,21 +16,10 @@ exports[`Header should render normally 1`] = ` - - - ki* - , - "indexPatternName": "ki*", - } - } - /> + + + ki* + `; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/header.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/header.tsx index 22e245f7ac1370..c17b356e159f6a 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/header.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/header/header.tsx @@ -39,15 +39,8 @@ export const Header: React.FC = ({ indexPattern, indexPatternName } - - {indexPattern}, - indexPatternName, - }} - /> + + {indexPattern} ); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/__snapshots__/time_field.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/__snapshots__/time_field.test.tsx.snap index 886a4ccad39ccf..73277b1963626b 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/__snapshots__/time_field.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/__snapshots__/time_field.test.tsx.snap @@ -2,55 +2,33 @@ exports[`TimeField should render a loading state 1`] = ` + +

+ +

+
+ -

- -

-

- -

- - } label={ - - - - - - - - - - + + } + labelAppend={ + } labelType="label" > @@ -73,62 +51,43 @@ exports[`TimeField should render a loading state 1`] = ` exports[`TimeField should render a selected time field 1`] = ` + +

+ +

+
+ -

- -

-

- -

- - } label={ - + } + labelAppend={ + - - - - - - - - - - - + + +
} labelType="label" > @@ -154,62 +113,43 @@ exports[`TimeField should render a selected time field 1`] = ` exports[`TimeField should render normally 1`] = ` + +

+ +

+
+ -

- -

-

- -

- - } label={ - + } + labelAppend={ + - - - - - - - - - - - + + +
} labelType="label" > diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/time_field.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/time_field.tsx index b4ed37118966ba..7a3d72551f4641 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/time_field.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/components/time_field/time_field.tsx @@ -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 = ({ }) => ( {isVisible ? ( - - - - - - - - {isLoading ? ( - - ) : ( - + <> + +

+ +

+
+ + + } + labelAppend={ + isLoading ? ( + + ) : ( + + - )} -
- - } - helpText={ -
-

- -

-

- -

-
- } - > - {isLoading ? ( - - ) : ( - - )} -
+ + ) + } + > + {isLoading ? ( + + ) : ( + + )} + + ) : (

diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx index 98ce22cd14227c..5d33a08557fed5 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx @@ -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 - - - - - - + + + +

- - - - +

+ + + + + + + ); } @@ -236,7 +242,7 @@ export class StepTimeField extends Component + <>
- + - + ); } } diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx index 111be41cfc53a9..cd76ca09ccb741 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx @@ -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, + asyncFn: Promise, errorValue: [] | string[], errorMsg: ReactElement ) => { @@ -102,12 +108,6 @@ export class CreateIndexPatternWizard extends Component< }; fetchData = async () => { - this.setState({ - allIndices: [], - isInitiallyLoadingIndices: true, - remoteClustersExist: false, - }); - const indicesFailMsg = ( + ).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 (
); } @@ -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 ; } - const hasDataIndices = allIndices.some(({ name }: MatchedIndex) => !name.startsWith('.')); - if (!hasDataIndices && !isIncludingSystemIndices && !remoteClustersExist) { + const hasDataIndices = allIndices.some(({ name }: MatchedItem) => !name.startsWith('.')); + if (!hasDataIndices && !remoteClustersExist) { return ( + + {header} + + + ); } if (step === 2) { return ( - + + {header} + + + ); } @@ -290,15 +282,11 @@ export class CreateIndexPatternWizard extends Component< }; render() { - const header = this.renderHeader(); const content = this.renderContent(); return ( - -
- {header} - {content} -
+ <> + {content} { @@ -306,7 +294,7 @@ export class CreateIndexPatternWizard extends Component< }} toastLifeTimeMs={6000} /> -
+ ); } } diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/__snapshots__/get_indices.test.ts.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/__snapshots__/get_indices.test.ts.snap new file mode 100644 index 00000000000000..99876383b43437 --- /dev/null +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/__snapshots__/get_indices.test.ts.snap @@ -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", + }, + ], + }, +] +`; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts index b1faca8a04964b..8e4dd37284333c 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts @@ -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); - }); - - 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'); + 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 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); }); }); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.ts index 9f75dc39a654c2..c6a11de1bc4fc0 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.ts +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.ts @@ -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 { + showAllIndices: boolean +): Promise { 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) { - 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' + const response = await http.get( + `/internal/index-pattern-management/resolve_index/${pattern}`, + { query } ); - } 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 + if (!response) { 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'); +}; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_matched_indices.test.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_matched_indices.test.ts index 65840aa64046d1..c27eaa5ebc99ee 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_matched_indices.test.ts +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_matched_indices.test.ts @@ -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', () => { diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_matched_indices.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_matched_indices.ts index 7e2eeb17ab3877..dbb166597152e1 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_matched_indices.ts +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_matched_indices.ts @@ -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); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/types.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/types.ts index 634bbd856ea862..b23924837ffb78 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/types.ts +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/types.ts @@ -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; } diff --git a/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap b/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap index 6bc99c356592e5..7a7545580d82a3 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap @@ -836,7 +836,6 @@ exports[`FieldEditor should show deprecated lang warning 1`] = ` testlang , "painlessLink": , "scriptsInAggregation": Please familiarize yourself with - - + and with - - + before using scripted fields. diff --git a/src/plugins/index_pattern_management/public/mocks.ts b/src/plugins/index_pattern_management/public/mocks.ts index 93574cde7dc857..ec8100db420851 100644 --- a/src/plugins/index_pattern_management/public/mocks.ts +++ b/src/plugins/index_pattern_management/public/mocks.ts @@ -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(); diff --git a/src/plugins/index_pattern_management/public/service/creation/config.ts b/src/plugins/index_pattern_management/public/service/creation/config.ts index 95a91fd7594caf..04510b1d64e1e2 100644 --- a/src/plugins/index_pattern_management/public/service/creation/config.ts +++ b/src/plugins/index_pattern_management/public/service/creation/config.ts @@ -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; } diff --git a/src/plugins/index_pattern_management/server/index.ts b/src/plugins/index_pattern_management/server/index.ts new file mode 100644 index 00000000000000..02a46315898323 --- /dev/null +++ b/src/plugins/index_pattern_management/server/index.ts @@ -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); +} diff --git a/src/plugins/index_pattern_management/server/plugin.ts b/src/plugins/index_pattern_management/server/plugin.ts new file mode 100644 index 00000000000000..ecca45cbcc453a --- /dev/null +++ b/src/plugins/index_pattern_management/server/plugin.ts @@ -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 { + 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() {} +} diff --git a/test/functional/apps/management/_create_index_pattern_wizard.js b/test/functional/apps/management/_create_index_pattern_wizard.js index 8209f3e1ac9d6d..cb8b5a6ddc65fb 100644 --- a/test/functional/apps/management/_create_index_pattern_wizard.js +++ b/test/functional/apps/management/_create_index_pattern_wizard.js @@ -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); + }); + }); }); } diff --git a/x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js b/x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js index 4ff189d8f1be08..643cc3efb0136d 100644 --- a/x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js +++ b/x-pack/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js @@ -100,6 +100,7 @@ export class RollupIndexPatternCreationConfig extends IndexPatternCreationConfig { key: this.type, name: rollupIndexPatternIndexLabel, + color: 'primary', }, ] : [];