From 145808f52b178210c2f32bb5cd3a311b65ed6b2a Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Thu, 27 Dec 2018 01:14:56 -0800 Subject: [PATCH 1/5] Store for follower indices --- .../public/app/services/api.js | 8 ++++ .../public/app/store/action_types.js | 5 +++ .../app/store/actions/follower_index.js | 38 +++++++++++++++++++ .../public/app/store/actions/index.js | 2 +- .../public/app/store/reducers/api.js | 4 +- .../app/store/reducers/follower_index.js | 32 ++++++++++++++++ .../public/app/store/reducers/index.js | 2 + .../public/app/store/selectors/index.js | 16 ++++++++ .../server/client/elasticsearch_ccr.js | 14 +++++++ .../server/routes/api/follower_index.js | 30 +++++++++++++++ 10 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js create mode 100644 x-pack/plugins/cross_cluster_replication/public/app/store/reducers/follower_index.js diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/api.js b/x-pack/plugins/cross_cluster_replication/public/app/services/api.js index f6d4a8f084b73..5528bb5b50251 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/api.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/api.js @@ -48,3 +48,11 @@ export const deleteAutoFollowPattern = (id) => { return httpClient.delete(`${apiPrefix}/auto_follow_patterns/${ids}`).then(extractData); }; + +export const loadFollowerIndices = () => ( + httpClient.get(`${apiPrefix}/follower_indices`).then(extractData) +); + +export const getFollowerIndex = (id) => ( + httpClient.get(`${apiPrefix}/follower_indices/${encodeURIComponent(id)}`).then(extractData) +); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/action_types.js b/x-pack/plugins/cross_cluster_replication/public/app/store/action_types.js index c018e7b44ebcc..678393c4683ce 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/action_types.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/action_types.js @@ -18,3 +18,8 @@ export const AUTO_FOLLOW_PATTERN_GET = 'AUTO_FOLLOW_PATTERN_GET'; export const AUTO_FOLLOW_PATTERN_CREATE = 'AUTO_FOLLOW_PATTERN_CREATE'; export const AUTO_FOLLOW_PATTERN_UPDATE = 'AUTO_FOLLOW_PATTERN_UPDATE'; export const AUTO_FOLLOW_PATTERN_DELETE = 'AUTO_FOLLOW_PATTERN_DELETE'; + +// Follower index +export const FOLLOWER_INDEX_SELECT_DETAIL = 'FOLLOWER_INDEX_SELECT_DETAIL'; +export const FOLLOWER_INDEX_LOAD = 'FOLLOWER_INDEX_LOAD'; +export const FOLLOWER_INDEX_GET = 'AUTO_FOLLOW_PATTERN_GET'; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js new file mode 100644 index 0000000000000..1f6b3ffcb1a86 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js @@ -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 { SECTIONS, API_STATUS } from '../../constants'; +import { + loadFollowerIndices as loadFollowerIndicesRequest, + getFollowerIndex as getFollowerIndexRequest, +} from '../../services/api'; +import * as t from '../action_types'; +import { sendApiRequest } from './api'; + +const { FOLLOWER_INDEX: scope } = SECTIONS; + +export const selectDetailFollowerIndex = (id) => ({ + type: t.FOLLOWER_INDEX_SELECT_DETAIL, + payload: id +}); + +export const loadFollowerIndices = (isUpdating = false) => + sendApiRequest({ + label: t.FOLLOWER_INDEX_LOAD, + scope, + status: isUpdating ? API_STATUS.UPDATING : API_STATUS.LOADING, + handler: async () => ( + await loadFollowerIndicesRequest() + ), + }); + +export const getFollowerIndex = (id) => + sendApiRequest({ + label: t.FOLLOWER_INDEX_GET, + scope, + handler: async () => ( + await getFollowerIndexRequest(id) + ) + }); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/index.js index d45c328f5d830..79e81fb3b727a 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/index.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/index.js @@ -5,5 +5,5 @@ */ export * from './auto_follow_pattern'; - +export * from './follower_index'; export * from './api'; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/api.js b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/api.js index 55570deb4b7a6..f32a1862078a4 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/api.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/api.js @@ -10,11 +10,11 @@ import * as t from '../action_types'; export const initialState = { status: { [SECTIONS.AUTO_FOLLOW_PATTERN]: API_STATUS.IDLE, - [SECTIONS.INDEX_FOLLOWER]: API_STATUS.IDLE, + [SECTIONS.FOLLOWER_INDEX]: API_STATUS.IDLE, }, error: { [SECTIONS.AUTO_FOLLOW_PATTERN]: null, - [SECTIONS.INDEX_FOLLOWER]: null, + [SECTIONS.FOLLOWER_INDEX]: null, }, }; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/follower_index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/follower_index.js new file mode 100644 index 0000000000000..53e446847500b --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/follower_index.js @@ -0,0 +1,32 @@ +/* + * 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 * as t from '../action_types'; +import { arrayToObject } from '../../services/utils'; + +const initialState = { + byId: {}, + selectedDetailId: null, + selectedEditId: null, +}; + +const success = action => `${action}_SUCCESS`; + +export const reducer = (state = initialState, action) => { + switch (action.type) { + case success(t.FOLLOWER_INDEX_LOAD): { + return { ...state, byId: arrayToObject(action.payload.indices, 'name') }; + } + case success(t.FOLLOWER_INDEX_GET): { + return { ...state, byId: { ...state.byId, [action.payload.name]: action.payload } }; + } + case t.FOLLOWER_INDEX_SELECT_DETAIL: { + return { ...state, selectedDetailId: action.payload }; + } + default: + return state; + } +}; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/index.js index b34d824c3f278..51a6a6eb6b901 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/index.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/index.js @@ -7,8 +7,10 @@ import { combineReducers } from 'redux'; import { reducer as api } from './api'; import { reducer as autoFollowPattern } from './auto_follow_pattern'; +import { reducer as followerIndex } from './follower_index'; export const ccr = combineReducers({ autoFollowPattern, + followerIndex, api, }); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/selectors/index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/selectors/index.js index 52e638cc0c513..346be3d3c33d5 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/selectors/index.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/selectors/index.js @@ -33,3 +33,19 @@ export const getSelectedAutoFollowPattern = (view = 'detail') => createSelector( return autoFollowPatternsState.byId[autoFollowPatternsState[propId]]; }); export const getListAutoFollowPatterns = createSelector(getAutoFollowPatterns, (autoFollowPatterns) => objectToArray(autoFollowPatterns)); + +// Follower index +export const getFollowerIndexState = (state) => state.followerIndex; +export const getFollowerIndices = createSelector(getFollowerIndexState, (followerIndexState) => followerIndexState.byId); +export const getSelectedFollowerIndexId = (view = 'detail') => createSelector(getFollowerIndexState, (followerIndexState) => ( + view === 'detail' ? followerIndexState.selectedDetailId : followerIndexState.selectedEditId +)); +export const getSelectedFollowerIndex = (view = 'detail') => createSelector(getFollowerIndexState, (followerIndexState) => { + const propId = view === 'detail' ? 'selectedDetailId' : 'selectedEditId'; + + if(!followerIndexState[propId]) { + return null; + } + return followerIndexState.byId[followerIndexState[propId]]; +}); +export const getListFollowerIndices = createSelector(getFollowerIndices, (followerIndices) => objectToArray(followerIndices)); diff --git a/x-pack/plugins/cross_cluster_replication/server/client/elasticsearch_ccr.js b/x-pack/plugins/cross_cluster_replication/server/client/elasticsearch_ccr.js index 8c03769f6ec14..2e6cdb37629b6 100644 --- a/x-pack/plugins/cross_cluster_replication/server/client/elasticsearch_ccr.js +++ b/x-pack/plugins/cross_cluster_replication/server/client/elasticsearch_ccr.js @@ -72,6 +72,20 @@ export const elasticsearchJsPlugin = (Client, config, components) => { method: 'GET' }); + ccr.followerIndex = ca({ + urls: [ + { + fmt: '/<%=id%>/_ccr/stats', + req: { + id: { + type: 'string' + } + } + } + ], + method: 'GET' + }); + ccr.saveFollowerIndex = ca({ urls: [ { diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index.js b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index.js index 239d1f664eeff..6ab97e6e7118a 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index.js +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index.js @@ -8,6 +8,7 @@ import { callWithRequestFactory } from '../../lib/call_with_request_factory'; import { isEsErrorFactory } from '../../lib/is_es_error_factory'; import { wrapEsError, wrapUnknownError } from '../../lib/error_wrappers'; import { + deserializeFollowerIndex, deserializeListFollowerIndices, serializeFollowerIndex, } from '../../lib/follower_index_serialization'; @@ -44,6 +45,35 @@ export const registerFollowerIndexRoutes = (server) => { }, }); + + /** + * Returns a single follower index pattern + */ + server.route({ + path: `${API_BASE_PATH}/follower_indices/{id}`, + method: 'GET', + config: { + pre: [ licensePreRouting ] + }, + handler: async (request) => { + const callWithRequest = callWithRequestFactory(server, request); + const { id } = request.params; + + try { + const response = await callWithRequest('ccr.followerIndex', { id }); + const followerIndex = response.indices[0]; + + return deserializeFollowerIndex(followerIndex); + } catch(err) { + if (isEsError(err)) { + throw wrapEsError(err); + } + throw wrapUnknownError(err); + } + }, + }); + + /** * Create a follower index */ From 2ee632648b5c3ca0edbe43f318284ddd8683ced0 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Thu, 27 Dec 2018 01:32:27 -0800 Subject: [PATCH 2/5] Initial work for follower indices table and detail panel --- .../public/app/app.js | 2 +- .../follower_index_delete_provider.js | 119 +++++++ .../public/app/components/index.js | 2 + .../public/app/constants/sections.js | 2 +- .../auto_follow_pattern_table.js | 2 +- .../detail_panel/detail_panel.container.js | 23 ++ .../components/detail_panel/detail_panel.js | 304 ++++++++++++++++++ .../components/detail_panel/index.js | 7 + .../follower_indices_table.container.js | 27 ++ .../follower_indices_table.js | 233 ++++++++++++++ .../follower_indices_table/index.js | 7 + .../follower_indices_list/components/index.js | 8 + .../follower_indices_list.container.js | 40 +++ .../follower_indices_list.js | 176 ++++++++++ .../home/follower_indices_list/index.js | 7 + .../app/sections/home/home.container.js | 6 +- .../public/app/sections/home/home.js | 201 ++++++++---- 17 files changed, 1104 insertions(+), 62 deletions(-) create mode 100644 x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_delete_provider.js create mode 100644 x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.container.js create mode 100644 x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js create mode 100644 x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/index.js create mode 100644 x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.container.js create mode 100644 x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js create mode 100644 x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/index.js create mode 100644 x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/index.js create mode 100644 x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.container.js create mode 100644 x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js create mode 100644 x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/index.js diff --git a/x-pack/plugins/cross_cluster_replication/public/app/app.js b/x-pack/plugins/cross_cluster_replication/public/app/app.js index 19703b8283a8c..8ae8dd60c2fa6 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/app.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/app.js @@ -49,7 +49,7 @@ export class App extends Component { return (
- + diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_delete_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_delete_provider.js new file mode 100644 index 0000000000000..4941df861eb51 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_delete_provider.js @@ -0,0 +1,119 @@ +/* + * 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, Fragment } from 'react'; +import { connect } from 'react-redux'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; +import { + EuiConfirmModal, + EuiOverlayMask, +} from '@elastic/eui'; + +// import { deleteFollowerIndex } from '../store/actions'; +import { arrify } from '../../../common/services/utils'; + +class Provider extends PureComponent { + state = { + isModalOpen: false, + ids: null + } + + onMouseOverModal = (event) => { + // This component can sometimes be used inside of an EuiToolTip, in which case mousing over + // the modal can trigger the tooltip. Stopping propagation prevents this. + event.stopPropagation(); + }; + + deleteFollowerIndex = (id) => { + this.setState({ isModalOpen: true, ids: arrify(id) }); + }; + + onConfirm = () => { + // this.props.deleteFollowerIndex(this.state.ids); + this.setState({ isModalOpen: false, ids: null }); + } + + closeConfirmModal = () => { + this.setState({ + isModalOpen: false, + }); + }; + + renderModal = () => { + const { intl } = this.props; + const { ids } = this.state; + const isSingle = ids.length === 1; + const title = isSingle + ? intl.formatMessage({ + id: 'xpack.crossClusterReplication.deleteFollowerIndex.confirmModal.deleteSingleTitle', + defaultMessage: 'Remove follower index \'{name}\'?', + }, { name: ids[0] }) + : intl.formatMessage({ + id: 'xpack.crossClusterReplication.deleteFollowerIndex.confirmModal.deleteMultipleTitle', + defaultMessage: 'Remove {count} follower indices?', + }, { count: ids.length }); + + return ( + + { /* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */ } + + {!isSingle && ( + +

+ +

+
    {ids.map(id =>
  • {id}
  • )}
+
+ )} +
+
+ ); + } + + render() { + const { children } = this.props; + const { isModalOpen } = this.state; + + return ( + + {children(this.deleteFollowerIndex)} + {isModalOpen && this.renderModal()} + + ); + } +} + +const mapDispatchToProps = (/*dispatch*/) => ({ + // deleteFollowerIndex: (id) => dispatch(deleteFollowerIndex(id)), +}); + +export const FollowerIndexDeleteProvider = connect( + undefined, + mapDispatchToProps +)(injectI18n(Provider)); + diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/index.js b/x-pack/plugins/cross_cluster_replication/public/app/components/index.js index 5b4a7b407eda0..1e717eb8ea22d 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/index.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/index.js @@ -12,3 +12,5 @@ export { AutoFollowPatternForm } from './auto_follow_pattern_form'; export { AutoFollowPatternDeleteProvider } from './auto_follow_pattern_delete_provider'; export { AutoFollowPatternPageTitle } from './auto_follow_pattern_page_title'; export { AutoFollowPatternIndicesPreview } from './auto_follow_pattern_indices_preview'; + +export { FollowerIndexDeleteProvider } from './follower_index_delete_provider'; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/constants/sections.js b/x-pack/plugins/cross_cluster_replication/public/app/constants/sections.js index 0b2eb94de9b12..aaeface2d3329 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/constants/sections.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/constants/sections.js @@ -6,6 +6,6 @@ export const SECTIONS = { AUTO_FOLLOW_PATTERN: 'autoFollowPattern', - INDEX_FOLLOWER: 'indexFollower', + FOLLOWER_INDEX: 'followerIndex', REMOTE_CLUSTER: 'remoteCluster' }; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js index 1a9966896db5c..91baccb42b9fe 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js @@ -116,7 +116,7 @@ export const AutoFollowPatternTable = injectI18n( { render: ({ name }) => { const label = i18n.translate( - 'xpack.crossClusterReplication.autofollowPatternList.table.actionDeleteDescription', + 'xpack.crossClusterReplication.autoFollowPatternList.table.actionDeleteDescription', { defaultMessage: 'Delete auto-follow pattern', } diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.container.js new file mode 100644 index 0000000000000..6efb6ad1efbe7 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.container.js @@ -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 { connect } from 'react-redux'; +import { DetailPanel as DetailPanelView } from './detail_panel'; + +import { getSelectedFollowerIndex, getSelectedFollowerIndexId, getApiStatus, } from '../../../../../store/selectors'; +import { SECTIONS } from '../../../../../constants'; + +const scope = SECTIONS.FOLLOWER_INDEX; + +const mapStateToProps = (state) => ({ + followerIndexId: getSelectedFollowerIndexId('detail')(state), + followerIndex: getSelectedFollowerIndex('detail')(state), + apiStatus: getApiStatus(scope)(state), +}); + +export const DetailPanel = connect( + mapStateToProps, +)(DetailPanelView); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js new file mode 100644 index 0000000000000..1fafe2cb740c5 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js @@ -0,0 +1,304 @@ +/* + * 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, { Component, Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; +import { getIndexListUri } from '../../../../../../../../index_management/public/services/navigation'; + +import { + EuiButton, + EuiButtonEmpty, + EuiCodeEditor, + EuiDescriptionList, + EuiDescriptionListDescription, + EuiDescriptionListTitle, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiIcon, + EuiLink, + EuiLoadingSpinner, + EuiSpacer, + EuiText, + EuiTextColor, + EuiTitle, +} from '@elastic/eui'; + +import 'brace/theme/textmate'; + +import { + FollowerIndexDeleteProvider, +} from '../../../../../components'; + +import { API_STATUS } from '../../../../../constants'; +// import routing from '../../../../../services/routing'; + +export class DetailPanelUi extends Component { + static propTypes = { + apiStatus: PropTypes.string, + followerIndexId: PropTypes.string, + followerIndex: PropTypes.object, + closeDetailPanel: PropTypes.func.isRequired, + } + + renderFollowerIndex() { + const { + followerIndex: { + name, + shards, + }, + } = this.props; + + const indexManagementUri = getIndexListUri(`name:${name}`); + + return ( + + + +

+ +

+
+ + + + + + + + + + + + + + {shards[0].remoteCluster} + + + + + + + + + + + + {shards[0].leaderIndex} + + + + + + + + + + + {shards.map((shard, i) => ( + + + +

+ +

+
+ + +
+ ))} +
+
+
+ ); + } + + renderContent() { + const { + apiStatus, + followerIndex, + } = this.props; + + if (apiStatus === API_STATUS.LOADING) { + return ( + + + + + + + + + + + + + + + + ); + } + + if (!followerIndex) { + return ( + + + + + + + + + + + + + + + + ); + } + + return this.renderFollowerIndex(); + } + + renderFooter() { + const { + followerIndex, + closeDetailPanel, + } = this.props; + + return ( + + + + + + + + + {followerIndex && ( + + + + + {(deleteFollowerIndex) => ( + deleteFollowerIndex(followerIndex.name)} + > + + + )} + + + + + { + // routing.navigate(encodeURI(`/follower_indices/edit/${encodeURIComponent(followerIndex.name)}`)); + }} + > + + + + + + )} + + + ); + } + + render() { + const { followerIndexId, closeDetailPanel } = this.props; + + return ( + + + + +

{followerIndexId}

+
+
+ + {this.renderContent()} + {this.renderFooter()} +
+ ); + } +} + +export const DetailPanel = injectI18n(DetailPanelUi); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/index.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/index.js new file mode 100644 index 0000000000000..c27bbd8ea830f --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/index.js @@ -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 { DetailPanel } from './detail_panel.container'; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.container.js new file mode 100644 index 0000000000000..d522f47559374 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.container.js @@ -0,0 +1,27 @@ +/* + * 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 { connect } from 'react-redux'; + +// import { SECTIONS } from '../../../../../constants'; +import { selectDetailFollowerIndex } from '../../../../../store/actions'; +// import { getApiStatus } from '../../../../../store/selectors'; +import { FollowerIndicesTable as FollowerIndicesTableComponent } from './follower_indices_table'; + +// const scope = SECTIONS.FOLLOWER_INDEX; +// +// const mapStateToProps = (state) => ({ +// // apiStatusDelete: getApiStatus(`${scope}-delete`)(state), +// }); +// +const mapDispatchToProps = (dispatch) => ({ + selectFollowerIndex: (name) => dispatch(selectDetailFollowerIndex(name)), +}); + +export const FollowerIndicesTable = connect( + null, + mapDispatchToProps, +)(FollowerIndicesTableComponent); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js new file mode 100644 index 0000000000000..23f0e8e095d92 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js @@ -0,0 +1,233 @@ +/* + * 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, Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { i18n } from '@kbn/i18n'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; +import { + EuiButton, + EuiButtonIcon, + EuiInMemoryTable, + EuiLink, + // EuiLoadingKibana, + EuiToolTip, + // EuiOverlayMask, +} from '@elastic/eui'; +// import { API_STATUS } from '../../../../../constants'; +import { FollowerIndexDeleteProvider } from '../../../../../components'; +// import routing from '../../../../../services/routing'; + +export const FollowerIndicesTable = injectI18n( + class extends PureComponent { + static propTypes = { + followerIndices: PropTypes.array, + selectFollowerIndex: PropTypes.func.isRequired, + } + + state = { + selectedItems: [], + } + + onSearch = ({ query }) => { + const { text } = query; + const normalizedSearchText = text.toLowerCase(); + this.setState({ + queryText: normalizedSearchText, + }); + }; + + getFilteredIndices = () => { + const { followerIndices } = this.props; + const { queryText } = this.state; + + if(queryText) { + return followerIndices.filter(followerIndex => { + const { name, shards } = followerIndex; + + const inName = name.toLowerCase().includes(queryText); + const inRemoteCluster = shards[0].remoteCluster.toLowerCase().includes(queryText); + const inLeaderIndex = shards[0].leaderIndex.toLowerCase().includes(queryText); + + return inName || inRemoteCluster || inLeaderIndex; + }); + } + + return followerIndices.slice(0); + }; + + getTableColumns() { + const { intl, selectFollowerIndex } = this.props; + + return [{ + field: 'name', + name: intl.formatMessage({ + id: 'xpack.crossClusterReplication.followerIndexList.table.nameColumnTitle', + defaultMessage: 'Name', + }), + sortable: true, + truncateText: false, + render: (name) => { + return ( + selectFollowerIndex(name)}> + {name} + + ); + } + }, { + field: 'shards[0].remoteCluster', + name: intl.formatMessage({ + id: 'xpack.crossClusterReplication.followerIndexList.table.clusterColumnTitle', + defaultMessage: 'Cluster', + }), + truncateText: true, + sortable: true, + }, { + field: 'shards[0].leaderIndex', + name: intl.formatMessage({ + id: 'xpack.crossClusterReplication.followerIndexList.table.leaderIndexColumnTitle', + defaultMessage: 'Leader index', + }), + truncateText: true, + sortable: true, + }, { + name: intl.formatMessage({ + id: 'xpack.crossClusterReplication.followerIndexList.table.actionsColumnTitle', + defaultMessage: 'Actions', + }), + actions: [ + { + render: ({ name }) => { + const label = i18n.translate( + 'xpack.crossClusterReplication.followerIndexList.table.actionDeleteDescription', + { + defaultMessage: 'Delete follower', + } + ); + + return ( + + + {(deleteFollowerIndex) => ( + deleteFollowerIndex(name)} + /> + )} + + + ); + }, + }, + { + render: ({ /*name*/ }) => { + const label = i18n.translate('xpack.crossClusterReplication.followerIndexList.table.actionEditDescription', { + defaultMessage: 'Edit follower', + }); + + return ( + + + + ); + }, + }, + ], + width: '100px', + }]; + } + + renderLoading = () => { + // const { apiStatusDelete } = this.props; + // + // if (apiStatusDelete === API_STATUS.DELETING) { + // return ( + // + // + // + // ); + // } + return null; + }; + + render() { + const { + selectedItems, + } = this.state; + + const sorting = { + sort: { + field: 'name', + direction: 'asc', + } + }; + + const pagination = { + initialPageSize: 20, + pageSizeOptions: [10, 20, 50] + }; + + const selection = { + onSelectionChange: (selectedItems) => this.setState({ selectedItems }) + }; + + const search = { + toolsLeft: selectedItems.length ? ( + + {(deleteFollowerIndex) => ( + deleteFollowerIndex(selectedItems.map(({ name }) => name))} + > + + + )} + + ) : undefined, + onChange: this.onSearch, + box: { + incremental: true, + }, + }; + + return ( + + + {this.renderLoading()} + + ); + } + } +); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/index.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/index.js new file mode 100644 index 0000000000000..8ea9cd98336c3 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/index.js @@ -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 { FollowerIndicesTable } from './follower_indices_table.container'; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/index.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/index.js new file mode 100644 index 0000000000000..d81a62e17a4b7 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/index.js @@ -0,0 +1,8 @@ +/* + * 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 { FollowerIndicesTable } from './follower_indices_table'; +export { DetailPanel } from './detail_panel'; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.container.js new file mode 100644 index 0000000000000..2baf716e7b90a --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.container.js @@ -0,0 +1,40 @@ +/* + * 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 { connect } from 'react-redux'; + +import { SECTIONS } from '../../../constants'; +import { + getListFollowerIndices, + getSelectedFollowerIndexId, + getApiStatus, + getApiError, + isApiAuthorized, +} from '../../../store/selectors'; +import { + loadFollowerIndices, selectDetailFollowerIndex, +} from '../../../store/actions'; +import { FollowerIndicesList as FollowerIndicesListView } from './follower_indices_list'; + +const scope = SECTIONS.FOLLOWER_INDEX; + +const mapStateToProps = (state) => ({ + followerIndices: getListFollowerIndices(state), + followerIndexId: getSelectedFollowerIndexId('detail')(state), + apiStatus: getApiStatus(scope)(state), + apiError: getApiError(scope)(state), + isAuthorized: isApiAuthorized(scope)(state), +}); + +const mapDispatchToProps = dispatch => ({ + loadFollowerIndices: (inBackground) => dispatch(loadFollowerIndices(inBackground)), + selectFollowerIndex: (id) => dispatch(selectDetailFollowerIndex(id)), +}); + +export const FollowerIndicesList = connect( + mapStateToProps, + mapDispatchToProps +)(FollowerIndicesListView); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js new file mode 100644 index 0000000000000..91fbbaaa64ec4 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js @@ -0,0 +1,176 @@ +/* + * 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, Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; +import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; + +// import routing from '../../../services/routing'; +import { extractQueryParams } from '../../../services/query_params'; +import { API_STATUS } from '../../../constants'; +import { SectionLoading, SectionError } from '../../../components'; +import { FollowerIndicesTable, DetailPanel } from './components'; + +const REFRESH_RATE_MS = 30000; + +const getQueryParamName = ({ location: { search } }) => { + const { name } = extractQueryParams(search); + return name ? decodeURIComponent(name) : null; +}; + +export const FollowerIndicesList = injectI18n( + class extends PureComponent { + static propTypes = { + loadFollowerIndices: PropTypes.func, + selectFollowerIndex: PropTypes.func, + followerIndices: PropTypes.array, + apiStatus: PropTypes.string, + apiError: PropTypes.object, + } + + static getDerivedStateFromProps({ followerIndexId }, { lastFollowerIndexId }) { + if (followerIndexId !== lastFollowerIndexId) { + return { + lastFollowerIndexId: followerIndexId, + isDetailPanelOpen: !!followerIndexId, + }; + } + return null; + } + + state = { + lastFollowerIndexId: null, + isDetailPanelOpen: false, + }; + + componentDidMount() { + const { loadFollowerIndices, selectFollowerIndex, history } = this.props; + + loadFollowerIndices(); + + // Select the pattern in the URL query params + selectFollowerIndex(getQueryParamName(history)); + + // Interval to load follower indices in the background passing "true" to the fetch method + this.interval = setInterval(() => loadFollowerIndices(true), REFRESH_RATE_MS); + } + + componentDidUpdate(prevProps, prevState) { + const { history } = this.props; + const { lastFollowerIndexId } = this.state; + + /** + * Each time our state is updated (through getDerivedStateFromProps()) + * we persist the follower index id to query params for deep linking + */ + if (lastFollowerIndexId !== prevState.lastFollowerIndexId) { + if(!lastFollowerIndexId) { + history.replace({ + search: '', + }); + } else { + history.replace({ + search: `?name=${encodeURIComponent(lastFollowerIndexId)}`, + }); + } + } + } + + componentWillUnmount() { + clearInterval(this.interval); + } + + renderEmpty() { + return ( + + + + )} + body={ + +

+ +

+
+ } + actions={ + + + + } + /> + ); + } + + renderList() { + const { + selectFollowerIndex, + followerIndices, + apiStatus, + } = this.props; + + const { isDetailPanelOpen } = this.state; + + if (apiStatus === API_STATUS.LOADING) { + return ( + + + + ); + } + + return ( + + + {isDetailPanelOpen && selectFollowerIndex(null)} />} + + ); + } + + render() { + const { followerIndices, apiStatus, apiError, isAuthorized, intl } = this.props; + + if (!isAuthorized) { + return null; + } + + if (apiStatus === API_STATUS.IDLE && !followerIndices.length) { + return this.renderEmpty(); + } + + if (apiError) { + const title = intl.formatMessage({ + id: 'xpack.crossClusterReplication.followerIndexList.loadingErrorTitle', + defaultMessage: 'Error loading follower indices', + }); + return ; + } + + return this.renderList(); + } + } +); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/index.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/index.js new file mode 100644 index 0000000000000..08c799176f297 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/index.js @@ -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 { FollowerIndicesList } from './follower_indices_list.container'; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.container.js index b5e897f93fa58..0ae54ddc611a6 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.container.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.container.js @@ -7,12 +7,14 @@ import { connect } from 'react-redux'; import { SECTIONS } from '../../constants'; -import { getListAutoFollowPatterns, isApiAuthorized } from '../../store/selectors'; +import { getListAutoFollowPatterns, getListFollowerIndices, isApiAuthorized } from '../../store/selectors'; import { CrossClusterReplicationHome as CrossClusterReplicationHomeView } from './home'; const mapStateToProps = (state) => ({ autoFollowPatterns: getListAutoFollowPatterns(state), - isAutoFollowApiAuthorized: isApiAuthorized(SECTIONS.AUTO_FOLLOW_PATTERN)(state) + isAutoFollowApiAuthorized: isApiAuthorized(SECTIONS.AUTO_FOLLOW_PATTERN)(state), + followerIndices: getListFollowerIndices(state), + isFollowerIndexApiAuthorized: isApiAuthorized(SECTIONS.FOLLOWER_INDEX)(state), }); export const CrossClusterReplicationHome = connect( diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js index 91b972599b67e..0be74d3c4d0ab 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js @@ -20,6 +20,8 @@ import { EuiPageBody, EuiPageContent, EuiSpacer, + EuiTab, + EuiTabs, EuiText, EuiTitle, } from '@elastic/eui'; @@ -27,83 +29,141 @@ import { import { listBreadcrumb } from '../../services/breadcrumbs'; import routing from '../../services/routing'; import { AutoFollowPatternList } from './auto_follow_pattern_list'; +import { FollowerIndicesList } from './follower_indices_list'; import { SectionUnauthorized } from '../../components'; export const CrossClusterReplicationHome = injectI18n( class extends PureComponent { static propTypes = { autoFollowPatterns: PropTypes.array, + isAutoFollowApiAuthorized: PropTypes.bool, + followerIndices: PropTypes.array, + isFollowerIndexApiAuthorized: PropTypes.bool, } state = { - sectionActive: 'auto-follow' + activeSection: 'follower_indices' } + tabs = [{ + id: 'follower_indices', + name: ( + + ) + }, { + id: 'auto_follow_patterns', + name: ( + + ) + }] + componentDidMount() { + const { match: { params: { section } } } = this.props; + this.setState({ + activeSection: section + }); chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb ]); } + onSectionChange = (section) => { + routing.navigate(`/${section}`); + this.setState({ + activeSection: section + }); + } + getHeaderSection() { - const { isAutoFollowApiAuthorized, autoFollowPatterns } = this.props; + if(this.state.activeSection === 'follower_indices') { + const { isFollowerIndexApiAuthorized, followerIndices } = this.props; - // We want to show the title when the user isn't authorized. - if (isAutoFollowApiAuthorized && !autoFollowPatterns.length) { - return null; - } - return ( - - -

- -

-
- - - - - - -

- -

-
+ // We want to show the title when the user isn't authorized. + if (isFollowerIndexApiAuthorized && !followerIndices.length) { + return null; + } - -

- -

-
-
- - - {isAutoFollowApiAuthorized && ( - - - - )} - -
+ return ( + + + + +

+ +

+
+
+ + + {isFollowerIndexApiAuthorized && ( + + + + )} + +
+ + +
+ ); + } else { + const { isAutoFollowApiAuthorized, autoFollowPatterns } = this.props; - -
- ); + // We want to show the title when the user isn't authorized. + if (isAutoFollowApiAuthorized && !autoFollowPatterns.length) { + return null; + } + + return ( + + + + +

+ +

+
+
+ + + {isAutoFollowApiAuthorized && ( + + + + )} + +
+ + +
+ ); + } } getUnauthorizedSection() { @@ -125,9 +185,36 @@ export const CrossClusterReplicationHome = injectI18n( + +

+ +

+
+ + + + + {this.tabs.map(tab => ( + this.onSectionChange(tab.id)} + isSelected={tab.id === this.state.activeSection} + key={tab.id} + > + {tab.name} + + ))} + + + + {this.getHeaderSection()} {this.getUnauthorizedSection()} + +
From 0c2cc0febc6fecc5b349f45ddf2f3140420f53e8 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Thu, 27 Dec 2018 01:48:27 -0800 Subject: [PATCH 3/5] Adjust routing --- .../public/app/sections/home/home.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js index 0be74d3c4d0ab..a9cc7c28f38a1 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js @@ -71,11 +71,15 @@ export const CrossClusterReplicationHome = injectI18n( chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb ]); } + static getDerivedStateFromProps(props) { + const { match: { params: { section } } } = props; + return { + activeSection: section + }; + } + onSectionChange = (section) => { routing.navigate(`/${section}`); - this.setState({ - activeSection: section - }); } getHeaderSection() { From 7691a9fc7aabeebbd1315386d463b5f16a033b5a Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Thu, 27 Dec 2018 13:59:35 -0800 Subject: [PATCH 4/5] Fix test --- .../server/routes/api/follower_index.test.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index.test.js b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index.test.js index 2fdb50ad7a96c..95b6ea5f650aa 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index.test.js +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index.test.js @@ -32,7 +32,8 @@ const registerHandlers = () => { const HANDLER_INDEX_TO_ACTION = { 0: 'list', - 1: 'create', + 1: 'get', + 2: 'create', }; const server = { @@ -104,6 +105,22 @@ describe('[CCR API Routes] Follower Index', () => { }); }); + describe('get()', () => { + beforeEach(() => { + routeHandler = routeHandlers.get; + }); + + it('should return a single resource even though ES return an array with 1 item', async () => { + const followerIndex = getFollowerIndexMock(); + const esResponse = { indices: [followerIndex] }; + + setHttpRequestResponse(null, esResponse); + + const response = await routeHandler({ params: { id: 1 } }); + expect(Object.keys(response)).toEqual(DESERIALIZED_KEYS); + }); + }); + describe('create()', () => { beforeEach(() => { resetHttpRequestResponses(); From 022dcde299859889c03ce8f6f237a16740f0309c Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Thu, 27 Dec 2018 15:09:37 -0800 Subject: [PATCH 5/5] PR feedback --- .../components/detail_panel/detail_panel.js | 6 ++++-- .../follower_indices_table/follower_indices_table.js | 8 ++++---- .../follower_indices_list/follower_indices_list.js | 4 ++-- .../public/app/sections/home/home.js | 6 +----- .../public/app/store/reducers/follower_index.js | 10 ++++++++-- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js index 1fafe2cb740c5..43c052fcd8094 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js @@ -52,6 +52,8 @@ export class DetailPanelUi extends Component { const { followerIndex: { name, + remoteCluster, + leaderIndex, shards, }, } = this.props; @@ -85,7 +87,7 @@ export class DetailPanelUi extends Component { - {shards[0].remoteCluster} + {remoteCluster} @@ -100,7 +102,7 @@ export class DetailPanelUi extends Component { - {shards[0].leaderIndex} + {leaderIndex} diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js index 23f0e8e095d92..d782211d9e3bb 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js @@ -78,7 +78,7 @@ export const FollowerIndicesTable = injectI18n( ); } }, { - field: 'shards[0].remoteCluster', + field: 'remoteCluster', name: intl.formatMessage({ id: 'xpack.crossClusterReplication.followerIndexList.table.clusterColumnTitle', defaultMessage: 'Cluster', @@ -86,7 +86,7 @@ export const FollowerIndicesTable = injectI18n( truncateText: true, sortable: true, }, { - field: 'shards[0].leaderIndex', + field: 'leaderIndex', name: intl.formatMessage({ id: 'xpack.crossClusterReplication.followerIndexList.table.leaderIndexColumnTitle', defaultMessage: 'Leader index', @@ -104,7 +104,7 @@ export const FollowerIndicesTable = injectI18n( const label = i18n.translate( 'xpack.crossClusterReplication.followerIndexList.table.actionDeleteDescription', { - defaultMessage: 'Delete follower', + defaultMessage: 'Delete follower index', } ); @@ -130,7 +130,7 @@ export const FollowerIndicesTable = injectI18n( { render: ({ /*name*/ }) => { const label = i18n.translate('xpack.crossClusterReplication.followerIndexList.table.actionEditDescription', { - defaultMessage: 'Edit follower', + defaultMessage: 'Edit follower index', }); return ( diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js index 91fbbaaa64ec4..bd6fbdd3812ef 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js @@ -92,7 +92,7 @@ export const FollowerIndicesList = injectI18n(

)} @@ -115,7 +115,7 @@ export const FollowerIndicesList = injectI18n( > } diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js index a9cc7c28f38a1..51b34c1b2769f 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js @@ -64,10 +64,6 @@ export const CrossClusterReplicationHome = injectI18n( }] componentDidMount() { - const { match: { params: { section } } } = this.props; - this.setState({ - activeSection: section - }); chrome.breadcrumbs.set([ MANAGEMENT_BREADCRUMB, listBreadcrumb ]); } @@ -115,7 +111,7 @@ export const CrossClusterReplicationHome = injectI18n( > )} diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/follower_index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/follower_index.js index 53e446847500b..352b600a35d59 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/follower_index.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/follower_index.js @@ -15,13 +15,19 @@ const initialState = { const success = action => `${action}_SUCCESS`; +const parseFollowerIndex = (followerIndex) => { + // Extract remote cluster and leader index from follower index shard information + const { remoteCluster, leaderIndex } = followerIndex.shards[0]; + + return { ...followerIndex, remoteCluster, leaderIndex }; +}; export const reducer = (state = initialState, action) => { switch (action.type) { case success(t.FOLLOWER_INDEX_LOAD): { - return { ...state, byId: arrayToObject(action.payload.indices, 'name') }; + return { ...state, byId: arrayToObject(action.payload.indices.map(parseFollowerIndex), 'name') }; } case success(t.FOLLOWER_INDEX_GET): { - return { ...state, byId: { ...state.byId, [action.payload.name]: action.payload } }; + return { ...state, byId: { ...state.byId, [action.payload.name]: parseFollowerIndex(action.payload) } }; } case t.FOLLOWER_INDEX_SELECT_DETAIL: { return { ...state, selectedDetailId: action.payload };