mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Search] Refactor: abstracting classic nav items (#196579)
## Summary Moved the base set of sidenav items from being statically defined in useEnterpriseSearchNav to using a function that can be shared with the plugin. Additionally wrapped this generation in a `useMemo` to improve performance. This will support the ability to share the classic navigation items for Search to other plugins so that they can render their own UIs without sharing components with enterprise_search just to have access to the side nav defined by enterprise_search. ### Checklist - [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 --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
b9a5d6a46d
commit
c4301d080b
37 changed files with 1180 additions and 358 deletions
|
@ -21,3 +21,7 @@ export const SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID = 'searchInferenceEndpo
|
|||
export const SEARCH_HOMEPAGE = 'searchHomepage';
|
||||
export const SEARCH_INDICES_START = 'elasticsearchStart';
|
||||
export const SEARCH_INDICES = 'elasticsearchIndices';
|
||||
export const SEARCH_ELASTICSEARCH = 'enterpriseSearchElasticsearch';
|
||||
export const SEARCH_VECTOR_SEARCH = 'enterpriseSearchVectorSearch';
|
||||
export const SEARCH_SEMANTIC_SEARCH = 'enterpriseSearchSemanticSearch';
|
||||
export const SEARCH_AI_SEARCH = 'enterpriseSearchAISearch';
|
||||
|
|
|
@ -22,6 +22,10 @@ import {
|
|||
SEARCH_HOMEPAGE,
|
||||
SEARCH_INDICES_START,
|
||||
SEARCH_INDICES,
|
||||
SEARCH_ELASTICSEARCH,
|
||||
SEARCH_VECTOR_SEARCH,
|
||||
SEARCH_SEMANTIC_SEARCH,
|
||||
SEARCH_AI_SEARCH,
|
||||
} from './constants';
|
||||
|
||||
export type EnterpriseSearchApp = typeof ENTERPRISE_SEARCH_APP_ID;
|
||||
|
@ -38,6 +42,10 @@ export type SearchInferenceEndpointsId = typeof SERVERLESS_ES_SEARCH_INFERENCE_E
|
|||
export type SearchHomepage = typeof SEARCH_HOMEPAGE;
|
||||
export type SearchStart = typeof SEARCH_INDICES_START;
|
||||
export type SearchIndices = typeof SEARCH_INDICES;
|
||||
export type SearchElasticsearch = typeof SEARCH_ELASTICSEARCH;
|
||||
export type SearchVectorSearch = typeof SEARCH_VECTOR_SEARCH;
|
||||
export type SearchSemanticSearch = typeof SEARCH_SEMANTIC_SEARCH;
|
||||
export type SearchAISearch = typeof SEARCH_AI_SEARCH;
|
||||
|
||||
export type ContentLinkId = 'searchIndices' | 'connectors' | 'webCrawlers';
|
||||
|
||||
|
@ -65,4 +73,8 @@ export type DeepLinkId =
|
|||
| `${EnterpriseSearchAppsearchApp}:${AppsearchLinkId}`
|
||||
| `${EnterpriseSearchRelevanceApp}:${RelevanceLinkId}`
|
||||
| SearchStart
|
||||
| SearchIndices;
|
||||
| SearchIndices
|
||||
| SearchElasticsearch
|
||||
| SearchVectorSearch
|
||||
| SearchSemanticSearch
|
||||
| SearchAISearch;
|
||||
|
|
|
@ -17,6 +17,10 @@ export {
|
|||
ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID,
|
||||
SERVERLESS_ES_APP_ID,
|
||||
SERVERLESS_ES_CONNECTORS_ID,
|
||||
SEARCH_ELASTICSEARCH,
|
||||
SEARCH_VECTOR_SEARCH,
|
||||
SEARCH_SEMANTIC_SEARCH,
|
||||
SEARCH_AI_SEARCH,
|
||||
} from './constants';
|
||||
|
||||
export type {
|
||||
|
|
|
@ -15,6 +15,10 @@ import {
|
|||
ENTERPRISE_SEARCH_ANALYTICS_APP_ID,
|
||||
ENTERPRISE_SEARCH_APPSEARCH_APP_ID,
|
||||
ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID,
|
||||
SEARCH_ELASTICSEARCH,
|
||||
SEARCH_VECTOR_SEARCH,
|
||||
SEARCH_SEMANTIC_SEARCH,
|
||||
SEARCH_AI_SEARCH,
|
||||
} from '@kbn/deeplinks-search';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
|
@ -58,7 +62,7 @@ export const ENTERPRISE_SEARCH_CONTENT_PLUGIN = {
|
|||
};
|
||||
|
||||
export const AI_SEARCH_PLUGIN = {
|
||||
ID: 'enterpriseSearchAISearch',
|
||||
ID: SEARCH_AI_SEARCH,
|
||||
NAME: i18n.translate('xpack.enterpriseSearch.aiSearch.productName', {
|
||||
defaultMessage: 'AI Search',
|
||||
}),
|
||||
|
@ -91,7 +95,7 @@ export const ANALYTICS_PLUGIN = {
|
|||
};
|
||||
|
||||
export const ELASTICSEARCH_PLUGIN = {
|
||||
ID: 'enterpriseSearchElasticsearch',
|
||||
ID: SEARCH_ELASTICSEARCH,
|
||||
NAME: i18n.translate('xpack.enterpriseSearch.elasticsearch.productName', {
|
||||
defaultMessage: 'Elasticsearch',
|
||||
}),
|
||||
|
@ -167,7 +171,7 @@ export const VECTOR_SEARCH_PLUGIN = {
|
|||
defaultMessage:
|
||||
'Elasticsearch can be used as a vector database, which enables vector search and semantic search use cases.',
|
||||
}),
|
||||
ID: 'enterpriseSearchVectorSearch',
|
||||
ID: SEARCH_VECTOR_SEARCH,
|
||||
LOGO: 'logoEnterpriseSearch',
|
||||
NAME: i18n.translate('xpack.enterpriseSearch.vectorSearch.productName', {
|
||||
defaultMessage: 'Vector Search',
|
||||
|
@ -184,7 +188,7 @@ export const SEMANTIC_SEARCH_PLUGIN = {
|
|||
defaultMessage:
|
||||
'Easily add semantic search to Elasticsearch with inference endpoints and the semantic_text field type, to boost search relevance.',
|
||||
}),
|
||||
ID: 'enterpriseSearchSemanticSearch',
|
||||
ID: SEARCH_SEMANTIC_SEARCH,
|
||||
LOGO: 'logoEnterpriseSearch',
|
||||
NAME: i18n.translate('xpack.enterpriseSearch.SemanticSearch.productName', {
|
||||
defaultMessage: 'Semantic Search',
|
||||
|
@ -297,3 +301,14 @@ export const CRAWLER = {
|
|||
|
||||
// TODO remove this once the connector service types are no longer in "example" state
|
||||
export const EXAMPLE_CONNECTOR_SERVICE_TYPES = ['opentext_documentum'];
|
||||
|
||||
export const GETTING_STARTED_TITLE = i18n.translate('xpack.enterpriseSearch.gettingStarted.title', {
|
||||
defaultMessage: 'Getting started',
|
||||
});
|
||||
|
||||
export const SEARCH_APPS_BREADCRUMB = i18n.translate(
|
||||
'xpack.enterpriseSearch.searchApplications.breadcrumb',
|
||||
{
|
||||
defaultMessage: 'Search Applications',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -41,6 +41,7 @@ export const mockKibanaValues = {
|
|||
data: dataPluginMock.createStartContract(),
|
||||
esConfig: { elasticsearch_host: 'https://your_deployment_url' },
|
||||
getChromeStyle$: jest.fn().mockReturnValue(of('classic')),
|
||||
getNavLinks: jest.fn().mockReturnValue([]),
|
||||
guidedOnboarding: {},
|
||||
history: mockHistory,
|
||||
indexMappingComponent: null,
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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 React, { useLayoutEffect } from 'react';
|
||||
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
|
||||
import { SEARCH_PRODUCT_NAME } from '../../../../../common/constants';
|
||||
import { KibanaLogic } from '../../../shared/kibana';
|
||||
import { SetSearchPlaygroundChrome } from '../../../shared/kibana_chrome/set_chrome';
|
||||
import { EnterpriseSearchPageTemplateWrapper, PageTemplateProps } from '../../../shared/layout';
|
||||
import { useEnterpriseSearchNav } from '../../../shared/layout';
|
||||
import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry';
|
||||
|
||||
import { PlaygroundHeaderDocsAction } from './header_docs_action';
|
||||
|
||||
export type SearchPlaygroundPageTemplateProps = Omit<
|
||||
PageTemplateProps,
|
||||
'useEndpointHeaderActions'
|
||||
> & {
|
||||
hasSchemaConflicts?: boolean;
|
||||
restrictWidth?: boolean;
|
||||
searchApplicationName?: string;
|
||||
};
|
||||
|
||||
export const SearchPlaygroundPageTemplate: React.FC<SearchPlaygroundPageTemplateProps> = ({
|
||||
children,
|
||||
pageChrome,
|
||||
pageViewTelemetry,
|
||||
searchApplicationName,
|
||||
hasSchemaConflicts,
|
||||
restrictWidth = true,
|
||||
...pageTemplateProps
|
||||
}) => {
|
||||
const navItems = useEnterpriseSearchNav();
|
||||
|
||||
const { renderHeaderActions, getChromeStyle$ } = useValues(KibanaLogic);
|
||||
const chromeStyle = useObservable(getChromeStyle$(), 'classic');
|
||||
|
||||
useLayoutEffect(() => {
|
||||
renderHeaderActions(PlaygroundHeaderDocsAction);
|
||||
|
||||
return () => {
|
||||
renderHeaderActions();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<EnterpriseSearchPageTemplateWrapper
|
||||
{...pageTemplateProps}
|
||||
solutionNav={{
|
||||
items: chromeStyle === 'classic' ? navItems : undefined,
|
||||
name: SEARCH_PRODUCT_NAME,
|
||||
}}
|
||||
restrictWidth={restrictWidth}
|
||||
setPageChrome={pageChrome && <SetSearchPlaygroundChrome trail={pageChrome} />}
|
||||
useEndpointHeaderActions={false}
|
||||
>
|
||||
{pageViewTelemetry && (
|
||||
<SendEnterpriseSearchTelemetry action="viewed" metric={pageViewTelemetry} />
|
||||
)}
|
||||
{children}
|
||||
</EnterpriseSearchPageTemplateWrapper>
|
||||
);
|
||||
};
|
|
@ -12,7 +12,8 @@ import { useValues } from 'kea';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { KibanaLogic } from '../../../shared/kibana';
|
||||
import { EnterpriseSearchApplicationsPageTemplate } from '../layout/page_template';
|
||||
|
||||
import { SearchPlaygroundPageTemplate } from './page_template';
|
||||
|
||||
export const Playground: React.FC = () => {
|
||||
const { searchPlayground } = useValues(KibanaLogic);
|
||||
|
@ -22,7 +23,7 @@ export const Playground: React.FC = () => {
|
|||
}
|
||||
return (
|
||||
<searchPlayground.PlaygroundProvider>
|
||||
<EnterpriseSearchApplicationsPageTemplate
|
||||
<SearchPlaygroundPageTemplate
|
||||
pageChrome={[
|
||||
i18n.translate('xpack.enterpriseSearch.content.playground.breadcrumb', {
|
||||
defaultMessage: 'Playground',
|
||||
|
@ -33,10 +34,9 @@ export const Playground: React.FC = () => {
|
|||
panelled={false}
|
||||
customPageSections
|
||||
bottomBorder="extended"
|
||||
docLink="playground"
|
||||
>
|
||||
<searchPlayground.Playground />
|
||||
</EnterpriseSearchApplicationsPageTemplate>
|
||||
</SearchPlaygroundPageTemplate>
|
||||
</searchPlayground.PlaygroundProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -40,7 +40,7 @@ export const ElasticsearchGuide = () => {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<EnterpriseSearchElasticsearchPageTemplate>
|
||||
<EnterpriseSearchElasticsearchPageTemplate pageChrome={[]}>
|
||||
{isFlyoutOpen && <CreateApiKeyFlyout onClose={() => setIsFlyoutOpen(false)} />}
|
||||
<EuiTitle size="l" data-test-subj="elasticsearchGuide">
|
||||
<h1>
|
||||
|
|
|
@ -19,13 +19,14 @@ export const EnterpriseSearchElasticsearchPageTemplate: React.FC<PageTemplatePro
|
|||
pageViewTelemetry,
|
||||
...pageTemplateProps
|
||||
}) => {
|
||||
const navItems = useEnterpriseSearchNav();
|
||||
return (
|
||||
<EnterpriseSearchPageTemplateWrapper
|
||||
{...pageTemplateProps}
|
||||
restrictWidth
|
||||
solutionNav={{
|
||||
name: SEARCH_PRODUCT_NAME,
|
||||
items: useEnterpriseSearchNav(),
|
||||
items: navItems,
|
||||
}}
|
||||
setPageChrome={pageChrome && <SetElasticsearchChrome trail={pageChrome} />}
|
||||
>
|
||||
|
|
|
@ -114,6 +114,7 @@ export const renderApp = (
|
|||
data: plugins.data,
|
||||
esConfig,
|
||||
getChromeStyle$: chrome.getChromeStyle$,
|
||||
getNavLinks: chrome.navLinks.getAll,
|
||||
guidedOnboarding,
|
||||
history,
|
||||
indexMappingComponent,
|
||||
|
|
|
@ -55,6 +55,7 @@ export interface KibanaLogicProps {
|
|||
data?: DataPublicPluginStart;
|
||||
esConfig: ESConfig;
|
||||
getChromeStyle$: ChromeStart['getChromeStyle$'];
|
||||
getNavLinks: ChromeStart['navLinks']['getAll'];
|
||||
guidedOnboarding?: GuidedOnboardingPluginStart;
|
||||
history: ScopedHistory;
|
||||
indexMappingComponent?: React.FC<IndexMappingProps>;
|
||||
|
@ -87,6 +88,7 @@ export interface KibanaValues {
|
|||
data: DataPublicPluginStart | null;
|
||||
esConfig: ESConfig;
|
||||
getChromeStyle$: ChromeStart['getChromeStyle$'];
|
||||
getNavLinks: ChromeStart['navLinks']['getAll'];
|
||||
guidedOnboarding: GuidedOnboardingPluginStart | null;
|
||||
history: ScopedHistory;
|
||||
indexMappingComponent: React.FC<IndexMappingProps> | null;
|
||||
|
@ -126,6 +128,7 @@ export const KibanaLogic = kea<MakeLogicType<KibanaValues>>({
|
|||
data: [props.data || null, {}],
|
||||
esConfig: [props.esConfig || { elasticsearch_host: ELASTICSEARCH_URL_PLACEHOLDER }, {}],
|
||||
getChromeStyle$: [props.getChromeStyle$, {}],
|
||||
getNavLinks: [props.getNavLinks, {}],
|
||||
guidedOnboarding: [props.guidedOnboarding || null, {}],
|
||||
history: [props.history, {}],
|
||||
indexMappingComponent: [props.indexMappingComponent || null, {}],
|
||||
|
|
|
@ -22,6 +22,8 @@ import {
|
|||
VECTOR_SEARCH_PLUGIN,
|
||||
WORKPLACE_SEARCH_PLUGIN,
|
||||
SEMANTIC_SEARCH_PLUGIN,
|
||||
APPLICATIONS_PLUGIN,
|
||||
GETTING_STARTED_TITLE,
|
||||
} from '../../../../common/constants';
|
||||
|
||||
import { stripLeadingSlash } from '../../../../common/strip_slashes';
|
||||
|
@ -126,7 +128,11 @@ export const useEnterpriseSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) =>
|
|||
]);
|
||||
|
||||
export const useAnalyticsBreadcrumbs = (breadcrumbs: Breadcrumbs = []) =>
|
||||
useSearchBreadcrumbs([{ text: ANALYTICS_PLUGIN.NAME, path: '/' }, ...breadcrumbs]);
|
||||
useSearchBreadcrumbs([
|
||||
{ text: APPLICATIONS_PLUGIN.NAV_TITLE },
|
||||
{ text: ANALYTICS_PLUGIN.NAME, path: '/' },
|
||||
...breadcrumbs,
|
||||
]);
|
||||
|
||||
export const useElasticsearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) =>
|
||||
useSearchBreadcrumbs([
|
||||
|
@ -161,13 +167,25 @@ export const useSearchExperiencesBreadcrumbs = (breadcrumbs: Breadcrumbs = []) =
|
|||
useSearchBreadcrumbs([{ text: SEARCH_EXPERIENCES_PLUGIN.NAV_TITLE, path: '/' }, ...breadcrumbs]);
|
||||
|
||||
export const useEnterpriseSearchApplicationsBreadcrumbs = (breadcrumbs: Breadcrumbs = []) =>
|
||||
useSearchBreadcrumbs(breadcrumbs);
|
||||
useSearchBreadcrumbs([{ text: APPLICATIONS_PLUGIN.NAV_TITLE }, ...breadcrumbs]);
|
||||
|
||||
export const useAiSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) =>
|
||||
useSearchBreadcrumbs([{ text: AI_SEARCH_PLUGIN.NAME, path: '/' }, ...breadcrumbs]);
|
||||
useSearchBreadcrumbs([
|
||||
{ text: GETTING_STARTED_TITLE },
|
||||
{ text: AI_SEARCH_PLUGIN.NAME, path: '/' },
|
||||
...breadcrumbs,
|
||||
]);
|
||||
|
||||
export const useVectorSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) =>
|
||||
useSearchBreadcrumbs([{ text: VECTOR_SEARCH_PLUGIN.NAV_TITLE, path: '/' }, ...breadcrumbs]);
|
||||
useSearchBreadcrumbs([
|
||||
{ text: GETTING_STARTED_TITLE },
|
||||
{ text: VECTOR_SEARCH_PLUGIN.NAV_TITLE, path: '/' },
|
||||
...breadcrumbs,
|
||||
]);
|
||||
|
||||
export const useSemanticSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) =>
|
||||
useSearchBreadcrumbs([{ text: SEMANTIC_SEARCH_PLUGIN.NAME, path: '/' }, ...breadcrumbs]);
|
||||
useSearchBreadcrumbs([
|
||||
{ text: GETTING_STARTED_TITLE },
|
||||
{ text: SEMANTIC_SEARCH_PLUGIN.NAME, path: '/' },
|
||||
...breadcrumbs,
|
||||
]);
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import {
|
||||
AI_SEARCH_PLUGIN,
|
||||
ANALYTICS_PLUGIN,
|
||||
|
@ -40,7 +42,12 @@ export const searchTitle = (page: Title = []) => generateTitle([...page, SEARCH_
|
|||
export const analyticsTitle = (page: Title = []) => generateTitle([...page, ANALYTICS_PLUGIN.NAME]);
|
||||
|
||||
export const elasticsearchTitle = (page: Title = []) =>
|
||||
generateTitle([...page, 'Getting started with Elasticsearch']);
|
||||
generateTitle([
|
||||
...page,
|
||||
i18n.translate('xpack.enterpriseSearch.titles.elasticsearch', {
|
||||
defaultMessage: 'Getting started with Elasticsearch',
|
||||
}),
|
||||
]);
|
||||
|
||||
export const appSearchTitle = (page: Title = []) =>
|
||||
generateTitle([...page, APP_SEARCH_PLUGIN.NAME]);
|
||||
|
@ -61,3 +68,11 @@ export const semanticSearchTitle = (page: Title = []) =>
|
|||
|
||||
export const enterpriseSearchContentTitle = (page: Title = []) =>
|
||||
generateTitle([...page, ENTERPRISE_SEARCH_CONTENT_PLUGIN.NAME]);
|
||||
|
||||
export const searchApplicationsTitle = (page: Title = []) =>
|
||||
generateTitle([
|
||||
...page,
|
||||
i18n.translate('xpack.enterpriseSearch.titles.searchApplications', {
|
||||
defaultMessage: 'Search Applications',
|
||||
}),
|
||||
]);
|
||||
|
|
|
@ -9,8 +9,7 @@ import React, { useEffect } from 'react';
|
|||
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import { APPLICATIONS_PLUGIN } from '../../../../common/constants';
|
||||
|
||||
import { SEARCH_APPS_BREADCRUMB } from '../../../../common/constants';
|
||||
import { KibanaLogic } from '../kibana';
|
||||
|
||||
import {
|
||||
|
@ -35,6 +34,8 @@ import {
|
|||
appSearchTitle,
|
||||
elasticsearchTitle,
|
||||
enterpriseSearchContentTitle,
|
||||
generateTitle,
|
||||
searchApplicationsTitle,
|
||||
searchExperiencesTitle,
|
||||
searchTitle,
|
||||
semanticSearchTitle,
|
||||
|
@ -210,14 +211,30 @@ export const SetSearchExperiencesChrome: React.FC<SetChromeProps> = ({ trail = [
|
|||
return null;
|
||||
};
|
||||
|
||||
export const SetSearchPlaygroundChrome: React.FC<SetChromeProps> = ({ trail = [] }) => {
|
||||
const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic);
|
||||
|
||||
const title = reverseArray(trail);
|
||||
const docTitle = generateTitle(title);
|
||||
|
||||
const breadcrumbs = useEnterpriseSearchApplicationsBreadcrumbs(useGenerateBreadcrumbs(trail));
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumbs(breadcrumbs);
|
||||
setDocTitle(docTitle);
|
||||
}, [trail]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const SetEnterpriseSearchApplicationsChrome: React.FC<SetChromeProps> = ({ trail = [] }) => {
|
||||
const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic);
|
||||
|
||||
const title = reverseArray(trail);
|
||||
const docTitle = appSearchTitle(title);
|
||||
const docTitle = searchApplicationsTitle(title);
|
||||
|
||||
const breadcrumbs = useEnterpriseSearchApplicationsBreadcrumbs(
|
||||
useGenerateBreadcrumbs([APPLICATIONS_PLUGIN.NAV_TITLE, ...trail])
|
||||
useGenerateBreadcrumbs([SEARCH_APPS_BREADCRUMB, ...trail])
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import {
|
||||
ENTERPRISE_SEARCH_APP_ID,
|
||||
ENTERPRISE_SEARCH_ANALYTICS_APP_ID,
|
||||
SEARCH_ELASTICSEARCH,
|
||||
SEARCH_VECTOR_SEARCH,
|
||||
SEARCH_SEMANTIC_SEARCH,
|
||||
SEARCH_AI_SEARCH,
|
||||
} from '@kbn/deeplinks-search';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { GETTING_STARTED_TITLE } from '../../../../common/constants';
|
||||
|
||||
import { ClassicNavItem, BuildClassicNavParameters } from '../types';
|
||||
|
||||
export const buildBaseClassicNavItems = ({
|
||||
productAccess,
|
||||
}: BuildClassicNavParameters): ClassicNavItem[] => {
|
||||
const navItems: ClassicNavItem[] = [];
|
||||
|
||||
// Home
|
||||
navItems.push({
|
||||
'data-test-subj': 'searchSideNav-Home',
|
||||
deepLink: {
|
||||
link: ENTERPRISE_SEARCH_APP_ID,
|
||||
shouldShowActiveForSubroutes: true,
|
||||
},
|
||||
id: 'home',
|
||||
name: (
|
||||
<EuiText size="s">
|
||||
{i18n.translate('xpack.enterpriseSearch.nav.homeTitle', {
|
||||
defaultMessage: 'Home',
|
||||
})}
|
||||
</EuiText>
|
||||
),
|
||||
});
|
||||
|
||||
// Content
|
||||
navItems.push({
|
||||
'data-test-subj': 'searchSideNav-Content',
|
||||
id: 'content',
|
||||
items: [
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-Indices',
|
||||
deepLink: {
|
||||
link: 'enterpriseSearchContent:searchIndices',
|
||||
shouldShowActiveForSubroutes: true,
|
||||
},
|
||||
id: 'search_indices',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-Connectors',
|
||||
deepLink: {
|
||||
link: 'enterpriseSearchContent:connectors',
|
||||
shouldShowActiveForSubroutes: true,
|
||||
},
|
||||
id: 'connectors',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-Crawlers',
|
||||
deepLink: {
|
||||
link: 'enterpriseSearchContent:webCrawlers',
|
||||
shouldShowActiveForSubroutes: true,
|
||||
},
|
||||
id: 'crawlers',
|
||||
},
|
||||
],
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.contentTitle', {
|
||||
defaultMessage: 'Content',
|
||||
}),
|
||||
});
|
||||
|
||||
// Build
|
||||
navItems.push({
|
||||
'data-test-subj': 'searchSideNav-Build',
|
||||
id: 'build',
|
||||
items: [
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-Playground',
|
||||
deepLink: {
|
||||
link: 'enterpriseSearchApplications:playground',
|
||||
shouldShowActiveForSubroutes: true,
|
||||
},
|
||||
id: 'playground',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-SearchApplications',
|
||||
deepLink: {
|
||||
link: 'enterpriseSearchApplications:searchApplications',
|
||||
},
|
||||
id: 'searchApplications',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-BehavioralAnalytics',
|
||||
deepLink: {
|
||||
link: ENTERPRISE_SEARCH_ANALYTICS_APP_ID,
|
||||
},
|
||||
id: 'analyticsCollections',
|
||||
},
|
||||
],
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.applicationsTitle', {
|
||||
defaultMessage: 'Build',
|
||||
}),
|
||||
});
|
||||
|
||||
navItems.push({
|
||||
'data-test-subj': 'searchSideNav-Relevance',
|
||||
id: 'relevance',
|
||||
items: [
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-InferenceEndpoints',
|
||||
deepLink: {
|
||||
link: 'searchInferenceEndpoints:inferenceEndpoints',
|
||||
shouldShowActiveForSubroutes: true,
|
||||
},
|
||||
id: 'inference_endpoints',
|
||||
},
|
||||
],
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.relevanceTitle', {
|
||||
defaultMessage: 'Relevance',
|
||||
}),
|
||||
});
|
||||
|
||||
// Getting Started
|
||||
navItems.push({
|
||||
'data-test-subj': 'searchSideNav-GettingStarted',
|
||||
id: 'es_getting_started',
|
||||
items: [
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-Elasticsearch',
|
||||
deepLink: {
|
||||
link: SEARCH_ELASTICSEARCH,
|
||||
},
|
||||
id: 'elasticsearch',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-VectorSearch',
|
||||
deepLink: {
|
||||
link: SEARCH_VECTOR_SEARCH,
|
||||
},
|
||||
id: 'vectorSearch',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-SemanticSearch',
|
||||
deepLink: {
|
||||
link: SEARCH_SEMANTIC_SEARCH,
|
||||
},
|
||||
id: 'semanticSearch',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-AISearch',
|
||||
deepLink: {
|
||||
link: SEARCH_AI_SEARCH,
|
||||
},
|
||||
id: 'aiSearch',
|
||||
},
|
||||
],
|
||||
name: GETTING_STARTED_TITLE,
|
||||
});
|
||||
|
||||
if (productAccess.hasAppSearchAccess || productAccess.hasWorkplaceSearchAccess) {
|
||||
const entSearchItems: ClassicNavItem[] = [];
|
||||
if (productAccess.hasAppSearchAccess) {
|
||||
entSearchItems.push({
|
||||
'data-test-subj': 'searchSideNav-AppSearch',
|
||||
deepLink: {
|
||||
link: 'appSearch:engines',
|
||||
},
|
||||
id: 'app_search',
|
||||
});
|
||||
}
|
||||
if (productAccess.hasWorkplaceSearchAccess) {
|
||||
entSearchItems.push({
|
||||
'data-test-subj': 'searchSideNav-WorkplaceSearch',
|
||||
deepLink: {
|
||||
link: 'workplaceSearch',
|
||||
},
|
||||
id: 'workplace_search',
|
||||
});
|
||||
}
|
||||
navItems.push({
|
||||
'data-test-subj': 'searchSideNav-EnterpriseSearch',
|
||||
id: 'enterpriseSearch',
|
||||
items: entSearchItems,
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.title', {
|
||||
defaultMessage: 'Enterprise Search',
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
return navItems;
|
||||
};
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* 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 { mockKibanaValues } from '../../__mocks__/kea_logic';
|
||||
|
||||
import type { ChromeNavLink } from '@kbn/core-chrome-browser';
|
||||
|
||||
import '../../__mocks__/react_router';
|
||||
|
||||
jest.mock('../react_router_helpers/link_events', () => ({
|
||||
letBrowserHandleEvent: jest.fn(),
|
||||
}));
|
||||
|
||||
import { ClassicNavItem } from '../types';
|
||||
|
||||
import { generateSideNavItems } from './classic_nav_helpers';
|
||||
|
||||
describe('generateSideNavItems', () => {
|
||||
const deepLinksMap = {
|
||||
enterpriseSearch: {
|
||||
id: 'enterpriseSearch',
|
||||
url: '/app/enterprise_search/overview',
|
||||
title: 'Overview',
|
||||
},
|
||||
'enterpriseSearchContent:searchIndices': {
|
||||
id: 'enterpriseSearchContent:searchIndices',
|
||||
title: 'Indices',
|
||||
url: '/app/enterprise_search/content/search_indices',
|
||||
},
|
||||
'enterpriseSearchContent:connectors': {
|
||||
id: 'enterpriseSearchContent:connectors',
|
||||
title: 'Connectors',
|
||||
url: '/app/enterprise_search/content/connectors',
|
||||
},
|
||||
'enterpriseSearchContent:webCrawlers': {
|
||||
id: 'enterpriseSearchContent:webCrawlers',
|
||||
title: 'Web crawlers',
|
||||
url: '/app/enterprise_search/content/crawlers',
|
||||
},
|
||||
} as unknown as Record<string, ChromeNavLink | undefined>;
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockKibanaValues.history.location.pathname = '/';
|
||||
});
|
||||
|
||||
it('renders top-level items', () => {
|
||||
const classicNavItems: ClassicNavItem[] = [
|
||||
{
|
||||
id: 'unit-test',
|
||||
deepLink: {
|
||||
link: 'enterpriseSearch',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
expect(generateSideNavItems(classicNavItems, deepLinksMap)).toEqual([
|
||||
{
|
||||
href: '/app/enterprise_search/overview',
|
||||
id: 'unit-test',
|
||||
isSelected: false,
|
||||
name: 'Overview',
|
||||
onClick: expect.any(Function),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('renders items with children', () => {
|
||||
const classicNavItems: ClassicNavItem[] = [
|
||||
{
|
||||
id: 'parent',
|
||||
name: 'Parent',
|
||||
items: [
|
||||
{
|
||||
id: 'unit-test',
|
||||
deepLink: {
|
||||
link: 'enterpriseSearch',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
expect(generateSideNavItems(classicNavItems, deepLinksMap)).toEqual([
|
||||
{
|
||||
id: 'parent',
|
||||
items: [
|
||||
{
|
||||
href: '/app/enterprise_search/overview',
|
||||
id: 'unit-test',
|
||||
isSelected: false,
|
||||
name: 'Overview',
|
||||
onClick: expect.any(Function),
|
||||
},
|
||||
],
|
||||
name: 'Parent',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('renders classic nav name over deep link title if provided', () => {
|
||||
const classicNavItems: ClassicNavItem[] = [
|
||||
{
|
||||
deepLink: {
|
||||
link: 'enterpriseSearch',
|
||||
},
|
||||
id: 'unit-test',
|
||||
name: 'Home',
|
||||
},
|
||||
];
|
||||
|
||||
expect(generateSideNavItems(classicNavItems, deepLinksMap)).toEqual([
|
||||
{
|
||||
href: '/app/enterprise_search/overview',
|
||||
id: 'unit-test',
|
||||
isSelected: false,
|
||||
name: 'Home',
|
||||
onClick: expect.any(Function),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('removes item if deep link is not defined', () => {
|
||||
const classicNavItems: ClassicNavItem[] = [
|
||||
{
|
||||
deepLink: {
|
||||
link: 'enterpriseSearch',
|
||||
},
|
||||
id: 'unit-test',
|
||||
name: 'Home',
|
||||
},
|
||||
{
|
||||
deepLink: {
|
||||
link: 'enterpriseSearchApplications:playground',
|
||||
},
|
||||
id: 'unit-test-missing',
|
||||
},
|
||||
];
|
||||
|
||||
expect(generateSideNavItems(classicNavItems, deepLinksMap)).toEqual([
|
||||
{
|
||||
href: '/app/enterprise_search/overview',
|
||||
id: 'unit-test',
|
||||
isSelected: false,
|
||||
name: 'Home',
|
||||
onClick: expect.any(Function),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('adds pre-rendered child items provided', () => {
|
||||
const classicNavItems: ClassicNavItem[] = [
|
||||
{
|
||||
id: 'unit-test',
|
||||
name: 'Indices',
|
||||
},
|
||||
];
|
||||
const subItems = {
|
||||
'unit-test': [
|
||||
{
|
||||
href: '/app/unit-test',
|
||||
id: 'child',
|
||||
isSelected: true,
|
||||
name: 'Index',
|
||||
onClick: jest.fn(),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(generateSideNavItems(classicNavItems, deepLinksMap, subItems)).toEqual([
|
||||
{
|
||||
id: 'unit-test',
|
||||
items: [
|
||||
{
|
||||
href: '/app/unit-test',
|
||||
id: 'child',
|
||||
isSelected: true,
|
||||
name: 'Index',
|
||||
onClick: expect.any(Function),
|
||||
},
|
||||
],
|
||||
name: 'Indices',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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 { ChromeNavLink, EuiSideNavItemTypeEnhanced } from '@kbn/core-chrome-browser';
|
||||
|
||||
import {
|
||||
ClassicNavItem,
|
||||
GenerateNavLinkFromDeepLinkParameters,
|
||||
GenerateNavLinkParameters,
|
||||
} from '../types';
|
||||
|
||||
import { generateNavLink } from './nav_link_helpers';
|
||||
|
||||
export const generateSideNavItems = (
|
||||
navItems: ClassicNavItem[],
|
||||
deepLinks: Record<string, ChromeNavLink | undefined>,
|
||||
subItemsMap: Record<string, Array<EuiSideNavItemTypeEnhanced<unknown>> | undefined> = {}
|
||||
): Array<EuiSideNavItemTypeEnhanced<unknown>> => {
|
||||
const sideNavItems: Array<EuiSideNavItemTypeEnhanced<unknown>> = [];
|
||||
|
||||
for (const navItem of navItems) {
|
||||
let sideNavChildItems: Array<EuiSideNavItemTypeEnhanced<unknown>> | undefined;
|
||||
|
||||
const { deepLink, items, ...rest } = navItem;
|
||||
const subItems = subItemsMap?.[navItem.id];
|
||||
|
||||
if (items || subItems) {
|
||||
sideNavChildItems = [];
|
||||
if (items) {
|
||||
sideNavChildItems.push(...generateSideNavItems(items, deepLinks, subItemsMap));
|
||||
}
|
||||
if (subItems) {
|
||||
sideNavChildItems.push(...subItems);
|
||||
}
|
||||
}
|
||||
|
||||
let sideNavItem: EuiSideNavItemTypeEnhanced<unknown> | undefined;
|
||||
if (deepLink) {
|
||||
const navLinkParams = getNavLinkParameters(deepLink, deepLinks);
|
||||
if (navLinkParams !== undefined) {
|
||||
const name = navItem.name ?? getDeepLinkTitle(deepLink.link, deepLinks);
|
||||
sideNavItem = {
|
||||
...rest,
|
||||
name,
|
||||
...generateNavLink({
|
||||
...navLinkParams,
|
||||
items: sideNavChildItems,
|
||||
}),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
sideNavItem = {
|
||||
...rest,
|
||||
items: sideNavChildItems,
|
||||
name: navItem.name,
|
||||
};
|
||||
}
|
||||
|
||||
if (isValidSideNavItem(sideNavItem)) {
|
||||
sideNavItems.push(sideNavItem);
|
||||
}
|
||||
}
|
||||
|
||||
return sideNavItems;
|
||||
};
|
||||
|
||||
const getNavLinkParameters = (
|
||||
navLink: GenerateNavLinkFromDeepLinkParameters,
|
||||
deepLinks: Record<string, ChromeNavLink | undefined>
|
||||
): GenerateNavLinkParameters | undefined => {
|
||||
const { link, ...navLinkProps } = navLink;
|
||||
const deepLink = deepLinks[link];
|
||||
if (!deepLink || !deepLink.url) return undefined;
|
||||
return {
|
||||
...navLinkProps,
|
||||
shouldNotCreateHref: true,
|
||||
shouldNotPrepend: true,
|
||||
to: deepLink.url,
|
||||
};
|
||||
};
|
||||
const getDeepLinkTitle = (
|
||||
link: string,
|
||||
deepLinks: Record<string, ChromeNavLink | undefined>
|
||||
): string | undefined => {
|
||||
const deepLink = deepLinks[link];
|
||||
if (!deepLink || !deepLink.url) return undefined;
|
||||
return deepLink.title;
|
||||
};
|
||||
|
||||
function isValidSideNavItem(
|
||||
item: EuiSideNavItemTypeEnhanced<unknown> | undefined
|
||||
): item is EuiSideNavItemTypeEnhanced<unknown> {
|
||||
if (item === undefined) return false;
|
||||
if (item.href || item.onClick) return true;
|
||||
if (item?.items?.length ?? 0 > 0) return true;
|
||||
|
||||
return false;
|
||||
}
|
|
@ -15,6 +15,8 @@ jest.mock('../../enterprise_search_content/components/search_index/indices/indic
|
|||
|
||||
import { setMockValues, mockKibanaValues } from '../../__mocks__/kea_logic';
|
||||
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
import { EuiSideNavItemType } from '@elastic/eui';
|
||||
|
||||
import { DEFAULT_PRODUCT_FEATURES } from '../../../../common/constants';
|
||||
|
@ -32,26 +34,31 @@ const DEFAULT_PRODUCT_ACCESS: ProductAccess = {
|
|||
};
|
||||
const baseNavItems = [
|
||||
expect.objectContaining({
|
||||
'data-test-subj': 'searchSideNav-Home',
|
||||
href: '/app/enterprise_search/overview',
|
||||
id: 'home',
|
||||
items: undefined,
|
||||
}),
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-Content',
|
||||
id: 'content',
|
||||
items: [
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-Indices',
|
||||
href: '/app/enterprise_search/content/search_indices',
|
||||
id: 'search_indices',
|
||||
items: [],
|
||||
name: 'Indices',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-Connectors',
|
||||
href: '/app/enterprise_search/content/connectors',
|
||||
id: 'connectors',
|
||||
items: undefined,
|
||||
name: 'Connectors',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-Crawlers',
|
||||
href: '/app/enterprise_search/content/crawlers',
|
||||
id: 'crawlers',
|
||||
items: undefined,
|
||||
|
@ -61,21 +68,25 @@ const baseNavItems = [
|
|||
name: 'Content',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-Build',
|
||||
id: 'build',
|
||||
items: [
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-Playground',
|
||||
href: '/app/enterprise_search/applications/playground',
|
||||
id: 'playground',
|
||||
items: undefined,
|
||||
name: 'Playground',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-SearchApplications',
|
||||
href: '/app/enterprise_search/applications/search_applications',
|
||||
id: 'searchApplications',
|
||||
items: undefined,
|
||||
name: 'Search Applications',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-BehavioralAnalytics',
|
||||
href: '/app/enterprise_search/analytics',
|
||||
id: 'analyticsCollections',
|
||||
items: undefined,
|
||||
|
@ -85,9 +96,11 @@ const baseNavItems = [
|
|||
name: 'Build',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-Relevance',
|
||||
id: 'relevance',
|
||||
items: [
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-InferenceEndpoints',
|
||||
href: '/app/enterprise_search/relevance/inference_endpoints',
|
||||
id: 'inference_endpoints',
|
||||
items: undefined,
|
||||
|
@ -97,27 +110,32 @@ const baseNavItems = [
|
|||
name: 'Relevance',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-GettingStarted',
|
||||
id: 'es_getting_started',
|
||||
items: [
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-Elasticsearch',
|
||||
href: '/app/enterprise_search/elasticsearch',
|
||||
id: 'elasticsearch',
|
||||
items: undefined,
|
||||
name: 'Elasticsearch',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-VectorSearch',
|
||||
href: '/app/enterprise_search/vector_search',
|
||||
id: 'vectorSearch',
|
||||
items: undefined,
|
||||
name: 'Vector Search',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-SemanticSearch',
|
||||
href: '/app/enterprise_search/semantic_search',
|
||||
id: 'semanticSearch',
|
||||
items: undefined,
|
||||
name: 'Semantic Search',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-AISearch',
|
||||
href: '/app/enterprise_search/ai_search',
|
||||
id: 'aiSearch',
|
||||
items: undefined,
|
||||
|
@ -127,15 +145,18 @@ const baseNavItems = [
|
|||
name: 'Getting started',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-EnterpriseSearch',
|
||||
id: 'enterpriseSearch',
|
||||
items: [
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-AppSearch',
|
||||
href: '/app/enterprise_search/app_search',
|
||||
id: 'app_search',
|
||||
items: undefined,
|
||||
name: 'App Search',
|
||||
},
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-WorkplaceSearch',
|
||||
href: '/app/enterprise_search/workplace_search',
|
||||
id: 'workplace_search',
|
||||
items: undefined,
|
||||
|
@ -146,21 +167,102 @@ const baseNavItems = [
|
|||
},
|
||||
];
|
||||
|
||||
const mockNavLinks = [
|
||||
{
|
||||
id: 'enterpriseSearch',
|
||||
url: '/app/enterprise_search/overview',
|
||||
},
|
||||
{
|
||||
id: 'enterpriseSearchContent:searchIndices',
|
||||
title: 'Indices',
|
||||
url: '/app/enterprise_search/content/search_indices',
|
||||
},
|
||||
{
|
||||
id: 'enterpriseSearchContent:connectors',
|
||||
title: 'Connectors',
|
||||
url: '/app/enterprise_search/content/connectors',
|
||||
},
|
||||
{
|
||||
id: 'enterpriseSearchContent:webCrawlers',
|
||||
title: 'Web crawlers',
|
||||
url: '/app/enterprise_search/content/crawlers',
|
||||
},
|
||||
{
|
||||
id: 'enterpriseSearchApplications:playground',
|
||||
title: 'Playground',
|
||||
url: '/app/enterprise_search/applications/playground',
|
||||
},
|
||||
{
|
||||
id: 'enterpriseSearchApplications:searchApplications',
|
||||
title: 'Search Applications',
|
||||
url: '/app/enterprise_search/applications/search_applications',
|
||||
},
|
||||
{
|
||||
id: 'enterpriseSearchAnalytics',
|
||||
title: 'Behavioral Analytics',
|
||||
url: '/app/enterprise_search/analytics',
|
||||
},
|
||||
{
|
||||
id: 'searchInferenceEndpoints:inferenceEndpoints',
|
||||
title: 'Inference Endpoints',
|
||||
url: '/app/enterprise_search/relevance/inference_endpoints',
|
||||
},
|
||||
{
|
||||
id: 'appSearch:engines',
|
||||
title: 'App Search',
|
||||
url: '/app/enterprise_search/app_search',
|
||||
},
|
||||
{
|
||||
id: 'workplaceSearch',
|
||||
title: 'Workplace Search',
|
||||
url: '/app/enterprise_search/workplace_search',
|
||||
},
|
||||
{
|
||||
id: 'enterpriseSearchElasticsearch',
|
||||
title: 'Elasticsearch',
|
||||
url: '/app/enterprise_search/elasticsearch',
|
||||
},
|
||||
{
|
||||
id: 'enterpriseSearchVectorSearch',
|
||||
title: 'Vector Search',
|
||||
url: '/app/enterprise_search/vector_search',
|
||||
},
|
||||
{
|
||||
id: 'enterpriseSearchSemanticSearch',
|
||||
title: 'Semantic Search',
|
||||
url: '/app/enterprise_search/semantic_search',
|
||||
},
|
||||
{
|
||||
id: 'enterpriseSearchAISearch',
|
||||
title: 'AI Search',
|
||||
url: '/app/enterprise_search/ai_search',
|
||||
},
|
||||
];
|
||||
|
||||
const defaultMockValues = {
|
||||
hasEnterpriseLicense: true,
|
||||
isSidebarEnabled: true,
|
||||
productAccess: DEFAULT_PRODUCT_ACCESS,
|
||||
productFeatures: DEFAULT_PRODUCT_FEATURES,
|
||||
};
|
||||
|
||||
describe('useEnterpriseSearchContentNav', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockKibanaValues.uiSettings.get.mockReturnValue(false);
|
||||
mockKibanaValues.getNavLinks.mockReturnValue(mockNavLinks);
|
||||
});
|
||||
|
||||
it('returns an array of top-level Enterprise Search nav items', () => {
|
||||
const fullProductAccess: ProductAccess = DEFAULT_PRODUCT_ACCESS;
|
||||
setMockValues({
|
||||
isSidebarEnabled: true,
|
||||
...defaultMockValues,
|
||||
productAccess: fullProductAccess,
|
||||
productFeatures: DEFAULT_PRODUCT_FEATURES,
|
||||
});
|
||||
|
||||
expect(useEnterpriseSearchNav()).toEqual(baseNavItems);
|
||||
const { result } = renderHook(() => useEnterpriseSearchNav());
|
||||
|
||||
expect(result.current).toEqual(baseNavItems);
|
||||
});
|
||||
|
||||
it('excludes legacy products when the user has no access to them', () => {
|
||||
|
@ -171,13 +273,13 @@ describe('useEnterpriseSearchContentNav', () => {
|
|||
};
|
||||
|
||||
setMockValues({
|
||||
isSidebarEnabled: true,
|
||||
...defaultMockValues,
|
||||
productAccess: noProductAccess,
|
||||
productFeatures: DEFAULT_PRODUCT_FEATURES,
|
||||
});
|
||||
mockKibanaValues.uiSettings.get.mockReturnValue(false);
|
||||
|
||||
const esNav = useEnterpriseSearchNav();
|
||||
const { result } = renderHook(() => useEnterpriseSearchNav());
|
||||
const esNav = result.current;
|
||||
const legacyESNav = esNav?.find((item) => item.id === 'enterpriseSearch');
|
||||
expect(legacyESNav).toBeUndefined();
|
||||
});
|
||||
|
@ -190,18 +292,20 @@ describe('useEnterpriseSearchContentNav', () => {
|
|||
};
|
||||
|
||||
setMockValues({
|
||||
isSidebarEnabled: true,
|
||||
...defaultMockValues,
|
||||
productAccess: workplaceSearchProductAccess,
|
||||
productFeatures: DEFAULT_PRODUCT_FEATURES,
|
||||
});
|
||||
|
||||
const esNav = useEnterpriseSearchNav();
|
||||
const { result } = renderHook(() => useEnterpriseSearchNav());
|
||||
const esNav = result.current;
|
||||
const legacyESNav = esNav?.find((item) => item.id === 'enterpriseSearch');
|
||||
expect(legacyESNav).not.toBeUndefined();
|
||||
expect(legacyESNav).toEqual({
|
||||
'data-test-subj': 'searchSideNav-EnterpriseSearch',
|
||||
id: 'enterpriseSearch',
|
||||
items: [
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-WorkplaceSearch',
|
||||
href: '/app/enterprise_search/workplace_search',
|
||||
id: 'workplace_search',
|
||||
name: 'Workplace Search',
|
||||
|
@ -218,18 +322,20 @@ describe('useEnterpriseSearchContentNav', () => {
|
|||
};
|
||||
|
||||
setMockValues({
|
||||
isSidebarEnabled: true,
|
||||
...defaultMockValues,
|
||||
productAccess: appSearchProductAccess,
|
||||
productFeatures: DEFAULT_PRODUCT_FEATURES,
|
||||
});
|
||||
|
||||
const esNav = useEnterpriseSearchNav();
|
||||
const { result } = renderHook(() => useEnterpriseSearchNav());
|
||||
const esNav = result.current;
|
||||
const legacyESNav = esNav?.find((item) => item.id === 'enterpriseSearch');
|
||||
expect(legacyESNav).not.toBeUndefined();
|
||||
expect(legacyESNav).toEqual({
|
||||
'data-test-subj': 'searchSideNav-EnterpriseSearch',
|
||||
id: 'enterpriseSearch',
|
||||
items: [
|
||||
{
|
||||
'data-test-subj': 'searchSideNav-AppSearch',
|
||||
href: '/app/enterprise_search/app_search',
|
||||
id: 'app_search',
|
||||
name: 'App Search',
|
||||
|
@ -243,21 +349,21 @@ describe('useEnterpriseSearchContentNav', () => {
|
|||
describe('useEnterpriseSearchApplicationNav', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockKibanaValues.getNavLinks.mockReturnValue(mockNavLinks);
|
||||
mockKibanaValues.uiSettings.get.mockReturnValue(true);
|
||||
setMockValues({
|
||||
isSidebarEnabled: true,
|
||||
productAccess: DEFAULT_PRODUCT_ACCESS,
|
||||
productFeatures: DEFAULT_PRODUCT_FEATURES,
|
||||
});
|
||||
setMockValues(defaultMockValues);
|
||||
});
|
||||
|
||||
it('returns an array of top-level Enterprise Search nav items', () => {
|
||||
expect(useEnterpriseSearchApplicationNav()).toEqual(baseNavItems);
|
||||
const { result } = renderHook(() => useEnterpriseSearchApplicationNav());
|
||||
expect(result.current).toEqual(baseNavItems);
|
||||
});
|
||||
|
||||
it('returns selected engine sub nav items', () => {
|
||||
const engineName = 'my-test-engine';
|
||||
const navItems = useEnterpriseSearchApplicationNav(engineName);
|
||||
const {
|
||||
result: { current: navItems },
|
||||
} = renderHook(() => useEnterpriseSearchApplicationNav(engineName));
|
||||
expect(navItems![0].id).toEqual('home');
|
||||
expect(navItems?.slice(1).map((ni) => ni.name)).toEqual([
|
||||
'Content',
|
||||
|
@ -317,7 +423,9 @@ describe('useEnterpriseSearchApplicationNav', () => {
|
|||
|
||||
it('returns selected engine without tabs when isEmpty', () => {
|
||||
const engineName = 'my-test-engine';
|
||||
const navItems = useEnterpriseSearchApplicationNav(engineName, true);
|
||||
const {
|
||||
result: { current: navItems },
|
||||
} = renderHook(() => useEnterpriseSearchApplicationNav(engineName, true));
|
||||
expect(navItems![0].id).toEqual('home');
|
||||
expect(navItems?.slice(1).map((ni) => ni.name)).toEqual([
|
||||
'Content',
|
||||
|
@ -348,7 +456,9 @@ describe('useEnterpriseSearchApplicationNav', () => {
|
|||
|
||||
it('returns selected engine with conflict warning when hasSchemaConflicts', () => {
|
||||
const engineName = 'my-test-engine';
|
||||
const navItems = useEnterpriseSearchApplicationNav(engineName, false, true);
|
||||
const {
|
||||
result: { current: navItems },
|
||||
} = renderHook(() => useEnterpriseSearchApplicationNav(engineName, false, true));
|
||||
|
||||
// @ts-ignore
|
||||
const engineItem = navItems
|
||||
|
@ -383,27 +493,20 @@ describe('useEnterpriseSearchApplicationNav', () => {
|
|||
describe('useEnterpriseSearchAnalyticsNav', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockValues({
|
||||
isSidebarEnabled: true,
|
||||
});
|
||||
setMockValues(defaultMockValues);
|
||||
mockKibanaValues.getNavLinks.mockReturnValue(mockNavLinks);
|
||||
});
|
||||
|
||||
it('returns basic nav all params are empty', () => {
|
||||
const navItems = useEnterpriseSearchAnalyticsNav();
|
||||
expect(navItems).toEqual(
|
||||
baseNavItems.map((item) =>
|
||||
item.id === 'content'
|
||||
? {
|
||||
...item,
|
||||
items: item.items,
|
||||
}
|
||||
: item
|
||||
)
|
||||
);
|
||||
const { result } = renderHook(() => useEnterpriseSearchAnalyticsNav());
|
||||
|
||||
expect(result.current).toEqual(baseNavItems);
|
||||
});
|
||||
|
||||
it('returns basic nav if only name provided', () => {
|
||||
const navItems = useEnterpriseSearchAnalyticsNav('my-test-collection');
|
||||
const {
|
||||
result: { current: navItems },
|
||||
} = renderHook(() => useEnterpriseSearchAnalyticsNav('my-test-collection'));
|
||||
expect(navItems).toEqual(
|
||||
baseNavItems.map((item) =>
|
||||
item.id === 'content'
|
||||
|
@ -417,16 +520,21 @@ describe('useEnterpriseSearchAnalyticsNav', () => {
|
|||
});
|
||||
|
||||
it('returns nav with sub items when name and paths provided', () => {
|
||||
const navItems = useEnterpriseSearchAnalyticsNav('my-test-collection', {
|
||||
explorer: '/explorer-path',
|
||||
integration: '/integration-path',
|
||||
overview: '/overview-path',
|
||||
});
|
||||
const {
|
||||
result: { current: navItems },
|
||||
} = renderHook(() =>
|
||||
useEnterpriseSearchAnalyticsNav('my-test-collection', {
|
||||
explorer: '/explorer-path',
|
||||
integration: '/integration-path',
|
||||
overview: '/overview-path',
|
||||
})
|
||||
);
|
||||
const applicationsNav = navItems?.find((item) => item.id === 'build');
|
||||
expect(applicationsNav).not.toBeUndefined();
|
||||
const analyticsNav = applicationsNav?.items?.[2];
|
||||
expect(analyticsNav).not.toBeUndefined();
|
||||
expect(analyticsNav).toEqual({
|
||||
'data-test-subj': 'searchSideNav-BehavioralAnalytics',
|
||||
href: '/app/enterprise_search/analytics',
|
||||
id: 'analyticsCollections',
|
||||
items: [
|
||||
|
|
|
@ -5,44 +5,22 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import { EuiFlexGroup, EuiIcon, EuiText } from '@elastic/eui';
|
||||
import type { EuiSideNavItemTypeEnhanced } from '@kbn/core-chrome-browser';
|
||||
import { EuiFlexGroup, EuiIcon } from '@elastic/eui';
|
||||
import type { ChromeNavLink, EuiSideNavItemTypeEnhanced } from '@kbn/core-chrome-browser';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import {
|
||||
ANALYTICS_PLUGIN,
|
||||
APPLICATIONS_PLUGIN,
|
||||
APP_SEARCH_PLUGIN,
|
||||
ELASTICSEARCH_PLUGIN,
|
||||
ENTERPRISE_SEARCH_CONTENT_PLUGIN,
|
||||
ENTERPRISE_SEARCH_OVERVIEW_PLUGIN,
|
||||
AI_SEARCH_PLUGIN,
|
||||
VECTOR_SEARCH_PLUGIN,
|
||||
WORKPLACE_SEARCH_PLUGIN,
|
||||
SEARCH_RELEVANCE_PLUGIN,
|
||||
SEMANTIC_SEARCH_PLUGIN,
|
||||
} from '../../../../common/constants';
|
||||
import {
|
||||
SEARCH_APPLICATIONS_PATH,
|
||||
SearchApplicationViewTabs,
|
||||
PLAYGROUND_PATH,
|
||||
} from '../../applications/routes';
|
||||
import { ANALYTICS_PLUGIN, APPLICATIONS_PLUGIN } from '../../../../common/constants';
|
||||
import { SEARCH_APPLICATIONS_PATH, SearchApplicationViewTabs } from '../../applications/routes';
|
||||
import { useIndicesNav } from '../../enterprise_search_content/components/search_index/indices/indices_nav';
|
||||
import {
|
||||
CONNECTORS_PATH,
|
||||
CRAWLERS_PATH,
|
||||
SEARCH_INDICES_PATH,
|
||||
} from '../../enterprise_search_content/routes';
|
||||
|
||||
import { INFERENCE_ENDPOINTS_PATH } from '../../enterprise_search_relevance/routes';
|
||||
import { KibanaLogic } from '../kibana';
|
||||
|
||||
import { LicensingLogic } from '../licensing';
|
||||
|
||||
import { buildBaseClassicNavItems } from './base_nav';
|
||||
import { generateSideNavItems } from './classic_nav_helpers';
|
||||
import { generateNavLink } from './nav_link_helpers';
|
||||
|
||||
/**
|
||||
|
@ -52,219 +30,21 @@ import { generateNavLink } from './nav_link_helpers';
|
|||
* @returns The Enterprise Search navigation items
|
||||
*/
|
||||
export const useEnterpriseSearchNav = (alwaysReturn = false) => {
|
||||
const { isSidebarEnabled, productAccess } = useValues(KibanaLogic);
|
||||
|
||||
const { hasEnterpriseLicense } = useValues(LicensingLogic);
|
||||
const { isSidebarEnabled, productAccess, getNavLinks } = useValues(KibanaLogic);
|
||||
|
||||
const indicesNavItems = useIndicesNav();
|
||||
|
||||
if (!isSidebarEnabled && !alwaysReturn) return undefined;
|
||||
const navItems: Array<EuiSideNavItemTypeEnhanced<unknown>> = useMemo(() => {
|
||||
const baseNavItems = buildBaseClassicNavItems({ productAccess });
|
||||
const deepLinks = getNavLinks().reduce((links, link) => {
|
||||
links[link.id] = link;
|
||||
return links;
|
||||
}, {} as Record<string, ChromeNavLink | undefined>);
|
||||
|
||||
const navItems: Array<EuiSideNavItemTypeEnhanced<unknown>> = [
|
||||
{
|
||||
id: 'home',
|
||||
name: (
|
||||
<EuiText size="s">
|
||||
{i18n.translate('xpack.enterpriseSearch.nav.homeTitle', {
|
||||
defaultMessage: 'Home',
|
||||
})}
|
||||
</EuiText>
|
||||
),
|
||||
...generateNavLink({
|
||||
shouldNotCreateHref: true,
|
||||
shouldShowActiveForSubroutes: true,
|
||||
to: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'content',
|
||||
items: [
|
||||
{
|
||||
id: 'search_indices',
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.searchIndicesTitle', {
|
||||
defaultMessage: 'Indices',
|
||||
}),
|
||||
...generateNavLink({
|
||||
items: indicesNavItems,
|
||||
shouldNotCreateHref: true,
|
||||
shouldShowActiveForSubroutes: true,
|
||||
to: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + SEARCH_INDICES_PATH,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'connectors',
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.connectorsTitle', {
|
||||
defaultMessage: 'Connectors',
|
||||
}),
|
||||
...generateNavLink({
|
||||
shouldNotCreateHref: true,
|
||||
shouldShowActiveForSubroutes: true,
|
||||
to: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + CONNECTORS_PATH,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'crawlers',
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.crawlersTitle', {
|
||||
defaultMessage: 'Web crawlers',
|
||||
}),
|
||||
...generateNavLink({
|
||||
shouldNotCreateHref: true,
|
||||
shouldShowActiveForSubroutes: true,
|
||||
to: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + CRAWLERS_PATH,
|
||||
}),
|
||||
},
|
||||
],
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.contentTitle', {
|
||||
defaultMessage: 'Content',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'build',
|
||||
items: [
|
||||
{
|
||||
id: 'playground',
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.PlaygroundTitle', {
|
||||
defaultMessage: 'Playground',
|
||||
}),
|
||||
...generateNavLink({
|
||||
shouldNotCreateHref: true,
|
||||
shouldShowActiveForSubroutes: true,
|
||||
to: APPLICATIONS_PLUGIN.URL + PLAYGROUND_PATH,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'searchApplications',
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.searchApplicationsTitle', {
|
||||
defaultMessage: 'Search Applications',
|
||||
}),
|
||||
...generateNavLink({
|
||||
shouldNotCreateHref: true,
|
||||
to: APPLICATIONS_PLUGIN.URL + SEARCH_APPLICATIONS_PATH,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'analyticsCollections',
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.analyticsTitle', {
|
||||
defaultMessage: 'Behavioral Analytics',
|
||||
}),
|
||||
...generateNavLink({
|
||||
shouldNotCreateHref: true,
|
||||
to: ANALYTICS_PLUGIN.URL,
|
||||
}),
|
||||
},
|
||||
],
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.applicationsTitle', {
|
||||
defaultMessage: 'Build',
|
||||
}),
|
||||
},
|
||||
...(hasEnterpriseLicense
|
||||
? [
|
||||
{
|
||||
id: 'relevance',
|
||||
items: [
|
||||
{
|
||||
id: 'inference_endpoints',
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.inferenceEndpointsTitle', {
|
||||
defaultMessage: 'Inference Endpoints',
|
||||
}),
|
||||
...generateNavLink({
|
||||
shouldNotCreateHref: true,
|
||||
shouldShowActiveForSubroutes: true,
|
||||
to: SEARCH_RELEVANCE_PLUGIN.URL + INFERENCE_ENDPOINTS_PATH,
|
||||
}),
|
||||
},
|
||||
],
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.relevanceTitle', {
|
||||
defaultMessage: 'Relevance',
|
||||
}),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
id: 'es_getting_started',
|
||||
items: [
|
||||
{
|
||||
id: 'elasticsearch',
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.elasticsearchTitle', {
|
||||
defaultMessage: 'Elasticsearch',
|
||||
}),
|
||||
...generateNavLink({
|
||||
shouldNotCreateHref: true,
|
||||
to: ELASTICSEARCH_PLUGIN.URL,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'vectorSearch',
|
||||
name: VECTOR_SEARCH_PLUGIN.NAME,
|
||||
...generateNavLink({
|
||||
shouldNotCreateHref: true,
|
||||
to: VECTOR_SEARCH_PLUGIN.URL,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'semanticSearch',
|
||||
name: SEMANTIC_SEARCH_PLUGIN.NAME,
|
||||
...generateNavLink({
|
||||
shouldNotCreateHref: true,
|
||||
to: SEMANTIC_SEARCH_PLUGIN.URL,
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'aiSearch',
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.aiSearchTitle', {
|
||||
defaultMessage: 'AI Search',
|
||||
}),
|
||||
...generateNavLink({
|
||||
shouldNotCreateHref: true,
|
||||
to: AI_SEARCH_PLUGIN.URL,
|
||||
}),
|
||||
},
|
||||
],
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle', {
|
||||
defaultMessage: 'Getting started',
|
||||
}),
|
||||
},
|
||||
...(productAccess.hasAppSearchAccess || productAccess.hasWorkplaceSearchAccess
|
||||
? [
|
||||
{
|
||||
id: 'enterpriseSearch',
|
||||
items: [
|
||||
...(productAccess.hasAppSearchAccess
|
||||
? [
|
||||
{
|
||||
id: 'app_search',
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.appSearchTitle', {
|
||||
defaultMessage: 'App Search',
|
||||
}),
|
||||
...generateNavLink({
|
||||
shouldNotCreateHref: true,
|
||||
to: APP_SEARCH_PLUGIN.URL,
|
||||
}),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(productAccess.hasWorkplaceSearchAccess
|
||||
? [
|
||||
{
|
||||
id: 'workplace_search',
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.workplaceSearchTitle', {
|
||||
defaultMessage: 'Workplace Search',
|
||||
}),
|
||||
...generateNavLink({
|
||||
shouldNotCreateHref: true,
|
||||
to: WORKPLACE_SEARCH_PLUGIN.URL,
|
||||
}),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
name: i18n.translate('xpack.enterpriseSearch.nav.title', {
|
||||
defaultMessage: 'Enterprise Search',
|
||||
}),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
return generateSideNavItems(baseNavItems, deepLinks, { search_indices: indicesNavItems });
|
||||
}, [productAccess, indicesNavItems]);
|
||||
|
||||
if (!isSidebarEnabled && !alwaysReturn) return undefined;
|
||||
|
||||
return navItems;
|
||||
};
|
||||
|
|
|
@ -36,6 +36,7 @@ describe('generateNavLink', () => {
|
|||
navItem.onClick({ preventDefault: jest.fn() } as any);
|
||||
expect(mockKibanaValues.navigateToUrl).toHaveBeenCalledWith('/test', {
|
||||
shouldNotCreateHref: false,
|
||||
shouldNotPrepend: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -5,27 +5,32 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiSideNavItemType } from '@elastic/eui';
|
||||
import { EuiSideNavItemTypeEnhanced } from '@kbn/core-chrome-browser';
|
||||
|
||||
import { stripTrailingSlash } from '../../../../common/strip_slashes';
|
||||
|
||||
import { KibanaLogic } from '../kibana';
|
||||
import { generateReactRouterProps, ReactRouterProps } from '../react_router_helpers';
|
||||
import { GeneratedReactRouterProps } from '../react_router_helpers/generate_react_router_props';
|
||||
import {
|
||||
type GeneratedReactRouterProps,
|
||||
generateReactRouterProps,
|
||||
} from '../react_router_helpers/generate_react_router_props';
|
||||
import { ReactRouterProps } from '../types';
|
||||
|
||||
interface Params {
|
||||
items?: Array<EuiSideNavItemType<unknown>>; // Primarily passed if using `items` to determine isSelected - if not, you can just set `items` outside of this helper
|
||||
items?: Array<EuiSideNavItemTypeEnhanced<unknown>>; // Primarily passed if using `items` to determine isSelected - if not, you can just set `items` outside of this helper
|
||||
shouldShowActiveForSubroutes?: boolean;
|
||||
to: string;
|
||||
}
|
||||
|
||||
type NavLinkProps<T> = GeneratedReactRouterProps<T> &
|
||||
Pick<EuiSideNavItemType<T>, 'isSelected' | 'items'>;
|
||||
Pick<EuiSideNavItemTypeEnhanced<T>, 'isSelected' | 'items'>;
|
||||
|
||||
export type GenerateNavLinkParameters = Params & ReactRouterProps;
|
||||
|
||||
export const generateNavLink = ({
|
||||
items,
|
||||
...rest
|
||||
}: Params & ReactRouterProps): NavLinkProps<unknown> => {
|
||||
}: GenerateNavLinkParameters): NavLinkProps<unknown> => {
|
||||
const linkProps = {
|
||||
...generateReactRouterProps({ ...rest }),
|
||||
isSelected: getNavLinkActive({ items, ...rest }),
|
||||
|
@ -38,14 +43,15 @@ export const getNavLinkActive = ({
|
|||
shouldShowActiveForSubroutes = false,
|
||||
items = [],
|
||||
shouldNotCreateHref = false,
|
||||
}: Params & ReactRouterProps): boolean => {
|
||||
shouldNotPrepend = false,
|
||||
}: GenerateNavLinkParameters): boolean => {
|
||||
const { pathname } = KibanaLogic.values.history.location;
|
||||
const currentPath = stripTrailingSlash(pathname);
|
||||
const { href: currentPathHref } = generateReactRouterProps({
|
||||
shouldNotCreateHref: false,
|
||||
to: currentPath,
|
||||
});
|
||||
const { href: toHref } = generateReactRouterProps({ shouldNotCreateHref, to });
|
||||
const { href: toHref } = generateReactRouterProps({ shouldNotCreateHref, shouldNotPrepend, to });
|
||||
|
||||
if (currentPathHref === toHref) return true;
|
||||
|
||||
|
|
|
@ -30,12 +30,19 @@ interface CreateHrefDeps {
|
|||
}
|
||||
export interface CreateHrefOptions {
|
||||
shouldNotCreateHref?: boolean;
|
||||
shouldNotPrepend?: boolean;
|
||||
}
|
||||
|
||||
export const createHref = (
|
||||
path: string,
|
||||
{ history, http }: CreateHrefDeps,
|
||||
{ shouldNotCreateHref }: CreateHrefOptions = {}
|
||||
{ shouldNotCreateHref, shouldNotPrepend }: CreateHrefOptions = {}
|
||||
): string => {
|
||||
return shouldNotCreateHref ? http.basePath.prepend(path) : history.createHref({ pathname: path });
|
||||
if (shouldNotCreateHref) {
|
||||
if (shouldNotPrepend) {
|
||||
return path;
|
||||
}
|
||||
return http.basePath.prepend(path);
|
||||
}
|
||||
return history.createHref({ pathname: path });
|
||||
};
|
||||
|
|
|
@ -26,7 +26,9 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { EuiPanelProps } from '@elastic/eui/src/components/panel/panel';
|
||||
|
||||
import { generateReactRouterProps, ReactRouterProps } from '.';
|
||||
import { ReactRouterProps } from '../types';
|
||||
|
||||
import { generateReactRouterProps } from '.';
|
||||
|
||||
/**
|
||||
* Correctly typed component helpers with React-Router-friendly `href` and `onClick` props
|
||||
|
|
|
@ -44,6 +44,7 @@ describe('generateReactRouterProps', () => {
|
|||
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
||||
expect(mockKibanaValues.navigateToUrl).toHaveBeenCalledWith('/test', {
|
||||
shouldNotCreateHref: false,
|
||||
shouldNotPrepend: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -63,6 +64,7 @@ describe('generateReactRouterProps', () => {
|
|||
expect(mockEvent.preventDefault).toHaveBeenCalled();
|
||||
expect(mockKibanaValues.navigateToUrl).toHaveBeenCalledWith('/app/enterprise_search/test', {
|
||||
shouldNotCreateHref: true,
|
||||
shouldNotPrepend: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import { EuiSideNavItemType } from '@elastic/eui';
|
|||
|
||||
import { HttpLogic } from '../http';
|
||||
import { KibanaLogic } from '../kibana';
|
||||
import { ReactRouterProps } from '../types';
|
||||
|
||||
import { letBrowserHandleEvent, createHref } from '.';
|
||||
|
||||
|
@ -23,14 +24,6 @@ import { letBrowserHandleEvent, createHref } from '.';
|
|||
* but separated out from EuiLink portion as we use this for multiple EUI components
|
||||
*/
|
||||
|
||||
export interface ReactRouterProps {
|
||||
to: string;
|
||||
onClick?(): void;
|
||||
// Used to navigate outside of the React Router plugin basename but still within Kibana,
|
||||
// e.g. if we need to go from Enterprise Search to App Search
|
||||
shouldNotCreateHref?: boolean;
|
||||
}
|
||||
|
||||
export type GeneratedReactRouterProps<T> = Required<
|
||||
Pick<EuiSideNavItemType<T>, 'href' | 'onClick'>
|
||||
>;
|
||||
|
@ -39,12 +32,13 @@ export const generateReactRouterProps = ({
|
|||
to,
|
||||
onClick,
|
||||
shouldNotCreateHref = false,
|
||||
shouldNotPrepend = false,
|
||||
}: ReactRouterProps): GeneratedReactRouterProps<unknown> => {
|
||||
const { navigateToUrl, history } = KibanaLogic.values;
|
||||
const { http } = HttpLogic.values;
|
||||
|
||||
// Generate the correct link href (with basename etc. accounted for)
|
||||
const href = createHref(to, { history, http }, { shouldNotCreateHref });
|
||||
const href = createHref(to, { history, http }, { shouldNotCreateHref, shouldNotPrepend });
|
||||
|
||||
const reactRouterLinkClick = (event: React.MouseEvent) => {
|
||||
if (onClick) onClick(); // Run any passed click events (e.g. telemetry)
|
||||
|
@ -54,7 +48,7 @@ export const generateReactRouterProps = ({
|
|||
event.preventDefault();
|
||||
|
||||
// Perform SPA navigation.
|
||||
navigateToUrl(to, { shouldNotCreateHref });
|
||||
navigateToUrl(to, { shouldNotCreateHref, shouldNotPrepend });
|
||||
};
|
||||
|
||||
return { href, onClick: reactRouterLinkClick };
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
export { letBrowserHandleEvent } from './link_events';
|
||||
export type { CreateHrefOptions } from './create_href';
|
||||
export { createHref } from './create_href';
|
||||
export type { ReactRouterProps } from './generate_react_router_props';
|
||||
export { generateReactRouterProps } from './generate_react_router_props';
|
||||
export {
|
||||
EuiLinkTo,
|
||||
|
|
|
@ -5,7 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import type { AppDeepLinkId, EuiSideNavItemTypeEnhanced } from '@kbn/core-chrome-browser';
|
||||
|
||||
import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../../../common/constants';
|
||||
import type { ProductAccess } from '../../../common/types';
|
||||
|
||||
import { ADD, UPDATE } from './constants/operations';
|
||||
|
||||
|
@ -57,3 +62,37 @@ export interface SingleUserRoleMapping<T> {
|
|||
roleMapping: T;
|
||||
hasEnterpriseSearchRole?: boolean;
|
||||
}
|
||||
|
||||
export interface ReactRouterProps {
|
||||
to: string;
|
||||
onClick?(): void;
|
||||
// Used to navigate outside of the React Router plugin basename but still within Kibana,
|
||||
// e.g. if we need to go from Enterprise Search to App Search
|
||||
shouldNotCreateHref?: boolean;
|
||||
// Used if to is already a fully qualified URL that doesn't need basePath prepended
|
||||
shouldNotPrepend?: boolean;
|
||||
}
|
||||
|
||||
export type GenerateNavLinkParameters = {
|
||||
items?: Array<EuiSideNavItemTypeEnhanced<unknown>>; // Primarily passed if using `items` to determine isSelected - if not, you can just set `items` outside of this helper
|
||||
shouldShowActiveForSubroutes?: boolean;
|
||||
to: string;
|
||||
} & ReactRouterProps;
|
||||
|
||||
export interface GenerateNavLinkFromDeepLinkParameters {
|
||||
link: AppDeepLinkId;
|
||||
shouldShowActiveForSubroutes?: boolean;
|
||||
}
|
||||
|
||||
export interface BuildClassicNavParameters {
|
||||
productAccess: ProductAccess;
|
||||
}
|
||||
|
||||
export interface ClassicNavItem {
|
||||
'data-test-subj'?: string;
|
||||
deepLink?: GenerateNavLinkFromDeepLinkParameters;
|
||||
iconToString?: string;
|
||||
id: string;
|
||||
items?: ClassicNavItem[];
|
||||
name?: ReactNode;
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ export const mockKibanaProps: KibanaLogicProps = {
|
|||
elasticsearch_host: 'https://your_deployment_url',
|
||||
},
|
||||
getChromeStyle$: jest.fn().mockReturnValue(of('classic')),
|
||||
getNavLinks: jest.fn().mockReturnValue([]),
|
||||
guidedOnboarding: {},
|
||||
history: mockHistory,
|
||||
indexMappingComponent: () => {
|
||||
|
|
|
@ -17359,27 +17359,16 @@
|
|||
"xpack.enterpriseSearch.modelCard.elserPlaceholder.description": "ELSER est le modèle NLP d'Elastic pour la recherche sémantique en anglais, utilisant des vecteurs creux. Il donne la priorité à l'intention et à la signification contextuelle plutôt qu'à la correspondance littérale des termes. Il est optimisé spécifiquement pour les documents et les recherches en anglais sur la plateforme Elastic.",
|
||||
"xpack.enterpriseSearch.nameLabel": "Nom",
|
||||
"xpack.enterpriseSearch.nativeLabel": "Natif",
|
||||
"xpack.enterpriseSearch.nav.aiSearchTitle": "Recherche propulsée par l'intelligence artificielle",
|
||||
"xpack.enterpriseSearch.nav.analyticsCollections.explorerTitle": "Explorer",
|
||||
"xpack.enterpriseSearch.nav.analyticsCollections.integrationTitle": "Intégration",
|
||||
"xpack.enterpriseSearch.nav.analyticsCollections.overviewTitle": "Aperçu",
|
||||
"xpack.enterpriseSearch.nav.analyticsTitle": "Behavioral Analytics",
|
||||
"xpack.enterpriseSearch.nav.applications.searchApplications.connectTitle": "Connecter",
|
||||
"xpack.enterpriseSearch.nav.applicationsTitle": "Développer",
|
||||
"xpack.enterpriseSearch.nav.appSearchTitle": "App Search",
|
||||
"xpack.enterpriseSearch.nav.connectorsTitle": "Connecteurs",
|
||||
"xpack.enterpriseSearch.nav.contentTitle": "Contenu",
|
||||
"xpack.enterpriseSearch.nav.crawlersTitle": "Robots d'indexation",
|
||||
"xpack.enterpriseSearch.nav.elasticsearchTitle": "Elasticsearch",
|
||||
"xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle": "Premiers pas",
|
||||
"xpack.enterpriseSearch.nav.homeTitle": "Accueil",
|
||||
"xpack.enterpriseSearch.nav.inferenceEndpointsTitle": "Points de terminaison d'inférence",
|
||||
"xpack.enterpriseSearch.nav.PlaygroundTitle": "Playground",
|
||||
"xpack.enterpriseSearch.nav.relevanceTitle": "Pertinence",
|
||||
"xpack.enterpriseSearch.nav.searchApplication.contentTitle": "Contenu",
|
||||
"xpack.enterpriseSearch.nav.searchApplication.docsExplorerTitle": "Explorateur de documents",
|
||||
"xpack.enterpriseSearch.nav.searchApplicationsTitle": "Applications de recherche",
|
||||
"xpack.enterpriseSearch.nav.searchIndicesTitle": "Index",
|
||||
"xpack.enterpriseSearch.nav.searchIndicesTitle.nav.connectorsConfigurationLabel": "Configuration",
|
||||
"xpack.enterpriseSearch.nav.searchIndicesTitle.nav.crawlerConfigurationLabel": "Configuration",
|
||||
"xpack.enterpriseSearch.nav.searchIndicesTitle.nav.crawlerSchedulingLabel": "Planification",
|
||||
|
@ -17391,7 +17380,6 @@
|
|||
"xpack.enterpriseSearch.nav.searchIndicesTitle.nav.schedulingTitle": "Planification",
|
||||
"xpack.enterpriseSearch.nav.searchIndicesTitle.nav.syncRulesLabel": "Règles de synchronisation",
|
||||
"xpack.enterpriseSearch.nav.title": "Enterprise Search",
|
||||
"xpack.enterpriseSearch.nav.workplaceSearchTitle": "Workplace Search",
|
||||
"xpack.enterpriseSearch.navigation.applicationsSearchApplicationsLinkLabel": "Applications de recherche",
|
||||
"xpack.enterpriseSearch.navigation.appSearchEnginesLinkLabel": "Moteurs",
|
||||
"xpack.enterpriseSearch.navigation.contentConnectorsLinkLabel": "Connecteurs",
|
||||
|
|
|
@ -17105,27 +17105,16 @@
|
|||
"xpack.enterpriseSearch.modelCard.elserPlaceholder.description": "ELSERは、疎ベクトルを利用した英語のセマンティック検索のためのElasticのNLPモデルです。Elasticプラットフォームの英語ドキュメントやクエリー向けに特別に最適化されており、文字通りの用語一致よりも意図や文脈上の意味を優先します。",
|
||||
"xpack.enterpriseSearch.nameLabel": "名前",
|
||||
"xpack.enterpriseSearch.nativeLabel": "ネイティブ",
|
||||
"xpack.enterpriseSearch.nav.aiSearchTitle": "AI検索",
|
||||
"xpack.enterpriseSearch.nav.analyticsCollections.explorerTitle": "エクスプローラー",
|
||||
"xpack.enterpriseSearch.nav.analyticsCollections.integrationTitle": "統合",
|
||||
"xpack.enterpriseSearch.nav.analyticsCollections.overviewTitle": "概要",
|
||||
"xpack.enterpriseSearch.nav.analyticsTitle": "Behavioral Analytics",
|
||||
"xpack.enterpriseSearch.nav.applications.searchApplications.connectTitle": "接続",
|
||||
"xpack.enterpriseSearch.nav.applicationsTitle": "ビルド",
|
||||
"xpack.enterpriseSearch.nav.appSearchTitle": "App Search",
|
||||
"xpack.enterpriseSearch.nav.connectorsTitle": "コネクター",
|
||||
"xpack.enterpriseSearch.nav.contentTitle": "コンテンツ",
|
||||
"xpack.enterpriseSearch.nav.crawlersTitle": "Webクローラー",
|
||||
"xpack.enterpriseSearch.nav.elasticsearchTitle": "Elasticsearch",
|
||||
"xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle": "はじめて使う",
|
||||
"xpack.enterpriseSearch.nav.homeTitle": "ホーム",
|
||||
"xpack.enterpriseSearch.nav.inferenceEndpointsTitle": "推論エンドポイント",
|
||||
"xpack.enterpriseSearch.nav.PlaygroundTitle": "Playground",
|
||||
"xpack.enterpriseSearch.nav.relevanceTitle": "<b>関連性</b>",
|
||||
"xpack.enterpriseSearch.nav.searchApplication.contentTitle": "コンテンツ",
|
||||
"xpack.enterpriseSearch.nav.searchApplication.docsExplorerTitle": "ドキュメントエクスプローラー",
|
||||
"xpack.enterpriseSearch.nav.searchApplicationsTitle": "検索アプリケーション",
|
||||
"xpack.enterpriseSearch.nav.searchIndicesTitle": "インデックス",
|
||||
"xpack.enterpriseSearch.nav.searchIndicesTitle.nav.connectorsConfigurationLabel": "構成",
|
||||
"xpack.enterpriseSearch.nav.searchIndicesTitle.nav.crawlerConfigurationLabel": "構成",
|
||||
"xpack.enterpriseSearch.nav.searchIndicesTitle.nav.crawlerSchedulingLabel": "スケジュール",
|
||||
|
@ -17137,7 +17126,6 @@
|
|||
"xpack.enterpriseSearch.nav.searchIndicesTitle.nav.schedulingTitle": "スケジュール",
|
||||
"xpack.enterpriseSearch.nav.searchIndicesTitle.nav.syncRulesLabel": "同期ルール",
|
||||
"xpack.enterpriseSearch.nav.title": "エンタープライズ サーチ",
|
||||
"xpack.enterpriseSearch.nav.workplaceSearchTitle": "Workplace Search",
|
||||
"xpack.enterpriseSearch.navigation.applicationsSearchApplicationsLinkLabel": "検索アプリケーション",
|
||||
"xpack.enterpriseSearch.navigation.appSearchEnginesLinkLabel": "エンジン",
|
||||
"xpack.enterpriseSearch.navigation.contentConnectorsLinkLabel": "コネクター",
|
||||
|
|
|
@ -17134,27 +17134,16 @@
|
|||
"xpack.enterpriseSearch.modelCard.elserPlaceholder.description": "ELSER 是 Elastic 的利用稀疏向量执行英语语义搜索的 NLP 模型。与字面值匹配相比,它优先处理意图和上下文含义,对 Elastic 平台上的英语文档和查询专门进行了优化。",
|
||||
"xpack.enterpriseSearch.nameLabel": "名称",
|
||||
"xpack.enterpriseSearch.nativeLabel": "原生",
|
||||
"xpack.enterpriseSearch.nav.aiSearchTitle": "AI 搜索",
|
||||
"xpack.enterpriseSearch.nav.analyticsCollections.explorerTitle": "浏览器",
|
||||
"xpack.enterpriseSearch.nav.analyticsCollections.integrationTitle": "集成",
|
||||
"xpack.enterpriseSearch.nav.analyticsCollections.overviewTitle": "概览",
|
||||
"xpack.enterpriseSearch.nav.analyticsTitle": "行为分析",
|
||||
"xpack.enterpriseSearch.nav.applications.searchApplications.connectTitle": "连接",
|
||||
"xpack.enterpriseSearch.nav.applicationsTitle": "构建",
|
||||
"xpack.enterpriseSearch.nav.appSearchTitle": "App Search",
|
||||
"xpack.enterpriseSearch.nav.connectorsTitle": "连接器",
|
||||
"xpack.enterpriseSearch.nav.contentTitle": "内容",
|
||||
"xpack.enterpriseSearch.nav.crawlersTitle": "网络爬虫",
|
||||
"xpack.enterpriseSearch.nav.elasticsearchTitle": "Elasticsearch",
|
||||
"xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle": "入门",
|
||||
"xpack.enterpriseSearch.nav.homeTitle": "主页",
|
||||
"xpack.enterpriseSearch.nav.inferenceEndpointsTitle": "推理终端",
|
||||
"xpack.enterpriseSearch.nav.PlaygroundTitle": "Playground",
|
||||
"xpack.enterpriseSearch.nav.relevanceTitle": "相关性",
|
||||
"xpack.enterpriseSearch.nav.searchApplication.contentTitle": "内容",
|
||||
"xpack.enterpriseSearch.nav.searchApplication.docsExplorerTitle": "文档浏览器",
|
||||
"xpack.enterpriseSearch.nav.searchApplicationsTitle": "搜索应用程序",
|
||||
"xpack.enterpriseSearch.nav.searchIndicesTitle": "索引",
|
||||
"xpack.enterpriseSearch.nav.searchIndicesTitle.nav.connectorsConfigurationLabel": "配置",
|
||||
"xpack.enterpriseSearch.nav.searchIndicesTitle.nav.crawlerConfigurationLabel": "配置",
|
||||
"xpack.enterpriseSearch.nav.searchIndicesTitle.nav.crawlerSchedulingLabel": "正在计划",
|
||||
|
@ -17166,7 +17155,6 @@
|
|||
"xpack.enterpriseSearch.nav.searchIndicesTitle.nav.schedulingTitle": "正在计划",
|
||||
"xpack.enterpriseSearch.nav.searchIndicesTitle.nav.syncRulesLabel": "同步规则",
|
||||
"xpack.enterpriseSearch.nav.title": "Enterprise Search",
|
||||
"xpack.enterpriseSearch.nav.workplaceSearchTitle": "Workplace Search",
|
||||
"xpack.enterpriseSearch.navigation.applicationsSearchApplicationsLinkLabel": "搜索应用程序",
|
||||
"xpack.enterpriseSearch.navigation.appSearchEnginesLinkLabel": "引擎",
|
||||
"xpack.enterpriseSearch.navigation.contentConnectorsLinkLabel": "连接器",
|
||||
|
|
|
@ -196,6 +196,9 @@ export default async function ({ readConfigFile }) {
|
|||
obsAIAssistantManagement: {
|
||||
pathname: '/app/management/kibana/observabilityAiAssistantManagement',
|
||||
},
|
||||
enterpriseSearch: {
|
||||
pathname: '/app/enterprise_search/overview',
|
||||
},
|
||||
},
|
||||
|
||||
suiteTags: {
|
||||
|
|
|
@ -54,6 +54,7 @@ import { UserProfilePageProvider } from './user_profile_page';
|
|||
import { WatcherPageObject } from './watcher_page';
|
||||
import { SearchProfilerPageProvider } from './search_profiler_page';
|
||||
import { SearchPlaygroundPageProvider } from './search_playground_page';
|
||||
import { SearchClassicNavigationProvider } from './search_classic_navigation';
|
||||
|
||||
// just like services, PageObjects are defined as a map of
|
||||
// names to Providers. Merge in Kibana's or pick specific ones
|
||||
|
@ -93,6 +94,7 @@ export const pageObjects = {
|
|||
reporting: ReportingPageObject,
|
||||
roleMappings: RoleMappingsPageProvider,
|
||||
rollup: RollupPageObject,
|
||||
searchClassicNavigation: SearchClassicNavigationProvider,
|
||||
searchProfiler: SearchProfilerPageProvider,
|
||||
searchPlayground: SearchPlaygroundPageProvider,
|
||||
searchSessionsManagement: SearchSessionsPageProvider,
|
||||
|
|
118
x-pack/test/functional/page_objects/search_classic_navigation.ts
Normal file
118
x-pack/test/functional/page_objects/search_classic_navigation.ts
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import type { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
|
||||
|
||||
import { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
const TIMEOUT_CHECK = 3000;
|
||||
|
||||
export function SearchClassicNavigationProvider({ getService }: FtrProviderContext) {
|
||||
const testSubjects = getService('testSubjects');
|
||||
const browser = getService('browser');
|
||||
const retry = getService('retry');
|
||||
|
||||
async function getByVisibleText(
|
||||
selector: string | (() => Promise<WebElementWrapper[]>),
|
||||
text: string
|
||||
) {
|
||||
const subjects =
|
||||
typeof selector === 'string' ? await testSubjects.findAll(selector) : await selector();
|
||||
let found: WebElementWrapper | null = null;
|
||||
for (const subject of subjects) {
|
||||
const visibleText = await subject.getVisibleText();
|
||||
if (visibleText === text) {
|
||||
found = subject;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
const sideNavTestSubj = (id: string) => `searchSideNav-${id}`;
|
||||
|
||||
return {
|
||||
async expectAllNavItems(items: Array<{ id: string; label: string }>) {
|
||||
for (const navItem of items) {
|
||||
await testSubjects.existOrFail(sideNavTestSubj(navItem.id));
|
||||
const itemElement = await testSubjects.find(sideNavTestSubj(navItem.id));
|
||||
const itemLabel = await itemElement.getVisibleText();
|
||||
expect(itemLabel).to.equal(navItem.label);
|
||||
}
|
||||
const allSideNavItems = await testSubjects.findAll('*searchSideNav-');
|
||||
expect(allSideNavItems.length).to.equal(items.length);
|
||||
},
|
||||
|
||||
async expectNavItemExists(id: string) {
|
||||
await testSubjects.existOrFail(sideNavTestSubj(id));
|
||||
},
|
||||
|
||||
async expectNavItemMissing(id: string) {
|
||||
await testSubjects.missingOrFail(sideNavTestSubj(id));
|
||||
},
|
||||
|
||||
async clickNavItem(id: string) {
|
||||
await testSubjects.existOrFail(sideNavTestSubj(id));
|
||||
await testSubjects.click(sideNavTestSubj(id));
|
||||
},
|
||||
|
||||
async expectNavItemActive(id: string) {
|
||||
await testSubjects.existOrFail(sideNavTestSubj(id));
|
||||
const item = await testSubjects.find(sideNavTestSubj(id));
|
||||
expect(await item.elementHasClass('euiSideNavItemButton-isSelected')).to.be(true);
|
||||
},
|
||||
|
||||
breadcrumbs: {
|
||||
async expectExists() {
|
||||
await testSubjects.existOrFail('breadcrumbs', { timeout: TIMEOUT_CHECK });
|
||||
},
|
||||
async clickBreadcrumb(text: string) {
|
||||
await (await getByVisibleText('~breadcrumb', text))?.click();
|
||||
},
|
||||
async getBreadcrumb(text: string) {
|
||||
return getByVisibleText('~breadcrumb', text);
|
||||
},
|
||||
async expectBreadcrumbExists(text: string) {
|
||||
await retry.try(async () => {
|
||||
expect(await getByVisibleText('~breadcrumb', text)).not.be(null);
|
||||
});
|
||||
},
|
||||
async expectBreadcrumbMissing(text: string) {
|
||||
await retry.try(async () => {
|
||||
expect(await getByVisibleText('~breadcrumb', text)).be(null);
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
// helper to assert that the page did not reload
|
||||
async createNoPageReloadCheck() {
|
||||
const trackReloadTs = Date.now();
|
||||
await browser.execute(
|
||||
({ ts }) => {
|
||||
// @ts-ignore
|
||||
window.__testTrackReload__ = ts;
|
||||
},
|
||||
{
|
||||
ts: trackReloadTs,
|
||||
}
|
||||
);
|
||||
|
||||
return async () => {
|
||||
const noReload = await browser.execute(
|
||||
({ ts }) => {
|
||||
// @ts-ignore
|
||||
return window.__testTrackReload__ && window.__testTrackReload__ === ts;
|
||||
},
|
||||
{
|
||||
ts: trackReloadTs,
|
||||
}
|
||||
);
|
||||
expect(noReload).to.be(true);
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
|
@ -17,7 +17,17 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
|
||||
return {
|
||||
...functionalConfig.getAll(),
|
||||
junit: {
|
||||
reportName: 'Search Solution UI Functional Tests',
|
||||
},
|
||||
testFiles: [require.resolve('.')],
|
||||
esTestCluster: {
|
||||
...functionalConfig.get('esTestCluster'),
|
||||
serverArgs: [
|
||||
...functionalConfig.get('esTestCluster.serverArgs'),
|
||||
'xpack.security.enabled=true',
|
||||
],
|
||||
},
|
||||
kbnTestServer: {
|
||||
...functionalConfig.get('kbnTestServer'),
|
||||
serverArgs: [
|
||||
|
|
|
@ -10,6 +10,7 @@ import { FtrProviderContext } from './ftr_provider_context';
|
|||
|
||||
export default ({ loadTestFile }: FtrProviderContext): void => {
|
||||
describe('Search solution tests', function () {
|
||||
loadTestFile(require.resolve('./tests/classic_navigation'));
|
||||
loadTestFile(require.resolve('./tests/solution_navigation'));
|
||||
});
|
||||
};
|
||||
|
|
131
x-pack/test/functional_search/tests/classic_navigation.ts
Normal file
131
x-pack/test/functional_search/tests/classic_navigation.ts
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
export default function searchSolutionNavigation({
|
||||
getPageObjects,
|
||||
getService,
|
||||
}: FtrProviderContext) {
|
||||
const { common, searchClassicNavigation } = getPageObjects(['common', 'searchClassicNavigation']);
|
||||
const spaces = getService('spaces');
|
||||
const browser = getService('browser');
|
||||
|
||||
describe('Search Classic Navigation', () => {
|
||||
let cleanUp: () => Promise<unknown>;
|
||||
let spaceCreated: { id: string } = { id: '' };
|
||||
|
||||
before(async () => {
|
||||
// Navigate to the spaces management page which will log us in Kibana
|
||||
await common.navigateToUrl('management', 'kibana/spaces', {
|
||||
shouldUseHashForSubUrl: false,
|
||||
});
|
||||
|
||||
// Create a space with the search solution and navigate to its home page
|
||||
({ cleanUp, space: spaceCreated } = await spaces.create({ solution: 'classic' }));
|
||||
await browser.navigateTo(spaces.getRootUrl(spaceCreated.id));
|
||||
await common.navigateToApp('enterpriseSearch');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
// Clean up space created
|
||||
await cleanUp();
|
||||
});
|
||||
|
||||
it('renders expected navigation items', async () => {
|
||||
await searchClassicNavigation.expectAllNavItems([
|
||||
{ id: 'Home', label: 'Home' },
|
||||
{ id: 'Content', label: 'Content' },
|
||||
{ id: 'Indices', label: 'Indices' },
|
||||
{ id: 'Connectors', label: 'Connectors' },
|
||||
{ id: 'Crawlers', label: 'Web crawlers' },
|
||||
{ id: 'Build', label: 'Build' },
|
||||
{ id: 'Playground', label: 'Playground' },
|
||||
{ id: 'SearchApplications', label: 'Search Applications' },
|
||||
{ id: 'BehavioralAnalytics', label: 'Behavioral Analytics' },
|
||||
{ id: 'Relevance', label: 'Relevance' },
|
||||
{ id: 'InferenceEndpoints', label: 'Inference Endpoints' },
|
||||
{ id: 'GettingStarted', label: 'Getting started' },
|
||||
{ id: 'Elasticsearch', label: 'Elasticsearch' },
|
||||
{ id: 'VectorSearch', label: 'Vector Search' },
|
||||
{ id: 'SemanticSearch', label: 'Semantic Search' },
|
||||
{ id: 'AISearch', label: 'AI Search' },
|
||||
]);
|
||||
});
|
||||
it('has expected navigation', async () => {
|
||||
const expectNoPageReload = await searchClassicNavigation.createNoPageReloadCheck();
|
||||
|
||||
await searchClassicNavigation.expectNavItemExists('Home');
|
||||
|
||||
// Check Content
|
||||
// > Indices
|
||||
await searchClassicNavigation.clickNavItem('Indices');
|
||||
await searchClassicNavigation.expectNavItemActive('Indices');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Content');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Elasticsearch indices');
|
||||
// > Connectors
|
||||
await searchClassicNavigation.clickNavItem('Connectors');
|
||||
await searchClassicNavigation.expectNavItemActive('Connectors');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Content');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Connectors');
|
||||
// > Crawlers
|
||||
await searchClassicNavigation.clickNavItem('Crawlers');
|
||||
await searchClassicNavigation.expectNavItemActive('Crawlers');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Content');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Web crawlers');
|
||||
|
||||
// Check Build
|
||||
// > Playground
|
||||
await searchClassicNavigation.clickNavItem('Playground');
|
||||
await searchClassicNavigation.expectNavItemActive('Playground');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Build');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Playground');
|
||||
// > SearchApplications
|
||||
await searchClassicNavigation.clickNavItem('SearchApplications');
|
||||
await searchClassicNavigation.expectNavItemActive('SearchApplications');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Build');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Search Applications');
|
||||
// > BehavioralAnalytics
|
||||
await searchClassicNavigation.clickNavItem('BehavioralAnalytics');
|
||||
await searchClassicNavigation.expectNavItemActive('BehavioralAnalytics');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Build');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Behavioral Analytics');
|
||||
|
||||
// Check Relevance
|
||||
// > InferenceEndpoints
|
||||
await searchClassicNavigation.clickNavItem('InferenceEndpoints');
|
||||
await searchClassicNavigation.expectNavItemActive('InferenceEndpoints');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Relevance');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Inference Endpoints');
|
||||
|
||||
// Check Getting started
|
||||
// > Elasticsearch
|
||||
await searchClassicNavigation.clickNavItem('Elasticsearch');
|
||||
await searchClassicNavigation.expectNavItemActive('Elasticsearch');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists(
|
||||
'Getting started with Elasticsearch'
|
||||
);
|
||||
// > VectorSearch
|
||||
await searchClassicNavigation.clickNavItem('VectorSearch');
|
||||
await searchClassicNavigation.expectNavItemActive('VectorSearch');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Getting started');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Vector Search');
|
||||
// > SemanticSearch
|
||||
await searchClassicNavigation.clickNavItem('SemanticSearch');
|
||||
await searchClassicNavigation.expectNavItemActive('SemanticSearch');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Getting started');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Semantic Search');
|
||||
// > AISearch
|
||||
await searchClassicNavigation.clickNavItem('AISearch');
|
||||
await searchClassicNavigation.expectNavItemActive('AISearch');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Getting started');
|
||||
await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('AI Search');
|
||||
|
||||
await expectNoPageReload();
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue