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/**/*",