([]);
+
+ // Track component loaded
+ useEffect(() => {
+ trackMetric('loaded', UIM_COMPONENT_TEMPLATE_LIST_LOAD);
+ }, [trackMetric]);
+
+ if (data && data.length === 0) {
+ return ;
+ }
+
+ let content: React.ReactNode;
+
+ if (isLoading) {
+ content = (
+
+
+
+ );
+ } else if (data?.length) {
+ content = (
+
+ );
+ } else if (error) {
+ content = ;
+ }
+
+ return (
+
+ {content}
+ {componentTemplatesToDelete?.length > 0 ? (
+ {
+ if (deleteResponse?.hasDeletedComponentTemplates) {
+ // refetch the component templates
+ sendRequest();
+ }
+ setComponentTemplatesToDelete([]);
+ }}
+ componentTemplatesToDelete={componentTemplatesToDelete}
+ />
+ ) : null}
+
+ );
+};
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/delete_modal.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/delete_modal.tsx
new file mode 100644
index 000000000000..bf621065842b
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/delete_modal.tsx
@@ -0,0 +1,128 @@
+/*
+ * 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 from 'react';
+import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import { useComponentTemplatesContext } from '../component_templates_context';
+
+export const ComponentTemplatesDeleteModal = ({
+ componentTemplatesToDelete,
+ callback,
+}: {
+ componentTemplatesToDelete: string[];
+ callback: (data?: { hasDeletedComponentTemplates: boolean }) => void;
+}) => {
+ const { toasts, api } = useComponentTemplatesContext();
+ const numComponentTemplatesToDelete = componentTemplatesToDelete.length;
+
+ const handleDeleteComponentTemplates = () => {
+ api
+ .deleteComponentTemplates(componentTemplatesToDelete)
+ .then(({ data: { itemsDeleted, errors }, error }) => {
+ const hasDeletedComponentTemplates = itemsDeleted && itemsDeleted.length;
+
+ if (hasDeletedComponentTemplates) {
+ const successMessage =
+ itemsDeleted.length === 1
+ ? i18n.translate(
+ 'xpack.idxMgmt.home.componentTemplates.deleteModal.successDeleteSingleNotificationMessageText',
+ {
+ defaultMessage: "Deleted component template '{componentTemplateName}'",
+ values: { componentTemplateName: componentTemplatesToDelete[0] },
+ }
+ )
+ : i18n.translate(
+ 'xpack.idxMgmt.home.componentTemplates.deleteModal.successDeleteMultipleNotificationMessageText',
+ {
+ defaultMessage:
+ 'Deleted {numSuccesses, plural, one {# component template} other {# component templates}}',
+ values: { numSuccesses: itemsDeleted.length },
+ }
+ );
+
+ callback({ hasDeletedComponentTemplates });
+ toasts.addSuccess(successMessage);
+ }
+
+ if (error || errors?.length) {
+ const hasMultipleErrors =
+ errors?.length > 1 || (error && componentTemplatesToDelete.length > 1);
+ const errorMessage = hasMultipleErrors
+ ? i18n.translate(
+ 'xpack.idxMgmt.home.componentTemplates.deleteModal.multipleErrorsNotificationMessageText',
+ {
+ defaultMessage: 'Error deleting {count} component templates',
+ values: {
+ count: errors?.length || componentTemplatesToDelete.length,
+ },
+ }
+ )
+ : i18n.translate(
+ 'xpack.idxMgmt.home.componentTemplates.deleteModal.errorNotificationMessageText',
+ {
+ defaultMessage: "Error deleting component template '{name}'",
+ values: { name: (errors && errors[0].name) || componentTemplatesToDelete[0] },
+ }
+ );
+ toasts.addDanger(errorMessage);
+ }
+ });
+ };
+
+ const handleOnCancel = () => {
+ callback();
+ };
+
+ return (
+
+
+ }
+ onCancel={handleOnCancel}
+ onConfirm={handleDeleteComponentTemplates}
+ cancelButtonText={
+
+ }
+ confirmButtonText={
+
+ }
+ >
+ <>
+
+
+
+
+
+ {componentTemplatesToDelete.map((name) => (
+ - {name}
+ ))}
+
+ >
+
+
+ );
+};
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/empty_prompt.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/empty_prompt.tsx
new file mode 100644
index 000000000000..edd9f77cbf63
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/empty_prompt.tsx
@@ -0,0 +1,43 @@
+/*
+ * 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, { FunctionComponent } from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiEmptyPrompt, EuiLink } from '@elastic/eui';
+
+import { useComponentTemplatesContext } from '../component_templates_context';
+
+export const EmptyPrompt: FunctionComponent = () => {
+ const { documentation } = useComponentTemplatesContext();
+
+ return (
+
+ {i18n.translate('xpack.idxMgmt.home.componentTemplates.emptyPromptTitle', {
+ defaultMessage: 'Start by creating a component template',
+ })}
+
+ }
+ body={
+
+
+
+
+ {i18n.translate('xpack.idxMgmt.home.componentTemplates.emptyPromptDocumentionLink', {
+ defaultMessage: 'Learn more',
+ })}
+
+
+ }
+ />
+ );
+};
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/error.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/error.tsx
new file mode 100644
index 000000000000..aa37b9ce5767
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/error.tsx
@@ -0,0 +1,38 @@
+/*
+ * 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, { FunctionComponent } from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiLink, EuiCallOut } from '@elastic/eui';
+
+export interface Props {
+ onReloadClick: () => void;
+}
+
+export const LoadError: FunctionComponent = ({ onReloadClick }) => {
+ return (
+
+
+
+ ),
+ }}
+ />
+ }
+ />
+ );
+};
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/index.ts b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/index.ts
new file mode 100644
index 000000000000..84ee48d14bb8
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/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 { ComponentTemplateList } from './component_template_list';
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx
new file mode 100644
index 000000000000..2d9557e64e6e
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx
@@ -0,0 +1,205 @@
+/*
+ * 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, { FunctionComponent, useState } from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import {
+ EuiInMemoryTable,
+ EuiButton,
+ EuiInMemoryTableProps,
+ EuiTextColor,
+ EuiIcon,
+} from '@elastic/eui';
+
+import { ComponentTemplateListItem } from '../types';
+
+export interface Props {
+ componentTemplates: ComponentTemplateListItem[];
+ onReloadClick: () => void;
+ onDeleteClick: (componentTemplateName: string[]) => void;
+}
+
+export const ComponentTable: FunctionComponent = ({
+ componentTemplates,
+ onReloadClick,
+ onDeleteClick,
+}) => {
+ const [selection, setSelection] = useState([]);
+
+ const tableProps: EuiInMemoryTableProps = {
+ itemId: 'name',
+ isSelectable: true,
+ 'data-test-subj': 'componentTemplatesTable',
+ sorting: { sort: { field: 'name', direction: 'asc' } },
+ selection: {
+ onSelectionChange: setSelection,
+ selectable: ({ usedBy }) => usedBy.length === 0,
+ selectableMessage: (selectable) =>
+ selectable
+ ? i18n.translate('xpack.idxMgmt.componentTemplatesList.table.selectionLabel', {
+ defaultMessage: 'Select this component template',
+ })
+ : i18n.translate('xpack.idxMgmt.componentTemplatesList.table.disabledSelectionLabel', {
+ defaultMessage: 'Component template is in use and cannot be deleted',
+ }),
+ },
+ rowProps: () => ({
+ 'data-test-subj': 'componentTemplateTableRow',
+ }),
+ search: {
+ toolsLeft:
+ selection.length > 0 ? (
+ onDeleteClick(selection.map(({ name }) => name))}
+ color="danger"
+ >
+
+
+ ) : undefined,
+ toolsRight: [
+
+ {i18n.translate('xpack.idxMgmt.componentTemplatesList.table.reloadButtonLabel', {
+ defaultMessage: 'Reload',
+ })}
+ ,
+ ],
+ box: {
+ incremental: true,
+ },
+ filters: [
+ {
+ type: 'field_value_toggle_group',
+ field: 'usedBy.length',
+ items: [
+ {
+ value: 1,
+ name: i18n.translate(
+ 'xpack.idxMgmt.componentTemplatesList.table.inUseFilterOptionLabel',
+ {
+ defaultMessage: 'In use',
+ }
+ ),
+ operator: 'gte',
+ },
+ {
+ value: 0,
+ name: i18n.translate(
+ 'xpack.idxMgmt.componentTemplatesList.table.notInUseFilterOptionLabel',
+ {
+ defaultMessage: 'Not in use',
+ }
+ ),
+ operator: 'eq',
+ },
+ ],
+ },
+ ],
+ },
+ pagination: {
+ initialPageSize: 10,
+ pageSizeOptions: [10, 20, 50],
+ },
+ columns: [
+ {
+ field: 'name',
+ name: i18n.translate('xpack.idxMgmt.componentTemplatesList.table.nameColumnTitle', {
+ defaultMessage: 'Name',
+ }),
+ sortable: true,
+ },
+ {
+ field: 'usedBy',
+ name: i18n.translate('xpack.idxMgmt.componentTemplatesList.table.isInUseColumnTitle', {
+ defaultMessage: 'Index templates',
+ }),
+ sortable: true,
+ render: (usedBy: string[]) => {
+ if (usedBy.length) {
+ return usedBy.length;
+ }
+
+ return (
+
+
+
+
+
+ );
+ },
+ },
+ {
+ field: 'hasMappings',
+ name: i18n.translate('xpack.idxMgmt.componentTemplatesList.table.mappingsColumnTitle', {
+ defaultMessage: 'Mappings',
+ }),
+ truncateText: true,
+ sortable: true,
+ render: (hasMappings: boolean) => (hasMappings ? : null),
+ },
+ {
+ field: 'hasSettings',
+ name: i18n.translate('xpack.idxMgmt.componentTemplatesList.table.settingsColumnTitle', {
+ defaultMessage: 'Settings',
+ }),
+ truncateText: true,
+ sortable: true,
+ render: (hasSettings: boolean) => (hasSettings ? : null),
+ },
+ {
+ field: 'hasAliases',
+ name: i18n.translate('xpack.idxMgmt.componentTemplatesList.table.aliasesColumnTitle', {
+ defaultMessage: 'Aliases',
+ }),
+ truncateText: true,
+ sortable: true,
+ render: (hasAliases: boolean) => (hasAliases ? : null),
+ },
+ {
+ name: (
+
+ ),
+ actions: [
+ {
+ 'data-test-subj': 'deleteComponentTemplateButton',
+ isPrimary: true,
+ name: i18n.translate('xpack.idxMgmt.componentTemplatesList.table.deleteActionLabel', {
+ defaultMessage: 'Delete',
+ }),
+ description: i18n.translate(
+ 'xpack.idxMgmt.componentTemplatesList.table.deleteActionDescription',
+ { defaultMessage: 'Delete this component template' }
+ ),
+ type: 'icon',
+ icon: 'trash',
+ color: 'danger',
+ onClick: ({ name }) => onDeleteClick([name]),
+ enabled: ({ usedBy }) => usedBy.length === 0,
+ },
+ ],
+ },
+ ],
+ items: componentTemplates ?? [],
+ };
+
+ return ;
+};
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx
new file mode 100644
index 000000000000..6f5f5bdebd6d
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_templates_context.tsx
@@ -0,0 +1,63 @@
+/*
+ * 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, { createContext, useContext } from 'react';
+import { HttpSetup, DocLinksSetup, NotificationsSetup } from 'src/core/public';
+
+import { getApi, getUseRequest, getSendRequest, getDocumentation } from './lib';
+
+const ComponentTemplatesContext = createContext(undefined);
+
+interface Props {
+ httpClient: HttpSetup;
+ apiBasePath: string;
+ appBasePath: string;
+ trackMetric: (type: 'loaded' | 'click' | 'count', eventName: string) => void;
+ docLinks: DocLinksSetup;
+ toasts: NotificationsSetup['toasts'];
+}
+
+interface Context {
+ api: ReturnType;
+ documentation: ReturnType;
+ trackMetric: (type: 'loaded' | 'click' | 'count', eventName: string) => void;
+ toasts: NotificationsSetup['toasts'];
+ appBasePath: string;
+}
+
+export const ComponentTemplatesProvider = ({
+ children,
+ value,
+}: {
+ value: Props;
+ children: React.ReactNode;
+}) => {
+ const { httpClient, apiBasePath, trackMetric, docLinks, toasts, appBasePath } = value;
+
+ const useRequest = getUseRequest(httpClient);
+ const sendRequest = getSendRequest(httpClient);
+
+ const api = getApi(useRequest, sendRequest, apiBasePath, trackMetric);
+ const documentation = getDocumentation(docLinks);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useComponentTemplatesContext = () => {
+ const ctx = useContext(ComponentTemplatesContext);
+ if (!ctx) {
+ throw new Error(
+ '"useComponentTemplatesContext" can only be called inside of ComponentTemplatesProvider!'
+ );
+ }
+ return ctx;
+};
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/constants.ts b/x-pack/plugins/index_management/public/application/components/component_templates/constants.ts
new file mode 100644
index 000000000000..3e763119fa9f
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/constants.ts
@@ -0,0 +1,10 @@
+/*
+ * 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.
+ */
+
+// ui metric constants
+export const UIM_COMPONENT_TEMPLATE_LIST_LOAD = 'component_template_list_load';
+export const UIM_COMPONENT_TEMPLATE_DELETE = 'component_template_delete';
+export const UIM_COMPONENT_TEMPLATE_DELETE_MANY = 'component_template_delete_many';
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/index.ts b/x-pack/plugins/index_management/public/application/components/component_templates/index.ts
new file mode 100644
index 000000000000..e0219ec71787
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/index.ts
@@ -0,0 +1,11 @@
+/*
+ * 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 { ComponentTemplatesProvider } from './component_templates_context';
+
+export { ComponentTemplateList } from './component_template_list';
+
+export * from './types';
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/lib/api.ts b/x-pack/plugins/index_management/public/application/components/component_templates/lib/api.ts
new file mode 100644
index 000000000000..351e83c6c0cb
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/lib/api.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 { ComponentTemplateListItem } from '../types';
+import { UseRequestHook, SendRequestHook } from './request';
+import { UIM_COMPONENT_TEMPLATE_DELETE_MANY, UIM_COMPONENT_TEMPLATE_DELETE } from '../constants';
+
+export const getApi = (
+ useRequest: UseRequestHook,
+ sendRequest: SendRequestHook,
+ apiBasePath: string,
+ trackMetric: (type: 'loaded' | 'click' | 'count', eventName: string) => void
+) => {
+ function useLoadComponentTemplates() {
+ return useRequest({
+ path: `${apiBasePath}/component_templates`,
+ method: 'get',
+ });
+ }
+
+ function deleteComponentTemplates(names: string[]) {
+ const result = sendRequest({
+ path: `${apiBasePath}/component_templates/${names
+ .map((name) => encodeURIComponent(name))
+ .join(',')}`,
+ method: 'delete',
+ });
+
+ trackMetric(
+ 'count',
+ names.length > 1 ? UIM_COMPONENT_TEMPLATE_DELETE_MANY : UIM_COMPONENT_TEMPLATE_DELETE
+ );
+
+ return result;
+ }
+
+ return {
+ useLoadComponentTemplates,
+ deleteComponentTemplates,
+ };
+};
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/lib/documentation.ts b/x-pack/plugins/index_management/public/application/components/component_templates/lib/documentation.ts
new file mode 100644
index 000000000000..dc27dadf0b80
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/lib/documentation.ts
@@ -0,0 +1,16 @@
+/*
+ * 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 { DocLinksSetup } from 'src/core/public';
+
+export const getDocumentation = ({ ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }: DocLinksSetup) => {
+ const docsBase = `${ELASTIC_WEBSITE_URL}guide/en`;
+ const esDocsBase = `${docsBase}/elasticsearch/reference/${DOC_LINK_VERSION}`;
+
+ return {
+ componentTemplates: `${esDocsBase}/indices-component-template.html`,
+ };
+};
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/lib/index.ts b/x-pack/plugins/index_management/public/application/components/component_templates/lib/index.ts
new file mode 100644
index 000000000000..9a91312f8329
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/lib/index.ts
@@ -0,0 +1,11 @@
+/*
+ * 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 * from './api';
+
+export * from './request';
+
+export * from './documentation';
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/lib/request.ts b/x-pack/plugins/index_management/public/application/components/component_templates/lib/request.ts
new file mode 100644
index 000000000000..97ffa4d875ec
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/lib/request.ts
@@ -0,0 +1,31 @@
+/*
+ * 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 { HttpSetup } from 'src/core/public';
+
+import {
+ UseRequestConfig,
+ UseRequestResponse,
+ SendRequestConfig,
+ SendRequestResponse,
+ sendRequest as _sendRequest,
+ useRequest as _useRequest,
+} from '../shared_imports';
+
+export type UseRequestHook = (config: UseRequestConfig) => UseRequestResponse;
+export type SendRequestHook = (config: SendRequestConfig) => Promise;
+
+export const getUseRequest = (httpClient: HttpSetup): UseRequestHook => (
+ config: UseRequestConfig
+) => {
+ return _useRequest(httpClient, config);
+};
+
+export const getSendRequest = (httpClient: HttpSetup): SendRequestHook => (
+ config: SendRequestConfig
+) => {
+ return _sendRequest(httpClient, config);
+};
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts b/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts
new file mode 100644
index 000000000000..863b00b353c4
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts
@@ -0,0 +1,15 @@
+/*
+ * 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 {
+ UseRequestConfig,
+ UseRequestResponse,
+ SendRequestConfig,
+ SendRequestResponse,
+ sendRequest,
+ useRequest,
+ SectionLoading,
+} from '../../../../../../../src/plugins/es_ui_shared/public';
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/types.ts b/x-pack/plugins/index_management/public/application/components/component_templates/types.ts
new file mode 100644
index 000000000000..0aab3b6b0a94
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/types.ts
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+// Ideally, we shouldn't depend on anything in index management that is
+// outside of the components_templates directory
+// We could consider creating shared types or duplicating the types here if
+// the component_templates app were to move outside of index management
+import {
+ ComponentTemplateSerialized,
+ ComponentTemplateDeserialized,
+ ComponentTemplateListItem,
+} from '../../../../common';
+
+export { ComponentTemplateSerialized, ComponentTemplateDeserialized, ComponentTemplateListItem };
diff --git a/x-pack/plugins/index_management/public/application/components/index.ts b/x-pack/plugins/index_management/public/application/components/index.ts
index e6d836c0d050..7ec25ed5583b 100644
--- a/x-pack/plugins/index_management/public/application/components/index.ts
+++ b/x-pack/plugins/index_management/public/application/components/index.ts
@@ -11,3 +11,4 @@ export { PageErrorForbidden } from './page_error';
export { TemplateDeleteModal } from './template_delete_modal';
export { TemplateForm } from './template_form';
export * from './mappings_editor';
+export * from './component_templates';
diff --git a/x-pack/plugins/index_management/public/application/index.tsx b/x-pack/plugins/index_management/public/application/index.tsx
index 8da556cc81fc..5d1096c9ee24 100644
--- a/x-pack/plugins/index_management/public/application/index.tsx
+++ b/x-pack/plugins/index_management/public/application/index.tsx
@@ -10,9 +10,12 @@ import { render, unmountComponentAtNode } from 'react-dom';
import { CoreStart } from '../../../../../src/core/public';
+import { API_BASE_PATH, BASE_PATH } from '../../common';
+
import { AppContextProvider, AppDependencies } from './app_context';
import { App } from './app';
import { indexManagementStore } from './store';
+import { ComponentTemplatesProvider } from './components';
export const renderApp = (
elem: HTMLElement | null,
@@ -22,15 +25,26 @@ export const renderApp = (
return () => undefined;
}
- const { i18n } = core;
+ const { i18n, docLinks, notifications } = core;
const { Context: I18nContext } = i18n;
const { services, history } = dependencies;
+ const componentTemplateProviderValues = {
+ httpClient: services.httpService.httpClient,
+ apiBasePath: API_BASE_PATH,
+ appBasePath: BASE_PATH,
+ trackMetric: services.uiMetricService.trackMetric.bind(services.uiMetricService),
+ docLinks,
+ toasts: notifications.toasts,
+ };
+
render(
-
+
+
+
,
diff --git a/x-pack/plugins/index_management/public/application/sections/home/home.tsx b/x-pack/plugins/index_management/public/application/sections/home/home.tsx
index 9d4331d742a2..0ddf4fefce46 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/home.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/home.tsx
@@ -21,9 +21,16 @@ import {
import { documentationService } from '../../services/documentation';
import { IndexList } from './index_list';
import { TemplateList } from './template_list';
+import { ComponentTemplateList } from '../../components/component_templates';
import { breadcrumbService } from '../../services/breadcrumbs';
-type Section = 'indices' | 'templates';
+export enum Section {
+ Indices = 'indices',
+ IndexTemplates = 'templates',
+ ComponentTemplates = 'component_templates',
+}
+
+export const homeSections = [Section.Indices, Section.IndexTemplates, Section.ComponentTemplates];
interface MatchParams {
section: Section;
@@ -37,11 +44,11 @@ export const IndexManagementHome: React.FunctionComponent {
const tabs = [
{
- id: 'indices' as Section,
+ id: Section.Indices,
name: ,
},
{
- id: 'templates' as Section,
+ id: Section.IndexTemplates,
name: (
),
},
+ {
+ id: Section.ComponentTemplates,
+ name: (
+
+ ),
+ },
];
const onSectionChange = (newSection: Section) => {
@@ -106,13 +122,14 @@ export const IndexManagementHome: React.FunctionComponent
-
-
+
+
+
diff --git a/x-pack/plugins/index_management/public/application/sections/home/index.ts b/x-pack/plugins/index_management/public/application/sections/home/index.ts
index 3a29ef4e5855..b53910748aed 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/index.ts
+++ b/x-pack/plugins/index_management/public/application/sections/home/index.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { IndexManagementHome } from './home';
+export { IndexManagementHome, Section, homeSections } from './home';
diff --git a/x-pack/plugins/index_management/server/client/elasticsearch.ts b/x-pack/plugins/index_management/server/client/elasticsearch.ts
index 65bd5411a249..b51f7d924dba 100644
--- a/x-pack/plugins/index_management/server/client/elasticsearch.ts
+++ b/x-pack/plugins/index_management/server/client/elasticsearch.ts
@@ -10,6 +10,7 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any)
Client.prototype.dataManagement = components.clientAction.namespaceFactory();
const dataManagement = Client.prototype.dataManagement.prototype;
+ // Component templates
dataManagement.getComponentTemplates = ca({
urls: [
{
@@ -60,4 +61,14 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any)
],
method: 'DELETE',
});
+
+ // Composable index templates
+ dataManagement.getComposableIndexTemplates = ca({
+ urls: [
+ {
+ fmt: '/_index_template',
+ },
+ ],
+ method: 'GET',
+ });
};
diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/get.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/get.ts
index 87aa64421624..f6f8e7d63d37 100644
--- a/x-pack/plugins/index_management/server/routes/api/component_templates/get.ts
+++ b/x-pack/plugins/index_management/server/routes/api/component_templates/get.ts
@@ -5,6 +5,11 @@
*/
import { schema } from '@kbn/config-schema';
+import {
+ deserializeComponentTemplate,
+ deserializeComponenTemplateList,
+} from '../../../../common/lib';
+import { ComponentTemplateFromEs } from '../../../../common';
import { RouteDependencies } from '../../../types';
import { addBasePath } from '../index';
@@ -20,9 +25,25 @@ export function registerGetAllRoute({ router, license, lib: { isEsError } }: Rou
const { callAsCurrentUser } = ctx.dataManagement!.client;
try {
- const response = await callAsCurrentUser('dataManagement.getComponentTemplates');
+ const {
+ component_templates: componentTemplates,
+ }: { component_templates: ComponentTemplateFromEs[] } = await callAsCurrentUser(
+ 'dataManagement.getComponentTemplates'
+ );
- return res.ok({ body: response.component_templates });
+ const { index_templates: indexTemplates } = await callAsCurrentUser(
+ 'dataManagement.getComposableIndexTemplates'
+ );
+
+ const body = componentTemplates.map((componentTemplate) => {
+ const deserializedComponentTemplateListItem = deserializeComponenTemplateList(
+ componentTemplate,
+ indexTemplates
+ );
+ return deserializedComponentTemplateListItem;
+ });
+
+ return res.ok({ body });
} catch (error) {
if (isEsError(error)) {
return res.customError({
@@ -56,11 +77,12 @@ export function registerGetAllRoute({ router, license, lib: { isEsError } }: Rou
}
);
+ const { index_templates: indexTemplates } = await callAsCurrentUser(
+ 'dataManagement.getComposableIndexTemplates'
+ );
+
return res.ok({
- body: {
- ...componentTemplates[0],
- name,
- },
+ body: deserializeComponentTemplate(componentTemplates[0], indexTemplates),
});
} catch (error) {
if (isEsError(error)) {
diff --git a/x-pack/test/api_integration/apis/management/index_management/component_templates.ts b/x-pack/test/api_integration/apis/management/index_management/component_templates.ts
index a33e82ad9f79..64bf03a043b5 100644
--- a/x-pack/test/api_integration/apis/management/index_management/component_templates.ts
+++ b/x-pack/test/api_integration/apis/management/index_management/component_templates.ts
@@ -61,7 +61,10 @@ export default function ({ getService }: FtrProviderContext) {
expect(testComponentTemplate).to.eql({
name: COMPONENT_NAME,
- component_template: COMPONENT,
+ usedBy: [],
+ hasSettings: true,
+ hasMappings: true,
+ hasAliases: false,
});
});
});
@@ -74,8 +77,9 @@ export default function ({ getService }: FtrProviderContext) {
expect(body).to.eql({
name: COMPONENT_NAME,
- component_template: {
- ...COMPONENT,
+ ...COMPONENT,
+ _kbnMeta: {
+ usedBy: [],
},
});
});
diff --git a/x-pack/test/functional/apps/index_management/home_page.ts b/x-pack/test/functional/apps/index_management/home_page.ts
index e985e338122e..cca0a5d1ad1b 100644
--- a/x-pack/test/functional/apps/index_management/home_page.ts
+++ b/x-pack/test/functional/apps/index_management/home_page.ts
@@ -47,5 +47,22 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
expect(templateList).to.be(true);
});
});
+
+ describe('Component templates', () => {
+ it('renders the component templates tab', async () => {
+ // Navigate to the component templates tab
+ await pageObjects.indexManagement.changeTabs('component_templatesTab');
+
+ await pageObjects.header.waitUntilLoadingHasFinished();
+
+ // Verify url
+ const url = await browser.getCurrentUrl();
+ expect(url).to.contain(`/component_templates`);
+
+ // There should be no component templates by default, so we verify the empty prompt displays
+ const componentTemplateEmptyPrompt = await testSubjects.exists('emptyList');
+ expect(componentTemplateEmptyPrompt).to.be(true);
+ });
+ });
});
};
diff --git a/x-pack/test/functional/page_objects/index_management_page.ts b/x-pack/test/functional/page_objects/index_management_page.ts
index d12186f2e218..9bfcd79671b4 100644
--- a/x-pack/test/functional/page_objects/index_management_page.ts
+++ b/x-pack/test/functional/page_objects/index_management_page.ts
@@ -44,7 +44,7 @@ export function IndexManagementPageProvider({ getService }: FtrProviderContext)
};
});
},
- async changeTabs(tab: 'indicesTab' | 'templatesTab') {
+ async changeTabs(tab: 'indicesTab' | 'templatesTab' | 'component_templatesTab') {
await testSubjects.click(tab);
},
};