fix: [Rules > Detection rules][AXE-CORE]: Buttons must have discernible text (#177273)

Closes: https://github.com/elastic/security-team/issues/8566
Closes: https://github.com/elastic/security-team/issues/8569

## Description
The `<RuleSwitch />` component is currently flagged by the axe browser
plugin for lacking text or an accessible label in its button switch.
This pull request introduces support for the addition of the
`aria-label` attribute to address this issue. Additionally, adjustments
are made in two instances where this component is utilized within the
codebase.

## Screens 

### Axe report 

![image](36287d4f-fd98-4b26-b313-a39a72aefb81)

### A11y label 

![image](c61c9d0d-dd6e-4af2-9d43-04e86ad21954)
This commit is contained in:
Alexey Antonov 2024-02-22 20:33:10 +02:00 committed by GitHub
parent bd311f3644
commit 3db4a8fa98
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 57 additions and 0 deletions

View file

@ -613,6 +613,7 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
enabled={isExistingRule && (rule?.enabled ?? false)}
startMlJobsIfNeeded={startMlJobsIfNeeded}
onChange={handleOnChangeEnabledRule}
ruleName={rule?.name}
/>
<EuiFlexItem>{i18n.ENABLE_RULE}</EuiFlexItem>
</EuiFlexGroup>

View file

@ -96,6 +96,7 @@ const useEnabledColumn = ({ hasCRUDPermissions, startMlJobs }: ColumnsProps): Ta
(isMlRule(rule.type) && !hasMlPermissions)
}
isLoading={loadingIds.includes(rule.id)}
ruleName={rule.name}
/>
</EuiToolTip>
),

View file

@ -74,6 +74,33 @@ describe('RuleSwitch', () => {
expect(wrapper.find('[data-test-subj="ruleSwitch"]').at(0).props().checked).toBeFalsy();
});
test('it sets the undefined aria-label for switch if ruleName not passed', () => {
const wrapper = mount(<RuleSwitchComponent enabled={true} id={'7'} />, {
wrappingComponent: TestProviders,
});
expect(
wrapper.find('[data-test-subj="ruleSwitch"]').at(0).props()['aria-label']
).toBeUndefined();
});
test('it sets the correct aria-label for switch if "enabled" is true', () => {
const wrapper = mount(<RuleSwitchComponent enabled={true} id={'7'} ruleName={'test'} />, {
wrappingComponent: TestProviders,
});
expect(wrapper.find('[data-test-subj="ruleSwitch"]').at(0).props()['aria-label']).toBe(
'Switch off "test"'
);
});
test('it sets the correct aria-label for switch if "enabled" is false', () => {
const wrapper = mount(<RuleSwitchComponent enabled={false} id={'7'} ruleName={'test'} />, {
wrappingComponent: TestProviders,
});
expect(wrapper.find('[data-test-subj="ruleSwitch"]').at(0).props()['aria-label']).toBe(
'Switch on "test"'
);
});
test('it dispatches error toaster if "enableRules" call rejects', async () => {
const mockError = new Error('uh oh');
(performBulkAction as jest.Mock).mockRejectedValue(mockError);

View file

@ -14,6 +14,7 @@ import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions';
import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction';
import { useExecuteBulkAction } from '../../../../detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action';
import { useRulesTableContextOptional } from '../../../../detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context';
import { ruleSwitchAriaLabel } from './translations';
const StaticSwitch = styled(EuiSwitch)`
.euiSwitch__thumb,
@ -31,6 +32,7 @@ export interface RuleSwitchProps {
isLoading?: boolean;
startMlJobsIfNeeded?: () => Promise<void>;
onChange?: (enabled: boolean) => void;
ruleName?: string;
}
/**
@ -43,8 +45,10 @@ export const RuleSwitchComponent = ({
enabled,
startMlJobsIfNeeded,
onChange,
ruleName,
}: RuleSwitchProps) => {
const [myIsLoading, setMyIsLoading] = useState(false);
const ariaLabel = ruleName ? ruleSwitchAriaLabel(ruleName, enabled) : undefined;
const rulesTableContext = useRulesTableContextOptional();
const { startTransaction } = useStartTransaction();
const { executeBulkAction } = useExecuteBulkAction({ suppressSuccessToast: !rulesTableContext });
@ -93,6 +97,7 @@ export const RuleSwitchComponent = ({
disabled={isDisabled}
checked={enabled}
onChange={onRuleStateChange}
aria-label={ariaLabel}
/>
)}
</EuiFlexItem>

View file

@ -0,0 +1,23 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
export const ruleSwitchAriaLabel = (name: string, isActive: boolean) =>
i18n.translate('xpack.securitySolution.ruleDetails.ruleSwitch.ariaLabel', {
values: {
name,
action: isActive
? i18n.translate('xpack.securitySolution.ruleDetails.ruleSwitch.ariaLabel.switchOff', {
defaultMessage: 'Switch off',
})
: i18n.translate('xpack.securitySolution.ruleDetails.ruleSwitch.ariaLabel.switchOn', {
defaultMessage: 'Switch on',
}),
},
defaultMessage: '{action} "{name}"',
});