From c7b28c531f10e69b1fcb82ccdba261a969360457 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Wed, 16 Jan 2019 18:00:09 -0800 Subject: [PATCH] Ignore advanced settings input when that section is hidden. - Cache and restore input when the section is shown again. - Add 'Advanced settings' section title and description. - Fix heading hierarchy so there are no gaps between levels. - Move validation out of form_entry_row and into follower_index_form so we can revalidate the entire form whenever the a field's input changes. --- .../follower_index_form.js | 220 +++++++++++++----- .../public/app/components/form_entry_row.js | 42 +--- 2 files changed, 167 insertions(+), 95 deletions(-) diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.js index 261d1c19e3462..d0a1951b6fa16 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_form/follower_index_form.js @@ -22,16 +22,17 @@ import { EuiFlexItem, EuiForm, EuiFormRow, + EuiHorizontalRule, EuiLoadingKibana, EuiLoadingSpinner, EuiOverlayMask, EuiSpacer, + EuiSuperSelect, EuiText, EuiTitle, - EuiSuperSelect, } from '@elastic/eui'; -import { indexNameValidator } from '../../services/input_validation'; +import { indexNameValidator, i18nValidationErrorMessages } from '../../services/input_validation'; import routing from '../../services/routing'; import { loadIndices } from '../../services/api'; import { API_STATUS } from '../../constants'; @@ -77,6 +78,17 @@ export const updateFormErrors = (errors) => ({ fieldsErrors }) => ({ } }); +const parseError = (err) => { + if (!err) { + return null; + } + + const error = err.details[0]; + const { type, context } = error; + const message = i18nValidationErrorMessages[type](context); + return { message }; +}; + export const FollowerIndexForm = injectI18n( class extends PureComponent { static propTypes = { @@ -114,14 +126,27 @@ export const FollowerIndexForm = injectI18n( onFieldsChange = (fields) => { this.setState(updateFields(fields)); + this.setState(updateFormErrors(this.getFieldsErrors(fields))); if (this.props.apiError) { this.props.clearApiError(); } }; - onFieldsErrorChange = (errors) => { - this.setState(updateFormErrors(errors)); + getFieldsErrors = (changedFields) => { + const newFields = { + ...this.state.fields, + ...changedFields, + }; + + return advancedSettingsFields.reduce((errors, advancedSetting) => { + const { field, validator, label } = advancedSetting; + const value = newFields[field]; + const result = validator.label(label).validate(value); + const error = parseError(result.error); + errors[field] = error; + return errors; + }, {}); }; onIndexNameChange = ({ name }) => { @@ -186,7 +211,34 @@ export const FollowerIndexForm = injectI18n( }; toggleAdvancedSettings = () => { - this.setState(({ areAdvancedSettingsVisible }) => ({ areAdvancedSettingsVisible: !areAdvancedSettingsVisible })); + this.setState(({ areAdvancedSettingsVisible, cachedAdvancedSettings }) => { + // Hide settings, clear fields, and create cache. + if (areAdvancedSettingsVisible) { + const fields = this.getFields(); + + const newCachedAdvancedSettings = advancedSettingsFields.reduce((cache, { field }) => { + const value = fields[field]; + if (value !== '') { + cache[field] = value; + } + return cache; + }, {}); + + this.onFieldsChange(emptyAdvancedSettings); + + return { + areAdvancedSettingsVisible: false, + cachedAdvancedSettings: newCachedAdvancedSettings, + }; + } + + // Show settings and restore fields from the cache. + this.onFieldsChange(cachedAdvancedSettings); + return { + areAdvancedSettingsVisible: true, + cachedAdvancedSettings: {}, + }; + }); } isFormValid() { @@ -249,19 +301,6 @@ export const FollowerIndexForm = injectI18n( isValidatingIndexName, } = this.state; - const toggleAdvancedSettingButtonLabel = areAdvancedSettingsVisible - ? ( - - ) : ( - - ); - /** * Follower index name */ @@ -286,14 +325,23 @@ export const FollowerIndexForm = injectI18n( ); + const indexNameLabel = i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.sectionFollowerIndexNameTitle', { + defaultMessage: 'Name' + } + ); + const renderFollowerIndexName = () => ( +

{indexNameLabel}

+ + )} + label={indexNameLabel} description={i18n.translate('xpack.crossClusterReplication.followerIndexForm.sectionFollowerIndexNameDescription', { defaultMessage: 'A name for the follower index.' })} @@ -303,7 +351,6 @@ export const FollowerIndexForm = injectI18n( disabled={!isNew} areErrorsVisible={areErrorsVisible} onValueUpdate={this.onIndexNameChange} - onErrorUpdate={this.onFieldsErrorChange} /> ); @@ -322,12 +369,12 @@ export const FollowerIndexForm = injectI18n( -

+

-

+ )} description={( @@ -353,6 +400,7 @@ export const FollowerIndexForm = injectI18n( options={remoteClustersOptions} valueOfSelected={followerIndex.remoteCluster} onChange={this.onClusterChange} + fullWidth /> )} { !isNew && ( @@ -371,14 +419,24 @@ export const FollowerIndexForm = injectI18n( /** * Leader index */ + + const leaderIndexLabel = i18n.translate( + 'xpack.crossClusterReplication.followerIndexForm.sectionLeaderIndexTitle', { + defaultMessage: 'Leader index' + } + ); + const renderLeaderIndex = () => ( +

{leaderIndexLabel}

+ + )} + label={leaderIndexLabel} description={i18n.translate('xpack.crossClusterReplication.followerIndexForm.sectionLeaderIndexDescription', { defaultMessage: 'The leader index you want to replicate from the remote cluster.' })} @@ -393,43 +451,91 @@ export const FollowerIndexForm = injectI18n( disabled={!isNew} areErrorsVisible={areErrorsVisible} onValueUpdate={this.onFieldsChange} - onErrorUpdate={this.onFieldsErrorChange} /> ); /** * Advanced settings */ + + const toggleAdvancedSettingButtonLabel = areAdvancedSettingsVisible + ? ( + + ) : ( + + ); + const renderAdvancedSettings = () => ( - - { toggleAdvancedSettingButtonLabel } - - + + + +

+ +

+ + )} + description={( + +

+ +

+ + + { toggleAdvancedSettingButtonLabel } + +
+ )} + fullWidth + /> + {areAdvancedSettingsVisible && ( - advancedSettingsFields.map((advancedSetting) => { - const { field, label, description, helpText, validator } = advancedSetting; - return ( - - ); - }) + + + + {advancedSettingsFields.map((advancedSetting) => { + const { field, label, description, helpText, validator } = advancedSetting; + return ( + +

{label}

+ + )} + label={label} + description={description} + helpText={helpText} + validator={validator} + areErrorsVisible={areErrorsVisible} + onValueUpdate={this.onFieldsChange} + /> + ); + })} +
)} + +
); @@ -446,7 +552,6 @@ export const FollowerIndexForm = injectI18n( return ( - + + ); }; @@ -530,9 +637,8 @@ export const FollowerIndexForm = injectI18n( {renderAdvancedSettings()} - + {renderFormErrorWarning()} - {renderActions()} ); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/form_entry_row.js b/x-pack/plugins/cross_cluster_replication/public/app/components/form_entry_row.js index b667e108a57ac..07035fae61f6d 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/form_entry_row.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/form_entry_row.js @@ -4,21 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ - import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import { debounce } from 'lodash'; import { - EuiTitle, EuiFieldText, EuiFieldNumber, EuiDescribedFormGroup, EuiFormRow, } from '@elastic/eui'; -import { i18nValidationErrorMessages } from '../services/input_validation'; - /** * State transitions: fields update */ @@ -29,55 +24,29 @@ export const updateFields = (newValues) => ({ fields }) => ({ }, }); -const parseError = (err) => { - if (!err) { - return null; - } - - const [error] = err.details; // Use the first error in the details array (error.details[0]) - const { type, context } = error; - const message = i18nValidationErrorMessages[type](context); - return { message }; -}; - export class FormEntryRow extends PureComponent { static propTypes = { + title: PropTypes.node, label: PropTypes.node, description: PropTypes.node, helpText: PropTypes.node, validator: PropTypes.object, onValueUpdate: PropTypes.func.isRequired, - onErrorUpdate: PropTypes.func.isRequired, field: PropTypes.string.isRequired, value: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]).isRequired, isLoading: PropTypes.bool, - error: PropTypes.object, + error: PropTypes.node, disabled: PropTypes.bool, areErrorsVisible: PropTypes.bool.isRequired, }; - componentDidMount() { - this.validateField(this.props.value); - this.validateField = debounce(this.validateField.bind(this), 300); - } - onFieldChange = (value) => { const { field, onValueUpdate, validator } = this.props; const isNumber = validator._type === 'number'; onValueUpdate({ [field]: isNumber ? parseInt(value, 10) : value }); - - this.validateField(value); - } - - validateField = (value) => { - const { field, validator, label, onErrorUpdate } = this.props; - const result = validator.label(label).validate(value); - const error = parseError(result.error); - - onErrorUpdate({ [field]: error }); } renderField = (isInvalid) => { @@ -112,6 +81,7 @@ export class FormEntryRow extends PureComponent { const { field, error, + title, label, description, helpText, @@ -123,11 +93,7 @@ export class FormEntryRow extends PureComponent { return ( -

{label}

- - )} + title={title} description={description} fullWidth key={field}