From d482a0a295cfc55c6635f32741543bde7cb38884 Mon Sep 17 00:00:00 2001
From: Saarika Bhasi <55930906+saarikabhasi@users.noreply.github.com>
Date: Wed, 2 Oct 2024 15:21:59 -0400
Subject: [PATCH] [Onboarding][Index detail] Update right side menu items
(#194604)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
In this PR, updates search index detail page right side menu item.
The drop down menu item is updated to have :
* Api reference doc link
* Use in playground link which navigates to playground selecting this
index name
When documents exists right side header action is replaced with `Use in
playground` else `Api reference doc link`
### Screenshot
**How to test:**
1. Enable searchIndices plugin in `kibana.dev.yml` as this plugin is
behind Feature flag
```
xpack.searchIndices.enabled: true
```
2. [Create new
index](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html)
3. Navigate to
`/app/elasticsearch/indices/index_details/${indexName}/data`
4. Confirm index header action is `Api Reference doc`
5. Add documents confirm index header action is changed to `Use in
playground`
6. Confirm menu item shows delete index, use in playground & api
reference doc link
### 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)
- [x] [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
- [x] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
---
.../index_documents/index_documents.tsx | 18 ++-
.../components/indices/details_page.tsx | 135 ++++++++++--------
.../indices/details_page_menu_item.tsx | 132 +++++++++++++++++
.../public/hooks/api/use_document_search.ts | 2 +-
.../svl_search_index_detail_page.ts | 18 ++-
.../test_suites/search/search_index_detail.ts | 13 +-
6 files changed, 243 insertions(+), 75 deletions(-)
create mode 100644 x-pack/plugins/search_indices/public/components/indices/details_page_menu_item.tsx
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 0a3de4f570551..f12618f4a76bd 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,
@@ -38,12 +32,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,
@@ -57,6 +54,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 [
@@ -65,7 +81,13 @@ export const SearchIndexDetailsPage = () => {
name: i18n.translate('xpack.searchIndices.documentsTabLabel', {
defaultMessage: 'Data',
}),
- content: ,
+ content: (
+
+ ),
'data-test-subj': `${SearchIndexDetailsTabs.DATA}Tab`,
},
{
@@ -85,7 +107,7 @@ export const SearchIndexDetailsPage = () => {
'data-test-subj': `${SearchIndexDetailsTabs.SETTINGS}Tab`,
},
];
- }, [index, indexName]);
+ }, [index, indexName, indexDocuments, indexDocumentsIsInitialLoading]);
const [selectedTab, setSelectedTab] = useState(detailsPageTabs[0]);
useEffect(() => {
@@ -124,47 +146,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 (
@@ -209,20 +195,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 dabc8dffac09e..d3d459f6225dd 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 b6247f90b37c5..2f190cfdf7eef 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
@@ -104,9 +104,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 () => {
@@ -149,8 +152,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();
});