) {
return Object.entries(apmIndices)
.map(([key, value]) => [key, value?.trim()])
- .filter(([key, value]) => !!value)
+ .filter(([_, value]) => !!value)
.reduce((obj, [key, value]) => ({ ...obj, [key as string]: value }), {});
}
diff --git a/x-pack/plugins/apm/server/routes/errors.ts b/x-pack/plugins/apm/server/routes/errors.ts
index 7e45f412d4bdb..1615550027d3c 100644
--- a/x-pack/plugins/apm/server/routes/errors.ts
+++ b/x-pack/plugins/apm/server/routes/errors.ts
@@ -12,7 +12,7 @@ import { getErrorGroups } from '../lib/errors/get_error_groups';
import { setupRequest } from '../lib/helpers/setup_request';
import { uiFiltersRt, rangeRt } from './default_api_types';
-export const errorsRoute = createRoute((core) => ({
+export const errorsRoute = createRoute(() => ({
path: '/api/apm/services/{serviceName}/errors',
params: {
path: t.type({
diff --git a/x-pack/plugins/apm/server/routes/service_nodes.ts b/x-pack/plugins/apm/server/routes/service_nodes.ts
index a6e9175fcb651..8721407671825 100644
--- a/x-pack/plugins/apm/server/routes/service_nodes.ts
+++ b/x-pack/plugins/apm/server/routes/service_nodes.ts
@@ -9,7 +9,7 @@ import { setupRequest } from '../lib/helpers/setup_request';
import { getServiceNodes } from '../lib/service_nodes';
import { rangeRt, uiFiltersRt } from './default_api_types';
-export const serviceNodesRoute = createRoute((core) => ({
+export const serviceNodesRoute = createRoute(() => ({
path: '/api/apm/services/{serviceName}/serviceNodes',
params: {
path: t.type({
diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts
index 996bfbd9184d1..8672c6c108c4c 100644
--- a/x-pack/plugins/apm/server/routes/services.ts
+++ b/x-pack/plugins/apm/server/routes/services.ts
@@ -17,7 +17,7 @@ import { uiFiltersRt, rangeRt } from './default_api_types';
import { getServiceAnnotations } from '../lib/services/annotations';
import { dateAsStringRt } from '../../common/runtime_types/date_as_string_rt';
-export const servicesRoute = createRoute((core) => ({
+export const servicesRoute = createRoute(() => ({
path: '/api/apm/services',
params: {
query: t.intersection([uiFiltersRt, rangeRt]),
diff --git a/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts b/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts
index 6fd864a337165..f5c9cc2adf238 100644
--- a/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts
+++ b/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts
@@ -24,7 +24,7 @@ import {
import { jsonRt } from '../../../common/runtime_types/json_rt';
// get list of configurations
-export const agentConfigurationRoute = createRoute((core) => ({
+export const agentConfigurationRoute = createRoute(() => ({
path: '/api/apm/settings/agent-configuration',
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
@@ -137,7 +137,7 @@ export const createOrUpdateAgentConfigurationRoute = createRoute(() => ({
}));
// Lookup single configuration (used by APM Server)
-export const agentConfigurationSearchRoute = createRoute((core) => ({
+export const agentConfigurationSearchRoute = createRoute(() => ({
method: 'POST',
path: '/api/apm/settings/agent-configuration/search',
params: {
diff --git a/x-pack/plugins/apm/server/routes/settings/apm_indices.ts b/x-pack/plugins/apm/server/routes/settings/apm_indices.ts
index 2d5722744f93e..e52ce760e026a 100644
--- a/x-pack/plugins/apm/server/routes/settings/apm_indices.ts
+++ b/x-pack/plugins/apm/server/routes/settings/apm_indices.ts
@@ -34,7 +34,7 @@ export const apmIndicesRoute = createRoute(() => ({
}));
// save ui indices
-export const saveApmIndicesRoute = createRoute((core) => ({
+export const saveApmIndicesRoute = createRoute(() => ({
method: 'POST',
path: '/api/apm/settings/apm-indices/save',
options: {
@@ -50,7 +50,7 @@ export const saveApmIndicesRoute = createRoute((core) => ({
'apm_oss.metricsIndices': t.string,
}),
},
- handler: async ({ context, request }) => {
+ handler: async ({ context }) => {
const { body } = context.params;
const savedObjectsClient = context.core.savedObjects.client;
return await saveApmIndices(savedObjectsClient, body);
diff --git a/x-pack/plugins/apm/server/routes/settings/custom_link.ts b/x-pack/plugins/apm/server/routes/settings/custom_link.ts
index f32840fe08b9c..83c23a75e999d 100644
--- a/x-pack/plugins/apm/server/routes/settings/custom_link.ts
+++ b/x-pack/plugins/apm/server/routes/settings/custom_link.ts
@@ -17,7 +17,7 @@ import { getTransaction } from '../../lib/settings/custom_link/get_transaction';
import { listCustomLinks } from '../../lib/settings/custom_link/list_custom_links';
import { createRoute } from '../create_route';
-export const customLinkTransactionRoute = createRoute((core) => ({
+export const customLinkTransactionRoute = createRoute(() => ({
path: '/api/apm/settings/custom_links/transaction',
params: {
query: filterOptionsRt,
@@ -31,7 +31,7 @@ export const customLinkTransactionRoute = createRoute((core) => ({
},
}));
-export const listCustomLinksRoute = createRoute((core) => ({
+export const listCustomLinksRoute = createRoute(() => ({
path: '/api/apm/settings/custom_links',
params: {
query: filterOptionsRt,
diff --git a/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx b/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx
index 47b461f22ad65..3014369d94857 100644
--- a/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx
+++ b/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx
@@ -69,6 +69,7 @@ export const withUnconnectedElementsLoadedTelemetry = (
) =>
function ElementsLoadedTelemetry(props: ElementsLoadedTelemetryProps) {
const { telemetryElementCounts, workpad, telemetryResolvedArgs, ...other } = props;
+ const { error, pending } = telemetryElementCounts;
const [currentWorkpadId, setWorkpadId] = useState(undefined);
const [hasReported, setHasReported] = useState(false);
@@ -87,27 +88,20 @@ export const withUnconnectedElementsLoadedTelemetry = (
0
);
- if (
- workpadElementCount === 0 ||
- (resolvedArgsAreForWorkpad && telemetryElementCounts.pending === 0)
- ) {
+ if (workpadElementCount === 0 || (resolvedArgsAreForWorkpad && pending === 0)) {
setHasReported(true);
} else {
setHasReported(false);
}
- } else if (
- !hasReported &&
- telemetryElementCounts.pending === 0 &&
- resolvedArgsAreForWorkpad
- ) {
- if (telemetryElementCounts.error > 0) {
+ } else if (!hasReported && pending === 0 && resolvedArgsAreForWorkpad) {
+ if (error > 0) {
trackMetric(METRIC_TYPE.LOADED, [WorkpadLoadedMetric, WorkpadLoadedWithErrorsMetric]);
} else {
trackMetric(METRIC_TYPE.LOADED, WorkpadLoadedMetric);
}
setHasReported(true);
}
- });
+ }, [currentWorkpadId, hasReported, error, pending, telemetryResolvedArgs, workpad]);
return ;
};
diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot b/x-pack/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot
index 6601f570209e9..14791cd3d8b25 100644
--- a/x-pack/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot
+++ b/x-pack/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot
@@ -63,6 +63,7 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = `
>
@@ -88,6 +89,7 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = `
>
@@ -118,6 +120,7 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = `
>
@@ -148,6 +151,7 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = `
>
@@ -237,6 +241,7 @@ exports[`Storyshots components/Assets/Asset marker 1`] = `
>
@@ -262,6 +267,7 @@ exports[`Storyshots components/Assets/Asset marker 1`] = `
>
@@ -292,6 +298,7 @@ exports[`Storyshots components/Assets/Asset marker 1`] = `
>
@@ -322,6 +329,7 @@ exports[`Storyshots components/Assets/Asset marker 1`] = `
>
diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset_manager.stories.storyshot b/x-pack/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset_manager.stories.storyshot
index aff630b21c770..1b8f1480759f6 100644
--- a/x-pack/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset_manager.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset_manager.stories.storyshot
@@ -422,6 +422,7 @@ Array [
>
@@ -447,6 +448,7 @@ Array [
>
@@ -477,6 +479,7 @@ Array [
>
@@ -507,6 +510,7 @@ Array [
>
@@ -585,6 +589,7 @@ Array [
>
@@ -610,6 +615,7 @@ Array [
>
@@ -640,6 +646,7 @@ Array [
>
@@ -670,6 +677,7 @@ Array [
>
diff --git a/x-pack/plugins/canvas/public/components/router/index.ts b/x-pack/plugins/canvas/public/components/router/index.ts
index 5e014870f5158..fa857c6f0cd3c 100644
--- a/x-pack/plugins/canvas/public/components/router/index.ts
+++ b/x-pack/plugins/canvas/public/components/router/index.ts
@@ -11,7 +11,6 @@ import {
enableAutoplay,
setRefreshInterval,
setAutoplayInterval,
- // @ts-ignore untyped local
} from '../../state/actions/workpad';
// @ts-ignore untyped local
import { Router as Component } from './router';
diff --git a/x-pack/plugins/canvas/public/components/saved_elements_modal/__examples__/__snapshots__/element_controls.stories.storyshot b/x-pack/plugins/canvas/public/components/saved_elements_modal/__examples__/__snapshots__/element_controls.stories.storyshot
index 6f12f68356467..408b0679c415f 100644
--- a/x-pack/plugins/canvas/public/components/saved_elements_modal/__examples__/__snapshots__/element_controls.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/saved_elements_modal/__examples__/__snapshots__/element_controls.stories.storyshot
@@ -16,6 +16,7 @@ exports[`Storyshots components/SavedElementsModal/ElementControls has two button
>
@@ -42,6 +43,7 @@ exports[`Storyshots components/SavedElementsModal/ElementControls has two button
>
diff --git a/x-pack/plugins/canvas/public/components/saved_elements_modal/__examples__/__snapshots__/element_grid.stories.storyshot b/x-pack/plugins/canvas/public/components/saved_elements_modal/__examples__/__snapshots__/element_grid.stories.storyshot
index be0fb0573c394..1c506819df1fb 100644
--- a/x-pack/plugins/canvas/public/components/saved_elements_modal/__examples__/__snapshots__/element_grid.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/saved_elements_modal/__examples__/__snapshots__/element_grid.stories.storyshot
@@ -66,6 +66,7 @@ exports[`Storyshots components/SavedElementsModal/ElementGrid default 1`] = `
>
@@ -92,6 +93,7 @@ exports[`Storyshots components/SavedElementsModal/ElementGrid default 1`] = `
>
@@ -170,6 +172,7 @@ exports[`Storyshots components/SavedElementsModal/ElementGrid default 1`] = `
>
@@ -196,6 +199,7 @@ exports[`Storyshots components/SavedElementsModal/ElementGrid default 1`] = `
>
@@ -274,6 +278,7 @@ exports[`Storyshots components/SavedElementsModal/ElementGrid default 1`] = `
>
@@ -300,6 +305,7 @@ exports[`Storyshots components/SavedElementsModal/ElementGrid default 1`] = `
>
diff --git a/x-pack/plugins/canvas/public/components/saved_elements_modal/__examples__/__snapshots__/saved_elements_modal.stories.storyshot b/x-pack/plugins/canvas/public/components/saved_elements_modal/__examples__/__snapshots__/saved_elements_modal.stories.storyshot
index 03093b41300b8..04b2184f27462 100644
--- a/x-pack/plugins/canvas/public/components/saved_elements_modal/__examples__/__snapshots__/saved_elements_modal.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/saved_elements_modal/__examples__/__snapshots__/saved_elements_modal.stories.storyshot
@@ -362,6 +362,7 @@ Array [
>
@@ -388,6 +389,7 @@ Array [
>
@@ -466,6 +468,7 @@ Array [
>
@@ -492,6 +495,7 @@ Array [
>
@@ -570,6 +574,7 @@ Array [
>
@@ -596,6 +601,7 @@ Array [
>
@@ -851,6 +857,7 @@ Array [
>
@@ -877,6 +884,7 @@ Array [
>
diff --git a/x-pack/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.stories.storyshot b/x-pack/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.stories.storyshot
index 4d5b9570ee20f..16263aa7ea384 100644
--- a/x-pack/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.stories.storyshot
+++ b/x-pack/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.stories.storyshot
@@ -55,6 +55,7 @@ exports[`Storyshots components/Sidebar/SidebarHeader with layer controls 1`] = `
>
@@ -80,6 +81,7 @@ exports[`Storyshots components/Sidebar/SidebarHeader with layer controls 1`] = `
>
@@ -105,6 +107,7 @@ exports[`Storyshots components/Sidebar/SidebarHeader with layer controls 1`] = `
>
@@ -130,6 +133,7 @@ exports[`Storyshots components/Sidebar/SidebarHeader with layer controls 1`] = `
>
diff --git a/x-pack/plugins/canvas/public/components/workpad_color_picker/index.ts b/x-pack/plugins/canvas/public/components/workpad_color_picker/index.ts
index c6dddab3b5dd1..abd40731078ec 100644
--- a/x-pack/plugins/canvas/public/components/workpad_color_picker/index.ts
+++ b/x-pack/plugins/canvas/public/components/workpad_color_picker/index.ts
@@ -5,7 +5,6 @@
*/
import { connect } from 'react-redux';
-// @ts-ignore
import { addColor, removeColor } from '../../state/actions/workpad';
import { getWorkpadColors } from '../../state/selectors/workpad';
diff --git a/x-pack/plugins/canvas/public/components/workpad_config/index.js b/x-pack/plugins/canvas/public/components/workpad_config/index.ts
similarity index 63%
rename from x-pack/plugins/canvas/public/components/workpad_config/index.js
rename to x-pack/plugins/canvas/public/components/workpad_config/index.ts
index 913cf7093e726..e417821fd4f67 100644
--- a/x-pack/plugins/canvas/public/components/workpad_config/index.js
+++ b/x-pack/plugins/canvas/public/components/workpad_config/index.ts
@@ -7,28 +7,29 @@
import { connect } from 'react-redux';
import { get } from 'lodash';
-import { sizeWorkpad, setName, setWorkpadCSS } from '../../state/actions/workpad';
+import { sizeWorkpad as setSize, setName, setWorkpadCSS } from '../../state/actions/workpad';
import { getWorkpad } from '../../state/selectors/workpad';
import { DEFAULT_WORKPAD_CSS } from '../../../common/lib/constants';
import { WorkpadConfig as Component } from './workpad_config';
+import { State } from '../../../types';
-const mapStateToProps = (state) => {
+const mapStateToProps = (state: State) => {
const workpad = getWorkpad(state);
return {
- name: get(workpad, 'name'),
+ name: get(workpad, 'name'),
size: {
- width: get(workpad, 'width'),
- height: get(workpad, 'height'),
+ width: get(workpad, 'width'),
+ height: get(workpad, 'height'),
},
- css: get(workpad, 'css', DEFAULT_WORKPAD_CSS),
+ css: get(workpad, 'css', DEFAULT_WORKPAD_CSS),
};
};
const mapDispatchToProps = {
- setSize: (size) => sizeWorkpad(size),
- setName: (name) => setName(name),
- setWorkpadCSS: (css) => setWorkpadCSS(css),
+ setSize,
+ setName,
+ setWorkpadCSS,
};
export const WorkpadConfig = connect(mapStateToProps, mapDispatchToProps)(Component);
diff --git a/x-pack/plugins/canvas/public/components/workpad_config/workpad_config.js b/x-pack/plugins/canvas/public/components/workpad_config/workpad_config.js
deleted file mode 100644
index 45758c9965653..0000000000000
--- a/x-pack/plugins/canvas/public/components/workpad_config/workpad_config.js
+++ /dev/null
@@ -1,169 +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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React, { PureComponent } from 'react';
-import PropTypes from 'prop-types';
-import {
- EuiFieldText,
- EuiFieldNumber,
- EuiBadge,
- EuiButtonIcon,
- EuiFormRow,
- EuiFlexGroup,
- EuiFlexItem,
- EuiSpacer,
- EuiTitle,
- EuiToolTip,
- EuiTextArea,
- EuiAccordion,
- EuiText,
- EuiButton,
-} from '@elastic/eui';
-import { DEFAULT_WORKPAD_CSS } from '../../../common/lib/constants';
-import { ComponentStrings } from '../../../i18n';
-
-const { WorkpadConfig: strings } = ComponentStrings;
-
-export class WorkpadConfig extends PureComponent {
- static propTypes = {
- size: PropTypes.object.isRequired,
- name: PropTypes.string.isRequired,
- css: PropTypes.string,
- setSize: PropTypes.func.isRequired,
- setName: PropTypes.func.isRequired,
- setWorkpadCSS: PropTypes.func.isRequired,
- };
-
- state = {
- css: this.props.css,
- };
-
- render() {
- const { size, name, setSize, setName, setWorkpadCSS } = this.props;
- const { css } = this.state;
- const rotate = () => setSize({ width: size.height, height: size.width });
-
- const badges = [
- {
- name: '1080p',
- size: { height: 1080, width: 1920 },
- },
- {
- name: '720p',
- size: { height: 720, width: 1280 },
- },
- {
- name: 'A4',
- size: { height: 842, width: 590 },
- },
- {
- name: strings.getUSLetterButtonLabel(),
- size: { height: 792, width: 612 },
- },
- ];
-
- return (
-
-
-
- {strings.getTitle()}
-
-
-
-
-
-
- setName(e.target.value)} />
-
-
-
-
-
-
-
- setSize({ width: Number(e.target.value), height: size.height })}
- value={size.width}
- />
-
-
-
-
-
-
-
-
-
-
-
- setSize({ height: Number(e.target.value), width: size.width })}
- value={size.height}
- />
-
-
-
-
-
-
-
- {badges.map((badge, i) => (
- setSize(badge.size)}
- aria-label={strings.getPageSizeBadgeAriaLabel(badge.name)}
- onClickAriaLabel={strings.getPageSizeBadgeOnClickAriaLabel(badge.name)}
- >
- {badge.name}
-
- ))}
-
-
-
-
-
-
- {strings.getGlobalCSSLabel()}
-
-
- }
- >
-
- this.setState({ css: e.target.value })}
- rows={10}
- />
-
- setWorkpadCSS(css || DEFAULT_WORKPAD_CSS)}>
- {strings.getApplyStylesheetButtonLabel()}
-
-
-
-
-
-
- );
- }
-}
diff --git a/x-pack/plugins/canvas/public/components/workpad_config/workpad_config.tsx b/x-pack/plugins/canvas/public/components/workpad_config/workpad_config.tsx
new file mode 100644
index 0000000000000..7b7a1e08b2c5d
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/workpad_config/workpad_config.tsx
@@ -0,0 +1,175 @@
+/*
+ * 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 PropTypes from 'prop-types';
+import {
+ EuiFieldText,
+ EuiFieldNumber,
+ EuiBadge,
+ EuiButtonIcon,
+ EuiFormRow,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+ EuiTitle,
+ EuiToolTip,
+ EuiTextArea,
+ EuiAccordion,
+ EuiText,
+ EuiButton,
+} from '@elastic/eui';
+import { DEFAULT_WORKPAD_CSS } from '../../../common/lib/constants';
+import { ComponentStrings } from '../../../i18n';
+
+const { WorkpadConfig: strings } = ComponentStrings;
+
+interface Props {
+ size: {
+ height: number;
+ width: number;
+ };
+ name: string;
+ css?: string;
+ setSize: ({ height, width }: { height: number; width: number }) => void;
+ setName: (name: string) => void;
+ setWorkpadCSS: (css: string) => void;
+}
+
+export const WorkpadConfig: FunctionComponent = (props) => {
+ const [css, setCSS] = useState(props.css);
+ const { size, name, setSize, setName, setWorkpadCSS } = props;
+ const rotate = () => setSize({ width: size.height, height: size.width });
+
+ const badges = [
+ {
+ name: '1080p',
+ size: { height: 1080, width: 1920 },
+ },
+ {
+ name: '720p',
+ size: { height: 720, width: 1280 },
+ },
+ {
+ name: 'A4',
+ size: { height: 842, width: 590 },
+ },
+ {
+ name: strings.getUSLetterButtonLabel(),
+ size: { height: 792, width: 612 },
+ },
+ ];
+
+ return (
+
+
+
+ {strings.getTitle()}
+
+
+
+
+
+
+ setName(e.target.value)} />
+
+
+
+
+
+
+
+ setSize({ width: Number(e.target.value), height: size.height })}
+ value={size.width}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ setSize({ height: Number(e.target.value), width: size.width })}
+ value={size.height}
+ />
+
+
+
+
+
+
+
+ {badges.map((badge, i) => (
+ setSize(badge.size)}
+ aria-label={strings.getPageSizeBadgeAriaLabel(badge.name)}
+ onClickAriaLabel={strings.getPageSizeBadgeOnClickAriaLabel(badge.name)}
+ >
+ {badge.name}
+
+ ))}
+
+
+
+
+
+
+ {strings.getGlobalCSSLabel()}
+
+
+ }
+ >
+
+ setCSS(e.target.value)}
+ rows={10}
+ />
+
+ setWorkpadCSS(css || DEFAULT_WORKPAD_CSS)}>
+ {strings.getApplyStylesheetButtonLabel()}
+
+
+
+
+
+
+ );
+};
+
+WorkpadConfig.propTypes = {
+ size: PropTypes.object.isRequired,
+ name: PropTypes.string.isRequired,
+ css: PropTypes.string,
+ setSize: PropTypes.func.isRequired,
+ setName: PropTypes.func.isRequired,
+ setWorkpadCSS: PropTypes.func.isRequired,
+};
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/index.ts b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/index.ts
index e561607cb101e..0765973915f77 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/index.ts
+++ b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/index.ts
@@ -14,13 +14,11 @@ import { State, CanvasWorkpadBoundingBox } from '../../../../types';
import { fetchAllRenderables } from '../../../state/actions/elements';
// @ts-ignore Untyped local
import { setZoomScale, setFullscreen, selectToplevelNodes } from '../../../state/actions/transient';
-// @ts-ignore Untyped local
import {
setWriteable,
setRefreshInterval,
enableAutoplay,
setAutoplayInterval,
- // @ts-ignore Untyped local
} from '../../../state/actions/workpad';
import { getZoomScale, canUserWrite } from '../../../state/selectors/app';
import {
@@ -75,7 +73,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
},
doRefresh: () => dispatch(fetchAllRenderables()),
setRefreshInterval: (interval: number) => dispatch(setRefreshInterval(interval)),
- enableAutoplay: (autoplay: number) => dispatch(enableAutoplay(autoplay)),
+ enableAutoplay: (autoplay: number) => dispatch(enableAutoplay(!!autoplay)),
setAutoplayInterval: (interval: number) => dispatch(setAutoplayInterval(interval)),
});
diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/simple_template.examples.storyshot b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/simple_template.examples.storyshot
index 14466cab1a698..f8583d7cd0dc0 100644
--- a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/simple_template.examples.storyshot
+++ b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/__snapshots__/simple_template.examples.storyshot
@@ -169,6 +169,7 @@ exports[`Storyshots arguments/SeriesStyle/components simple: no series 1`] = `
>
diff --git a/x-pack/plugins/canvas/public/lib/create_thunk.ts b/x-pack/plugins/canvas/public/lib/create_thunk.ts
new file mode 100644
index 0000000000000..cbcaeeccc8b93
--- /dev/null
+++ b/x-pack/plugins/canvas/public/lib/create_thunk.ts
@@ -0,0 +1,23 @@
+/*
+ * 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 { Dispatch, Action } from 'redux';
+// @ts-ignore untyped dependency
+import { createThunk as createThunkFn } from 'redux-thunks/cjs';
+import { State } from '../../types';
+
+type CreateThunk = (
+ type: string,
+ fn: (
+ params: { type: string; dispatch: Dispatch; getState: () => State },
+ ...args: Arguments
+ ) => void
+) => (...args: Arguments) => Action;
+
+// This declaration exists because redux-thunks is not typed, and has a dependency on
+// Canvas State. Therefore, creating a wrapper that strongly-types the function-- and creates
+// a single point of replacement, should the need arise-- is a nice workaround.
+export const createThunk = createThunkFn as CreateThunk;
diff --git a/x-pack/plugins/canvas/public/state/actions/elements.js b/x-pack/plugins/canvas/public/state/actions/elements.js
index 47fbc782f90d3..e89e62917da39 100644
--- a/x-pack/plugins/canvas/public/state/actions/elements.js
+++ b/x-pack/plugins/canvas/public/state/actions/elements.js
@@ -5,10 +5,10 @@
*/
import { createAction } from 'redux-actions';
-import { createThunk } from 'redux-thunks/cjs';
import immutable from 'object-path-immutable';
import { get, pick, cloneDeep, without } from 'lodash';
import { toExpression, safeElementFromExpression } from '@kbn/interpreter/common';
+import { createThunk } from '../../lib/create_thunk';
import { getPages, getNodeById, getNodes, getSelectedPageIndex } from '../selectors/workpad';
import { getValue as getResolvedArgsValue } from '../selectors/resolved_args';
import { getDefaultElement } from '../defaults';
diff --git a/x-pack/plugins/canvas/public/state/actions/embeddable.ts b/x-pack/plugins/canvas/public/state/actions/embeddable.ts
index e2cf588ec20a9..a153cb7f4354d 100644
--- a/x-pack/plugins/canvas/public/state/actions/embeddable.ts
+++ b/x-pack/plugins/canvas/public/state/actions/embeddable.ts
@@ -6,8 +6,7 @@
import { Dispatch } from 'redux';
import { createAction } from 'redux-actions';
-// @ts-ignore Untyped
-import { createThunk } from 'redux-thunks';
+import { createThunk } from '../../lib/create_thunk';
// @ts-ignore Untyped Local
import { fetchRenderable } from './elements';
import { State } from '../../../types';
diff --git a/x-pack/plugins/canvas/public/state/actions/workpad.js b/x-pack/plugins/canvas/public/state/actions/workpad.ts
similarity index 54%
rename from x-pack/plugins/canvas/public/state/actions/workpad.js
rename to x-pack/plugins/canvas/public/state/actions/workpad.ts
index 167c156dce998..47df38838f890 100644
--- a/x-pack/plugins/canvas/public/state/actions/workpad.js
+++ b/x-pack/plugins/canvas/public/state/actions/workpad.ts
@@ -5,26 +5,28 @@
*/
import { createAction } from 'redux-actions';
-import { createThunk } from 'redux-thunks/cjs';
import { without, includes } from 'lodash';
+import { createThunk } from '../../lib/create_thunk';
import { getWorkpadColors } from '../selectors/workpad';
+// @ts-ignore
import { fetchAllRenderables } from './elements';
+import { CanvasWorkpad } from '../../../types';
-export const sizeWorkpad = createAction('sizeWorkpad');
-export const setName = createAction('setName');
-export const setWriteable = createAction('setWriteable');
-export const setColors = createAction('setColors');
-export const setRefreshInterval = createAction('setRefreshInterval');
-export const setWorkpadCSS = createAction('setWorkpadCSS');
-export const enableAutoplay = createAction('enableAutoplay');
-export const setAutoplayInterval = createAction('setAutoplayInterval');
-export const resetWorkpad = createAction('resetWorkpad');
+export const sizeWorkpad = createAction<{ height: number; width: number }>('sizeWorkpad');
+export const setName = createAction('setName');
+export const setWriteable = createAction('setWriteable');
+export const setColors = createAction('setColors');
+export const setRefreshInterval = createAction('setRefreshInterval');
+export const setWorkpadCSS = createAction('setWorkpadCSS');
+export const enableAutoplay = createAction('enableAutoplay');
+export const setAutoplayInterval = createAction('setAutoplayInterval');
+export const resetWorkpad = createAction('resetWorkpad');
export const initializeWorkpad = createThunk('initializeWorkpad', ({ dispatch }) => {
dispatch(fetchAllRenderables());
});
-export const addColor = createThunk('addColor', ({ dispatch, getState }, color) => {
+export const addColor = createThunk('addColor', ({ dispatch, getState }, color: string) => {
const colors = getWorkpadColors(getState()).slice(0);
if (!includes(colors, color)) {
colors.push(color);
@@ -32,16 +34,20 @@ export const addColor = createThunk('addColor', ({ dispatch, getState }, color)
dispatch(setColors(colors));
});
-export const removeColor = createThunk('removeColor', ({ dispatch, getState }, color) => {
+export const removeColor = createThunk('removeColor', ({ dispatch, getState }, color: string) => {
dispatch(setColors(without(getWorkpadColors(getState()), color)));
});
export const setWorkpad = createThunk(
'setWorkpad',
- ({ dispatch, type }, workpad, { loadPages = true } = {}) => {
+ (
+ { dispatch, type },
+ workpad: CanvasWorkpad,
+ { loadPages = true }: { loadPages?: boolean } = {}
+ ) => {
dispatch(createAction(type)(workpad)); // set the workpad object in state
if (loadPages) {
dispatch(initializeWorkpad());
- } // load all the elements on the workpad
+ }
}
);
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 da461609f0b83..56d76da522ac2 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
@@ -12,7 +12,7 @@ type HttpResponse = Record | any[];
// Register helpers to mock HTTP Requests
const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
const setLoadTemplatesResponse = (response: HttpResponse = []) => {
- server.respondWith('GET', `${API_BASE_PATH}/index-templates`, [
+ server.respondWith('GET', `${API_BASE_PATH}/index_templates`, [
200,
{ 'Content-Type': 'application/json' },
JSON.stringify(response),
@@ -27,8 +27,16 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
]);
};
+ const setLoadDataStreamsResponse = (response: HttpResponse = []) => {
+ server.respondWith('GET', `${API_BASE_PATH}/data_streams`, [
+ 200,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify(response),
+ ]);
+ };
+
const setDeleteTemplateResponse = (response: HttpResponse = []) => {
- server.respondWith('POST', `${API_BASE_PATH}/delete-index-templates`, [
+ server.respondWith('POST', `${API_BASE_PATH}/delete_index_templates`, [
200,
{ 'Content-Type': 'application/json' },
JSON.stringify(response),
@@ -39,7 +47,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
const status = error ? error.status || 400 : 200;
const body = error ? error.body : response;
- server.respondWith('GET', `${API_BASE_PATH}/index-templates/:id`, [
+ server.respondWith('GET', `${API_BASE_PATH}/index_templates/:id`, [
status,
{ 'Content-Type': 'application/json' },
JSON.stringify(body),
@@ -50,7 +58,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
const status = error ? error.body.status || 400 : 200;
const body = error ? JSON.stringify(error.body) : JSON.stringify(response);
- server.respondWith('POST', `${API_BASE_PATH}/index-templates`, [
+ server.respondWith('POST', `${API_BASE_PATH}/index_templates`, [
status,
{ 'Content-Type': 'application/json' },
body,
@@ -61,7 +69,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
const status = error ? error.status || 400 : 200;
const body = error ? JSON.stringify(error.body) : JSON.stringify(response);
- server.respondWith('PUT', `${API_BASE_PATH}/index-templates/:name`, [
+ server.respondWith('PUT', `${API_BASE_PATH}/index_templates/:name`, [
status,
{ 'Content-Type': 'application/json' },
body,
@@ -71,6 +79,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
return {
setLoadTemplatesResponse,
setLoadIndicesResponse,
+ setLoadDataStreamsResponse,
setDeleteTemplateResponse,
setLoadTemplateResponse,
setCreateTemplateResponse,
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts
index 8e7755a65af3c..f581083e28cc6 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts
@@ -10,44 +10,4 @@ export { nextTick, getRandomString, findTestSubject, TestBed } from '../../../..
export { setupEnvironment, WithAppDependencies, services } from './setup_environment';
-export type TestSubjects =
- | 'aliasesTab'
- | 'appTitle'
- | 'cell'
- | 'closeDetailsButton'
- | 'createTemplateButton'
- | 'createLegacyTemplateButton'
- | 'deleteSystemTemplateCallOut'
- | 'deleteTemplateButton'
- | 'deleteTemplatesConfirmation'
- | 'documentationLink'
- | 'emptyPrompt'
- | 'manageTemplateButton'
- | 'mappingsTab'
- | 'noAliasesCallout'
- | 'noMappingsCallout'
- | 'noSettingsCallout'
- | 'indicesList'
- | 'indicesTab'
- | 'indexTableIncludeHiddenIndicesToggle'
- | 'indexTableIndexNameLink'
- | 'reloadButton'
- | 'reloadIndicesButton'
- | 'row'
- | 'sectionError'
- | 'sectionLoading'
- | 'settingsTab'
- | 'summaryTab'
- | 'summaryTitle'
- | 'systemTemplatesSwitch'
- | 'templateDetails'
- | 'templateDetails.manageTemplateButton'
- | 'templateDetails.sectionLoading'
- | 'templateDetails.tab'
- | 'templateDetails.title'
- | 'templateList'
- | 'templateTable'
- | 'templatesTab'
- | 'legacyTemplateTable'
- | 'viewButton'
- | 'filterList.filterItem';
+export { TestSubjects } from './test_subjects';
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts
new file mode 100644
index 0000000000000..4e297118b0fdd
--- /dev/null
+++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts
@@ -0,0 +1,51 @@
+/*
+ * 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 type TestSubjects =
+ | 'aliasesTab'
+ | 'appTitle'
+ | 'cell'
+ | 'closeDetailsButton'
+ | 'createLegacyTemplateButton'
+ | 'createTemplateButton'
+ | 'dataStreamsEmptyPromptTemplateLink'
+ | 'dataStreamTable'
+ | 'dataStreamTable'
+ | 'deleteSystemTemplateCallOut'
+ | 'deleteTemplateButton'
+ | 'deleteTemplatesConfirmation'
+ | 'documentationLink'
+ | 'emptyPrompt'
+ | 'filterList.filterItem'
+ | 'indexTable'
+ | 'indexTableIncludeHiddenIndicesToggle'
+ | 'indexTableIndexNameLink'
+ | 'indicesList'
+ | 'indicesTab'
+ | 'legacyTemplateTable'
+ | 'manageTemplateButton'
+ | 'mappingsTab'
+ | 'noAliasesCallout'
+ | 'noMappingsCallout'
+ | 'noSettingsCallout'
+ | 'reloadButton'
+ | 'reloadIndicesButton'
+ | 'row'
+ | 'sectionError'
+ | 'sectionLoading'
+ | 'settingsTab'
+ | 'summaryTab'
+ | 'summaryTitle'
+ | 'systemTemplatesSwitch'
+ | 'templateDetails'
+ | 'templateDetails.manageTemplateButton'
+ | 'templateDetails.sectionLoading'
+ | 'templateDetails.tab'
+ | 'templateDetails.title'
+ | 'templateList'
+ | 'templatesTab'
+ | 'templateTable'
+ | 'viewButton';
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
new file mode 100644
index 0000000000000..ef6aca44a1754
--- /dev/null
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts
@@ -0,0 +1,101 @@
+/*
+ * 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 { act } from 'react-dom/test-utils';
+
+import {
+ registerTestBed,
+ TestBed,
+ TestBedConfig,
+ findTestSubject,
+} from '../../../../../test_utils';
+import { DataStream } from '../../../common';
+import { IndexManagementHome } from '../../../public/application/sections/home'; // eslint-disable-line @kbn/eslint/no-restricted-paths
+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;
+ clickIndicesAt: (index: number) => void;
+ };
+}
+
+export const setup = async (): Promise => {
+ const testBed = await initTestBed();
+
+ /**
+ * User Actions
+ */
+
+ const goToDataStreamsList = () => {
+ testBed.find('data_streamsTab').simulate('click');
+ };
+
+ const clickEmptyPromptIndexTemplateLink = async () => {
+ const { find, component, router } = testBed;
+
+ const templateLink = find('dataStreamsEmptyPromptTemplateLink');
+
+ await act(async () => {
+ router.navigateTo(templateLink.props().href!);
+ });
+
+ component.update();
+ };
+
+ const clickReloadButton = () => {
+ const { find } = testBed;
+ find('reloadButton').simulate('click');
+ };
+
+ const clickIndicesAt = async (index: number) => {
+ const { component, table, router } = testBed;
+ const { rows } = table.getMetaData('dataStreamTable');
+ const indicesLink = findTestSubject(rows[index].reactWrapper, 'indicesLink');
+
+ await act(async () => {
+ router.navigateTo(indicesLink.props().href!);
+ });
+
+ component.update();
+ };
+
+ return {
+ ...testBed,
+ actions: {
+ goToDataStreamsList,
+ clickEmptyPromptIndexTemplateLink,
+ clickReloadButton,
+ clickIndicesAt,
+ },
+ };
+};
+
+export const createDataStreamPayload = (name: string): DataStream => ({
+ name,
+ timeStampField: '@timestamp',
+ indices: [
+ {
+ name: 'indexName',
+ uuid: 'indexId',
+ },
+ ],
+ generation: 1,
+});
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
new file mode 100644
index 0000000000000..efe2e2d0c74ae
--- /dev/null
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
@@ -0,0 +1,137 @@
+/*
+ * 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 { act } from 'react-dom/test-utils';
+
+import { API_BASE_PATH } from '../../../common/constants';
+import { setupEnvironment } from '../helpers';
+
+import { DataStreamsTabTestBed, setup, createDataStreamPayload } from './data_streams_tab.helpers';
+
+describe('Data Streams tab', () => {
+ const { server, httpRequestsMockHelpers } = setupEnvironment();
+ let testBed: DataStreamsTabTestBed;
+
+ afterAll(() => {
+ 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.setLoadDataStreamsResponse([]);
+ httpRequestsMockHelpers.setLoadTemplatesResponse({ templates: [], legacyTemplates: [] });
+
+ await act(async () => {
+ actions.goToDataStreamsList();
+ });
+
+ 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;
+
+ await act(async () => {
+ actions.clickEmptyPromptIndexTemplateLink();
+ });
+
+ expect(exists('templateList')).toBe(true);
+ });
+ });
+
+ describe('when there are data streams', () => {
+ beforeEach(async () => {
+ const { actions, component } = testBed;
+
+ httpRequestsMockHelpers.setLoadDataStreamsResponse([
+ createDataStreamPayload('dataStream1'),
+ createDataStreamPayload('dataStream2'),
+ ]);
+
+ await act(async () => {
+ actions.goToDataStreamsList();
+ });
+
+ 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'],
+ ]);
+ });
+
+ test('has a button to reload the data streams', async () => {
+ const { exists, actions } = testBed;
+ const totalRequests = server.requests.length;
+
+ expect(exists('reloadButton')).toBe(true);
+
+ await act(async () => {
+ actions.clickReloadButton();
+ });
+
+ expect(server.requests.length).toBe(totalRequests + 1);
+ expect(server.requests[server.requests.length - 1].url).toBe(`${API_BASE_PATH}/data_streams`);
+ });
+
+ 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'],
+ ]);
+ });
+ });
+});
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts
index 8f6a8dddeb195..7c79c7e61174e 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.test.ts
@@ -188,7 +188,7 @@ describe('Index Templates tab', () => {
expect(server.requests.length).toBe(totalRequests + 1);
expect(server.requests[server.requests.length - 1].url).toBe(
- `${API_BASE_PATH}/index-templates`
+ `${API_BASE_PATH}/index_templates`
);
});
@@ -318,7 +318,7 @@ describe('Index Templates tab', () => {
const latestRequest = server.requests[server.requests.length - 1];
expect(latestRequest.method).toBe('POST');
- expect(latestRequest.url).toBe(`${API_BASE_PATH}/delete-index-templates`);
+ expect(latestRequest.url).toBe(`${API_BASE_PATH}/delete_index_templates`);
expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({
templates: [{ name: legacyTemplates[0].name, isLegacy }],
});
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 e995932dfa00d..f00348aacbf08 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
@@ -6,8 +6,13 @@
import { act } from 'react-dom/test-utils';
-import { registerTestBed, TestBed, TestBedConfig } from '../../../../../test_utils';
-import { IndexList } from '../../../public/application/sections/home/index_list'; // eslint-disable-line @kbn/eslint/no-restricted-paths
+import {
+ registerTestBed,
+ TestBed,
+ TestBedConfig,
+ findTestSubject,
+} from '../../../../../test_utils';
+import { IndexManagementHome } from '../../../public/application/sections/home'; // eslint-disable-line @kbn/eslint/no-restricted-paths
import { indexManagementStore } from '../../../public/application/store'; // eslint-disable-line @kbn/eslint/no-restricted-paths
import { WithAppDependencies, services, TestSubjects } from '../helpers';
@@ -15,18 +20,19 @@ const testBedConfig: TestBedConfig = {
store: () => indexManagementStore(services as any),
memoryRouter: {
initialEntries: [`/indices?includeHiddenIndices=true`],
- componentRoutePath: `/:section(indices|templates)`,
+ componentRoutePath: `/:section(indices|data_streams)`,
},
doMountAsync: true,
};
-const initTestBed = registerTestBed(WithAppDependencies(IndexList), testBedConfig);
+const initTestBed = registerTestBed(WithAppDependencies(IndexManagementHome), testBedConfig);
export interface IndicesTestBed extends TestBed {
actions: {
selectIndexDetailsTab: (tab: 'settings' | 'mappings' | 'stats' | 'edit_settings') => void;
getIncludeHiddenIndicesToggleStatus: () => boolean;
clickIncludeHiddenIndicesToggle: () => void;
+ clickDataStreamAt: (index: number) => void;
};
}
@@ -59,12 +65,25 @@ export const setup = async (): Promise => {
component.update();
};
+ const clickDataStreamAt = async (index: number) => {
+ const { component, table, router } = testBed;
+ const { rows } = table.getMetaData('indexTable');
+ const dataStreamLink = findTestSubject(rows[index].reactWrapper, 'dataStreamLink');
+
+ await act(async () => {
+ router.navigateTo(dataStreamLink.props().href!);
+ });
+
+ component.update();
+ };
+
return {
...testBed,
actions: {
selectIndexDetailsTab,
getIncludeHiddenIndicesToggleStatus,
clickIncludeHiddenIndicesToggle,
+ clickDataStreamAt,
},
};
};
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 11c25ffbb590f..c2d955bb4dfce 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
@@ -9,6 +9,7 @@ import { act } from 'react-dom/test-utils';
import { API_BASE_PATH } from '../../../common/constants';
import { setupEnvironment, nextTick } from '../helpers';
import { IndicesTestBed, setup } from './indices_tab.helpers';
+import { createDataStreamPayload } from './data_streams_tab.helpers';
/**
* The below import is required to avoid a console error warn from the "brace" package
@@ -52,6 +53,49 @@ describe('', () => {
});
});
+ describe('data stream column', () => {
+ beforeEach(async () => {
+ httpRequestsMockHelpers.setLoadIndicesResponse([
+ {
+ health: '',
+ status: '',
+ primary: '',
+ replica: '',
+ documents: '',
+ documents_deleted: '',
+ size: '',
+ primary_size: '',
+ name: 'data-stream-index',
+ data_stream: 'dataStream1',
+ },
+ ]);
+
+ httpRequestsMockHelpers.setLoadDataStreamsResponse([
+ createDataStreamPayload('dataStream1'),
+ createDataStreamPayload('dataStream2'),
+ ]);
+
+ testBed = await setup();
+
+ await act(async () => {
+ const { component } = testBed;
+
+ await nextTick();
+ component.update();
+ });
+ });
+
+ test('navigates to the data stream in the Data Streams tab', async () => {
+ const { table, actions } = testBed;
+
+ await actions.clickDataStreamAt(0);
+
+ expect(table.getMetaData('dataStreamTable').tableCellsValues).toEqual([
+ ['dataStream1', '1', '@timestamp', '1'],
+ ]);
+ });
+ });
+
describe('index detail panel with % character in index name', () => {
const indexName = 'test%';
beforeEach(async () => {
diff --git a/x-pack/plugins/index_management/common/index.ts b/x-pack/plugins/index_management/common/index.ts
index 3792e322ae40b..4ad428744deab 100644
--- a/x-pack/plugins/index_management/common/index.ts
+++ b/x-pack/plugins/index_management/common/index.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { PLUGIN, API_BASE_PATH, CREATE_LEGACY_TEMPLATE_BY_DEFAULT } from './constants';
+export { PLUGIN, API_BASE_PATH, CREATE_LEGACY_TEMPLATE_BY_DEFAULT, BASE_PATH } from './constants';
export { getTemplateParameter } from './lib';
diff --git a/x-pack/plugins/index_management/common/lib/component_template_serialization.test.ts b/x-pack/plugins/index_management/common/lib/component_template_serialization.test.ts
new file mode 100644
index 0000000000000..eaa7f24017a2f
--- /dev/null
+++ b/x-pack/plugins/index_management/common/lib/component_template_serialization.test.ts
@@ -0,0 +1,94 @@
+/*
+ * 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 { deserializeComponentTemplate } from './component_template_serialization';
+
+describe('deserializeComponentTemplate', () => {
+ test('deserializes a component template', () => {
+ expect(
+ deserializeComponentTemplate(
+ {
+ name: 'my_component_template',
+ component_template: {
+ version: 1,
+ _meta: {
+ serialization: {
+ id: 10,
+ class: 'MyComponentTemplate',
+ },
+ description: 'set number of shards to one',
+ },
+ template: {
+ settings: {
+ number_of_shards: 1,
+ },
+ mappings: {
+ _source: {
+ enabled: false,
+ },
+ properties: {
+ host_name: {
+ type: 'keyword',
+ },
+ created_at: {
+ type: 'date',
+ format: 'EEE MMM dd HH:mm:ss Z yyyy',
+ },
+ },
+ },
+ },
+ },
+ },
+ [
+ {
+ name: 'my_index_template',
+ index_template: {
+ index_patterns: ['foo'],
+ template: {
+ settings: {
+ number_of_replicas: 2,
+ },
+ },
+ composed_of: ['my_component_template'],
+ },
+ },
+ ]
+ )
+ ).toEqual({
+ name: 'my_component_template',
+ version: 1,
+ _meta: {
+ serialization: {
+ id: 10,
+ class: 'MyComponentTemplate',
+ },
+ description: 'set number of shards to one',
+ },
+ template: {
+ settings: {
+ number_of_shards: 1,
+ },
+ mappings: {
+ _source: {
+ enabled: false,
+ },
+ properties: {
+ host_name: {
+ type: 'keyword',
+ },
+ created_at: {
+ type: 'date',
+ format: 'EEE MMM dd HH:mm:ss Z yyyy',
+ },
+ },
+ },
+ },
+ _kbnMeta: {
+ usedBy: ['my_index_template'],
+ },
+ });
+ });
+});
diff --git a/x-pack/plugins/index_management/common/lib/component_template_serialization.ts b/x-pack/plugins/index_management/common/lib/component_template_serialization.ts
new file mode 100644
index 0000000000000..0db81bf81d300
--- /dev/null
+++ b/x-pack/plugins/index_management/common/lib/component_template_serialization.ts
@@ -0,0 +1,86 @@
+/*
+ * 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 {
+ TemplateFromEs,
+ ComponentTemplateFromEs,
+ ComponentTemplateDeserialized,
+ ComponentTemplateListItem,
+} from '../types';
+
+const hasEntries = (data: object = {}) => Object.entries(data).length > 0;
+
+/**
+ * Normalize a list of component templates to a map where each key
+ * is a component template name, and the value is an array of index templates name using it
+ *
+ * @example
+ *
+ {
+ "comp-1": [
+ "template-1",
+ "template-2"
+ ],
+ "comp2": [
+ "template-1",
+ "template-2"
+ ]
+ }
+ *
+ * @param indexTemplatesEs List of component templates
+ */
+
+const getIndexTemplatesToUsedBy = (indexTemplatesEs: TemplateFromEs[]) => {
+ return indexTemplatesEs.reduce((acc, item) => {
+ if (item.index_template.composed_of) {
+ item.index_template.composed_of.forEach((component) => {
+ acc[component] = acc[component] ? [...acc[component], item.name] : [item.name];
+ });
+ }
+ return acc;
+ }, {} as { [key: string]: string[] });
+};
+
+export function deserializeComponentTemplate(
+ componentTemplateEs: ComponentTemplateFromEs,
+ indexTemplatesEs: TemplateFromEs[]
+) {
+ const { name, component_template: componentTemplate } = componentTemplateEs;
+ const { template, _meta, version } = componentTemplate;
+
+ const indexTemplatesToUsedBy = getIndexTemplatesToUsedBy(indexTemplatesEs);
+
+ const deserializedComponentTemplate: ComponentTemplateDeserialized = {
+ name,
+ template,
+ version,
+ _meta,
+ _kbnMeta: {
+ usedBy: indexTemplatesToUsedBy[name] || [],
+ },
+ };
+
+ return deserializedComponentTemplate;
+}
+
+export function deserializeComponenTemplateList(
+ componentTemplateEs: ComponentTemplateFromEs,
+ indexTemplatesEs: TemplateFromEs[]
+) {
+ const { name, component_template: componentTemplate } = componentTemplateEs;
+ const { template } = componentTemplate;
+
+ const indexTemplatesToUsedBy = getIndexTemplatesToUsedBy(indexTemplatesEs);
+
+ const componentTemplateListItem: ComponentTemplateListItem = {
+ name,
+ usedBy: indexTemplatesToUsedBy[name] || [],
+ hasSettings: hasEntries(template.settings),
+ hasMappings: hasEntries(template.mappings),
+ hasAliases: hasEntries(template.aliases),
+ };
+
+ return componentTemplateListItem;
+}
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
new file mode 100644
index 0000000000000..9d267210a6b31
--- /dev/null
+++ b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts
@@ -0,0 +1,21 @@
+/*
+ * 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 { DataStream, DataStreamFromEs } from '../types';
+
+export function deserializeDataStreamList(dataStreamsFromEs: DataStreamFromEs[]): DataStream[] {
+ return dataStreamsFromEs.map(({ name, timestamp_field, indices, generation }) => ({
+ name,
+ timeStampField: timestamp_field,
+ indices: indices.map(
+ ({ index_name, index_uuid }: { index_name: string; index_uuid: string }) => ({
+ name: index_name,
+ uuid: index_uuid,
+ })
+ ),
+ generation,
+ }));
+}
diff --git a/x-pack/plugins/index_management/common/lib/index.ts b/x-pack/plugins/index_management/common/lib/index.ts
index 16eb544c56a08..fce4d8ccc2502 100644
--- a/x-pack/plugins/index_management/common/lib/index.ts
+++ b/x-pack/plugins/index_management/common/lib/index.ts
@@ -3,6 +3,9 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+
+export { deserializeDataStreamList } from './data_stream_serialization';
+
export {
deserializeLegacyTemplateList,
deserializeTemplateList,
@@ -11,3 +14,8 @@ export {
} from './template_serialization';
export { getTemplateParameter } from './utils';
+
+export {
+ deserializeComponentTemplate,
+ deserializeComponenTemplateList,
+} from './component_template_serialization';
diff --git a/x-pack/plugins/index_management/common/types/component_templates.ts b/x-pack/plugins/index_management/common/types/component_templates.ts
new file mode 100644
index 0000000000000..bc7ebdc2753dd
--- /dev/null
+++ b/x-pack/plugins/index_management/common/types/component_templates.ts
@@ -0,0 +1,39 @@
+/*
+ * 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 { IndexSettings } from './indices';
+import { Aliases } from './aliases';
+import { Mappings } from './mappings';
+
+export interface ComponentTemplateSerialized {
+ template: {
+ settings?: IndexSettings;
+ aliases?: Aliases;
+ mappings?: Mappings;
+ };
+ version?: number;
+ _meta?: { [key: string]: any };
+}
+
+export interface ComponentTemplateDeserialized extends ComponentTemplateSerialized {
+ name: string;
+ _kbnMeta: {
+ usedBy: string[];
+ };
+}
+
+export interface ComponentTemplateFromEs {
+ name: string;
+ component_template: ComponentTemplateSerialized;
+}
+
+export interface ComponentTemplateListItem {
+ name: string;
+ usedBy: string[];
+ hasMappings: boolean;
+ hasAliases: boolean;
+ hasSettings: boolean;
+}
diff --git a/x-pack/plugins/index_management/common/types/data_streams.ts b/x-pack/plugins/index_management/common/types/data_streams.ts
new file mode 100644
index 0000000000000..5b743296d868b
--- /dev/null
+++ b/x-pack/plugins/index_management/common/types/data_streams.ts
@@ -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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export interface DataStreamFromEs {
+ name: string;
+ timestamp_field: string;
+ indices: DataStreamIndexFromEs[];
+ generation: number;
+}
+
+export interface DataStreamIndexFromEs {
+ index_name: string;
+ index_uuid: string;
+}
+
+export interface DataStream {
+ name: string;
+ timeStampField: string;
+ indices: DataStreamIndex[];
+ generation: number;
+}
+
+export interface DataStreamIndex {
+ name: string;
+ uuid: string;
+}
diff --git a/x-pack/plugins/index_management/common/types/index.ts b/x-pack/plugins/index_management/common/types/index.ts
index b467f020978a5..c4ba60573d430 100644
--- a/x-pack/plugins/index_management/common/types/index.ts
+++ b/x-pack/plugins/index_management/common/types/index.ts
@@ -11,3 +11,7 @@ export * from './indices';
export * from './mappings';
export * from './templates';
+
+export { DataStreamFromEs, DataStream, DataStreamIndex } from './data_streams';
+
+export * from './component_templates';
diff --git a/x-pack/plugins/index_management/common/types/templates.ts b/x-pack/plugins/index_management/common/types/templates.ts
index f113aa44d058f..006a2d9dea8f2 100644
--- a/x-pack/plugins/index_management/common/types/templates.ts
+++ b/x-pack/plugins/index_management/common/types/templates.ts
@@ -49,6 +49,11 @@ export interface TemplateDeserialized {
};
}
+export interface TemplateFromEs {
+ name: string;
+ index_template: TemplateSerialized;
+}
+
/**
* Interface for the template list in our UI table
* we don't include the mappings, settings and aliases
diff --git a/x-pack/plugins/index_management/public/application/app.tsx b/x-pack/plugins/index_management/public/application/app.tsx
index 10bbe3ced64da..92197bee30c88 100644
--- a/x-pack/plugins/index_management/public/application/app.tsx
+++ b/x-pack/plugins/index_management/public/application/app.tsx
@@ -5,10 +5,12 @@
*/
import React, { useEffect } from 'react';
+
import { Router, Switch, Route, Redirect } from 'react-router-dom';
import { ScopedHistory } from 'kibana/public';
+
import { UIM_APP_LOAD } from '../../common/constants';
-import { IndexManagementHome } from './sections/home';
+import { IndexManagementHome, homeSections } from './sections/home';
import { TemplateCreate } from './sections/template_create';
import { TemplateClone } from './sections/template_clone';
import { TemplateEdit } from './sections/template_edit';
@@ -29,10 +31,10 @@ export const App = ({ history }: { history: ScopedHistory }) => {
// Export this so we can test it with a different router.
export const AppWithoutRouter = () => (
-
-
-
-
+
+
+
+
);
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts
new file mode 100644
index 0000000000000..830cc0ee6a980
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts
@@ -0,0 +1,174 @@
+/*
+ * 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 { act } from 'react-dom/test-utils';
+
+import { setupEnvironment, pageHelpers } from './helpers';
+import { ComponentTemplateListTestBed } from './helpers/component_template_list.helpers';
+import { API_BASE_PATH } from '../../../../../../common/constants';
+import { ComponentTemplateListItem } from '../../types';
+
+const { setup } = pageHelpers.componentTemplateList;
+
+jest.mock('ui/i18n', () => {
+ const I18nContext = ({ children }: any) => children;
+ return { I18nContext };
+});
+
+describe('', () => {
+ const { server, httpRequestsMockHelpers } = setupEnvironment();
+ let testBed: ComponentTemplateListTestBed;
+
+ afterAll(() => {
+ server.restore();
+ });
+
+ beforeEach(async () => {
+ await act(async () => {
+ testBed = await setup();
+ });
+
+ testBed.component.update();
+ });
+
+ describe('With component templates', () => {
+ const componentTemplate1: ComponentTemplateListItem = {
+ name: 'test_component_template_1',
+ hasMappings: true,
+ hasAliases: true,
+ hasSettings: true,
+ usedBy: [],
+ };
+
+ const componentTemplate2: ComponentTemplateListItem = {
+ name: 'test_component_template_2',
+ hasMappings: true,
+ hasAliases: true,
+ hasSettings: true,
+ usedBy: ['test_index_template_1'],
+ };
+
+ const componentTemplates = [componentTemplate1, componentTemplate2];
+
+ httpRequestsMockHelpers.setLoadComponentTemplatesResponse(componentTemplates);
+
+ test('should render the list view', async () => {
+ const { table } = testBed;
+
+ // Verify table content
+ const { tableCellsValues } = table.getMetaData('componentTemplatesTable');
+ tableCellsValues.forEach((row, i) => {
+ const { name, usedBy } = componentTemplates[i];
+ const usedByText = usedBy.length === 0 ? 'Not in use' : usedBy.length.toString();
+
+ expect(row).toEqual(['', name, usedByText, '', '', '', '']);
+ });
+ });
+
+ test('should reload the component templates data', async () => {
+ const { component, actions } = testBed;
+ const totalRequests = server.requests.length;
+
+ await act(async () => {
+ actions.clickReloadButton();
+ });
+
+ component.update();
+
+ expect(server.requests.length).toBe(totalRequests + 1);
+ expect(server.requests[server.requests.length - 1].url).toBe(
+ `${API_BASE_PATH}/component_templates`
+ );
+ });
+
+ test('should delete a component template', async () => {
+ const { actions, component } = testBed;
+ const { name: componentTemplateName } = componentTemplate1;
+
+ await act(async () => {
+ actions.clickDeleteActionAt(0);
+ });
+
+ // We need to read the document "body" as the modal is added there and not inside
+ // the component DOM tree.
+ const modal = document.body.querySelector(
+ '[data-test-subj="deleteComponentTemplatesConfirmation"]'
+ );
+ const confirmButton: HTMLButtonElement | null = modal!.querySelector(
+ '[data-test-subj="confirmModalConfirmButton"]'
+ );
+
+ expect(modal).not.toBe(null);
+ expect(modal!.textContent).toContain('Delete component template');
+
+ httpRequestsMockHelpers.setDeleteComponentTemplateResponse({
+ itemsDeleted: [componentTemplateName],
+ errors: [],
+ });
+
+ await act(async () => {
+ confirmButton!.click();
+ });
+
+ component.update();
+
+ const deleteRequest = server.requests[server.requests.length - 2];
+
+ expect(deleteRequest.method).toBe('DELETE');
+ expect(deleteRequest.url).toBe(
+ `${API_BASE_PATH}/component_templates/${componentTemplateName}`
+ );
+ expect(deleteRequest.status).toEqual(200);
+ });
+ });
+
+ describe('No component templates', () => {
+ beforeEach(async () => {
+ httpRequestsMockHelpers.setLoadComponentTemplatesResponse([]);
+
+ await act(async () => {
+ testBed = await setup();
+ });
+
+ testBed.component.update();
+ });
+
+ test('should display an empty prompt', async () => {
+ const { exists, find } = testBed;
+
+ expect(exists('sectionLoading')).toBe(false);
+ expect(exists('emptyList')).toBe(true);
+ expect(find('emptyList.title').text()).toEqual('Start by creating a component template');
+ });
+ });
+
+ describe('Error handling', () => {
+ beforeEach(async () => {
+ const error = {
+ status: 500,
+ error: 'Internal server error',
+ message: 'Internal server error',
+ };
+
+ httpRequestsMockHelpers.setLoadComponentTemplatesResponse(undefined, { body: error });
+
+ await act(async () => {
+ testBed = await setup();
+ });
+
+ testBed.component.update();
+ });
+
+ test('should render an error message if error fetching component templates', async () => {
+ const { exists, find } = testBed;
+
+ expect(exists('componentTemplatesLoadError')).toBe(true);
+ expect(find('componentTemplatesLoadError').text()).toContain(
+ 'Unable to load component templates. Try again.'
+ );
+ });
+ });
+});
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts
new file mode 100644
index 0000000000000..8fb4dcff0bcea
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts
@@ -0,0 +1,95 @@
+/*
+ * 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 { act } from 'react-dom/test-utils';
+
+import { BASE_PATH } from '../../../../../../../common';
+import {
+ registerTestBed,
+ TestBed,
+ TestBedConfig,
+ findTestSubject,
+ nextTick,
+} from '../../../../../../../../../test_utils';
+import { WithAppDependencies } from './setup_environment';
+import { ComponentTemplateList } from '../../../component_template_list';
+
+const testBedConfig: TestBedConfig = {
+ memoryRouter: {
+ initialEntries: [`${BASE_PATH}component_templates`],
+ componentRoutePath: `${BASE_PATH}component_templates`,
+ },
+ doMountAsync: true,
+};
+
+const initTestBed = registerTestBed(WithAppDependencies(ComponentTemplateList), testBedConfig);
+
+export type ComponentTemplateListTestBed = TestBed & {
+ actions: ReturnType;
+};
+
+const createActions = (testBed: TestBed) => {
+ const { find } = testBed;
+
+ /**
+ * User Actions
+ */
+ const clickReloadButton = () => {
+ find('reloadButton').simulate('click');
+ };
+
+ const clickComponentTemplateAt = async (index: number) => {
+ const { component, table, router } = testBed;
+ const { rows } = table.getMetaData('componentTemplatesTable');
+ const componentTemplateLink = findTestSubject(
+ rows[index].reactWrapper,
+ 'componentTemplateDetailsLink'
+ );
+
+ await act(async () => {
+ const { href } = componentTemplateLink.props();
+ router.navigateTo(href!);
+ await nextTick();
+ component.update();
+ });
+ };
+
+ const clickDeleteActionAt = (index: number) => {
+ const { table } = testBed;
+
+ const { rows } = table.getMetaData('componentTemplatesTable');
+ const deleteButton = findTestSubject(rows[index].reactWrapper, 'deleteComponentTemplateButton');
+
+ deleteButton.simulate('click');
+ };
+
+ return {
+ clickReloadButton,
+ clickComponentTemplateAt,
+ clickDeleteActionAt,
+ };
+};
+
+export const setup = async (): Promise => {
+ const testBed = await initTestBed();
+
+ return {
+ ...testBed,
+ actions: createActions(testBed),
+ };
+};
+
+export type ComponentTemplateTestSubjects =
+ | 'componentTemplatesTable'
+ | 'componentTemplateDetails'
+ | 'componentTemplateDetails.title'
+ | 'deleteComponentTemplatesConfirmation'
+ | 'emptyList'
+ | 'emptyList.title'
+ | 'sectionLoading'
+ | 'componentTemplatesLoadError'
+ | 'deleteComponentTemplateButton'
+ | 'reloadButton';
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/http_requests.ts
new file mode 100644
index 0000000000000..8473041ee0af3
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/http_requests.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 sinon, { SinonFakeServer } from 'sinon';
+import { API_BASE_PATH } from '../../../../../../../common';
+
+// Register helpers to mock HTTP Requests
+const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
+ const setLoadComponentTemplatesResponse = (response?: any[], error?: any) => {
+ const status = error ? error.status || 400 : 200;
+ const body = error ? error.body : response;
+
+ server.respondWith('GET', `${API_BASE_PATH}/component_templates`, [
+ status,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify(body),
+ ]);
+ };
+
+ const setDeleteComponentTemplateResponse = (response?: object) => {
+ server.respondWith('DELETE', `${API_BASE_PATH}/component_templates/:name`, [
+ 200,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify(response),
+ ]);
+ };
+
+ return {
+ setLoadComponentTemplatesResponse,
+ setDeleteComponentTemplateResponse,
+ };
+};
+
+export const init = () => {
+ const server = sinon.fakeServer.create();
+ server.respondImmediately = true;
+
+ // Define default response for unhandled requests.
+ // We make requests to APIs which don't impact the component under test, e.g. UI metric telemetry,
+ // and we can mock them all with a 200 instead of mocking each one individually.
+ server.respondWith([200, {}, 'DefaultMockedResponse']);
+
+ const httpRequestsMockHelpers = registerHttpRequestMockHelpers(server);
+
+ return {
+ server,
+ httpRequestsMockHelpers,
+ };
+};
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/index.ts
new file mode 100644
index 0000000000000..c1d75b3c2dd9b
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/index.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.
+ */
+
+import { setup as componentTemplatesListSetup } from './component_template_list.helpers';
+
+export { nextTick, getRandomString, findTestSubject } from '../../../../../../../../../test_utils';
+
+export { setupEnvironment } from './setup_environment';
+
+export const pageHelpers = {
+ componentTemplateList: { setup: componentTemplatesListSetup },
+};
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx
new file mode 100644
index 0000000000000..c0aeb70166b5b
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/setup_environment.tsx
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+/* eslint-disable @kbn/eslint/no-restricted-paths */
+import React from 'react';
+import axios from 'axios';
+import axiosXhrAdapter from 'axios/lib/adapters/xhr';
+
+import { HttpSetup } from 'kibana/public';
+import { BASE_PATH, API_BASE_PATH } from '../../../../../../../common/constants';
+import {
+ notificationServiceMock,
+ docLinksServiceMock,
+} from '../../../../../../../../../../src/core/public/mocks';
+
+import { init as initHttpRequests } from './http_requests';
+import { ComponentTemplatesProvider } from '../../../component_templates_context';
+
+const mockHttpClient = axios.create({ adapter: axiosXhrAdapter });
+
+const appDependencies = {
+ httpClient: (mockHttpClient as unknown) as HttpSetup,
+ apiBasePath: API_BASE_PATH,
+ appBasePath: BASE_PATH,
+ trackMetric: () => {},
+ docLinks: docLinksServiceMock.createStartContract(),
+ toasts: notificationServiceMock.createSetupContract().toasts,
+};
+
+export const setupEnvironment = () => {
+ const { server, httpRequestsMockHelpers } = initHttpRequests();
+
+ return {
+ server,
+ httpRequestsMockHelpers,
+ };
+};
+
+export const WithAppDependencies = (Comp: any) => (props: any) => (
+
+
+
+);
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx
new file mode 100644
index 0000000000000..41fa608ef538b
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/component_template_list.tsx
@@ -0,0 +1,75 @@
+/*
+ * 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, { useState, useEffect } from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import { SectionLoading } from '../shared_imports';
+import { useComponentTemplatesContext } from '../component_templates_context';
+import { UIM_COMPONENT_TEMPLATE_LIST_LOAD } from '../constants';
+
+import { EmptyPrompt } from './empty_prompt';
+import { ComponentTable } from './table';
+import { LoadError } from './error';
+import { ComponentTemplatesDeleteModal } from './delete_modal';
+
+export const ComponentTemplateList: React.FunctionComponent = () => {
+ const { api, trackMetric } = useComponentTemplatesContext();
+
+ const { data, isLoading, error, sendRequest } = api.useLoadComponentTemplates();
+
+ const [componentTemplatesToDelete, setComponentTemplatesToDelete] = useState([]);
+
+ // 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 0000000000000..bf621065842b5
--- /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 0000000000000..edd9f77cbf635
--- /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 0000000000000..aa37b9ce5767c
--- /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/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/index.ts b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/index.ts
similarity index 79%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/index.ts
rename to x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/index.ts
index 3a25359373aa6..84ee48d14bb8c 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/index.ts
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/index.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { CreateAnalyticsFlyout } from './create_analytics_flyout';
+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 0000000000000..2d9557e64e6e7
--- /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 0000000000000..6f5f5bdebd6d0
--- /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 0000000000000..3e763119fa9fb
--- /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/security_solution/server/lib/detection_engine/routes/schemas/create_rules_bulk_schema.ts b/x-pack/plugins/index_management/public/application/components/component_templates/index.ts
similarity index 59%
rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/schemas/create_rules_bulk_schema.ts
rename to x-pack/plugins/index_management/public/application/components/component_templates/index.ts
index bcc4475f2d9f0..e0219ec71787f 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/schemas/create_rules_bulk_schema.ts
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/index.ts
@@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import Joi from 'joi';
+export { ComponentTemplatesProvider } from './component_templates_context';
-import { createRulesSchema } from './create_rules_schema';
+export { ComponentTemplateList } from './component_template_list';
-export const createRulesBulkSchema = Joi.array().items(createRulesSchema);
+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 0000000000000..351e83c6c0cb5
--- /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 0000000000000..dc27dadf0b807
--- /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 0000000000000..9a91312f83294
--- /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 0000000000000..97ffa4d875ecb
--- /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/infra/public/routers/index.ts b/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts
similarity index 54%
rename from x-pack/plugins/infra/public/routers/index.ts
rename to x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts
index 71ab2613d8dc1..863b00b353c49 100644
--- a/x-pack/plugins/infra/public/routers/index.ts
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/shared_imports.ts
@@ -3,13 +3,13 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { History } from 'history';
-export * from './logs_router';
-export * from './metrics_router';
-
-interface RouterProps {
- history: History;
-}
-
-export type AppRouter = React.FC;
+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 0000000000000..0aab3b6b0a94a
--- /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 e6d836c0d0501..7ec25ed5583b7 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 8da556cc81fcc..5d1096c9ee24e 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/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
new file mode 100644
index 0000000000000..a6c8b83a05f98
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx
@@ -0,0 +1,104 @@
+/*
+ * 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 { FormattedMessage } from '@kbn/i18n/react';
+import {
+ EuiFlyout,
+ EuiFlyoutHeader,
+ EuiTitle,
+ EuiFlyoutBody,
+ EuiFlyoutFooter,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButtonEmpty,
+} from '@elastic/eui';
+
+import { SectionLoading, SectionError, Error } from '../../../../components';
+import { useLoadDataStream } from '../../../../services/api';
+
+interface Props {
+ dataStreamName: string;
+ onClose: () => void;
+}
+
+/**
+ * NOTE: This currently isn't in use by data_stream_list.tsx because it doesn't contain any
+ * information that doesn't already exist in the table. We'll use it once we add additional
+ * info, e.g. storage size, docs count.
+ */
+export const DataStreamDetailPanel: React.FunctionComponent = ({
+ dataStreamName,
+ onClose,
+}) => {
+ const { error, data: dataStream, isLoading } = useLoadDataStream(dataStreamName);
+
+ let content;
+
+ if (isLoading) {
+ content = (
+
+
+
+ );
+ } else if (error) {
+ content = (
+
+ }
+ error={error as Error}
+ data-test-subj="sectionError"
+ />
+ );
+ } else if (dataStream) {
+ content = {JSON.stringify(dataStream)};
+ }
+
+ return (
+
+
+
+
+ {dataStreamName}
+
+
+
+
+ {content}
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout_wrapper/index.ts b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/index.ts
similarity index 74%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout_wrapper/index.ts
rename to x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/index.ts
index c8e7a958f6d42..3f45267c032ed 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout_wrapper/index.ts
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/index.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { CreateAnalyticsFlyoutWrapper } from './create_analytics_flyout_wrapper';
+export { DataStreamDetailPanel } from './data_stream_detail_panel';
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
new file mode 100644
index 0000000000000..951c4a0d7f3c3
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx
@@ -0,0 +1,127 @@
+/*
+ * 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 { RouteComponentProps } from 'react-router-dom';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
+import { EuiTitle, EuiText, EuiSpacer, EuiEmptyPrompt, EuiLink } from '@elastic/eui';
+import { ScopedHistory } from 'kibana/public';
+
+import { reactRouterNavigate } from '../../../../shared_imports';
+import { SectionError, SectionLoading, Error } from '../../../components';
+import { useLoadDataStreams } from '../../../services/api';
+import { DataStreamTable } from './data_stream_table';
+
+interface MatchParams {
+ dataStreamName?: string;
+}
+
+export const DataStreamList: React.FunctionComponent> = ({
+ match: {
+ params: { dataStreamName },
+ },
+ history,
+}) => {
+ const { error, isLoading, data: dataStreams, sendRequest: reload } = useLoadDataStreams();
+
+ let content;
+
+ if (isLoading) {
+ content = (
+
+
+
+ );
+ } else if (error) {
+ content = (
+
+ }
+ error={error as Error}
+ />
+ );
+ } else if (Array.isArray(dataStreams) && dataStreams.length === 0) {
+ content = (
+
+
+
+ }
+ body={
+
+
+ {i18n.translate('xpack.idxMgmt.dataStreamList.emptyPrompt.getStartedLink', {
+ defaultMessage: 'composable index template',
+ })}
+
+ ),
+ }}
+ />
+
+ }
+ data-test-subj="emptyPrompt"
+ />
+ );
+ } else if (Array.isArray(dataStreams) && dataStreams.length > 0) {
+ content = (
+ <>
+ {/* TODO: Add a switch for toggling on data streams created by Ingest Manager */}
+
+
+
+
+
+
+
+
+
+
+ {/* TODO: Implement this once we have something to put in here, e.g. storage size, docs count */}
+ {/* dataStreamName && (
+ {
+ history.push('/data_streams');
+ }}
+ />
+ )*/}
+ >
+ );
+ }
+
+ return {content}
;
+};
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
new file mode 100644
index 0000000000000..54b215e561b46
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx
@@ -0,0 +1,139 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { EuiInMemoryTable, EuiBasicTableColumn, EuiButton, EuiLink } from '@elastic/eui';
+import { ScopedHistory } from 'kibana/public';
+
+import { DataStream } from '../../../../../../common/types';
+import { reactRouterNavigate } from '../../../../../shared_imports';
+import { encodePathForReactRouter } from '../../../../services/routing';
+
+interface Props {
+ dataStreams?: DataStream[];
+ reload: () => {};
+ history: ScopedHistory;
+ filters?: string;
+}
+
+export const DataStreamTable: React.FunctionComponent = ({
+ dataStreams,
+ reload,
+ history,
+ filters,
+}) => {
+ const columns: Array> = [
+ {
+ field: 'name',
+ name: i18n.translate('xpack.idxMgmt.dataStreamList.table.nameColumnTitle', {
+ defaultMessage: 'Name',
+ }),
+ truncateText: true,
+ sortable: true,
+ // TODO: Render as a link to open the detail panel
+ },
+ {
+ field: 'indices',
+ name: i18n.translate('xpack.idxMgmt.dataStreamList.table.indicesColumnTitle', {
+ defaultMessage: 'Indices',
+ }),
+ truncateText: true,
+ sortable: true,
+ render: (indices: DataStream['indices'], dataStream) => (
+
+ {indices.length}
+
+ ),
+ },
+ {
+ field: 'timeStampField',
+ name: i18n.translate('xpack.idxMgmt.dataStreamList.table.timeStampFieldColumnTitle', {
+ defaultMessage: 'Timestamp field',
+ }),
+ truncateText: true,
+ sortable: true,
+ },
+ {
+ field: 'generation',
+ name: i18n.translate('xpack.idxMgmt.dataStreamList.table.generationFieldColumnTitle', {
+ defaultMessage: 'Generation',
+ }),
+ truncateText: true,
+ sortable: true,
+ },
+ ];
+
+ const pagination = {
+ initialPageSize: 20,
+ pageSizeOptions: [10, 20, 50],
+ };
+
+ const sorting = {
+ sort: {
+ field: 'name',
+ direction: 'asc',
+ },
+ } as const;
+
+ const searchConfig = {
+ query: filters,
+ box: {
+ incremental: true,
+ },
+ toolsLeft: undefined /* TODO: Actions menu */,
+ toolsRight: [
+
+
+ ,
+ ],
+ };
+
+ return (
+ <>
+ ({
+ 'data-test-subj': 'row',
+ })}
+ cellProps={() => ({
+ 'data-test-subj': 'cell',
+ })}
+ data-test-subj="dataStreamTable"
+ message={
+
+ }
+ />
+ >
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/index.ts b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/index.ts
similarity index 79%
rename from x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/index.ts
rename to x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/index.ts
index 20b96a3668e4b..3922ca5c1d50c 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/index.ts
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/index.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { CreateAnalyticsForm } from './create_analytics_form';
+export { DataStreamTable } from './data_stream_table';
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/index.ts b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/index.ts
new file mode 100644
index 0000000000000..e2f588cc2a0fb
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_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 { DataStreamList } from './data_stream_list';
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 9d4331d742a25..51deaf42cc72c 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
@@ -19,11 +19,25 @@ import {
EuiTitle,
} from '@elastic/eui';
import { documentationService } from '../../services/documentation';
+import { DataStreamList } from './data_stream_list';
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',
+ DataStreams = 'data_streams',
+ IndexTemplates = 'templates',
+ ComponentTemplates = 'component_templates',
+}
+
+export const homeSections = [
+ Section.Indices,
+ Section.DataStreams,
+ Section.IndexTemplates,
+ Section.ComponentTemplates,
+];
interface MatchParams {
section: Section;
@@ -37,11 +51,20 @@ export const IndexManagementHome: React.FunctionComponent {
const tabs = [
{
- id: 'indices' as Section,
+ id: Section.Indices,
name: ,
},
{
- id: 'templates' as Section,
+ id: Section.DataStreams,
+ name: (
+
+ ),
+ },
+ {
+ id: Section.IndexTemplates,
name: (
),
},
+ {
+ id: Section.ComponentTemplates,
+ name: (
+
+ ),
+ },
];
const onSectionChange = (newSection: Section) => {
@@ -106,13 +138,19 @@ 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 3a29ef4e58555..b53910748aedb 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/public/application/sections/home/index_list/detail_panel/detail_panel.container.d.ts b/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/detail_panel.container.d.ts
new file mode 100644
index 0000000000000..6f37b4dc486a7
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/detail_panel.container.d.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 declare function DetailPanel(props: any): any;
diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/index.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/index.ts
similarity index 100%
rename from x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/index.js
rename to x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/index.ts
diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_list.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_list.tsx
similarity index 71%
rename from x-pack/plugins/index_management/public/application/sections/home/index_list/index_list.js
rename to x-pack/plugins/index_management/public/application/sections/home/index_list/index_list.tsx
index df5ca7f837d10..f81221238c536 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_list.js
+++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_list.tsx
@@ -5,14 +5,16 @@
*/
import React from 'react';
+import { RouteComponentProps } from 'react-router-dom';
+
import { DetailPanel } from './detail_panel';
import { IndexTable } from './index_table';
-export function IndexList() {
+export const IndexList: React.FunctionComponent = ({ history }) => {
return (
-
+
);
-}
+};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index.ts
similarity index 100%
rename from x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index.js
rename to x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index.ts
diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.container.d.ts b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.container.d.ts
new file mode 100644
index 0000000000000..35ddfc4813617
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.container.d.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 declare function IndexTable(props: any): any;
diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js
index f33d486520a29..c3acff087146a 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js
+++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js
@@ -37,8 +37,10 @@ import {
} from '@elastic/eui';
import { UIM_SHOW_DETAILS_CLICK } from '../../../../../../common/constants';
+import { reactRouterNavigate } from '../../../../../shared_imports';
import { REFRESH_RATE_INDEX_LIST } from '../../../../constants';
import { healthToColor } from '../../../../services';
+import { encodePathForReactRouter } from '../../../../services/routing';
import { AppContextConsumer } from '../../../../app_context';
import { renderBadges } from '../../../../lib/render_badges';
import { NoMatch, PageErrorForbidden } from '../../../../components';
@@ -117,6 +119,7 @@ export class IndexTable extends Component {
}
}
}
+
componentWillUnmount() {
clearInterval(this.interval);
}
@@ -146,11 +149,14 @@ export class IndexTable extends Component {
const newIsSortAscending = sortField === column ? !isSortAscending : true;
sortChanged(column, newIsSortAscending);
};
+
renderFilterError() {
const { filterError } = this.state;
+
if (!filterError) {
return;
}
+
return (
<>
@@ -169,6 +175,7 @@ export class IndexTable extends Component {
>
);
}
+
onFilterChanged = ({ query, error }) => {
if (error) {
this.setState({ filterError: error });
@@ -177,6 +184,7 @@ export class IndexTable extends Component {
this.setState({ filterError: null });
}
};
+
getFilters = (extensionsService) => {
const { allIndices } = this.props;
return extensionsService.filters.reduce((accum, filterExtension) => {
@@ -184,6 +192,7 @@ export class IndexTable extends Component {
return [...accum, ...filtersToAdd];
}, []);
};
+
toggleAll = () => {
const allSelected = this.areAllItemsSelected();
if (allSelected) {
@@ -243,7 +252,8 @@ export class IndexTable extends Component {
}
buildRowCell(fieldName, value, index, appServices) {
- const { openDetailPanel, filterChanged } = this.props;
+ const { openDetailPanel, filterChanged, history } = this.props;
+
if (fieldName === 'health') {
return {value};
} else if (fieldName === 'name') {
@@ -261,7 +271,19 @@ export class IndexTable extends Component {
{renderBadges(index, filterChanged, appServices.extensionsService)}
);
+ } else if (fieldName === 'data_stream') {
+ return (
+
+ {value}
+
+ );
}
+
return value;
}
@@ -480,12 +502,14 @@ export class IndexTable extends Component {
+
{(indicesLoading && allIndices.length === 0) || indicesError ? null : (
{extensionsService.toggles.map((toggle) => {
return this.renderToggleControl(toggle);
})}
+
+
+
{this.renderBanners(extensionsService)}
+
{indicesError && this.renderError()}
+
{atLeastOneItemSelected ? (
@@ -523,6 +551,7 @@ export class IndexTable extends Component {
/>
) : null}
+
{(indicesLoading && allIndices.length === 0) || indicesError ? null : (
@@ -572,11 +601,14 @@ export class IndexTable extends Component {
)}
+
{this.renderFilterError()}
+
+
{indices.length > 0 ? (
-
+
+
{this.buildHeader()}
+
{this.buildRows(services)}
) : (
emptyState
)}
+
+
{indices.length > 0 ? this.renderPager() : null}
);
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx
index ec2956973d4f6..807229fb36267 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_details/template_details.tsx
@@ -38,7 +38,7 @@ import {
Error,
} from '../../../../../components';
import { useLoadIndexTemplate } from '../../../../../services/api';
-import { decodePath } from '../../../../../services/routing';
+import { decodePathFromReactRouter } from '../../../../../services/routing';
import { SendRequestResponse } from '../../../../../../shared_imports';
import { useServices } from '../../../../../app_context';
import { TabSummary, TabMappings, TabSettings, TabAliases } from '../../template_details/tabs';
@@ -107,7 +107,7 @@ export const LegacyTemplateDetails: React.FunctionComponent = ({
reload,
}) => {
const { uiMetricService } = useServices();
- const decodedTemplateName = decodePath(templateName);
+ const decodedTemplateName = decodePathFromReactRouter(templateName);
const { error, data: templateDetails, isLoading } = useLoadIndexTemplate(
decodedTemplateName,
isLegacy
diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_table/template_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_table/template_table.tsx
index 92fedd5d68f00..edce05018ce39 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_table/template_table.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/legacy_templates/template_table/template_table.tsx
@@ -9,12 +9,12 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiInMemoryTable, EuiIcon, EuiButton, EuiLink, EuiBasicTableColumn } from '@elastic/eui';
import { ScopedHistory } from 'kibana/public';
-import { reactRouterNavigate } from '../../../../../../../../../../src/plugins/kibana_react/public';
+import { SendRequestResponse, reactRouterNavigate } from '../../../../../../shared_imports';
import { TemplateListItem } from '../../../../../../../common';
import { UIM_TEMPLATE_SHOW_DETAILS_CLICK } from '../../../../../../../common/constants';
import { TemplateDeleteModal } from '../../../../../components';
+import { encodePathForReactRouter } from '../../../../../services/routing';
import { useServices } from '../../../../../app_context';
-import { SendRequestResponse } from '../../../../../../shared_imports';
interface Props {
templates: TemplateListItem[];
@@ -52,7 +52,7 @@ export const LegacyTemplateTable: React.FunctionComponent = ({
{...reactRouterNavigate(
history,
{
- pathname: `/templates/${encodeURIComponent(encodeURIComponent(name))}`,
+ pathname: `/templates/${encodePathForReactRouter(name)}`,
search: `legacy=${Boolean(item._kbnMeta.isLegacy)}`,
},
() => uiMetricService.trackMetric('click', UIM_TEMPLATE_SHOW_DETAILS_CLICK)
diff --git a/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx b/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx
index 8bdd230f89952..82835c56a3877 100644
--- a/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/template_clone/template_clone.tsx
@@ -11,7 +11,7 @@ import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui';
import { TemplateDeserialized } from '../../../../common';
import { TemplateForm, SectionLoading, SectionError, Error } from '../../components';
import { breadcrumbService } from '../../services/breadcrumbs';
-import { decodePath, getTemplateDetailsLink } from '../../services/routing';
+import { decodePathFromReactRouter, getTemplateDetailsLink } from '../../services/routing';
import { saveTemplate, useLoadIndexTemplate } from '../../services/api';
import { getIsLegacyFromQueryParams } from '../../lib/index_templates';
@@ -26,7 +26,7 @@ export const TemplateClone: React.FunctionComponent {
- const decodedTemplateName = decodePath(name);
+ const decodedTemplateName = decodePathFromReactRouter(name);
const isLegacy = getIsLegacyFromQueryParams(location);
const [isSaving, setIsSaving] = useState(false);
diff --git a/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx b/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx
index d3e539989bc96..7cacb5ee97a60 100644
--- a/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/template_edit/template_edit.tsx
@@ -11,7 +11,7 @@ import { EuiPageBody, EuiPageContent, EuiTitle, EuiSpacer, EuiCallOut } from '@e
import { TemplateDeserialized } from '../../../../common';
import { breadcrumbService } from '../../services/breadcrumbs';
import { useLoadIndexTemplate, updateTemplate } from '../../services/api';
-import { decodePath, getTemplateDetailsLink } from '../../services/routing';
+import { decodePathFromReactRouter, getTemplateDetailsLink } from '../../services/routing';
import { SectionLoading, SectionError, TemplateForm, Error } from '../../components';
import { getIsLegacyFromQueryParams } from '../../lib/index_templates';
@@ -26,7 +26,7 @@ export const TemplateEdit: React.FunctionComponent {
- const decodedTemplateName = decodePath(name);
+ const decodedTemplateName = decodePathFromReactRouter(name);
const isLegacy = getIsLegacyFromQueryParams(location);
const [isSaving, setIsSaving] = useState(false);
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 3961942b83ea3..5ad84395d24c2 100644
--- a/x-pack/plugins/index_management/public/application/services/api.ts
+++ b/x-pack/plugins/index_management/public/application/services/api.ts
@@ -31,14 +31,12 @@ import {
UIM_TEMPLATE_UPDATE,
UIM_TEMPLATE_CLONE,
} from '../../../common/constants';
-
+import { TemplateDeserialized, TemplateListItem, DataStream } from '../../../common';
+import { IndexMgmtMetricsType } from '../../types';
import { TAB_SETTINGS, TAB_MAPPING, TAB_STATS } from '../constants';
-
import { useRequest, sendRequest } from './use_request';
import { httpService } from './http';
import { UiMetricService } from './ui_metric';
-import { TemplateDeserialized, TemplateListItem } from '../../../common';
-import { IndexMgmtMetricsType } from '../../types';
// Temporary hack to provide the uiMetricService instance to this file.
// TODO: Refactor and export an ApiService instance through the app dependencies context
@@ -48,6 +46,21 @@ export const setUiMetricService = (_uiMetricService: UiMetricService({
+ path: `${API_BASE_PATH}/data_streams`,
+ method: 'get',
+ });
+}
+
+// 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)}`,
+ method: 'get',
+ });
+}
+
export async function loadIndices() {
const response = await httpService.httpClient.get(`${API_BASE_PATH}/indices`);
return response.data ? response.data : response;
@@ -211,14 +224,14 @@ export async function loadIndexData(type: string, indexName: string) {
export function useLoadIndexTemplates() {
return useRequest<{ templates: TemplateListItem[]; legacyTemplates: TemplateListItem[] }>({
- path: `${API_BASE_PATH}/index-templates`,
+ path: `${API_BASE_PATH}/index_templates`,
method: 'get',
});
}
export async function deleteTemplates(templates: Array<{ name: string; isLegacy?: boolean }>) {
const result = sendRequest({
- path: `${API_BASE_PATH}/delete-index-templates`,
+ path: `${API_BASE_PATH}/delete_index_templates`,
method: 'post',
body: { templates },
});
@@ -232,7 +245,7 @@ export async function deleteTemplates(templates: Array<{ name: string; isLegacy?
export function useLoadIndexTemplate(name: TemplateDeserialized['name'], isLegacy?: boolean) {
return useRequest({
- path: `${API_BASE_PATH}/index-templates/${encodeURIComponent(name)}`,
+ path: `${API_BASE_PATH}/index_templates/${encodeURIComponent(name)}`,
method: 'get',
query: {
legacy: isLegacy,
@@ -242,7 +255,7 @@ export function useLoadIndexTemplate(name: TemplateDeserialized['name'], isLegac
export async function saveTemplate(template: TemplateDeserialized, isClone?: boolean) {
const result = await sendRequest({
- path: `${API_BASE_PATH}/index-templates`,
+ path: `${API_BASE_PATH}/index_templates`,
method: 'post',
body: JSON.stringify(template),
});
@@ -257,7 +270,7 @@ export async function saveTemplate(template: TemplateDeserialized, isClone?: boo
export async function updateTemplate(template: TemplateDeserialized) {
const { name } = template;
const result = await sendRequest({
- path: `${API_BASE_PATH}/index-templates/${encodeURIComponent(name)}`,
+ path: `${API_BASE_PATH}/index_templates/${encodeURIComponent(name)}`,
method: 'put',
body: JSON.stringify(template),
});
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 a999c58f5bb42..2a895196189d0 100644
--- a/x-pack/plugins/index_management/public/application/services/routing.ts
+++ b/x-pack/plugins/index_management/public/application/services/routing.ts
@@ -6,10 +6,8 @@
export const getTemplateListLink = () => `/templates`;
-// Need to add some additonal encoding/decoding logic to work with React Router
-// For background, see: https://github.com/ReactTraining/history/issues/505
export const getTemplateDetailsLink = (name: string, isLegacy?: boolean, withHash = false) => {
- const baseUrl = `/templates/${encodeURIComponent(encodeURIComponent(name))}`;
+ const baseUrl = `/templates/${encodePathForReactRouter(name)}`;
let url = withHash ? `#${baseUrl}` : baseUrl;
if (isLegacy) {
url = `${url}?legacy=${isLegacy}`;
@@ -18,18 +16,14 @@ export const getTemplateDetailsLink = (name: string, isLegacy?: boolean, withHas
};
export const getTemplateEditLink = (name: string, isLegacy?: boolean) => {
- return encodeURI(
- `/edit_template/${encodeURIComponent(encodeURIComponent(name))}?legacy=${isLegacy === true}`
- );
+ return encodeURI(`/edit_template/${encodePathForReactRouter(name)}?legacy=${isLegacy === true}`);
};
export const getTemplateCloneLink = (name: string, isLegacy?: boolean) => {
- return encodeURI(
- `/clone_template/${encodeURIComponent(encodeURIComponent(name))}?legacy=${isLegacy === true}`
- );
+ return encodeURI(`/clone_template/${encodePathForReactRouter(name)}?legacy=${isLegacy === true}`);
};
-export const decodePath = (pathname: string): string => {
+export const decodePathFromReactRouter = (pathname: string): string => {
let decodedPath;
try {
decodedPath = decodeURI(pathname);
@@ -39,3 +33,8 @@ export const decodePath = (pathname: string): string => {
}
return decodeURIComponent(decodedPath);
};
+
+// Need to add some additonal encoding/decoding logic to work with React Router
+// For background, see: https://github.com/ReactTraining/history/issues/505
+export const encodePathForReactRouter = (pathname: string): string =>
+ encodeURIComponent(encodeURIComponent(pathname));
diff --git a/x-pack/plugins/index_management/public/shared_imports.ts b/x-pack/plugins/index_management/public/shared_imports.ts
index 8942367261511..afd5a5cf650e1 100644
--- a/x-pack/plugins/index_management/public/shared_imports.ts
+++ b/x-pack/plugins/index_management/public/shared_imports.ts
@@ -31,3 +31,5 @@ export {
export { getFormRow, Field } from '../../../../src/plugins/es_ui_shared/static/forms/components';
export { isJSON } from '../../../../src/plugins/es_ui_shared/static/validators/string';
+
+export { reactRouterNavigate } from '../../../../src/plugins/kibana_react/public';
diff --git a/x-pack/plugins/index_management/server/client/elasticsearch.ts b/x-pack/plugins/index_management/server/client/elasticsearch.ts
index 65bd5411a249b..6b1bf47512b21 100644
--- a/x-pack/plugins/index_management/server/client/elasticsearch.ts
+++ b/x-pack/plugins/index_management/server/client/elasticsearch.ts
@@ -10,6 +10,47 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any)
Client.prototype.dataManagement = components.clientAction.namespaceFactory();
const dataManagement = Client.prototype.dataManagement.prototype;
+ // Data streams
+ dataManagement.getDataStreams = ca({
+ urls: [
+ {
+ fmt: '/_data_stream',
+ },
+ ],
+ 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({
+ urls: [
+ {
+ fmt: '/_data_stream/<%=name%>',
+ req: {
+ name: {
+ type: 'string',
+ },
+ },
+ },
+ ],
+ method: 'PUT',
+ });
+
+ dataManagement.deleteDataStream = ca({
+ urls: [
+ {
+ fmt: '/_data_stream/<%=name%>',
+ req: {
+ name: {
+ type: 'string',
+ },
+ },
+ },
+ ],
+ method: 'DELETE',
+ });
+
+ // Component templates
dataManagement.getComponentTemplates = ca({
urls: [
{
@@ -60,4 +101,43 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any)
],
method: 'DELETE',
});
+
+ // Composable index templates
+ dataManagement.getComposableIndexTemplates = ca({
+ urls: [
+ {
+ fmt: '/_index_template',
+ },
+ ],
+ method: 'GET',
+ });
+
+ dataManagement.saveComposableIndexTemplate = ca({
+ urls: [
+ {
+ fmt: '/_index_template/<%=name%>',
+ req: {
+ name: {
+ type: 'string',
+ },
+ },
+ },
+ ],
+ needBody: true,
+ method: 'PUT',
+ });
+
+ dataManagement.deleteComposableIndexTemplate = ca({
+ urls: [
+ {
+ fmt: '/_index_template/<%=name%>',
+ req: {
+ name: {
+ type: 'string',
+ },
+ },
+ },
+ ],
+ method: 'DELETE',
+ });
};
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 87aa64421624e..f6f8e7d63d370 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'
+ );
+
+ const { index_templates: indexTemplates } = await callAsCurrentUser(
+ 'dataManagement.getComposableIndexTemplates'
+ );
+
+ const body = componentTemplates.map((componentTemplate) => {
+ const deserializedComponentTemplateListItem = deserializeComponenTemplateList(
+ componentTemplate,
+ indexTemplates
+ );
+ return deserializedComponentTemplateListItem;
+ });
- return res.ok({ body: response.component_templates });
+ 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/plugins/security_solution/server/lib/detection_engine/routes/schemas/update_rules_bulk_schema.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/index.ts
similarity index 51%
rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/schemas/update_rules_bulk_schema.ts
rename to x-pack/plugins/index_management/server/routes/api/data_streams/index.ts
index 123ec2d5b7e15..56c514e30f242 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/schemas/update_rules_bulk_schema.ts
+++ b/x-pack/plugins/index_management/server/routes/api/data_streams/index.ts
@@ -4,8 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import Joi from 'joi';
+import { RouteDependencies } from '../../../types';
-import { updateRulesSchema } from './update_rules_schema';
+import { registerGetAllRoute } from './register_get_route';
-export const updateRulesBulkSchema = Joi.array().items(updateRulesSchema);
+export function registerDataStreamRoutes(dependencies: RouteDependencies) {
+ registerGetAllRoute(dependencies);
+}
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
new file mode 100644
index 0000000000000..9128556130bf4
--- /dev/null
+++ b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts
@@ -0,0 +1,34 @@
+/*
+ * 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 { deserializeDataStreamList } from '../../../../common/lib';
+import { RouteDependencies } from '../../../types';
+import { addBasePath } from '../index';
+
+export function registerGetAllRoute({ router, license, lib: { isEsError } }: RouteDependencies) {
+ router.get(
+ { path: addBasePath('/data_streams'), validate: false },
+ license.guardApiRoute(async (ctx, req, res) => {
+ const { callAsCurrentUser } = ctx.dataManagement!.client;
+
+ try {
+ const dataStreams = await callAsCurrentUser('dataManagement.getDataStreams');
+ const body = deserializeDataStreamList(dataStreams);
+
+ return res.ok({ body });
+ } catch (error) {
+ if (isEsError(error)) {
+ return res.customError({
+ statusCode: error.statusCode,
+ body: error,
+ });
+ }
+
+ return res.internalError({ body: error });
+ }
+ })
+ );
+}
diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts
index 26e74847e3e05..e0d92b3800785 100644
--- a/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts
+++ b/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts
@@ -16,7 +16,7 @@ const bodySchema = templateSchema;
export function registerCreateRoute({ router, license, lib }: RouteDependencies) {
router.post(
- { path: addBasePath('/index-templates'), validate: { body: bodySchema } },
+ { path: addBasePath('/index_templates'), validate: { body: bodySchema } },
license.guardApiRoute(async (ctx, req, res) => {
const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client;
const template = req.body as TemplateDeserialized;
diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts
index b5cc00ad6d8cc..1527af12a92a4 100644
--- a/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts
+++ b/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts
@@ -24,7 +24,7 @@ const bodySchema = schema.object({
export function registerDeleteRoute({ router, license }: RouteDependencies) {
router.post(
{
- path: addBasePath('/delete-index-templates'),
+ path: addBasePath('/delete_index_templates'),
validate: { body: bodySchema },
},
license.guardApiRoute(async (ctx, req, res) => {
diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts
index 12ec005258a62..ae5f7802a8408 100644
--- a/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts
+++ b/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts
@@ -16,7 +16,7 @@ import { addBasePath } from '../index';
export function registerGetAllRoute({ router, license }: RouteDependencies) {
router.get(
- { path: addBasePath('/index-templates'), validate: false },
+ { path: addBasePath('/index_templates'), validate: false },
license.guardApiRoute(async (ctx, req, res) => {
const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client;
const managedTemplatePrefix = await getManagedTemplatePrefix(callAsCurrentUser);
@@ -55,7 +55,7 @@ const querySchema = schema.object({
export function registerGetOneRoute({ router, license, lib }: RouteDependencies) {
router.get(
{
- path: addBasePath('/index-templates/{name}'),
+ path: addBasePath('/index_templates/{name}'),
validate: { params: paramsSchema, query: querySchema },
},
license.guardApiRoute(async (ctx, req, res) => {
diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts
index 5b2a0d8722e46..7e9c3174d0591 100644
--- a/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts
+++ b/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts
@@ -19,7 +19,7 @@ const paramsSchema = schema.object({
export function registerUpdateRoute({ router, license, lib }: RouteDependencies) {
router.put(
{
- path: addBasePath('/index-templates/{name}'),
+ path: addBasePath('/index_templates/{name}'),
validate: { body: bodySchema, params: paramsSchema },
},
license.guardApiRoute(async (ctx, req, res) => {
diff --git a/x-pack/plugins/index_management/server/routes/index.ts b/x-pack/plugins/index_management/server/routes/index.ts
index 1e5aaf8087624..202e6919f7b13 100644
--- a/x-pack/plugins/index_management/server/routes/index.ts
+++ b/x-pack/plugins/index_management/server/routes/index.ts
@@ -6,6 +6,7 @@
import { RouteDependencies } from '../types';
+import { registerDataStreamRoutes } from './api/data_streams';
import { registerIndicesRoutes } from './api/indices';
import { registerTemplateRoutes } from './api/templates';
import { registerMappingRoute } from './api/mapping';
@@ -15,6 +16,7 @@ import { registerComponentTemplateRoutes } from './api/component_templates';
export class ApiRoutes {
setup(dependencies: RouteDependencies) {
+ registerDataStreamRoutes(dependencies);
registerIndicesRoutes(dependencies);
registerTemplateRoutes(dependencies);
registerSettingsRoutes(dependencies);
diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx
index 7a71bb68bc54f..d5d61733e8717 100644
--- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx
+++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx
@@ -384,3 +384,7 @@ export const Expressions: React.FC = (props) => {
>
);
};
+
+// required for dynamic import
+// eslint-disable-next-line import/no-default-export
+export default Expressions;
diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx
index 2d9524ca158c8..da342f0a45420 100644
--- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx
+++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx
@@ -5,7 +5,6 @@
*/
import { i18n } from '@kbn/i18n';
-import { isNumber } from 'lodash';
import {
MetricExpressionParams,
Comparator,
@@ -106,3 +105,5 @@ export function validateMetricThreshold({
return validationResult;
}
+
+const isNumber = (value: unknown): value is number => typeof value === 'number';
diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts
index a40cb1eaec50c..6a999a86c99d1 100644
--- a/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts
+++ b/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts
@@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
+import React from 'react';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types';
-import { Expressions } from './components/expression';
import { validateMetricThreshold } from './components/validation';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../server/lib/alerting/metric_threshold/types';
@@ -18,7 +18,7 @@ export function createMetricThresholdAlertType(): AlertTypeModel {
defaultMessage: 'Metric threshold',
}),
iconClass: 'bell',
- alertParamsExpression: Expressions,
+ alertParamsExpression: React.lazy(() => import('./components/expression')),
validate: validateMetricThreshold,
defaultActionMessage: i18n.translate(
'xpack.infra.metrics.alerting.threshold.defaultActionMessage',
diff --git a/x-pack/plugins/infra/public/apps/common_providers.tsx b/x-pack/plugins/infra/public/apps/common_providers.tsx
new file mode 100644
index 0000000000000..facb0f1539a10
--- /dev/null
+++ b/x-pack/plugins/infra/public/apps/common_providers.tsx
@@ -0,0 +1,47 @@
+/*
+ * 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 { CoreStart } from 'kibana/public';
+import { ApolloClient } from 'apollo-client';
+import {
+ useUiSetting$,
+ KibanaContextProvider,
+} from '../../../../../src/plugins/kibana_react/public';
+import { TriggersActionsProvider } from '../utils/triggers_actions_context';
+import { ClientPluginDeps } from '../types';
+import { TriggersAndActionsUIPublicPluginStart } from '../../../triggers_actions_ui/public';
+import { ApolloClientContext } from '../utils/apollo_context';
+import { EuiThemeProvider } from '../../../observability/public';
+import { NavigationWarningPromptProvider } from '../utils/navigation_warning_prompt';
+
+export const CommonInfraProviders: React.FC<{
+ apolloClient: ApolloClient<{}>;
+ triggersActionsUI: TriggersAndActionsUIPublicPluginStart;
+}> = ({ apolloClient, children, triggersActionsUI }) => {
+ const [darkMode] = useUiSetting$('theme:darkMode');
+
+ return (
+
+
+