Skip to content

Commit

Permalink
[Onboarding][Index detail] Update right side menu items (#194604)
Browse files Browse the repository at this point in the history
## 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
<img width="1728" alt="Screenshot 2024-10-01 at 11 07 45 AM"
src="https://github.com/user-attachments/assets/026c89f8-08fa-41cf-b47f-73fcc2fb07ef">
<img width="1728" alt="Screenshot 2024-10-01 at 11 08 20 AM"
src="https://github.com/user-attachments/assets/447641e0-8693-466a-a4d7-32764f86bf01">

**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
  • Loading branch information
saarikabhasi authored Oct 2, 2024
1 parent dfee928 commit d482a0a
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<IndexDocumentsProps> = ({ indexName }) => {
const { data: indexDocuments, isInitialLoading } = useIndexDocumentSearch(indexName, {
pageSize: DEFAULT_PAGE_SIZE,
pageIndex: 0,
});

export const IndexDocuments: React.FC<IndexDocumentsProps> = ({
indexName,
indexDocuments,
isInitialLoading,
}) => {
const { data: mappingData } = useIndexMapping(indexName);

const docs = indexDocuments?.results?.data ?? [];
const mappingProperties = mappingData?.mappings?.properties ?? {};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@ import {
EuiPageTemplate,
EuiFlexItem,
EuiFlexGroup,
EuiPopover,
EuiButtonIcon,
EuiContextMenuItem,
EuiContextMenuPanel,
EuiText,
EuiIcon,
EuiButtonEmpty,
EuiTabbedContent,
EuiTabbedContentTab,
Expand All @@ -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,
Expand All @@ -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<boolean>(false);
const [isDocumentsLoading, setDocumentsLoading] = useState<boolean>(true);
useEffect(() => {
setDocumentsLoading(isInitialLoading);
setDocumentsExists(!(!isInitialLoading && indexDocuments?.results?.data.length === 0));
}, [indexDocuments, isInitialLoading, setDocumentsExists, setDocumentsLoading]);

const detailsPageTabs: EuiTabbedContentTab[] = useMemo(() => {
return [
Expand All @@ -65,7 +81,13 @@ export const SearchIndexDetailsPage = () => {
name: i18n.translate('xpack.searchIndices.documentsTabLabel', {
defaultMessage: 'Data',
}),
content: <IndexDocuments indexName={indexName} />,
content: (
<IndexDocuments
indexName={indexName}
indexDocuments={indexDocuments}
isInitialLoading={indexDocumentsIsInitialLoading}
/>
),
'data-test-subj': `${SearchIndexDetailsTabs.DATA}Tab`,
},
{
Expand All @@ -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(() => {
Expand Down Expand Up @@ -124,47 +146,11 @@ export const SearchIndexDetailsPage = () => {
},
[isIndexError, indexLoadingError, mappingsError]
);
const [showMoreOptions, setShowMoreOptions] = useState<boolean>(false);
const [isShowingDeleteModal, setShowDeleteIndexModal] = useState<boolean>(false);
const moreOptionsPopover = (
<EuiPopover
isOpen={showMoreOptions}
closePopover={() => setShowMoreOptions(!showMoreOptions)}
button={
<EuiButtonIcon
iconType="boxesVertical"
onClick={() => setShowMoreOptions(!showMoreOptions)}
size="m"
data-test-subj="moreOptionsActionButton"
aria-label={i18n.translate('xpack.searchIndices.moreOptions.ariaLabel', {
defaultMessage: 'More options',
})}
/>
}
>
<EuiContextMenuPanel
data-test-subj="moreOptionsContextMenu"
items={[
<EuiContextMenuItem
key="trash"
icon={<EuiIcon type="trash" color="danger" />}
onClick={() => {
setShowDeleteIndexModal(!isShowingDeleteModal);
}}
size="s"
color="danger"
data-test-subj="moreOptionsDeleteIndex"
>
<EuiText size="s" color="danger">
{i18n.translate('xpack.searchIndices.moreOptions.deleteIndexLabel', {
defaultMessage: 'Delete Index',
})}
</EuiText>
</EuiContextMenuItem>,
]}
/>
</EuiPopover>
);
const handleDeleteIndexModal = useCallback(() => {
setShowDeleteIndexModal(!isShowingDeleteModal);
}, [isShowingDeleteModal]);

if (isInitialLoading || isMappingsInitialLoading) {
return (
<SectionLoading>
Expand Down Expand Up @@ -209,20 +195,47 @@ export const SearchIndexDetailsPage = () => {
data-test-subj="searchIndexDetailsHeader"
pageTitle={index?.name}
rightSideItems={[
<EuiFlexGroup>
<EuiFlexGroup gutterSize="none">
<EuiFlexItem>
<EuiButtonEmpty
href={docLinks.links.apiReference}
target="_blank"
iconType="documentation"
data-test-subj="ApiReferenceDoc"
>
{i18n.translate('xpack.searchIndices.indexActionsMenu.apiReference.docLink', {
defaultMessage: 'API Reference',
})}
</EuiButtonEmpty>
{!isDocumentsExists ? (
<EuiButtonEmpty
href={docLinks.links.apiReference}
target="_blank"
isLoading={isDocumentsLoading}
iconType="documentation"
data-test-subj="ApiReferenceDoc"
>
<FormattedMessage
id="xpack.searchIndices.indexAction.ApiReferenceButtonLabel"
defaultMessage="{buttonLabel}"
values={{
buttonLabel: isDocumentsLoading ? 'Loading' : 'API Reference',
}}
/>
</EuiButtonEmpty>
) : (
<EuiButtonEmpty
isLoading={isDocumentsLoading}
iconType="launch"
data-test-subj="useInPlaygroundLink"
onClick={navigateToPlayground}
>
<FormattedMessage
id="xpack.searchIndices.indexAction.useInPlaygroundButtonLabel"
defaultMessage="{buttonLabel}"
values={{
buttonLabel: isDocumentsLoading ? 'Loading' : 'Use in Playground',
}}
/>
</EuiButtonEmpty>
)}
</EuiFlexItem>
<EuiFlexItem>
<SearchIndexDetailsPageMenuItemPopover
handleDeleteIndexModal={handleDeleteIndexModal}
navigateToPlayground={navigateToPlayground}
/>
</EuiFlexItem>
<EuiFlexItem>{moreOptionsPopover}</EuiFlexItem>
</EuiFlexGroup>,
]}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -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: <EuiIcon type="launch" />,
target: undefined,
text: (
<EuiText size="s">
{i18n.translate('xpack.searchIndices.moreOptions.playgroundLabel', {
defaultMessage: 'Use in Playground',
})}
</EuiText>
),
color: undefined,
},
{
type: MenuItems.apiReference,
iconType: 'documentation',
dataTestSubj: 'moreOptionsApiReference',
iconComponent: <EuiIcon type="documentation" />,
target: '_blank',
text: (
<EuiText size="s">
{i18n.translate('xpack.searchIndices.moreOptions.apiReferenceLabel', {
defaultMessage: 'API Reference',
})}
</EuiText>
),
color: undefined,
},
{
type: MenuItems.deleteIndex,
iconType: 'trash',
dataTestSubj: 'moreOptionsDeleteIndex',
iconComponent: <EuiIcon color="danger" type="trash" />,
target: undefined,
text: (
<EuiText size="s" color="danger">
{i18n.translate('xpack.searchIndices.moreOptions.deleteIndexLabel', {
defaultMessage: 'Delete Index',
})}
</EuiText>
),
color: 'danger',
},
];
interface SearchIndexDetailsPageMenuItemPopoverProps {
handleDeleteIndexModal: () => void;
navigateToPlayground: () => void;
}

export const SearchIndexDetailsPageMenuItemPopover = ({
handleDeleteIndexModal,
navigateToPlayground,
}: SearchIndexDetailsPageMenuItemPopoverProps) => {
const [showMoreOptions, setShowMoreOptions] = useState<boolean>(false);
const { docLinks } = useKibana().services;
const contextMenuItemsActions: Record<MenuItems, MenuItemsAction> = {
playground: {
href: undefined,
onClick: navigateToPlayground,
},
apiReference: { href: docLinks.links.apiReference, onClick: undefined },
deleteIndex: { href: undefined, onClick: handleDeleteIndexModal },
};
const contextMenuItems: ReactElement[] = SearchIndexDetailsPageMenuItemPopoverItems.map(
(item) => (
<EuiContextMenuItem
key={item.iconType}
icon={item.iconComponent}
href={contextMenuItemsActions[item.type]?.href}
size="s"
onClick={contextMenuItemsActions[item.type]?.onClick}
target={item.target}
data-test-subj={item.dataTestSubj}
color={item.color}
>
{item.text}
</EuiContextMenuItem>
)
);

return (
<EuiPopover
isOpen={showMoreOptions}
closePopover={() => setShowMoreOptions(!showMoreOptions)}
button={
<EuiButtonIcon
iconType="boxesVertical"
onClick={() => setShowMoreOptions(!showMoreOptions)}
size="m"
data-test-subj="moreOptionsActionButton"
aria-label={i18n.translate('xpack.searchIndices.moreOptions.ariaLabel', {
defaultMessage: 'More options',
})}
/>
}
>
<EuiContextMenuPanel data-test-subj="moreOptionsContextMenu" items={contextMenuItems} />
</EuiPopover>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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<SearchHit>;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
},
Expand Down Expand Up @@ -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() {
Expand Down
Loading

0 comments on commit d482a0a

Please sign in to comment.