Skip to content

Commit

Permalink
fix: [Rules > Add Elastic rules][SCREEN READER]: Table headers must b…
Browse files Browse the repository at this point in the history
…e programmatically determinable (elastic#178765)

Closes: elastic/security-team#8654
Closes: elastic/security-team#8645
Closes: elastic/security-team#8641
Closes: elastic/security-team#8647

## Description

The Add Elastic Rules table has three columns that do not include table
header text. Screen reader users depend on these column headers to make
the column -> data cell information relationship. Screenshot attached
below.

### Steps to recreate

1. Open [Add Elastic
rules](https://kibana.siem.estc.dev/app/security/rules/add_rules)
2. Ensure at least 1 rule is not added to monitoring so the table
populates
3. Turn on your preferred screen reader and skip to the table (screen
reader shortcuts in the vendor guidance below)
4. Navigate the table by cell to cell and verify the Integrations, Tags,
and Actions cells do not announce a table header

### Screenshots or Trace Logs
<img width="1498" alt="Screenshot 2024-02-09 at 3 09 20 PM"
src="https://github.com/elastic/security-team/assets/934879/0f390da2-1b70-450f-83f4-c7cb62632f1e">

### Solution 

- `RulesTableEmptyColumnName` component was added and applied to all
columns with `name = null || ""`

<img width="1170" alt="image"
src="https://github.com/elastic/kibana/assets/20072247/54c9544b-1add-443f-905a-0a2e766eea63">
  • Loading branch information
alexwizp authored Mar 18, 2024
1 parent e0adf1d commit 98c069f
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import type { EuiBasicTableColumn } from '@elastic/eui';
import { EuiButtonEmpty, EuiBadge, EuiText, EuiLoadingSpinner, EuiLink } from '@elastic/eui';
import React, { useMemo } from 'react';
import { RulesTableEmptyColumnName } from '../rules_table_empty_column_name';
import { SHOW_RELATED_INTEGRATIONS_SETTING } from '../../../../../../common/constants';
import { PopoverItems } from '../../../../../common/components/popover_items';
import { useUiSetting$ } from '../../../../../common/lib/kibana';
Expand Down Expand Up @@ -63,7 +64,7 @@ export const RULE_NAME_COLUMN: TableColumn = {

const TAGS_COLUMN: TableColumn = {
field: 'tags',
name: null,
name: <RulesTableEmptyColumnName name={i18n.COLUMN_TAGS} />,
align: 'center',
render: (tags: RuleResponse['tags']) => {
if (tags == null || tags.length === 0) {
Expand Down Expand Up @@ -92,7 +93,7 @@ const TAGS_COLUMN: TableColumn = {

const INTEGRATIONS_COLUMN: TableColumn = {
field: 'related_integrations',
name: null,
name: <RulesTableEmptyColumnName name={i18n.COLUMN_INTEGRATIONS} />,
align: 'center',
render: (integrations: RuleResponse['related_integrations']) => {
if (integrations == null || integrations.length === 0) {
Expand All @@ -111,7 +112,7 @@ const createInstallButtonColumn = (
isDisabled: boolean
): TableColumn => ({
field: 'rule_id',
name: '',
name: <RulesTableEmptyColumnName name={i18n.INSTALL_RULE_BUTTON} />,
render: (ruleId: RuleSignatureId, record: Rule) => {
const isRuleInstalling = loadingRules.includes(ruleId);
const isInstallButtonDisabled = isRuleInstalling || isDisabled;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 React, { type FC } from 'react';
import { EuiScreenReaderOnly } from '@elastic/eui';

interface RulesTableEmptyColumnNameProps {
name: string;
}

export const RulesTableEmptyColumnName: FC<RulesTableEmptyColumnNameProps> = React.memo(
({ name }) => {
return (
<EuiScreenReaderOnly>
<p>{name}</p>
</EuiScreenReaderOnly>
);
}
);

RulesTableEmptyColumnName.displayName = 'RulesTableEmptyColumnName';
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import type { EuiBasicTableColumn } from '@elastic/eui';
import { EuiBadge, EuiButtonEmpty, EuiLink, EuiLoadingSpinner, EuiText } from '@elastic/eui';
import React, { useMemo } from 'react';
import { RulesTableEmptyColumnName } from '../rules_table_empty_column_name';
import { SHOW_RELATED_INTEGRATIONS_SETTING } from '../../../../../../common/constants';
import type { RuleUpgradeInfoForReview } from '../../../../../../common/api/detection_engine/prebuilt_rules';
import type { RuleSignatureId } from '../../../../../../common/api/detection_engine/model/rule_schema';
Expand Down Expand Up @@ -62,7 +63,7 @@ const RULE_NAME_COLUMN: TableColumn = {

const TAGS_COLUMN: TableColumn = {
field: 'current_rule.tags',
name: null,
name: <RulesTableEmptyColumnName name={i18n.COLUMN_TAGS} />,
align: 'center',
render: (tags: Rule['tags']) => {
if (tags == null || tags.length === 0) {
Expand Down Expand Up @@ -91,7 +92,7 @@ const TAGS_COLUMN: TableColumn = {

const INTEGRATIONS_COLUMN: TableColumn = {
field: 'current_rule.related_integrations',
name: null,
name: <RulesTableEmptyColumnName name={i18n.COLUMN_INTEGRATIONS} />,
align: 'center',
render: (integrations: Rule['related_integrations']) => {
if (integrations == null || integrations.length === 0) {
Expand All @@ -110,7 +111,7 @@ const createUpgradeButtonColumn = (
isDisabled: boolean
): TableColumn => ({
field: 'rule_id',
name: '',
name: <RulesTableEmptyColumnName name={i18n.UPDATE_RULE_BUTTON} />,
render: (ruleId: RuleUpgradeInfoForReview['rule_id']) => {
const isRuleUpgrading = loadingRules.includes(ruleId);
const isUpgradeButtonDisabled = isRuleUpgrading || isDisabled;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiLink, EuiText, EuiToolTip } fro
import { FormattedMessage } from '@kbn/i18n-react';
import moment from 'moment';
import React, { useMemo } from 'react';
import { RulesTableEmptyColumnName } from './rules_table_empty_column_name';
import type { SecurityJob } from '../../../../common/components/ml_popover/types';
import {
DEFAULT_RELATIVE_DATE_THRESHOLD,
Expand Down Expand Up @@ -188,7 +189,7 @@ const useRuleExecutionStatusColumn = ({

const TAGS_COLUMN: TableColumn = {
field: 'tags',
name: null,
name: <RulesTableEmptyColumnName name={i18n.COLUMN_TAGS} />,
align: 'center',
render: (tags: Rule['tags']) => {
if (tags == null || tags.length === 0) {
Expand Down Expand Up @@ -217,7 +218,7 @@ const TAGS_COLUMN: TableColumn = {

const INTEGRATIONS_COLUMN: TableColumn = {
field: 'related_integrations',
name: null,
name: <RulesTableEmptyColumnName name={i18n.COLUMN_INTEGRATIONS} />,
align: 'center',
render: (integrations: Rule['related_integrations']) => {
if (integrations == null || integrations.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,13 @@ export const COLUMN_TAGS = i18n.translate(
}
);

export const COLUMN_INTEGRATIONS = i18n.translate(
'xpack.securitySolution.detectionEngine.rules.allRules.columns.integrationsTitle',
{
defaultMessage: 'Integrations',
}
);

export const COLUMN_ENABLE = i18n.translate(
'xpack.securitySolution.detectionEngine.rules.allRules.columns.enabledTitle',
{
Expand Down

0 comments on commit 98c069f

Please sign in to comment.