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 =