From 69a79a863d39491cbb91a5f2727aabbd27fc3d14 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Fri, 29 May 2020 14:34:39 -0700 Subject: [PATCH 01/11] Surface data streams in Index Management. - Add GET data streams API endpoint. - Render table of data streams in its own tab. --- .../common/lib/data_stream_serialization.ts | 21 ++++ .../index_management/common/lib/index.ts | 3 + .../common/types/data_streams.ts | 29 +++++ .../index_management/common/types/index.ts | 2 + .../public/application/app.tsx | 10 +- .../data_stream_list/data_stream_list.tsx | 75 +++++++++++++ .../data_stream_table/data_stream_table.tsx | 105 ++++++++++++++++++ .../data_stream_table/index.ts | 7 ++ .../sections/home/data_stream_list/index.ts | 7 ++ .../public/application/sections/home/home.tsx | 13 ++- .../public/application/services/api.ts | 18 ++- .../server/client/elasticsearch.ts | 9 ++ .../server/routes/api/data_streams/index.ts | 13 +++ .../api/data_streams/register_get_route.ts | 34 ++++++ .../index_management/server/routes/index.ts | 2 + 15 files changed, 338 insertions(+), 10 deletions(-) create mode 100644 x-pack/plugins/index_management/common/lib/data_stream_serialization.ts create mode 100644 x-pack/plugins/index_management/common/types/data_streams.ts create mode 100644 x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx create mode 100644 x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx create mode 100644 x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/index.ts create mode 100644 x-pack/plugins/index_management/public/application/sections/home/data_stream_list/index.ts create mode 100644 x-pack/plugins/index_management/server/routes/api/data_streams/index.ts create mode 100644 x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts diff --git a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts new file mode 100644 index 00000000000000..9d267210a6b318 --- /dev/null +++ b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DataStream, DataStreamFromEs } from '../types'; + +export function deserializeDataStreamList(dataStreamsFromEs: DataStreamFromEs[]): DataStream[] { + return dataStreamsFromEs.map(({ name, timestamp_field, indices, generation }) => ({ + name, + timeStampField: timestamp_field, + indices: indices.map( + ({ index_name, index_uuid }: { index_name: string; index_uuid: string }) => ({ + name: index_name, + uuid: index_uuid, + }) + ), + generation, + })); +} diff --git a/x-pack/plugins/index_management/common/lib/index.ts b/x-pack/plugins/index_management/common/lib/index.ts index 16eb544c56a089..95bada8b3726c8 100644 --- a/x-pack/plugins/index_management/common/lib/index.ts +++ b/x-pack/plugins/index_management/common/lib/index.ts @@ -3,6 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + +export { deserializeDataStreamList } from './data_stream_serialization'; + export { deserializeLegacyTemplateList, deserializeTemplateList, diff --git a/x-pack/plugins/index_management/common/types/data_streams.ts b/x-pack/plugins/index_management/common/types/data_streams.ts new file mode 100644 index 00000000000000..5b743296d868bd --- /dev/null +++ b/x-pack/plugins/index_management/common/types/data_streams.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface DataStreamFromEs { + name: string; + timestamp_field: string; + indices: DataStreamIndexFromEs[]; + generation: number; +} + +export interface DataStreamIndexFromEs { + index_name: string; + index_uuid: string; +} + +export interface DataStream { + name: string; + timeStampField: string; + indices: DataStreamIndex[]; + generation: number; +} + +export interface DataStreamIndex { + name: string; + uuid: string; +} diff --git a/x-pack/plugins/index_management/common/types/index.ts b/x-pack/plugins/index_management/common/types/index.ts index b467f020978a56..fd0197c4ad8da1 100644 --- a/x-pack/plugins/index_management/common/types/index.ts +++ b/x-pack/plugins/index_management/common/types/index.ts @@ -11,3 +11,5 @@ export * from './indices'; export * from './mappings'; export * from './templates'; + +export { DataStreamFromEs, DataStream, DataStreamIndex } from './data_streams'; diff --git a/x-pack/plugins/index_management/public/application/app.tsx b/x-pack/plugins/index_management/public/application/app.tsx index 10bbe3ced64da4..3f6aa81fb845cf 100644 --- a/x-pack/plugins/index_management/public/application/app.tsx +++ b/x-pack/plugins/index_management/public/application/app.tsx @@ -29,10 +29,10 @@ export const App = ({ history }: { history: ScopedHistory }) => { // Export this so we can test it with a different router. export const AppWithoutRouter = () => ( - - - - - + + + + + ); diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx new file mode 100644 index 00000000000000..518187e97c31c7 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiTitle, EuiText, EuiSpacer, EuiEmptyPrompt } from '@elastic/eui'; + +import { SectionError, SectionLoading, Error } from '../../../components'; +import { useLoadDataStreams } from '../../../services/api'; +import { DataStreamTable } from './data_stream_table'; + +export const DataStreamList = () => { + const { error, isLoading, data: dataStreams } = useLoadDataStreams(); + + let content; + + if (isLoading) { + content = ( + + + + ); + } else if (error) { + content = ( + + } + error={error as Error} + /> + ); + } else if (Array.isArray(dataStreams) && dataStreams.length === 0) { + content = ( + + + + } + data-test-subj="emptyPrompt" + /> + ); + } else if (Array.isArray(dataStreams) && dataStreams.length > 0) { + content = ( + <> + {/* TODO: Add a switch for toggling on data streams created by Ingest Manager */} + + + + + + + + + ); + } + + return
{content}
; +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx new file mode 100644 index 00000000000000..3fec66e1bcd6d9 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiInMemoryTable, EuiBasicTableColumn } from '@elastic/eui'; + +import { DataStream } from '../../../../../../common/types'; + +interface Props { + dataStreams?: DataStream[]; +} + +export const DataStreamTable: React.FunctionComponent = ({ dataStreams }) => { + const columns: Array> = [ + { + field: 'name', + name: i18n.translate('xpack.idxMgmt.dataStreamList.table.nameColumnTitle', { + defaultMessage: 'Name', + }), + truncateText: true, + sortable: true, + // TODO: Render as a link to open the detail panel + }, + { + field: 'indices', + name: i18n.translate('xpack.idxMgmt.dataStreamList.table.indicesColumnTitle', { + defaultMessage: 'Indices', + }), + truncateText: true, + sortable: true, + // TODO: Render as a deep-link into the indices tab + render: (indices: DataStream['indices']) => indices.length, + }, + { + field: 'timeStampField', + name: i18n.translate('xpack.idxMgmt.dataStreamList.table.timeStampFieldColumnTitle', { + defaultMessage: 'Timestamp field', + }), + truncateText: true, + sortable: true, + }, + { + field: 'generation', + name: i18n.translate('xpack.idxMgmt.dataStreamList.table.generationFieldColumnTitle', { + defaultMessage: 'Generation', + }), + truncateText: true, + sortable: true, + }, + ]; + + const pagination = { + initialPageSize: 20, + pageSizeOptions: [10, 20, 50], + }; + + const sorting = { + sort: { + field: 'name', + direction: 'asc', + }, + } as const; + + const searchConfig = { + box: { + incremental: true, + }, + toolsLeft: undefined /* TODO: Actions menu */, + toolsRight: [ + /* TODO: Reload button */ + ], + }; + + return ( + <> + ({ + 'data-test-subj': 'row', + })} + cellProps={() => ({ + 'data-test-subj': 'cell', + })} + data-test-subj="dataStreamTable" + message={ + + } + /> + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/index.ts b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/index.ts new file mode 100644 index 00000000000000..3922ca5c1d50ca --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { DataStreamTable } from './data_stream_table'; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/index.ts b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/index.ts new file mode 100644 index 00000000000000..e2f588cc2a0fb0 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { DataStreamList } from './data_stream_list'; diff --git a/x-pack/plugins/index_management/public/application/sections/home/home.tsx b/x-pack/plugins/index_management/public/application/sections/home/home.tsx index 9d4331d742a253..72293a6522157f 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/home.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/home.tsx @@ -19,11 +19,12 @@ import { EuiTitle, } from '@elastic/eui'; import { documentationService } from '../../services/documentation'; +import { DataStreamList } from './data_stream_list'; import { IndexList } from './index_list'; import { TemplateList } from './template_list'; import { breadcrumbService } from '../../services/breadcrumbs'; -type Section = 'indices' | 'templates'; +type Section = 'data_streams' | 'indices' | 'templates'; interface MatchParams { section: Section; @@ -36,6 +37,15 @@ export const IndexManagementHome: React.FunctionComponent { const tabs = [ + { + id: 'data_streams' as Section, + name: ( + + ), + }, { id: 'indices' as Section, name: , @@ -106,6 +116,7 @@ export const IndexManagementHome: React.FunctionComponent + ({ + path: `${API_BASE_PATH}/data_streams`, + method: 'get', + }); +} + export async function loadIndices() { const response = await httpService.httpClient.get(`${API_BASE_PATH}/indices`); return response.data ? response.data : response; diff --git a/x-pack/plugins/index_management/server/client/elasticsearch.ts b/x-pack/plugins/index_management/server/client/elasticsearch.ts index 65bd5411a249b2..4705ac111b779b 100644 --- a/x-pack/plugins/index_management/server/client/elasticsearch.ts +++ b/x-pack/plugins/index_management/server/client/elasticsearch.ts @@ -10,6 +10,15 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any) Client.prototype.dataManagement = components.clientAction.namespaceFactory(); const dataManagement = Client.prototype.dataManagement.prototype; + dataManagement.getDataStreams = ca({ + urls: [ + { + fmt: '/_data_stream', + }, + ], + method: 'GET', + }); + dataManagement.getComponentTemplates = ca({ urls: [ { diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/index.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/index.ts new file mode 100644 index 00000000000000..56c514e30f2427 --- /dev/null +++ b/x-pack/plugins/index_management/server/routes/api/data_streams/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RouteDependencies } from '../../../types'; + +import { registerGetAllRoute } from './register_get_route'; + +export function registerDataStreamRoutes(dependencies: RouteDependencies) { + registerGetAllRoute(dependencies); +} diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts new file mode 100644 index 00000000000000..9128556130bf45 --- /dev/null +++ b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { deserializeDataStreamList } from '../../../../common/lib'; +import { RouteDependencies } from '../../../types'; +import { addBasePath } from '../index'; + +export function registerGetAllRoute({ router, license, lib: { isEsError } }: RouteDependencies) { + router.get( + { path: addBasePath('/data_streams'), validate: false }, + license.guardApiRoute(async (ctx, req, res) => { + const { callAsCurrentUser } = ctx.dataManagement!.client; + + try { + const dataStreams = await callAsCurrentUser('dataManagement.getDataStreams'); + const body = deserializeDataStreamList(dataStreams); + + return res.ok({ body }); + } catch (error) { + if (isEsError(error)) { + return res.customError({ + statusCode: error.statusCode, + body: error, + }); + } + + return res.internalError({ body: error }); + } + }) + ); +} diff --git a/x-pack/plugins/index_management/server/routes/index.ts b/x-pack/plugins/index_management/server/routes/index.ts index 1e5aaf8087624a..202e6919f7b13c 100644 --- a/x-pack/plugins/index_management/server/routes/index.ts +++ b/x-pack/plugins/index_management/server/routes/index.ts @@ -6,6 +6,7 @@ import { RouteDependencies } from '../types'; +import { registerDataStreamRoutes } from './api/data_streams'; import { registerIndicesRoutes } from './api/indices'; import { registerTemplateRoutes } from './api/templates'; import { registerMappingRoute } from './api/mapping'; @@ -15,6 +16,7 @@ import { registerComponentTemplateRoutes } from './api/component_templates'; export class ApiRoutes { setup(dependencies: RouteDependencies) { + registerDataStreamRoutes(dependencies); registerIndicesRoutes(dependencies); registerTemplateRoutes(dependencies); registerSettingsRoutes(dependencies); From 43db94bd458c34129e2fd1a34686f616f2d79580 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Mon, 1 Jun 2020 17:34:28 -0700 Subject: [PATCH 02/11] Fix functional tests. --- .../apps/index_management/home_page.ts | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/x-pack/test/functional/apps/index_management/home_page.ts b/x-pack/test/functional/apps/index_management/home_page.ts index e985e338122e79..521f7195b4984a 100644 --- a/x-pack/test/functional/apps/index_management/home_page.ts +++ b/x-pack/test/functional/apps/index_management/home_page.ts @@ -24,11 +24,28 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const headingText = await pageObjects.indexManagement.sectionHeadingText(); expect(headingText).to.be('Index Management'); - const indicesList = await testSubjects.exists('indicesList'); - expect(indicesList).to.be(true); + const dataStreamList = await testSubjects.exists('dataStreamList'); + expect(dataStreamList).to.be(true); + }); + + describe('Indices', () => { + it('renders the indices tab', async () => { + // Navigate to the index templates tab + await pageObjects.indexManagement.changeTabs('indicesTab'); - const reloadIndicesButton = await pageObjects.indexManagement.reloadIndicesButton(); - expect(await reloadIndicesButton.isDisplayed()).to.be(true); + await pageObjects.header.waitUntilLoadingHasFinished(); + + // Verify url + const url = await browser.getCurrentUrl(); + expect(url).to.contain(`/indices`); + + // Verify content + const indicesList = await testSubjects.exists('indicesList'); + expect(indicesList).to.be(true); + + const reloadIndicesButton = await pageObjects.indexManagement.reloadIndicesButton(); + expect(await reloadIndicesButton.isDisplayed()).to.be(true); + }); }); describe('Index templates', () => { From 0c3243428f2756f37eba561ad97643812bf68fb3 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Tue, 2 Jun 2020 11:10:02 -0700 Subject: [PATCH 03/11] Change tab order so Indices is first. It's likely that this tab will be more fully featured than the Data Streams tab and this order will be less of a shock to users who upgrade. --- .../public/application/app.tsx | 4 +-- .../public/application/sections/home/home.tsx | 8 ++--- .../apps/index_management/home_page.ts | 31 +++++++++++-------- .../page_objects/index_management_page.ts | 2 +- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/app.tsx b/x-pack/plugins/index_management/public/application/app.tsx index 3f6aa81fb845cf..45e768cf65df71 100644 --- a/x-pack/plugins/index_management/public/application/app.tsx +++ b/x-pack/plugins/index_management/public/application/app.tsx @@ -32,7 +32,7 @@ export const AppWithoutRouter = () => ( - - + + ); diff --git a/x-pack/plugins/index_management/public/application/sections/home/home.tsx b/x-pack/plugins/index_management/public/application/sections/home/home.tsx index 72293a6522157f..5d04125a937886 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/home.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/home.tsx @@ -37,6 +37,10 @@ export const IndexManagementHome: React.FunctionComponent { const tabs = [ + { + id: 'indices' as Section, + name: , + }, { id: 'data_streams' as Section, name: ( @@ -46,10 +50,6 @@ export const IndexManagementHome: React.FunctionComponent ), }, - { - id: 'indices' as Section, - name: , - }, { id: 'templates' as Section, name: ( diff --git a/x-pack/test/functional/apps/index_management/home_page.ts b/x-pack/test/functional/apps/index_management/home_page.ts index 521f7195b4984a..13b73287db4092 100644 --- a/x-pack/test/functional/apps/index_management/home_page.ts +++ b/x-pack/test/functional/apps/index_management/home_page.ts @@ -18,33 +18,38 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.common.navigateToApp('indexManagement'); }); - it('Loads the app', async () => { + it('Loads the app and renders the indices tab by default', async () => { await log.debug('Checking for section heading to say Index Management.'); const headingText = await pageObjects.indexManagement.sectionHeadingText(); expect(headingText).to.be('Index Management'); - const dataStreamList = await testSubjects.exists('dataStreamList'); - expect(dataStreamList).to.be(true); + // Verify url + const url = await browser.getCurrentUrl(); + expect(url).to.contain(`/indices`); + + // Verify content + const indicesList = await testSubjects.exists('indicesList'); + expect(indicesList).to.be(true); + + const reloadIndicesButton = await pageObjects.indexManagement.reloadIndicesButton(); + expect(await reloadIndicesButton.isDisplayed()).to.be(true); }); - describe('Indices', () => { - it('renders the indices tab', async () => { - // Navigate to the index templates tab - await pageObjects.indexManagement.changeTabs('indicesTab'); + describe('Data streams', () => { + it('renders the data streams tab', async () => { + // Navigate to the data streams tab + await pageObjects.indexManagement.changeTabs('data_streamsTab'); await pageObjects.header.waitUntilLoadingHasFinished(); // Verify url const url = await browser.getCurrentUrl(); - expect(url).to.contain(`/indices`); + expect(url).to.contain(`/data_streams`); // Verify content - const indicesList = await testSubjects.exists('indicesList'); - expect(indicesList).to.be(true); - - const reloadIndicesButton = await pageObjects.indexManagement.reloadIndicesButton(); - expect(await reloadIndicesButton.isDisplayed()).to.be(true); + const dataStreamList = await testSubjects.exists('dataStreamList'); + expect(dataStreamList).to.be(true); }); }); diff --git a/x-pack/test/functional/page_objects/index_management_page.ts b/x-pack/test/functional/page_objects/index_management_page.ts index d12186f2e21893..7fd329f9b01bdf 100644 --- a/x-pack/test/functional/page_objects/index_management_page.ts +++ b/x-pack/test/functional/page_objects/index_management_page.ts @@ -44,7 +44,7 @@ export function IndexManagementPageProvider({ getService }: FtrProviderContext) }; }); }, - async changeTabs(tab: 'indicesTab' | 'templatesTab') { + async changeTabs(tab: 'indicesTab' | 'data_streamsTab' | 'templatesTab') { await testSubjects.click(tab); }, }; From aaa14c1c68e4df65a61474f266144449f76cfd5e Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 4 Jun 2020 15:15:11 -0700 Subject: [PATCH 04/11] Add Reload button and fix TS error. --- .../home/data_stream_list/data_stream_list.tsx | 4 ++-- .../data_stream_table/data_stream_table.tsx | 18 +++++++++++++++--- .../public/application/services/api.ts | 7 +------ 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx index 518187e97c31c7..590505bc529c76 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx @@ -13,7 +13,7 @@ import { useLoadDataStreams } from '../../../services/api'; import { DataStreamTable } from './data_stream_table'; export const DataStreamList = () => { - const { error, isLoading, data: dataStreams } = useLoadDataStreams(); + const { error, isLoading, data: dataStreams, sendRequest: reload } = useLoadDataStreams(); let content; @@ -66,7 +66,7 @@ export const DataStreamList = () => { - + ); } diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx index 3fec66e1bcd6d9..65cda5dea9f8d6 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx @@ -7,15 +7,16 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiInMemoryTable, EuiBasicTableColumn } from '@elastic/eui'; +import { EuiInMemoryTable, EuiBasicTableColumn, EuiButton } from '@elastic/eui'; import { DataStream } from '../../../../../../common/types'; interface Props { dataStreams?: DataStream[]; + reload: () => {}; } -export const DataStreamTable: React.FunctionComponent = ({ dataStreams }) => { +export const DataStreamTable: React.FunctionComponent = ({ dataStreams, reload }) => { const columns: Array> = [ { field: 'name', @@ -72,7 +73,18 @@ export const DataStreamTable: React.FunctionComponent = ({ dataStreams }) }, toolsLeft: undefined /* TODO: Actions menu */, toolsRight: [ - /* TODO: Reload button */ + + + , ], }; diff --git a/x-pack/plugins/index_management/public/application/services/api.ts b/x-pack/plugins/index_management/public/application/services/api.ts index b4b5d3e52e5deb..daf5cd9b4fbcc3 100644 --- a/x-pack/plugins/index_management/public/application/services/api.ts +++ b/x-pack/plugins/index_management/public/application/services/api.ts @@ -31,12 +31,7 @@ import { UIM_TEMPLATE_UPDATE, UIM_TEMPLATE_CLONE, } from '../../../common/constants'; -import { - TemplateDeserialized, - TemplateListItem, - IndexTemplateFormatVersion, - DataStream, -} from '../../../common'; +import { TemplateDeserialized, TemplateListItem, DataStream } from '../../../common'; import { IndexMgmtMetricsType } from '../../types'; import { TAB_SETTINGS, TAB_MAPPING, TAB_STATS } from '../constants'; import { useRequest, sendRequest } from './use_request'; From ecc96db652eb46858bf1cae210878b6b5a03caac Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 4 Jun 2020 15:20:56 -0700 Subject: [PATCH 05/11] Add component integration tests for data streams table. --- .../helpers/http_requests.ts | 9 ++ .../client_integration/helpers/index.ts | 42 +-------- .../helpers/test_subjects.ts | 48 ++++++++++ .../home/data_streams_tab.helpers.ts | 68 ++++++++++++++ .../home/data_streams_tab.test.ts | 92 +++++++++++++++++++ 5 files changed, 218 insertions(+), 41 deletions(-) create mode 100644 x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts create mode 100644 x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts create mode 100644 x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts index da461609f0b836..780d7b91b10369 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts @@ -27,6 +27,14 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { ]); }; + const setLoadDataStreamsResponse = (response: HttpResponse = []) => { + server.respondWith('GET', `${API_BASE_PATH}/data_streams`, [ + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify(response), + ]); + }; + const setDeleteTemplateResponse = (response: HttpResponse = []) => { server.respondWith('POST', `${API_BASE_PATH}/delete-index-templates`, [ 200, @@ -71,6 +79,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { return { setLoadTemplatesResponse, setLoadIndicesResponse, + setLoadDataStreamsResponse, setDeleteTemplateResponse, setLoadTemplateResponse, setCreateTemplateResponse, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts index 8e7755a65af3cf..f581083e28cc63 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts @@ -10,44 +10,4 @@ export { nextTick, getRandomString, findTestSubject, TestBed } from '../../../.. export { setupEnvironment, WithAppDependencies, services } from './setup_environment'; -export type TestSubjects = - | 'aliasesTab' - | 'appTitle' - | 'cell' - | 'closeDetailsButton' - | 'createTemplateButton' - | 'createLegacyTemplateButton' - | 'deleteSystemTemplateCallOut' - | 'deleteTemplateButton' - | 'deleteTemplatesConfirmation' - | 'documentationLink' - | 'emptyPrompt' - | 'manageTemplateButton' - | 'mappingsTab' - | 'noAliasesCallout' - | 'noMappingsCallout' - | 'noSettingsCallout' - | 'indicesList' - | 'indicesTab' - | 'indexTableIncludeHiddenIndicesToggle' - | 'indexTableIndexNameLink' - | 'reloadButton' - | 'reloadIndicesButton' - | 'row' - | 'sectionError' - | 'sectionLoading' - | 'settingsTab' - | 'summaryTab' - | 'summaryTitle' - | 'systemTemplatesSwitch' - | 'templateDetails' - | 'templateDetails.manageTemplateButton' - | 'templateDetails.sectionLoading' - | 'templateDetails.tab' - | 'templateDetails.title' - | 'templateList' - | 'templateTable' - | 'templatesTab' - | 'legacyTemplateTable' - | 'viewButton' - | 'filterList.filterItem'; +export { TestSubjects } from './test_subjects'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts new file mode 100644 index 00000000000000..5e17da2b46696b --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export type TestSubjects = + | 'aliasesTab' + | 'appTitle' + | 'cell' + | 'closeDetailsButton' + | 'createTemplateButton' + | 'createLegacyTemplateButton' + | 'dataStreamTable' + | 'deleteSystemTemplateCallOut' + | 'deleteTemplateButton' + | 'deleteTemplatesConfirmation' + | 'documentationLink' + | 'emptyPrompt' + | 'manageTemplateButton' + | 'mappingsTab' + | 'noAliasesCallout' + | 'noMappingsCallout' + | 'noSettingsCallout' + | 'indicesList' + | 'indicesTab' + | 'indexTableIncludeHiddenIndicesToggle' + | 'indexTableIndexNameLink' + | 'reloadButton' + | 'reloadIndicesButton' + | 'row' + | 'sectionError' + | 'sectionLoading' + | 'settingsTab' + | 'summaryTab' + | 'summaryTitle' + | 'systemTemplatesSwitch' + | 'templateDetails' + | 'templateDetails.manageTemplateButton' + | 'templateDetails.sectionLoading' + | 'templateDetails.tab' + | 'templateDetails.title' + | 'templateList' + | 'templateTable' + | 'templatesTab' + | 'legacyTemplateTable' + | 'viewButton' + | 'filterList.filterItem'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts new file mode 100644 index 00000000000000..74b11eabf18b30 --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { registerTestBed, TestBed, TestBedConfig } from '../../../../../test_utils'; +import { DataStream } from '../../../common'; +// NOTE: We have to use the Home component instead of the DataStreamList component because we depend +// upon react router to provide the name of the template to load in the detail panel. +import { IndexManagementHome } from '../../../public/application/sections/home'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { indexManagementStore } from '../../../public/application/store'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { WithAppDependencies, services, TestSubjects } from '../helpers'; + +const testBedConfig: TestBedConfig = { + store: () => indexManagementStore(services as any), + memoryRouter: { + initialEntries: [`/indices`], + componentRoutePath: `/:section(indices|data_streams)`, + }, + doMountAsync: true, +}; + +const initTestBed = registerTestBed(WithAppDependencies(IndexManagementHome), testBedConfig); + +export interface DataStreamsTabTestBed extends TestBed { + actions: { + goToDataStreamsList: () => void; + clickReloadButton: () => void; + }; +} + +export const setup = async (): Promise => { + const testBed = await initTestBed(); + + /** + * User Actions + */ + + const goToDataStreamsList = () => { + testBed.find('data_streamsTab').simulate('click'); + }; + + const clickReloadButton = () => { + const { find } = testBed; + find('reloadButton').simulate('click'); + }; + + return { + ...testBed, + actions: { + goToDataStreamsList, + clickReloadButton, + }, + }; +}; + +export const createDataStreamPayload = (name: string): DataStream => ({ + name, + timeStampField: '@timestamp', + indices: [ + { + name: 'indexName', + uuid: 'indexId', + }, + ], + generation: 1, +}); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts new file mode 100644 index 00000000000000..bd09e425c20435 --- /dev/null +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { act } from 'react-dom/test-utils'; + +import { API_BASE_PATH } from '../../../common/constants'; +import { setupEnvironment } from '../helpers'; + +import { DataStreamsTabTestBed, setup, createDataStreamPayload } from './data_streams_tab.helpers'; + +describe('Data Streams tab', () => { + const { server, httpRequestsMockHelpers } = setupEnvironment(); + let testBed: DataStreamsTabTestBed; + + afterAll(() => { + server.restore(); + }); + + beforeEach(async () => { + httpRequestsMockHelpers.setLoadIndicesResponse([]); + + await act(async () => { + testBed = await setup(); + }); + }); + + describe('when there are no data streams', () => { + beforeEach(async () => { + const { actions, component } = testBed; + + httpRequestsMockHelpers.setLoadDataStreamsResponse([]); + + await act(async () => { + actions.goToDataStreamsList(); + }); + + component.update(); + }); + + test('should display an empty prompt', async () => { + const { exists } = testBed; + + expect(exists('sectionLoading')).toBe(false); + expect(exists('emptyPrompt')).toBe(true); + }); + }); + + describe('when there are data streams', () => { + beforeEach(async () => { + const { actions, component } = testBed; + + httpRequestsMockHelpers.setLoadDataStreamsResponse([ + createDataStreamPayload('dataStream1'), + createDataStreamPayload('dataStream2'), + ]); + + await act(async () => { + actions.goToDataStreamsList(); + }); + + component.update(); + }); + + test('should list them in the table', async () => { + const { table } = testBed; + + const { tableCellsValues } = table.getMetaData('dataStreamTable'); + + expect(tableCellsValues).toEqual([ + ['dataStream1', '1', '@timestamp', '1'], + ['dataStream2', '1', '@timestamp', '1'], + ]); + }); + + test('should have a button to reload the data streams', async () => { + const { exists, actions } = testBed; + const totalRequests = server.requests.length; + + expect(exists('reloadButton')).toBe(true); + + await act(async () => { + actions.clickReloadButton(); + }); + + expect(server.requests.length).toBe(totalRequests + 1); + expect(server.requests[server.requests.length - 1].url).toBe(`${API_BASE_PATH}/data_streams`); + }); + }); +}); From e17df1d7d50484e1b47927bc22160b9e311b0274 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 4 Jun 2020 16:26:38 -0700 Subject: [PATCH 06/11] Add skipped API integration test. Extend ES client with createDataStream and deleteDataStream methods. --- .../server/client/elasticsearch.ts | 30 ++++++++ .../index_management/data_streams.ts | 77 +++++++++++++++++++ .../apis/management/index_management/index.js | 1 + .../index_management/lib/elasticsearch.js | 1 + 4 files changed, 109 insertions(+) create mode 100644 x-pack/test/api_integration/apis/management/index_management/data_streams.ts diff --git a/x-pack/plugins/index_management/server/client/elasticsearch.ts b/x-pack/plugins/index_management/server/client/elasticsearch.ts index 4705ac111b779b..23d3c78481dd75 100644 --- a/x-pack/plugins/index_management/server/client/elasticsearch.ts +++ b/x-pack/plugins/index_management/server/client/elasticsearch.ts @@ -19,6 +19,36 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any) method: 'GET', }); + // We don't allow the user to create a data stream in the UI or API. We're just adding this here + // to enable the API integration tests. + dataManagement.createDataStream = ca({ + urls: [ + { + fmt: '/_data_stream/<%=name%>', + req: { + name: { + type: 'string', + }, + }, + }, + ], + method: 'PUT', + }); + + dataManagement.deleteDataStream = ca({ + urls: [ + { + fmt: '/_data_stream/<%=name%>', + req: { + name: { + type: 'string', + }, + }, + }, + ], + method: 'DELETE', + }); + dataManagement.getComponentTemplates = ca({ urls: [ { diff --git a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts new file mode 100644 index 00000000000000..441aa75fa98d47 --- /dev/null +++ b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../../ftr_provider_context'; +// @ts-ignore +import { API_BASE_PATH } from './constants'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const es = getService('legacyEs'); + + const createDataStream = (name) => { + // A data stream requires an index template before it can be created. + return supertest + .post(`${API_BASE_PATH}/index-templates`) + .set('kbn-xsrf', 'xxx') + .send({ + name, + indexPatterns: ['*'], + _kbnMeta: { + isLegacy: false, + }, + }) + .then(() => + es.dataManagement.createDataStream({ + name, + }) + ); + }; + + const deleteDataStream = (name) => { + return supertest + .post(`${API_BASE_PATH}/delete-index-templates`) + .set('kbn-xsrf', 'xxx') + .send({ + templates: [{ name, isLegacy: false }], + }) + .then(() => + es.dataManagement.deleteDataStream({ + name, + }) + ); + }; + + describe('Data streams', function () { + // TODO: Implement this test once the API supports creating composable index templates. + describe.skip('Get', () => { + before(() => createDataStream('test-data-stream')); + after(() => deleteDataStream('test-data-stream')); + + describe('all data streams', () => { + it('returns an array of data streams', async () => { + const { body: dataStreams } = await supertest + .get(`${API_BASE_PATH}/data_streams`) + .set('kbn-xsrf', 'xxx') + .expect(200); + + expect(dataStreams).to.eql([ + { + name: 'test-data-stream', + }, + ]); + }); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/management/index_management/index.js b/x-pack/test/api_integration/apis/management/index_management/index.js index fdee325938ff41..93e8a4a8d6130d 100644 --- a/x-pack/test/api_integration/apis/management/index_management/index.js +++ b/x-pack/test/api_integration/apis/management/index_management/index.js @@ -10,6 +10,7 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./mapping')); loadTestFile(require.resolve('./settings')); loadTestFile(require.resolve('./stats')); + loadTestFile(require.resolve('./data_streams')); loadTestFile(require.resolve('./templates')); loadTestFile(require.resolve('./component_templates')); }); diff --git a/x-pack/test/api_integration/apis/management/index_management/lib/elasticsearch.js b/x-pack/test/api_integration/apis/management/index_management/lib/elasticsearch.js index b950a56a913db0..1a1517567eaedf 100644 --- a/x-pack/test/api_integration/apis/management/index_management/lib/elasticsearch.js +++ b/x-pack/test/api_integration/apis/management/index_management/lib/elasticsearch.js @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { getRandomString } from './random'; /** From fdd85adf0ef1a5660d67229bccd1626f396816fe Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Fri, 5 Jun 2020 14:25:07 -0700 Subject: [PATCH 07/11] Add cross-links back and forth between the Indices and Data Streams tabs. - Refactor routing service to expose decodePathFromReactRouter and encodePathForReactRouter methods to clarify their purpose. - Add stub data stream detail panel, with the intention to populate it with content in the future. - Converted index_list to TS. --- .../data_stream_detail_panel.tsx | 99 +++++++++++++++++++ .../data_stream_detail_panel/index.ts | 7 ++ .../data_stream_list/data_stream_list.tsx | 32 +++++- .../data_stream_table/data_stream_table.tsx | 29 +++++- .../public/application/sections/home/home.tsx | 6 +- .../detail_panel/detail_panel.container.d.ts | 7 ++ .../detail_panel/{index.js => index.ts} | 0 .../{index_list.js => index_list.tsx} | 8 +- .../index_table/{index.js => index.ts} | 0 .../index_table.container.d.ts} | 2 +- .../index_list/index_table/index_table.js | 37 ++++++- .../template_details/template_details.tsx | 4 +- .../template_table/template_table.tsx | 6 +- .../template_clone/template_clone.tsx | 4 +- .../sections/template_edit/template_edit.tsx | 4 +- .../public/application/services/api.ts | 8 ++ .../public/application/services/routing.ts | 19 ++-- .../index_management/public/shared_imports.ts | 2 + 18 files changed, 243 insertions(+), 31 deletions(-) create mode 100644 x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx create mode 100644 x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/index.ts create mode 100644 x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/detail_panel.container.d.ts rename x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/{index.js => index.ts} (100%) rename x-pack/plugins/index_management/public/application/sections/home/index_list/{index_list.js => index_list.tsx} (71%) rename x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/{index.js => index.ts} (100%) rename x-pack/plugins/index_management/public/application/sections/home/index_list/{index_list.d.ts => index_table/index_table.container.d.ts} (82%) diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx new file mode 100644 index 00000000000000..8051153e5aacac --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlyout, + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, +} from '@elastic/eui'; + +import { SectionLoading, SectionError, Error } from '../../../../components'; +import { useLoadDataStream } from '../../../../services/api'; + +interface Props { + dataStreamName: string; + onClose: () => void; +} + +export const DataStreamDetailPanel: React.FunctionComponent = ({ + dataStreamName, + onClose, +}) => { + const { error, data: dataStream, isLoading } = useLoadDataStream(dataStreamName); + + let content; + + if (isLoading) { + content = ( + + + + ); + } else if (error) { + content = ( + + } + error={error as Error} + data-test-subj="sectionError" + /> + ); + } else if (dataStream) { + content = {JSON.stringify(dataStream)}; + } + + return ( + + + +

+ {dataStreamName} +

+
+
+ + {content} + + + + + + + + + + +
+ ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/index.ts b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/index.ts new file mode 100644 index 00000000000000..3f45267c032edb --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { DataStreamDetailPanel } from './data_stream_detail_panel'; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx index 590505bc529c76..85cbf9f12d7a82 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx @@ -5,14 +5,25 @@ */ import React from 'react'; +import { RouteComponentProps } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, EuiText, EuiSpacer, EuiEmptyPrompt } from '@elastic/eui'; +import { ScopedHistory } from 'kibana/public'; import { SectionError, SectionLoading, Error } from '../../../components'; import { useLoadDataStreams } from '../../../services/api'; import { DataStreamTable } from './data_stream_table'; -export const DataStreamList = () => { +interface MatchParams { + dataStreamName?: string; +} + +export const DataStreamList: React.FunctionComponent> = ({ + match: { + params: { dataStreamName }, + }, + history, +}) => { const { error, isLoading, data: dataStreams, sendRequest: reload } = useLoadDataStreams(); let content; @@ -65,8 +76,25 @@ export const DataStreamList = () => { /> + - + + + + {/* TODO: Implement this once we have something to put in here, e.g. storage size, docs count */} + {/* dataStreamName && ( + { + history.push('/data_streams'); + }} + /> + )*/} ); } diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx index 65cda5dea9f8d6..8f6d2841589bbf 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx @@ -7,16 +7,26 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiInMemoryTable, EuiBasicTableColumn, EuiButton } from '@elastic/eui'; +import { EuiInMemoryTable, EuiBasicTableColumn, EuiButton, EuiLink } from '@elastic/eui'; +import { ScopedHistory } from 'kibana/public'; import { DataStream } from '../../../../../../common/types'; +import { reactRouterNavigate } from '../../../../../shared_imports'; +import { encodePathForReactRouter } from '../../../../services/routing'; interface Props { dataStreams?: DataStream[]; reload: () => {}; + history: ScopedHistory; + filters?: string; } -export const DataStreamTable: React.FunctionComponent = ({ dataStreams, reload }) => { +export const DataStreamTable: React.FunctionComponent = ({ + dataStreams, + reload, + history, + filters, +}) => { const columns: Array> = [ { field: 'name', @@ -34,8 +44,18 @@ export const DataStreamTable: React.FunctionComponent = ({ dataStreams, r }), truncateText: true, sortable: true, - // TODO: Render as a deep-link into the indices tab - render: (indices: DataStream['indices']) => indices.length, + render: (indices: DataStream['indices'], dataStream) => ( + + {indices.length} + + ), }, { field: 'timeStampField', @@ -68,6 +88,7 @@ export const DataStreamTable: React.FunctionComponent = ({ dataStreams, r } as const; const searchConfig = { + query: filters, box: { incremental: true, }, diff --git a/x-pack/plugins/index_management/public/application/sections/home/home.tsx b/x-pack/plugins/index_management/public/application/sections/home/home.tsx index 5d04125a937886..f65f0fb09d2967 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/home.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/home.tsx @@ -116,7 +116,11 @@ export const IndexManagementHome: React.FunctionComponent - + = ({ history }) => { return (
- +
); -} +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index.ts similarity index 100% rename from x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index.js rename to x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index.ts diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_list.d.ts b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.container.d.ts similarity index 82% rename from x-pack/plugins/index_management/public/application/sections/home/index_list/index_list.d.ts rename to x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.container.d.ts index f03f483c0e821b..35ddfc48136173 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_list.d.ts +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.container.d.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export declare function IndexList(match: any): any; +export declare function IndexTable(props: any): any; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js index f33d486520a294..d51b7d1dbb0571 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js @@ -37,8 +37,10 @@ import { } from '@elastic/eui'; import { UIM_SHOW_DETAILS_CLICK } from '../../../../../../common/constants'; +import { reactRouterNavigate } from '../../../../../shared_imports'; import { REFRESH_RATE_INDEX_LIST } from '../../../../constants'; import { healthToColor } from '../../../../services'; +import { encodePathForReactRouter } from '../../../../services/routing'; import { AppContextConsumer } from '../../../../app_context'; import { renderBadges } from '../../../../lib/render_badges'; import { NoMatch, PageErrorForbidden } from '../../../../components'; @@ -117,6 +119,7 @@ export class IndexTable extends Component { } } } + componentWillUnmount() { clearInterval(this.interval); } @@ -146,11 +149,14 @@ export class IndexTable extends Component { const newIsSortAscending = sortField === column ? !isSortAscending : true; sortChanged(column, newIsSortAscending); }; + renderFilterError() { const { filterError } = this.state; + if (!filterError) { return; } + return ( <> @@ -169,6 +175,7 @@ export class IndexTable extends Component { ); } + onFilterChanged = ({ query, error }) => { if (error) { this.setState({ filterError: error }); @@ -177,6 +184,7 @@ export class IndexTable extends Component { this.setState({ filterError: null }); } }; + getFilters = (extensionsService) => { const { allIndices } = this.props; return extensionsService.filters.reduce((accum, filterExtension) => { @@ -184,6 +192,7 @@ export class IndexTable extends Component { return [...accum, ...filtersToAdd]; }, []); }; + toggleAll = () => { const allSelected = this.areAllItemsSelected(); if (allSelected) { @@ -243,7 +252,8 @@ export class IndexTable extends Component { } buildRowCell(fieldName, value, index, appServices) { - const { openDetailPanel, filterChanged } = this.props; + const { openDetailPanel, filterChanged, history } = this.props; + if (fieldName === 'health') { return {value}; } else if (fieldName === 'name') { @@ -261,7 +271,18 @@ export class IndexTable extends Component { {renderBadges(index, filterChanged, appServices.extensionsService)} ); + } else if (fieldName === 'data_stream') { + return ( + + {value} + + ); } + return value; } @@ -480,12 +501,14 @@ export class IndexTable extends Component { + {(indicesLoading && allIndices.length === 0) || indicesError ? null : ( {extensionsService.toggles.map((toggle) => { return this.renderToggleControl(toggle); })} + + + {this.renderBanners(extensionsService)} + {indicesError && this.renderError()} + {atLeastOneItemSelected ? ( @@ -523,6 +550,7 @@ export class IndexTable extends Component { /> ) : null} + {(indicesLoading && allIndices.length === 0) || indicesError ? null : ( @@ -572,8 +600,11 @@ export class IndexTable extends Component { )} + {this.renderFilterError()} + + {indices.length > 0 ? (
@@ -586,6 +617,7 @@ export class IndexTable extends Component { /> + {this.buildHeader()} + {this.buildRows(services)}
) : ( emptyState )} + + {indices.length > 0 ? this.renderPager() : null} ); diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx index ec2956973d4f6b..807229fb362676 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx @@ -38,7 +38,7 @@ import { Error, } from '../../../../../components'; import { useLoadIndexTemplate } from '../../../../../services/api'; -import { decodePath } from '../../../../../services/routing'; +import { decodePathFromReactRouter } from '../../../../../services/routing'; import { SendRequestResponse } from '../../../../../../shared_imports'; import { useServices } from '../../../../../app_context'; import { TabSummary, TabMappings, TabSettings, TabAliases } from '../../template_details/tabs'; @@ -107,7 +107,7 @@ export const LegacyTemplateDetails: React.FunctionComponent = ({ reload, }) => { const { uiMetricService } = useServices(); - const decodedTemplateName = decodePath(templateName); + const decodedTemplateName = decodePathFromReactRouter(templateName); const { error, data: templateDetails, isLoading } = useLoadIndexTemplate( decodedTemplateName, isLegacy diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_table/template_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_table/template_table.tsx index 92fedd5d68f002..edce05018ce395 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_table/template_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_table/template_table.tsx @@ -9,12 +9,12 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiInMemoryTable, EuiIcon, EuiButton, EuiLink, EuiBasicTableColumn } from '@elastic/eui'; import { ScopedHistory } from 'kibana/public'; -import { reactRouterNavigate } from '../../../../../../../../../../src/plugins/kibana_react/public'; +import { SendRequestResponse, reactRouterNavigate } from '../../../../../../shared_imports'; import { TemplateListItem } from '../../../../../../../common'; import { UIM_TEMPLATE_SHOW_DETAILS_CLICK } from '../../../../../../../common/constants'; import { TemplateDeleteModal } from '../../../../../components'; +import { encodePathForReactRouter } from '../../../../../services/routing'; import { useServices } from '../../../../../app_context'; -import { SendRequestResponse } from '../../../../../../shared_imports'; interface Props { templates: TemplateListItem[]; @@ -52,7 +52,7 @@ export const LegacyTemplateTable: React.FunctionComponent = ({ {...reactRouterNavigate( history, { - pathname: `/templates/${encodeURIComponent(encodeURIComponent(name))}`, + pathname: `/templates/${encodePathForReactRouter(name)}`, search: `legacy=${Boolean(item._kbnMeta.isLegacy)}`, }, () => uiMetricService.trackMetric('click', UIM_TEMPLATE_SHOW_DETAILS_CLICK) diff --git a/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx b/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx index 8bdd230f899524..82835c56a38775 100644 --- a/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx +++ b/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx @@ -11,7 +11,7 @@ import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui'; import { TemplateDeserialized } from '../../../../common'; import { TemplateForm, SectionLoading, SectionError, Error } from '../../components'; import { breadcrumbService } from '../../services/breadcrumbs'; -import { decodePath, getTemplateDetailsLink } from '../../services/routing'; +import { decodePathFromReactRouter, getTemplateDetailsLink } from '../../services/routing'; import { saveTemplate, useLoadIndexTemplate } from '../../services/api'; import { getIsLegacyFromQueryParams } from '../../lib/index_templates'; @@ -26,7 +26,7 @@ export const TemplateClone: React.FunctionComponent { - const decodedTemplateName = decodePath(name); + const decodedTemplateName = decodePathFromReactRouter(name); const isLegacy = getIsLegacyFromQueryParams(location); const [isSaving, setIsSaving] = useState(false); diff --git a/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx b/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx index d3e539989bc96c..7cacb5ee97a601 100644 --- a/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx +++ b/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx @@ -11,7 +11,7 @@ import { EuiPageBody, EuiPageContent, EuiTitle, EuiSpacer, EuiCallOut } from '@e import { TemplateDeserialized } from '../../../../common'; import { breadcrumbService } from '../../services/breadcrumbs'; import { useLoadIndexTemplate, updateTemplate } from '../../services/api'; -import { decodePath, getTemplateDetailsLink } from '../../services/routing'; +import { decodePathFromReactRouter, getTemplateDetailsLink } from '../../services/routing'; import { SectionLoading, SectionError, TemplateForm, Error } from '../../components'; import { getIsLegacyFromQueryParams } from '../../lib/index_templates'; @@ -26,7 +26,7 @@ export const TemplateEdit: React.FunctionComponent { - const decodedTemplateName = decodePath(name); + const decodedTemplateName = decodePathFromReactRouter(name); const isLegacy = getIsLegacyFromQueryParams(location); const [isSaving, setIsSaving] = useState(false); diff --git a/x-pack/plugins/index_management/public/application/services/api.ts b/x-pack/plugins/index_management/public/application/services/api.ts index daf5cd9b4fbcc3..624ed875e476cb 100644 --- a/x-pack/plugins/index_management/public/application/services/api.ts +++ b/x-pack/plugins/index_management/public/application/services/api.ts @@ -53,6 +53,14 @@ export function useLoadDataStreams() { }); } +// TODO: Implement this API endpoint once we have content to surface in the detail panel. +export function useLoadDataStream(name: string) { + return useRequest({ + path: `${API_BASE_PATH}/data_stream/${encodeURIComponent(name)}`, + method: 'get', + }); +} + export async function loadIndices() { const response = await httpService.httpClient.get(`${API_BASE_PATH}/indices`); return response.data ? response.data : response; diff --git a/x-pack/plugins/index_management/public/application/services/routing.ts b/x-pack/plugins/index_management/public/application/services/routing.ts index a999c58f5bb429..2a895196189d09 100644 --- a/x-pack/plugins/index_management/public/application/services/routing.ts +++ b/x-pack/plugins/index_management/public/application/services/routing.ts @@ -6,10 +6,8 @@ export const getTemplateListLink = () => `/templates`; -// Need to add some additonal encoding/decoding logic to work with React Router -// For background, see: https://github.com/ReactTraining/history/issues/505 export const getTemplateDetailsLink = (name: string, isLegacy?: boolean, withHash = false) => { - const baseUrl = `/templates/${encodeURIComponent(encodeURIComponent(name))}`; + const baseUrl = `/templates/${encodePathForReactRouter(name)}`; let url = withHash ? `#${baseUrl}` : baseUrl; if (isLegacy) { url = `${url}?legacy=${isLegacy}`; @@ -18,18 +16,14 @@ export const getTemplateDetailsLink = (name: string, isLegacy?: boolean, withHas }; export const getTemplateEditLink = (name: string, isLegacy?: boolean) => { - return encodeURI( - `/edit_template/${encodeURIComponent(encodeURIComponent(name))}?legacy=${isLegacy === true}` - ); + return encodeURI(`/edit_template/${encodePathForReactRouter(name)}?legacy=${isLegacy === true}`); }; export const getTemplateCloneLink = (name: string, isLegacy?: boolean) => { - return encodeURI( - `/clone_template/${encodeURIComponent(encodeURIComponent(name))}?legacy=${isLegacy === true}` - ); + return encodeURI(`/clone_template/${encodePathForReactRouter(name)}?legacy=${isLegacy === true}`); }; -export const decodePath = (pathname: string): string => { +export const decodePathFromReactRouter = (pathname: string): string => { let decodedPath; try { decodedPath = decodeURI(pathname); @@ -39,3 +33,8 @@ export const decodePath = (pathname: string): string => { } return decodeURIComponent(decodedPath); }; + +// Need to add some additonal encoding/decoding logic to work with React Router +// For background, see: https://github.com/ReactTraining/history/issues/505 +export const encodePathForReactRouter = (pathname: string): string => + encodeURIComponent(encodeURIComponent(pathname)); diff --git a/x-pack/plugins/index_management/public/shared_imports.ts b/x-pack/plugins/index_management/public/shared_imports.ts index 89423672615113..afd5a5cf650e1e 100644 --- a/x-pack/plugins/index_management/public/shared_imports.ts +++ b/x-pack/plugins/index_management/public/shared_imports.ts @@ -31,3 +31,5 @@ export { export { getFormRow, Field } from '../../../../src/plugins/es_ui_shared/static/forms/components'; export { isJSON } from '../../../../src/plugins/es_ui_shared/static/validators/string'; + +export { reactRouterNavigate } from '../../../../src/plugins/kibana_react/public'; From 97369b0fe8aad5798e036d36faf7c5551e5255c4 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Fri, 5 Jun 2020 16:11:02 -0700 Subject: [PATCH 08/11] Add component integration tests for cross-linking between Indices and Data Streams tabs. Fix TS error with API integration test. --- .../helpers/test_subjects.ts | 20 +++++---- .../home/data_streams_tab.helpers.ts | 25 +++++++++-- .../home/data_streams_tab.test.ts | 42 ++++++++++++++++-- .../home/indices_tab.helpers.ts | 27 ++++++++++-- .../home/indices_tab.test.ts | 44 +++++++++++++++++++ .../data_stream_table/data_stream_table.tsx | 1 + .../index_list/index_table/index_table.js | 3 +- .../index_management/data_streams.ts | 4 +- 8 files changed, 143 insertions(+), 23 deletions(-) diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts index 5e17da2b46696b..a2ed114ae1459b 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts @@ -9,23 +9,27 @@ export type TestSubjects = | 'appTitle' | 'cell' | 'closeDetailsButton' - | 'createTemplateButton' | 'createLegacyTemplateButton' + | 'createTemplateButton' + | 'dataStreamTable' | 'dataStreamTable' | 'deleteSystemTemplateCallOut' | 'deleteTemplateButton' | 'deleteTemplatesConfirmation' | 'documentationLink' | 'emptyPrompt' + | 'filterList.filterItem' + | 'indexTable' + | 'indexTableIncludeHiddenIndicesToggle' + | 'indexTableIndexNameLink' + | 'indicesList' + | 'indicesTab' + | 'legacyTemplateTable' | 'manageTemplateButton' | 'mappingsTab' | 'noAliasesCallout' | 'noMappingsCallout' | 'noSettingsCallout' - | 'indicesList' - | 'indicesTab' - | 'indexTableIncludeHiddenIndicesToggle' - | 'indexTableIndexNameLink' | 'reloadButton' | 'reloadIndicesButton' | 'row' @@ -41,8 +45,6 @@ export type TestSubjects = | 'templateDetails.tab' | 'templateDetails.title' | 'templateList' - | 'templateTable' | 'templatesTab' - | 'legacyTemplateTable' - | 'viewButton' - | 'filterList.filterItem'; + | 'templateTable' + | 'viewButton'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts index 74b11eabf18b30..9ab63f4ab232dd 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts @@ -4,10 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { registerTestBed, TestBed, TestBedConfig } from '../../../../../test_utils'; +import { act } from 'react-dom/test-utils'; + +import { + registerTestBed, + TestBed, + TestBedConfig, + findTestSubject, +} from '../../../../../test_utils'; import { DataStream } from '../../../common'; -// NOTE: We have to use the Home component instead of the DataStreamList component because we depend -// upon react router to provide the name of the template to load in the detail panel. import { IndexManagementHome } from '../../../public/application/sections/home'; // eslint-disable-line @kbn/eslint/no-restricted-paths import { indexManagementStore } from '../../../public/application/store'; // eslint-disable-line @kbn/eslint/no-restricted-paths import { WithAppDependencies, services, TestSubjects } from '../helpers'; @@ -27,6 +32,7 @@ export interface DataStreamsTabTestBed extends TestBed { actions: { goToDataStreamsList: () => void; clickReloadButton: () => void; + clickIndicesAt: (index: number) => void; }; } @@ -46,11 +52,24 @@ export const setup = async (): Promise => { find('reloadButton').simulate('click'); }; + const clickIndicesAt = async (index: number) => { + const { component, table, router } = testBed; + const { rows } = table.getMetaData('dataStreamTable'); + const indicesLink = findTestSubject(rows[index].reactWrapper, 'indicesLink'); + + await act(async () => { + router.navigateTo(indicesLink.props().href!); + }); + + component.update(); + }; + return { ...testBed, actions: { goToDataStreamsList, clickReloadButton, + clickIndicesAt, }, }; }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts index bd09e425c20435..1116ed8f94c94f 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts @@ -20,7 +20,31 @@ describe('Data Streams tab', () => { }); beforeEach(async () => { - httpRequestsMockHelpers.setLoadIndicesResponse([]); + httpRequestsMockHelpers.setLoadIndicesResponse([ + { + health: '', + status: '', + primary: '', + replica: '', + documents: '', + documents_deleted: '', + size: '', + primary_size: '', + name: 'data-stream-index', + data_stream: 'dataStream1', + }, + { + health: 'green', + status: 'open', + primary: 1, + replica: 1, + documents: 10000, + documents_deleted: 100, + size: '156kb', + primary_size: '156kb', + name: 'non-data-stream-index', + }, + ]); await act(async () => { testBed = await setup(); @@ -40,7 +64,7 @@ describe('Data Streams tab', () => { component.update(); }); - test('should display an empty prompt', async () => { + test('displays an empty prompt', async () => { const { exists } = testBed; expect(exists('sectionLoading')).toBe(false); @@ -64,7 +88,7 @@ describe('Data Streams tab', () => { component.update(); }); - test('should list them in the table', async () => { + test('lists them in the table', async () => { const { table } = testBed; const { tableCellsValues } = table.getMetaData('dataStreamTable'); @@ -75,7 +99,7 @@ describe('Data Streams tab', () => { ]); }); - test('should have a button to reload the data streams', async () => { + test('has a button to reload the data streams', async () => { const { exists, actions } = testBed; const totalRequests = server.requests.length; @@ -88,5 +112,15 @@ describe('Data Streams tab', () => { expect(server.requests.length).toBe(totalRequests + 1); expect(server.requests[server.requests.length - 1].url).toBe(`${API_BASE_PATH}/data_streams`); }); + + test('clicking the indices count navigates to the backing indices', async () => { + const { table, actions } = testBed; + + await actions.clickIndicesAt(0); + + expect(table.getMetaData('indexTable').tableCellsValues).toEqual([ + ['', '', '', '', '', '', '', 'dataStream1'], + ]); + }); }); }); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts index e995932dfa00d6..f00348aacbf085 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts @@ -6,8 +6,13 @@ import { act } from 'react-dom/test-utils'; -import { registerTestBed, TestBed, TestBedConfig } from '../../../../../test_utils'; -import { IndexList } from '../../../public/application/sections/home/index_list'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { + registerTestBed, + TestBed, + TestBedConfig, + findTestSubject, +} from '../../../../../test_utils'; +import { IndexManagementHome } from '../../../public/application/sections/home'; // eslint-disable-line @kbn/eslint/no-restricted-paths import { indexManagementStore } from '../../../public/application/store'; // eslint-disable-line @kbn/eslint/no-restricted-paths import { WithAppDependencies, services, TestSubjects } from '../helpers'; @@ -15,18 +20,19 @@ const testBedConfig: TestBedConfig = { store: () => indexManagementStore(services as any), memoryRouter: { initialEntries: [`/indices?includeHiddenIndices=true`], - componentRoutePath: `/:section(indices|templates)`, + componentRoutePath: `/:section(indices|data_streams)`, }, doMountAsync: true, }; -const initTestBed = registerTestBed(WithAppDependencies(IndexList), testBedConfig); +const initTestBed = registerTestBed(WithAppDependencies(IndexManagementHome), testBedConfig); export interface IndicesTestBed extends TestBed { actions: { selectIndexDetailsTab: (tab: 'settings' | 'mappings' | 'stats' | 'edit_settings') => void; getIncludeHiddenIndicesToggleStatus: () => boolean; clickIncludeHiddenIndicesToggle: () => void; + clickDataStreamAt: (index: number) => void; }; } @@ -59,12 +65,25 @@ export const setup = async (): Promise => { component.update(); }; + const clickDataStreamAt = async (index: number) => { + const { component, table, router } = testBed; + const { rows } = table.getMetaData('indexTable'); + const dataStreamLink = findTestSubject(rows[index].reactWrapper, 'dataStreamLink'); + + await act(async () => { + router.navigateTo(dataStreamLink.props().href!); + }); + + component.update(); + }; + return { ...testBed, actions: { selectIndexDetailsTab, getIncludeHiddenIndicesToggleStatus, clickIncludeHiddenIndicesToggle, + clickDataStreamAt, }, }; }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts index 11c25ffbb590f5..c2d955bb4dfce8 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts @@ -9,6 +9,7 @@ import { act } from 'react-dom/test-utils'; import { API_BASE_PATH } from '../../../common/constants'; import { setupEnvironment, nextTick } from '../helpers'; import { IndicesTestBed, setup } from './indices_tab.helpers'; +import { createDataStreamPayload } from './data_streams_tab.helpers'; /** * The below import is required to avoid a console error warn from the "brace" package @@ -52,6 +53,49 @@ describe('', () => { }); }); + describe('data stream column', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadIndicesResponse([ + { + health: '', + status: '', + primary: '', + replica: '', + documents: '', + documents_deleted: '', + size: '', + primary_size: '', + name: 'data-stream-index', + data_stream: 'dataStream1', + }, + ]); + + httpRequestsMockHelpers.setLoadDataStreamsResponse([ + createDataStreamPayload('dataStream1'), + createDataStreamPayload('dataStream2'), + ]); + + testBed = await setup(); + + await act(async () => { + const { component } = testBed; + + await nextTick(); + component.update(); + }); + }); + + test('navigates to the data stream in the Data Streams tab', async () => { + const { table, actions } = testBed; + + await actions.clickDataStreamAt(0); + + expect(table.getMetaData('dataStreamTable').tableCellsValues).toEqual([ + ['dataStream1', '1', '@timestamp', '1'], + ]); + }); + }); + describe('index detail panel with % character in index name', () => { const indexName = 'test%'; beforeEach(async () => { diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx index 8f6d2841589bbf..54b215e561b462 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx @@ -46,6 +46,7 @@ export const DataStreamTable: React.FunctionComponent = ({ sortable: true, render: (indices: DataStream['indices'], dataStream) => ( 0 ? (
- + { + const createDataStream = (name: string) => { // A data stream requires an index template before it can be created. return supertest .post(`${API_BASE_PATH}/index-templates`) @@ -38,7 +38,7 @@ export default function ({ getService }: FtrProviderContext) { ); }; - const deleteDataStream = (name) => { + const deleteDataStream = (name: string) => { return supertest .post(`${API_BASE_PATH}/delete-index-templates`) .set('kbn-xsrf', 'xxx') From e7d48d1761fa21484a53fe39c6c0961008dc447e Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Tue, 9 Jun 2020 11:02:30 -0700 Subject: [PATCH 09/11] Address Alison's feedback. - Add link to Index Templates from Data Streams empty prompt and component integration test. - Extend legacy ES client with index templates API and unskip API integration test. - Add comment to unused detail panel file. --- .../helpers/test_subjects.ts | 1 + .../home/data_streams_tab.helpers.ts | 16 ++++++- .../home/data_streams_tab.test.ts | 11 +++++ .../data_stream_detail_panel.tsx | 5 ++ .../data_stream_list/data_stream_list.tsx | 32 +++++++++++-- .../server/client/elasticsearch.ts | 30 ++++++++++++ .../index_management/data_streams.ts | 46 ++++++++++++------- 7 files changed, 119 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts index a2ed114ae1459b..4e297118b0fdd9 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts @@ -11,6 +11,7 @@ export type TestSubjects = | 'closeDetailsButton' | 'createLegacyTemplateButton' | 'createTemplateButton' + | 'dataStreamsEmptyPromptTemplateLink' | 'dataStreamTable' | 'dataStreamTable' | 'deleteSystemTemplateCallOut' diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts index 9ab63f4ab232dd..ef6aca44a1754c 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts @@ -21,7 +21,7 @@ const testBedConfig: TestBedConfig = { store: () => indexManagementStore(services as any), memoryRouter: { initialEntries: [`/indices`], - componentRoutePath: `/:section(indices|data_streams)`, + componentRoutePath: `/:section(indices|data_streams|templates)`, }, doMountAsync: true, }; @@ -31,6 +31,7 @@ const initTestBed = registerTestBed(WithAppDependencies(IndexManagementHome), te export interface DataStreamsTabTestBed extends TestBed { actions: { goToDataStreamsList: () => void; + clickEmptyPromptIndexTemplateLink: () => void; clickReloadButton: () => void; clickIndicesAt: (index: number) => void; }; @@ -47,6 +48,18 @@ export const setup = async (): Promise => { testBed.find('data_streamsTab').simulate('click'); }; + const clickEmptyPromptIndexTemplateLink = async () => { + const { find, component, router } = testBed; + + const templateLink = find('dataStreamsEmptyPromptTemplateLink'); + + await act(async () => { + router.navigateTo(templateLink.props().href!); + }); + + component.update(); + }; + const clickReloadButton = () => { const { find } = testBed; find('reloadButton').simulate('click'); @@ -68,6 +81,7 @@ export const setup = async (): Promise => { ...testBed, actions: { goToDataStreamsList, + clickEmptyPromptIndexTemplateLink, clickReloadButton, clickIndicesAt, }, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts index 1116ed8f94c94f..0a284f311d8902 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts @@ -56,6 +56,7 @@ describe('Data Streams tab', () => { const { actions, component } = testBed; httpRequestsMockHelpers.setLoadDataStreamsResponse([]); + httpRequestsMockHelpers.setLoadTemplatesResponse({ templates: [], legacyTemplates: [] }); await act(async () => { actions.goToDataStreamsList(); @@ -70,6 +71,16 @@ describe('Data Streams tab', () => { expect(exists('sectionLoading')).toBe(false); expect(exists('emptyPrompt')).toBe(true); }); + + test('goes to index templates tab when "Get started" link is clicked', async () => { + const { actions, exists, component } = testBed; + + await act(async () => { + actions.clickEmptyPromptIndexTemplateLink(); + }); + + expect(exists('templateList')).toBe(true); + }); }); describe('when there are data streams', () => { diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx index 8051153e5aacac..a6c8b83a05f989 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx @@ -25,6 +25,11 @@ interface Props { onClose: () => void; } +/** + * NOTE: This currently isn't in use by data_stream_list.tsx because it doesn't contain any + * information that doesn't already exist in the table. We'll use it once we add additional + * info, e.g. storage size, docs count. + */ export const DataStreamDetailPanel: React.FunctionComponent = ({ dataStreamName, onClose, diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx index 85cbf9f12d7a82..951c4a0d7f3c31 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx @@ -7,9 +7,11 @@ import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiTitle, EuiText, EuiSpacer, EuiEmptyPrompt } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EuiTitle, EuiText, EuiSpacer, EuiEmptyPrompt, EuiLink } from '@elastic/eui'; import { ScopedHistory } from 'kibana/public'; +import { reactRouterNavigate } from '../../../../shared_imports'; import { SectionError, SectionLoading, Error } from '../../../components'; import { useLoadDataStreams } from '../../../services/api'; import { DataStreamTable } from './data_stream_table'; @@ -32,7 +34,7 @@ export const DataStreamList: React.FunctionComponent @@ -42,7 +44,7 @@ export const DataStreamList: React.FunctionComponent } @@ -56,11 +58,33 @@ export const DataStreamList: React.FunctionComponent } + body={ +

+ + {i18n.translate('xpack.idxMgmt.dataStreamList.emptyPrompt.getStartedLink', { + defaultMessage: 'composable index template', + })} + + ), + }} + /> +

+ } data-test-subj="emptyPrompt" /> ); diff --git a/x-pack/plugins/index_management/server/client/elasticsearch.ts b/x-pack/plugins/index_management/server/client/elasticsearch.ts index 23d3c78481dd75..bfd47943cf61bd 100644 --- a/x-pack/plugins/index_management/server/client/elasticsearch.ts +++ b/x-pack/plugins/index_management/server/client/elasticsearch.ts @@ -99,4 +99,34 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any) ], method: 'DELETE', }); + + // Index templates v2 + dataManagement.saveComposableIndexTemplate = ca({ + urls: [ + { + fmt: '/_index_template/<%=name%>', + req: { + name: { + type: 'string', + }, + }, + }, + ], + needBody: true, + method: 'PUT', + }); + + dataManagement.deleteComposableIndexTemplate = ca({ + urls: [ + { + fmt: '/_index_template/<%=name%>', + req: { + name: { + type: 'string', + }, + }, + }, + ], + method: 'DELETE', + }); }; diff --git a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts index ed29071da1154d..4b8adec1283a33 100644 --- a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts +++ b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts @@ -21,14 +21,17 @@ export default function ({ getService }: FtrProviderContext) { const createDataStream = (name: string) => { // A data stream requires an index template before it can be created. - return supertest - .post(`${API_BASE_PATH}/index-templates`) - .set('kbn-xsrf', 'xxx') - .send({ + return es.dataManagement + .saveComposableIndexTemplate({ name, - indexPatterns: ['*'], - _kbnMeta: { - isLegacy: false, + body: { + index_patterns: ['*'], + template: { + settings: {}, + }, + data_stream: { + timestamp_field: '@timestamp', + }, }, }) .then(() => @@ -39,11 +42,9 @@ export default function ({ getService }: FtrProviderContext) { }; const deleteDataStream = (name: string) => { - return supertest - .post(`${API_BASE_PATH}/delete-index-templates`) - .set('kbn-xsrf', 'xxx') - .send({ - templates: [{ name, isLegacy: false }], + return es.dataManagement + .deleteComposableIndexTemplate({ + name, }) .then(() => es.dataManagement.deleteDataStream({ @@ -53,10 +54,11 @@ export default function ({ getService }: FtrProviderContext) { }; describe('Data streams', function () { - // TODO: Implement this test once the API supports creating composable index templates. - describe.skip('Get', () => { - before(() => createDataStream('test-data-stream')); - after(() => deleteDataStream('test-data-stream')); + const testDataStreamName = 'test-data-stream'; + + describe('Get', () => { + before(() => createDataStream(testDataStreamName)); + after(() => deleteDataStream(testDataStreamName)); describe('all data streams', () => { it('returns an array of data streams', async () => { @@ -65,9 +67,19 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .expect(200); + // ES determines this value so we'll just echo it back. + const { uuid } = dataStreams[0].indices[0]; expect(dataStreams).to.eql([ { - name: 'test-data-stream', + name: testDataStreamName, + timeStampField: '@timestamp', + indices: [ + { + name: `${testDataStreamName}-000001`, + uuid, + }, + ], + generation: 1, }, ]); }); From f797a23eebcb6e100ee54dbe66c9bdf45245bbb5 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Tue, 9 Jun 2020 12:04:48 -0700 Subject: [PATCH 10/11] Make API integration test more robust. --- .../apis/management/index_management/data_streams.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts index 4b8adec1283a33..9a8511b4331ea2 100644 --- a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts +++ b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts @@ -57,8 +57,8 @@ export default function ({ getService }: FtrProviderContext) { const testDataStreamName = 'test-data-stream'; describe('Get', () => { - before(() => createDataStream(testDataStreamName)); - after(() => deleteDataStream(testDataStreamName)); + before(async () => await createDataStream(testDataStreamName)); + after(async () => await deleteDataStream(testDataStreamName)); describe('all data streams', () => { it('returns an array of data streams', async () => { @@ -67,15 +67,15 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .expect(200); - // ES determines this value so we'll just echo it back. - const { uuid } = dataStreams[0].indices[0]; + // ES determines these values so we'll just echo them back. + const { name: indexName, uuid } = dataStreams[0].indices[0]; expect(dataStreams).to.eql([ { name: testDataStreamName, timeStampField: '@timestamp', indices: [ { - name: `${testDataStreamName}-000001`, + name: indexName, uuid, }, ], From ddc8666099d3286bac15bb77a670029930cd9607 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Tue, 9 Jun 2020 14:13:25 -0700 Subject: [PATCH 11/11] Fix TS error. --- .../__jest__/client_integration/home/data_streams_tab.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts index 0a284f311d8902..efe2e2d0c74aee 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts @@ -73,7 +73,7 @@ describe('Data Streams tab', () => { }); test('goes to index templates tab when "Get started" link is clicked', async () => { - const { actions, exists, component } = testBed; + const { actions, exists } = testBed; await act(async () => { actions.clickEmptyPromptIndexTemplateLink();