diff --git a/x-pack/packages/index-management/index_management_shared_types/src/services/extensions_service.ts b/x-pack/packages/index-management/index_management_shared_types/src/services/extensions_service.ts index 98d981752e584..434f18f1fa1ef 100644 --- a/x-pack/packages/index-management/index_management_shared_types/src/services/extensions_service.ts +++ b/x-pack/packages/index-management/index_management_shared_types/src/services/extensions_service.ts @@ -31,7 +31,7 @@ export interface IndexBadge { color: EuiBadgeProps['color']; } export interface IndexDetailsPageRoute { - renderRoute: (indexName: string) => string; + renderRoute: (indexName: string, detailsTabId?: string) => string; } export interface EmptyListContent { @@ -72,5 +72,5 @@ export interface ExtensionsSetup { // sets content to render below the docs link on the mappings tab of the index page setIndexMappingsContent(content: IndexContent): void; // sets index details page route - setIndexDetailsPageRoute(route: IndexDetailsPageRoute): void; + setIndexDetailsPageRoute(route: IndexDetailsPageRoute, detailsTabId?: string): void; } diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.tsx index 351bc068f36a0..a8256a2e00b27 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.tsx @@ -10,6 +10,7 @@ */ import { EuiSearchBoxProps } from '@elastic/eui/src/components/search_bar/search_box'; +import { applicationServiceMock } from '@kbn/core/public/mocks'; jest.mock('@elastic/eui/lib/components/search_bar/search_box', () => { return { EuiSearchBox: (props: EuiSearchBoxProps) => ( @@ -136,15 +137,21 @@ describe('', () => { createNonDataStreamIndex(indexName) ); + const application = applicationServiceMock.createStartContract(); testBed = await setup(httpSetup, { history: createMemoryHistory(), + core: { + application, + }, }); const { component, actions } = testBed; component.update(); await actions.clickIndexNameAt(0); - expect(testBed.actions.findIndexDetailsPageTitle()).toContain('testIndex'); + expect(application.navigateToUrl).toHaveBeenCalledWith( + '/app/management/data/index_management/indices/index_details?indexName=testIndex&includeHiddenIndices=true' + ); }); it('index page works with % character in index name', async () => { @@ -155,13 +162,21 @@ describe('', () => { createNonDataStreamIndex(indexName) ); - testBed = await setup(httpSetup); + const application = applicationServiceMock.createStartContract(); + testBed = await setup(httpSetup, { + history: createMemoryHistory(), + core: { + application, + }, + }); const { component, actions } = testBed; component.update(); await actions.clickIndexNameAt(0); - expect(testBed.actions.findIndexDetailsPageTitle()).toContain(indexName); + expect(application.navigateToUrl).toHaveBeenCalledWith( + '/app/management/data/index_management/indices/index_details?indexName=test%25&includeHiddenIndices=true' + ); }); describe('empty list component', () => { diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js index 2acce7f28b06f..d733fdfd2f6e6 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js @@ -23,7 +23,7 @@ import { import { flattenPanelTree } from '../../../../lib/flatten_panel_tree'; import { INDEX_OPEN, IndexDetailsSection } from '../../../../../../common/constants'; -import { getIndexDetailsLink } from '../../../../services/routing'; +import { getIndexDetailsLink, navigateToIndexDetailsPage } from '../../../../services/routing'; import { AppContext } from '../../../../app_context'; export class IndexActionsContextMenu extends Component { @@ -50,7 +50,7 @@ export class IndexActionsContextMenu extends Component { panels() { const { services: { extensionsService }, - core: { getUrlForApp }, + core: { getUrlForApp, application, http }, history, config: { enableIndexActions }, } = this.context; @@ -83,8 +83,13 @@ export class IndexActionsContextMenu extends Component { defaultMessage: 'Show index overview', }), onClick: () => { - history.push( - getIndexDetailsLink(indexNames[0], indicesListURLParams, IndexDetailsSection.Overview) + navigateToIndexDetailsPage( + indexNames[0], + indicesListURLParams, + extensionsService, + application, + http, + IndexDetailsSection.Overview ); }, }); @@ -94,8 +99,13 @@ export class IndexActionsContextMenu extends Component { defaultMessage: 'Show index settings', }), onClick: () => { - history.push( - getIndexDetailsLink(indexNames[0], indicesListURLParams, IndexDetailsSection.Settings) + navigateToIndexDetailsPage( + indexNames[0], + indicesListURLParams, + extensionsService, + application, + http, + IndexDetailsSection.Settings ); }, }); @@ -105,8 +115,13 @@ export class IndexActionsContextMenu extends Component { defaultMessage: 'Show index mapping', }), onClick: () => { - history.push( - getIndexDetailsLink(indexNames[0], indicesListURLParams, IndexDetailsSection.Mappings) + navigateToIndexDetailsPage( + indexNames[0], + indicesListURLParams, + extensionsService, + application, + http, + IndexDetailsSection.Mappings ); }, }); 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 9567aee715c3b..b63c211f74dbf 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 @@ -41,7 +41,7 @@ import { reactRouterNavigate, attemptToURIDecode, } from '../../../../../shared_imports'; -import { getDataStreamDetailsLink, getIndexDetailsLink } from '../../../../services/routing'; +import { getDataStreamDetailsLink, navigateToIndexDetailsPage } from '../../../../services/routing'; import { documentationService } from '../../../../services/documentation'; import { AppContextConsumer } from '../../../../app_context'; import { renderBadges } from '../../../../lib/render_badges'; @@ -73,12 +73,13 @@ const getColumnConfigs = ({ { - if (!extensionsService.indexDetailsPageRoute) { - history.push(getIndexDetailsLink(index.name, location.search || '')); - } else { - const route = extensionsService.indexDetailsPageRoute.renderRoute(index.name); - application.navigateToUrl(http.basePath.prepend(route)); - } + navigateToIndexDetailsPage( + index.name, + location.search || '', + extensionsService, + application, + http + ); }} > {index.name} diff --git a/x-pack/plugins/index_management/public/application/services/routing.test.ts b/x-pack/plugins/index_management/public/application/services/routing.test.ts index 24500cb6059bf..0b1462deab58b 100644 --- a/x-pack/plugins/index_management/public/application/services/routing.test.ts +++ b/x-pack/plugins/index_management/public/application/services/routing.test.ts @@ -5,10 +5,16 @@ * 2.0. */ -import { getIndexDetailsLink, getIndexListUri } from './routing'; +import { getIndexDetailsLink, getIndexListUri, navigateToIndexDetailsPage } from './routing'; +import { applicationServiceMock, httpServiceMock } from '@kbn/core/public/mocks'; +import { ExtensionsService } from '../../services/extensions_service'; +import { IndexDetailsSection } from '../../../common/constants'; describe('routing', () => { describe('index details link', () => { + const application = applicationServiceMock.createStartContract(); + const http = httpServiceMock.createSetupContract(); + it('adds the index name to the url', () => { const indexName = 'testIndex'; const url = getIndexDetailsLink(indexName, ''); @@ -26,6 +32,33 @@ describe('routing', () => { const url = getIndexDetailsLink('testIndex', '', tab); expect(url).toContain(`tab=${tab}`); }); + it('renders default index details route without extensionService indexDetailsPageRoute ', () => { + const extensionService = { + indexDetailsPageRoute: null, + } as ExtensionsService; + navigateToIndexDetailsPage('testIndex', '', extensionService, application, http); + expect(application.navigateToUrl).toHaveBeenCalled(); + }); + + it('renders route from extensionService indexDetailsPageRoute with tab id', () => { + const extensionService = { + indexDetailsPageRoute: { + renderRoute: (indexName: string, detailsTabId?: string) => { + return `test_url/${detailsTabId}`; + }, + }, + } as ExtensionsService; + navigateToIndexDetailsPage( + 'testIndex', + '', + extensionService, + application, + http, + IndexDetailsSection.Settings + ); + expect(application.navigateToUrl).toHaveBeenCalled(); + expect(application.navigateToUrl).toHaveBeenCalledWith('test_url/settings'); + }); }); describe('indices list link', () => { 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 07653d2591ffc..bce7a14f03e46 100644 --- a/x-pack/plugins/index_management/public/application/services/routing.ts +++ b/x-pack/plugins/index_management/public/application/services/routing.ts @@ -5,9 +5,12 @@ * 2.0. */ +import { ApplicationStart } from '@kbn/core/public'; +import { HttpSetup } from '@kbn/core/public'; import { Section } from '../../../common/constants'; import type { IndexDetailsTabId } from '../../../common/constants'; - +import { ExtensionsService } from '../../services/extensions_service'; +import { IndexDetailsSection } from '../../../common/constants'; export const getTemplateListLink = () => `/templates`; export const getTemplateDetailsLink = (name: string, isLegacy?: boolean) => { @@ -78,3 +81,26 @@ export const getComponentTemplatesLink = (usedByTemplateName?: string) => { } return url; }; +export const navigateToIndexDetailsPage = ( + indexName: string, + indicesListURLParams: string, + extensionsService: ExtensionsService, + application: ApplicationStart, + http: HttpSetup, + tabId?: IndexDetailsSection +) => { + if (!extensionsService.indexDetailsPageRoute) { + application.navigateToUrl( + http.basePath.prepend( + `/app/management/data/index_management${getIndexDetailsLink( + indexName, + indicesListURLParams, + tabId + )}` + ) + ); + } else { + const route = extensionsService.indexDetailsPageRoute.renderRoute(indexName, tabId); + application.navigateToUrl(http.basePath.prepend(route)); + } +}; diff --git a/x-pack/plugins/search_indices/public/plugin.ts b/x-pack/plugins/search_indices/public/plugin.ts index 2f9a8ca3cf950..c9b5c8f4c7659 100644 --- a/x-pack/plugins/search_indices/public/plugin.ts +++ b/x-pack/plugins/search_indices/public/plugin.ts @@ -17,7 +17,7 @@ import type { } from './types'; import { initQueryClient } from './services/query_client'; import { INDICES_APP_ID, START_APP_ID } from '../common'; -import { INDICES_APP_BASE, START_APP_BASE } from './routes'; +import { INDICES_APP_BASE, START_APP_BASE, SearchIndexDetailsTabValues } from './routes'; export class SearchIndicesPlugin implements Plugin @@ -81,8 +81,12 @@ export class SearchIndicesPlugin docLinks.setDocLinks(core.docLinks.links); if (this.pluginEnabled) { indexManagement?.extensionsService.setIndexDetailsPageRoute({ - renderRoute: (indexName) => { - return `/app/elasticsearch/indices/index_details/${indexName}`; + renderRoute: (indexName, detailsTabId) => { + const route = `/app/elasticsearch/indices/index_details/${indexName}`; + if (detailsTabId && SearchIndexDetailsTabValues.includes(detailsTabId)) { + return `${route}/${detailsTabId}`; + } + return route; }, }); } diff --git a/x-pack/plugins/search_indices/public/routes.ts b/x-pack/plugins/search_indices/public/routes.ts index c72e84c66a7d0..057891d63226d 100644 --- a/x-pack/plugins/search_indices/public/routes.ts +++ b/x-pack/plugins/search_indices/public/routes.ts @@ -14,5 +14,6 @@ export enum SearchIndexDetailsTabs { SETTINGS = 'settings', } +export const SearchIndexDetailsTabValues: string[] = Object.values(SearchIndexDetailsTabs); export const START_APP_BASE = '/app/elasticsearch/start'; export const INDICES_APP_BASE = '/app/elasticsearch/indices'; 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 848c7c9e5b0e3..f257f76cbfc5b 100644 --- a/x-pack/test/functional/page_objects/index_management_page.ts +++ b/x-pack/test/functional/page_objects/index_management_page.ts @@ -12,6 +12,7 @@ export function IndexManagementPageProvider({ getService }: FtrProviderContext) const find = getService('find'); const testSubjects = getService('testSubjects'); + const browser = getService('browser'); return { async sectionHeadingText() { return await testSubjects.getVisibleText('appTitle'); @@ -154,6 +155,10 @@ export function IndexManagementPageProvider({ getService }: FtrProviderContext) await testSubjects.existOrFail('indexDetailsContent'); await testSubjects.existOrFail('indexDetailsBackToIndicesButton'); }, + async expectUrlShouldChangeTo(tabId: string) { + const url = await browser.getCurrentUrl(); + expect(url).to.contain(`tab=${tabId}`); + }, }, async clickCreateIndexButton() { await testSubjects.click('createIndexButton'); @@ -181,23 +186,9 @@ export function IndexManagementPageProvider({ getService }: FtrProviderContext) expect(indexNames.some((i) => i === indexName)).to.be(true); }, - async selectIndex(indexName: string) { - const id = `checkboxSelectIndex-${indexName}`; - const checkbox = await find.byCssSelector(`input[id="${id}"]`); - if (!(await checkbox.isSelected())) { - await find.clickByCssSelector(`input[id="${id}"]`); - } - }, - async clickManageButton() { - await testSubjects.existOrFail('indexActionsContextMenuButton'); - await testSubjects.click('indexActionsContextMenuButton'); - }, - async contextMenuIsVisible() { - await testSubjects.existOrFail('indexContextMenu'); + async confirmDeleteModalIsVisible() { await testSubjects.existOrFail('deleteIndexMenuButton'); await testSubjects.click('deleteIndexMenuButton'); - }, - async confirmDeleteModalIsVisible() { await testSubjects.existOrFail('confirmModalTitleText'); const modalText: string = await testSubjects.getVisibleText('confirmModalTitleText'); expect(modalText).to.be('Delete index'); @@ -217,5 +208,37 @@ export function IndexManagementPageProvider({ getService }: FtrProviderContext) ); expect(indexNames.includes(indexName)).to.be(false); }, + async manageIndex(indexName: string) { + const id = `checkboxSelectIndex-${indexName}`; + const checkbox = await find.byCssSelector(`input[id="${id}"]`); + if (!(await checkbox.isSelected())) { + await find.clickByCssSelector(`input[id="${id}"]`); + } + await retry.waitFor('manage index to show up ', async () => { + return (await testSubjects.isDisplayed('indexActionsContextMenuButton')) === true; + }); + const contextMenuButton = await testSubjects.find('indexActionsContextMenuButton'); + await contextMenuButton.click(); + await retry.waitFor('manage index context menu to show ', async () => { + return (await testSubjects.isDisplayed('indexContextMenu')) === true; + }); + }, + async manageIndexContextMenuExists() { + await testSubjects.existOrFail('showOverviewIndexMenuButton'); + await testSubjects.existOrFail('showSettingsIndexMenuButton'); + await testSubjects.existOrFail('showMappingsIndexMenuButton'); + await testSubjects.existOrFail('deleteIndexMenuButton'); + }, + async changeManageIndexTab( + manageIndexTab: + | 'showOverviewIndexMenuButton' + | 'showSettingsIndexMenuButton' + | 'showMappingsIndexMenuButton' + | 'deleteIndexMenuButton' + ) { + await testSubjects.existOrFail(manageIndexTab); + const manageIndexComponent = await testSubjects.find(manageIndexTab); + await manageIndexComponent.click(); + }, }; } diff --git a/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts b/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts index 656c75fb308bc..df2632650f678 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts @@ -136,9 +136,6 @@ export function SvlSearchIndexDetailPageProvider({ getService }: FtrProviderCont 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' | 'settingsTab') { await testSubjects.click(tab); }, @@ -202,7 +199,6 @@ export function SvlSearchIndexDetailPageProvider({ getService }: FtrProviderCont return (await testSubjects.isDisplayed('searchIndexDetailsHeader')) === true; }); }, - async expectSearchIndexDetailsTabsExists() { await testSubjects.existOrFail('dataTab'); await testSubjects.existOrFail('mappingsTab'); diff --git a/x-pack/test_serverless/functional/test_suites/common/management/index_management/indices.ts b/x-pack/test_serverless/functional/test_suites/common/management/index_management/indices.ts index fe5938109d7b8..e98fcc09e97d1 100644 --- a/x-pack/test_serverless/functional/test_suites/common/management/index_management/indices.ts +++ b/x-pack/test_serverless/functional/test_suites/common/management/index_management/indices.ts @@ -12,6 +12,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['svlCommonPage', 'common', 'indexManagement', 'header']); const browser = getService('browser'); const security = getService('security'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + const testIndexName = `index-ftr-test-${Math.random()}`; + const es = getService('es'); describe('Indices', function () { before(async () => { @@ -22,7 +25,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.indexManagement.changeTabs('indicesTab'); await pageObjects.header.waitUntilLoadingHasFinished(); }); - const testIndexName = `index-ftr-test-${Math.random()}`; + it('renders the indices tab', async () => { const url = await browser.getCurrentUrl(); expect(url).to.contain(`/indices`); @@ -33,14 +36,45 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.indexManagement.clickCreateIndexSaveButton(); await pageObjects.indexManagement.expectIndexToExist(testIndexName); }); - it('can manage index', async () => { - await pageObjects.indexManagement.selectIndex(testIndexName); - await pageObjects.indexManagement.clickManageButton(); - await pageObjects.indexManagement.contextMenuIsVisible(); - }); - it('can delete index', async () => { - await pageObjects.indexManagement.confirmDeleteModalIsVisible(); - await pageObjects.indexManagement.expectIndexIsDeleted(testIndexName); + + describe('manage index', function () { + beforeEach(async () => { + await pageObjects.common.navigateToApp('indexManagement'); + // Navigate to the indices tab + await pageObjects.indexManagement.changeTabs('indicesTab'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await pageObjects.indexManagement.manageIndex(testIndexName); + await pageObjects.indexManagement.manageIndexContextMenuExists(); + }); + describe('navigate to index detail tabs', function () { + before(async () => { + await es.indices.create({ index: testIndexName }); + }); + after(async () => { + await esDeleteAllIndices(testIndexName); + }); + this.tags('skipSvlSearch'); + it('navigates to overview', async () => { + await pageObjects.indexManagement.changeManageIndexTab('showOverviewIndexMenuButton'); + await pageObjects.indexManagement.indexDetailsPage.expectIndexDetailsPageIsLoaded(); + await pageObjects.indexManagement.indexDetailsPage.expectUrlShouldChangeTo('overview'); + }); + + it('navigates to settings tab', async () => { + await pageObjects.indexManagement.changeManageIndexTab('showSettingsIndexMenuButton'); + await pageObjects.indexManagement.indexDetailsPage.expectIndexDetailsPageIsLoaded(); + await pageObjects.indexManagement.indexDetailsPage.expectUrlShouldChangeTo('settings'); + }); + it('navigates to mappings tab', async () => { + await pageObjects.indexManagement.changeManageIndexTab('showMappingsIndexMenuButton'); + await pageObjects.indexManagement.indexDetailsPage.expectIndexDetailsPageIsLoaded(); + await pageObjects.indexManagement.indexDetailsPage.expectUrlShouldChangeTo('mappings'); + }); + }); + it('can delete index', async () => { + await pageObjects.indexManagement.confirmDeleteModalIsVisible(); + await pageObjects.indexManagement.expectIndexIsDeleted(testIndexName); + }); }); }); }; diff --git a/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts b/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts index 9c927eaee3a95..eab050cc25d3e 100644 --- a/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts +++ b/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts @@ -132,7 +132,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('should have with data tabs', async () => { await pageObjects.svlSearchIndexDetailPage.expectWithDataTabsExists(); - await pageObjects.svlSearchIndexDetailPage.expectShouldDefaultToDataTab(); + await pageObjects.svlSearchIndexDetailPage.expectUrlShouldChangeTo('data'); }); it('should be able to change tabs to mappings and mappings is shown', async () => { await pageObjects.svlSearchIndexDetailPage.withDataChangeTabs('mappingsTab'); @@ -187,11 +187,38 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { before(async () => { await es.indices.create({ index: indexName }); await security.testUser.setRoles(['index_management_user']); + }); + beforeEach(async () => { await pageObjects.common.navigateToApp('indexManagement'); // Navigate to the indices tab await pageObjects.indexManagement.changeTabs('indicesTab'); await pageObjects.header.waitUntilLoadingHasFinished(); }); + after(async () => { + await esDeleteAllIndices(indexName); + }); + describe('manage index action', () => { + beforeEach(async () => { + await pageObjects.indexManagement.manageIndex(indexName); + await pageObjects.indexManagement.manageIndexContextMenuExists(); + }); + it('navigates to overview tab', async () => { + await pageObjects.indexManagement.changeManageIndexTab('showOverviewIndexMenuButton'); + await pageObjects.svlSearchIndexDetailPage.expectIndexDetailPageHeader(); + await pageObjects.svlSearchIndexDetailPage.expectUrlShouldChangeTo('data'); + }); + + it('navigates to settings tab', async () => { + await pageObjects.indexManagement.changeManageIndexTab('showSettingsIndexMenuButton'); + await pageObjects.svlSearchIndexDetailPage.expectIndexDetailPageHeader(); + await pageObjects.svlSearchIndexDetailPage.expectUrlShouldChangeTo('settings'); + }); + it('navigates to mappings tab', async () => { + await pageObjects.indexManagement.changeManageIndexTab('showMappingsIndexMenuButton'); + await pageObjects.svlSearchIndexDetailPage.expectIndexDetailPageHeader(); + await pageObjects.svlSearchIndexDetailPage.expectUrlShouldChangeTo('mappings'); + }); + }); describe('can view search index details', function () { it('renders search index details with no documents', async () => { await pageObjects.svlSearchIndexDetailPage.openIndicesDetailFromIndexManagementIndicesListTable(