mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Onboarding] Add mappings component to index details page (#193314)
## Summary This PR adds mappings component to index detail page. <img width="1719" alt="Screenshot 2024-09-18 at 11 26 55 AM" src="https://github.com/user-attachments/assets/5d10f372-fdf5-4452-82ea-f6b2e29398af"> ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed
This commit is contained in:
parent
f32ba5ce6c
commit
0c222addbb
9 changed files with 144 additions and 40 deletions
|
@ -12,6 +12,7 @@
|
|||
],
|
||||
"requiredPlugins": [
|
||||
"share",
|
||||
"indexManagement",
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"cloud",
|
||||
|
|
|
@ -13,7 +13,6 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
|||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
||||
import { Router } from '@kbn/shared-ux-router';
|
||||
import { UsageTrackerContextProvider } from './contexts/usage_tracker_context';
|
||||
import { SearchIndicesServicesContextDeps } from './types';
|
||||
|
||||
|
@ -30,9 +29,7 @@ export const renderApp = async (
|
|||
<UsageTrackerContextProvider usageCollection={services.usageCollection}>
|
||||
<I18nProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Router history={services.history}>
|
||||
<App />
|
||||
</Router>
|
||||
<App />
|
||||
</QueryClientProvider>
|
||||
</I18nProvider>
|
||||
</UsageTrackerContextProvider>
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
EuiButton,
|
||||
EuiPageTemplate,
|
||||
EuiFlexItem,
|
||||
EuiTabbedContent,
|
||||
EuiFlexGroup,
|
||||
EuiPopover,
|
||||
EuiButtonIcon,
|
||||
|
@ -19,8 +18,10 @@ import {
|
|||
EuiText,
|
||||
EuiIcon,
|
||||
EuiButtonEmpty,
|
||||
EuiTabbedContent,
|
||||
EuiTabbedContentTab,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -33,11 +34,14 @@ import { useIndexMapping } from '../../hooks/api/use_index_mappings';
|
|||
import { IndexDocuments } from '../index_documents/index_documents';
|
||||
import { DeleteIndexModal } from './delete_index_modal';
|
||||
import { IndexloadingError } from './details_page_loading_error';
|
||||
import { SearchIndicesDetailsMappingsTabs } from '../../routes';
|
||||
import { SearchIndexDetailsMappings } from './details_page_mappings';
|
||||
|
||||
export const SearchIndexDetailsPage = () => {
|
||||
const indexName = decodeURIComponent(useParams<{ indexName: string }>().indexName);
|
||||
const { console: consolePlugin, docLinks, application } = useKibana().services;
|
||||
const tabId = decodeURIComponent(useParams<{ tabId: string }>().tabId);
|
||||
|
||||
const { console: consolePlugin, docLinks, application, history } = useKibana().services;
|
||||
const { data: index, refetch, isError: isIndexError, isInitialLoading } = useIndex(indexName);
|
||||
const {
|
||||
data: mappings,
|
||||
|
@ -45,6 +49,41 @@ export const SearchIndexDetailsPage = () => {
|
|||
isInitialLoading: isMappingsInitialLoading,
|
||||
} = useIndexMapping(indexName);
|
||||
|
||||
const SearchIndexDetailsTabs: EuiTabbedContentTab[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
id: SearchIndicesDetailsMappingsTabs.DATA,
|
||||
name: i18n.translate('xpack.searchIndices.documentsTabLabel', {
|
||||
defaultMessage: 'Data',
|
||||
}),
|
||||
content: <IndexDocuments indexName={indexName} />,
|
||||
'data-test-subj': `${SearchIndicesDetailsMappingsTabs.DATA}Tab`,
|
||||
},
|
||||
{
|
||||
id: SearchIndicesDetailsMappingsTabs.MAPPINGS,
|
||||
name: i18n.translate('xpack.searchIndices.mappingsTabLabel', {
|
||||
defaultMessage: 'Mappings',
|
||||
}),
|
||||
content: <SearchIndexDetailsMappings index={index} />,
|
||||
'data-test-subj': `${SearchIndicesDetailsMappingsTabs.MAPPINGS}Tab`,
|
||||
},
|
||||
];
|
||||
}, [index, indexName]);
|
||||
|
||||
const [selectedTab, setSelectedTab] = useState(SearchIndexDetailsTabs[0]);
|
||||
|
||||
useEffect(() => {
|
||||
const newTab = SearchIndexDetailsTabs.find((tab) => tab.id === tabId);
|
||||
if (newTab) setSelectedTab(newTab);
|
||||
}, [SearchIndexDetailsTabs, tabId]);
|
||||
|
||||
const handleTabClick = useCallback(
|
||||
(tab) => {
|
||||
history.push(`index_details/${indexName}/${tab.id}`);
|
||||
},
|
||||
|
||||
[history, indexName]
|
||||
);
|
||||
const embeddableConsole = useMemo(
|
||||
() => (consolePlugin?.EmbeddableConsole ? <consolePlugin.EmbeddableConsole /> : null),
|
||||
[consolePlugin]
|
||||
|
@ -176,15 +215,9 @@ export const SearchIndexDetailsPage = () => {
|
|||
<EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTabbedContent
|
||||
tabs={[
|
||||
{
|
||||
id: 'data',
|
||||
name: i18n.translate('xpack.searchIndices.documentsTabLabel', {
|
||||
defaultMessage: 'Data',
|
||||
}),
|
||||
content: <IndexDocuments indexName={indexName} />,
|
||||
},
|
||||
]}
|
||||
tabs={SearchIndexDetailsTabs}
|
||||
onTabClick={handleTabClick}
|
||||
selectedTab={selectedTab}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 { EuiSpacer } from '@elastic/eui';
|
||||
import { Index } from '@kbn/index-management-shared-types';
|
||||
import React from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useKibana } from '../../hooks/use_kibana';
|
||||
export interface SearchIndexDetailsMappingsProps {
|
||||
index?: Index;
|
||||
}
|
||||
export const SearchIndexDetailsMappings = ({ index }: SearchIndexDetailsMappingsProps) => {
|
||||
const { indexManagement, history } = useKibana().services;
|
||||
|
||||
const IndexMappingComponent = useMemo(
|
||||
() => indexManagement.getIndexMappingComponent({ history }),
|
||||
[indexManagement, history]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<IndexMappingComponent index={index} showAboutMappings={false} />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -5,17 +5,32 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Route, Routes } from '@kbn/shared-ux-router';
|
||||
import { SEARCH_INDICES_DETAILS_PATH } from '../../routes';
|
||||
import { SearchIndexDetailsPage } from './details_page';
|
||||
import { Route, Router, Routes } from '@kbn/shared-ux-router';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import { useKibana } from '../../hooks/use_kibana';
|
||||
|
||||
import {
|
||||
SearchIndicesDetailsMappingsTabs,
|
||||
SEARCH_INDICES_DETAILS_PATH,
|
||||
SEARCH_INDICES_DETAILS_TABS_PATH,
|
||||
} from '../../routes';
|
||||
import { SearchIndexDetailsPage } from './details_page';
|
||||
export const SearchIndicesRouter: React.FC = () => {
|
||||
const { application } = useKibana().services;
|
||||
const { application, history } = useKibana().services;
|
||||
return (
|
||||
<Routes>
|
||||
<Route exact path={SEARCH_INDICES_DETAILS_PATH} component={SearchIndexDetailsPage} />
|
||||
<Route render={() => application.navigateToApp('elasticsearchStart')} />
|
||||
</Routes>
|
||||
<Router history={history}>
|
||||
<Routes>
|
||||
<Route exact path={[SEARCH_INDICES_DETAILS_TABS_PATH, SEARCH_INDICES_DETAILS_PATH]}>
|
||||
<Routes>
|
||||
<Route path={SEARCH_INDICES_DETAILS_TABS_PATH} component={SearchIndexDetailsPage} />
|
||||
<Redirect
|
||||
exact
|
||||
from={`${SEARCH_INDICES_DETAILS_PATH}/`}
|
||||
to={`${SEARCH_INDICES_DETAILS_PATH}/${SearchIndicesDetailsMappingsTabs.DATA}`}
|
||||
/>
|
||||
</Routes>
|
||||
</Route>
|
||||
<Route render={() => application.navigateToApp('elasticsearchStart')} />
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,3 +7,8 @@
|
|||
|
||||
export const ROOT_PATH = '/';
|
||||
export const SEARCH_INDICES_DETAILS_PATH = `${ROOT_PATH}index_details/:indexName`;
|
||||
export const SEARCH_INDICES_DETAILS_TABS_PATH = `${SEARCH_INDICES_DETAILS_PATH}/:tabId`;
|
||||
export enum SearchIndicesDetailsMappingsTabs {
|
||||
DATA = 'data',
|
||||
MAPPINGS = 'mappings',
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'
|
|||
import type { SharePluginStart } from '@kbn/share-plugin/public';
|
||||
import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
|
||||
import type { MappingPropertyBase } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { IndexManagementPluginStart } from '@kbn/index-management-shared-types';
|
||||
|
||||
export interface SearchIndicesPluginSetup {
|
||||
enabled: boolean;
|
||||
|
@ -37,6 +38,7 @@ export interface SearchIndicesServicesContextDeps {
|
|||
export type SearchIndicesServicesContext = CoreStart &
|
||||
SearchIndicesAppPluginStartDependencies & {
|
||||
history: AppMountParameters['history'];
|
||||
indexManagement: IndexManagementPluginStart;
|
||||
};
|
||||
|
||||
export interface AppUsageTracker {
|
||||
|
|
|
@ -14,9 +14,6 @@ export function SvlSearchIndexDetailPageProvider({ getService }: FtrProviderCont
|
|||
const retry = getService('retry');
|
||||
|
||||
return {
|
||||
async expectToBeIndexDetailPage() {
|
||||
expect(await browser.getCurrentUrl()).contain('/index_details');
|
||||
},
|
||||
async expectIndexDetailPageHeader() {
|
||||
await testSubjects.existOrFail('searchIndexDetailsHeader', { timeout: 2000 });
|
||||
},
|
||||
|
@ -111,5 +108,18 @@ export function SvlSearchIndexDetailPageProvider({ getService }: FtrProviderCont
|
|||
await testSubjects.click('reloadButton', 2000);
|
||||
});
|
||||
},
|
||||
async expectWithDataTabsExists() {
|
||||
await testSubjects.existOrFail('mappingsTab', { timeout: 2000 });
|
||||
await testSubjects.existOrFail('dataTab', { timeout: 2000 });
|
||||
},
|
||||
async expectShouldDefaultToDataTab() {
|
||||
expect(await browser.getCurrentUrl()).contain('/data');
|
||||
},
|
||||
async withDataChangeTabs(tab: 'dataTab' | 'mappingsTab') {
|
||||
await testSubjects.click(tab);
|
||||
},
|
||||
async expectUrlShouldChangeTo(tab: 'data' | 'mappings') {
|
||||
expect(await browser.getCurrentUrl()).contain(`/${tab}`);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -68,23 +68,34 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await pageObjects.svlSearchIndexDetailPage.expectAddDocumentCodeExamples();
|
||||
});
|
||||
|
||||
it('should have index documents', async () => {
|
||||
await es.index({
|
||||
index: indexName,
|
||||
body: {
|
||||
my_field: [1, 0, 1],
|
||||
},
|
||||
});
|
||||
|
||||
await svlSearchNavigation.navigateToIndexDetailPage(indexName);
|
||||
await pageObjects.svlSearchIndexDetailPage.expectHasIndexDocuments();
|
||||
});
|
||||
|
||||
it('back to indices button should redirect to list page', async () => {
|
||||
await pageObjects.svlSearchIndexDetailPage.expectBackToIndicesButtonExists();
|
||||
await pageObjects.svlSearchIndexDetailPage.clickBackToIndicesButton();
|
||||
await pageObjects.svlSearchIndexDetailPage.expectBackToIndicesButtonRedirectsToListPage();
|
||||
});
|
||||
describe('With data', () => {
|
||||
before(async () => {
|
||||
await svlSearchNavigation.navigateToIndexDetailPage(indexName);
|
||||
await es.index({
|
||||
index: indexName,
|
||||
body: {
|
||||
my_field: [1, 0, 1],
|
||||
},
|
||||
});
|
||||
});
|
||||
it('should have index documents', async () => {
|
||||
await svlSearchNavigation.navigateToIndexDetailPage(indexName);
|
||||
await pageObjects.svlSearchIndexDetailPage.expectHasIndexDocuments();
|
||||
});
|
||||
it('should have with data tabs', async () => {
|
||||
await pageObjects.svlSearchIndexDetailPage.expectWithDataTabsExists();
|
||||
await pageObjects.svlSearchIndexDetailPage.expectShouldDefaultToDataTab();
|
||||
});
|
||||
it('should be able to change tabs', async () => {
|
||||
await pageObjects.svlSearchIndexDetailPage.withDataChangeTabs('mappingsTab');
|
||||
await pageObjects.svlSearchIndexDetailPage.expectUrlShouldChangeTo('mappings');
|
||||
});
|
||||
});
|
||||
|
||||
describe('page loading error', () => {
|
||||
before(async () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue