CCR tab pages (#113988)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Sandra G 2021-10-06 10:14:24 -04:00 committed by GitHub
parent e594deed5a
commit 1eceef4fc8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 604 additions and 1 deletions

View file

@ -40,6 +40,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 { LogStashOverviewPage } from './pages/logstash/overview';
@ -106,6 +108,20 @@ const MonitoringApp: React.FC<{
/>
{/* ElasticSearch Views */}
<RouteInit
path="/elasticsearch/ccr/:index/shard/:shardId"
component={ElasticsearchCcrShardPage}
codePaths={[CODE_PATH_ELASTICSEARCH]}
fetchAllClusters={false}
/>
<RouteInit
path="/elasticsearch/ccr"
component={ElasticsearchCcrPage}
codePaths={[CODE_PATH_ELASTICSEARCH]}
fetchAllClusters={false}
/>
<RouteInit
path="/elasticsearch/indices/:index/advanced"
component={ElasticsearchIndexAdvancedPage}

View file

@ -0,0 +1,81 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { find } from 'lodash';
import { ElasticsearchTemplate } from './elasticsearch_template';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
import { GlobalStateContext } from '../../global_state_context';
// @ts-ignore
import { Ccr } from '../../../components/elasticsearch/ccr';
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 ElasticsearchCcrPage: React.FC<ComponentProps> = ({ 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 (
<ElasticsearchTemplate
title={title}
pageTitle={pageTitle}
getPageData={getPageData}
data-test-subj="elasticsearchCcrPage"
cluster={cluster}
>
<SetupModeRenderer
render={({ flyoutComponent, bottomBarComponent }: SetupModeProps) => (
<SetupModeContext.Provider value={{ setupModeSupported: true }}>
{flyoutComponent}
<Ccr data={data.data} alerts={{}} />
{bottomBarComponent}
</SetupModeContext.Provider>
)}
/>
</ElasticsearchTemplate>
);
};

View file

@ -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<ComponentProps> = ({ 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 (
<PageTemplate
title={title}
pageTitle={pageTitle}
getPageData={getPageData}
data-test-subj="elasticsearchCcrShardPage"
>
<SetupModeRenderer
instance={instance}
render={({ flyoutComponent, bottomBarComponent }: SetupModeProps) => (
<SetupModeContext.Provider value={{ setupModeSupported: true }}>
{flyoutComponent}
<CcrShardReact {...data} alerts={{}} />
{bottomBarComponent}
</SetupModeContext.Provider>
)}
/>
</PageTemplate>
);
};

View file

@ -0,0 +1,185 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CcrShardReact that is renders an exception properly 1`] = `
<EuiPanel>
<Status
formattedLeader="leader on remote"
oldestStat={
Object {
"failed_read_requests": 0,
"operations_written": 2976,
}
}
stat={
Object {
"failed_read_requests": 0,
"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,
"number_of_queued_writes": 0,
"number_of_successful_bulk_operations": 3050,
"number_of_successful_fetches": 3050,
"operations_received": 3050,
"operations_written": 3050,
"read_exceptions": Array [
Object {
"reason": "not sure but something happened",
"type": "something_is_wrong",
},
],
"shard_id": 0,
"time_since_last_read_millis": 9402,
"total_fetch_time_millis": 44128980,
"total_index_time_millis": 41827,
"total_transferred_bytes": 234156,
}
}
/>
</EuiPanel>
`;
exports[`CcrShardReact that it renders normally 1`] = `
<EuiPage>
<EuiPageBody>
<EuiPanel>
<Status
formattedLeader="leader on remote"
oldestStat={
Object {
"failed_read_requests": 0,
"operations_written": 2976,
}
}
stat={
Object {
"failed_read_requests": 0,
"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,
"number_of_queued_writes": 0,
"number_of_successful_bulk_operations": 3050,
"number_of_successful_fetches": 3050,
"operations_received": 3050,
"operations_written": 3050,
"read_exceptions": Array [],
"shard_id": 0,
"time_since_last_read_millis": 9402,
"total_fetch_time_millis": 44128980,
"total_index_time_millis": 41827,
"total_transferred_bytes": 234156,
}
}
/>
</EuiPanel>
<EuiSpacer
size="m"
/>
<AlertsCallout />
<EuiSpacer
size="m"
/>
<EuiFlexGroup
wrap={true}
>
<EuiFlexItem
key="0"
style={
Object {
"minWidth": "45%",
}
}
>
<EuiPanel>
<MonitoringTimeseriesContainer />
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem
key="1"
style={
Object {
"minWidth": "45%",
}
}
>
<EuiPanel>
<MonitoringTimeseriesContainer />
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
<EuiHorizontalRule />
<EuiAccordion
arrowDisplay="left"
buttonContent={
<EuiTitle>
<h2>
<FormattedMessage
defaultMessage="Advanced"
id="xpack.monitoring.elasticsearch.ccrShard.latestStateAdvancedButtonLabel"
values={Object {}}
/>
</h2>
</EuiTitle>
}
id="ccrLatestStat"
initialIsOpen={false}
isLoading={false}
isLoadingMessage={false}
paddingSize="l"
>
<EuiTitle
size="s"
>
<h2>
September 27, 2018 9:32:09 AM
</h2>
</EuiTitle>
<EuiHorizontalRule />
<EuiCodeBlock
language="json"
>
{
"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
}
</EuiCodeBlock>
</EuiAccordion>
</EuiPageBody>
</EuiPage>
`;

View file

@ -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) => (
<EuiFlexItem style={{ minWidth: '45%' }} key={index}>
<EuiPanel>
<MonitoringTimeseriesContainer series={data} />
</EuiPanel>
</EuiFlexItem>
));
return <Fragment>{charts}</Fragment>;
};
const renderErrors = () => {
if (stat.read_exceptions && stat.read_exceptions.length > 0) {
return (
<Fragment>
<EuiPanel>
<EuiTitle size="s" color="danger">
<h3>
<EuiTextColor color="danger">
<FormattedMessage
id="xpack.monitoring.elasticsearch.ccrShard.errorsTableTitle"
defaultMessage="Errors"
/>
</EuiTextColor>
</h3>
</EuiTitle>
<EuiSpacer size="s" />
<EuiBasicTable
items={stat.read_exceptions}
columns={[
{
name: i18n.translate(
'xpack.monitoring.elasticsearch.ccrShard.errorsTable.typeColumnTitle',
{
defaultMessage: 'Type',
}
),
field: 'exception.type',
},
{
name: i18n.translate(
'xpack.monitoring.elasticsearch.ccrShard.errorsTable.reasonColumnTitle',
{
defaultMessage: 'Reason',
}
),
field: 'exception.reason',
width: '75%',
},
]}
/>
</EuiPanel>
<EuiHorizontalRule />
</Fragment>
);
}
return null;
};
const renderLatestStat = () => {
return (
<EuiAccordion
id="ccrLatestStat"
buttonContent={
<EuiTitle>
<h2>
<FormattedMessage
id="xpack.monitoring.elasticsearch.ccrShard.latestStateAdvancedButtonLabel"
defaultMessage="Advanced"
/>
</h2>
</EuiTitle>
}
paddingSize="l"
>
<Fragment>
<EuiTitle size="s">
<h2>{formatDateTimeLocal(timestamp, timezone)}</h2>
</EuiTitle>
<EuiHorizontalRule />
<EuiCodeBlock language="json">{JSON.stringify(stat, null, 2)}</EuiCodeBlock>
</Fragment>
</EuiAccordion>
);
};
return (
<EuiPage>
<EuiPageBody>
<EuiPanel>
<Status
stat={stat}
formattedLeader={formattedLeader}
oldestStat={oldestStat}
alerts={alerts}
/>
</EuiPanel>
<EuiSpacer size="m" />
<AlertsCallout alerts={alerts} />
<EuiSpacer size="m" />
{renderErrors()}
<EuiFlexGroup wrap>{renderCharts()}</EuiFlexGroup>
<EuiHorizontalRule />
{renderLatestStat()}
</EuiPageBody>
</EuiPage>
);
}

View file

@ -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(<CcrShardReact {...props} />);
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(<CcrShardReact {...localProps} />);
expect(component.find('EuiPanel').get(0)).toMatchSnapshot();
});
});

View file

@ -6,3 +6,4 @@
*/
export { CcrShard } from './ccr_shard';
export { CcrShardReact } from './ccr_shard_react';

View file

@ -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 =