Skip to content

Commit 4c7a9c3

Browse files
committed
Surface license errors in-app and refine permissions error UI.
1 parent 3a489ce commit 4c7a9c3

File tree

10 files changed

+207
-180
lines changed

10 files changed

+207
-180
lines changed

x-pack/plugins/cross_cluster_replication/public/app/app.js

Lines changed: 65 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@
77
import React, { Component } from 'react';
88
import PropTypes from 'prop-types';
99
import { Route, Switch, Redirect } from 'react-router-dom';
10+
import chrome from 'ui/chrome';
11+
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
1012

11-
import routing from './services/routing';
1213
import { BASE_PATH } from '../../common/constants';
14+
import { SectionUnauthorized } from './components';
15+
import routing from './services/routing';
16+
import { isAvailable, isActive, getReason } from './services/license';
1317

1418
import {
1519
CrossClusterReplicationHome,
@@ -19,47 +23,70 @@ import {
1923
FollowerIndexEdit,
2024
} from './sections';
2125

22-
export class App extends Component {
23-
static contextTypes = {
24-
router: PropTypes.shape({
25-
history: PropTypes.shape({
26-
push: PropTypes.func.isRequired,
27-
createHref: PropTypes.func.isRequired
26+
export const App = injectI18n(
27+
class extends Component {
28+
static contextTypes = {
29+
router: PropTypes.shape({
30+
history: PropTypes.shape({
31+
push: PropTypes.func.isRequired,
32+
createHref: PropTypes.func.isRequired
33+
}).isRequired
2834
}).isRequired
29-
}).isRequired
30-
}
35+
}
3136

32-
constructor(...args) {
33-
super(...args);
34-
this.registerRouter();
35-
}
37+
constructor(...args) {
38+
super(...args);
39+
this.registerRouter();
40+
}
3641

37-
componentWillMount() {
38-
routing.userHasLeftApp = false;
39-
}
42+
componentWillMount() {
43+
routing.userHasLeftApp = false;
44+
}
4045

41-
componentWillUnmount() {
42-
routing.userHasLeftApp = true;
43-
}
46+
componentWillUnmount() {
47+
routing.userHasLeftApp = true;
48+
}
4449

45-
registerRouter() {
46-
const { router } = this.context;
47-
routing.reactRouter = router;
48-
}
50+
registerRouter() {
51+
const { router } = this.context;
52+
routing.reactRouter = router;
53+
}
4954

50-
render() {
51-
return (
52-
<div>
53-
<Switch>
54-
<Redirect exact from={`${BASE_PATH}`} to={`${BASE_PATH}/follower_indices`} />
55-
<Route exact path={`${BASE_PATH}/auto_follow_patterns/add`} component={AutoFollowPatternAdd} />
56-
<Route exact path={`${BASE_PATH}/auto_follow_patterns/edit/:id`} component={AutoFollowPatternEdit} />
57-
<Route exact path={`${BASE_PATH}/follower_indices/add`} component={FollowerIndexAdd} />
58-
<Route exact path={`${BASE_PATH}/follower_indices/edit/:id`} component={FollowerIndexEdit} />
59-
<Route exact path={`${BASE_PATH}/:section`} component={CrossClusterReplicationHome} />
60-
</Switch>
61-
</div>
62-
);
63-
}
64-
}
55+
render() {
56+
if (!isAvailable() || !isActive()) {
57+
return (
58+
<SectionUnauthorized
59+
title={(
60+
<FormattedMessage
61+
id="xpack.crossClusterReplication.home.licenseErrorTitle"
62+
defaultMessage="License error"
63+
/>
64+
)}
65+
>
66+
{getReason()}
67+
{' '}
68+
<a href={chrome.addBasePath('/app/kibana#/management/elasticsearch/license_management/home')}>
69+
<FormattedMessage
70+
id="xpack.crossClusterReplication.home.licenseErrorLinkText"
71+
defaultMessage="Manage your license."
72+
/>
73+
</a>
74+
</SectionUnauthorized>
75+
);
76+
}
6577

78+
return (
79+
<div>
80+
<Switch>
81+
<Redirect exact from={`${BASE_PATH}`} to={`${BASE_PATH}/follower_indices`} />
82+
<Route exact path={`${BASE_PATH}/auto_follow_patterns/add`} component={AutoFollowPatternAdd} />
83+
<Route exact path={`${BASE_PATH}/auto_follow_patterns/edit/:id`} component={AutoFollowPatternEdit} />
84+
<Route exact path={`${BASE_PATH}/follower_indices/add`} component={FollowerIndexAdd} />
85+
<Route exact path={`${BASE_PATH}/follower_indices/edit/:id`} component={FollowerIndexEdit} />
86+
<Route exact path={`${BASE_PATH}/:section`} component={CrossClusterReplicationHome} />
87+
</Switch>
88+
</div>
89+
);
90+
}
91+
}
92+
);

x-pack/plugins/cross_cluster_replication/public/app/components/section_unauthorized.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,10 @@
55
*/
66

77
import React, { Fragment } from 'react';
8-
import { injectI18n } from '@kbn/i18n/react';
98

109
import { EuiCallOut } from '@elastic/eui';
1110

12-
export function SectionUnauthorizedUI({ intl, children }) {
13-
const title = intl.formatMessage({
14-
id: 'xpack.crossClusterReplication.remoteClusterList.noPermissionTitle',
15-
defaultMessage: 'Permission error',
16-
});
11+
export function SectionUnauthorized({ title, children }) {
1712
return (
1813
<Fragment>
1914
<EuiCallOut
@@ -26,5 +21,3 @@ export function SectionUnauthorizedUI({ intl, children }) {
2621
</Fragment>
2722
);
2823
}
29-
30-
export const SectionUnauthorized = injectI18n(SectionUnauthorizedUI);

x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.container.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@
77
import { connect } from 'react-redux';
88

99
import { SECTIONS } from '../../constants';
10-
import { getListAutoFollowPatterns, getListFollowerIndices, isApiAuthorized } from '../../store/selectors';
10+
import { isApiAuthorized } from '../../store/selectors';
1111
import { CrossClusterReplicationHome as CrossClusterReplicationHomeView } from './home';
1212

1313
const mapStateToProps = (state) => ({
14-
autoFollowPatterns: getListAutoFollowPatterns(state),
1514
isAutoFollowApiAuthorized: isApiAuthorized(SECTIONS.AUTO_FOLLOW_PATTERN)(state),
16-
followerIndices: getListFollowerIndices(state),
1715
isFollowerIndexApiAuthorized: isApiAuthorized(SECTIONS.FOLLOWER_INDEX)(state),
1816
});
1917

x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js

Lines changed: 85 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { Route, Switch } from 'react-router-dom';
1010
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
1111
import chrome from 'ui/chrome';
1212
import { MANAGEMENT_BREADCRUMB } from 'ui/management';
13-
import { BASE_PATH } from '../../../../common/constants';
1413

1514
import {
1615
EuiButton,
@@ -25,6 +24,7 @@ import {
2524
EuiTitle,
2625
} from '@elastic/eui';
2726

27+
import { BASE_PATH } from '../../../../common/constants';
2828
import { listBreadcrumb } from '../../services/breadcrumbs';
2929
import routing from '../../services/routing';
3030
import { AutoFollowPatternList } from './auto_follow_pattern_list';
@@ -34,9 +34,7 @@ import { SectionUnauthorized } from '../../components';
3434
export const CrossClusterReplicationHome = injectI18n(
3535
class extends PureComponent {
3636
static propTypes = {
37-
autoFollowPatterns: PropTypes.array,
3837
isAutoFollowApiAuthorized: PropTypes.bool,
39-
followerIndices: PropTypes.array,
4038
isFollowerIndexApiAuthorized: PropTypes.bool,
4139
}
4240

@@ -77,16 +75,8 @@ export const CrossClusterReplicationHome = injectI18n(
7775
routing.navigate(`/${section}`);
7876
}
7977

80-
getHeaderSection() {
81-
if(this.state.activeSection === 'follower_indices') {
82-
const { isFollowerIndexApiAuthorized, followerIndices } = this.props;
83-
84-
85-
// We want to show the title when the user isn't authorized.
86-
if (isFollowerIndexApiAuthorized && !followerIndices.length) {
87-
return null;
88-
}
89-
78+
renderHeaderSection() {
79+
if (this.state.activeSection === 'follower_indices') {
9080
return (
9181
<Fragment>
9282
<EuiFlexGroup justifyContent="spaceBetween" alignItems="flexStart">
@@ -102,81 +92,103 @@ export const CrossClusterReplicationHome = injectI18n(
10292
</EuiFlexItem>
10393

10494
<EuiFlexItem grow={false}>
105-
{isFollowerIndexApiAuthorized && (
106-
<EuiButton
107-
{...routing.getRouterLinkProps('/follower_indices/add')}
108-
fill
109-
iconType="plusInCircle"
110-
>
111-
<FormattedMessage
112-
id="xpack.crossClusterReplication.followerIndexList.addFollowerButtonLabel"
113-
defaultMessage="Create a follower index"
114-
/>
115-
</EuiButton>
116-
)}
95+
<EuiButton
96+
{...routing.getRouterLinkProps('/follower_indices/add')}
97+
fill
98+
iconType="plusInCircle"
99+
>
100+
<FormattedMessage
101+
id="xpack.crossClusterReplication.followerIndexList.addFollowerButtonLabel"
102+
defaultMessage="Create a follower index"
103+
/>
104+
</EuiButton>
117105
</EuiFlexItem>
118106
</EuiFlexGroup>
119107

120108
<EuiSpacer />
121109
</Fragment>
122110
);
123-
} else {
124-
const { isAutoFollowApiAuthorized, autoFollowPatterns } = this.props;
125-
126-
// We want to show the title when the user isn't authorized.
127-
if (isAutoFollowApiAuthorized && !autoFollowPatterns.length) {
128-
return null;
129-
}
130-
131-
return (
132-
<Fragment>
133-
<EuiFlexGroup justifyContent="spaceBetween" alignItems="flexStart">
134-
<EuiFlexItem grow={false}>
135-
<EuiText>
136-
<p>
137-
<FormattedMessage
138-
id="xpack.crossClusterReplication.autoFollowPatternList.autoFollowPatternsDescription"
139-
defaultMessage="Auto-follow patterns replicate leader indices from a remote
140-
cluster to follower indices on the local cluster."
141-
/>
142-
</p>
143-
</EuiText>
144-
</EuiFlexItem>
111+
}
145112

146-
<EuiFlexItem grow={false}>
147-
{isAutoFollowApiAuthorized && (
148-
<EuiButton
149-
{...routing.getRouterLinkProps('/auto_follow_patterns/add')}
150-
fill
151-
iconType="plusInCircle"
152-
>
153-
<FormattedMessage
154-
id="xpack.crossClusterReplication.autoFollowPatternList.addAutoFollowPatternButtonLabel"
155-
defaultMessage="Create an auto-follow pattern"
156-
/>
157-
</EuiButton>
158-
)}
159-
</EuiFlexItem>
160-
</EuiFlexGroup>
113+
return (
114+
<Fragment>
115+
<EuiFlexGroup justifyContent="spaceBetween" alignItems="flexStart">
116+
<EuiFlexItem grow={false}>
117+
<EuiText>
118+
<p>
119+
<FormattedMessage
120+
id="xpack.crossClusterReplication.autoFollowPatternList.autoFollowPatternsDescription"
121+
defaultMessage="Auto-follow patterns replicate leader indices from a remote
122+
cluster to follower indices on the local cluster."
123+
/>
124+
</p>
125+
</EuiText>
126+
</EuiFlexItem>
127+
128+
<EuiFlexItem grow={false}>
129+
<EuiButton
130+
{...routing.getRouterLinkProps('/auto_follow_patterns/add')}
131+
fill
132+
iconType="plusInCircle"
133+
>
134+
<FormattedMessage
135+
id="xpack.crossClusterReplication.autoFollowPatternList.addAutoFollowPatternButtonLabel"
136+
defaultMessage="Create an auto-follow pattern"
137+
/>
138+
</EuiButton>
139+
</EuiFlexItem>
140+
</EuiFlexGroup>
161141

162-
<EuiSpacer />
163-
</Fragment>
164-
);
165-
}
142+
<EuiSpacer />
143+
</Fragment>
144+
);
166145
}
167146

168-
getUnauthorizedSection() {
169-
const { isAutoFollowApiAuthorized } = this.props;
170-
if (!isAutoFollowApiAuthorized) {
147+
renderContent() {
148+
const { isAutoFollowApiAuthorized, isFollowerIndexApiAuthorized } = this.props;
149+
150+
if (!isAutoFollowApiAuthorized || !isFollowerIndexApiAuthorized) {
171151
return (
172-
<SectionUnauthorized>
152+
<SectionUnauthorized
153+
title={(
154+
<FormattedMessage
155+
id="xpack.crossClusterReplication.home.permissionErrorTitle"
156+
defaultMessage="Permission error"
157+
/>
158+
)}
159+
>
173160
<FormattedMessage
174-
id="xpack.crossClusterReplication.autoFollowPatternList.noPermissionText"
175-
defaultMessage="You do not have permission to view or add auto-follow patterns."
161+
id="xpack.crossClusterReplication.home.permissionErrorText"
162+
defaultMessage="You do not have permission to view or add follower indices or auto-follow patterns."
176163
/>
177164
</SectionUnauthorized>
178165
);
179166
}
167+
168+
return (
169+
<Fragment>
170+
<EuiTabs>
171+
{this.tabs.map(tab => (
172+
<EuiTab
173+
onClick={() => this.onSectionChange(tab.id)}
174+
isSelected={tab.id === this.state.activeSection}
175+
key={tab.id}
176+
>
177+
{tab.name}
178+
</EuiTab>
179+
))}
180+
</EuiTabs>
181+
182+
<EuiSpacer size="m" />
183+
184+
{this.renderHeaderSection()}
185+
186+
<Switch>
187+
<Route exact path={`${BASE_PATH}/follower_indices`} component={FollowerIndicesList} />
188+
<Route exact path={`${BASE_PATH}/auto_follow_patterns`} component={AutoFollowPatternList} />
189+
</Switch>
190+
</Fragment>
191+
);
180192
}
181193

182194
render() {
@@ -194,27 +206,7 @@ export const CrossClusterReplicationHome = injectI18n(
194206

195207
<EuiSpacer size="s" />
196208

197-
<EuiTabs>
198-
{this.tabs.map(tab => (
199-
<EuiTab
200-
onClick={() => this.onSectionChange(tab.id)}
201-
isSelected={tab.id === this.state.activeSection}
202-
key={tab.id}
203-
>
204-
{tab.name}
205-
</EuiTab>
206-
))}
207-
</EuiTabs>
208-
209-
<EuiSpacer size="m" />
210-
211-
{this.getHeaderSection()}
212-
{this.getUnauthorizedSection()}
213-
214-
<Switch>
215-
<Route exact path={`${BASE_PATH}/follower_indices`} component={FollowerIndicesList} />
216-
<Route exact path={`${BASE_PATH}/auto_follow_patterns`} component={AutoFollowPatternList} />
217-
</Switch>
209+
{this.renderContent()}
218210
</EuiPageContent>
219211
</EuiPageBody>
220212
);

0 commit comments

Comments
 (0)