diff --git a/x-pack/plugins/monitoring/public/application/index.tsx b/x-pack/plugins/monitoring/public/application/index.tsx
index e5b6da13d768c..220067077531b 100644
--- a/x-pack/plugins/monitoring/public/application/index.tsx
+++ b/x-pack/plugins/monitoring/public/application/index.tsx
@@ -33,6 +33,8 @@ import { ElasticsearchIndexPage } from './pages/elasticsearch/index_page';
import { ElasticsearchIndexAdvancedPage } from './pages/elasticsearch/index_advanced_page';
import { ElasticsearchNodePage } from './pages/elasticsearch/node_page';
import { ElasticsearchNodeAdvancedPage } from './pages/elasticsearch/node_advanced_page';
+import { ElasticsearchCcrPage } from './pages/elasticsearch/ccr_page';
+import { ElasticsearchCcrShardPage } from './pages/elasticsearch/ccr_shard_page';
import { MonitoringTimeContainer } from './hooks/use_monitoring_time';
import { BreadcrumbContainer } from './hooks/use_breadcrumbs';
import {
@@ -96,6 +98,20 @@ const MonitoringApp: React.FC<{
/>
{/* ElasticSearch Views */}
+
+
+
+
= ({ clusters }) => {
+ const globalState = useContext(GlobalStateContext);
+ const { services } = useKibana<{ data: any }>();
+
+ const clusterUuid = globalState.cluster_uuid;
+ const cluster = find(clusters, {
+ cluster_uuid: clusterUuid,
+ }) as any;
+ const ccs = globalState.ccs;
+ const [data, setData] = useState({} as any);
+
+ const title = i18n.translate('xpack.monitoring.elasticsearch.ccr.title', {
+ defaultMessage: 'Elasticsearch - Ccr',
+ });
+
+ const pageTitle = i18n.translate('xpack.monitoring.elasticsearch.ccr.pageTitle', {
+ defaultMessage: 'Elasticsearch Ccr',
+ });
+
+ const getPageData = useCallback(async () => {
+ const bounds = services.data?.query.timefilter.timefilter.getBounds();
+ const url = `../api/monitoring/v1/clusters/${clusterUuid}/elasticsearch/ccr`;
+
+ const response = await services.http?.fetch(url, {
+ method: 'POST',
+ body: JSON.stringify({
+ ccs,
+ timeRange: {
+ min: bounds.min.toISOString(),
+ max: bounds.max.toISOString(),
+ },
+ }),
+ });
+
+ setData(response);
+ }, [ccs, clusterUuid, services.data?.query.timefilter.timefilter, services.http]);
+
+ return (
+
+ (
+
+ {flyoutComponent}
+
+ {bottomBarComponent}
+
+ )}
+ />
+
+ );
+};
diff --git a/x-pack/plugins/monitoring/public/application/pages/elasticsearch/ccr_shard_page.tsx b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/ccr_shard_page.tsx
new file mode 100644
index 0000000000000..56de1cace3546
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/application/pages/elasticsearch/ccr_shard_page.tsx
@@ -0,0 +1,92 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import React, { useContext, useState, useCallback } from 'react';
+import { useParams } from 'react-router-dom';
+import { get } from 'lodash';
+import { i18n } from '@kbn/i18n';
+import { PageTemplate } from '../page_template';
+import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
+import { GlobalStateContext } from '../../global_state_context';
+// @ts-ignore
+import { CcrShardReact } from '../../../components/elasticsearch/ccr_shard';
+import { ComponentProps } from '../../route_init';
+import { SetupModeRenderer } from '../../setup_mode/setup_mode_renderer';
+import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
+
+interface SetupModeProps {
+ setupMode: any;
+ flyoutComponent: any;
+ bottomBarComponent: any;
+}
+
+export const ElasticsearchCcrShardPage: React.FC = ({ clusters }) => {
+ const globalState = useContext(GlobalStateContext);
+ const { services } = useKibana<{ data: any }>();
+ const { index, shardId }: { index: string; shardId: string } = useParams();
+
+ const clusterUuid = globalState.cluster_uuid;
+ const ccs = globalState.ccs;
+ const [data, setData] = useState({} as any);
+
+ const title = i18n.translate('xpack.monitoring.elasticsearch.ccr.shard.title', {
+ defaultMessage: 'Elasticsearch - Ccr - Shard',
+ });
+
+ const pageTitle = i18n.translate('xpack.monitoring.elasticsearch.ccr.shard.pageTitle', {
+ defaultMessage: 'Elasticsearch Ccr Shard - Index: {followerIndex} Shard: {shardId}',
+ values: {
+ followerIndex: get(data, 'stat.follower.index', get(data, 'stat.follower_index')),
+ shardId: get(data, 'stat.follower.shard.number', get(data, 'stat.shard_id')),
+ },
+ });
+
+ const instance = i18n.translate('xpack.monitoring.elasticsearch.ccr.shard.instanceTitle', {
+ defaultMessage: 'Index: {followerIndex} Shard: {shardId}',
+ values: {
+ followerIndex: get(data, 'stat.follower_index'),
+ shardId: get(data, 'stat.shard_id'),
+ },
+ });
+
+ const getPageData = useCallback(async () => {
+ const bounds = services.data?.query.timefilter.timefilter.getBounds();
+ const url = `../api/monitoring/v1/clusters/${clusterUuid}/elasticsearch/ccr/${index}/shard/${shardId}`;
+
+ const response = await services.http?.fetch(url, {
+ method: 'POST',
+ body: JSON.stringify({
+ ccs,
+ timeRange: {
+ min: bounds.min.toISOString(),
+ max: bounds.max.toISOString(),
+ },
+ }),
+ });
+
+ setData(response);
+ }, [ccs, clusterUuid, services.data?.query.timefilter.timefilter, services.http, index, shardId]);
+
+ return (
+
+ (
+
+ {flyoutComponent}
+
+ {bottomBarComponent}
+
+ )}
+ />
+
+ );
+};
diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard_react.test.js.snap b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard_react.test.js.snap
new file mode 100644
index 0000000000000..9302c86a222b1
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard_react.test.js.snap
@@ -0,0 +1,185 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CcrShardReact that is renders an exception properly 1`] = `
+
+
+
+`;
+
+exports[`CcrShardReact that it renders normally 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ id="ccrLatestStat"
+ initialIsOpen={false}
+ isLoading={false}
+ isLoadingMessage={false}
+ paddingSize="l"
+ >
+
+
+ September 27, 2018 9:32:09 AM
+
+
+
+
+ {
+ "read_exceptions": [],
+ "follower_global_checkpoint": 3049,
+ "follower_index": "follower",
+ "follower_max_seq_no": 3049,
+ "last_requested_seq_no": 3049,
+ "leader_global_checkpoint": 3049,
+ "leader_index": "leader",
+ "leader_max_seq_no": 3049,
+ "mapping_version": 2,
+ "number_of_concurrent_reads": 1,
+ "number_of_concurrent_writes": 0,
+ "number_of_failed_bulk_operations": 0,
+ "failed_read_requests": 0,
+ "operations_written": 3050,
+ "number_of_queued_writes": 0,
+ "number_of_successful_bulk_operations": 3050,
+ "number_of_successful_fetches": 3050,
+ "operations_received": 3050,
+ "shard_id": 0,
+ "time_since_last_read_millis": 9402,
+ "total_fetch_time_millis": 44128980,
+ "total_index_time_millis": 41827,
+ "total_transferred_bytes": 234156
+}
+
+
+
+
+`;
diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard_react.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard_react.js
new file mode 100644
index 0000000000000..65586d602c85e
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard_react.js
@@ -0,0 +1,145 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { Fragment } from 'react';
+import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
+import {
+ EuiPage,
+ EuiPageBody,
+ EuiPanel,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+ EuiTitle,
+ EuiBasicTable,
+ EuiCodeBlock,
+ EuiTextColor,
+ EuiHorizontalRule,
+ EuiAccordion,
+} from '@elastic/eui';
+import { MonitoringTimeseriesContainer } from '../../chart';
+import { Status } from './status';
+import { formatDateTimeLocal } from '../../../../common/formatting';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
+import { AlertsCallout } from '../../../alerts/callout';
+
+export function CcrShardReact(props) {
+ const { services } = useKibana();
+ const timezone = services.uiSettings?.get('dateFormat:tz');
+ const { metrics, stat, timestamp, oldestStat, formattedLeader, alerts } = props;
+ const renderCharts = () => {
+ const seriesToShow = [metrics.ccr_sync_lag_ops, metrics.ccr_sync_lag_time];
+
+ const charts = seriesToShow.map((data, index) => (
+
+
+
+
+
+ ));
+
+ return {charts};
+ };
+
+ const renderErrors = () => {
+ if (stat.read_exceptions && stat.read_exceptions.length > 0) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+ return null;
+ };
+
+ const renderLatestStat = () => {
+ return (
+
+
+
+
+
+ }
+ paddingSize="l"
+ >
+
+
+ {formatDateTimeLocal(timestamp, timezone)}
+
+
+ {JSON.stringify(stat, null, 2)}
+
+
+ );
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {renderErrors()}
+ {renderCharts()}
+
+ {renderLatestStat()}
+
+
+ );
+}
diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard_react.test.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard_react.test.js
new file mode 100644
index 0000000000000..afd289a33457a
--- /dev/null
+++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/ccr_shard_react.test.js
@@ -0,0 +1,82 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { shallow } from 'enzyme';
+import { CcrShardReact } from './ccr_shard_react';
+
+jest.mock('../../../legacy_shims', () => {
+ return {
+ Legacy: {
+ shims: { getAngularInjector: () => ({ get: () => ({ get: () => 'utc' }) }) },
+ },
+ };
+});
+
+jest.mock('../../chart', () => ({
+ MonitoringTimeseriesContainer: () => 'MonitoringTimeseriesContainer',
+}));
+
+describe('CcrShardReact', () => {
+ const props = {
+ formattedLeader: 'leader on remote',
+ metrics: [],
+ stat: {
+ read_exceptions: [],
+ follower_global_checkpoint: 3049,
+ follower_index: 'follower',
+ follower_max_seq_no: 3049,
+ last_requested_seq_no: 3049,
+ leader_global_checkpoint: 3049,
+ leader_index: 'leader',
+ leader_max_seq_no: 3049,
+ mapping_version: 2,
+ number_of_concurrent_reads: 1,
+ number_of_concurrent_writes: 0,
+ number_of_failed_bulk_operations: 0,
+ failed_read_requests: 0,
+ operations_written: 3050,
+ number_of_queued_writes: 0,
+ number_of_successful_bulk_operations: 3050,
+ number_of_successful_fetches: 3050,
+ operations_received: 3050,
+ shard_id: 0,
+ time_since_last_read_millis: 9402,
+ total_fetch_time_millis: 44128980,
+ total_index_time_millis: 41827,
+ total_transferred_bytes: 234156,
+ },
+ oldestStat: {
+ failed_read_requests: 0,
+ operations_written: 2976,
+ },
+ timestamp: '2018-09-27T13:32:09.412Z',
+ };
+
+ test('that it renders normally', () => {
+ const component = shallow();
+ expect(component).toMatchSnapshot();
+ });
+
+ test('that is renders an exception properly', () => {
+ const localProps = {
+ ...props,
+ stat: {
+ ...props.stat,
+ read_exceptions: [
+ {
+ type: 'something_is_wrong',
+ reason: 'not sure but something happened',
+ },
+ ],
+ },
+ };
+
+ const component = shallow();
+ expect(component.find('EuiPanel').get(0)).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/index.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/index.js
index 4cfd362b8ab0c..036a21e9b8a72 100644
--- a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/index.js
+++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/index.js
@@ -6,3 +6,4 @@
*/
export { CcrShard } from './ccr_shard';
+export { CcrShardReact } from './ccr_shard_react';
diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js
index 2f88b2da8e09b..c6884c541f415 100644
--- a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js
+++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js
@@ -14,7 +14,8 @@ import { AlertsStatus } from '../../../alerts/status';
export function Status({ stat, formattedLeader, oldestStat, alerts = {} }) {
const followerIndex = stat.follower_index || get(stat, 'follower.index');
- const shardId = stat.shard_id || get(stat, 'follower.shard.number');
+ const shardId =
+ typeof stat.shard_id === 'number' ? stat.shard_id : get(stat, 'follower.shard.number');
const operationsReceived = stat.operations_written || get(stat, 'follower.operations_written');
const failedFetches = stat.failed_read_requests || get(stat, 'requests.failed.read.count');
const oldestOperationsReceived =