Skip to content

Commit e12a92a

Browse files
authored
[CCR] Improve form error handling and general UX (#29419)
1 parent 8669d6d commit e12a92a

File tree

15 files changed

+308
-318
lines changed

15 files changed

+308
-318
lines changed

src/server/sass/build.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import fs from 'fs';
2323
import sass from 'node-sass';
2424
import autoprefixer from 'autoprefixer';
2525
import postcss from 'postcss';
26-
import postcssUrl from 'postcss-url';
26+
import postcssUrl from 'postcss-url'; // eslint-disable-line import/no-unresolved
2727
import mkdirp from 'mkdirp';
2828
import chalk from 'chalk';
2929
import isPathInside from 'is-path-inside';

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ export const FollowerIndexForm = injectI18n(
512512
<Fragment>
513513
<EuiSpacer size="s"/>
514514
{advancedSettingsFields.map((advancedSetting) => {
515-
const { field, title, description, label, helpText, defaultValue } = advancedSetting;
515+
const { field, title, description, label, helpText, defaultValue, type } = advancedSetting;
516516
return (
517517
<FormEntryRow
518518
key={field}
@@ -539,6 +539,7 @@ export const FollowerIndexForm = injectI18n(
539539
)}
540540
label={label}
541541
helpText={helpText}
542+
type={type}
542543
areErrorsVisible={areErrorsVisible}
543544
onValueUpdate={this.onFieldsChange}
544545
/>

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,14 @@ export class FormEntryRow extends PureComponent {
4949
onFieldChange = (value) => {
5050
const { field, onValueUpdate, type } = this.props;
5151
const isNumber = type === 'number';
52-
onValueUpdate({ [field]: isNumber ? parseInt(value, 10) : value });
52+
53+
let valueParsed = value;
54+
55+
if (isNumber) {
56+
valueParsed = !!value ? Math.max(0, parseInt(value, 10)) : value; // make sure we don't send NaN value or a negative number
57+
}
58+
59+
onValueUpdate({ [field]: valueParsed });
5360
}
5461

5562
renderField = (isInvalid) => {

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

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,10 @@ export const RemoteClustersFormField = injectI18n(
9999
const hasClusters = Boolean(remoteClusters.length);
100100
const remoteClustersOptions = hasClusters ? remoteClusters.map(({ name, isConnected }) => ({
101101
value: name,
102-
text: isConnected ? name : (
103-
<FormattedMessage
104-
id="xpack.crossClusterReplication.forms.remoteClusterDropdownNotConnected"
105-
defaultMessage="{name} (not connected)"
106-
values={{ name }}
107-
/>
108-
),
102+
text: isConnected ? name : this.props.intl.formatMessage({
103+
id: 'xpack.crossClusterReplication.forms.remoteClusterDropdownNotConnected',
104+
defaultMessage: '{name} (not connected)',
105+
}, { name }),
109106
'data-test-subj': `option-${name}`
110107
})) : [];
111108
const errorMessage = this.renderErrorMessage();

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import { AutoFollowPatternAdd as AutoFollowPatternAddView } from './auto_follow_
1414
const scope = SECTIONS.AUTO_FOLLOW_PATTERN;
1515

1616
const mapStateToProps = (state) => ({
17-
apiStatus: getApiStatus(scope)(state),
18-
apiError: getApiError(scope)(state),
17+
apiStatus: getApiStatus(`${scope}-save`)(state),
18+
apiError: getApiError(`${scope}-save`)(state),
1919
});
2020

2121
const mapDispatchToProps = dispatch => ({

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

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
AutoFollowPatternPageTitle,
2121
RemoteClustersProvider,
2222
SectionLoading,
23-
SectionError,
2423
} from '../../components';
2524

2625
export const AutoFollowPatternAdd = injectI18n(
@@ -41,7 +40,7 @@ export const AutoFollowPatternAdd = injectI18n(
4140
}
4241

4342
render() {
44-
const { saveAutoFollowPattern, apiStatus, apiError, intl, match: { url: currentUrl } } = this.props;
43+
const { saveAutoFollowPattern, apiStatus, apiError, match: { url: currentUrl } } = this.props;
4544

4645
return (
4746
<EuiPageContent>
@@ -67,20 +66,12 @@ export const AutoFollowPatternAdd = injectI18n(
6766
);
6867
}
6968

70-
if (error) {
71-
const title = intl.formatMessage({
72-
id: 'xpack.crossClusterReplication.autoFollowPatternCreateForm.loadingRemoteClustersErrorTitle',
73-
defaultMessage: 'Error loading remote clusters',
74-
});
75-
return <SectionError title={title} error={error} />;
76-
}
77-
7869
return (
7970
<AutoFollowPatternForm
8071
apiStatus={apiStatus}
8172
apiError={apiError}
8273
currentUrl={currentUrl}
83-
remoteClusters={remoteClusters}
74+
remoteClusters={error ? [] : remoteClusters}
8475
saveAutoFollowPattern={saveAutoFollowPattern}
8576
/>
8677
);

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

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export const AutoFollowPatternEdit = injectI18n(
119119
}
120120

121121
render() {
122-
const { saveAutoFollowPattern, apiStatus, apiError, autoFollowPattern, intl, match: { url: currentUrl } } = this.props;
122+
const { saveAutoFollowPattern, apiStatus, apiError, autoFollowPattern, match: { url: currentUrl } } = this.props;
123123

124124
return (
125125
<EuiPageContent
@@ -154,20 +154,12 @@ export const AutoFollowPatternEdit = injectI18n(
154154
);
155155
}
156156

157-
if (error) {
158-
const title = intl.formatMessage({
159-
id: 'xpack.crossClusterReplication.autoFollowPatternEditForm.loadingRemoteClustersErrorTitle',
160-
defaultMessage: 'Error loading remote clusters',
161-
});
162-
return <SectionError title={title} error={error} />;
163-
}
164-
165157
return (
166158
<AutoFollowPatternForm
167159
apiStatus={apiStatus.save}
168160
apiError={apiError.save}
169161
currentUrl={currentUrl}
170-
remoteClusters={remoteClusters}
162+
remoteClusters={error ? [] : remoteClusters}
171163
autoFollowPattern={autoFollowPattern}
172164
saveAutoFollowPattern={saveAutoFollowPattern}
173165
/>

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

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
FollowerIndexPageTitle,
2121
RemoteClustersProvider,
2222
SectionLoading,
23-
SectionError,
2423
} from '../../components';
2524

2625
export const FollowerIndexAdd = injectI18n(
@@ -41,7 +40,7 @@ export const FollowerIndexAdd = injectI18n(
4140
}
4241

4342
render() {
44-
const { saveFollowerIndex, clearApiError, apiStatus, apiError, intl, match: { url: currentUrl } } = this.props;
43+
const { saveFollowerIndex, clearApiError, apiStatus, apiError, match: { url: currentUrl } } = this.props;
4544

4645
return (
4746
<EuiPageContent
@@ -70,20 +69,12 @@ export const FollowerIndexAdd = injectI18n(
7069
);
7170
}
7271

73-
if (error) {
74-
const title = intl.formatMessage({
75-
id: 'xpack.crossClusterReplication.followerIndexCreateForm.loadingRemoteClustersErrorTitle',
76-
defaultMessage: 'Error loading remote clusters',
77-
});
78-
return <SectionError title={title} error={error} />;
79-
}
80-
8172
return (
8273
<FollowerIndexForm
8374
apiStatus={apiStatus}
8475
apiError={apiError}
8576
currentUrl={currentUrl}
86-
remoteClusters={remoteClusters}
77+
remoteClusters={error ? [] : remoteClusters}
8778
saveFollowerIndex={saveFollowerIndex}
8879
clearApiError={clearApiError}
8980
/>

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -242,17 +242,13 @@ export const FollowerIndexEdit = injectI18n(
242242
);
243243
}
244244

245-
if (error) {
246-
remoteClusters = [];
247-
}
248-
249245
return (
250246
<FollowerIndexForm
251247
followerIndex={rest}
252248
apiStatus={apiStatus.save}
253249
apiError={apiError.save}
254250
currentUrl={currentUrl}
255-
remoteClusters={remoteClusters}
251+
remoteClusters={error ? [] : remoteClusters}
256252
saveFollowerIndex={this.saveFollowerIndex}
257253
clearApiError={clearApiError}
258254
/>

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

Lines changed: 90 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,19 @@
77
import React, { PureComponent, Fragment } from 'react';
88
import PropTypes from 'prop-types';
99
import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
10-
import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
10+
import {
11+
EuiButton,
12+
EuiEmptyPrompt,
13+
EuiFlexGroup,
14+
EuiFlexItem,
15+
EuiText,
16+
EuiSpacer,
17+
} from '@elastic/eui';
1118

1219
import routing from '../../../services/routing';
1320
import { extractQueryParams } from '../../../services/query_params';
1421
import { API_STATUS } from '../../../constants';
15-
import { SectionLoading, SectionError } from '../../../components';
22+
import { SectionLoading, SectionError, SectionUnauthorized } from '../../../components';
1623
import { AutoFollowPatternTable, DetailPanel } from './components';
1724

1825
const REFRESH_RATE_MS = 30000;
@@ -88,6 +95,79 @@ export const AutoFollowPatternList = injectI18n(
8895
clearInterval(this.interval);
8996
}
9097

98+
renderHeader() {
99+
const { isAuthorized } = this.props;
100+
return (
101+
<Fragment>
102+
<EuiFlexGroup justifyContent="spaceBetween" alignItems="flexStart">
103+
<EuiFlexItem grow={false}>
104+
<EuiText>
105+
<p>
106+
<FormattedMessage
107+
id="xpack.crossClusterReplication.autoFollowPatternList.autoFollowPatternsDescription"
108+
defaultMessage="Auto-follow patterns replicate leader indices from a remote
109+
cluster to follower indices on the local cluster."
110+
/>
111+
</p>
112+
</EuiText>
113+
</EuiFlexItem>
114+
115+
<EuiFlexItem grow={false}>
116+
{isAuthorized && (
117+
<EuiButton
118+
{...routing.getRouterLinkProps('/auto_follow_patterns/add')}
119+
fill
120+
iconType="plusInCircle"
121+
>
122+
<FormattedMessage
123+
id="xpack.crossClusterReplication.autoFollowPatternList.addAutoFollowPatternButtonLabel"
124+
defaultMessage="Create an auto-follow pattern"
125+
/>
126+
</EuiButton>
127+
)}
128+
</EuiFlexItem>
129+
</EuiFlexGroup>
130+
131+
<EuiSpacer />
132+
</Fragment>
133+
);
134+
}
135+
136+
renderContent(isEmpty) {
137+
const { apiError, isAuthorized, intl } = this.props;
138+
if (!isAuthorized) {
139+
return (
140+
<SectionUnauthorized
141+
title={(
142+
<FormattedMessage
143+
id="xpack.crossClusterReplication.autoFollowPatternList.permissionErrorTitle"
144+
defaultMessage="Permission error"
145+
/>
146+
)}
147+
>
148+
<FormattedMessage
149+
id="xpack.crossClusterReplication.autoFollowPatternList.noPermissionText"
150+
defaultMessage="You do not have permission to view or add auto-follow patterns."
151+
/>
152+
</SectionUnauthorized>
153+
);
154+
}
155+
156+
if (apiError) {
157+
const title = intl.formatMessage({
158+
id: 'xpack.crossClusterReplication.autoFollowPatternList.loadingErrorTitle',
159+
defaultMessage: 'Error loading auto-follow patterns',
160+
});
161+
return <SectionError title={title} error={apiError} />;
162+
}
163+
164+
if (isEmpty) {
165+
return this.renderEmpty();
166+
}
167+
168+
return this.renderList();
169+
}
170+
91171
renderEmpty() {
92172
return (
93173
<EuiEmptyPrompt
@@ -156,25 +236,15 @@ export const AutoFollowPatternList = injectI18n(
156236
}
157237

158238
render() {
159-
const { autoFollowPatterns, apiStatus, apiError, isAuthorized, intl } = this.props;
160-
161-
if (!isAuthorized) {
162-
return null;
163-
}
164-
165-
if (apiStatus === API_STATUS.IDLE && !autoFollowPatterns.length) {
166-
return this.renderEmpty();
167-
}
168-
169-
if (apiError) {
170-
const title = intl.formatMessage({
171-
id: 'xpack.crossClusterReplication.autoFollowPatternList.loadingErrorTitle',
172-
defaultMessage: 'Error loading auto-follow patterns',
173-
});
174-
return <SectionError title={title} error={apiError} />;
175-
}
239+
const { autoFollowPatterns, apiStatus, } = this.props;
240+
const isEmpty = apiStatus === API_STATUS.IDLE && !autoFollowPatterns.length;
176241

177-
return this.renderList();
242+
return (
243+
<Fragment>
244+
{!isEmpty && this.renderHeader()}
245+
{this.renderContent(isEmpty)}
246+
</Fragment>
247+
);
178248
}
179249
}
180250
);

0 commit comments

Comments
 (0)