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 0a054fccdfcc1..82ee841eb7398 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 @@ -97,8 +97,11 @@ const registerHttpRequestMockHelpers = ( const setLoadIndexSettingsResponse = (response?: HttpResponse, error?: ResponseError) => mockResponse('GET', `${API_BASE_PATH}/settings/:name`, response, error); - const setLoadIndexMappingResponse = (response?: HttpResponse, error?: ResponseError) => - mockResponse('GET', `${API_BASE_PATH}/mapping/:name`, response, error); + const setLoadIndexMappingResponse = ( + indexName: string, + response?: HttpResponse, + error?: ResponseError + ) => mockResponse('GET', `${API_BASE_PATH}/mapping/${indexName}`, response, error); const setLoadIndexStatsResponse = (response?: HttpResponse, error?: ResponseError) => mockResponse('GET', `${API_BASE_PATH}/stats/:name`, response, error); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts index 43d436c495799..e31e368628c86 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts @@ -38,6 +38,12 @@ export interface IndexDetailsPageTestBed extends TestBed { getHeader: () => string; clickIndexDetailsTab: (tab: IndexDetailsSection) => Promise; getActiveTabContent: () => string; + mappings: { + getCodeBlockContent: () => string; + getDocsLinkHref: () => string; + isErrorDisplayed: () => boolean; + clickErrorReloadButton: () => Promise; + }; clickBackToIndicesButton: () => Promise; discoverLinkExists: () => boolean; contextMenu: { @@ -91,6 +97,24 @@ export const setup = async ( return find('indexDetailsContent').text(); }; + const mappings = { + getCodeBlockContent: () => { + return find('indexDetailsMappingsCodeBlock').text(); + }, + getDocsLinkHref: () => { + return find('indexDetailsMappingsDocsLink').prop('href'); + }, + isErrorDisplayed: () => { + return exists('indexDetailsMappingsError'); + }, + clickErrorReloadButton: async () => { + await act(async () => { + find('indexDetailsMappingsReloadButton').simulate('click'); + }); + component.update(); + }, + }; + const clickBackToIndicesButton = async () => { await act(async () => { find('indexDetailsBackToIndicesButton').simulate('click'); @@ -142,6 +166,7 @@ export const setup = async ( getHeader, clickIndexDetailsTab, getActiveTabContent, + mappings, clickBackToIndicesButton, discoverLinkExists, contextMenu, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.ts index 7b144fd0bad40..e4b4c33489262 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.ts @@ -9,7 +9,7 @@ import { setupEnvironment } from '../helpers'; import { IndexDetailsPageTestBed, setup } from './index_details_page.helpers'; import { act } from 'react-dom/test-utils'; import { IndexDetailsSection } from '../../../public/application/sections/home/index_list/details_page'; -import { testIndexMock, testIndexName } from './mocks'; +import { testIndexMappings, testIndexMock, testIndexName } from './mocks'; import { API_BASE_PATH, INTERNAL_API_BASE_PATH } from '../../../common'; describe('', () => { @@ -22,6 +22,7 @@ describe('', () => { ({ httpSetup, httpRequestsMockHelpers } = mockEnvironment); // testIndexName is configured in initialEntries of the memory router httpRequestsMockHelpers.setLoadIndexDetailsResponse(testIndexName, testIndexMock); + httpRequestsMockHelpers.setLoadIndexMappingResponse(testIndexName, testIndexMappings); await act(async () => { testBed = await setup(httpSetup, { @@ -84,10 +85,59 @@ describe('', () => { expect(tabContent).toEqual('Documents'); }); - it('mappings tab', async () => { - await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings); - const tabContent = testBed.actions.getActiveTabContent(); - expect(tabContent).toEqual('Mappings'); + describe('mappings tab', () => { + it('loads mappings from the API', async () => { + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings); + expect(httpSetup.get).toHaveBeenLastCalledWith(`${API_BASE_PATH}/mapping/${testIndexName}`, { + asSystemRequest: undefined, + body: undefined, + query: undefined, + version: undefined, + }); + }); + + it('displays the mappings in the code block', async () => { + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings); + + const tabContent = testBed.actions.mappings.getCodeBlockContent(); + expect(tabContent).toEqual(JSON.stringify(testIndexMappings, null, 2)); + }); + + it('sets the docs link href from the documenation service', async () => { + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings); + const docsLinkHref = testBed.actions.mappings.getDocsLinkHref(); + // the url from the mocked docs mock + expect(docsLinkHref).toEqual( + 'https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/mapping.html' + ); + }); + + describe('error loading mappings', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadIndexMappingResponse(testIndexName, undefined, { + statusCode: 400, + message: `Was not able to load mappings`, + }); + await act(async () => { + testBed = await setup(httpSetup); + }); + + testBed.component.update(); + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings); + }); + + it('there is an error prompt', async () => { + expect(testBed.actions.mappings.isErrorDisplayed()).toBe(true); + }); + + it('resends a request when reload button is clicked', async () => { + // already sent 3 requests while setting up the component + const numberOfRequests = 3; + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests); + await testBed.actions.mappings.clickErrorReloadButton(); + expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1); + }); + }); }); it('settings tab', async () => { diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/mocks.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/mocks.ts index 5e165fe0702e6..bda26b3c2368c 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/mocks.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/mocks.ts @@ -29,3 +29,15 @@ export const testIndexMock: Index = { }, isFollowerIndex: false, }; + +export const testIndexMappings = { + mappings: { + dynamic: 'false', + dynamic_templates: [], + properties: { + '@timestamp': { + type: 'date', + }, + }, + }, +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx index 6d91b3a7991a1..5464e3ebf0ac2 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page.tsx @@ -18,12 +18,14 @@ import { } from '@elastic/eui'; import { SectionLoading } from '@kbn/es-ui-shared-plugin/public'; +import { css } from '@emotion/react'; import { Index } from '../../../../../../common'; import { loadIndex } from '../../../../services'; import { DiscoverLink } from '../../../../lib/discover_link'; import { Section } from '../../home'; import { DetailsPageError } from './details_page_error'; import { ManageIndexButton } from './manage_index_button'; +import { DetailsPageMappings } from './details_page_mappings'; export enum IndexDetailsSection { Overview = 'overview', @@ -164,7 +166,12 @@ export const DetailsPage: React.FunctionComponent< -
+
Documents
} />
Mappings
} + path={`/${Section.Indices}/:indexName/${IndexDetailsSection.Mappings}`} + component={DetailsPageMappings} />
- - -
-
{JSON.stringify(index, null, 2)}
-
); }; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings.tsx new file mode 100644 index 0000000000000..ca8556599f224 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings.tsx @@ -0,0 +1,168 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent } from 'react'; +import { + EuiButton, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiPageTemplate, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { RouteComponentProps } from 'react-router-dom'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { SectionLoading } from '@kbn/es-ui-shared-plugin/public'; +import { useLoadIndexMappings, documentationService } from '../../../../services'; + +export const DetailsPageMappings: FunctionComponent> = ({ + match: { + params: { indexName }, + }, +}) => { + const { isLoading, data, error, resendRequest } = useLoadIndexMappings(indexName); + + if (isLoading) { + return ( + + + + ); + } + if (error) { + return ( + + + + } + body={ + <> + + + + + + + + + } + /> + ); + } + + return ( + // using "rowReverse" to keep docs links on the top of the mappings code block on smaller screen + + + + + + + + + +

+ +

+
+
+
+ + +

+ +

+
+ + + + +
+
+ + + + + {JSON.stringify(data, null, 2)} + + + +
+ ); +}; 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 b19382364722c..829e4dda357b0 100644 --- a/x-pack/plugins/index_management/public/application/services/api.ts +++ b/x-pack/plugins/index_management/public/application/services/api.ts @@ -315,7 +315,14 @@ export function useLoadNodesPlugins() { export function loadIndex(indexName: string) { return sendRequest({ - path: `${INTERNAL_API_BASE_PATH}/indices/${indexName}`, + path: `${INTERNAL_API_BASE_PATH}/indices/${encodeURIComponent(indexName)}`, + method: 'get', + }); +} + +export function useLoadIndexMappings(indexName: string) { + return useRequest({ + path: `${API_BASE_PATH}/mapping/${encodeURIComponent(indexName)}`, method: 'get', }); } diff --git a/x-pack/plugins/index_management/public/application/services/index.ts b/x-pack/plugins/index_management/public/application/services/index.ts index 5cb65c04b6c9d..5d3cd1f52efe6 100644 --- a/x-pack/plugins/index_management/public/application/services/index.ts +++ b/x-pack/plugins/index_management/public/application/services/index.ts @@ -25,6 +25,7 @@ export { simulateIndexTemplate, useLoadNodesPlugins, loadIndex, + useLoadIndexMappings, } from './api'; export { sortTable } from './sort_table'; @@ -32,3 +33,4 @@ export { sortTable } from './sort_table'; export { UiMetricService } from './ui_metric'; export { HttpService } from './http'; export { NotificationService } from './notification'; +export { documentationService } from './documentation';