mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Onboarding] Expose settings component in index_management to reuse in search_indices (#193492)
## Summary This PR exposes `index_management` index details [settings component ](https://github.com/elastic/kibana/blob/main/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_settings.tsx#L16)during `index_management` plugin start. This would enable `search_indices` plugin to reuse. With this change, in new search index details page user can : - Can View settings - Update settings & save - Reset changes <img width="1719" alt="Screenshot 2024-09-19 at 5 48 28 PM" src="https://github.com/user-attachments/assets/a6179fb6-c180-434e-bdb1-3c784006069f"> **How to test:** 1. Enable searchIndices plugin in `kibana.dev.yml` as this plugin is behind Feature flag ``` xpack.searchIndices.enabled: true ``` 2. [Create new index](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html) 3. Navigate to `/app/elasticsearch/indices/index_details/${indexName}/settings` ### Checklist - [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 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
92f1320019
commit
d925391b90
14 changed files with 220 additions and 28 deletions
|
@ -25,6 +25,9 @@ export interface IndexManagementPluginStart {
|
|||
getIndexMappingComponent: (deps: {
|
||||
history: ScopedHistory<unknown>;
|
||||
}) => React.FC<IndexMappingProps>;
|
||||
getIndexSettingsComponent: (deps: {
|
||||
history: ScopedHistory<unknown>;
|
||||
}) => React.FC<IndexSettingProps>;
|
||||
}
|
||||
|
||||
export interface Index {
|
||||
|
@ -56,7 +59,9 @@ export interface IndexMappingProps {
|
|||
index?: Index;
|
||||
showAboutMappings?: boolean;
|
||||
}
|
||||
|
||||
export interface IndexSettingProps {
|
||||
indexName: string;
|
||||
}
|
||||
export interface SendRequestResponse<D = any, E = any> {
|
||||
data: D | null;
|
||||
error: E | null;
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { documentationService } from '../../../../services';
|
||||
import { UIM_APP_NAME } from '../../../../../../common/constants/ui_metric';
|
||||
import { httpService } from '../../../../services/http';
|
||||
import { notificationService } from '../../../../services/notification';
|
||||
import { UiMetricService } from '../../../../services/ui_metric';
|
||||
import { AppDependencies, IndexManagementAppContext } from '../../../..';
|
||||
import { documentationService } from '../../../../../services';
|
||||
import { UIM_APP_NAME } from '../../../../../../../common/constants/ui_metric';
|
||||
import { httpService } from '../../../../../services/http';
|
||||
import { notificationService } from '../../../../../services/notification';
|
||||
import { UiMetricService } from '../../../../../services/ui_metric';
|
||||
import { AppDependencies, IndexManagementAppContext } from '../../../../..';
|
||||
import { IndexMappingWithContextProps } from './index_mapping_with_context_types';
|
||||
import { DetailsPageMappings } from './details_page_mappings';
|
||||
import { DetailsPageMappings } from '../details_page_mappings';
|
||||
|
||||
export const IndexMappingWithContext: React.FC<IndexMappingWithContextProps> = ({
|
||||
core,
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
import { CoreStart } from '@kbn/core/public';
|
||||
import { IndexMappingProps } from '@kbn/index-management-shared-types';
|
||||
import { AppDependencies } from '../../../../app_context';
|
||||
import { ExtensionsService } from '../../../../../services/extensions_service';
|
||||
import { AppDependencies } from '../../../../../app_context';
|
||||
import { ExtensionsService } from '../../../../../../services/extensions_service';
|
||||
|
||||
export type IndexMappingWithContextProps = {
|
||||
core: CoreStart;
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
/* 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 { EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { dynamic } from '@kbn/shared-ux-utility';
|
||||
import React, { Suspense, ComponentType } from 'react';
|
||||
import { IndexSettingWithContextProps } from './index_settings_with_context_types';
|
||||
|
||||
const IndexSettingsWithContext = dynamic<ComponentType<IndexSettingWithContextProps>>(() =>
|
||||
import('./index_settings_with_context').then((mod) => ({ default: mod.IndexSettingsWithContext }))
|
||||
);
|
||||
|
||||
export const IndexSettings: React.FC<IndexSettingWithContextProps> = (props) => {
|
||||
return (
|
||||
<Suspense fallback={<EuiLoadingSpinner />}>
|
||||
<IndexSettingsWithContext {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { documentationService } from '../../../../../services';
|
||||
import { UIM_APP_NAME } from '../../../../../../../common/constants/ui_metric';
|
||||
import { httpService } from '../../../../../services/http';
|
||||
import { notificationService } from '../../../../../services/notification';
|
||||
import { UiMetricService } from '../../../../../services/ui_metric';
|
||||
import { AppDependencies, IndexManagementAppContext } from '../../../../..';
|
||||
import { DetailsPageSettings } from '../details_page_settings';
|
||||
import { IndexSettingWithContextProps } from './index_settings_with_context_types';
|
||||
import { setUiMetricService } from '../../../../../services/api';
|
||||
|
||||
export const IndexSettingsWithContext: React.FC<IndexSettingWithContextProps> = ({
|
||||
core,
|
||||
dependencies,
|
||||
indexName,
|
||||
usageCollection,
|
||||
}) => {
|
||||
// this normally happens when the index management app is rendered
|
||||
// but if components are embedded elsewhere that setup is skipped, so we have to do it here
|
||||
// would do it in plugin.ts but that blows up the bundle size
|
||||
// can't do it in an effect because then the first http call fails as the instantiation happens after first render
|
||||
if (!httpService.httpClient) {
|
||||
httpService.setup(core.http);
|
||||
notificationService.setup(core.notifications);
|
||||
}
|
||||
documentationService.setup(core.docLinks);
|
||||
|
||||
const uiMetricService = new UiMetricService(UIM_APP_NAME);
|
||||
setUiMetricService(uiMetricService);
|
||||
uiMetricService.setup(usageCollection);
|
||||
|
||||
const newDependencies: AppDependencies = {
|
||||
...dependencies,
|
||||
services: {
|
||||
...(dependencies.services || {}),
|
||||
httpService,
|
||||
notificationService,
|
||||
uiMetricService,
|
||||
},
|
||||
};
|
||||
return (
|
||||
<IndexManagementAppContext core={core} dependencies={newDependencies}>
|
||||
<DetailsPageSettings indexName={indexName} />
|
||||
</IndexManagementAppContext>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 { CoreStart } from '@kbn/core/public';
|
||||
import type { IndexSettingProps } from '@kbn/index-management-shared-types';
|
||||
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
|
||||
import { AppDependencies } from '../../../../../app_context';
|
||||
import { ExtensionsService } from '../../../../../../services/extensions_service';
|
||||
|
||||
export type IndexSettingWithContextProps = {
|
||||
core: CoreStart;
|
||||
// omitting services here to constitute them inside the component
|
||||
// this helps reduce bundle size significantly
|
||||
dependencies: Omit<AppDependencies, 'services'> & {
|
||||
services: { extensionsService: ExtensionsService };
|
||||
};
|
||||
usageCollection: UsageCollectionSetup;
|
||||
} & IndexSettingProps;
|
|
@ -26,8 +26,9 @@ import { ClientConfigType, SetupDependencies, StartDependencies } from './types'
|
|||
|
||||
// avoid import from index files in plugin.ts, use specific import paths
|
||||
import { PLUGIN } from '../common/constants/plugin';
|
||||
import { IndexMapping } from './application/sections/home/index_list/details_page/index_mappings_embeddable';
|
||||
import { IndexMapping } from './application/sections/home/index_list/details_page/with_context_components/index_mappings_embeddable';
|
||||
import { PublicApiService } from './services/public_api_service';
|
||||
import { IndexSettings } from './application/sections/home/index_list/details_page/with_context_components/index_settings_embeddable';
|
||||
|
||||
export class IndexMgmtUIPlugin
|
||||
implements
|
||||
|
@ -159,6 +160,44 @@ export class IndexMgmtUIPlugin
|
|||
return IndexMapping({ dependencies: appDependencies, core: coreStart, ...props });
|
||||
};
|
||||
},
|
||||
getIndexSettingsComponent: (deps: { history: ScopedHistory<unknown> }) => {
|
||||
const { docLinks, fatalErrors, application, uiSettings, executionContext, settings, http } =
|
||||
coreStart;
|
||||
const { url } = share;
|
||||
const appDependencies = {
|
||||
core: {
|
||||
fatalErrors,
|
||||
getUrlForApp: application.getUrlForApp,
|
||||
executionContext,
|
||||
application,
|
||||
http,
|
||||
},
|
||||
plugins: {
|
||||
usageCollection,
|
||||
isFleetEnabled: Boolean(fleet),
|
||||
share,
|
||||
cloud,
|
||||
console,
|
||||
ml,
|
||||
licensing,
|
||||
},
|
||||
services: {
|
||||
extensionsService: this.extensionsService,
|
||||
},
|
||||
config: this.config,
|
||||
history: deps.history,
|
||||
setBreadcrumbs: undefined as any, // breadcrumbService.setBreadcrumbs,
|
||||
uiSettings,
|
||||
settings,
|
||||
url,
|
||||
docLinks,
|
||||
kibanaVersion: this.kibanaVersion,
|
||||
theme$: coreStart.theme.theme$,
|
||||
};
|
||||
return (props: any) => {
|
||||
return IndexSettings({ dependencies: appDependencies, core: coreStart, ...props });
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
public stop() {}
|
||||
|
|
|
@ -34,8 +34,9 @@ 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 { SearchIndexDetailsTabs } from '../../routes';
|
||||
import { SearchIndexDetailsMappings } from './details_page_mappings';
|
||||
import { SearchIndexDetailsSettings } from './details_page_settings';
|
||||
|
||||
export const SearchIndexDetailsPage = () => {
|
||||
const indexName = decodeURIComponent(useParams<{ indexName: string }>().indexName);
|
||||
|
@ -49,33 +50,40 @@ export const SearchIndexDetailsPage = () => {
|
|||
isInitialLoading: isMappingsInitialLoading,
|
||||
} = useIndexMapping(indexName);
|
||||
|
||||
const SearchIndexDetailsTabs: EuiTabbedContentTab[] = useMemo(() => {
|
||||
const detailsPageTabs: EuiTabbedContentTab[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
id: SearchIndicesDetailsMappingsTabs.DATA,
|
||||
id: SearchIndexDetailsTabs.DATA,
|
||||
name: i18n.translate('xpack.searchIndices.documentsTabLabel', {
|
||||
defaultMessage: 'Data',
|
||||
}),
|
||||
content: <IndexDocuments indexName={indexName} />,
|
||||
'data-test-subj': `${SearchIndicesDetailsMappingsTabs.DATA}Tab`,
|
||||
'data-test-subj': `${SearchIndexDetailsTabs.DATA}Tab`,
|
||||
},
|
||||
{
|
||||
id: SearchIndicesDetailsMappingsTabs.MAPPINGS,
|
||||
id: SearchIndexDetailsTabs.MAPPINGS,
|
||||
name: i18n.translate('xpack.searchIndices.mappingsTabLabel', {
|
||||
defaultMessage: 'Mappings',
|
||||
}),
|
||||
content: <SearchIndexDetailsMappings index={index} />,
|
||||
'data-test-subj': `${SearchIndicesDetailsMappingsTabs.MAPPINGS}Tab`,
|
||||
'data-test-subj': `${SearchIndexDetailsTabs.MAPPINGS}Tab`,
|
||||
},
|
||||
{
|
||||
id: SearchIndexDetailsTabs.SETTINGS,
|
||||
name: i18n.translate('xpack.searchIndices.settingsTabLabel', {
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
content: <SearchIndexDetailsSettings indexName={indexName} />,
|
||||
'data-test-subj': `${SearchIndexDetailsTabs.SETTINGS}Tab`,
|
||||
},
|
||||
];
|
||||
}, [index, indexName]);
|
||||
|
||||
const [selectedTab, setSelectedTab] = useState(SearchIndexDetailsTabs[0]);
|
||||
const [selectedTab, setSelectedTab] = useState(detailsPageTabs[0]);
|
||||
|
||||
useEffect(() => {
|
||||
const newTab = SearchIndexDetailsTabs.find((tab) => tab.id === tabId);
|
||||
const newTab = detailsPageTabs.find((tab) => tab.id === tabId);
|
||||
if (newTab) setSelectedTab(newTab);
|
||||
}, [SearchIndexDetailsTabs, tabId]);
|
||||
}, [detailsPageTabs, tabId]);
|
||||
|
||||
const handleTabClick = useCallback(
|
||||
(tab) => {
|
||||
|
@ -215,7 +223,7 @@ export const SearchIndexDetailsPage = () => {
|
|||
<EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTabbedContent
|
||||
tabs={SearchIndexDetailsTabs}
|
||||
tabs={detailsPageTabs}
|
||||
onTabClick={handleTabClick}
|
||||
selectedTab={selectedTab}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useKibana } from '../../hooks/use_kibana';
|
||||
|
||||
interface SearchIndexDetailsSettingsProps {
|
||||
indexName: string;
|
||||
}
|
||||
export const SearchIndexDetailsSettings = ({ indexName }: SearchIndexDetailsSettingsProps) => {
|
||||
const { indexManagement, history } = useKibana().services;
|
||||
|
||||
const IndexSettingsComponent = useMemo(
|
||||
() => indexManagement.getIndexSettingsComponent({ history }),
|
||||
[indexManagement, history]
|
||||
);
|
||||
|
||||
return <IndexSettingsComponent indexName={indexName} />;
|
||||
};
|
|
@ -9,7 +9,7 @@ import { Route, Router, Routes } from '@kbn/shared-ux-router';
|
|||
import { Redirect } from 'react-router-dom';
|
||||
import { useKibana } from '../../hooks/use_kibana';
|
||||
import {
|
||||
SearchIndicesDetailsMappingsTabs,
|
||||
SearchIndexDetailsTabs,
|
||||
SEARCH_INDICES_DETAILS_PATH,
|
||||
SEARCH_INDICES_DETAILS_TABS_PATH,
|
||||
} from '../../routes';
|
||||
|
@ -25,7 +25,7 @@ export const SearchIndicesRouter: React.FC = () => {
|
|||
<Redirect
|
||||
exact
|
||||
from={`${SEARCH_INDICES_DETAILS_PATH}/`}
|
||||
to={`${SEARCH_INDICES_DETAILS_PATH}/${SearchIndicesDetailsMappingsTabs.DATA}`}
|
||||
to={`${SEARCH_INDICES_DETAILS_PATH}/${SearchIndexDetailsTabs.DATA}`}
|
||||
/>
|
||||
</Routes>
|
||||
</Route>
|
||||
|
|
|
@ -8,7 +8,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 {
|
||||
export enum SearchIndexDetailsTabs {
|
||||
DATA = 'data',
|
||||
MAPPINGS = 'mappings',
|
||||
SETTINGS = 'settings',
|
||||
}
|
||||
|
|
|
@ -115,11 +115,17 @@ export function SvlSearchIndexDetailPageProvider({ getService }: FtrProviderCont
|
|||
async expectShouldDefaultToDataTab() {
|
||||
expect(await browser.getCurrentUrl()).contain('/data');
|
||||
},
|
||||
async withDataChangeTabs(tab: 'dataTab' | 'mappingsTab') {
|
||||
async withDataChangeTabs(tab: 'dataTab' | 'mappingsTab' | 'settingsTab') {
|
||||
await testSubjects.click(tab);
|
||||
},
|
||||
async expectUrlShouldChangeTo(tab: 'data' | 'mappings') {
|
||||
async expectUrlShouldChangeTo(tab: 'data' | 'mappings' | 'settings') {
|
||||
expect(await browser.getCurrentUrl()).contain(`/${tab}`);
|
||||
},
|
||||
async expectMappingsComponentIsVisible() {
|
||||
await testSubjects.existOrFail('indexDetailsMappingsToggleViewButton', { timeout: 2000 });
|
||||
},
|
||||
async expectSettingsComponentIsVisible() {
|
||||
await testSubjects.existOrFail('indexDetailsSettingsEditModeSwitch', { timeout: 2000 });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -91,9 +91,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
await pageObjects.svlSearchIndexDetailPage.expectWithDataTabsExists();
|
||||
await pageObjects.svlSearchIndexDetailPage.expectShouldDefaultToDataTab();
|
||||
});
|
||||
it('should be able to change tabs', async () => {
|
||||
it('should be able to change tabs to mappings and mappings is shown', async () => {
|
||||
await pageObjects.svlSearchIndexDetailPage.withDataChangeTabs('mappingsTab');
|
||||
await pageObjects.svlSearchIndexDetailPage.expectUrlShouldChangeTo('mappings');
|
||||
await pageObjects.svlSearchIndexDetailPage.expectMappingsComponentIsVisible();
|
||||
});
|
||||
it('should be able to change tabs to settings and settings is shown', async () => {
|
||||
await pageObjects.svlSearchIndexDetailPage.withDataChangeTabs('settingsTab');
|
||||
await pageObjects.svlSearchIndexDetailPage.expectUrlShouldChangeTo('settings');
|
||||
await pageObjects.svlSearchIndexDetailPage.expectSettingsComponentIsVisible();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue