Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CCR] Remote clusters settings sources #26067

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export class DisconnectButtonUi extends Component {
disconnectClusters: PropTypes.func.isRequired,
clusterNames: PropTypes.array.isRequired,
isSmallButton: PropTypes.bool,
isDisabled: PropTypes.bool,
disabledReason: PropTypes.string,
};

constructor(props) {
Expand Down Expand Up @@ -79,7 +81,7 @@ export class DisconnectButtonUi extends Component {
}

render() {
const { intl, clusterNames } = this.props;
const { intl, clusterNames, isDisabled, disabledReason } = this.props;
const { isModalOpen } = this.state;
const isSingleCluster = clusterNames.length === 1;

Expand All @@ -94,16 +96,18 @@ export class DisconnectButtonUi extends Component {
defaultMessage: 'Disconnect {count} remote clusters?',
}, { count: clusterNames.length });

const content = isSingleCluster ? null : (
const content = (
<Fragment>
<p>
<FormattedMessage
id="xpack.remoteClusters.disconnectButton.confirmModal.multipleDeletionDescription"
defaultMessage="You are about to disconnect from {isSingleCluster, plural, one {this cluster} other {these clusters}}"
defaultMessage="You are about to remove transient and persistent settings from
{isSingleCluster, plural, one {this cluster} other {these clusters}}.
Settings from elasticsearch.yml configuration file will not be removed."
values={{ isSingleCluster: isSingleCluster ? 1 : 0 }}
/>
</p>
{<ul>{clusterNames.map(name => <li key={name}>{name}</li>)}</ul>}
{ isSingleCluster ? null : (<ul>{clusterNames.map(name => <li key={name}>{name}</li>)}</ul>)}
</Fragment>
);

Expand Down Expand Up @@ -135,7 +139,7 @@ export class DisconnectButtonUi extends Component {

return (
<Fragment>
<EuiButton color="danger" onClick={this.showConfirmModal}>
<EuiButton color="danger" onClick={this.showConfirmModal} isDisabled={isDisabled} title={disabledReason}>
{this.renderButtonText()}
</EuiButton>
{modal}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';

import {
EuiBadge,
EuiButton,
EuiDescriptionList,
EuiDescriptionListDescription,
Expand All @@ -19,6 +21,7 @@ import {
EuiFlyoutBody,
EuiFlyoutHeader,
EuiFlyoutFooter,
EuiHorizontalRule,
EuiIcon,
EuiLoadingSpinner,
EuiSpacer,
Expand All @@ -31,6 +34,11 @@ import { CRUD_APP_BASE_PATH } from '../../../constants';

import { ConnectionStatus, DisconnectButton } from '../components';

const disconnectButtonDisabledReason = i18n.translate(
'xpack.remoteClusters.detailPanel.disconnectButtonDisabledReason',
{ defaultMessage: 'Cannot disconnect from cluster configured in elasticsearch.yml' }
);

export class DetailPanelUi extends Component {
static propTypes = {
isOpen: PropTypes.bool.isRequired,
Expand All @@ -44,15 +52,113 @@ export class DetailPanelUi extends Component {
super(props);
}

renderSkipUnavailableValue(value) {
if(value === true) {
return (
<FormattedMessage
id="xpack.remoteClusters.detailPanel.skipUnavailableTrueValue"
defaultMessage="Yes"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must say I am surprised we don't have global translation keys for "yes" and "no" 😊

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One possible explanation is that context can change the meaning of an affirmative in other languages. For example, "hai" in Japanese is required for formal responses, but in other situations a more casual "ee" is appropriate (https://www.thoughtco.com/can-i-use-ee-instead-of-hai-4037928).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks for the link @cjcenizal !

/>
);
}

if(value === false) {
return (
<FormattedMessage
id="xpack.remoteClusters.detailPanel.skipUnavailableFalseValue"
defaultMessage="No"
/>
);
}

return (
<FormattedMessage
id="xpack.remoteClusters.detailPanel.skipUnavailableNullValue"
defaultMessage="Default"
/>
);
}

renderSettings(settings) {
const {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code style: this could be deconstructed in the method argument.

seeds,
skipUnavailable,
} = settings;

return (
<EuiDescriptionList>
<EuiFlexGroup>
<EuiFlexItem>
<EuiDescriptionListTitle>
<EuiTitle size="xs">
<FormattedMessage
id="xpack.remoteClusters.detailPanel.seedsLabel"
defaultMessage="Seeds"
/>
</EuiTitle>
</EuiDescriptionListTitle>

<EuiDescriptionListDescription>
{seeds.map(seed => <EuiText key={seed}>{seed}</EuiText>)}
</EuiDescriptionListDescription>
</EuiFlexItem>
<EuiFlexItem>
<EuiDescriptionListTitle>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the code would be easier to read if the whole setting block would be rendered (not just the value). At first sight, renderSkipUnavailableValue() does not mean much, but something like renderSkipUnavailableSetting() containing both the title + description would be more meaningful.

<EuiTitle size="xs">
<FormattedMessage
id="xpack.remoteClusters.detailPanel.skipUnavailableLabel"
defaultMessage="Skip unavailable"
/>
</EuiTitle>
</EuiDescriptionListTitle>

<EuiDescriptionListDescription>
{this.renderSkipUnavailableValue(skipUnavailable)}
</EuiDescriptionListDescription>
</EuiFlexItem>
</EuiFlexGroup>
</EuiDescriptionList>
);
}

renderActiveSettingBadge(isActive) {
if(isActive) {
return (
<EuiBadge color="primary">
<FormattedMessage
id="xpack.remoteClusters.detailPanel.activeSettingBadge"
defaultMessage="Active"
/>
</EuiBadge>
);
}
return (
<EuiBadge color="hollow">
<FormattedMessage
id="xpack.remoteClusters.detailPanel.fallbackSettingBadge"
defaultMessage="Fallback"
/>
</EuiBadge>
);
}

renderCluster() {
const { cluster } = this.props;
const { isConnected, seeds, connectedNodesCount } = cluster;

const renderedSeeds = seeds.map(seed => <EuiText key={seed}>{seed}</EuiText>);
const { isConnected, connectedNodesCount } = cluster;

return (
<Fragment>
<EuiFlyoutBody>

<EuiTitle size="s">
<h3>
<FormattedMessage
id="xpack.remoteClusters.detailPanel.statusTitle"
defaultMessage="Status"
/>
</h3>
</EuiTitle>
<EuiSpacer size="s" />
<EuiDescriptionList>
<EuiFlexGroup>
<EuiFlexItem>
Expand Down Expand Up @@ -85,22 +191,60 @@ export class DetailPanelUi extends Component {
</EuiDescriptionListDescription>
</EuiFlexItem>
</EuiFlexGroup>
</EuiDescriptionList>
{this.renderSettings(cluster)}

<EuiSpacer size="s" />
<EuiHorizontalRule />

<EuiDescriptionListTitle>
<EuiTitle size="xs">
<FormattedMessage
id="xpack.remoteClusters.detailPanel.seedsLabel"
defaultMessage="Seeds"
/>
{cluster.transientSettings ? (
<Fragment>
<EuiTitle size="s">
<h3>
<FormattedMessage
id="xpack.remoteClusters.detailPanel.transientSettingsTitle"
defaultMessage="Transient settings"
/>
{' '}{this.renderActiveSettingBadge(true)}
</h3>
</EuiTitle>
</EuiDescriptionListTitle>
<EuiSpacer size="s" />
{this.renderSettings(cluster.transientSettings)}
<EuiSpacer size="l" />
</Fragment>
) : null}

<EuiDescriptionListDescription>
{renderedSeeds}
</EuiDescriptionListDescription>
</EuiDescriptionList>
{cluster.persistentSettings ? (
<Fragment>
<EuiTitle size="s">
<h3>
<FormattedMessage
id="xpack.remoteClusters.detailPanel.persistentSettingsTitle"
defaultMessage="Persistent settings"
/>
{' '}{this.renderActiveSettingBadge(!cluster.transientSettings)}
</h3>
</EuiTitle>
<EuiSpacer size="s" />
{this.renderSettings(cluster.persistentSettings)}
<EuiSpacer size="l" />
</Fragment>
) : null}

{!cluster.transientSettings && !cluster.persistentSettings ? (
<Fragment>
<EuiTitle size="s">
<h3>
<FormattedMessage
id="xpack.remoteClusters.detailPanel.configurationFileSettingsTitle"
defaultMessage="Configuration file settings"
/>
{' '}{this.renderActiveSettingBadge(true)}
</h3>
</EuiTitle>
<EuiSpacer size="s" />
{this.renderSettings(cluster)}
</Fragment>
) : null}
</EuiFlyoutBody>
</Fragment>
);
Expand All @@ -115,6 +259,8 @@ export class DetailPanelUi extends Component {
clusterName,
} = this.props;

const isDisconnectDisabled = !cluster || !(cluster.isTransient || cluster.isPersistent);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might also want to disable the button if the cluster is not connected, what do you think?

if (!isOpen) {
return null;
}
Expand Down Expand Up @@ -197,6 +343,8 @@ export class DetailPanelUi extends Component {
<DisconnectButton
clusterNames={[clusterName]}
isSmallButton={true}
isDisabled={isDisconnectDisabled}
disabledReason={isDisconnectDisabled ? disconnectButtonDisabledReason : null}
/>
</EuiFlexItem>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';

import {
EuiLink,
Expand All @@ -15,6 +16,26 @@ import {

import { ConnectionStatus, DisconnectButton } from '../components';

const unselectableMessage = i18n.translate(
'xpack.remoteClusters.remoteClusterList.table.unselectableMessage',
{ defaultMessage: 'Settings from elasticsearch.yml cannot be deleted' }
);

const sourceValueTransient = i18n.translate(
'xpack.remoteClusters.remoteClusterList.table.sourceValueTransient',
{ defaultMessage: 'Transient' }
);

const sourceValuePersistent = i18n.translate(
'xpack.remoteClusters.remoteClusterList.table.sourceValuePersistent',
{ defaultMessage: 'Persistent' }
);

const sourceValueConfiguration = i18n.translate(
'xpack.remoteClusters.remoteClusterList.table.sourceValueConfiguration',
{ defaultMessage: 'Config file' }
);

export class RemoteClusterTableUi extends Component {
static propTypes = {
clusters: PropTypes.array,
Expand All @@ -34,8 +55,8 @@ export class RemoteClusterTableUi extends Component {
};
}

onSearch = (queryObject) => {
const { text } = queryObject.query;
onSearch = ({ query }) => {
const { text } = query;
const normalizedSearchText = text.toLowerCase();
this.setState({
queryText: normalizedSearchText,
Expand Down Expand Up @@ -97,6 +118,25 @@ export class RemoteClusterTableUi extends Component {
),
truncateText: true,
render: (seeds) => seeds.join(', '),
}, {
name: (
<FormattedMessage
id="xpack.remoteClusters.remoteClusterList.table.sourceColumnTitle"
defaultMessage="Source"
/>
),
truncateText: true,
render: (item) => {
if(item.isTransient) {
return sourceValueTransient;
}

if(item.isPersistent) {
return sourceValuePersistent;
}

return sourceValueConfiguration;
},
}, {
field: 'isConnected',
name: (
Expand Down Expand Up @@ -145,7 +185,9 @@ export class RemoteClusterTableUi extends Component {
};

const selection = {
onSelectionChange: (selectedItems) => this.setState({ selectedItems })
onSelectionChange: (selectedItems) => this.setState({ selectedItems }),
selectable: (item) => item.isTransient || item.isPersistent,
selectableMessage: (selectable) => !selectable ? unselectableMessage : null,
};

const filteredClusters = this.getFilteredClusters();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const disconnectClusters = (names) => async (dispatch, getState) => {
}));
}

// If we've just deleted a job we were looking at, we need to close the panel.
// If we've just deleted a cluster we were looking at, we need to close the panel.
const detailPanelClusterName = getDetailPanelClusterName(getState());
if (detailPanelClusterName && names.includes(detailPanelClusterName)) {
dispatch(closeDetailPanel());
Expand Down
Loading