diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts
index 56d76da522ac2..907c749f8ec0b 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts
@@ -35,6 +35,22 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
]);
};
+ const setLoadDataStreamResponse = (response: HttpResponse = []) => {
+ server.respondWith('GET', `${API_BASE_PATH}/data_streams/:id`, [
+ 200,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify(response),
+ ]);
+ };
+
+ const setDeleteDataStreamResponse = (response: HttpResponse = []) => {
+ server.respondWith('POST', `${API_BASE_PATH}/delete_data_streams`, [
+ 200,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify(response),
+ ]);
+ };
+
const setDeleteTemplateResponse = (response: HttpResponse = []) => {
server.respondWith('POST', `${API_BASE_PATH}/delete_index_templates`, [
200,
@@ -80,6 +96,8 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
setLoadTemplatesResponse,
setLoadIndicesResponse,
setLoadDataStreamsResponse,
+ setLoadDataStreamResponse,
+ setDeleteDataStreamResponse,
setDeleteTemplateResponse,
setLoadTemplateResponse,
setCreateTemplateResponse,
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx
index 0a49191fdb149..d85db94d4a970 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx
@@ -8,6 +8,7 @@
import React from 'react';
import axios from 'axios';
import axiosXhrAdapter from 'axios/lib/adapters/xhr';
+import { merge } from 'lodash';
import {
notificationServiceMock,
@@ -33,7 +34,7 @@ export const services = {
services.uiMetricService.setup({ reportUiStats() {} } as any);
setExtensionsService(services.extensionsService);
setUiMetricService(services.uiMetricService);
-const appDependencies = { services, core: {}, plugins: {} } as any;
+const appDependencies = { services, core: { getUrlForApp: () => {} }, plugins: {} } as any;
export const setupEnvironment = () => {
// Mock initialization of services
@@ -51,8 +52,13 @@ export const setupEnvironment = () => {
};
};
-export const WithAppDependencies = (Comp: any) => (props: any) => (
-
-
-
-);
+export const WithAppDependencies = (Comp: any, overridingDependencies: any = {}) => (
+ props: any
+) => {
+ const mergedDependencies = merge({}, appDependencies, overridingDependencies);
+ return (
+
+
+
+ );
+};
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts
index 572889954db6a..ecea230ecab85 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts
@@ -5,6 +5,7 @@
*/
import { act } from 'react-dom/test-utils';
+import { ReactWrapper } from 'enzyme';
import {
registerTestBed,
@@ -17,27 +18,38 @@ import { IndexManagementHome } from '../../../public/application/sections/home';
import { indexManagementStore } from '../../../public/application/store'; // eslint-disable-line @kbn/eslint/no-restricted-paths
import { WithAppDependencies, services, TestSubjects } from '../helpers';
-const testBedConfig: TestBedConfig = {
- store: () => indexManagementStore(services as any),
- memoryRouter: {
- initialEntries: [`/indices`],
- componentRoutePath: `/:section(indices|data_streams|templates)`,
- },
- doMountAsync: true,
-};
-
-const initTestBed = registerTestBed(WithAppDependencies(IndexManagementHome), testBedConfig);
-
export interface DataStreamsTabTestBed extends TestBed {
actions: {
goToDataStreamsList: () => void;
clickEmptyPromptIndexTemplateLink: () => void;
clickReloadButton: () => void;
+ clickNameAt: (index: number) => void;
clickIndicesAt: (index: number) => void;
+ clickDeletActionAt: (index: number) => void;
+ clickConfirmDelete: () => void;
+ clickDeletDataStreamButton: () => void;
};
+ findDeleteActionAt: (index: number) => ReactWrapper;
+ findDeleteConfirmationModal: () => ReactWrapper;
+ findDetailPanel: () => ReactWrapper;
+ findDetailPanelTitle: () => string;
+ findEmptyPromptIndexTemplateLink: () => ReactWrapper;
}
-export const setup = async (): Promise => {
+export const setup = async (overridingDependencies: any = {}): Promise => {
+ const testBedConfig: TestBedConfig = {
+ store: () => indexManagementStore(services as any),
+ memoryRouter: {
+ initialEntries: [`/indices`],
+ componentRoutePath: `/:section(indices|data_streams|templates)`,
+ },
+ doMountAsync: true,
+ };
+
+ const initTestBed = registerTestBed(
+ WithAppDependencies(IndexManagementHome, overridingDependencies),
+ testBedConfig
+ );
const testBed = await initTestBed();
/**
@@ -48,15 +60,17 @@ export const setup = async (): Promise => {
testBed.find('data_streamsTab').simulate('click');
};
- const clickEmptyPromptIndexTemplateLink = async () => {
- const { find, component, router } = testBed;
-
+ const findEmptyPromptIndexTemplateLink = () => {
+ const { find } = testBed;
const templateLink = find('dataStreamsEmptyPromptTemplateLink');
+ return templateLink;
+ };
+ const clickEmptyPromptIndexTemplateLink = async () => {
+ const { component, router } = testBed;
await act(async () => {
- router.navigateTo(templateLink.props().href!);
+ router.navigateTo(findEmptyPromptIndexTemplateLink().props().href!);
});
-
component.update();
};
@@ -65,10 +79,15 @@ export const setup = async (): Promise => {
find('reloadButton').simulate('click');
};
- const clickIndicesAt = async (index: number) => {
- const { component, table, router } = testBed;
+ const findTestSubjectAt = (testSubject: string, index: number) => {
+ const { table } = testBed;
const { rows } = table.getMetaData('dataStreamTable');
- const indicesLink = findTestSubject(rows[index].reactWrapper, 'indicesLink');
+ return findTestSubject(rows[index].reactWrapper, testSubject);
+ };
+
+ const clickIndicesAt = async (index: number) => {
+ const { component, router } = testBed;
+ const indicesLink = findTestSubjectAt('indicesLink', index);
await act(async () => {
router.navigateTo(indicesLink.props().href!);
@@ -77,14 +96,71 @@ export const setup = async (): Promise => {
component.update();
};
+ const clickNameAt = async (index: number) => {
+ const { component, router } = testBed;
+ const nameLink = findTestSubjectAt('nameLink', index);
+
+ await act(async () => {
+ router.navigateTo(nameLink.props().href!);
+ });
+
+ component.update();
+ };
+
+ const findDeleteActionAt = findTestSubjectAt.bind(null, 'deleteDataStream');
+
+ const clickDeletActionAt = (index: number) => {
+ findDeleteActionAt(index).simulate('click');
+ };
+
+ const findDeleteConfirmationModal = () => {
+ const { find } = testBed;
+ return find('deleteDataStreamsConfirmation');
+ };
+
+ const clickConfirmDelete = async () => {
+ const modal = document.body.querySelector('[data-test-subj="deleteDataStreamsConfirmation"]');
+ const confirmButton: HTMLButtonElement | null = modal!.querySelector(
+ '[data-test-subj="confirmModalConfirmButton"]'
+ );
+
+ await act(async () => {
+ confirmButton!.click();
+ });
+ };
+
+ const clickDeletDataStreamButton = () => {
+ const { find } = testBed;
+ find('deleteDataStreamButton').simulate('click');
+ };
+
+ const findDetailPanel = () => {
+ const { find } = testBed;
+ return find('dataStreamDetailPanel');
+ };
+
+ const findDetailPanelTitle = () => {
+ const { find } = testBed;
+ return find('dataStreamDetailPanelTitle').text();
+ };
+
return {
...testBed,
actions: {
goToDataStreamsList,
clickEmptyPromptIndexTemplateLink,
clickReloadButton,
+ clickNameAt,
clickIndicesAt,
+ clickDeletActionAt,
+ clickConfirmDelete,
+ clickDeletDataStreamButton,
},
+ findDeleteActionAt,
+ findDeleteConfirmationModal,
+ findDetailPanel,
+ findDetailPanelTitle,
+ findEmptyPromptIndexTemplateLink,
};
};
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
index efe2e2d0c74ae..dfcbb51869466 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
@@ -19,61 +19,38 @@ describe('Data Streams tab', () => {
server.restore();
});
- beforeEach(async () => {
- httpRequestsMockHelpers.setLoadIndicesResponse([
- {
- health: '',
- status: '',
- primary: '',
- replica: '',
- documents: '',
- documents_deleted: '',
- size: '',
- primary_size: '',
- name: 'data-stream-index',
- data_stream: 'dataStream1',
- },
- {
- health: 'green',
- status: 'open',
- primary: 1,
- replica: 1,
- documents: 10000,
- documents_deleted: 100,
- size: '156kb',
- primary_size: '156kb',
- name: 'non-data-stream-index',
- },
- ]);
-
- await act(async () => {
- testBed = await setup();
- });
- });
-
describe('when there are no data streams', () => {
beforeEach(async () => {
- const { actions, component } = testBed;
-
+ httpRequestsMockHelpers.setLoadIndicesResponse([]);
httpRequestsMockHelpers.setLoadDataStreamsResponse([]);
httpRequestsMockHelpers.setLoadTemplatesResponse({ templates: [], legacyTemplates: [] });
+ });
+
+ test('displays an empty prompt', async () => {
+ testBed = await setup();
await act(async () => {
- actions.goToDataStreamsList();
+ testBed.actions.goToDataStreamsList();
});
+ const { exists, component } = testBed;
component.update();
- });
-
- test('displays an empty prompt', async () => {
- const { exists } = testBed;
expect(exists('sectionLoading')).toBe(false);
expect(exists('emptyPrompt')).toBe(true);
});
- test('goes to index templates tab when "Get started" link is clicked', async () => {
- const { actions, exists } = testBed;
+ test('when Ingest Manager is disabled, goes to index templates tab when "Get started" link is clicked', async () => {
+ testBed = await setup({
+ plugins: {},
+ });
+
+ await act(async () => {
+ testBed.actions.goToDataStreamsList();
+ });
+
+ const { actions, exists, component } = testBed;
+ component.update();
await act(async () => {
actions.clickEmptyPromptIndexTemplateLink();
@@ -81,32 +58,77 @@ describe('Data Streams tab', () => {
expect(exists('templateList')).toBe(true);
});
+
+ test('when Ingest Manager is enabled, links to Ingest Manager', async () => {
+ testBed = await setup({
+ plugins: { ingestManager: { hi: 'ok' } },
+ });
+
+ await act(async () => {
+ testBed.actions.goToDataStreamsList();
+ });
+
+ const { findEmptyPromptIndexTemplateLink, component } = testBed;
+ component.update();
+
+ // Assert against the text because the href won't be available, due to dependency upon our core mock.
+ expect(findEmptyPromptIndexTemplateLink().text()).toBe('Ingest Manager');
+ });
});
describe('when there are data streams', () => {
beforeEach(async () => {
- const { actions, component } = testBed;
+ httpRequestsMockHelpers.setLoadIndicesResponse([
+ {
+ health: '',
+ status: '',
+ primary: '',
+ replica: '',
+ documents: '',
+ documents_deleted: '',
+ size: '',
+ primary_size: '',
+ name: 'data-stream-index',
+ data_stream: 'dataStream1',
+ },
+ {
+ health: 'green',
+ status: 'open',
+ primary: 1,
+ replica: 1,
+ documents: 10000,
+ documents_deleted: 100,
+ size: '156kb',
+ primary_size: '156kb',
+ name: 'non-data-stream-index',
+ },
+ ]);
+
+ const dataStreamForDetailPanel = createDataStreamPayload('dataStream1');
httpRequestsMockHelpers.setLoadDataStreamsResponse([
- createDataStreamPayload('dataStream1'),
+ dataStreamForDetailPanel,
createDataStreamPayload('dataStream2'),
]);
+ httpRequestsMockHelpers.setLoadDataStreamResponse(dataStreamForDetailPanel);
+
+ testBed = await setup();
+
await act(async () => {
- actions.goToDataStreamsList();
+ testBed.actions.goToDataStreamsList();
});
- component.update();
+ testBed.component.update();
});
test('lists them in the table', async () => {
const { table } = testBed;
-
const { tableCellsValues } = table.getMetaData('dataStreamTable');
expect(tableCellsValues).toEqual([
- ['dataStream1', '1', '@timestamp', '1'],
- ['dataStream2', '1', '@timestamp', '1'],
+ ['', 'dataStream1', '1', ''],
+ ['', 'dataStream2', '1', ''],
]);
});
@@ -126,12 +148,90 @@ describe('Data Streams tab', () => {
test('clicking the indices count navigates to the backing indices', async () => {
const { table, actions } = testBed;
-
await actions.clickIndicesAt(0);
-
expect(table.getMetaData('indexTable').tableCellsValues).toEqual([
['', '', '', '', '', '', '', 'dataStream1'],
]);
});
+
+ describe('row actions', () => {
+ test('can delete', () => {
+ const { findDeleteActionAt } = testBed;
+ const deleteAction = findDeleteActionAt(0);
+ expect(deleteAction.length).toBe(1);
+ });
+ });
+
+ describe('deleting a data stream', () => {
+ test('shows a confirmation modal', async () => {
+ const {
+ actions: { clickDeletActionAt },
+ findDeleteConfirmationModal,
+ } = testBed;
+ clickDeletActionAt(0);
+ const confirmationModal = findDeleteConfirmationModal();
+ expect(confirmationModal).toBeDefined();
+ });
+
+ test('sends a request to the Delete API', async () => {
+ const {
+ actions: { clickDeletActionAt, clickConfirmDelete },
+ } = testBed;
+ clickDeletActionAt(0);
+
+ httpRequestsMockHelpers.setDeleteDataStreamResponse({
+ results: {
+ dataStreamsDeleted: ['dataStream1'],
+ errors: [],
+ },
+ });
+
+ await clickConfirmDelete();
+
+ const { method, url, requestBody } = server.requests[server.requests.length - 1];
+
+ expect(method).toBe('POST');
+ expect(url).toBe(`${API_BASE_PATH}/delete_data_streams`);
+ expect(JSON.parse(JSON.parse(requestBody).body)).toEqual({
+ dataStreams: ['dataStream1'],
+ });
+ });
+ });
+
+ describe('detail panel', () => {
+ test('opens when the data stream name in the table is clicked', async () => {
+ const { actions, findDetailPanel, findDetailPanelTitle } = testBed;
+ await actions.clickNameAt(0);
+ expect(findDetailPanel().length).toBe(1);
+ expect(findDetailPanelTitle()).toBe('dataStream1');
+ });
+
+ test('deletes the data stream when delete button is clicked', async () => {
+ const {
+ actions: { clickNameAt, clickDeletDataStreamButton, clickConfirmDelete },
+ } = testBed;
+
+ await clickNameAt(0);
+
+ clickDeletDataStreamButton();
+
+ httpRequestsMockHelpers.setDeleteDataStreamResponse({
+ results: {
+ dataStreamsDeleted: ['dataStream1'],
+ errors: [],
+ },
+ });
+
+ await clickConfirmDelete();
+
+ const { method, url, requestBody } = server.requests[server.requests.length - 1];
+
+ expect(method).toBe('POST');
+ expect(url).toBe(`${API_BASE_PATH}/delete_data_streams`);
+ expect(JSON.parse(JSON.parse(requestBody).body)).toEqual({
+ dataStreams: ['dataStream1'],
+ });
+ });
+ });
});
});
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts
index f00348aacbf08..11ea29fd9b78c 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts
@@ -5,6 +5,7 @@
*/
import { act } from 'react-dom/test-utils';
+import { ReactWrapper } from 'enzyme';
import {
registerTestBed,
@@ -34,6 +35,8 @@ export interface IndicesTestBed extends TestBed {
clickIncludeHiddenIndicesToggle: () => void;
clickDataStreamAt: (index: number) => void;
};
+ findDataStreamDetailPanel: () => ReactWrapper;
+ findDataStreamDetailPanelTitle: () => string;
}
export const setup = async (): Promise => {
@@ -77,6 +80,16 @@ export const setup = async (): Promise => {
component.update();
};
+ const findDataStreamDetailPanel = () => {
+ const { find } = testBed;
+ return find('dataStreamDetailPanel');
+ };
+
+ const findDataStreamDetailPanelTitle = () => {
+ const { find } = testBed;
+ return find('dataStreamDetailPanelTitle').text();
+ };
+
return {
...testBed,
actions: {
@@ -85,5 +98,7 @@ export const setup = async (): Promise => {
clickIncludeHiddenIndicesToggle,
clickDataStreamAt,
},
+ findDataStreamDetailPanel,
+ findDataStreamDetailPanelTitle,
};
};
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 c2d955bb4dfce..3d6d94d165855 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
@@ -70,10 +70,10 @@ describe('', () => {
},
]);
- httpRequestsMockHelpers.setLoadDataStreamsResponse([
- createDataStreamPayload('dataStream1'),
- createDataStreamPayload('dataStream2'),
- ]);
+ // The detail panel should still appear even if there are no data streams.
+ httpRequestsMockHelpers.setLoadDataStreamsResponse([]);
+
+ httpRequestsMockHelpers.setLoadDataStreamResponse(createDataStreamPayload('dataStream1'));
testBed = await setup();
@@ -86,13 +86,16 @@ describe('', () => {
});
test('navigates to the data stream in the Data Streams tab', async () => {
- const { table, actions } = testBed;
+ const {
+ findDataStreamDetailPanel,
+ findDataStreamDetailPanelTitle,
+ actions: { clickDataStreamAt },
+ } = testBed;
- await actions.clickDataStreamAt(0);
+ await clickDataStreamAt(0);
- expect(table.getMetaData('dataStreamTable').tableCellsValues).toEqual([
- ['dataStream1', '1', '@timestamp', '1'],
- ]);
+ expect(findDataStreamDetailPanel().length).toBe(1);
+ expect(findDataStreamDetailPanelTitle()).toBe('dataStream1');
});
});
diff --git a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts
index 9d267210a6b31..51528ed9856ce 100644
--- a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts
+++ b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts
@@ -6,8 +6,10 @@
import { DataStream, DataStreamFromEs } from '../types';
-export function deserializeDataStreamList(dataStreamsFromEs: DataStreamFromEs[]): DataStream[] {
- return dataStreamsFromEs.map(({ name, timestamp_field, indices, generation }) => ({
+export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataStream {
+ const { name, timestamp_field, indices, generation } = dataStreamFromEs;
+
+ return {
name,
timeStampField: timestamp_field,
indices: indices.map(
@@ -17,5 +19,9 @@ export function deserializeDataStreamList(dataStreamsFromEs: DataStreamFromEs[])
})
),
generation,
- }));
+ };
+}
+
+export function deserializeDataStreamList(dataStreamsFromEs: DataStreamFromEs[]): DataStream[] {
+ return dataStreamsFromEs.map((dataStream) => deserializeDataStream(dataStream));
}
diff --git a/x-pack/plugins/index_management/common/lib/index.ts b/x-pack/plugins/index_management/common/lib/index.ts
index fce4d8ccc2502..4e76a40ced524 100644
--- a/x-pack/plugins/index_management/common/lib/index.ts
+++ b/x-pack/plugins/index_management/common/lib/index.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { deserializeDataStreamList } from './data_stream_serialization';
+export { deserializeDataStream, deserializeDataStreamList } from './data_stream_serialization';
export {
deserializeLegacyTemplateList,
diff --git a/x-pack/plugins/index_management/kibana.json b/x-pack/plugins/index_management/kibana.json
index 2e0fa04337b40..40ecb26e8f0c9 100644
--- a/x-pack/plugins/index_management/kibana.json
+++ b/x-pack/plugins/index_management/kibana.json
@@ -10,7 +10,8 @@
],
"optionalPlugins": [
"security",
- "usageCollection"
+ "usageCollection",
+ "ingestManager"
],
"configPath": ["xpack", "index_management"]
}
diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx
index 84938de416941..c821907120373 100644
--- a/x-pack/plugins/index_management/public/application/app_context.tsx
+++ b/x-pack/plugins/index_management/public/application/app_context.tsx
@@ -6,9 +6,10 @@
import React, { createContext, useContext } from 'react';
import { ScopedHistory } from 'kibana/public';
-import { CoreStart } from '../../../../../src/core/public';
+import { UsageCollectionSetup } from 'src/plugins/usage_collection/public';
-import { UsageCollectionSetup } from '../../../../../src/plugins/usage_collection/public';
+import { CoreStart } from '../../../../../src/core/public';
+import { IngestManagerSetup } from '../../../ingest_manager/public';
import { IndexMgmtMetricsType } from '../types';
import { UiMetricService, NotificationService, HttpService } from './services';
import { ExtensionsService } from '../services';
@@ -22,6 +23,7 @@ export interface AppDependencies {
};
plugins: {
usageCollection: UsageCollectionSetup;
+ ingestManager?: IngestManagerSetup;
};
services: {
uiMetricService: UiMetricService;
diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts
index e8b6f200fb349..258f32865720a 100644
--- a/x-pack/plugins/index_management/public/application/mount_management_section.ts
+++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts
@@ -8,6 +8,7 @@ import { CoreSetup } from 'src/core/public';
import { ManagementAppMountParams } from 'src/plugins/management/public/';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/public';
+import { IngestManagerSetup } from '../../../ingest_manager/public';
import { ExtensionsService } from '../services';
import { IndexMgmtMetricsType } from '../types';
import { AppDependencies } from './app_context';
@@ -28,7 +29,8 @@ export async function mountManagementSection(
coreSetup: CoreSetup,
usageCollection: UsageCollectionSetup,
services: InternalServices,
- params: ManagementAppMountParams
+ params: ManagementAppMountParams,
+ ingestManager?: IngestManagerSetup
) {
const { element, setBreadcrumbs, history } = params;
const [core] = await coreSetup.getStartServices();
@@ -44,6 +46,7 @@ export async function mountManagementSection(
},
plugins: {
usageCollection,
+ ingestManager,
},
services,
history,
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx
index a6c8b83a05f98..577f04a4a7efd 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx
@@ -4,9 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { Fragment } from 'react';
+import React, { useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
+ EuiButton,
EuiFlyout,
EuiFlyoutHeader,
EuiTitle,
@@ -15,14 +16,18 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiButtonEmpty,
+ EuiDescriptionList,
+ EuiDescriptionListTitle,
+ EuiDescriptionListDescription,
} from '@elastic/eui';
import { SectionLoading, SectionError, Error } from '../../../../components';
import { useLoadDataStream } from '../../../../services/api';
+import { DeleteDataStreamConfirmationModal } from '../delete_data_stream_confirmation_modal';
interface Props {
dataStreamName: string;
- onClose: () => void;
+ onClose: (shouldReload?: boolean) => void;
}
/**
@@ -36,6 +41,8 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({
}) => {
const { error, data: dataStream, isLoading } = useLoadDataStream(dataStreamName);
+ const [isDeleting, setIsDeleting] = useState(false);
+
let content;
if (isLoading) {
@@ -61,44 +68,97 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({
/>
);
} else if (dataStream) {
- content = {JSON.stringify(dataStream)};
+ const { timeStampField, generation } = dataStream;
+
+ content = (
+
+
+
+
+
+ {timeStampField.name}
+
+
+
+
+
+ {generation}
+
+ );
}
return (
-
-
-
-
- {dataStreamName}
-
-
-
-
- {content}
-
-
-
-
-
-
-
-
-
-
-
+ <>
+ {isDeleting ? (
+ {
+ if (data && data.hasDeletedDataStreams) {
+ onClose(true);
+ } else {
+ setIsDeleting(false);
+ }
+ }}
+ dataStreams={[dataStreamName]}
+ />
+ ) : null}
+
+
+
+
+
+ {dataStreamName}
+
+
+
+
+ {content}
+
+
+
+
+ onClose()}
+ data-test-subj="closeDetailsButton"
+ >
+
+
+
+
+ {!isLoading && !error ? (
+
+ setIsDeleting(true)}
+ data-test-subj="deleteDataStreamButton"
+ >
+
+
+
+ ) : null}
+
+
+
+ >
);
};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx
index 951c4a0d7f3c3..bad008b665cfb 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx
@@ -12,9 +12,13 @@ import { EuiTitle, EuiText, EuiSpacer, EuiEmptyPrompt, EuiLink } from '@elastic/
import { ScopedHistory } from 'kibana/public';
import { reactRouterNavigate } from '../../../../shared_imports';
+import { useAppContext } from '../../../app_context';
import { SectionError, SectionLoading, Error } from '../../../components';
import { useLoadDataStreams } from '../../../services/api';
+import { decodePathFromReactRouter } from '../../../services/routing';
+import { Section } from '../../home';
import { DataStreamTable } from './data_stream_table';
+import { DataStreamDetailPanel } from './data_stream_detail_panel';
interface MatchParams {
dataStreamName?: string;
@@ -26,6 +30,11 @@ export const DataStreamList: React.FunctionComponent {
+ const {
+ core: { getUrlForApp },
+ plugins: { ingestManager },
+ } = useAppContext();
+
const { error, isLoading, data: dataStreams, sendRequest: reload } = useLoadDataStreams();
let content;
@@ -67,22 +76,52 @@ export const DataStreamList: React.FunctionComponent
- {i18n.translate('xpack.idxMgmt.dataStreamList.emptyPrompt.getStartedLink', {
- defaultMessage: 'composable index template',
- })}
-
- ),
- }}
+ defaultMessage="Data streams represent collections of time series indices."
/>
+ {' ' /* We need this space to separate these two sentences. */}
+ {ingestManager ? (
+
+ {i18n.translate(
+ 'xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaIngestManagerLink',
+ {
+ defaultMessage: 'Ingest Manager',
+ }
+ )}
+
+ ),
+ }}
+ />
+ ) : (
+
+ {i18n.translate(
+ 'xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaIndexTemplateLink',
+ {
+ defaultMessage: 'composable index template',
+ }
+ )}
+
+ ),
+ }}
+ />
+ )}
}
data-test-subj="emptyPrompt"
@@ -104,24 +143,38 @@ export const DataStreamList: React.FunctionComponent
-
- {/* TODO: Implement this once we have something to put in here, e.g. storage size, docs count */}
- {/* dataStreamName && (
- {
- history.push('/data_streams');
- }}
- />
- )*/}
>
);
}
- return {content}
;
+ return (
+
+ {content}
+
+ {/*
+ If the user has been deep-linked, they'll expect to see the detail panel because it reflects
+ the URL state, even if there are no data streams or if there was an error loading them.
+ */}
+ {dataStreamName && (
+ {
+ history.push(`/${Section.DataStreams}`);
+
+ // If the data stream was deleted, we need to refresh the list.
+ if (shouldReload) {
+ reload();
+ }
+ }}
+ />
+ )}
+
+ );
};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx
index 54035e2193624..d01d8fa03a3fa 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
+import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiInMemoryTable, EuiBasicTableColumn, EuiButton, EuiLink } from '@elastic/eui';
@@ -13,6 +13,8 @@ import { ScopedHistory } from 'kibana/public';
import { DataStream } from '../../../../../../common/types';
import { reactRouterNavigate } from '../../../../../shared_imports';
import { encodePathForReactRouter } from '../../../../services/routing';
+import { Section } from '../../../home';
+import { DeleteDataStreamConfirmationModal } from '../delete_data_stream_confirmation_modal';
interface Props {
dataStreams?: DataStream[];
@@ -27,6 +29,9 @@ export const DataStreamTable: React.FunctionComponent = ({
history,
filters,
}) => {
+ const [selection, setSelection] = useState([]);
+ const [dataStreamsToDelete, setDataStreamsToDelete] = useState([]);
+
const columns: Array> = [
{
field: 'name',
@@ -35,7 +40,19 @@ export const DataStreamTable: React.FunctionComponent = ({
}),
truncateText: true,
sortable: true,
- // TODO: Render as a link to open the detail panel
+ render: (name: DataStream['name'], item: DataStream) => {
+ return (
+ /* eslint-disable-next-line @elastic/eui/href-or-on-click */
+
+ {name}
+
+ );
+ },
},
{
field: 'indices',
@@ -59,20 +76,27 @@ export const DataStreamTable: React.FunctionComponent = ({
),
},
{
- field: 'timeStampField.name',
- name: i18n.translate('xpack.idxMgmt.dataStreamList.table.timeStampFieldColumnTitle', {
- defaultMessage: 'Timestamp field',
+ name: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionColumnTitle', {
+ defaultMessage: 'Actions',
}),
- truncateText: true,
- sortable: true,
- },
- {
- field: 'generation',
- name: i18n.translate('xpack.idxMgmt.dataStreamList.table.generationFieldColumnTitle', {
- defaultMessage: 'Generation',
- }),
- truncateText: true,
- sortable: true,
+ actions: [
+ {
+ name: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionDeleteText', {
+ defaultMessage: 'Delete',
+ }),
+ description: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionDeleteDecription', {
+ defaultMessage: 'Delete this data stream',
+ }),
+ icon: 'trash',
+ color: 'danger',
+ type: 'icon',
+ onClick: ({ name }: DataStream) => {
+ setDataStreamsToDelete([name]);
+ },
+ isPrimary: true,
+ 'data-test-subj': 'deleteDataStream',
+ },
+ ],
},
];
@@ -88,12 +112,29 @@ export const DataStreamTable: React.FunctionComponent = ({
},
} as const;
+ const selectionConfig = {
+ onSelectionChange: setSelection,
+ };
+
const searchConfig = {
query: filters,
box: {
incremental: true,
},
- toolsLeft: undefined /* TODO: Actions menu */,
+ toolsLeft:
+ selection.length > 0 ? (
+ setDataStreamsToDelete(selection.map(({ name }: DataStream) => name))}
+ color="danger"
+ >
+
+
+ ) : undefined,
toolsRight: [
= ({
return (
<>
+ {dataStreamsToDelete && dataStreamsToDelete.length > 0 ? (
+ {
+ if (data && data.hasDeletedDataStreams) {
+ reload();
+ } else {
+ setDataStreamsToDelete([]);
+ }
+ }}
+ dataStreams={dataStreamsToDelete}
+ />
+ ) : null}
= ({
search={searchConfig}
sorting={sorting}
isSelectable={true}
+ selection={selectionConfig}
pagination={pagination}
rowProps={() => ({
'data-test-subj': 'row',
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx
new file mode 100644
index 0000000000000..fc8e41aa634b4
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx
@@ -0,0 +1,149 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { Fragment } from 'react';
+import { EuiCallOut, EuiConfirmModal, EuiOverlayMask, EuiSpacer } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import { deleteDataStreams } from '../../../../services/api';
+import { notificationService } from '../../../../services/notification';
+
+interface Props {
+ dataStreams: string[];
+ onClose: (data?: { hasDeletedDataStreams: boolean }) => void;
+}
+
+export const DeleteDataStreamConfirmationModal: React.FunctionComponent = ({
+ dataStreams,
+ onClose,
+}: {
+ dataStreams: string[];
+ onClose: (data?: { hasDeletedDataStreams: boolean }) => void;
+}) => {
+ const dataStreamsCount = dataStreams.length;
+
+ const handleDeleteDataStreams = () => {
+ deleteDataStreams(dataStreams).then(({ data: { dataStreamsDeleted, errors }, error }) => {
+ const hasDeletedDataStreams = dataStreamsDeleted && dataStreamsDeleted.length;
+
+ if (hasDeletedDataStreams) {
+ const successMessage =
+ dataStreamsDeleted.length === 1
+ ? i18n.translate(
+ 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.successDeleteSingleNotificationMessageText',
+ {
+ defaultMessage: "Deleted data stream '{dataStreamName}'",
+ values: { dataStreamName: dataStreams[0] },
+ }
+ )
+ : i18n.translate(
+ 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.successDeleteMultipleNotificationMessageText',
+ {
+ defaultMessage:
+ 'Deleted {numSuccesses, plural, one {# data stream} other {# data streams}}',
+ values: { numSuccesses: dataStreamsDeleted.length },
+ }
+ );
+
+ onClose({ hasDeletedDataStreams });
+ notificationService.showSuccessToast(successMessage);
+ }
+
+ if (error || (errors && errors.length)) {
+ const hasMultipleErrors =
+ (errors && errors.length > 1) || (error && dataStreams.length > 1);
+
+ const errorMessage = hasMultipleErrors
+ ? i18n.translate(
+ 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.multipleErrorsNotificationMessageText',
+ {
+ defaultMessage: 'Error deleting {count} data streams',
+ values: {
+ count: (errors && errors.length) || dataStreams.length,
+ },
+ }
+ )
+ : i18n.translate(
+ 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.errorNotificationMessageText',
+ {
+ defaultMessage: "Error deleting data stream '{name}'",
+ values: { name: (errors && errors[0].name) || dataStreams[0] },
+ }
+ );
+
+ notificationService.showDangerToast(errorMessage);
+ }
+ });
+ };
+
+ return (
+
+
+ }
+ onCancel={() => onClose()}
+ onConfirm={handleDeleteDataStreams}
+ cancelButtonText={
+
+ }
+ confirmButtonText={
+
+ }
+ >
+
+
+ }
+ color="danger"
+ iconType="alert"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ {dataStreams.map((name) => (
+ - {name}
+ ))}
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/index.ts b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/index.ts
new file mode 100644
index 0000000000000..eaa4a8fc2de02
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { DeleteDataStreamConfirmationModal } from './delete_data_stream_confirmation_modal';
diff --git a/x-pack/plugins/index_management/public/application/services/api.ts b/x-pack/plugins/index_management/public/application/services/api.ts
index 5ad84395d24c2..d7874ec2dcf32 100644
--- a/x-pack/plugins/index_management/public/application/services/api.ts
+++ b/x-pack/plugins/index_management/public/application/services/api.ts
@@ -53,14 +53,21 @@ export function useLoadDataStreams() {
});
}
-// TODO: Implement this API endpoint once we have content to surface in the detail panel.
export function useLoadDataStream(name: string) {
- return useRequest({
- path: `${API_BASE_PATH}/data_stream/${encodeURIComponent(name)}`,
+ return useRequest({
+ path: `${API_BASE_PATH}/data_streams/${encodeURIComponent(name)}`,
method: 'get',
});
}
+export async function deleteDataStreams(dataStreams: string[]) {
+ return sendRequest({
+ path: `${API_BASE_PATH}/delete_data_streams`,
+ method: 'post',
+ body: { dataStreams },
+ });
+}
+
export async function loadIndices() {
const response = await httpService.httpClient.get(`${API_BASE_PATH}/indices`);
return response.data ? response.data : response;
diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts
index 94d9bccdc63ca..aec25ee3247d6 100644
--- a/x-pack/plugins/index_management/public/plugin.ts
+++ b/x-pack/plugins/index_management/public/plugin.ts
@@ -8,6 +8,8 @@ import { i18n } from '@kbn/i18n';
import { CoreSetup } from '../../../../src/core/public';
import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public';
import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public';
+
+import { IngestManagerSetup } from '../../ingest_manager/public';
import { UIM_APP_NAME, PLUGIN } from '../common/constants';
import { httpService } from './application/services/http';
@@ -25,6 +27,7 @@ export interface IndexManagementPluginSetup {
}
interface PluginsDependencies {
+ ingestManager?: IngestManagerSetup;
usageCollection: UsageCollectionSetup;
management: ManagementSetup;
}
@@ -42,7 +45,7 @@ export class IndexMgmtUIPlugin {
public setup(coreSetup: CoreSetup, plugins: PluginsDependencies): IndexManagementPluginSetup {
const { http, notifications } = coreSetup;
- const { usageCollection, management } = plugins;
+ const { ingestManager, usageCollection, management } = plugins;
httpService.setup(http);
notificationService.setup(notifications);
@@ -60,7 +63,7 @@ export class IndexMgmtUIPlugin {
uiMetricService: this.uiMetricService,
extensionsService: this.extensionsService,
};
- return mountManagementSection(coreSetup, usageCollection, services, params);
+ return mountManagementSection(coreSetup, usageCollection, services, params, ingestManager);
},
});
diff --git a/x-pack/plugins/index_management/server/client/elasticsearch.ts b/x-pack/plugins/index_management/server/client/elasticsearch.ts
index 6b1bf47512b21..6c0fbe3dd6a65 100644
--- a/x-pack/plugins/index_management/server/client/elasticsearch.ts
+++ b/x-pack/plugins/index_management/server/client/elasticsearch.ts
@@ -20,6 +20,20 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any)
method: 'GET',
});
+ dataManagement.getDataStream = ca({
+ urls: [
+ {
+ fmt: '/_data_stream/<%=name%>',
+ req: {
+ name: {
+ type: 'string',
+ },
+ },
+ },
+ ],
+ method: 'GET',
+ });
+
// We don't allow the user to create a data stream in the UI or API. We're just adding this here
// to enable the API integration tests.
dataManagement.createDataStream = ca({
diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/index.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/index.ts
index 56c514e30f242..4aaf2b1bc5ed5 100644
--- a/x-pack/plugins/index_management/server/routes/api/data_streams/index.ts
+++ b/x-pack/plugins/index_management/server/routes/api/data_streams/index.ts
@@ -6,8 +6,11 @@
import { RouteDependencies } from '../../../types';
-import { registerGetAllRoute } from './register_get_route';
+import { registerGetOneRoute, registerGetAllRoute } from './register_get_route';
+import { registerDeleteRoute } from './register_delete_route';
export function registerDataStreamRoutes(dependencies: RouteDependencies) {
+ registerGetOneRoute(dependencies);
registerGetAllRoute(dependencies);
+ registerDeleteRoute(dependencies);
}
diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/register_delete_route.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/register_delete_route.ts
new file mode 100644
index 0000000000000..45b185bcd053b
--- /dev/null
+++ b/x-pack/plugins/index_management/server/routes/api/data_streams/register_delete_route.ts
@@ -0,0 +1,52 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { schema, TypeOf } from '@kbn/config-schema';
+
+import { RouteDependencies } from '../../../types';
+import { addBasePath } from '../index';
+import { wrapEsError } from '../../helpers';
+
+const bodySchema = schema.object({
+ dataStreams: schema.arrayOf(schema.string()),
+});
+
+export function registerDeleteRoute({ router, license }: RouteDependencies) {
+ router.post(
+ {
+ path: addBasePath('/delete_data_streams'),
+ validate: { body: bodySchema },
+ },
+ license.guardApiRoute(async (ctx, req, res) => {
+ const { callAsCurrentUser } = ctx.dataManagement!.client;
+ const { dataStreams } = req.body as TypeOf;
+
+ const response: { dataStreamsDeleted: string[]; errors: any[] } = {
+ dataStreamsDeleted: [],
+ errors: [],
+ };
+
+ await Promise.all(
+ dataStreams.map(async (name: string) => {
+ try {
+ await callAsCurrentUser('dataManagement.deleteDataStream', {
+ name,
+ });
+
+ return response.dataStreamsDeleted.push(name);
+ } catch (e) {
+ return response.errors.push({
+ name,
+ error: wrapEsError(e),
+ });
+ }
+ })
+ );
+
+ return res.ok({ body: response });
+ })
+ );
+}
diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts
index 9128556130bf4..5f4e625348333 100644
--- a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts
+++ b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts
@@ -4,7 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { deserializeDataStreamList } from '../../../../common/lib';
+import { schema, TypeOf } from '@kbn/config-schema';
+
+import { deserializeDataStream, deserializeDataStreamList } from '../../../../common/lib';
import { RouteDependencies } from '../../../types';
import { addBasePath } from '../index';
@@ -32,3 +34,40 @@ export function registerGetAllRoute({ router, license, lib: { isEsError } }: Rou
})
);
}
+
+export function registerGetOneRoute({ router, license, lib: { isEsError } }: RouteDependencies) {
+ const paramsSchema = schema.object({
+ name: schema.string(),
+ });
+
+ router.get(
+ {
+ path: addBasePath('/data_streams/{name}'),
+ validate: { params: paramsSchema },
+ },
+ license.guardApiRoute(async (ctx, req, res) => {
+ const { name } = req.params as TypeOf;
+ const { callAsCurrentUser } = ctx.dataManagement!.client;
+
+ try {
+ const dataStream = await callAsCurrentUser('dataManagement.getDataStream', { name });
+
+ if (dataStream[0]) {
+ const body = deserializeDataStream(dataStream[0]);
+ return res.ok({ body });
+ }
+
+ return res.notFound();
+ } catch (e) {
+ if (isEsError(e)) {
+ return res.customError({
+ statusCode: e.statusCode,
+ body: e,
+ });
+ }
+ // Case: default
+ return res.internalError({ body: e });
+ }
+ })
+ );
+}
diff --git a/x-pack/plugins/ingest_manager/public/index.ts b/x-pack/plugins/ingest_manager/public/index.ts
index 9f4893ac6e499..ac56349b30c13 100644
--- a/x-pack/plugins/ingest_manager/public/index.ts
+++ b/x-pack/plugins/ingest_manager/public/index.ts
@@ -6,7 +6,7 @@
import { PluginInitializerContext } from 'src/core/public';
import { IngestManagerPlugin } from './plugin';
-export { IngestManagerStart } from './plugin';
+export { IngestManagerSetup, IngestManagerStart } from './plugin';
export const plugin = (initializerContext: PluginInitializerContext) => {
return new IngestManagerPlugin(initializerContext);
diff --git a/x-pack/plugins/ingest_manager/public/plugin.ts b/x-pack/plugins/ingest_manager/public/plugin.ts
index 1cd70f70faa37..4a10a26151e78 100644
--- a/x-pack/plugins/ingest_manager/public/plugin.ts
+++ b/x-pack/plugins/ingest_manager/public/plugin.ts
@@ -22,7 +22,11 @@ import { registerDatasource } from './applications/ingest_manager/sections/agent
export { IngestManagerConfigType } from '../common/types';
-export type IngestManagerSetup = void;
+// We need to provide an object instead of void so that dependent plugins know when Ingest Manager
+// is disabled.
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface IngestManagerSetup {}
+
/**
* Describes public IngestManager plugin contract returned at the `start` stage.
*/
@@ -72,6 +76,8 @@ export class IngestManagerPlugin
};
},
});
+
+ return {};
}
public async start(core: CoreStart): Promise {