From 01d32b0dc0f377d8608a4b63c455b4982b1f33d3 Mon Sep 17 00:00:00 2001 From: Yan Savitski Date: Fri, 18 Oct 2024 18:32:33 +0200 Subject: [PATCH] [Search] Onboarding ux improvements (#196726) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Acceptance Criteria - [x] Update Use in Playground to be a filled button and labelled “Search in Playground” (not empty button) - [x] Include View in Discover as a default button in page header - [x] Hide 'Add reference' in context menu when documents not presented in index to avoid duplication of this button - [x] Add Spacer in settings image image --- .../components/indices/details_page.tsx | 68 ++++++----- .../indices/details_page_menu_item.tsx | 114 ++++++------------ .../indices/details_page_settings.tsx | 8 +- .../public/hooks/use_navigate_to_discover.ts | 22 ++++ .../svl_search_index_detail_page.ts | 10 +- .../test_suites/search/search_index_detail.ts | 13 +- 6 files changed, 116 insertions(+), 119 deletions(-) create mode 100644 x-pack/plugins/search_indices/public/hooks/use_navigate_to_discover.ts diff --git a/x-pack/plugins/search_indices/public/components/indices/details_page.tsx b/x-pack/plugins/search_indices/public/components/indices/details_page.tsx index ff62233e52e70..e8868663a9a3f 100644 --- a/x-pack/plugins/search_indices/public/components/indices/details_page.tsx +++ b/x-pack/plugins/search_indices/public/components/indices/details_page.tsx @@ -13,6 +13,7 @@ import { EuiTabbedContent, EuiTabbedContentTab, useEuiTheme, + EuiButton, } from '@elastic/eui'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useParams } from 'react-router-dom'; @@ -20,6 +21,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { SectionLoading } from '@kbn/es-ui-shared-plugin/public'; import { ApiKeyForm } from '@kbn/search-api-keys-components'; +import { useNavigateToDiscover } from '../../hooks/use_navigate_to_discover'; import { useIndex } from '../../hooks/api/use_index'; import { useKibana } from '../../hooks/use_kibana'; import { ConnectionDetails } from '../connection_details/connection_details'; @@ -71,13 +73,14 @@ export const SearchIndexDetailsPage = () => { await playgroundLocator.navigate({ 'default-index': index.name }); } }, [share, index]); + const navigateToDiscover = useNavigateToDiscover(indexName); - const [isDocumentsExists, setDocumentsExists] = useState(false); + const [hasDocuments, setHasDocuments] = useState(false); const [isDocumentsLoading, setDocumentsLoading] = useState(true); useEffect(() => { setDocumentsLoading(isInitialLoading); - setDocumentsExists(!(!isInitialLoading && indexDocuments?.results?.data.length === 0)); - }, [indexDocuments, isInitialLoading, setDocumentsExists, setDocumentsLoading]); + setHasDocuments(!(!isInitialLoading && indexDocuments?.results?.data.length === 0)); + }, [indexDocuments, isInitialLoading, setHasDocuments, setDocumentsLoading]); useEffect(() => { chrome.docTitle.change(indexName); @@ -221,8 +224,37 @@ export const SearchIndexDetailsPage = () => { bottomBorder={false} rightSideItems={[ - - {!isDocumentsExists ? ( + {hasDocuments ? ( + <> + + + + + + + + + + + + ) : ( + { > - - ) : ( - - - )} - + + )} , diff --git a/x-pack/plugins/search_indices/public/components/indices/details_page_menu_item.tsx b/x-pack/plugins/search_indices/public/components/indices/details_page_menu_item.tsx index 77c2d9c6a8ee1..df45cdab7fba7 100644 --- a/x-pack/plugins/search_indices/public/components/indices/details_page_menu_item.tsx +++ b/x-pack/plugins/search_indices/public/components/indices/details_page_menu_item.tsx @@ -14,101 +14,55 @@ import { EuiText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { MouseEventHandler, ReactElement, useState } from 'react'; +import React, { ReactElement, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; import { useKibana } from '../../hooks/use_kibana'; -enum MenuItems { - playground = 'playground', - apiReference = 'apiReference', - deleteIndex = 'deleteIndex', -} -interface MenuItemsAction { - href?: string; - onClick?: (() => void) | MouseEventHandler; -} - -const SearchIndexDetailsPageMenuItemPopoverItems = [ - { - type: MenuItems.playground, - iconType: 'launch', - dataTestSubj: 'moreOptionsPlayground', - iconComponent: , - target: undefined, - text: ( - - {i18n.translate('xpack.searchIndices.moreOptions.playgroundLabel', { - defaultMessage: 'Use in Playground', - })} - - ), - color: undefined, - }, - { - type: MenuItems.apiReference, - iconType: 'documentation', - dataTestSubj: 'moreOptionsApiReference', - iconComponent: , - target: '_blank', - text: ( - - {i18n.translate('xpack.searchIndices.moreOptions.apiReferenceLabel', { - defaultMessage: 'API Reference', - })} - - ), - color: undefined, - }, - { - type: MenuItems.deleteIndex, - iconType: 'trash', - dataTestSubj: 'moreOptionsDeleteIndex', - iconComponent: , - target: undefined, - text: ( - - {i18n.translate('xpack.searchIndices.moreOptions.deleteIndexLabel', { - defaultMessage: 'Delete Index', - })} - - ), - color: 'danger', - }, -]; interface SearchIndexDetailsPageMenuItemPopoverProps { handleDeleteIndexModal: () => void; - navigateToPlayground: () => void; + showApiReference: boolean; } export const SearchIndexDetailsPageMenuItemPopover = ({ + showApiReference = false, handleDeleteIndexModal, - navigateToPlayground, }: SearchIndexDetailsPageMenuItemPopoverProps) => { const [showMoreOptions, setShowMoreOptions] = useState(false); const { docLinks } = useKibana().services; - const contextMenuItemsActions: Record = { - playground: { - href: undefined, - onClick: navigateToPlayground, - }, - apiReference: { href: docLinks.links.apiReference, onClick: undefined }, - deleteIndex: { href: undefined, onClick: handleDeleteIndexModal }, - }; - const contextMenuItems: ReactElement[] = SearchIndexDetailsPageMenuItemPopoverItems.map( - (item) => ( + const contextMenuItems = [ + showApiReference && ( } + href={docLinks.links.apiReference} size="s" - onClick={contextMenuItemsActions[item.type]?.onClick} - target={item.target} - data-test-subj={item.dataTestSubj} - color={item.color} + target="_blank" + data-test-subj="moreOptionsApiReference" > - {item.text} + + + - ) - ); + ), + } + size="s" + onClick={handleDeleteIndexModal} + data-test-subj="moreOptionsDeleteIndex" + color="danger" + > + + + + , + ].filter(Boolean) as ReactElement[]; return ( ; + return ( + <> + + + + ); }; diff --git a/x-pack/plugins/search_indices/public/hooks/use_navigate_to_discover.ts b/x-pack/plugins/search_indices/public/hooks/use_navigate_to_discover.ts new file mode 100644 index 0000000000000..b0239c89c46bc --- /dev/null +++ b/x-pack/plugins/search_indices/public/hooks/use_navigate_to_discover.ts @@ -0,0 +1,22 @@ +/* + * 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 { useCallback } from 'react'; +import { useKibana } from './use_kibana'; + +const DISCOVER_LOCATOR_ID = 'DISCOVER_APP_LOCATOR'; + +export const useNavigateToDiscover = (indexName: string) => { + const { share } = useKibana().services; + + return useCallback(async () => { + const discoverLocator = share.url.locators.get(DISCOVER_LOCATOR_ID); + if (discoverLocator && indexName) { + await discoverLocator.navigate({ dataViewSpec: { title: indexName } }); + } + }, [share, indexName]); +}; diff --git a/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts b/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts index df14d39118de7..161be50cae410 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_search_index_detail_page.ts @@ -20,8 +20,10 @@ export function SvlSearchIndexDetailPageProvider({ getService }: FtrProviderCont async expectAPIReferenceDocLinkExists() { await testSubjects.existOrFail('ApiReferenceDoc', { timeout: 2000 }); }, - async expectUseInPlaygroundLinkExists() { + async expectActionItemReplacedWhenHasDocs() { + await testSubjects.missingOrFail('ApiReferenceDoc', { timeout: 2000 }); await testSubjects.existOrFail('useInPlaygroundLink', { timeout: 5000 }); + await testSubjects.existOrFail('viewInDiscoverLink', { timeout: 5000 }); }, async expectConnectionDetails() { await testSubjects.existOrFail('connectionDetailsEndpoint', { timeout: 2000 }); @@ -76,9 +78,6 @@ export function SvlSearchIndexDetailPageProvider({ getService }: FtrProviderCont async expectMoreOptionsOverviewMenuIsShown() { await testSubjects.existOrFail('moreOptionsContextMenu'); }, - async expectPlaygroundButtonExistsInMoreOptions() { - await testSubjects.existOrFail('moreOptionsPlayground'); - }, async expectToNavigateToPlayground(indexName: string) { await testSubjects.click('moreOptionsPlayground'); expect(await browser.getCurrentUrl()).contain( @@ -89,6 +88,9 @@ export function SvlSearchIndexDetailPageProvider({ getService }: FtrProviderCont async expectAPIReferenceDocLinkExistsInMoreOptions() { await testSubjects.existOrFail('moreOptionsApiReference', { timeout: 2000 }); }, + async expectAPIReferenceDocLinkMissingInMoreOptions() { + await testSubjects.missingOrFail('moreOptionsApiReference', { timeout: 2000 }); + }, async expectDeleteIndexButtonExistsInMoreOptions() { await testSubjects.existOrFail('moreOptionsDeleteIndex'); }, diff --git a/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts b/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts index 63f524edef035..eeb1487816d7c 100644 --- a/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts +++ b/x-pack/test_serverless/functional/test_suites/search/search_index_detail.ts @@ -45,6 +45,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await pageObjects.svlSearchIndexDetailPage.expectIndexDetailPageHeader(); await pageObjects.svlSearchIndexDetailPage.expectSearchIndexDetailsTabsExists(); await pageObjects.svlSearchIndexDetailPage.expectAPIReferenceDocLinkExists(); + await pageObjects.svlSearchIndexDetailPage.expectAPIReferenceDocLinkMissingInMoreOptions(); }); it('should have embedded dev console', async () => { await testHasEmbeddedConsole(pageObjects); @@ -117,7 +118,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await svlSearchNavigation.navigateToIndexDetailPage(indexName); }); it('menu action item should be replaced with playground', async () => { - await pageObjects.svlSearchIndexDetailPage.expectUseInPlaygroundLinkExists(); + await pageObjects.svlSearchIndexDetailPage.expectActionItemReplacedWhenHasDocs(); + }); + it('should have link to API reference doc link in options menu', async () => { + await pageObjects.svlSearchIndexDetailPage.clickMoreOptionsActionsButton(); + await pageObjects.svlSearchIndexDetailPage.expectAPIReferenceDocLinkExistsInMoreOptions(); }); it('should have index documents', async () => { await pageObjects.svlSearchIndexDetailPage.expectHasIndexDocuments(); @@ -167,12 +172,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await pageObjects.svlSearchIndexDetailPage.clickMoreOptionsActionsButton(); await pageObjects.svlSearchIndexDetailPage.expectMoreOptionsOverviewMenuIsShown(); }); - it('should have link to API reference doc link', async () => { - await pageObjects.svlSearchIndexDetailPage.expectAPIReferenceDocLinkExistsInMoreOptions(); - }); - it('should have link to playground', async () => { - await pageObjects.svlSearchIndexDetailPage.expectPlaygroundButtonExistsInMoreOptions(); - }); it('should delete index', async () => { await pageObjects.svlSearchIndexDetailPage.expectDeleteIndexButtonExistsInMoreOptions(); await pageObjects.svlSearchIndexDetailPage.clickDeleteIndexButton();