mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Fix a11y for remote clusters managment form validation errors (#39656)
Co-authored-by: Filipp Baranovskii <filipp_baranovskii@epam.com> Co-authored-by: Michail Yasonik <michail.yasonik@elastic.co> Co-authored-by: Alexey Antonov <alexwizp@gmail.com>
This commit is contained in:
parent
48e007aa07
commit
9a5afa06b2
3 changed files with 97 additions and 54 deletions
|
@ -17,12 +17,12 @@ Array [
|
|||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<h4
|
||||
<h2
|
||||
class="euiTitle euiTitle--small euiTitle euiTitle--xsmall euiDescribedFormGroup__title"
|
||||
id="mockId-title"
|
||||
>
|
||||
Name
|
||||
</h4>
|
||||
</h2>
|
||||
<div
|
||||
class="euiText euiText--small euiDescribedFormGroup__description"
|
||||
id="mockId"
|
||||
|
@ -89,12 +89,12 @@ Array [
|
|||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<h4
|
||||
<h2
|
||||
class="euiTitle euiTitle--small euiTitle euiTitle--xsmall euiDescribedFormGroup__title"
|
||||
id="mockId-title"
|
||||
>
|
||||
Seed nodes for cluster discovery
|
||||
</h4>
|
||||
</h2>
|
||||
<div
|
||||
class="euiText euiText--small euiDescribedFormGroup__description"
|
||||
id="mockId"
|
||||
|
@ -102,9 +102,7 @@ Array [
|
|||
<div
|
||||
class="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
<p>
|
||||
A list of remote cluster nodes to query for the cluster state. Specify multiple seed nodes so discovery doesn't fail if a node is unavailable.
|
||||
</p>
|
||||
A list of remote cluster nodes to query for the cluster state. Specify multiple seed nodes so discovery doesn't fail if a node is unavailable.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -197,12 +195,12 @@ Array [
|
|||
<div
|
||||
class="euiFlexItem"
|
||||
>
|
||||
<h4
|
||||
<h2
|
||||
class="euiTitle euiTitle--small euiTitle euiTitle--xsmall euiDescribedFormGroup__title"
|
||||
id="mockId-title"
|
||||
>
|
||||
Make remote cluster optional
|
||||
</h4>
|
||||
</h2>
|
||||
<div
|
||||
class="euiText euiText--small euiDescribedFormGroup__description"
|
||||
id="mockId"
|
||||
|
@ -292,6 +290,7 @@ Array [
|
|||
class="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<button
|
||||
aria-describedby="staticGenerator_removeClustersErrorTitle staticGenerator_removeClustersErrorList"
|
||||
class="euiButton euiButton--secondary euiButton--fill"
|
||||
data-test-subj="remoteClusterFormSaveButton"
|
||||
type="button"
|
||||
|
@ -494,27 +493,8 @@ Array [
|
|||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="euiCallOut euiCallOut--danger"
|
||||
class="euiSpacer euiSpacer--m"
|
||||
data-test-subj="remoteClusterFormGlobalError"
|
||||
>
|
||||
<div
|
||||
class="euiCallOutHeader"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="euiIcon euiIcon--medium euiIcon-isLoading euiCallOutHeader__icon"
|
||||
focusable="false"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
<span
|
||||
class="euiCallOutHeader__title"
|
||||
>
|
||||
Fix errors before continuing.
|
||||
</span>
|
||||
</div>
|
||||
</div>,
|
||||
/>,
|
||||
]
|
||||
`;
|
||||
|
|
|
@ -9,7 +9,6 @@ import PropTypes from 'prop-types';
|
|||
import { merge } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
|
@ -29,6 +28,9 @@ import {
|
|||
EuiSwitch,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
EuiDelayRender,
|
||||
EuiScreenReaderOnly,
|
||||
htmlIdGenerator,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import {
|
||||
|
@ -44,6 +46,9 @@ const defaultFields = {
|
|||
skipUnavailable: false,
|
||||
};
|
||||
|
||||
const ERROR_TITLE_ID = 'removeClustersErrorTitle';
|
||||
const ERROR_LIST_ID = 'removeClustersErrorList';
|
||||
|
||||
export class RemoteClusterForm extends Component {
|
||||
static propTypes = {
|
||||
save: PropTypes.func.isRequired,
|
||||
|
@ -65,6 +70,7 @@ export class RemoteClusterForm extends Component {
|
|||
const { fields, disabledFields } = props;
|
||||
const fieldsState = merge({}, defaultFields, fields);
|
||||
|
||||
this.generateId = htmlIdGenerator();
|
||||
this.state = {
|
||||
localSeedErrors: [],
|
||||
seedInput: '',
|
||||
|
@ -232,24 +238,20 @@ export class RemoteClusterForm extends Component {
|
|||
<EuiDescribedFormGroup
|
||||
title={(
|
||||
<EuiTitle size="s">
|
||||
<h4>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.sectionSeedsTitle"
|
||||
defaultMessage="Seed nodes for cluster discovery"
|
||||
/>
|
||||
</h4>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
)}
|
||||
description={(
|
||||
<Fragment>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.sectionSeedsDescription1"
|
||||
defaultMessage="A list of remote cluster nodes to query for the cluster state.
|
||||
Specify multiple seed nodes so discovery doesn't fail if a node is unavailable."
|
||||
/>
|
||||
</p>
|
||||
</Fragment>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.sectionSeedsDescription1"
|
||||
defaultMessage="A list of remote cluster nodes to query for the cluster state.
|
||||
Specify multiple seed nodes so discovery doesn't fail if a node is unavailable."
|
||||
/>
|
||||
)}
|
||||
fullWidth
|
||||
>
|
||||
|
@ -312,12 +314,12 @@ export class RemoteClusterForm extends Component {
|
|||
<EuiDescribedFormGroup
|
||||
title={(
|
||||
<EuiTitle size="s">
|
||||
<h4>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.sectionSkipUnavailableTitle"
|
||||
defaultMessage="Make remote cluster optional"
|
||||
/>
|
||||
</h4>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
)}
|
||||
description={(
|
||||
|
@ -434,6 +436,7 @@ export class RemoteClusterForm extends Component {
|
|||
onClick={this.save}
|
||||
fill
|
||||
disabled={isSaveDisabled}
|
||||
aria-describedby={`${this.generateId(ERROR_TITLE_ID)} ${this.generateId(ERROR_LIST_ID)}`}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.saveButtonLabel"
|
||||
|
@ -486,7 +489,7 @@ export class RemoteClusterForm extends Component {
|
|||
<EuiCallOut
|
||||
title={message}
|
||||
icon="cross"
|
||||
color="danger"
|
||||
color="warning"
|
||||
>
|
||||
{errorBody}
|
||||
</EuiCallOut>
|
||||
|
@ -500,27 +503,84 @@ export class RemoteClusterForm extends Component {
|
|||
}
|
||||
|
||||
renderErrors = () => {
|
||||
const { areErrorsVisible } = this.state;
|
||||
const {
|
||||
areErrorsVisible,
|
||||
fieldsErrors: {
|
||||
name: errorClusterName,
|
||||
seeds: errorsSeeds,
|
||||
},
|
||||
localSeedErrors,
|
||||
} = this.state;
|
||||
|
||||
const hasErrors = this.hasErrors();
|
||||
|
||||
if (!areErrorsVisible || !hasErrors) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const errorExplanations = [];
|
||||
|
||||
if (errorClusterName) {
|
||||
errorExplanations.push({
|
||||
key: 'nameExplanation',
|
||||
field: i18n.translate('xpack.remoteClusters.remoteClusterForm.inputNameErrorMessage', {
|
||||
defaultMessage: 'The "Name" field is invalid.',
|
||||
}),
|
||||
error: errorClusterName
|
||||
});
|
||||
}
|
||||
|
||||
if (errorsSeeds) {
|
||||
errorExplanations.push({
|
||||
key: 'seedsExplanation',
|
||||
field: i18n.translate('xpack.remoteClusters.remoteClusterForm.inputSeedsErrorMessage', {
|
||||
defaultMessage: 'The "Seed nodes" field is invalid.',
|
||||
}),
|
||||
error: errorsSeeds
|
||||
});
|
||||
}
|
||||
|
||||
if (localSeedErrors && localSeedErrors.length) {
|
||||
errorExplanations.push({
|
||||
key: 'localSeedExplanation',
|
||||
field: i18n.translate('xpack.remoteClusters.remoteClusterForm.inputLocalSeedErrorMessage', {
|
||||
defaultMessage: 'The "Seed nodes" field is invalid.',
|
||||
}),
|
||||
error: localSeedErrors.join(' '),
|
||||
});
|
||||
}
|
||||
|
||||
const messagesToBeRendered = errorExplanations.length && (
|
||||
<EuiScreenReaderOnly>
|
||||
<dl id={this.generateId(ERROR_LIST_ID)} aria-labelledby={this.generateId(ERROR_TITLE_ID)}>
|
||||
{errorExplanations.map(({ key, field, error }) => (
|
||||
<div key={key}>
|
||||
<dt>{field}</dt>
|
||||
<dd>{error}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</EuiScreenReaderOnly>
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiSpacer size="m" data-test-subj="remoteClusterFormGlobalError" />
|
||||
<EuiCallOut
|
||||
data-test-subj="remoteClusterFormGlobalError"
|
||||
title={(
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.errorTitle"
|
||||
defaultMessage="Fix errors before continuing."
|
||||
/>
|
||||
<h3 id={this.generateId(ERROR_TITLE_ID)}>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.errorTitle"
|
||||
defaultMessage="Fix errors before continuing."
|
||||
/>
|
||||
</h3>
|
||||
)}
|
||||
color="danger"
|
||||
iconType="cross"
|
||||
/>
|
||||
<EuiDelayRender>
|
||||
{messagesToBeRendered}
|
||||
</EuiDelayRender>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
@ -550,12 +610,12 @@ export class RemoteClusterForm extends Component {
|
|||
<EuiDescribedFormGroup
|
||||
title={(
|
||||
<EuiTitle size="s">
|
||||
<h4>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.remoteClusterForm.sectionNameTitle"
|
||||
defaultMessage="Name"
|
||||
/>
|
||||
</h4>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
)}
|
||||
description={(
|
||||
|
|
|
@ -11,6 +11,9 @@ import { RemoteClusterForm } from './remote_cluster_form';
|
|||
|
||||
// Make sure we have deterministic aria IDs.
|
||||
jest.mock('@elastic/eui/lib/components/form/form_row/make_id', () => () => 'mockId');
|
||||
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
|
||||
htmlIdGenerator: (prefix = 'staticGenerator') => (suffix = 'staticId') => `${prefix}_${suffix}`
|
||||
}));
|
||||
|
||||
describe('RemoteClusterForm', () => {
|
||||
test(`renders untouched state`, () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue