From 98c069ff950ce2632677e975533b793f19311ea3 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 18 Mar 2024 14:48:15 +0200 Subject: [PATCH] fix: [Rules > Add Elastic rules][SCREEN READER]: Table headers must be programmatically determinable (#178765) Closes: https://github.com/elastic/security-team/issues/8654 Closes: https://github.com/elastic/security-team/issues/8645 Closes: https://github.com/elastic/security-team/issues/8641 Closes: https://github.com/elastic/security-team/issues/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 Screenshot 2024-02-09 at 3 09 20 PM ### Solution - `RulesTableEmptyColumnName` component was added and applied to all columns with `name = null || ""` image --- .../use_add_prebuilt_rules_table_columns.tsx | 7 +++--- .../rules_table_empty_column_name.tsx | 24 +++++++++++++++++++ ...e_upgrade_prebuilt_rules_table_columns.tsx | 7 +++--- .../components/rules_table/use_columns.tsx | 5 ++-- .../detection_engine/rules/translations.ts | 7 ++++++ 5 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_empty_column_name.tsx diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_add_prebuilt_rules_table_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_add_prebuilt_rules_table_columns.tsx index 25f605772e941..70c40349fc80c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_add_prebuilt_rules_table_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_add_prebuilt_rules_table_columns.tsx @@ -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'; @@ -63,7 +64,7 @@ export const RULE_NAME_COLUMN: TableColumn = { const TAGS_COLUMN: TableColumn = { field: 'tags', - name: null, + name: , align: 'center', render: (tags: RuleResponse['tags']) => { if (tags == null || tags.length === 0) { @@ -92,7 +93,7 @@ const TAGS_COLUMN: TableColumn = { const INTEGRATIONS_COLUMN: TableColumn = { field: 'related_integrations', - name: null, + name: , align: 'center', render: (integrations: RuleResponse['related_integrations']) => { if (integrations == null || integrations.length === 0) { @@ -111,7 +112,7 @@ const createInstallButtonColumn = ( isDisabled: boolean ): TableColumn => ({ field: 'rule_id', - name: '', + name: , render: (ruleId: RuleSignatureId, record: Rule) => { const isRuleInstalling = loadingRules.includes(ruleId); const isInstallButtonDisabled = isRuleInstalling || isDisabled; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_empty_column_name.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_empty_column_name.tsx new file mode 100644 index 0000000000000..0efd15ff8374e --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_empty_column_name.tsx @@ -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 = React.memo( + ({ name }) => { + return ( + +

{name}

+
+ ); + } +); + +RulesTableEmptyColumnName.displayName = 'RulesTableEmptyColumnName'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx index cd56a0cd49074..b188b1afea7f2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx @@ -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'; @@ -62,7 +63,7 @@ const RULE_NAME_COLUMN: TableColumn = { const TAGS_COLUMN: TableColumn = { field: 'current_rule.tags', - name: null, + name: , align: 'center', render: (tags: Rule['tags']) => { if (tags == null || tags.length === 0) { @@ -91,7 +92,7 @@ const TAGS_COLUMN: TableColumn = { const INTEGRATIONS_COLUMN: TableColumn = { field: 'current_rule.related_integrations', - name: null, + name: , align: 'center', render: (integrations: Rule['related_integrations']) => { if (integrations == null || integrations.length === 0) { @@ -110,7 +111,7 @@ const createUpgradeButtonColumn = ( isDisabled: boolean ): TableColumn => ({ field: 'rule_id', - name: '', + name: , render: (ruleId: RuleUpgradeInfoForReview['rule_id']) => { const isRuleUpgrading = loadingRules.includes(ruleId); const isUpgradeButtonDisabled = isRuleUpgrading || isDisabled; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx index ca8808682b208..82f1f42481579 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx @@ -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, @@ -188,7 +189,7 @@ const useRuleExecutionStatusColumn = ({ const TAGS_COLUMN: TableColumn = { field: 'tags', - name: null, + name: , align: 'center', render: (tags: Rule['tags']) => { if (tags == null || tags.length === 0) { @@ -217,7 +218,7 @@ const TAGS_COLUMN: TableColumn = { const INTEGRATIONS_COLUMN: TableColumn = { field: 'related_integrations', - name: null, + name: , align: 'center', render: (integrations: Rule['related_integrations']) => { if (integrations == null || integrations.length === 0) { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index 13e52e855f2b1..a7823f39abb70 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -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', {