Skip to content

Commit

Permalink
[Security Solution][Timeline] Timeline and Draggable Accessibility (a…
Browse files Browse the repository at this point in the history
…11y) Improvements (#85767) (#86081)

# [Security Solution][Timeline] Timeline and Draggable Accessibility (a11y) Improvements

This PR improves the accessibility of Timeline and the draggables used throughout the Security Solution.

- ⌨️ Keyboard support for all draggables
  - 🖱 Click on any draggable with the mouse to give it keyboard focus

  - 🕵️‍♀️ Press `Enter` to display the draggable's action / options menu (e.g. Filter value, Investigate in Timeline, Show Top N). The menu also displays hyperlinks to allow for keyboard navigation from draggables containing links.

  - 🚀 Press `Space` to start dragging via keyboard, and press `Space` again to drop

- 🏓 Timeline, + all Timeline-based tables (i.e. Detections, Host / Network Events, External alerts) support keyboard navigation
  - ⬆️ ⬇️ ⬅️ ➡️ arrow key navigation per the `Keyboard Support` section of [w3.org grid examples](https://www.w3.org/TR/wai-aria-practices-1.1/examples/grid/dataGrids.html)

  - 📉 📈 `page down` and `page up` support per the `Keyboard interactions` section of the [MDN ARIA grid role documentation](<https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Grid_Role>) (and also the [w3](https://www.w3.org/TR/wai-aria-practices-1.1/examples/grid/dataGrids.html))

  - 🏠 🔚 `home` and `end` moves focus to the first and last cell in the row, per [w3.org](https://www.w3.org/TR/wai-aria-practices-1.1/examples/grid/dataGrids.html) & [MDN](<https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Grid_Role>)

  - 🕹🏠 🕹🔚 `ctrl + home` and `ctrl + end` moves focus to the first and last cell in the row, per [w3.org](https://www.w3.org/TR/wai-aria-practices-1.1/examples/grid/dataGrids.html) & [MDN](<https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Grid_Role>)

- 🎉 Screenreader improvements for Timeline, + all Timeline-based tables, and draggables (tested with Voiceover for `macOS` version `10.15`)

  - 👂 Draggables implement custom screenreader announcements. For example, when a draggable field is selected, the screenreader reads a custom message that reads the _field name_ in addition to the value, to provide additional context. For example, selecting a field named `network_flow` may be read phonetically by the screen reader as:
  > event.action network underscore flow Press enter for actions, or press space to begin dragging

  - 📅 Timeline, + all Timeline-based tables provide an enhanced screenreader experience via the use of `EuiScreenReaderOnly` and [w3 grid roles](https://www.w3.org/TR/wai-aria-practices-1.1/examples/grid/dataGrids.html) to provide context. For example, after clicking an arrow key to select a cell, the screenreader reads:

  > You are in a table cell. row: 2, column: 5, event.module system Press enter for options, or press space to begin dragging.

  ![you-are-in-a-table-cell](https://user-images.githubusercontent.com/4459398/102074207-a7e95000-3dc1-11eb-8e01-5b14c72094c9.png)

  - 📰 Timeline's table supports Voiceover screenreader shortcuts that read all cells across a row, or all rows down a column. See <#83364> for details

  - 🍿 popovers and tooltips were enhanced via `EuiScreenReaderOnly` to provide additional context to screen readers

- 🪓 The [axe - Web Accessibility Testing Chrome extension](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd) was run interactively throughout the Security Solution to find and fix the highest-severity issues found by `axe`

- 🧹 Other keyboard-related improvements include

  - The `Customize Columns` popover traps keyboard focus, and allows navigation of content via arrow keys & home / end

  - The `Event details` view allows navigation of content via arrow keys & home / end

  - Improvements to keyboard-focus trapping in popovers throughout the Security Solution

This PR addresses the following issues:

- [X] [[Security Solution] Add support for Voiceover announcements In Timeline's table #83364](#83364)

- [X] [[Security app] Detections events list 'overflow' ... button and expand > button do not have hover over names #74140](#74140)

- [X] [SIEM app accessibility issues #64596](#64596)

- After merging with the _Timeline Evolution_ PR, the drop zone at the bottom of the screen stopped accepting draggables after the first draggable is dropped. The current workaround is to press `Enter` to open the draggables action menu, and select the `Investigate in timeline` action. When Timeline is open, the drag area at the top of the screen accepts additional draggables via keyboard. EDIT: I paired with @XavierM on this, and he has a fix for this issue (duplicate draggable IDs) in his PR.
  • Loading branch information
andrew-goldstein authored Dec 16, 2020
1 parent 27ad160 commit 01ba881
Show file tree
Hide file tree
Showing 141 changed files with 4,581 additions and 844 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Inspect, Maybe } from '../../../common';
import { TimelineRequestOptionsPaginated } from '../..';

export interface TimelineEventsDetailsItem {
ariaRowindex?: Maybe<number>;
category?: string;
field: string;
values?: Maybe<string[]>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import { openTimeline } from '../tasks/timelines';
import { OVERVIEW_URL } from '../urls/navigation';

// FLAKY: https://github.com/elastic/kibana/issues/79389
describe('Timelines', () => {
describe.skip('Timelines', () => {
let timelineId: string;

after(() => {
Expand All @@ -63,7 +63,9 @@ describe('Timelines', () => {
addFilter(timeline.filter);
pinFirstEvent();

cy.get(PIN_EVENT).should('have.attr', 'aria-label', 'Pinned event');
cy.get(PIN_EVENT)
.should('have.attr', 'aria-label')
.and('match', /Unpin the event in row 2/);
cy.get(LOCKED_ICON).should('be.visible');

addNameToTimeline(timeline.title);
Expand Down Expand Up @@ -92,7 +94,10 @@ describe('Timelines', () => {
cy.get(TIMELINE_DESCRIPTION).should('have.text', timeline.description);
cy.get(TIMELINE_QUERY).should('have.text', `${timeline.query} `);
cy.get(TIMELINE_FILTER(timeline.filter)).should('exist');
cy.get(PIN_EVENT).should('have.attr', 'aria-label', 'Pinned event');
cy.get(PIN_EVENT)
.should('have.attr', 'aria-label')
.and('match', /Unpin the event in row 2/);
cy.get(LOCKED_ICON).should('be.visible');
cy.get(NOTES_TAB_BUTTON).click();
cy.get(NOTES_TEXT_AREA).should('exist');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,16 @@ import { useCreateCaseModal } from '../use_create_case_modal';
import { useAllCasesModal } from '../use_all_cases_modal';

interface AddToCaseActionProps {
ariaLabel?: string;
ecsRowData: Ecs;
disabled: boolean;
}

const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({ ecsRowData, disabled }) => {
const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
ariaLabel = i18n.ACTION_ADD_TO_CASE_ARIA_LABEL,
ecsRowData,
disabled,
}) => {
const eventId = ecsRowData._id;
const eventIndex = ecsRowData._index;

Expand Down Expand Up @@ -120,7 +125,7 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({ ecsRowData,
content={i18n.ACTION_ADD_TO_CASE_TOOLTIP}
>
<EuiButtonIcon
aria-label={i18n.ACTION_ADD_TO_CASE_ARIA_LABEL}
aria-label={ariaLabel}
data-test-subj="attach-alert-to-case-button"
size="s"
iconType="folderClosed"
Expand All @@ -129,7 +134,7 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({ ecsRowData,
/>
</EuiToolTip>
),
[disabled, openPopover]
[ariaLabel, disabled, openPopover]
);

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { ariaIndexToArrayIndex, arrayIndexToAriaIndex } from './helpers';

describe('helpers', () => {
describe('ariaIndexToArrayIndex', () => {
it('returns the expected array index', () => {
expect(ariaIndexToArrayIndex(1)).toEqual(0);
});
});

describe('arrayIndexToAriaIndex', () => {
it('returns the expected aria index', () => {
expect(arrayIndexToAriaIndex(0)).toEqual(1);
});
});
});
Loading

0 comments on commit 01ba881

Please sign in to comment.