From df2264fe7094dd381aab2176dc6f4ca827a2b5b8 Mon Sep 17 00:00:00 2001 From: Robert Stelmach Date: Fri, 27 Jun 2025 16:30:02 +0200 Subject: [PATCH 1/8] make Documents & Retention columns keyboard accessible. Improve voiceover for streams table --- .../stream_list_view/documents_column.tsx | 126 ++++++++++++++---- .../stream_list_view/retention_column.tsx | 73 +++++++++- .../stream_list_view/tree_table.tsx | 72 +++++++++- 3 files changed, 232 insertions(+), 39 deletions(-) diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/documents_column.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/documents_column.tsx index 286eac753d0f8..0efb57f143c12 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/documents_column.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/documents_column.tsx @@ -59,6 +59,12 @@ export function DocumentsColumn({ white-space: nowrap; padding-right: ${euiTheme.size.xl}; `} + role="status" + aria-live="polite" + aria-label={i18n.translate('xpack.streams.documentsColumn.loadingAriaLabel', { + defaultMessage: 'Loading document count and chart data for {indexPattern}', + values: { indexPattern }, + })} > ), - [euiTheme] + [euiTheme, indexPattern] ); const { timeState } = useTimefilter(); @@ -130,6 +136,29 @@ export function DocumentsColumn({ const xFormatter = niceTimeFormatter([timeState.start, timeState.end]); + const chartDescription = React.useMemo(() => { + if (!hasData) { + return i18n.translate('xpack.streams.documentsColumn.noDataDescription', { + defaultMessage: 'No document data available for {indexPattern}', + values: { indexPattern }, + }); + } + + const timeRange = i18n.translate('xpack.streams.documentsColumn.timeRangeDescription', { + defaultMessage: 'from {start} to {end}', + values: { + start: xFormatter(timeState.start), + end: xFormatter(timeState.end), + }, + }); + + return i18n.translate('xpack.streams.documentsColumn.chartDescription', { + defaultMessage: + 'Document count chart for {indexPattern} showing {docCount} total documents {timeRange}', + values: { indexPattern, docCount, timeRange }, + }); + }, [hasData, indexPattern, docCount, xFormatter, timeState.start, timeState.end]); + return ( {histogramQueryFetch.loading ? ( @@ -150,11 +184,25 @@ export function DocumentsColumn({ `} > {hasData ? ( - + + + ) : ( - i18n.translate('xpack.streams.documentsColumn.noDataLabel', { - defaultMessage: 'N/A', - }) + + {i18n.translate('xpack.streams.documentsColumn.noDataLabel', { + defaultMessage: 'N/A', + })} + )} {hasData ? ( - - } - /> - xFormatter(value)} - /> - {allTimeseries.map((serie) => ( - + + } + ariaLabel={chartDescription} + /> + xFormatter(value)} /> - ))} - + {allTimeseries.map((serie) => ( + + ))} + + ) : ( - + )} diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/retention_column.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/retention_column.tsx index 3fa4b72921a32..6d5a86025b79f 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/retention_column.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/retention_column.tsx @@ -26,12 +26,32 @@ export function RetentionColumn({ lifecycle }: { lifecycle: IngestStreamEffectiv const ilmLocator = share.url.locators.get(ILM_LOCATOR_ID); if (isErrorLifecycle(lifecycle)) { - return {lifecycle.error.message}; + return ( + + {lifecycle.error.message} + + ); } if (isIlmLifecycle(lifecycle)) { return ( - + {i18n.translate('xpack.streams.streamsRetentionColumn.ilmBadgeLabel', { defaultMessage: 'ILM policy: {name}', @@ -48,16 +73,56 @@ export function RetentionColumn({ lifecycle }: { lifecycle: IngestStreamEffectiv }, })} + + {i18n.translate('xpack.streams.streamsRetentionColumn.ilmPolicyDescription', { + defaultMessage: 'Index Lifecycle Management policy that controls data retention', + })} + ); } if (isDslLifecycle(lifecycle)) { - return lifecycle.dsl.data_retention || ; + const retentionValue = lifecycle.dsl.data_retention; + + if (retentionValue) { + return ( + + {retentionValue} + + ); + } + + return ( + + ); } return ( - + {i18n.translate('xpack.streams.streamsRetentionColumn.noDataLabel', { defaultMessage: 'N/A', })} diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/tree_table.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/tree_table.tsx index f00c0298d8ae2..e01c47d827417 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/tree_table.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/tree_table.tsx @@ -75,18 +75,37 @@ export function StreamsTreeTable({ className={css` margin-left: ${item.level * parseInt(euiTheme.size.xl, 10)}px; `} + aria-label={i18n.translate('xpack.streams.streamsTreeTable.nameColumnAriaLabel', { + defaultMessage: 'Stream name: {name}, level {level}', + values: { name: item.name, level: item.level }, + })} > {item.children.length > 0 ? ( - + ) : ( - + {item.name} @@ -101,6 +120,15 @@ export function StreamsTreeTable({ className={css` margin-right: ${euiTheme.size.l}; `} + role="columnheader" + tabIndex={0} + aria-label={i18n.translate( + 'xpack.streams.streamsTreeTable.documentsColumnHeaderAriaLabel', + { + defaultMessage: + 'Documents column - shows document count and chart data for each stream', + } + )} > {i18n.translate('xpack.streams.streamsTreeTable.documentsColumnName', { defaultMessage: 'Documents', @@ -117,15 +145,34 @@ export function StreamsTreeTable({ }, { field: 'retentionMs', - name: i18n.translate('xpack.streams.streamsTreeTable.retentionColumnName', { - defaultMessage: 'Retention', - }), + name: ( + + {i18n.translate('xpack.streams.streamsTreeTable.retentionColumnName', { + defaultMessage: 'Retention', + })} + + ), width: '160px', align: 'left', sortable: (row: TableRow) => row.rootRetentionMs, dataType: 'number', render: (_: unknown, item: TableRow) => ( - + ), }, ]} @@ -143,9 +190,22 @@ export function StreamsTreeTable({ search={{ box: { incremental: true, + 'aria-label': i18n.translate('xpack.streams.streamsTreeTable.searchAriaLabel', { + defaultMessage: 'Search streams by name', + }), }, toolsRight: , }} + tableCaption={i18n.translate('xpack.streams.streamsTreeTable.tableCaptionAriaLabel', { + defaultMessage: + 'Streams data table showing stream names, document counts with charts, and retention policies. Use Tab to navigate between columns and Enter to interact with elements.', + })} + rowProps={(item: TableRow) => ({ + 'aria-label': i18n.translate('xpack.streams.streamsTreeTable.rowAriaLabel', { + defaultMessage: 'Stream row for {name}', + values: { name: item.name }, + }), + })} /> ); } From dcf48aa66e8d7e1fc1c00d0e52ceb3fa459042c9 Mon Sep 17 00:00:00 2001 From: Robert Stelmach Date: Mon, 30 Jun 2025 10:45:41 +0200 Subject: [PATCH 2/8] remove unnecessary aria label --- .../public/components/stream_list_view/tree_table.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/tree_table.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/tree_table.tsx index e01c47d827417..40bd59e63213e 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/tree_table.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/tree_table.tsx @@ -102,10 +102,6 @@ export function StreamsTreeTable({ {item.name} From 418b443023eba95b4504bfa32ea65b2174cbd5d6 Mon Sep 17 00:00:00 2001 From: Robert Stelmach Date: Tue, 22 Jul 2025 19:27:39 +0200 Subject: [PATCH 3/8] extract translations to different file, clean up voiceover reptitions --- .../stream_list_view/documents_column.tsx | 53 +++++--------- .../stream_list_view/retention_column.tsx | 39 ++++------ .../stream_list_view/translations.ts | 71 ++++++++++++++++++ .../stream_list_view/tree_table.tsx | 72 +++++-------------- 4 files changed, 119 insertions(+), 116 deletions(-) create mode 100644 x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/translations.ts diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/documents_column.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/documents_column.tsx index 0efb57f143c12..8f6e2ed98414f 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/documents_column.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/documents_column.tsx @@ -26,6 +26,7 @@ import { } from '@elastic/charts'; import { useElasticChartsTheme } from '@kbn/charts-theme'; import { i18n } from '@kbn/i18n'; +import { NO_DATA_SHORT_LABEL, DOCUMENTS_NO_DATA_ICON_ARIA_LABEL } from './translations'; import { useStreamsAppFetch } from '../../hooks/use_streams_app_fetch'; import { useKibana } from '../../hooks/use_kibana'; import { esqlResultToTimeseries } from '../../util/esql_result_to_timeseries'; @@ -159,6 +160,16 @@ export function DocumentsColumn({ }); }, [hasData, indexPattern, docCount, xFormatter, timeState.start, timeState.end]); + const cellAriaLabel = hasData + ? i18n.translate('xpack.streams.documentsColumn.cellDocCountLabel', { + defaultMessage: '{docCount} documents in {indexPattern}', + values: { docCount, indexPattern }, + }) + : i18n.translate('xpack.streams.documentsColumn.cellNoDataLabel', { + defaultMessage: 'No document data available for {indexPattern}', + values: { indexPattern }, + }); + return ( {histogramQueryFetch.loading ? ( @@ -179,34 +186,16 @@ export function DocumentsColumn({ <> diff --git a/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/retention_column.tsx b/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/retention_column.tsx index 6d5a86025b79f..b5a45e97f115e 100644 --- a/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/retention_column.tsx +++ b/x-pack/platform/plugins/shared/streams_app/public/components/stream_list_view/retention_column.tsx @@ -16,6 +16,12 @@ import { isIlmLifecycle, } from '@kbn/streams-schema'; import { useKibana } from '../../hooks/use_kibana'; +import { + RETENTION_ILM_POLICY_DESCRIPTION, + INFINITE_RETENTION_LABEL, + NO_RETENTION_LABEL, + NO_DATA_SHORT_LABEL, +} from './translations'; export function RetentionColumn({ lifecycle }: { lifecycle: IngestStreamEffectiveLifecycle }) { const { @@ -74,9 +80,7 @@ export function RetentionColumn({ lifecycle }: { lifecycle: IngestStreamEffectiv })} - {i18n.translate('xpack.streams.streamsRetentionColumn.ilmPolicyDescription', { - defaultMessage: 'Index Lifecycle Management policy that controls data retention', - })} + {RETENTION_ILM_POLICY_DESCRIPTION} ); @@ -100,32 +104,19 @@ export function RetentionColumn({ lifecycle }: { lifecycle: IngestStreamEffectiv } return ( - + aria-label={INFINITE_RETENTION_LABEL} + style={{ display: 'inline-flex', alignItems: 'center' }} + > +