mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[Search] Refactor: abstracting classic nav items (#196579)](https://github.com/elastic/kibana/pull/196579) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Rodney Norris","email":"rodney.norris@elastic.co"},"sourceCommit":{"committedDate":"2024-10-30T14:39:42Z","message":"[Search] Refactor: abstracting classic nav items (#196579)\n\n## Summary\r\n\r\nMoved the base set of sidenav items from being statically defined in\r\nuseEnterpriseSearchNav to using a function that can be shared with the\r\nplugin. Additionally wrapped this generation in a `useMemo` to improve\r\nperformance.\r\n\r\nThis will support the ability to share the classic navigation items for\r\nSearch to other plugins so that they can render their own UIs without\r\nsharing components with enterprise_search just to have access to the\r\nside nav defined by enterprise_search.\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [x] [Flaky Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\r\nused on any tests changed\r\n\r\n---------\r\n\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"c4301d080b9fd595b6cf2313d2053256b0fae89d","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Search","backport:prev-minor","v8.17.0"],"title":"[Search] Refactor: abstracting classic nav items","number":196579,"url":"https://github.com/elastic/kibana/pull/196579","mergeCommit":{"message":"[Search] Refactor: abstracting classic nav items (#196579)\n\n## Summary\r\n\r\nMoved the base set of sidenav items from being statically defined in\r\nuseEnterpriseSearchNav to using a function that can be shared with the\r\nplugin. Additionally wrapped this generation in a `useMemo` to improve\r\nperformance.\r\n\r\nThis will support the ability to share the classic navigation items for\r\nSearch to other plugins so that they can render their own UIs without\r\nsharing components with enterprise_search just to have access to the\r\nside nav defined by enterprise_search.\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [x] [Flaky Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\r\nused on any tests changed\r\n\r\n---------\r\n\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"c4301d080b9fd595b6cf2313d2053256b0fae89d"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/196579","number":196579,"mergeCommit":{"message":"[Search] Refactor: abstracting classic nav items (#196579)\n\n## Summary\r\n\r\nMoved the base set of sidenav items from being statically defined in\r\nuseEnterpriseSearchNav to using a function that can be shared with the\r\nplugin. Additionally wrapped this generation in a `useMemo` to improve\r\nperformance.\r\n\r\nThis will support the ability to share the classic navigation items for\r\nSearch to other plugins so that they can render their own UIs without\r\nsharing components with enterprise_search just to have access to the\r\nside nav defined by enterprise_search.\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [x] [Flaky Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\r\nused on any tests changed\r\n\r\n---------\r\n\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"c4301d080b9fd595b6cf2313d2053256b0fae89d"}},{"branch":"8.x","label":"v8.17.0","branchLabelMappingKey":"^v8.17.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Rodney Norris <rodney.norris@elastic.co>
This commit is contained in:
parent
b5edaf6fca
commit
4ba21991f7
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