diff --git a/x-pack/plugins/search_indices/public/components/index_documents/index_documents.tsx b/x-pack/plugins/search_indices/public/components/index_documents/index_documents.tsx index 9b8852eb596bc..83595913cece3 100644 --- a/x-pack/plugins/search_indices/public/components/index_documents/index_documents.tsx +++ b/x-pack/plugins/search_indices/public/components/index_documents/index_documents.tsx @@ -8,25 +8,23 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiProgress, EuiSpacer } from '@elastic/eui'; -import { useIndexDocumentSearch } from '../../hooks/api/use_document_search'; import { useIndexMapping } from '../../hooks/api/use_index_mappings'; import { AddDocumentsCodeExample } from './add_documents_code_example'; - -import { DEFAULT_PAGE_SIZE } from './constants'; +import { IndexDocuments as IndexDocumentsType } from '../../hooks/api/use_document_search'; import { DocumentList } from './document_list'; interface IndexDocumentsProps { indexName: string; + indexDocuments?: IndexDocumentsType; + isInitialLoading: boolean; } -export const IndexDocuments: React.FC = ({ indexName }) => { - const { data: indexDocuments, isInitialLoading } = useIndexDocumentSearch(indexName, { - pageSize: DEFAULT_PAGE_SIZE, - pageIndex: 0, - }); - +export const IndexDocuments: React.FC = ({ + indexName, + indexDocuments, + isInitialLoading, +}) => { const { data: mappingData } = useIndexMapping(indexName); - const docs = indexDocuments?.results?.data ?? []; const mappingProperties = mappingData?.mappings?.properties ?? {}; 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 d7a359f56a6f4..fab451c137bdf 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 @@ -11,12 +11,6 @@ import { EuiPageTemplate, EuiFlexItem, EuiFlexGroup, - EuiPopover, - EuiButtonIcon, - EuiContextMenuItem, - EuiContextMenuPanel, - EuiText, - EuiIcon, EuiButtonEmpty, EuiTabbedContent, EuiTabbedContentTab, @@ -37,12 +31,15 @@ import { IndexloadingError } from './details_page_loading_error'; import { SearchIndexDetailsTabs } from '../../routes'; import { SearchIndexDetailsMappings } from './details_page_mappings'; import { SearchIndexDetailsSettings } from './details_page_settings'; +import { SearchIndexDetailsPageMenuItemPopover } from './details_page_menu_item'; +import { useIndexDocumentSearch } from '../../hooks/api/use_document_search'; +import { DEFAULT_PAGE_SIZE } from '../index_documents/constants'; export const SearchIndexDetailsPage = () => { const indexName = decodeURIComponent(useParams<{ indexName: string }>().indexName); const tabId = decodeURIComponent(useParams<{ tabId: string }>().tabId); - const { console: consolePlugin, docLinks, application, history } = useKibana().services; + const { console: consolePlugin, docLinks, application, history, share } = useKibana().services; const { data: index, refetch, @@ -56,6 +53,25 @@ export const SearchIndexDetailsPage = () => { isInitialLoading: isMappingsInitialLoading, error: mappingsError, } = useIndexMapping(indexName); + const { data: indexDocuments, isInitialLoading: indexDocumentsIsInitialLoading } = + useIndexDocumentSearch(indexName, { + pageSize: DEFAULT_PAGE_SIZE, + pageIndex: 0, + }); + + const navigateToPlayground = useCallback(async () => { + const playgroundLocator = share.url.locators.get('PLAYGROUND_LOCATOR_ID'); + if (playgroundLocator && index) { + await playgroundLocator.navigate({ 'default-index': index.name }); + } + }, [share, index]); + + const [isDocumentsExists, setDocumentsExists] = useState(false); + const [isDocumentsLoading, setDocumentsLoading] = useState(true); + useEffect(() => { + setDocumentsLoading(isInitialLoading); + setDocumentsExists(!(!isInitialLoading && indexDocuments?.results?.data.length === 0)); + }, [indexDocuments, isInitialLoading, setDocumentsExists, setDocumentsLoading]); const detailsPageTabs: EuiTabbedContentTab[] = useMemo(() => { return [ @@ -64,7 +80,13 @@ export const SearchIndexDetailsPage = () => { name: i18n.translate('xpack.searchIndices.documentsTabLabel', { defaultMessage: 'Data', }), - content: , + content: ( + + ), 'data-test-subj': `${SearchIndexDetailsTabs.DATA}Tab`, }, { @@ -84,7 +106,7 @@ export const SearchIndexDetailsPage = () => { 'data-test-subj': `${SearchIndexDetailsTabs.SETTINGS}Tab`, }, ]; - }, [index, indexName]); + }, [index, indexName, indexDocuments, indexDocumentsIsInitialLoading]); const [selectedTab, setSelectedTab] = useState(detailsPageTabs[0]); useEffect(() => { @@ -123,47 +145,11 @@ export const SearchIndexDetailsPage = () => { }, [isIndexError, indexLoadingError, mappingsError] ); - const [showMoreOptions, setShowMoreOptions] = useState(false); const [isShowingDeleteModal, setShowDeleteIndexModal] = useState(false); - const moreOptionsPopover = ( - setShowMoreOptions(!showMoreOptions)} - button={ - setShowMoreOptions(!showMoreOptions)} - size="m" - data-test-subj="moreOptionsActionButton" - aria-label={i18n.translate('xpack.searchIndices.moreOptions.ariaLabel', { - defaultMessage: 'More options', - })} - /> - } - > - } - onClick={() => { - setShowDeleteIndexModal(!isShowingDeleteModal); - }} - size="s" - color="danger" - data-test-subj="moreOptionsDeleteIndex" - > - - {i18n.translate('xpack.searchIndices.moreOptions.deleteIndexLabel', { - defaultMessage: 'Delete Index', - })} - - , - ]} - /> - - ); + const handleDeleteIndexModal = useCallback(() => { + setShowDeleteIndexModal(!isShowingDeleteModal); + }, [isShowingDeleteModal]); + if (isInitialLoading || isMappingsInitialLoading) { return ( @@ -208,20 +194,47 @@ export const SearchIndexDetailsPage = () => { data-test-subj="searchIndexDetailsHeader" pageTitle={index?.name} rightSideItems={[ - + - - {i18n.translate('xpack.searchIndices.indexActionsMenu.apiReference.docLink', { - defaultMessage: 'API Reference', - })} - + {!isDocumentsExists ? ( + + + + ) : ( + + + + )} + + + - {moreOptionsPopover} , ]} /> 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 new file mode 100644 index 0000000000000..77c2d9c6a8ee1 --- /dev/null +++ b/x-pack/plugins/search_indices/public/components/indices/details_page_menu_item.tsx @@ -0,0 +1,132 @@ +/* + * 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 { + EuiButtonIcon, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiIcon, + EuiPopover, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { MouseEventHandler, ReactElement, useState } from '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; +} + +export const SearchIndexDetailsPageMenuItemPopover = ({ + 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) => ( + + {item.text} + + ) + ); + + return ( + setShowMoreOptions(!showMoreOptions)} + button={ + setShowMoreOptions(!showMoreOptions)} + size="m" + data-test-subj="moreOptionsActionButton" + aria-label={i18n.translate('xpack.searchIndices.moreOptions.ariaLabel', { + defaultMessage: 'More options', + })} + /> + } + > + + + ); +}; diff --git a/x-pack/plugins/search_indices/public/hooks/api/use_document_search.ts b/x-pack/plugins/search_indices/public/hooks/api/use_document_search.ts index 8a0570844b1f9..c1ee54088bfe3 100644 --- a/x-pack/plugins/search_indices/public/hooks/api/use_document_search.ts +++ b/x-pack/plugins/search_indices/public/hooks/api/use_document_search.ts @@ -11,7 +11,7 @@ import { pageToPagination, Paginate } from '@kbn/search-index-documents'; import { useQuery } from '@tanstack/react-query'; import { useKibana } from '../use_kibana'; -interface IndexDocuments { +export interface IndexDocuments { meta: Pagination; results: Paginate; } 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 7101571036c4e..3da6666905738 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,6 +20,9 @@ export function SvlSearchIndexDetailPageProvider({ getService }: FtrProviderCont async expectAPIReferenceDocLinkExists() { await testSubjects.existOrFail('ApiReferenceDoc', { timeout: 2000 }); }, + async expectUseInPlaygroundLinkExists() { + await testSubjects.existOrFail('useInPlaygroundLink', { timeout: 5000 }); + }, async expectBackToIndicesButtonExists() { await testSubjects.existOrFail('backToIndicesButton', { timeout: 2000 }); }, @@ -82,7 +85,20 @@ export function SvlSearchIndexDetailPageProvider({ getService }: FtrProviderCont async expectMoreOptionsOverviewMenuIsShown() { await testSubjects.existOrFail('moreOptionsContextMenu'); }, - async expectDeleteIndexButtonExists() { + async expectPlaygroundButtonExistsInMoreOptions() { + await testSubjects.existOrFail('moreOptionsPlayground'); + }, + async expectToNavigateToPlayground(indexName: string) { + await testSubjects.click('moreOptionsPlayground'); + expect(await browser.getCurrentUrl()).contain( + `/search_playground/chat?default-index=${indexName}` + ); + await testSubjects.existOrFail('chatPage'); + }, + async expectAPIReferenceDocLinkExistsInMoreOptions() { + await testSubjects.existOrFail('moreOptionsApiReference', { timeout: 2000 }); + }, + async expectDeleteIndexButtonExistsInMoreOptions() { await testSubjects.existOrFail('moreOptionsDeleteIndex'); }, async clickDeleteIndexButton() { 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 7192eca9d13c3..be58cba8abf7f 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 @@ -96,9 +96,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { my_field: [1, 0, 1], }, }); + await svlSearchNavigation.navigateToIndexDetailPage(indexName); + }); + it('menu action item should be replaced with playground', async () => { + await pageObjects.svlSearchIndexDetailPage.expectUseInPlaygroundLinkExists(); }); it('should have index documents', async () => { - await svlSearchNavigation.navigateToIndexDetailPage(indexName); await pageObjects.svlSearchIndexDetailPage.expectHasIndexDocuments(); }); it('should have with data tabs', async () => { @@ -141,8 +144,14 @@ 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.expectDeleteIndexButtonExists(); + await pageObjects.svlSearchIndexDetailPage.expectDeleteIndexButtonExistsInMoreOptions(); await pageObjects.svlSearchIndexDetailPage.clickDeleteIndexButton(); await pageObjects.svlSearchIndexDetailPage.clickConfirmingDeleteIndex(); });