From fdec4bf4740d0c4c264326c0703c9ba11ce954ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Fri, 3 Nov 2023 16:26:34 +0100 Subject: [PATCH] [Index Management] Add content to index details page via extensions service (#170054) ## Summary Fixes https://github.com/elastic/kibana/issues/168704 This PR adds a function to the extensions service that allows to render custom content on overview tab of the index details page. When custom content is set, it will be rendered instead of the code block describing adding documents to the index. This PR also moves the ILM content from the overview tab to a separate tab. We will work on the design of this tab in a follow up PR. ### How to test To test the custom content apply changes in this [commit](https://github.com/elastic/kibana/pull/170054/commits/16769d6c3906000ee939fa5cf177af6e309256b6). ### Screenshots #### Custom content (example) Screenshot 2023-11-01 at 19 03 32 #### ILM tab Screenshot 2023-11-01 at 18 54 07 ### Checklist - [ ] 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) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [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 - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Alison Goryachev --- docs/developer/plugin-list.asciidoc | 2 +- .../__jest__/extend_index_management.test.tsx | 37 ++-- .../common/types/policies.ts | 6 - .../add_lifecycle_confirm_modal.tsx | 3 +- .../components/index_lifecycle_summary.tsx | 34 ++- .../public/extend_index_management/index.tsx | 10 +- .../server/plugin.ts | 8 +- x-pack/plugins/index_management/README.md | 43 ++++ .../client_integration/home/home.test.ts | 9 - .../home/indices_tab.test.ts | 8 + .../index_details_page.helpers.ts | 10 +- .../index_details_page.test.tsx | 56 ++--- .../common/constants/home_sections.ts | 15 +- .../common/constants/index.ts | 2 +- .../index_management/common/types/indices.ts | 7 +- .../index_list/details_page/details_page.tsx | 196 +----------------- .../details_page/details_page_content.tsx | 176 ++++++++++++++++ .../details_page_overview.tsx | 118 ++++++----- .../extensions_summary.tsx | 34 --- .../details_page/details_page_tab.tsx | 29 +++ .../details_page/manage_index_button.tsx | 20 +- .../public/application/services/routing.ts | 4 +- .../services/extensions_service.mock.ts | 2 +- .../public/services/extensions_service.ts | 34 ++- x-pack/plugins/index_management/tsconfig.json | 1 + 25 files changed, 456 insertions(+), 408 deletions(-) create mode 100644 x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_content.tsx delete mode 100644 x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/extensions_summary.tsx create mode 100644 x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_tab.tsx diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 40c8fbf2b338b..868ef59f47966 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -598,7 +598,7 @@ Index Management by running this series of requests in Console: |{kib-repo}blob/{branch}/x-pack/plugins/index_management/README.md[indexManagement] -|Create an index with special characters and verify it renders correctly: +|This service is exposed from the Index Management setup contract and can be used to add content to the indices list and the index details page. |{kib-repo}blob/{branch}/x-pack/plugins/infra/README.md[infra] diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.tsx index be64e1a4674aa..244536ad4afef 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.tsx @@ -5,12 +5,12 @@ * 2.0. */ +import React from 'react'; import moment from 'moment-timezone'; import { init } from '../integration_tests/helpers/http_requests'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/public/mocks'; -import { Index } from '../common/types'; import { retryLifecycleActionExtension, removeLifecyclePolicyActionExtension, @@ -20,8 +20,8 @@ import { } from '../public/extend_index_management'; import { init as initHttp } from '../public/application/services/http'; import { init as initUiMetric } from '../public/application/services/ui_metric'; -import { IndexLifecycleSummary } from '../public/extend_index_management/components/index_lifecycle_summary'; -import React from 'react'; +import { indexLifecycleTab } from '../public/extend_index_management/components/index_lifecycle_summary'; +import { Index } from '@kbn/index-management-plugin/common'; const { httpSetup } = init(); @@ -113,6 +113,7 @@ const indexWithLifecycleError: Index = { }, phase_execution: { policy: 'testy', + // @ts-expect-error ILM type is incorrect https://github.com/elastic/elasticsearch-specification/issues/2326 phase_definition: { min_age: '0s', actions: { rollover: { max_size: '1gb' } } }, version: 1, modified_date_in_millis: 1544031699844, @@ -243,29 +244,31 @@ describe('extend index management', () => { }); describe('ilm summary extension', () => { - test('should render null when index has no index lifecycle policy', () => { - const extension = ( - - ); - const rendered = mountWithIntl(extension); - expect(rendered.isEmptyRender()).toBeTruthy(); + const IlmComponent = indexLifecycleTab.renderTabContent; + test('should not render the tab when index has no index lifecycle policy', () => { + const shouldRenderTab = + indexLifecycleTab.shouldRenderTab && + indexLifecycleTab.shouldRenderTab({ + index: indexWithoutLifecyclePolicy, + }); + expect(shouldRenderTab).toBeFalsy(); }); test('should return extension when index has lifecycle policy', () => { - const extension = ( - + const ilmContent = ( + ); - expect(extension).toBeDefined(); - const rendered = mountWithIntl(extension); + expect(ilmContent).toBeDefined(); + const rendered = mountWithIntl(ilmContent); expect(rendered.render()).toMatchSnapshot(); }); test('should return extension when index has lifecycle error', () => { - const extension = ( - + const ilmContent = ( + ); - expect(extension).toBeDefined(); - const rendered = mountWithIntl(extension); + expect(ilmContent).toBeDefined(); + const rendered = mountWithIntl(ilmContent); expect(rendered.render()).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts index fcc9e89da4796..c241ce6b5c58d 100644 --- a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts +++ b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts @@ -5,8 +5,6 @@ * 2.0. */ -import { Index as IndexInterface } from '@kbn/index-management-plugin/common/types'; - export type Phase = keyof Phases; export type PhaseWithAllocation = 'warm' | 'cold'; @@ -244,7 +242,3 @@ export interface IndexLifecyclePolicy { }; step_time_millis?: number; } - -export interface Index extends IndexInterface { - ilm: IndexLifecyclePolicy; -} diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.tsx b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.tsx index 015c1576de5db..79af627f578b1 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.tsx @@ -25,10 +25,11 @@ import { EuiModalHeaderTitle, } from '@elastic/eui'; +import { Index } from '@kbn/index-management-plugin/common'; import { loadPolicies, addLifecyclePolicyToIndex } from '../../application/services/api'; import { showApiError } from '../../application/services/api_errors'; import { toasts } from '../../application/services/notification'; -import { Index, PolicyFromES } from '../../../common/types'; +import { PolicyFromES } from '../../../common/types'; interface Props { indexName: string; diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx index f1468117dc53c..162457479788f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx @@ -25,10 +25,12 @@ import { } from '@elastic/eui'; import { ApplicationStart } from '@kbn/core/public'; +import { Index } from '@kbn/index-management-plugin/common'; +import { IndexDetailsTab } from '@kbn/index-management-plugin/common/constants'; +import { IlmExplainLifecycleLifecycleExplainManaged } from '@elastic/elasticsearch/lib/api/types'; import { getPolicyEditPath } from '../../application/services/navigation'; -import { Index, IndexLifecyclePolicy } from '../../../common/types'; -const getHeaders = (): Array<[keyof IndexLifecyclePolicy, string]> => { +const getHeaders = (): Array<[keyof IlmExplainLifecycleLifecycleExplainManaged, string]> => { return [ [ 'policy', @@ -85,7 +87,9 @@ interface Props { export const IndexLifecycleSummary: FunctionComponent = ({ index, getUrlForApp }) => { const [showPhaseExecutionPopover, setShowPhaseExecutionPopover] = useState(false); - const { ilm } = index; + const { ilm: ilmData } = index; + // only ILM managed indices render the ILM tab + const ilm = ilmData as IlmExplainLifecycleLifecycleExplainManaged; const togglePhaseExecutionPopover = () => { setShowPhaseExecutionPopover(!showPhaseExecutionPopover); @@ -144,15 +148,15 @@ export const IndexLifecycleSummary: FunctionComponent = ({ index, getUrlF right: [], }; headers.forEach(([fieldName, label], arrayIndex) => { - const value: any = ilm[fieldName]; + const value = ilm[fieldName]; let content; if (fieldName === 'action_time_millis') { - content = moment(value).format('YYYY-MM-DD HH:mm:ss'); + content = moment(value as string).format('YYYY-MM-DD HH:mm:ss'); } else if (fieldName === 'policy') { content = ( {value} @@ -184,9 +188,6 @@ export const IndexLifecycleSummary: FunctionComponent = ({ index, getUrlF return rows; }; - if (!ilm.managed) { - return null; - } const { left, right } = buildRows(); return ( <> @@ -243,3 +244,18 @@ export const IndexLifecycleSummary: FunctionComponent = ({ index, getUrlF ); }; + +export const indexLifecycleTab: IndexDetailsTab = { + id: 'ilm', + name: ( + + ), + order: 50, + renderTabContent: IndexLifecycleSummary, + shouldRenderTab: ({ index }) => { + return !!index.ilm && index.ilm.managed; + }, +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.tsx b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.tsx index 9d3d14f11e685..519a0606c36aa 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.tsx @@ -11,20 +11,19 @@ import { i18n } from '@kbn/i18n'; import { EuiSearchBar } from '@elastic/eui'; import { ApplicationStart } from '@kbn/core/public'; -import { IndexManagementPluginSetup } from '@kbn/index-management-plugin/public'; +import { Index, IndexManagementPluginSetup } from '@kbn/index-management-plugin/public'; import { retryLifecycleForIndex } from '../application/services/api'; -import { IndexLifecycleSummary } from './components/index_lifecycle_summary'; +import { indexLifecycleTab } from './components/index_lifecycle_summary'; import { AddLifecyclePolicyConfirmModal } from './components/add_lifecycle_confirm_modal'; import { RemoveLifecyclePolicyConfirmModal } from './components/remove_lifecycle_confirm_modal'; -import { Index } from '../../common/types'; const stepPath = 'ilm.step'; export const retryLifecycleActionExtension = ({ indices }: { indices: Index[] }) => { const allHaveErrors = every(indices, (index) => { - return index.ilm && index.ilm.failed_step; + return index.ilm?.managed && index.ilm.failed_step; }); if (!allHaveErrors) { return null; @@ -224,6 +223,7 @@ export const addAllExtensions = ( extensionsService.addAction(addLifecyclePolicyActionExtension); extensionsService.addBanner(ilmBannerExtension); - extensionsService.addSummary(IndexLifecycleSummary); extensionsService.addFilter(ilmFilterExtension); + + extensionsService.addIndexDetailsTab(indexLifecycleTab); }; diff --git a/x-pack/plugins/index_lifecycle_management/server/plugin.ts b/x-pack/plugins/index_lifecycle_management/server/plugin.ts index 011825ed9bf77..6b21a477866bb 100644 --- a/x-pack/plugins/index_lifecycle_management/server/plugin.ts +++ b/x-pack/plugins/index_lifecycle_management/server/plugin.ts @@ -9,9 +9,8 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, Plugin, Logger, PluginInitializerContext } from '@kbn/core/server'; import { IScopedClusterClient } from '@kbn/core/server'; -import { Index as IndexWithoutIlm } from '@kbn/index-management-plugin/common/types'; +import { Index } from '@kbn/index-management-plugin/common/types'; import { PLUGIN } from '../common/constants'; -import { Index } from '../common/types'; import { Dependencies } from './types'; import { registerApiRoutes } from './routes'; import { License } from './services'; @@ -19,7 +18,7 @@ import { IndexLifecycleManagementConfig } from './config'; import { handleEsError } from './shared_imports'; const indexLifecycleDataEnricher = async ( - indicesList: IndexWithoutIlm[], + indicesList: Index[], client: IScopedClusterClient ): Promise => { if (!indicesList || !indicesList.length) { @@ -29,8 +28,7 @@ const indexLifecycleDataEnricher = async ( const { indices: ilmIndicesData } = await client.asCurrentUser.ilm.explainLifecycle({ index: '*', }); - // @ts-expect-error IndexLifecyclePolicy is not compatible with IlmExplainLifecycleResponse - return indicesList.map((index: IndexWithoutIlm) => { + return indicesList.map((index: Index) => { return { ...index, ilm: { ...(ilmIndicesData[index.name] || {}) }, diff --git a/x-pack/plugins/index_management/README.md b/x-pack/plugins/index_management/README.md index 8ac2837a683b6..8673447fc577c 100644 --- a/x-pack/plugins/index_management/README.md +++ b/x-pack/plugins/index_management/README.md @@ -1,4 +1,47 @@ # Index Management UI +## Extensions service +This service is exposed from the Index Management setup contract and can be used to add content to the indices list and the index details page. +### Extensions to the indices list +- `addBanner(banner: any)`: adds a banner on top of the indices list, for example when some indices run into an ILM issue +- `addFilter(filter: any)`: adds a filter to the indices list, for example to filter indices managed by ILM +- `addToggle(toggle: any)`: adds a toggle to the indices list, for example to display hidden indices + +#### Extensions to the indices list and the index details page +- `addAction(action: any)`: adds an option to the "manage index" menu, for example to add an ILM policy to the index +- `addBadge(badge: any)`: adds a badge to the index name, for example to indicate frozen, rollup or follower indices + +#### Extensions to the index details page +- `addIndexDetailsTab(tab: IndexDetailsTab)`: adds a tab to the index details page. The tab has the following interface: + +```ts +interface IndexDetailsTab { + // a unique key to identify the tab + id: IndexDetailsTabId; + // a text that is displayed on the tab label, usually a Formatted message component + name: ReactNode; + // a function that renders the content of the tab + renderTabContent: (args: { + index: Index; + getUrlForApp: ApplicationStart['getUrlForApp']; + }) => ReturnType; + // a number to specify the order of the tabs + order: number; + // an optional function to return a boolean for when to render the tab + // if omitted, the tab is always rendered + shouldRenderTab?: (args: { index: Index }) => boolean; +} +``` + +An example of adding an ILM tab can be found in [this file](https://github.com/elastic/kibana/blob/main/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx#L250). + +- `setIndexOverviewContent(content: IndexOverviewContent)`: replaces the default content in the overview tab (code block describing adding documents to the index) with the custom content. The custom content has the following interface: +```ts +interface IndexOverviewContent { + renderContent: (args: { + index: Index; + getUrlForApp: ApplicationStart['getUrlForApp']; + }) => ReturnType; +``` ## Indices tab diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts index 2085368fc7760..49216eb285498 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts @@ -10,15 +10,6 @@ import { act } from 'react-dom/test-utils'; import { setupEnvironment, nextTick } from '../helpers'; import { HomeTestBed, setup } from './home.helpers'; -/** - * The below import is required to avoid a console error warn from the "brace" package - * console.warn ../node_modules/brace/index.js:3999 - Could not load worker ReferenceError: Worker is not defined - at createWorker (//node_modules/brace/index.js:17992:5) - */ -import { stubWebWorker } from '@kbn/test-jest-helpers'; -stubWebWorker(); - describe('', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); let testBed: HomeTestBed; 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 1158815a62fcc..f7990a3288f04 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 @@ -135,6 +135,10 @@ describe('', () => { it('navigates to the index details page when the index name is clicked', async () => { const indexName = 'testIndex'; httpRequestsMockHelpers.setLoadIndicesResponse([createNonDataStreamIndex(indexName)]); + httpRequestsMockHelpers.setLoadIndexDetailsResponse( + indexName, + createNonDataStreamIndex(indexName) + ); testBed = await setup(httpSetup, { history: createMemoryHistory(), @@ -150,6 +154,10 @@ describe('', () => { it('index page works with % character in index name', async () => { const indexName = 'test%'; httpRequestsMockHelpers.setLoadIndicesResponse([createNonDataStreamIndex(indexName)]); + httpRequestsMockHelpers.setLoadIndexDetailsResponse( + encodeURIComponent(indexName), + createNonDataStreamIndex(indexName) + ); testBed = await setup(httpSetup); const { component, actions } = testBed; 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 cd0c81715cbe9..732fc8f0456fa 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 @@ -14,7 +14,7 @@ import { import { HttpSetup } from '@kbn/core/public'; import { act } from 'react-dom/test-utils'; -import { IndexDetailsTabIds } from '../../../common/constants'; +import { IndexDetailsTabId } from '../../../common/constants'; import { IndexDetailsPage } from '../../../public/application/sections/home/index_list/details_page'; import { WithAppDependencies } from '../helpers'; import { testIndexName } from './mocks'; @@ -35,7 +35,7 @@ export interface IndexDetailsPageTestBed extends TestBed { routerMock: typeof reactRouterMock; actions: { getHeader: () => string; - clickIndexDetailsTab: (tab: IndexDetailsTabIds) => Promise; + clickIndexDetailsTab: (tab: IndexDetailsTabId) => Promise; getIndexDetailsTabs: () => string[]; getActiveTabContent: () => string; mappings: { @@ -88,7 +88,6 @@ export interface IndexDetailsPageTestBed extends TestBed { getDataStreamDetailsContent: () => string; reloadDataStreamDetails: () => Promise; addDocCodeBlockExists: () => boolean; - extensionSummaryExists: (index: number) => boolean; }; }; } @@ -127,7 +126,7 @@ export const setup = async ({ return component.find('[data-test-subj="indexDetailsHeader"] h1').text(); }; - const clickIndexDetailsTab = async (tab: IndexDetailsTabIds) => { + const clickIndexDetailsTab = async (tab: IndexDetailsTabId) => { await act(async () => { find(`indexDetailsTab-${tab}`).simulate('click'); }); @@ -178,9 +177,6 @@ export const setup = async ({ addDocCodeBlockExists: () => { return exists('codeBlockControlsPanel'); }, - extensionSummaryExists: (index: number) => { - return exists(`extensionsSummary-${index}`); - }, }; const mappings = { diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx index 76247c483b3c4..9f9018edceead 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.test.tsx @@ -11,11 +11,7 @@ import { act } from 'react-dom/test-utils'; import React from 'react'; -import { - IndexDetailsSection, - IndexDetailsTab, - IndexDetailsTabIds, -} from '../../../common/constants'; +import { IndexDetailsSection, IndexDetailsTab, IndexDetailsTabId } from '../../../common/constants'; import { API_BASE_PATH, Index, INTERNAL_API_BASE_PATH } from '../../../common'; import { @@ -399,57 +395,28 @@ describe('', () => { expect(testBed.actions.overview.addDocCodeBlockExists()).toBe(true); }); - describe('extension service summary', () => { - it('renders all summaries added to the extension service', async () => { + describe('extension service overview content', () => { + it('renders the content instead of the default code block', async () => { + const extensionsServiceOverview = 'Test content via extensions service'; await act(async () => { testBed = await setup({ httpSetup, dependencies: { services: { extensionsService: { - summaries: [() => test, () => test2], + _indexOverviewContent: { + renderContent: () => extensionsServiceOverview, + }, }, }, }, }); }); testBed.component.update(); - expect(testBed.actions.overview.extensionSummaryExists(0)).toBe(true); - expect(testBed.actions.overview.extensionSummaryExists(1)).toBe(true); - }); - it(`doesn't render empty panels if the summary renders null`, async () => { - await act(async () => { - testBed = await setup({ - httpSetup, - dependencies: { - services: { - extensionsService: { - summaries: [() => null], - }, - }, - }, - }); - }); - testBed.component.update(); - expect(testBed.actions.overview.extensionSummaryExists(0)).toBe(false); - }); - - it(`doesn't render anything when no summaries added to the extension service`, async () => { - await act(async () => { - testBed = await setup({ - httpSetup, - dependencies: { - services: { - extensionsService: { - summaries: [], - }, - }, - }, - }); - }); - testBed.component.update(); - expect(testBed.actions.overview.extensionSummaryExists(0)).toBe(false); + expect(testBed.actions.overview.addDocCodeBlockExists()).toBe(false); + const content = testBed.actions.getActiveTabContent(); + expect(content).toContain(extensionsServiceOverview); }); }); }); @@ -851,8 +818,9 @@ describe('', () => { ); }); }); + describe('extension service tabs', () => { - const testTabId = 'testTab' as IndexDetailsTabIds; + const testTabId = 'testTab' as IndexDetailsTabId; const testContent = 'Test content'; const additionalTab: IndexDetailsTab = { id: testTabId, diff --git a/x-pack/plugins/index_management/common/constants/home_sections.ts b/x-pack/plugins/index_management/common/constants/home_sections.ts index c478c9ea0ec67..436a9ae56aa1a 100644 --- a/x-pack/plugins/index_management/common/constants/home_sections.ts +++ b/x-pack/plugins/index_management/common/constants/home_sections.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { ReactNode } from 'react'; +import { FunctionComponent, ReactNode } from 'react'; +import { ApplicationStart } from '@kbn/core-application-browser'; import { Index } from '../types'; export enum Section { @@ -23,15 +24,21 @@ export enum IndexDetailsSection { Stats = 'stats', } -export type IndexDetailsTabIds = IndexDetailsSection | string; +export type IndexDetailsTabId = IndexDetailsSection | string; export interface IndexDetailsTab { // a unique key to identify the tab - id: IndexDetailsTabIds; + id: IndexDetailsTabId; // a text that is displayed on the tab label, usually a Formatted message component name: ReactNode; // a function that renders the content of the tab - renderTabContent: (indexName: string, index: Index) => ReactNode; + renderTabContent: (args: { + index: Index; + getUrlForApp: ApplicationStart['getUrlForApp']; + }) => ReturnType; // a number to specify the order of the tabs order: number; + // an optional function to return a boolean for when to render the tab + // if omitted, the tab is always rendered + shouldRenderTab?: (args: { index: Index }) => boolean; } diff --git a/x-pack/plugins/index_management/common/constants/index.ts b/x-pack/plugins/index_management/common/constants/index.ts index b84442a89d49f..a41f3d71bc6bc 100644 --- a/x-pack/plugins/index_management/common/constants/index.ts +++ b/x-pack/plugins/index_management/common/constants/index.ts @@ -49,4 +49,4 @@ export { export { MAJOR_VERSION } from './plugin'; export { Section, IndexDetailsSection } from './home_sections'; -export type { IndexDetailsTab, IndexDetailsTabIds } from './home_sections'; +export type { IndexDetailsTab, IndexDetailsTabId } from './home_sections'; diff --git a/x-pack/plugins/index_management/common/types/indices.ts b/x-pack/plugins/index_management/common/types/indices.ts index ab6080f30fdb2..b5f70ec5e1463 100644 --- a/x-pack/plugins/index_management/common/types/indices.ts +++ b/x-pack/plugins/index_management/common/types/indices.ts @@ -7,6 +7,7 @@ import { HealthStatus, + IlmExplainLifecycleLifecycleExplain, IndicesStatsIndexMetadataState, Uuid, } from '@elastic/elasticsearch/lib/api/types'; @@ -56,6 +57,7 @@ export interface IndexSettings { analysis?: AnalysisModule; [key: string]: any; } + export interface Index { name: string; primary?: number | string; @@ -67,10 +69,7 @@ export interface Index { // The types below are added by extension services if corresponding plugins are enabled (ILM, Rollup, CCR) isRollupIndex?: boolean; - ilm?: { - index: string; - managed: boolean; - }; + ilm?: IlmExplainLifecycleLifecycleExplain; isFollowerIndex?: boolean; // The types from here below represent information returned from the index stats API; 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 f3e54ae946a99..f028dee3d8eee 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 @@ -6,142 +6,29 @@ */ import React, { useCallback, useEffect, useMemo, useState, FunctionComponent } from 'react'; -import { css } from '@emotion/react'; import { RouteComponentProps } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; -import { - EuiPageHeader, - EuiSpacer, - EuiPageHeaderProps, - EuiPageSection, - EuiButton, - EuiPageTemplate, - EuiText, - EuiCode, -} from '@elastic/eui'; +import { EuiPageTemplate, EuiText, EuiCode } from '@elastic/eui'; import { SectionLoading } from '@kbn/es-ui-shared-plugin/public'; -import { - Section, - IndexDetailsSection, - IndexDetailsTab, - IndexDetailsTabIds, -} from '../../../../../../common/constants'; -import { getIndexDetailsLink } from '../../../../services/routing'; +import { IndexDetailsSection, IndexDetailsTabId } from '../../../../../../common/constants'; import { Index } from '../../../../../../common'; -import { INDEX_OPEN } from '../../../../../../common/constants'; import { Error } from '../../../../../shared_imports'; import { loadIndex } from '../../../../services'; -import { useAppContext } from '../../../../app_context'; -import { DiscoverLink } from '../../../../lib/discover_link'; import { DetailsPageError } from './details_page_error'; -import { ManageIndexButton } from './manage_index_button'; -import { DetailsPageStats } from './details_page_stats'; -import { DetailsPageMappings } from './details_page_mappings'; -import { DetailsPageOverview } from './details_page_overview'; -import { DetailsPageSettings } from './details_page_settings'; +import { DetailsPageContent } from './details_page_content'; -const defaultTabs: IndexDetailsTab[] = [ - { - id: IndexDetailsSection.Overview, - name: ( - - ), - renderTabContent: (indexName: string, index: Index) => ( - - ), - order: 10, - }, - { - id: IndexDetailsSection.Mappings, - name: ( - - ), - renderTabContent: (indexName: string, index: Index) => ( - - ), - order: 20, - }, - { - id: IndexDetailsSection.Settings, - name: ( - - ), - renderTabContent: (indexName: string, index: Index) => ( - - ), - order: 30, - }, -]; - -const statsTab: IndexDetailsTab = { - id: IndexDetailsSection.Stats, - name: , - renderTabContent: (indexName: string, index: Index) => ( - - ), - order: 40, -}; - -const getSelectedTabContent = ({ - tabs, - indexDetailsSection, - index, - indexName, -}: { - tabs: IndexDetailsTab[]; - indexDetailsSection: IndexDetailsTabIds; - index?: Index | null; - indexName: string; -}) => { - // if there is no index data, the tab content won't be rendered, so it's safe to return null here - if (!index) { - return null; - } - const selectedTab = tabs.find((tab) => tab.id === indexDetailsSection); - return selectedTab ? ( - selectedTab.renderTabContent(indexName, index) - ) : ( - - ); -}; export const DetailsPage: FunctionComponent< RouteComponentProps<{ indexName: string; indexDetailsSection: IndexDetailsSection }> > = ({ location: { search }, history }) => { - const { - config, - services: { extensionsService }, - } = useAppContext(); const queryParams = useMemo(() => new URLSearchParams(search), [search]); const indexName = queryParams.get('indexName') ?? ''; - - const tabs = useMemo(() => { - const sortedTabs = [...defaultTabs]; - if (config.enableIndexStats) { - sortedTabs.push(statsTab); - } - sortedTabs.push(...extensionsService.indexDetailsTabs); - - sortedTabs.sort((tabA, tabB) => { - return tabA.order - tabB.order; - }); - return sortedTabs; - }, [config.enableIndexStats, extensionsService.indexDetailsTabs]); - - const tabQueryParam = queryParams.get('tab') ?? IndexDetailsSection.Overview; - let indexDetailsSection = IndexDetailsSection.Overview; - if (tabs.map((tab) => tab.id).includes(tabQueryParam as IndexDetailsTabIds)) { - indexDetailsSection = tabQueryParam as IndexDetailsSection; - } + const tab: IndexDetailsTabId = queryParams.get('tab') ?? IndexDetailsSection.Overview; const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [index, setIndex] = useState(); - const selectedTabContent = useMemo(() => { - return getSelectedTabContent({ tabs, indexDetailsSection, index, indexName }); - }, [index, indexDetailsSection, indexName, tabs]); - const fetchIndexDetails = useCallback(async () => { if (indexName) { setIsLoading(true); @@ -161,27 +48,6 @@ export const DetailsPage: FunctionComponent< fetchIndexDetails(); }, [fetchIndexDetails]); - const onSectionChange = useCallback( - (newSection: IndexDetailsTabIds) => { - return history.push(getIndexDetailsLink(indexName, newSection)); - }, - [history, indexName] - ); - - const navigateToAllIndices = useCallback(() => { - history.push(`/${Section.Indices}`); - }, [history]); - - const headerTabs = useMemo(() => { - return tabs.map((tab) => ({ - onClick: () => onSectionChange(tab.id), - isSelected: tab.id === indexDetailsSection, - key: tab.id, - 'data-test-subj': `indexDetailsTab-${tab.id}`, - label: tab.name, - })); - }, [tabs, indexDetailsSection, onSectionChange]); - if (!indexName) { return ( ; } return ( - <> - - - - - - - - - , - , - ]} - rightSideGroupProps={{ - wrap: false, - }} - responsive="reverse" - tabs={headerTabs} - /> - - - -
- {selectedTabContent} -
- + ); }; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_content.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_content.tsx new file mode 100644 index 0000000000000..1a9d5935cca19 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_content.tsx @@ -0,0 +1,176 @@ +/* + * 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, useCallback, useMemo } from 'react'; +import { + EuiButton, + EuiPageHeader, + EuiPageHeaderProps, + EuiPageSection, + EuiSpacer, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { RouteComponentProps } from 'react-router-dom'; + +import { Index } from '../../../../../../common'; +import { + INDEX_OPEN, + IndexDetailsSection, + IndexDetailsTab, + IndexDetailsTabId, + Section, +} from '../../../../../../common/constants'; +import { getIndexDetailsLink } from '../../../../services/routing'; +import { useAppContext } from '../../../../app_context'; +import { DiscoverLink } from '../../../../lib/discover_link'; +import { ManageIndexButton } from './manage_index_button'; +import { DetailsPageOverview } from './details_page_overview'; +import { DetailsPageMappings } from './details_page_mappings'; +import { DetailsPageSettings } from './details_page_settings'; +import { DetailsPageStats } from './details_page_stats'; +import { DetailsPageTab } from './details_page_tab'; + +const defaultTabs: IndexDetailsTab[] = [ + { + id: IndexDetailsSection.Overview, + name: ( + + ), + renderTabContent: ({ index }) => , + order: 10, + }, + { + id: IndexDetailsSection.Mappings, + name: ( + + ), + renderTabContent: ({ index }) => , + order: 20, + }, + { + id: IndexDetailsSection.Settings, + name: ( + + ), + renderTabContent: ({ index }) => ( + + ), + order: 30, + }, +]; + +const statsTab: IndexDetailsTab = { + id: IndexDetailsSection.Stats, + name: , + renderTabContent: ({ index }) => ( + + ), + order: 40, +}; + +interface Props { + index: Index; + tab: IndexDetailsTabId; + history: RouteComponentProps['history']; + fetchIndexDetails: () => Promise; +} +export const DetailsPageContent: FunctionComponent = ({ + index, + tab, + history, + fetchIndexDetails, +}) => { + const { + config: { enableIndexStats }, + services: { extensionsService }, + } = useAppContext(); + + const tabs = useMemo(() => { + const sortedTabs = [...defaultTabs]; + if (enableIndexStats) { + sortedTabs.push(statsTab); + } + extensionsService.indexDetailsTabs.forEach((dynamicTab) => { + if (!dynamicTab.shouldRenderTab || dynamicTab.shouldRenderTab({ index })) { + sortedTabs.push(dynamicTab); + } + }); + + sortedTabs.sort((tabA, tabB) => { + return tabA.order - tabB.order; + }); + return sortedTabs; + }, [enableIndexStats, extensionsService.indexDetailsTabs, index]); + + const onSectionChange = useCallback( + (newSection: IndexDetailsTabId) => { + return history.push(getIndexDetailsLink(index.name, newSection)); + }, + [history, index] + ); + + const navigateToAllIndices = useCallback(() => { + history.push(`/${Section.Indices}`); + }, [history]); + + const headerTabs = useMemo(() => { + return tabs.map((tabConfig) => ({ + onClick: () => onSectionChange(tabConfig.id), + isSelected: tabConfig.id === tab, + key: tabConfig.id, + 'data-test-subj': `indexDetailsTab-${tabConfig.id}`, + label: tabConfig.name, + })); + }, [tabs, tab, onSectionChange]); + + return ( + <> + + + + + + + , + , + ]} + rightSideGroupProps={{ + wrap: false, + }} + responsive="reverse" + tabs={headerTabs} + /> + +
+ +
+ + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/details_page_overview.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/details_page_overview.tsx index 624a58f6e722a..0a9503c56cb59 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/details_page_overview.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/details_page_overview.tsx @@ -32,7 +32,6 @@ import { useAppContext } from '../../../../../app_context'; import { documentationService } from '../../../../../services'; import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../../services/breadcrumbs'; import { languageDefinitions, curlDefinition } from './languages'; -import { ExtensionsSummary } from './extensions_summary'; import { DataStreamDetails } from './data_stream_details'; import { StorageDetails } from './storage_details'; import { AliasesDetails } from './aliases_details'; @@ -55,7 +54,11 @@ export const DetailsPageOverview: React.FunctionComponent = ({ indexDetai size, primary_size: primarySize, } = indexDetails; - const { core, plugins } = useAppContext(); + const { + core, + plugins, + services: { extensionsService }, + } = useAppContext(); useEffect(() => { breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.indexDetailsOverview); @@ -94,59 +97,64 @@ export const DetailsPageOverview: React.FunctionComponent = ({ indexDetai - - - - - -

- {i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.addMoreDataTitle', { - defaultMessage: 'Add data to this index', - })} -

-
- - - - - -

- - - - ), - }} - /> -

-
-
-
- - - - -
+ {extensionsService.indexOverviewContent ? ( + extensionsService.indexOverviewContent.renderContent({ + index: indexDetails, + getUrlForApp: core.getUrlForApp, + }) + ) : ( + + + +

+ {i18n.translate('xpack.idxMgmt.indexDetails.overviewTab.addMoreDataTitle', { + defaultMessage: 'Add data to this index', + })} +

+
+ + + + + +

+ + + + ), + }} + /> +

+
+
+
+ + + + +
+ )} ); }; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/extensions_summary.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/extensions_summary.tsx deleted file mode 100644 index b119a99cd1a0a..0000000000000 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_overview/extensions_summary.tsx +++ /dev/null @@ -1,34 +0,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 React, { Fragment, FunctionComponent } from 'react'; -import { EuiPanel, EuiSpacer } from '@elastic/eui'; -import { Index } from '../../../../../../../common'; -import { useAppContext } from '../../../../../app_context'; - -export const ExtensionsSummary: FunctionComponent<{ index: Index }> = ({ index }) => { - const { - services: { extensionsService }, - core: { getUrlForApp }, - } = useAppContext(); - const summaries = extensionsService.summaries.map((summaryExtension, i) => { - const summary = summaryExtension({ index, getUrlForApp }); - - if (!summary) { - return null; - } - return ( - - - {summary} - - - - ); - }); - return <>{summaries}; -}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_tab.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_tab.tsx new file mode 100644 index 0000000000000..9f760aab91324 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_tab.tsx @@ -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. + */ + +import React, { FunctionComponent } from 'react'; +import { Index } from '../../../../../../common'; +import { IndexDetailsTab, IndexDetailsTabId } from '../../../../../../common/constants'; +import { useAppContext } from '../../../../app_context'; +import { DetailsPageOverview } from './details_page_overview'; + +interface Props { + tabs: IndexDetailsTab[]; + tab: IndexDetailsTabId; + index: Index; +} +export const DetailsPageTab: FunctionComponent = ({ tabs, tab, index }) => { + const selectedTab = tabs.find((tabConfig) => tabConfig.id === tab); + const { + core: { getUrlForApp }, + } = useAppContext(); + return selectedTab ? ( + selectedTab.renderTabContent({ index, getUrlForApp }) + ) : ( + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/manage_index_button.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/manage_index_button.tsx index 2d7d7aab0d23f..8bfaffa51273f 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/manage_index_button.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/manage_index_button.tsx @@ -41,21 +41,26 @@ const getIndexStatusByName = ( }; interface Props { - indexName: string; - indexDetails: Index; + index: Index; reloadIndexDetails: () => Promise; navigateToAllIndices: () => void; } + +/** + * This component is a wrapper for the underlying "index actions context menu" that is currently used + * in the indices list and works with redux. That is why all request helpers from the services are expecting + * an array of indices, for example "deleteIndices(indexNames)". + * + */ export const ManageIndexButton: FunctionComponent = ({ - indexName, - indexDetails, + index, reloadIndexDetails, navigateToAllIndices, }) => { const [isLoading, setIsLoading] = useState(false); - // the variables are created to write the index actions in a way to later re-use for indices list without redux - const indexNames = useMemo(() => [indexName], [indexName]); + // the "index actions context menu" component is expecting an array of indices, the same as on the indices list + const indexNames = useMemo(() => [index.name], [index]); const reloadIndices = useCallback(async () => { setIsLoading(true); @@ -63,7 +68,8 @@ export const ManageIndexButton: FunctionComponent = ({ setIsLoading(false); }, [reloadIndexDetails]); - const indices = [indexDetails]; + // the "index actions context menu" component is expecting an array of indices, the same as on the indices list + const indices = [index]; const indexStatusByName = getIndexStatusByName(indexNames, indices); const closeIndices = useCallback(async () => { 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 1d3a41f0d54a3..3ec13eca06516 100644 --- a/x-pack/plugins/index_management/public/application/services/routing.ts +++ b/x-pack/plugins/index_management/public/application/services/routing.ts @@ -6,7 +6,7 @@ */ import { Section } from '../../../common/constants'; -import type { IndexDetailsTabIds } from '../../../common/constants'; +import type { IndexDetailsTabId } from '../../../common/constants'; export const getTemplateListLink = () => `/templates`; @@ -58,7 +58,7 @@ export const getDataStreamDetailsLink = (name: string) => { return encodeURI(`/data_streams/${encodeURIComponent(name)}`); }; -export const getIndexDetailsLink = (indexName: string, tab?: IndexDetailsTabIds) => { +export const getIndexDetailsLink = (indexName: string, tab?: IndexDetailsTabId) => { let link = `/${Section.Indices}/index_details?indexName=${encodeURIComponent(indexName)}`; if (tab) { link = `${link}&tab=${tab}`; diff --git a/x-pack/plugins/index_management/public/services/extensions_service.mock.ts b/x-pack/plugins/index_management/public/services/extensions_service.mock.ts index 6ef42d7944d67..3c0886b0fe4a3 100644 --- a/x-pack/plugins/index_management/public/services/extensions_service.mock.ts +++ b/x-pack/plugins/index_management/public/services/extensions_service.mock.ts @@ -15,9 +15,9 @@ const createServiceMock = (): ExtensionsSetupMock => ({ addBadge: jest.fn(), addBanner: jest.fn(), addFilter: jest.fn(), - addSummary: jest.fn(), addToggle: jest.fn(), addIndexDetailsTab: jest.fn(), + setIndexOverviewContent: jest.fn(), }); const createMock = () => { diff --git a/x-pack/plugins/index_management/public/services/extensions_service.ts b/x-pack/plugins/index_management/public/services/extensions_service.ts index 5c81e825eb1b1..5ffef366a016c 100644 --- a/x-pack/plugins/index_management/public/services/extensions_service.ts +++ b/x-pack/plugins/index_management/public/services/extensions_service.ts @@ -6,21 +6,29 @@ */ import { i18n } from '@kbn/i18n'; +import { FunctionComponent } from 'react'; +import { ApplicationStart } from '@kbn/core-application-browser'; import type { IndexDetailsTab } from '../../common/constants'; +import { Index } from '..'; + +export interface IndexOverviewContent { + renderContent: (args: { + index: Index; + getUrlForApp: ApplicationStart['getUrlForApp']; + }) => ReturnType; +} export interface ExtensionsSetup { - addSummary(summary: any): void; addAction(action: any): void; addBanner(banner: any): void; addFilter(filter: any): void; addBadge(badge: any): void; addToggle(toggle: any): void; addIndexDetailsTab(tab: IndexDetailsTab): void; + setIndexOverviewContent(content: IndexOverviewContent): void; } export class ExtensionsService { - private _indexDetailsTabs: IndexDetailsTab[] = []; - private _summaries: any[] = []; private _actions: any[] = []; private _banners: any[] = []; private _filters: any[] = []; @@ -37,6 +45,8 @@ export class ExtensionsService { }, ]; private _toggles: any[] = []; + private _indexDetailsTabs: IndexDetailsTab[] = []; + private _indexOverviewContent: IndexOverviewContent | null = null; private service?: ExtensionsSetup; public setup(): ExtensionsSetup { @@ -45,18 +55,14 @@ export class ExtensionsService { addBadge: this.addBadge.bind(this), addBanner: this.addBanner.bind(this), addFilter: this.addFilter.bind(this), - addSummary: this.addSummary.bind(this), addToggle: this.addToggle.bind(this), addIndexDetailsTab: this.addIndexDetailsTab.bind(this), + setIndexOverviewContent: this.setIndexOverviewMainContent.bind(this), }; return this.service; } - private addSummary(summary: any) { - this._summaries.push(summary); - } - private addAction(action: any) { this._actions.push(action); } @@ -81,8 +87,12 @@ export class ExtensionsService { this._indexDetailsTabs.push(tab); } - public get summaries() { - return this._summaries; + private setIndexOverviewMainContent(content: IndexOverviewContent) { + if (this._indexOverviewContent) { + throw new Error(`The content for index overview has already been set.`); + } else { + this._indexOverviewContent = content; + } } public get actions() { @@ -108,4 +118,8 @@ export class ExtensionsService { public get indexDetailsTabs() { return this._indexDetailsTabs; } + + public get indexOverviewContent() { + return this._indexOverviewContent; + } } diff --git a/x-pack/plugins/index_management/tsconfig.json b/x-pack/plugins/index_management/tsconfig.json index 7b52920e5e5d4..b1ccc4b12cdd5 100644 --- a/x-pack/plugins/index_management/tsconfig.json +++ b/x-pack/plugins/index_management/tsconfig.json @@ -41,6 +41,7 @@ "@kbn/search-api-panels", "@kbn/cloud-plugin", "@kbn/ui-theme", + "@kbn/core-application-browser", ], "exclude": [ "target/**/*",