From 8487bc83d9ebc20ec2e1a5a18286a2a522346e6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Gonz=C3=A1lez?= Date: Tue, 26 Nov 2024 15:48:15 +0100 Subject: [PATCH] [Search][a11y] Fixing connectors pageHeader hierarchy content (#201359) Replace the existing `ConnectorNameAndDescription` component with separate `ConnectorName` and `ConnectorDescription` components for improved accessibility as pointed out in this issue https://github.com/elastic/kibana/issues/198001 . Now only the H1 wraps the Title and the Descriptions is out of it. Before: ![image](https://github.com/user-attachments/assets/e3b297a9-31e1-4471-a638-2166185551e6) ![image](https://github.com/user-attachments/assets/c9030e25-b067-40b5-b4f6-d07a52d16a30) After: ![CleanShot 2024-11-22 at 12 56 25@2x](https://github.com/user-attachments/assets/41a8cbf9-e609-4fbb-b1cd-3f9256bcaa8e) --------- Co-authored-by: Elastic Machine --- .../connector_description.tsx | 16 ++ .../connector_detail/connector_detail.tsx | 6 +- .../connector_detail/connector_field.tsx | 110 ++++++++++++++ .../connector_detail/connector_name.tsx | 16 ++ .../connector_name_and_description.tsx | 142 ------------------ .../translations/translations/fr-FR.json | 4 - .../translations/translations/ja-JP.json | 4 - .../translations/translations/zh-CN.json | 4 - 8 files changed, 146 insertions(+), 156 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_description.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_field.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_name.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_name_and_description.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_description.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_description.tsx new file mode 100644 index 0000000000000..c6f41acb10da8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_description.tsx @@ -0,0 +1,16 @@ +/* + * 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 from 'react'; + +import { Connector } from '@kbn/search-connectors'; + +import { ConnectorField } from './connector_field'; + +export const ConnectorDescription: React.FC<{ connector: Connector }> = ({ connector }) => ( + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_detail.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_detail.tsx index 4c787e9ef28ef..b3251ad5a15b8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_detail.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_detail.tsx @@ -28,7 +28,8 @@ import { SearchIndexPipelines } from '../search_index/pipelines/pipelines'; import { getHeaderActions } from '../shared/header_actions/header_actions'; import { ConnectorConfiguration } from './connector_configuration'; -import { ConnectorNameAndDescription } from './connector_name_and_description'; +import { ConnectorDescription } from './connector_description'; +import { ConnectorName } from './connector_name'; import { ConnectorViewLogic } from './connector_view_logic'; import { ConnectorDetailOverview } from './overview'; @@ -246,7 +247,8 @@ export const ConnectorDetail: React.FC = () => { pageViewTelemetry={tabId} isLoading={isLoading} pageHeader={{ - pageTitle: connector ? : '...', + description: connector ? : '...', + pageTitle: connector ? : '...', rightSideGroupProps: { gutterSize: 's', responsive: false, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_field.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_field.tsx new file mode 100644 index 0000000000000..8bddef94243ee --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_field.tsx @@ -0,0 +1,110 @@ +/* + * 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, { ChangeEvent, useEffect, useState } from 'react'; + +import { useActions, useValues } from 'kea'; + +import { EuiFlexItem, EuiInlineEditText, EuiInlineEditTitle } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { Connector } from '@kbn/search-connectors'; + +import { ConnectorNameAndDescriptionLogic } from './connector_name_and_description_logic'; + +interface ConnectorFieldProps { + connector: Connector; + field: 'name' | 'description'; // The field to edit + isTitle?: boolean; // Whether to render a title (`EuiInlineEditTitle`) or text (`EuiInlineEditText`) +} + +export const ConnectorField: React.FC = ({ connector, field, isTitle }) => { + const [value, setValue] = useState(connector[field]); + const [resolverObject, setResolverObject] = useState({ + rej: () => {}, + res: () => {}, + }); + const { saveNameAndDescription, setConnector } = useActions(ConnectorNameAndDescriptionLogic); + const { status, isLoading, isFailed, isSuccess } = useValues(ConnectorNameAndDescriptionLogic); + + useEffect(() => { + setConnector(connector); + }, [connector]); + + useEffect(() => { + if (isSuccess) resolverObject.res(); + if (isFailed) resolverObject.rej(); + }, [status]); + + const getValidationPromiseResolvers = () => { + const resolvers = { + rej: () => {}, + res: () => {}, + }; + const promise = new Promise((resolve, reject) => { + resolvers.res = resolve; + resolvers.rej = reject; + }); + setResolverObject(resolvers); + return promise; + }; + + const handleSave = async (newValue: string) => { + setValue(newValue); + saveNameAndDescription({ ...connector, [field]: newValue }); + await getValidationPromiseResolvers(); + return true; + }; + + const handleCancel = (previousValue: string) => { + setValue(previousValue); + }; + + const Component = isTitle ? EuiInlineEditTitle : EuiInlineEditText; + + return ( + + handleCancel(connector[field] || '') }, + formRowProps: + field === 'name' && !value?.trim() + ? { + error: [ + i18n.translate( + 'xpack.enterpriseSearch.content.nameAndDescription.name.error.empty', + { defaultMessage: 'Connector name cannot be empty' } + ), + ], + } + : undefined, + inputProps: { readOnly: isLoading }, + }} + onSave={handleSave} + onChange={(e: ChangeEvent) => setValue(e.target.value)} + onCancel={() => handleCancel(connector[field] || '')} + /> + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_name.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_name.tsx new file mode 100644 index 0000000000000..54f31c4beb6e6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_name.tsx @@ -0,0 +1,16 @@ +/* + * 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 from 'react'; + +import { Connector } from '@kbn/search-connectors'; + +import { ConnectorField } from './connector_field'; + +export const ConnectorName: React.FC<{ connector: Connector }> = ({ connector }) => ( + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_name_and_description.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_name_and_description.tsx deleted file mode 100644 index cab9624a2d197..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/connector_name_and_description.tsx +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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, { ChangeEvent, useEffect, useState } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { EuiFlexGroup, EuiFlexItem, EuiInlineEditText, EuiInlineEditTitle } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { Connector } from '@kbn/search-connectors'; - -import { ConnectorNameAndDescriptionLogic } from './connector_name_and_description_logic'; - -export interface ConnectorNameAndDescriptionProps { - connector: Connector; -} - -export interface ResolverObject { - rej: (value: boolean | void | PromiseLike) => void; - res: (value: boolean | void | PromiseLike) => void; -} - -let promise: Promise | undefined; - -const getValidationPromiseResolvers = (): ResolverObject => { - const resolvers = { - rej: () => {}, - res: () => {}, - }; - promise = new Promise((resolve, reject) => { - resolvers.res = resolve; - resolvers.rej = reject; - }); - return resolvers; -}; - -export const ConnectorNameAndDescription: React.FC = ({ - connector, -}) => { - const [resolverObject, setResolverObject] = useState({ - rej: () => {}, - res: () => {}, - }); - const [connectorName, setConnectorName] = useState(connector.name); - const [connectorDescription, setConnectorDescription] = useState( - connector.description - ); - const [nameErrors, setNameErrors] = useState([]); - const { saveNameAndDescription, setConnector } = useActions(ConnectorNameAndDescriptionLogic); - const { status, isLoading, isFailed, isSuccess } = useValues(ConnectorNameAndDescriptionLogic); - useEffect(() => { - setConnector(connector); - }, [connector]); - - useEffect(() => { - if (isSuccess) { - resolverObject.res(true); - } - if (isFailed) { - resolverObject.rej(); - } - }, [status]); - - return ( - - - 0} - size="m" - editModeProps={{ - formRowProps: { error: nameErrors }, - cancelButtonProps: { onClick: () => setNameErrors([]) }, - inputProps: { readOnly: isLoading }, - }} - onSave={(inputValue) => { - if (inputValue.trim().length <= 0) { - setNameErrors([ - i18n.translate( - 'xpack.enterpriseSearch.content.nameAndDescription.name.error.empty', - { defaultMessage: 'Connector name cannot be empty' } - ), - ]); - return false; - } - setConnectorName(inputValue); - saveNameAndDescription({ description: connectorDescription, name: inputValue }); - setResolverObject(getValidationPromiseResolvers()); - return promise; - }} - onChange={(event: ChangeEvent) => { - setConnectorName(event.target.value); - }} - onCancel={(previousValue) => { - setConnectorName(previousValue); - }} - /> - - - { - setConnectorDescription(inputValue); - saveNameAndDescription({ description: inputValue, name: connectorName }); - setResolverObject(getValidationPromiseResolvers()); - return promise; - }} - onChange={(event: ChangeEvent) => { - setConnectorDescription(event.target.value); - }} - onCancel={(previousValue) => { - setConnectorDescription(previousValue); - }} - /> - - - ); -}; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index e347165c6258c..9866bea029a98 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -17473,10 +17473,6 @@ "xpack.enterpriseSearch.content.connectors.deleteModal.delete.crawler.description": "Vous êtes sur le point de supprimer le robot d'indexation suivant :", "xpack.enterpriseSearch.content.connectors.deleteModal.syncsWarning.indexNameDescription": "Cette action ne peut pas être annulée. Veuillez saisir {connectorName} pour confirmer.", "xpack.enterpriseSearch.content.connectors.deleteModal.title": "Supprimer {connectorCount} connecteur ?", - "xpack.enterpriseSearch.content.connectors.nameAndDescription.description.ariaLabel": "Modifier la description du connecteur", - "xpack.enterpriseSearch.content.connectors.nameAndDescription.description.placeholder": "Ajouter une description", - "xpack.enterpriseSearch.content.connectors.nameAndDescription.name.ariaLabel": "Modifier le nom du connecteur", - "xpack.enterpriseSearch.content.connectors.nameAndDescription.name.placeholder": "Ajouter un nom pour votre connecteur", "xpack.enterpriseSearch.content.connectors.overview.connectorErrorCallOut.title": "Votre connecteur a rapporté une erreur", "xpack.enterpriseSearch.content.connectors.overview.connectorIndexDoesntExistCallOut.description": "Le connecteur va créer l'index lors de sa prochaine synchronisation. Vous pouvez également créer l’index {indexName} manuellement avec les paramètres et les mappings de votre choix.", "xpack.enterpriseSearch.content.connectors.overview.connectorIndexDoesntExistCallOut.title": "L'index attaché n'existe pas", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index a42951676bde5..08befe63cfa0a 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17448,10 +17448,6 @@ "xpack.enterpriseSearch.content.connectors.deleteModal.delete.crawler.description": "このコネクターを削除しようとしています:", "xpack.enterpriseSearch.content.connectors.deleteModal.syncsWarning.indexNameDescription": "この操作は元に戻すことができません。{connectorName}を入力して確認してください。", "xpack.enterpriseSearch.content.connectors.deleteModal.title": "\"{connectorCount}\"コネクターを削除しますか?", - "xpack.enterpriseSearch.content.connectors.nameAndDescription.description.ariaLabel": "コネクターの説明を編集", - "xpack.enterpriseSearch.content.connectors.nameAndDescription.description.placeholder": "説明を追加", - "xpack.enterpriseSearch.content.connectors.nameAndDescription.name.ariaLabel": "コネクター名を編集", - "xpack.enterpriseSearch.content.connectors.nameAndDescription.name.placeholder": "コネクターに名前を追加", "xpack.enterpriseSearch.content.connectors.overview.connectorErrorCallOut.title": "コネクターでエラーが発生しました", "xpack.enterpriseSearch.content.connectors.overview.connectorIndexDoesntExistCallOut.description": "コネクターは、次回の同期でインデックスを作成します。あるいは、任意の設定とマッピングを使用して、手動でインデックス{indexName}を作成できます。", "xpack.enterpriseSearch.content.connectors.overview.connectorIndexDoesntExistCallOut.title": "付けられたインデックスが存在しません", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6bc41cb2e9203..e357618355f9b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17116,10 +17116,6 @@ "xpack.enterpriseSearch.content.connectors.deleteModal.delete.crawler.description": "您即将删除此网络爬虫:", "xpack.enterpriseSearch.content.connectors.deleteModal.syncsWarning.indexNameDescription": "此操作无法撤消。请尝试 {connectorName} 以确认。", "xpack.enterpriseSearch.content.connectors.deleteModal.title": "删除 {connectorCount} 个连接器?", - "xpack.enterpriseSearch.content.connectors.nameAndDescription.description.ariaLabel": "编辑连接器描述", - "xpack.enterpriseSearch.content.connectors.nameAndDescription.description.placeholder": "添加描述", - "xpack.enterpriseSearch.content.connectors.nameAndDescription.name.ariaLabel": "编辑连接器名称", - "xpack.enterpriseSearch.content.connectors.nameAndDescription.name.placeholder": "为连接器添加名称", "xpack.enterpriseSearch.content.connectors.overview.connectorErrorCallOut.title": "您的连接器报告了错误", "xpack.enterpriseSearch.content.connectors.overview.connectorIndexDoesntExistCallOut.description": "此连接器将在其下次同步时创建索引,或者,您也可以使用所需设置和映射手动创建索引 {indexName}。", "xpack.enterpriseSearch.content.connectors.overview.connectorIndexDoesntExistCallOut.title": "附加的索引不存在",