From 7981e5e3db8ccbabdc57d83d66f721bf6a792d62 Mon Sep 17 00:00:00 2001 From: Jedr Blaszyk Date: Tue, 12 Mar 2024 20:56:25 +0100 Subject: [PATCH] [Search] Fix index_name not proposed if query matches existing index (#178480) ## Summary If user input partially matches existing index name in the attach index box, then the user is only presented with options to select existing index names. Only when the user input doesn't match existing index names, the user is shown the option to `Create new {} index`. Current logic still "works" for partially matched index names, as the user can hit `enter` key to accept current input, but it's not communicated clearly. We want be clear that any valid index name can be attached to the connector. In a case that user input matches existing index name, we want to somehow communicate that user is not forced to only select existing indices. #### Proposed solution - [customOptionText](https://github.com/elastic/eui/blob/0396b26fd04796a1d5580cf59ba73fc377fa3c42/src/components/combo_box/combo_box_options_list/combo_box_options_list.tsx#L47-L51) shows up only when there are no matches with existing index names. The EUI component doesn't allow us to show it `always` - it's a limitation - My workaround is to inject to list of options, a custom option related to user input, if the user input is partial, but not a full match to existing index names. - Additionally group options in the `Create new index` and `Select existing index` categories ### Preview #### Status quo Screenshot 2024-03-12 at 13 48 41 #### Changed flow Screenshot Screenshot 2024-03-12 at 13 53 59 Video https://github.com/elastic/kibana/assets/14121688/b57e9cd6-6922-420f-a020-1ae77564752f ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../connector_detail/attach_index_box.tsx | 67 +++++++++++++++++-- 1 file changed, 60 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/attach_index_box.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/attach_index_box.tsx index 93f14f89259b9..5699d0019aed1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/attach_index_box.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_detail/attach_index_box.tsx @@ -34,6 +34,20 @@ import { FetchAllIndicesAPILogic } from '../../api/index/fetch_all_indices_api_l import { AttachIndexLogic } from './attach_index_logic'; +const CREATE_NEW_INDEX_GROUP_LABEL = i18n.translate( + 'xpack.enterpriseSearch.attachIndexBox.optionsGroup.createNewIndex', + { + defaultMessage: 'Create new index', + } +); + +const SELECT_EXISTING_INDEX_GROUP_LABEL = i18n.translate( + 'xpack.enterpriseSearch.attachIndexBox.optionsGroup.selectExistingIndex', + { + defaultMessage: 'Select existing index', + } +); + export interface AttachIndexBoxProps { connector: Connector; } @@ -58,7 +72,11 @@ export const AttachIndexBox: React.FC = ({ connector }) => : undefined ); const [selectedLanguage] = useState(); - const [query, setQuery] = useState(); + const [query, setQuery] = useState<{ + hasMatchingOptions: boolean; + isFullMatch: boolean; + searchValue: string; + }>(); const { makeRequest } = useActions(FetchAllIndicesAPILogic); const { data, status } = useValues(FetchAllIndicesAPILogic); @@ -72,13 +90,33 @@ export const AttachIndexBox: React.FC = ({ connector }) => } }; - const options: Array> = isLoading + const options: Array> = isLoading ? [] : data?.indices.map((index) => { return { label: index.name, }; }) ?? []; + + const shouldPrependUserInputAsOption = + !!query?.searchValue && query.hasMatchingOptions && !query.isFullMatch; + + const groupedOptions: Array> = shouldPrependUserInputAsOption + ? [ + ...[ + { + label: CREATE_NEW_INDEX_GROUP_LABEL, + options: [ + { + label: query.searchValue, + }, + ], + }, + ], + ...[{ label: SELECT_EXISTING_INDEX_GROUP_LABEL, options }], + ] + : [{ label: SELECT_EXISTING_INDEX_GROUP_LABEL, options }]; + useEffect(() => { setConnector(connector); makeRequest({}); @@ -89,7 +127,7 @@ export const AttachIndexBox: React.FC = ({ connector }) => useEffect(() => { if (query) { - checkIndexExists({ indexName: query }); + checkIndexExists({ indexName: query.searchValue }); } }, [query]); @@ -107,7 +145,7 @@ export const AttachIndexBox: React.FC = ({ connector }) => }, [hash]); const error = - !!query && indexExists[query] + !!query && indexExists[query.searchValue] ? i18n.translate( 'xpack.enterpriseSearch.attachIndexBox.euiFormRow.associatedIndexErrorTextLabel', { @@ -167,15 +205,30 @@ export const AttachIndexBox: React.FC = ({ connector }) => } )} isLoading={isLoading} - options={options} + options={groupedOptions} + onSearchChange={(searchValue, hasMatchingOptions) => { + setQuery({ + hasMatchingOptions: !!hasMatchingOptions, + isFullMatch: options.some((option) => option.label === searchValue), + searchValue, + }); + }} onChange={(selection) => { - setSelectedIndex(selection[0] || undefined); + const currentSelection = selection[0] ?? undefined; + const selectedIndexOption = currentSelection + ? { + label: currentSelection.label, + shouldCreate: + shouldPrependUserInputAsOption && + !!(currentSelection?.label === query?.searchValue), + } + : undefined; + setSelectedIndex(selectedIndexOption); }} selectedOptions={selectedIndex ? [selectedIndex] : undefined} onCreateOption={(value) => { setSelectedIndex({ label: value.trim(), shouldCreate: true }); }} - onSearchChange={(value) => setQuery(value)} singleSelection />