From 8f221bfad00a1774eb3777a8e32e47d1cd57cc3a Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 4 Aug 2025 15:18:33 +0300 Subject: [PATCH] Fix violations of the @elastic/eui/require-aria-label-for-modals ESLint rule (Step 3) (#229616) ## Summary This PR manually fixes some remaining violations of the following rule that, for some reason, were not addressed in the previous PR: https://github.com/elastic/kibana/pull/227821 It applies the auto-fix for the newly introduced `@elastic/eui/require-aria-label-for-modals` rule. This rule ensures that `EUI` modal components (`EuiModal` and `EuiFlyout`) include either an aria-label or `aria-labelledby` prop to support screen reader accessibility. These attributes help users understand the purpose and content of modal dialogs. ## Changes Made 1. **Identified the modal title/header element** used within `EuiModal` or `EuiFlyout`. Common elements include: `EuiFlyoutTitle`, `EuiModalTitle`, `EuiTitle`, `

`, `

`, or other heading tags. 2. **Ensured the title element has an `id`:** If the header element lacked an `id`, one was programmatically generated: * **For function components:** * Imported `useGeneratedHtmlId` from `@elastic/eui` if not already present: ```ts import { useGeneratedHtmlId } from '@elastic/eui'; ``` * Defined a unique ID before the `return` statement: ```ts const modalTitleId = useGeneratedHtmlId(); ``` * Applied the ID to the title element: ```tsx Title ``` * **For class components:** * Imported `htmlIdGenerator` from `@elastic/eui` if not already present: ```ts import { htmlIdGenerator } from '@elastic/eui'; ``` * Defined a unique ID within the `render()` method: ```ts const modalTitleId = htmlIdGenerator()('modalTitle'); ``` * Applied the ID to the title element: ```tsx Title ``` 3. **Updated the `EuiModal` or `EuiFlyout` component** to include the `aria-labelledby` attribute referencing the generated title ID: ```tsx ``` --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit ef05e61a64ff49080636578a37d6502492f9c947) # Conflicts: # src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/test_workflow_modal.tsx # src/platform/plugins/shared/workflows_management/public/features/run_workflow/ui/workflow_event_modal.tsx --- .../public/cluster_configuration_form.tsx | 10 +- .../lib/external_url_error_modal.tsx | 71 +++++++------- .../drilldowns_with_embeddable_example.tsx | 2 +- .../drilldowns_without_embeddable_example.tsx | 2 +- ...thout_embeddable_single_button_example.tsx | 2 +- .../datasource_preview/datasource_preview.js | 93 ++++++++++--------- .../auto_follow_pattern_request_flyout.js | 6 +- .../follower_index_request_flyout.js | 6 +- .../__snapshots__/detail_drawer.test.js.snap | 25 ++++- .../pipeline_viewer/views/detail_drawer.js | 7 +- ...nowledge_base_edit_manual_entry_flyout.tsx | 7 +- .../components/agent_policy_yaml_flyout.tsx | 9 +- .../detail/settings/changelog_modal.tsx | 11 ++- .../validate_job/validate_job_view.js | 33 ++++--- .../test_models/test_flyout.tsx | 60 +++++++----- .../edit/import_modal/import_modal.js | 7 +- .../components/forecasting_modal/modal.js | 12 ++- .../import_content_pack_flyout.tsx | 7 +- .../components/bulk_snooze_schedule_modal.tsx | 11 ++- .../components/enablement_modal.tsx | 11 ++- 20 files changed, 246 insertions(+), 146 deletions(-) diff --git a/src/platform/plugins/private/interactive_setup/public/cluster_configuration_form.tsx b/src/platform/plugins/private/interactive_setup/public/cluster_configuration_form.tsx index bdeff4fb930b4..78112a6956a2f 100644 --- a/src/platform/plugins/private/interactive_setup/public/cluster_configuration_form.tsx +++ b/src/platform/plugins/private/interactive_setup/public/cluster_configuration_form.tsx @@ -32,6 +32,7 @@ import { EuiSpacer, EuiText, EuiTitle, + useGeneratedHtmlId, } from '@elastic/eui'; import type { FunctionComponent } from 'react'; import React, { useState } from 'react'; @@ -414,6 +415,7 @@ export interface CertificateChainProps { } const CertificateChain: FunctionComponent = ({ certificateChain }) => { const [showModal, setShowModal] = useState(false); + const modalTitleId = useGeneratedHtmlId(); return ( <> @@ -423,9 +425,13 @@ const CertificateChain: FunctionComponent = ({ certificat compressed /> {showModal && ( - setShowModal(false)} maxWidth={euiThemeVars.euiBreakpoints.s}> + setShowModal(false)} + maxWidth={euiThemeVars.euiBreakpoints.s} + > - + void; } -export const ExternalUrlErrorModal = ({ url, handleClose }: ExternalUrlErrorModalProps) => ( - - - - - - - - - {url} - - ), - externalUrlPolicy: 'externalUrl.policy', - kibanaConfigFileName: 'kibana.yml', - }} - /> - - - +export const ExternalUrlErrorModal = ({ url, handleClose }: ExternalUrlErrorModalProps) => { + const externalUrlErrorModalTitleId = useGeneratedHtmlId(); + + return ( + + + + + + + + {url} + + ), + externalUrlPolicy: 'externalUrl.policy', + kibanaConfigFileName: 'kibana.yml', + }} /> - - - -); + + + + + + + + ); +}; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx index f8b805fb8a21b..6a291b90f7781 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx @@ -107,7 +107,7 @@ export const DrilldownsWithEmbeddableExample: React.FC = () => { {showManager && ( - setShowManager(false)} aria-labelledby="Drilldown Manager"> + setShowManager(false)} aria-label="Drilldown Manager"> { {showManager && ( - setShowManager(false)} aria-labelledby="Drilldown Manager"> + setShowManager(false)} aria-label="Drilldown Manager"> { {showManager && ( - setShowManager(false)} aria-labelledby="Drilldown Manager"> + setShowManager(false)} aria-label="Drilldown Manager"> ( - - - {strings.getModalTitle()} - - - -

- {strings.getSaveButtonLabel()}, - }} - /> -

-
- - {datatable.type === 'error' ? ( - - ) : ( - - {datatable.rows.length > 0 ? ( - - ) : ( - {strings.getEmptyTitle()}

} - titleSize="s" - body={ -

- {strings.getEmptyFirstLineDescription()} -
- {strings.getEmptySecondLineDescription()} -

- } +export const DatasourcePreview = ({ done, datatable }) => { + const modalTitleId = useGeneratedHtmlId(); + + return ( + + + {strings.getModalTitle()} + + + +

+ {strings.getSaveButtonLabel()}, + }} /> - )} - - )} - - -); +

+
+ + {datatable.type === 'error' ? ( + + ) : ( + + {datatable.rows.length > 0 ? ( + + ) : ( + {strings.getEmptyTitle()}} + titleSize="s" + body={ +

+ {strings.getEmptyFirstLineDescription()} +
+ {strings.getEmptySecondLineDescription()} +

+ } + /> + )} +
+ )} +
+
+ ); +}; DatasourcePreview.propTypes = { datatable: PropTypes.object, diff --git a/x-pack/platform/plugins/private/cross_cluster_replication/public/app/components/auto_follow_pattern_request_flyout.js b/x-pack/platform/plugins/private/cross_cluster_replication/public/app/components/auto_follow_pattern_request_flyout.js index 115eb75c2d4f5..52d20c910549d 100644 --- a/x-pack/platform/plugins/private/cross_cluster_replication/public/app/components/auto_follow_pattern_request_flyout.js +++ b/x-pack/platform/plugins/private/cross_cluster_replication/public/app/components/auto_follow_pattern_request_flyout.js @@ -19,6 +19,7 @@ import { EuiSpacer, EuiText, EuiTitle, + htmlIdGenerator, } from '@elastic/eui'; import { serializeAutoFollowPattern } from '../../../common/services/auto_follow_pattern_serialization'; @@ -36,12 +37,13 @@ export class AutoFollowPatternRequestFlyout extends PureComponent { const endpoint = `PUT /_ccr/auto_follow/${name ? name : ''}`; const payload = JSON.stringify(serializeAutoFollowPattern(autoFollowPattern), null, 2); const request = `${endpoint}\n${payload}`; + const modalTitleId = htmlIdGenerator()('autoFollowPatternRequestFlyoutTitle'); return ( - + -

+

{name ? ( '}/_ccr/follow`; const payload = JSON.stringify(serializeFollowerIndex(followerIndex), null, 2); const request = `${endpoint}\n${payload}`; + const flyoutTitleId = htmlIdGenerator()('flyoutTitle'); return ( - + -

+

@@ -17,7 +18,9 @@ exports[`DetailDrawer component If vertices shows basic info and no stats for if -

+

if

@@ -53,6 +56,7 @@ exports[`DetailDrawer component If vertices shows basic info and no stats for if exports[`DetailDrawer component Plugin vertices Plugin does not have explicit ID shows basic info and stats for plugin, suggesting that user set explicit ID 1`] = ` @@ -68,7 +72,9 @@ exports[`DetailDrawer component Plugin vertices Plugin does not have explicit ID
-

+

grok filter

@@ -300,6 +306,7 @@ exports[`DetailDrawer component Plugin vertices Plugin does not have explicit ID exports[`DetailDrawer component Plugin vertices Plugin has explicit ID shows basic info and stats for plugin, including explicit ID 1`] = ` @@ -315,7 +322,9 @@ exports[`DetailDrawer component Plugin vertices Plugin has explicit ID shows bas
-

+

grok filter

@@ -541,6 +550,7 @@ exports[`DetailDrawer component Plugin vertices Plugin has explicit ID shows bas exports[`DetailDrawer component Queue vertices shows basic info and no stats for queue 1`] = ` @@ -556,7 +566,9 @@ exports[`DetailDrawer component Queue vertices shows basic info and no stats for
-

+

queue

@@ -584,6 +596,7 @@ exports[`DetailDrawer component Queue vertices shows basic info and no stats for exports[`DetailDrawer component shows vertex title 1`] = ` @@ -599,7 +612,9 @@ exports[`DetailDrawer component shows vertex title 1`] = `
-

+

diff --git a/x-pack/platform/plugins/private/monitoring/public/components/logstash/pipeline_viewer/views/detail_drawer.js b/x-pack/platform/plugins/private/monitoring/public/components/logstash/pipeline_viewer/views/detail_drawer.js index e1048b93e2d77..c4d5d68fee617 100644 --- a/x-pack/platform/plugins/private/monitoring/public/components/logstash/pipeline_viewer/views/detail_drawer.js +++ b/x-pack/platform/plugins/private/monitoring/public/components/logstash/pipeline_viewer/views/detail_drawer.js @@ -24,6 +24,7 @@ import { EuiTableRowCell, EuiText, EuiTitle, + useGeneratedHtmlId, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -301,14 +302,16 @@ function renderTitle(vertex) { } export function DetailDrawer({ vertex, onHide, timeseriesTooltipXValueFormatter }) { + const detailDrawerTitleId = useGeneratedHtmlId(); + return ( - + {renderIcon(vertex)} -

{renderTitle(vertex)}

+

{renderTitle(vertex)}

diff --git a/x-pack/platform/plugins/private/observability_ai_assistant_management/public/routes/components/knowledge_base_edit_manual_entry_flyout.tsx b/x-pack/platform/plugins/private/observability_ai_assistant_management/public/routes/components/knowledge_base_edit_manual_entry_flyout.tsx index a5f096788c75a..e697ca4fa7238 100644 --- a/x-pack/platform/plugins/private/observability_ai_assistant_management/public/routes/components/knowledge_base_edit_manual_entry_flyout.tsx +++ b/x-pack/platform/plugins/private/observability_ai_assistant_management/public/routes/components/knowledge_base_edit_manual_entry_flyout.tsx @@ -24,6 +24,7 @@ import { EuiSpacer, EuiText, EuiTitle, + useGeneratedHtmlId, } from '@elastic/eui'; import moment from 'moment'; import { KnowledgeBaseEntryRole } from '@kbn/observability-ai-assistant-plugin/public'; @@ -74,11 +75,13 @@ export function KnowledgeBaseEditManualEntryFlyout({ onClose(); }; + const flyoutTitleId = useGeneratedHtmlId(); + return ( - + -

+

{!entry ? i18n.translate( 'xpack.observabilityAiAssistantManagement.knowledgeBaseNewEntryFlyout.h2.newEntryLabel', diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx index b9822329f2bf5..e088c9d576753 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx @@ -22,6 +22,7 @@ import { EuiButton, EuiCallOut, EuiSpacer, + useGeneratedHtmlId, } from '@elastic/eui'; import { MAX_FLYOUT_WIDTH } from '../../../constants'; @@ -38,6 +39,8 @@ const FlyoutBody = styled(EuiFlyoutBody)` export const AgentPolicyYamlFlyout = memo<{ policyId: string; onClose: () => void }>( ({ policyId, onClose }) => { + const flyoutTitleId = useGeneratedHtmlId(); + const core = useStartServices(); const { isLoading: isLoadingYaml, data: yamlData, error } = useGetOneAgentPolicyFull(policyId); const { data: agentPolicyData } = useGetOneAgentPolicy(policyId); @@ -72,10 +75,10 @@ export const AgentPolicyYamlFlyout = memo<{ policyId: string; onClose: () => voi `?apiVersion=${API_VERSIONS.public.v1}`; return ( - - + + -

+

{agentPolicyData?.item ? ( = ({ onClose, }) => { const changelogText = formatChangelog(changelog); + const changelogModalTitleId = useGeneratedHtmlId(); return ( - + - {'Changelog'} + {'Changelog'} ( ); -const Modal = ({ close, title, children }) => ( - - - {title} - +const Modal = ({ close, title, children }) => { + const modalTitleId = useGeneratedHtmlId(); + return ( + + + {title} + - {children} + {children} - - - - - - -); + + + + + + + ); +}; Modal.propType = { close: PropTypes.func.isRequired, title: PropTypes.string, diff --git a/x-pack/platform/plugins/shared/ml/public/application/model_management/test_models/test_flyout.tsx b/x-pack/platform/plugins/shared/ml/public/application/model_management/test_models/test_flyout.tsx index 3b8d3cc7bdea9..f9c33676ab601 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/model_management/test_models/test_flyout.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/model_management/test_models/test_flyout.tsx @@ -8,7 +8,14 @@ import type { FC } from 'react'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiSpacer, + EuiTitle, + useGeneratedHtmlId, +} from '@elastic/eui'; import type { TrainedModelItem } from '../../../../common/types/trained_models'; import { TestTrainedModelContent } from './test_trained_model_content'; @@ -16,24 +23,33 @@ interface Props { model: TrainedModelItem; onClose: () => void; } -export const TestTrainedModelFlyout: FC = ({ model, onClose }) => ( - - - -

- -

-
- - -

{model.model_id}

-
-
- - - -
-); +export const TestTrainedModelFlyout: FC = ({ model, onClose }) => { + const flyoutTitleId = useGeneratedHtmlId(); + + return ( + + + +

+ +

+
+ + +

{model.model_id}

+
+
+ + + +
+ ); +}; diff --git a/x-pack/platform/plugins/shared/ml/public/application/settings/calendars/edit/import_modal/import_modal.js b/x-pack/platform/plugins/shared/ml/public/application/settings/calendars/edit/import_modal/import_modal.js index 2abec566b43d1..40133009655d0 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/settings/calendars/edit/import_modal/import_modal.js +++ b/x-pack/platform/plugins/shared/ml/public/application/settings/calendars/edit/import_modal/import_modal.js @@ -19,6 +19,7 @@ import { EuiModalFooter, EuiFlexGroup, EuiFlexItem, + htmlIdGenerator, } from '@elastic/eui'; import { ImportedEvents } from '../imported_events'; @@ -127,6 +128,8 @@ export class ImportModal extends Component { includePastEvents, } = this.state; + const modalTitleId = htmlIdGenerator()('importModalTitle'); + let showRecurringWarning = false; let importedEvents; @@ -142,11 +145,11 @@ export class ImportModal extends Component { return ( - + - + + - + (null); const [contentPackObjects, setContentPackObjects] = useState([]); @@ -50,10 +53,10 @@ export function ImportContentPackFlyout({ const [manifest, setManifest] = useState(); return ( - + -

+

{i18n.translate('xpack.streams.streamDetailDashboard.importContent', { defaultMessage: 'Import content pack', })} diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rules_list/components/bulk_snooze_schedule_modal.tsx b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rules_list/components/bulk_snooze_schedule_modal.tsx index 4b1447e9bcbe6..bf31f66e2956a 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rules_list/components/bulk_snooze_schedule_modal.tsx +++ b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rules_list/components/bulk_snooze_schedule_modal.tsx @@ -125,13 +125,14 @@ export const BulkSnoozeScheduleModal = (props: BulkSnoozeScheduleModalProps) => return deleteConfirmPlural(numberOfSelectedRules); }, [rules, filter, numberOfSelectedRules]); - const modalTitleId = useGeneratedHtmlId(); + const confirmModalTitleId = useGeneratedHtmlId(); + const modalHeaderTitleId = useGeneratedHtmlId(); if (bulkEditAction === 'unschedule' && (rules.length || filter)) { return ( if (bulkEditAction === 'schedule' && (rules.length || filter)) { return ( - + - + ); return ( - toggle(false)} data-test-subj="entityStoreEnablementModal"> + toggle(false)} + aria-labelledby={modalTitleId} + data-test-subj="entityStoreEnablementModal" + > - +