mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Onboarding][Index detail] Update right side menu items (#194604)
## 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
This commit is contained in:
parent
dfee92802d
commit
d482a0a295
6 changed files with 243 additions and 75 deletions
|
@ -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 ?? {};
|
||||
|
||||
|
|
|
@ -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<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 [
|
||||
|
@ -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`,
|
||||
},
|
||||
{
|
||||
|
@ -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<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>
|
||||
|
@ -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>,
|
||||
]}
|
||||
/>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue