fix: [Obs Alerts > Rules][SCREEN READER] Icons and repeated controls need unique accessible labels: 0001 (#188075)

Closes: https://github.com/elastic/observability-dev/issues/3638

## Description
Observability has a lot of icons that are used for controls and table
row actions. These icons often have the same aria-label repeated across
rows. While this meets the letter of SC 1.3.1: Info and Relationships,
the repeated generic labels do not usually answer question what users
are editing, or what users are deleting. We want to provide clear labels
for each row to make the implicit relationships sighted users depend on,
explicit for screen reader users.

## What was changed?:
1. `title`, `arial-label` attributes were updated for mentioned places

## Screen 

<img width="1546" alt="image"
src="3bd626b4-2154-48b7-a6cd-22580cfd3d4b">

<img width="1546" alt="image"
src="2863c9d9-b09b-4150-9d02-b89a3c2fd9f6">
This commit is contained in:
Alexey Antonov 2024-07-16 15:59:34 +03:00 committed by GitHub
parent dfa0549b85
commit 5756b299a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 48 additions and 28 deletions

View file

@ -15,6 +15,7 @@ interface SandboxProps {
const mockSnoozeSettings: RuleSnoozeSettings = {
muteAll: true,
name: '',
};
export const RulesListNotifyBadgeSandbox = ({ triggersActionsUi }: SandboxProps) => {

View file

@ -898,10 +898,12 @@ describe('Detections Rules API', () => {
expect(result).toEqual({
'1': {
muteAll: false,
name: '',
activeSnoozes: [],
},
'2': {
muteAll: false,
name: '',
activeSnoozes: [],
isSnoozedUntil: new Date('2023-04-24T19:31:46.765Z'),
},

View file

@ -252,6 +252,7 @@ export const fetchRulesSnoozeSettings = async ({
return response.data?.reduce((result, { id, ...snoozeSettings }) => {
result[id] = {
name: snoozeSettings.name ?? '',
muteAll: snoozeSettings.mute_all ?? false,
activeSnoozes: snoozeSettings.active_snoozes ?? [],
isSnoozedUntil: snoozeSettings.is_snoozed_until

View file

@ -70,6 +70,7 @@ interface RuleSnoozeSettingsResponse {
* Rule's SO id
*/
id: string;
name: string;
mute_all: boolean;
snooze_schedule?: RuleSnooze;
active_snoozes?: string[];

View file

@ -191,8 +191,8 @@ describe('RulesTableContextProvider', () => {
{ id: '2', name: 'rule 2' },
] as Rule[],
rulesSnoozeSettings: {
'1': { muteAll: true, snoozeSchedule: [] },
'2': { muteAll: false, snoozeSchedule: [] },
'1': { muteAll: true, snoozeSchedule: [], name: 'rule 1' },
'2': { muteAll: false, snoozeSchedule: [], name: 'rule 2' },
},
});
@ -217,18 +217,20 @@ describe('RulesTableContextProvider', () => {
{ id: '2', name: 'rule 2' },
] as Rule[],
rulesSnoozeSettings: {
'1': { muteAll: true, snoozeSchedule: [] },
'2': { muteAll: false, snoozeSchedule: [] },
'1': { muteAll: true, snoozeSchedule: [], name: 'rule 1' },
'2': { muteAll: false, snoozeSchedule: [], name: 'rule 2' },
},
});
expect(state.rulesSnoozeSettings.data).toEqual({
'1': {
muteAll: true,
name: 'rule 1',
snoozeSchedule: [],
},
'2': {
muteAll: false,
name: 'rule 2',
snoozeSchedule: [],
},
});

View file

@ -42231,7 +42231,6 @@
"xpack.triggersActionsUI.sections.rulesList.collapsedItemActons.disableTitle": "Désactiver",
"xpack.triggersActionsUI.sections.rulesList.collapsedItemActons.editTitle": "Modifier la règle",
"xpack.triggersActionsUI.sections.rulesList.collapsedItemActons.enableTitle": "Activer",
"xpack.triggersActionsUI.sections.rulesList.collapsedItemActons.popoverButtonTitle": "Actions",
"xpack.triggersActionsUI.sections.rulesList.collapsedItemActons.snoozeActions": "Répéter les notifications",
"xpack.triggersActionsUI.sections.rulesList.daysLabel": "jours",
"xpack.triggersActionsUI.sections.rulesList.deleteSchedule": "Supprimer le calendrier",
@ -42268,14 +42267,12 @@
"xpack.triggersActionsUI.sections.rulesList.ruleParamBannerButton": "Afficher tout",
"xpack.triggersActionsUI.sections.rulesList.ruleParamBannerTitle": "Liste de règles filtrées par paramètres d'URL.",
"xpack.triggersActionsUI.sections.rulesList.rulesListNotifyBadge.noSnoozeAppliedTooltip": "Notifier lors de la génération d'alertes",
"xpack.triggersActionsUI.sections.rulesList.rulesListNotifyBadge.openSnoozePanel": "Ouvrir le panneau Répéter",
"xpack.triggersActionsUI.sections.rulesList.rulesListNotifyBadge.snoozedIndefinitelyTooltip": "Notifications répétées indéfiniment",
"xpack.triggersActionsUI.sections.rulesList.rulesListNotifyBadge.timeRemaining": "Temps restant",
"xpack.triggersActionsUI.sections.rulesList.rulesListSnoozePanel.snoozeFailed": "Impossible de modifier les paramètres de répétition de la règle",
"xpack.triggersActionsUI.sections.rulesList.rulesListSnoozePanel.snoozeSuccess": "Répétition de la notification des règles ajoutée",
"xpack.triggersActionsUI.sections.rulesList.rulesListSnoozePanel.unsnoozeSuccess": "Répétition de la notification des règles annulée",
"xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.apiKeyOwnerTitle": "Propriétaire de la clé d'API",
"xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.deleteAriaLabel": "Supprimer",
"xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.deleteButtonTooltip": "Supprimer",
"xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.durationTitle": "Durée",
"xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.durationTitleTooltip": "Temps passé sur l'exécution de la règle (mm:ss).",

View file

@ -42205,7 +42205,6 @@
"xpack.triggersActionsUI.sections.rulesList.collapsedItemActons.disableTitle": "無効にする",
"xpack.triggersActionsUI.sections.rulesList.collapsedItemActons.editTitle": "ルールを編集",
"xpack.triggersActionsUI.sections.rulesList.collapsedItemActons.enableTitle": "有効にする",
"xpack.triggersActionsUI.sections.rulesList.collapsedItemActons.popoverButtonTitle": "アクション",
"xpack.triggersActionsUI.sections.rulesList.collapsedItemActons.snoozeActions": "通知をスヌーズ",
"xpack.triggersActionsUI.sections.rulesList.daysLabel": "日",
"xpack.triggersActionsUI.sections.rulesList.deleteSchedule": "スケジュールを削除",
@ -42242,14 +42241,12 @@
"xpack.triggersActionsUI.sections.rulesList.ruleParamBannerButton": "すべて表示",
"xpack.triggersActionsUI.sections.rulesList.ruleParamBannerTitle": "ルールリストはURLパラメーターによってフィルタリングされます。",
"xpack.triggersActionsUI.sections.rulesList.rulesListNotifyBadge.noSnoozeAppliedTooltip": "アラートが生成されたときに通知",
"xpack.triggersActionsUI.sections.rulesList.rulesListNotifyBadge.openSnoozePanel": "スヌーズパネルを開く",
"xpack.triggersActionsUI.sections.rulesList.rulesListNotifyBadge.snoozedIndefinitelyTooltip": "通知は無期限にスヌーズされました",
"xpack.triggersActionsUI.sections.rulesList.rulesListNotifyBadge.timeRemaining": "残り時間",
"xpack.triggersActionsUI.sections.rulesList.rulesListSnoozePanel.snoozeFailed": "ルールスヌーズ設定を変更できません",
"xpack.triggersActionsUI.sections.rulesList.rulesListSnoozePanel.snoozeSuccess": "ルール通知が正常にスヌーズされました",
"xpack.triggersActionsUI.sections.rulesList.rulesListSnoozePanel.unsnoozeSuccess": "ルール通知が正常にスヌーズ解除されました",
"xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.apiKeyOwnerTitle": "APIキー所有者",
"xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.deleteAriaLabel": "削除",
"xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.deleteButtonTooltip": "削除",
"xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.durationTitle": "期間",
"xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.durationTitleTooltip": "ルールを実行するのにかかる時間mm:ss。",

View file

@ -42253,7 +42253,6 @@
"xpack.triggersActionsUI.sections.rulesList.collapsedItemActons.disableTitle": "禁用",
"xpack.triggersActionsUI.sections.rulesList.collapsedItemActons.editTitle": "编辑规则",
"xpack.triggersActionsUI.sections.rulesList.collapsedItemActons.enableTitle": "启用",
"xpack.triggersActionsUI.sections.rulesList.collapsedItemActons.popoverButtonTitle": "操作",
"xpack.triggersActionsUI.sections.rulesList.collapsedItemActons.snoozeActions": "暂停通知",
"xpack.triggersActionsUI.sections.rulesList.daysLabel": "天",
"xpack.triggersActionsUI.sections.rulesList.deleteSchedule": "删除计划",
@ -42290,14 +42289,12 @@
"xpack.triggersActionsUI.sections.rulesList.ruleParamBannerButton": "全部显示",
"xpack.triggersActionsUI.sections.rulesList.ruleParamBannerTitle": "按 URL 参数筛选的规则列表。",
"xpack.triggersActionsUI.sections.rulesList.rulesListNotifyBadge.noSnoozeAppliedTooltip": "生成告警时发送通知",
"xpack.triggersActionsUI.sections.rulesList.rulesListNotifyBadge.openSnoozePanel": "打开暂停面板",
"xpack.triggersActionsUI.sections.rulesList.rulesListNotifyBadge.snoozedIndefinitelyTooltip": "通知已无期限暂停",
"xpack.triggersActionsUI.sections.rulesList.rulesListNotifyBadge.timeRemaining": "剩余时间",
"xpack.triggersActionsUI.sections.rulesList.rulesListSnoozePanel.snoozeFailed": "无法更改规则暂停设置",
"xpack.triggersActionsUI.sections.rulesList.rulesListSnoozePanel.snoozeSuccess": "已成功暂停规则通知",
"xpack.triggersActionsUI.sections.rulesList.rulesListSnoozePanel.unsnoozeSuccess": "已成功取消暂停规则通知",
"xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.apiKeyOwnerTitle": "API 密钥所有者",
"xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.deleteAriaLabel": "删除",
"xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.deleteButtonTooltip": "删除",
"xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.durationTitle": "持续时间",
"xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.durationTitleTooltip": "运行规则所需的时间长度 (mm:ss)。",

View file

@ -129,7 +129,12 @@ export const CollapsedItemActions: React.FunctionComponent<ComponentOpts> = ({
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
aria-label={i18n.translate(
'xpack.triggersActionsUI.sections.rulesList.collapsedItemActons.popoverButtonTitle',
{ defaultMessage: 'Actions' }
{
defaultMessage: 'Actions for "{name}" column',
values: {
name: item.name,
},
}
)}
/>
);

View file

@ -27,6 +27,7 @@ describe('RulesListNotifyBadge', () => {
const wrapper = mountWithIntl(
<RulesListNotifyBadge
snoozeSettings={{
name: 'rule 1',
isSnoozedUntil: null,
muteAll: false,
}}
@ -47,6 +48,7 @@ describe('RulesListNotifyBadge', () => {
const wrapper = mountWithIntl(
<RulesListNotifyBadge
snoozeSettings={{
name: 'rule 1',
muteAll: false,
isSnoozedUntil: moment('1990-02-01').toDate(),
}}
@ -68,6 +70,7 @@ describe('RulesListNotifyBadge', () => {
const wrapper = mountWithIntl(
<RulesListNotifyBadge
snoozeSettings={{
name: 'rule 1',
muteAll: true,
isSnoozedUntil: moment('1990-02-01').toDate(),
}}
@ -88,6 +91,7 @@ describe('RulesListNotifyBadge', () => {
const wrapper = mountWithIntl(
<RulesListNotifyBadge
snoozeSettings={{
name: 'rule 1',
muteAll: false,
isSnoozedUntil: null,
}}
@ -124,6 +128,7 @@ describe('RulesListNotifyBadge', () => {
const wrapper = mountWithIntl(
<RulesListNotifyBadge
snoozeSettings={{
name: 'rule 1',
muteAll: true,
}}
onRuleChanged={onRuleChanged}

View file

@ -93,6 +93,11 @@ export const RulesListNotifyBadge: React.FunctionComponent<RulesListNotifyBadgeP
const focusTrapButtonRef = useRef<HTMLButtonElement>(null);
const snoozeButtonAriaLabel = useMemo(
() => (snoozeSettings?.name ? OPEN_SNOOZE_PANEL_ARIA_LABEL(snoozeSettings.name) : undefined),
[snoozeSettings]
);
const {
notifications: { toasts },
} = useKibana().services;
@ -189,7 +194,7 @@ export const RulesListNotifyBadge: React.FunctionComponent<RulesListNotifyBadgeP
isLoading={isLoading}
disabled={isLoading || isDisabled}
data-test-subj="rulesListNotifyBadge-snoozed"
aria-label={OPEN_SNOOZE_PANEL_ARIA_LABEL}
aria-label={snoozeButtonAriaLabel}
minWidth={85}
iconType="bellSlash"
color="accent"
@ -199,7 +204,7 @@ export const RulesListNotifyBadge: React.FunctionComponent<RulesListNotifyBadgeP
<EuiText size="xs">{formattedSnoozeText}</EuiText>
</EuiButton>
);
}, [formattedSnoozeText, isLoading, isDisabled, openPopover]);
}, [isLoading, isDisabled, snoozeButtonAriaLabel, openPopover, formattedSnoozeText]);
const scheduledSnoozeButton = useMemo(() => {
return (
@ -211,14 +216,14 @@ export const RulesListNotifyBadge: React.FunctionComponent<RulesListNotifyBadgeP
minWidth={85}
iconType="calendar"
color="text"
aria-label={OPEN_SNOOZE_PANEL_ARIA_LABEL}
aria-label={snoozeButtonAriaLabel}
onClick={openPopover}
buttonRef={focusTrapButtonRef}
>
<EuiText size="xs">{formattedSnoozeText}</EuiText>
</EuiButton>
);
}, [formattedSnoozeText, isLoading, isDisabled, openPopover]);
}, [isLoading, isDisabled, snoozeButtonAriaLabel, openPopover, formattedSnoozeText]);
const unsnoozedButton = useMemo(() => {
// This show on hover is needed because we need style sheets to achieve the
@ -232,14 +237,14 @@ export const RulesListNotifyBadge: React.FunctionComponent<RulesListNotifyBadgeP
disabled={isLoading || isDisabled}
display={isLoading ? 'base' : 'empty'}
data-test-subj="rulesListNotifyBadge-unsnoozed"
aria-label={OPEN_SNOOZE_PANEL_ARIA_LABEL}
aria-label={snoozeButtonAriaLabel}
className={isPopoverOpen || isLoading ? '' : showOnHoverClass}
iconType="bell"
onClick={openPopover}
buttonRef={focusTrapButtonRef}
/>
);
}, [isPopoverOpen, isLoading, isDisabled, showOnHover, openPopover]);
}, [showOnHover, isLoading, isDisabled, snoozeButtonAriaLabel, isPopoverOpen, openPopover]);
const indefiniteSnoozeButton = useMemo(() => {
return (
@ -249,14 +254,14 @@ export const RulesListNotifyBadge: React.FunctionComponent<RulesListNotifyBadgeP
disabled={isLoading || isDisabled}
display="base"
data-test-subj="rulesListNotifyBadge-snoozedIndefinitely"
aria-label={OPEN_SNOOZE_PANEL_ARIA_LABEL}
aria-label={snoozeButtonAriaLabel}
iconType="bellSlash"
color="accent"
onClick={openPopover}
buttonRef={focusTrapButtonRef}
/>
);
}, [isLoading, isDisabled, openPopover]);
}, [isLoading, isDisabled, snoozeButtonAriaLabel, openPopover]);
const button = useMemo(() => {
if (isScheduled) {

View file

@ -16,6 +16,7 @@ import { RulesListNotifyBadgePropsWithApi } from './types';
const rule = {
id: uuidv4(),
name: '',
activeSnoozes: [],
isSnoozedUntil: undefined,
muteAll: false,

View file

@ -28,10 +28,16 @@ export const SNOOZE_FAILED_MESSAGE = i18n.translate(
}
);
export const OPEN_SNOOZE_PANEL_ARIA_LABEL = i18n.translate(
'xpack.triggersActionsUI.sections.rulesList.rulesListNotifyBadge.openSnoozePanel',
{ defaultMessage: 'Open snooze panel' }
);
export const OPEN_SNOOZE_PANEL_ARIA_LABEL = (name: string) =>
i18n.translate(
'xpack.triggersActionsUI.sections.rulesList.rulesListNotifyBadge.openSnoozePanel',
{
defaultMessage: 'Open "{name}" snooze panel',
values: {
name,
},
}
);
const getSecondsTranslation = (value: number) =>
i18n.translate('xpack.triggersActionsUI.sections.rulesList.rulesListNotifyBadge.seconds', {

View file

@ -767,7 +767,7 @@ export const RulesListTable = (props: RulesListTableProps) => {
iconType={'trash'}
aria-label={i18n.translate(
'xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.deleteAriaLabel',
{ defaultMessage: 'Delete' }
{ defaultMessage: 'Delete "{name}"', values: { name: rule.name } }
)}
/>
</EuiFlexItem>

View file

@ -265,7 +265,7 @@ export type RuleUpdates = Omit<Rule, 'id' | 'executionStatus' | 'lastRun' | 'nex
export type RuleSnoozeSettings = Pick<
Rule,
'activeSnoozes' | 'isSnoozedUntil' | 'muteAll' | 'snoozeSchedule'
'activeSnoozes' | 'isSnoozedUntil' | 'muteAll' | 'snoozeSchedule' | 'name'
>;
export interface RuleTableItem extends Rule {