Skip to content

Commit

Permalink
Fix CCR search bug caused by paused follower indices (elastic#64717)
Browse files Browse the repository at this point in the history
# Conflicts:
#	x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js
  • Loading branch information
cjcenizal committed May 4, 2020
1 parent 2f74672 commit b925695
Show file tree
Hide file tree
Showing 2 changed files with 317 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ export class FollowerIndicesTable extends PureComponent {

if (queryText) {
return followerIndices.filter(followerIndex => {
const { name, shards } = followerIndex;
const { name, remoteCluster, leaderIndex } = followerIndex;

const inName = name.toLowerCase().includes(queryText);
const inRemoteCluster = shards[0].remoteCluster.toLowerCase().includes(queryText);
const inLeaderIndex = shards[0].leaderIndex.toLowerCase().includes(queryText);
const inRemoteCluster = remoteCluster.toLowerCase().includes(queryText);
const inLeaderIndex = leaderIndex.toLowerCase().includes(queryText);

return inName || inRemoteCluster || inLeaderIndex;
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
/*
* 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 { FormattedMessage } from '@kbn/i18n/react';
import {
EuiIcon,
EuiHealth,
EuiInMemoryTable,
EuiLink,
EuiLoadingKibana,
EuiOverlayMask,
} from '@elastic/eui';
import { API_STATUS, UIM_FOLLOWER_INDEX_SHOW_DETAILS_CLICK } from '../../../../../constants';
import {
FollowerIndexPauseProvider,
FollowerIndexResumeProvider,
FollowerIndexUnfollowProvider,
} from '../../../../../components';
import { routing } from '../../../../../services/routing';
import { trackUiMetric } from '../../../../../services/track_ui_metric';
import { ContextMenu } from '../context_menu';

export class FollowerIndicesTable 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,
});
};

editFollowerIndex = id => {
const uri = routing.getFollowerIndexPath(id, '/edit', false);
routing.navigate(uri);
};

getFilteredIndices = () => {
const { followerIndices } = this.props;
const { queryText } = this.state;

if (queryText) {
return followerIndices.filter(followerIndex => {
const { name, remoteCluster, leaderIndex } = followerIndex;

const inName = name.toLowerCase().includes(queryText);
const inRemoteCluster = remoteCluster.toLowerCase().includes(queryText);
const inLeaderIndex = leaderIndex.toLowerCase().includes(queryText);

return inName || inRemoteCluster || inLeaderIndex;
});
}

return followerIndices.slice(0);
};

getTableColumns() {
const { selectFollowerIndex } = this.props;

const actions = [
/* Pause or resume follower index */
{
render: followerIndex => {
const { name, isPaused } = followerIndex;
const label = isPaused
? i18n.translate(
'xpack.crossClusterReplication.followerIndexList.table.actionResumeDescription',
{
defaultMessage: 'Resume replication',
}
)
: i18n.translate(
'xpack.crossClusterReplication.followerIndexList.table.actionPauseDescription',
{
defaultMessage: 'Pause replication',
}
);

return isPaused ? (
<FollowerIndexResumeProvider>
{resumeFollowerIndex => (
<span onClick={() => resumeFollowerIndex(name)} data-test-subj="resumeButton">
<EuiIcon aria-label={label} type="play" className="euiContextMenu__icon" />
<span>{label}</span>
</span>
)}
</FollowerIndexResumeProvider>
) : (
<FollowerIndexPauseProvider>
{pauseFollowerIndex => (
<span
onClick={() => pauseFollowerIndex(followerIndex)}
data-test-subj="pauseButton"
>
<EuiIcon aria-label={label} type="pause" className="euiContextMenu__icon" />
<span>{label}</span>
</span>
)}
</FollowerIndexPauseProvider>
);
},
},
/* Edit follower index */
{
render: ({ name }) => {
const label = i18n.translate(
'xpack.crossClusterReplication.followerIndexList.table.actionEditDescription',
{
defaultMessage: 'Edit follower index',
}
);

return (
<span onClick={() => this.editFollowerIndex(name)} data-test-subj="editButton">
<EuiIcon aria-label={label} type="pencil" className="euiContextMenu__icon" />
<span>{label}</span>
</span>
);
},
},
/* Unfollow leader index */
{
render: ({ name }) => {
const label = i18n.translate(
'xpack.crossClusterReplication.followerIndexList.table.actionUnfollowDescription',
{
defaultMessage: 'Unfollow leader index',
}
);

return (
<FollowerIndexUnfollowProvider>
{unfollowLeaderIndex => (
<span onClick={() => unfollowLeaderIndex(name)} data-test-subj="unfollowButton">
<EuiIcon aria-label={label} type="indexFlush" className="euiContextMenu__icon" />
<span>{label}</span>
</span>
)}
</FollowerIndexUnfollowProvider>
);
},
},
];

return [
{
field: 'name',
name: i18n.translate(
'xpack.crossClusterReplication.followerIndexList.table.nameColumnTitle',
{
defaultMessage: 'Name',
}
),
sortable: true,
truncateText: false,
render: name => {
return (
<EuiLink
onClick={() => {
trackUiMetric('click', UIM_FOLLOWER_INDEX_SHOW_DETAILS_CLICK);
selectFollowerIndex(name);
}}
data-test-subj="followerIndexLink"
>
{name}
</EuiLink>
);
},
},
{
field: 'isPaused',
name: i18n.translate(
'xpack.crossClusterReplication.followerIndexList.table.statusColumnTitle',
{
defaultMessage: 'Status',
}
),
truncateText: true,
sortable: true,
render: isPaused => {
return isPaused ? (
<EuiHealth color="subdued">
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.table.statusColumn.pausedLabel"
defaultMessage="Paused"
/>
</EuiHealth>
) : (
<EuiHealth color="success">
<FormattedMessage
id="xpack.crossClusterReplication.followerIndexList.table.statusColumn.activeLabel"
defaultMessage="Active"
/>
</EuiHealth>
);
},
},
{
field: 'remoteCluster',
name: i18n.translate(
'xpack.crossClusterReplication.followerIndexList.table.clusterColumnTitle',
{
defaultMessage: 'Remote cluster',
}
),
truncateText: true,
sortable: true,
},
{
field: 'leaderIndex',
name: i18n.translate(
'xpack.crossClusterReplication.followerIndexList.table.leaderIndexColumnTitle',
{
defaultMessage: 'Leader index',
}
),
truncateText: true,
sortable: true,
},
{
name: i18n.translate(
'xpack.crossClusterReplication.followerIndexList.table.actionsColumnTitle',
{
defaultMessage: 'Actions',
}
),
actions,
width: '100px',
},
];
}

renderLoading = () => {
const { apiStatusDelete } = this.props;

if (apiStatusDelete === API_STATUS.DELETING) {
return (
<EuiOverlayMask>
<EuiLoadingKibana size="xl" />
</EuiOverlayMask>
);
}
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: newSelectedItems => this.setState({ selectedItems: newSelectedItems }),
};

const search = {
toolsLeft: selectedItems.length ? (
<ContextMenu followerIndices={selectedItems} testSubj="contextMenuButton" />
) : (
undefined
),
onChange: this.onSearch,
box: {
incremental: true,
},
};

return (
<Fragment>
<EuiInMemoryTable
items={this.getFilteredIndices()}
itemId="name"
columns={this.getTableColumns()}
search={search}
pagination={pagination}
sorting={sorting}
selection={selection}
isSelectable={true}
rowProps={() => ({
'data-test-subj': 'row',
})}
cellProps={(item, column) => ({
'data-test-subj': `cell-${column.field}`,
})}
data-test-subj="followerIndexListTable"
/>
{this.renderLoading()}
</Fragment>
);
}
}

0 comments on commit b925695

Please sign in to comment.