From 47a2808e6d97acfc797e09bea59519f1141680c3 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Mon, 14 Oct 2024 20:36:15 +0000 Subject: [PATCH 01/23] test: search classic nav - basic --- .../public/applications/shared/layout/nav.tsx | 19 ++++++ x-pack/test/functional/config.base.js | 3 + x-pack/test/functional/page_objects/index.ts | 2 + .../page_objects/search_classic_navigation.ts | 26 ++++++++ x-pack/test/functional_search/index.ts | 1 + .../tests/classic_navigation.ts | 60 +++++++++++++++++++ 6 files changed, 111 insertions(+) create mode 100644 x-pack/test/functional/page_objects/search_classic_navigation.ts create mode 100644 x-pack/test/functional_search/tests/classic_navigation.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx index 3b3960a7a92ba..98d5eb9f28e51 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx @@ -62,6 +62,7 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { const navItems: Array> = [ { + 'data-test-subj': 'searchSideNav-Home', id: 'home', name: ( @@ -77,9 +78,11 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { }), }, { + 'data-test-subj': 'searchSideNav-Content', id: 'content', items: [ { + 'data-test-subj': 'searchSideNav-Indices', id: 'search_indices', name: i18n.translate('xpack.enterpriseSearch.nav.searchIndicesTitle', { defaultMessage: 'Indices', @@ -92,6 +95,7 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { }), }, { + 'data-test-subj': 'searchSideNav-Connectors', id: 'connectors', name: i18n.translate('xpack.enterpriseSearch.nav.connectorsTitle', { defaultMessage: 'Connectors', @@ -103,6 +107,7 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { }), }, { + 'data-test-subj': 'searchSideNav-Crawlers', id: 'crawlers', name: i18n.translate('xpack.enterpriseSearch.nav.crawlersTitle', { defaultMessage: 'Web crawlers', @@ -119,9 +124,11 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { }), }, { + 'data-test-subj': 'searchSideNav-Build', id: 'build', items: [ { + 'data-test-subj': 'searchSideNav-Playground', id: 'playground', name: i18n.translate('xpack.enterpriseSearch.nav.PlaygroundTitle', { defaultMessage: 'Playground', @@ -133,6 +140,7 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { }), }, { + 'data-test-subj': 'searchSideNav-SearchApplications', id: 'searchApplications', name: i18n.translate('xpack.enterpriseSearch.nav.searchApplicationsTitle', { defaultMessage: 'Search Applications', @@ -143,6 +151,7 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { }), }, { + 'data-test-subj': 'searchSideNav-BehavioralAnalytics', id: 'analyticsCollections', name: i18n.translate('xpack.enterpriseSearch.nav.analyticsTitle', { defaultMessage: 'Behavioral Analytics', @@ -160,9 +169,11 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { ...(hasEnterpriseLicense ? [ { + 'data-test-subj': 'searchSideNav-Relevance', id: 'relevance', items: [ { + 'data-test-subj': 'searchSideNav-InferenceEndpoints', id: 'inference_endpoints', name: i18n.translate('xpack.enterpriseSearch.nav.inferenceEndpointsTitle', { defaultMessage: 'Inference Endpoints', @@ -181,9 +192,11 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { ] : []), { + 'data-test-subj': 'searchSideNav-GettingStarted', id: 'es_getting_started', items: [ { + 'data-test-subj': 'searchSideNav-Elasticsearch', id: 'elasticsearch', name: i18n.translate('xpack.enterpriseSearch.nav.elasticsearchTitle', { defaultMessage: 'Elasticsearch', @@ -194,6 +207,7 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { }), }, { + 'data-test-subj': 'searchSideNav-VectorSearch', id: 'vectorSearch', name: VECTOR_SEARCH_PLUGIN.NAME, ...generateNavLink({ @@ -202,6 +216,7 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { }), }, { + 'data-test-subj': 'searchSideNav-SemanticSearch', id: 'semanticSearch', name: SEMANTIC_SEARCH_PLUGIN.NAME, ...generateNavLink({ @@ -210,6 +225,7 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { }), }, { + 'data-test-subj': 'searchSideNav-AISearch', id: 'aiSearch', name: i18n.translate('xpack.enterpriseSearch.nav.aiSearchTitle', { defaultMessage: 'AI Search', @@ -227,11 +243,13 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { ...(productAccess.hasAppSearchAccess || productAccess.hasWorkplaceSearchAccess ? [ { + 'data-test-subj': 'searchSideNav-EnterpriseSearch', id: 'enterpriseSearch', items: [ ...(productAccess.hasAppSearchAccess ? [ { + 'data-test-subj': 'searchSideNav-AppSearch', id: 'app_search', name: i18n.translate('xpack.enterpriseSearch.nav.appSearchTitle', { defaultMessage: 'App Search', @@ -246,6 +264,7 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { ...(productAccess.hasWorkplaceSearchAccess ? [ { + 'data-test-subj': 'searchSideNav-WorkplaceSearch', id: 'workplace_search', name: i18n.translate('xpack.enterpriseSearch.nav.workplaceSearchTitle', { defaultMessage: 'Workplace Search', diff --git a/x-pack/test/functional/config.base.js b/x-pack/test/functional/config.base.js index b35d1f6b6673c..d5c6d77785b85 100644 --- a/x-pack/test/functional/config.base.js +++ b/x-pack/test/functional/config.base.js @@ -196,6 +196,9 @@ export default async function ({ readConfigFile }) { obsAIAssistantManagement: { pathname: '/app/management/kibana/observabilityAiAssistantManagement', }, + enterpriseSearch: { + pathname: '/app/enterprise_search/overview', + }, }, suiteTags: { diff --git a/x-pack/test/functional/page_objects/index.ts b/x-pack/test/functional/page_objects/index.ts index e0d12bdd7459e..0d270661a05df 100644 --- a/x-pack/test/functional/page_objects/index.ts +++ b/x-pack/test/functional/page_objects/index.ts @@ -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, diff --git a/x-pack/test/functional/page_objects/search_classic_navigation.ts b/x-pack/test/functional/page_objects/search_classic_navigation.ts new file mode 100644 index 0000000000000..c401262fc7023 --- /dev/null +++ b/x-pack/test/functional/page_objects/search_classic_navigation.ts @@ -0,0 +1,26 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; + +export function SearchClassicNavigationProvider({ getService }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + + return { + async expectAllNavItems(items: Array<{ id: string; label: string }>) { + for (const navItem of items) { + await testSubjects.existOrFail(`searchSideNav-${navItem.id}`); + const itemElement = await testSubjects.find(`searchSideNav-${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); + }, + }; +} diff --git a/x-pack/test/functional_search/index.ts b/x-pack/test/functional_search/index.ts index 149b3dbcf7eca..d48bd1d695d16 100644 --- a/x-pack/test/functional_search/index.ts +++ b/x-pack/test/functional_search/index.ts @@ -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')); }); }; diff --git a/x-pack/test/functional_search/tests/classic_navigation.ts b/x-pack/test/functional_search/tests/classic_navigation.ts new file mode 100644 index 0000000000000..590c580b3b18b --- /dev/null +++ b/x-pack/test/functional_search/tests/classic_navigation.ts @@ -0,0 +1,60 @@ +/* + * 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; + 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' }, + ]); + }); + }); +} From 9c410a3ef31b340a706992666b0479674112be38 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Wed, 16 Oct 2024 16:28:08 +0000 Subject: [PATCH 02/23] refactor: build base sidenav items with a function 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. --- .../applications/shared/layout/base_nav.tsx | 268 ++++++++++++++++ .../public/applications/shared/layout/nav.tsx | 303 +++--------------- .../shared/layout/nav_link_helpers.ts | 15 +- .../react_router_helpers/eui_components.tsx | 4 +- .../generate_react_router_props.ts | 9 +- .../public/applications/shared/types.ts | 33 ++ 6 files changed, 365 insertions(+), 267 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx new file mode 100644 index 0000000000000..3b884790ed722 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx @@ -0,0 +1,268 @@ +/* + * 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 { 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, PLAYGROUND_PATH } from '../../applications/routes'; +import { + CONNECTORS_PATH, + CRAWLERS_PATH, + SEARCH_INDICES_PATH, +} from '../../enterprise_search_content/routes'; +import { INFERENCE_ENDPOINTS_PATH } from '../../enterprise_search_relevance/routes'; + +import { ClassicNavItem, BuildClassicNavParameters } from '../types'; + +export const buildBaseClassicNavItems = ({ + hasEnterpriseLicense, + productAccess, +}: BuildClassicNavParameters): ClassicNavItem[] => { + const navItems: ClassicNavItem[] = []; + + // Home + navItems.push({ + 'data-test-subj': 'searchSideNav-Home', + id: 'home', + name: ( + + {i18n.translate('xpack.enterpriseSearch.nav.homeTitle', { + defaultMessage: 'Home', + })} + + ), + navLink: { + shouldNotCreateHref: true, + shouldShowActiveForSubroutes: true, + to: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL, + }, + }); + + // Content + navItems.push({ + 'data-test-subj': 'searchSideNav-Content', + id: 'content', + items: [ + { + 'data-test-subj': 'searchSideNav-Indices', + id: 'search_indices', + name: i18n.translate('xpack.enterpriseSearch.nav.searchIndicesTitle', { + defaultMessage: 'Indices', + }), + navLink: { + shouldNotCreateHref: true, + shouldShowActiveForSubroutes: true, + to: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + SEARCH_INDICES_PATH, + }, + }, + { + 'data-test-subj': 'searchSideNav-Connectors', + id: 'connectors', + name: i18n.translate('xpack.enterpriseSearch.nav.connectorsTitle', { + defaultMessage: 'Connectors', + }), + navLink: { + shouldNotCreateHref: true, + shouldShowActiveForSubroutes: true, + to: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + CONNECTORS_PATH, + }, + }, + { + 'data-test-subj': 'searchSideNav-Crawlers', + id: 'crawlers', + name: i18n.translate('xpack.enterpriseSearch.nav.crawlersTitle', { + defaultMessage: 'Web crawlers', + }), + navLink: { + shouldNotCreateHref: true, + shouldShowActiveForSubroutes: true, + to: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + CRAWLERS_PATH, + }, + }, + ], + 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', + id: 'playground', + name: i18n.translate('xpack.enterpriseSearch.nav.PlaygroundTitle', { + defaultMessage: 'Playground', + }), + navLink: { + shouldNotCreateHref: true, + shouldShowActiveForSubroutes: true, + to: APPLICATIONS_PLUGIN.URL + PLAYGROUND_PATH, + }, + }, + { + 'data-test-subj': 'searchSideNav-SearchApplications', + id: 'searchApplications', + name: i18n.translate('xpack.enterpriseSearch.nav.searchApplicationsTitle', { + defaultMessage: 'Search Applications', + }), + navLink: { + shouldNotCreateHref: true, + to: APPLICATIONS_PLUGIN.URL + SEARCH_APPLICATIONS_PATH, + }, + }, + { + 'data-test-subj': 'searchSideNav-BehavioralAnalytics', + id: 'analyticsCollections', + name: i18n.translate('xpack.enterpriseSearch.nav.analyticsTitle', { + defaultMessage: 'Behavioral Analytics', + }), + navLink: { + shouldNotCreateHref: true, + to: ANALYTICS_PLUGIN.URL, + }, + }, + ], + name: i18n.translate('xpack.enterpriseSearch.nav.applicationsTitle', { + defaultMessage: 'Build', + }), + }); + + if (hasEnterpriseLicense) { + // Relevance + navItems.push({ + 'data-test-subj': 'searchSideNav-Relevance', + id: 'relevance', + items: [ + { + 'data-test-subj': 'searchSideNav-InferenceEndpoints', + id: 'inference_endpoints', + name: i18n.translate('xpack.enterpriseSearch.nav.inferenceEndpointsTitle', { + defaultMessage: 'Inference Endpoints', + }), + navLink: { + shouldNotCreateHref: true, + shouldShowActiveForSubroutes: true, + to: SEARCH_RELEVANCE_PLUGIN.URL + INFERENCE_ENDPOINTS_PATH, + }, + }, + ], + 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', + id: 'elasticsearch', + name: i18n.translate('xpack.enterpriseSearch.nav.elasticsearchTitle', { + defaultMessage: 'Elasticsearch', + }), + navLink: { + shouldNotCreateHref: true, + to: ELASTICSEARCH_PLUGIN.URL, + }, + }, + { + 'data-test-subj': 'searchSideNav-VectorSearch', + id: 'vectorSearch', + name: VECTOR_SEARCH_PLUGIN.NAME, + navLink: { + shouldNotCreateHref: true, + to: VECTOR_SEARCH_PLUGIN.URL, + }, + }, + { + 'data-test-subj': 'searchSideNav-SemanticSearch', + id: 'semanticSearch', + name: SEMANTIC_SEARCH_PLUGIN.NAME, + navLink: { + shouldNotCreateHref: true, + to: SEMANTIC_SEARCH_PLUGIN.URL, + }, + }, + { + 'data-test-subj': 'searchSideNav-AISearch', + id: 'aiSearch', + name: i18n.translate('xpack.enterpriseSearch.nav.aiSearchTitle', { + defaultMessage: 'AI Search', + }), + navLink: { + shouldNotCreateHref: true, + to: AI_SEARCH_PLUGIN.URL, + }, + }, + ], + name: i18n.translate('xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle', { + defaultMessage: 'Getting started', + }), + }); + + if (productAccess.hasAppSearchAccess || productAccess.hasWorkplaceSearchAccess) { + const entSearchItems: ClassicNavItem[] = []; + if (productAccess.hasAppSearchAccess) { + entSearchItems.push({ + 'data-test-subj': 'searchSideNav-AppSearch', + id: 'app_search', + name: i18n.translate('xpack.enterpriseSearch.nav.appSearchTitle', { + defaultMessage: 'App Search', + }), + navLink: { + shouldNotCreateHref: true, + to: APP_SEARCH_PLUGIN.URL, + }, + }); + } + if (productAccess.hasWorkplaceSearchAccess) { + entSearchItems.push({ + 'data-test-subj': 'searchSideNav-WorkplaceSearch', + id: 'workplace_search', + name: i18n.translate('xpack.enterpriseSearch.nav.workplaceSearchTitle', { + defaultMessage: 'Workplace Search', + }), + navLink: { + shouldNotCreateHref: true, + to: WORKPLACE_SEARCH_PLUGIN.URL, + }, + }); + } + navItems.push({ + 'data-test-subj': 'searchSideNav-EnterpriseSearch', + id: 'enterpriseSearch', + items: entSearchItems, + name: i18n.translate('xpack.enterpriseSearch.nav.title', { + defaultMessage: 'Enterprise Search', + }), + }); + } + + return navItems; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx index 98d5eb9f28e51..b34b018a89e95 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx @@ -5,44 +5,23 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { useValues } from 'kea'; -import { EuiFlexGroup, EuiIcon, EuiText } from '@elastic/eui'; +import { EuiFlexGroup, EuiIcon } from '@elastic/eui'; import type { 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 { ClassicNavItem } from '../types'; +import { buildBaseClassicNavItems } from './base_nav'; import { generateNavLink } from './nav_link_helpers'; /** @@ -58,232 +37,12 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { const indicesNavItems = useIndicesNav(); - if (!isSidebarEnabled && !alwaysReturn) return undefined; + const navItems: Array> = useMemo(() => { + const baseNavItems = buildBaseClassicNavItems({ hasEnterpriseLicense, productAccess }); + return generateSideNavItems(baseNavItems, { search_indices: indicesNavItems }); + }, [hasEnterpriseLicense, productAccess, indicesNavItems]); - const navItems: Array> = [ - { - 'data-test-subj': 'searchSideNav-Home', - id: 'home', - name: ( - - {i18n.translate('xpack.enterpriseSearch.nav.homeTitle', { - defaultMessage: 'Home', - })} - - ), - ...generateNavLink({ - shouldNotCreateHref: true, - shouldShowActiveForSubroutes: true, - to: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL, - }), - }, - { - 'data-test-subj': 'searchSideNav-Content', - id: 'content', - items: [ - { - 'data-test-subj': 'searchSideNav-Indices', - 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, - }), - }, - { - 'data-test-subj': 'searchSideNav-Connectors', - id: 'connectors', - name: i18n.translate('xpack.enterpriseSearch.nav.connectorsTitle', { - defaultMessage: 'Connectors', - }), - ...generateNavLink({ - shouldNotCreateHref: true, - shouldShowActiveForSubroutes: true, - to: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + CONNECTORS_PATH, - }), - }, - { - 'data-test-subj': 'searchSideNav-Crawlers', - 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', - }), - }, - { - 'data-test-subj': 'searchSideNav-Build', - id: 'build', - items: [ - { - 'data-test-subj': 'searchSideNav-Playground', - id: 'playground', - name: i18n.translate('xpack.enterpriseSearch.nav.PlaygroundTitle', { - defaultMessage: 'Playground', - }), - ...generateNavLink({ - shouldNotCreateHref: true, - shouldShowActiveForSubroutes: true, - to: APPLICATIONS_PLUGIN.URL + PLAYGROUND_PATH, - }), - }, - { - 'data-test-subj': 'searchSideNav-SearchApplications', - id: 'searchApplications', - name: i18n.translate('xpack.enterpriseSearch.nav.searchApplicationsTitle', { - defaultMessage: 'Search Applications', - }), - ...generateNavLink({ - shouldNotCreateHref: true, - to: APPLICATIONS_PLUGIN.URL + SEARCH_APPLICATIONS_PATH, - }), - }, - { - 'data-test-subj': 'searchSideNav-BehavioralAnalytics', - 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 - ? [ - { - 'data-test-subj': 'searchSideNav-Relevance', - id: 'relevance', - items: [ - { - 'data-test-subj': 'searchSideNav-InferenceEndpoints', - 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', - }), - }, - ] - : []), - { - 'data-test-subj': 'searchSideNav-GettingStarted', - id: 'es_getting_started', - items: [ - { - 'data-test-subj': 'searchSideNav-Elasticsearch', - id: 'elasticsearch', - name: i18n.translate('xpack.enterpriseSearch.nav.elasticsearchTitle', { - defaultMessage: 'Elasticsearch', - }), - ...generateNavLink({ - shouldNotCreateHref: true, - to: ELASTICSEARCH_PLUGIN.URL, - }), - }, - { - 'data-test-subj': 'searchSideNav-VectorSearch', - id: 'vectorSearch', - name: VECTOR_SEARCH_PLUGIN.NAME, - ...generateNavLink({ - shouldNotCreateHref: true, - to: VECTOR_SEARCH_PLUGIN.URL, - }), - }, - { - 'data-test-subj': 'searchSideNav-SemanticSearch', - id: 'semanticSearch', - name: SEMANTIC_SEARCH_PLUGIN.NAME, - ...generateNavLink({ - shouldNotCreateHref: true, - to: SEMANTIC_SEARCH_PLUGIN.URL, - }), - }, - { - 'data-test-subj': 'searchSideNav-AISearch', - 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 - ? [ - { - 'data-test-subj': 'searchSideNav-EnterpriseSearch', - id: 'enterpriseSearch', - items: [ - ...(productAccess.hasAppSearchAccess - ? [ - { - 'data-test-subj': 'searchSideNav-AppSearch', - id: 'app_search', - name: i18n.translate('xpack.enterpriseSearch.nav.appSearchTitle', { - defaultMessage: 'App Search', - }), - ...generateNavLink({ - shouldNotCreateHref: true, - to: APP_SEARCH_PLUGIN.URL, - }), - }, - ] - : []), - ...(productAccess.hasWorkplaceSearchAccess - ? [ - { - 'data-test-subj': 'searchSideNav-WorkplaceSearch', - 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', - }), - }, - ] - : []), - ]; + if (!isSidebarEnabled && !alwaysReturn) return undefined; return navItems; }; @@ -447,3 +206,43 @@ export const useEnterpriseSearchAnalyticsNav = ( return navItems; }; + +export const generateSideNavItems = ( + navItems: ClassicNavItem[], + subItemsMap: Record> | undefined> = {} +): Array> => { + const sideNavItems: Array> = []; + + for (const navItem of navItems) { + let sideNavChildItems: Array> | undefined; + + const { navLink, items, ...rest } = navItem; + const subItems = subItemsMap?.[navItem.id]; + + if (items || subItems) { + sideNavChildItems = []; + if (items) { + sideNavChildItems.push(...generateSideNavItems(items, subItemsMap)); + } + if (subItems) { + sideNavChildItems.push(...subItems); + } + } + if (navLink) { + sideNavItems.push({ + ...rest, + ...generateNavLink({ + ...navLink, + items: sideNavChildItems, + }), + }); + } else { + sideNavItems.push({ + ...rest, + items: sideNavChildItems, + }); + } + } + + return sideNavItems; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav_link_helpers.ts b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav_link_helpers.ts index f086433c9fc0e..6056b00e545c3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav_link_helpers.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav_link_helpers.ts @@ -5,27 +5,30 @@ * 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 { generateReactRouterProps } from '../react_router_helpers'; import { GeneratedReactRouterProps } from '../react_router_helpers/generate_react_router_props'; +import { ReactRouterProps } from '../types'; interface Params { - items?: Array>; // Primarily passed if using `items` to determine isSelected - if not, you can just set `items` outside of this helper + items?: Array>; // 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 = GeneratedReactRouterProps & - Pick, 'isSelected' | 'items'>; + Pick, 'isSelected' | 'items'>; + +export type GenerateNavLinkParameters = Params & ReactRouterProps; export const generateNavLink = ({ items, ...rest -}: Params & ReactRouterProps): NavLinkProps => { +}: GenerateNavLinkParameters): NavLinkProps => { const linkProps = { ...generateReactRouterProps({ ...rest }), isSelected: getNavLinkActive({ items, ...rest }), @@ -38,7 +41,7 @@ export const getNavLinkActive = ({ shouldShowActiveForSubroutes = false, items = [], shouldNotCreateHref = false, -}: Params & ReactRouterProps): boolean => { +}: GenerateNavLinkParameters): boolean => { const { pathname } = KibanaLogic.values.history.location; const currentPath = stripTrailingSlash(pathname); const { href: currentPathHref } = generateReactRouterProps({ diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_components.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_components.tsx index 8271f49f9f39a..708cc597e582d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_components.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_components.tsx @@ -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 diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/generate_react_router_props.ts b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/generate_react_router_props.ts index 2ef7f556eb2d1..03e0022d64770 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/generate_react_router_props.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/generate_react_router_props.ts @@ -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 = Required< Pick, 'href' | 'onClick'> >; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts index 51a83cb15cca5..bea1a21f96af6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts @@ -5,7 +5,12 @@ * 2.0. */ +import type { ReactNode } from 'react'; + +import type { 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,31 @@ export interface SingleUserRoleMapping { 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; +} + +export type GenerateNavLinkParameters = { + items?: Array>; // 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 BuildClassicNavParameters { + hasEnterpriseLicense: boolean; + productAccess: ProductAccess; +} + +export interface ClassicNavItem { + 'data-test-subj'?: string; + iconToString?: string; + id: string; + items?: ClassicNavItem[]; + name: ReactNode; + navLink?: GenerateNavLinkParameters; +} From 7d37362e42fcd5c1c81e2c524fa1d2557d880ea8 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Wed, 16 Oct 2024 22:04:16 +0000 Subject: [PATCH 03/23] search nav: add support for deep links Updated the search solution nav hooks to support loading urls from deeplinks instead of using constants. --- .../__mocks__/kea_logic/kibana_logic.mock.ts | 1 + .../public/applications/index.tsx | 1 + .../shared/kibana/kibana_logic.ts | 3 + .../applications/shared/layout/base_nav.tsx | 44 ++--- .../applications/shared/layout/nav.test.tsx | 161 +++++++++++++----- .../public/applications/shared/layout/nav.tsx | 65 +++++-- .../shared/layout/nav_link_helpers.test.ts | 1 + .../shared/layout/nav_link_helpers.ts | 3 +- .../react_router_helpers/create_href.ts | 11 +- .../generate_react_router_props.test.ts | 2 + .../generate_react_router_props.ts | 5 +- .../public/applications/shared/types.ts | 12 +- 12 files changed, 214 insertions(+), 95 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts index cca5523ded681..9b37c661d923a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts @@ -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, diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx index eafa8827869d8..717379d433dd1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -114,6 +114,7 @@ export const renderApp = ( data: plugins.data, esConfig, getChromeStyle$: chrome.getChromeStyle$, + getNavLinks: chrome.navLinks.getAll, guidedOnboarding, history, indexMappingComponent, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts index f74345a1c75c1..6cd6e5410ef11 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts @@ -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; @@ -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 | null; @@ -126,6 +128,7 @@ export const KibanaLogic = kea>({ 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, {}], diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx index 3b884790ed722..89ce326b10498 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx @@ -11,27 +11,12 @@ import { EuiText } from '@elastic/eui'; 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, PLAYGROUND_PATH } from '../../applications/routes'; -import { - CONNECTORS_PATH, - CRAWLERS_PATH, - SEARCH_INDICES_PATH, -} from '../../enterprise_search_content/routes'; -import { INFERENCE_ENDPOINTS_PATH } from '../../enterprise_search_relevance/routes'; - import { ClassicNavItem, BuildClassicNavParameters } from '../types'; export const buildBaseClassicNavItems = ({ @@ -52,9 +37,8 @@ export const buildBaseClassicNavItems = ({ ), navLink: { - shouldNotCreateHref: true, + link: 'enterpriseSearch', shouldShowActiveForSubroutes: true, - to: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL, }, }); @@ -70,9 +54,8 @@ export const buildBaseClassicNavItems = ({ defaultMessage: 'Indices', }), navLink: { - shouldNotCreateHref: true, + link: 'enterpriseSearchContent:searchIndices', shouldShowActiveForSubroutes: true, - to: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + SEARCH_INDICES_PATH, }, }, { @@ -82,9 +65,8 @@ export const buildBaseClassicNavItems = ({ defaultMessage: 'Connectors', }), navLink: { - shouldNotCreateHref: true, + link: 'enterpriseSearchContent:connectors', shouldShowActiveForSubroutes: true, - to: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + CONNECTORS_PATH, }, }, { @@ -94,9 +76,8 @@ export const buildBaseClassicNavItems = ({ defaultMessage: 'Web crawlers', }), navLink: { - shouldNotCreateHref: true, + link: 'enterpriseSearchContent:webCrawlers', shouldShowActiveForSubroutes: true, - to: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + CRAWLERS_PATH, }, }, ], @@ -117,9 +98,8 @@ export const buildBaseClassicNavItems = ({ defaultMessage: 'Playground', }), navLink: { - shouldNotCreateHref: true, + link: 'enterpriseSearchApplications:playground', shouldShowActiveForSubroutes: true, - to: APPLICATIONS_PLUGIN.URL + PLAYGROUND_PATH, }, }, { @@ -129,8 +109,7 @@ export const buildBaseClassicNavItems = ({ defaultMessage: 'Search Applications', }), navLink: { - shouldNotCreateHref: true, - to: APPLICATIONS_PLUGIN.URL + SEARCH_APPLICATIONS_PATH, + link: 'enterpriseSearchApplications:searchApplications', }, }, { @@ -140,8 +119,8 @@ export const buildBaseClassicNavItems = ({ defaultMessage: 'Behavioral Analytics', }), navLink: { + link: 'enterpriseSearchAnalytics', shouldNotCreateHref: true, - to: ANALYTICS_PLUGIN.URL, }, }, ], @@ -163,9 +142,8 @@ export const buildBaseClassicNavItems = ({ defaultMessage: 'Inference Endpoints', }), navLink: { - shouldNotCreateHref: true, + link: 'enterpriseSearchRelevance:inferenceEndpoints', shouldShowActiveForSubroutes: true, - to: SEARCH_RELEVANCE_PLUGIN.URL + INFERENCE_ENDPOINTS_PATH, }, }, ], @@ -236,8 +214,7 @@ export const buildBaseClassicNavItems = ({ defaultMessage: 'App Search', }), navLink: { - shouldNotCreateHref: true, - to: APP_SEARCH_PLUGIN.URL, + link: 'appSearch:engines', }, }); } @@ -249,8 +226,7 @@ export const buildBaseClassicNavItems = ({ defaultMessage: 'Workplace Search', }), navLink: { - shouldNotCreateHref: true, - to: WORKPLACE_SEARCH_PLUGIN.URL, + link: 'workplaceSearch', }, }); } diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx index b2c31ff4868bc..2846ac9a63dd9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx @@ -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,73 @@ const baseNavItems = [ }, ]; +const mockNavLinks = [ + { + id: 'enterpriseSearch', + url: '/app/enterprise_search/overview', + }, + { + id: 'enterpriseSearchContent:searchIndices', + url: '/app/enterprise_search/content/search_indices', + }, + { + id: 'enterpriseSearchContent:connectors', + url: '/app/enterprise_search/content/connectors', + }, + { + id: 'enterpriseSearchContent:webCrawlers', + url: '/app/enterprise_search/content/crawlers', + }, + { + id: 'enterpriseSearchApplications:playground', + url: '/app/enterprise_search/applications/playground', + }, + { + id: 'enterpriseSearchApplications:searchApplications', + url: '/app/enterprise_search/applications/search_applications', + }, + { + id: 'enterpriseSearchAnalytics', + url: '/app/enterprise_search/analytics', + }, + { + id: 'enterpriseSearchRelevance:inferenceEndpoints', + url: '/app/enterprise_search/relevance/inference_endpoints', + }, + { + id: 'appSearch:engines', + url: '/app/enterprise_search/app_search', + }, + { + id: 'workplaceSearch', + url: '/app/enterprise_search/workplace_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 +244,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 +263,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 +293,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 +320,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 +394,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 +427,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 +464,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 +491,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: [ diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx index b34b018a89e95..1c004166bd9ce 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx @@ -10,7 +10,7 @@ import React, { useMemo } from 'react'; import { useValues } from 'kea'; import { EuiFlexGroup, EuiIcon } from '@elastic/eui'; -import type { EuiSideNavItemTypeEnhanced } from '@kbn/core-chrome-browser'; +import type { ChromeNavLink, EuiSideNavItemTypeEnhanced } from '@kbn/core-chrome-browser'; import { i18n } from '@kbn/i18n'; import { ANALYTICS_PLUGIN, APPLICATIONS_PLUGIN } from '../../../../common/constants'; @@ -19,7 +19,11 @@ import { useIndicesNav } from '../../enterprise_search_content/components/search import { KibanaLogic } from '../kibana'; import { LicensingLogic } from '../licensing'; -import { ClassicNavItem } from '../types'; +import { + ClassicNavItem, + GenerateNavLinkParameters, + GenerateNavLinkFromDeepLinkParameters, +} from '../types'; import { buildBaseClassicNavItems } from './base_nav'; import { generateNavLink } from './nav_link_helpers'; @@ -31,16 +35,23 @@ import { generateNavLink } from './nav_link_helpers'; * @returns The Enterprise Search navigation items */ export const useEnterpriseSearchNav = (alwaysReturn = false) => { - const { isSidebarEnabled, productAccess } = useValues(KibanaLogic); + const { isSidebarEnabled, productAccess, getNavLinks } = useValues(KibanaLogic); const { hasEnterpriseLicense } = useValues(LicensingLogic); const indicesNavItems = useIndicesNav(); + const deepLinks = useMemo(() => { + return getNavLinks().reduce((links, link) => { + links[link.id] = link; + return links; + }, {} as Record); + }, []); const navItems: Array> = useMemo(() => { const baseNavItems = buildBaseClassicNavItems({ hasEnterpriseLicense, productAccess }); - return generateSideNavItems(baseNavItems, { search_indices: indicesNavItems }); - }, [hasEnterpriseLicense, productAccess, indicesNavItems]); + + return generateSideNavItems(baseNavItems, deepLinks, { search_indices: indicesNavItems }); + }, [hasEnterpriseLicense, productAccess, indicesNavItems, deepLinks]); if (!isSidebarEnabled && !alwaysReturn) return undefined; @@ -209,6 +220,7 @@ export const useEnterpriseSearchAnalyticsNav = ( export const generateSideNavItems = ( navItems: ClassicNavItem[], + deepLinks: Record, subItemsMap: Record> | undefined> = {} ): Array> => { const sideNavItems: Array> = []; @@ -222,20 +234,23 @@ export const generateSideNavItems = ( if (items || subItems) { sideNavChildItems = []; if (items) { - sideNavChildItems.push(...generateSideNavItems(items, subItemsMap)); + sideNavChildItems.push(...generateSideNavItems(items, deepLinks, subItemsMap)); } if (subItems) { sideNavChildItems.push(...subItems); } } if (navLink) { - sideNavItems.push({ - ...rest, - ...generateNavLink({ - ...navLink, - items: sideNavChildItems, - }), - }); + const navLinkParams = getNavLinkParameters(navLink, deepLinks); + if (navLinkParams !== undefined) { + sideNavItems.push({ + ...rest, + ...generateNavLink({ + ...navLinkParams, + items: sideNavChildItems, + }), + }); + } } else { sideNavItems.push({ ...rest, @@ -246,3 +261,27 @@ export const generateSideNavItems = ( return sideNavItems; }; + +const getNavLinkParameters = ( + navLink: GenerateNavLinkParameters | GenerateNavLinkFromDeepLinkParameters, + deepLinks: Record +): GenerateNavLinkParameters | undefined => { + if (isGenerateNavLinkParameters(navLink)) { + return navLink; + } + const { link, ...navLinkProps } = navLink; + const deepLink = deepLinks[link]; + if (!deepLink || !deepLink.url) return undefined; + return { + ...navLinkProps, + shouldNotPrepend: true, + shouldNotCreateHref: true, + to: deepLink.url, + }; +}; + +function isGenerateNavLinkParameters( + navLink: GenerateNavLinkParameters | GenerateNavLinkFromDeepLinkParameters +): navLink is GenerateNavLinkParameters { + return Object.hasOwn(navLink, 'to'); +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav_link_helpers.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav_link_helpers.test.ts index fff28345bb1bb..50c85a268e366 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav_link_helpers.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav_link_helpers.test.ts @@ -36,6 +36,7 @@ describe('generateNavLink', () => { navItem.onClick({ preventDefault: jest.fn() } as any); expect(mockKibanaValues.navigateToUrl).toHaveBeenCalledWith('/test', { shouldNotCreateHref: false, + shouldNotPrepend: false, }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav_link_helpers.ts b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav_link_helpers.ts index 6056b00e545c3..b2cf342e8cb93 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav_link_helpers.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav_link_helpers.ts @@ -41,6 +41,7 @@ export const getNavLinkActive = ({ shouldShowActiveForSubroutes = false, items = [], shouldNotCreateHref = false, + shouldNotPrepend = false, }: GenerateNavLinkParameters): boolean => { const { pathname } = KibanaLogic.values.history.location; const currentPath = stripTrailingSlash(pathname); @@ -48,7 +49,7 @@ export const getNavLinkActive = ({ shouldNotCreateHref: false, to: currentPath, }); - const { href: toHref } = generateReactRouterProps({ shouldNotCreateHref, to }); + const { href: toHref } = generateReactRouterProps({ shouldNotCreateHref, shouldNotPrepend, to }); if (currentPathHref === toHref) return true; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/create_href.ts b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/create_href.ts index a399d632140b6..cf02c3ed74f71 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/create_href.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/create_href.ts @@ -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 }); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/generate_react_router_props.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/generate_react_router_props.test.ts index 309f94fcf55b4..de2a80ee5eaf4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/generate_react_router_props.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/generate_react_router_props.test.ts @@ -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, }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/generate_react_router_props.ts b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/generate_react_router_props.ts index 03e0022d64770..89219362e5be4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/generate_react_router_props.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/generate_react_router_props.ts @@ -32,12 +32,13 @@ export const generateReactRouterProps = ({ to, onClick, shouldNotCreateHref = false, + shouldNotPrepend = false, }: ReactRouterProps): GeneratedReactRouterProps => { 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) @@ -47,7 +48,7 @@ export const generateReactRouterProps = ({ event.preventDefault(); // Perform SPA navigation. - navigateToUrl(to, { shouldNotCreateHref }); + navigateToUrl(to, { shouldNotCreateHref, shouldNotPrepend }); }; return { href, onClick: reactRouterLinkClick }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts index bea1a21f96af6..76cc7aac500c0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts @@ -7,7 +7,7 @@ import type { ReactNode } from 'react'; -import type { EuiSideNavItemTypeEnhanced } from '@kbn/core-chrome-browser'; +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'; @@ -69,6 +69,8 @@ export interface ReactRouterProps { // 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 = { @@ -77,6 +79,12 @@ export type GenerateNavLinkParameters = { to: string; } & ReactRouterProps; +export type GenerateNavLinkFromDeepLinkParameters = { + items?: Array>; // Primarily passed if using `items` to determine isSelected - if not, you can just set `items` outside of this helper + link: AppDeepLinkId; + shouldShowActiveForSubroutes?: boolean; +} & Omit; + export interface BuildClassicNavParameters { hasEnterpriseLicense: boolean; productAccess: ProductAccess; @@ -88,5 +96,5 @@ export interface ClassicNavItem { id: string; items?: ClassicNavItem[]; name: ReactNode; - navLink?: GenerateNavLinkParameters; + navLink?: GenerateNavLinkParameters | GenerateNavLinkFromDeepLinkParameters; } From 305e4c4668f93de9401bede6870f2ab882d4ada0 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Tue, 22 Oct 2024 20:41:29 +0000 Subject: [PATCH 04/23] search: define deeplinks for getting started defined deep link ids for getting started pages in search to align all side nav items to use deep links over urls --- packages/deeplinks/search/constants.ts | 4 +++ packages/deeplinks/search/deep_links.ts | 14 +++++++++- packages/deeplinks/search/index.ts | 4 +++ .../enterprise_search/common/constants.ts | 12 ++++++--- .../applications/shared/layout/base_nav.tsx | 27 ++++++++++--------- 5 files changed, 44 insertions(+), 17 deletions(-) diff --git a/packages/deeplinks/search/constants.ts b/packages/deeplinks/search/constants.ts index a2a17b20efba8..9848bb0c3d42e 100644 --- a/packages/deeplinks/search/constants.ts +++ b/packages/deeplinks/search/constants.ts @@ -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'; diff --git a/packages/deeplinks/search/deep_links.ts b/packages/deeplinks/search/deep_links.ts index 98703f18ac3fb..22dfb91bdff33 100644 --- a/packages/deeplinks/search/deep_links.ts +++ b/packages/deeplinks/search/deep_links.ts @@ -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; diff --git a/packages/deeplinks/search/index.ts b/packages/deeplinks/search/index.ts index 250dfeed299e6..7c78d64081133 100644 --- a/packages/deeplinks/search/index.ts +++ b/packages/deeplinks/search/index.ts @@ -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 { diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index 4da0244b2ec5e..eabefc0db45e5 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -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', diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx index 89ce326b10498..54788175d7120 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx @@ -8,14 +8,17 @@ 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 { - ELASTICSEARCH_PLUGIN, - AI_SEARCH_PLUGIN, - VECTOR_SEARCH_PLUGIN, - SEMANTIC_SEARCH_PLUGIN, -} from '../../../../common/constants'; +import { VECTOR_SEARCH_PLUGIN, SEMANTIC_SEARCH_PLUGIN } from '../../../../common/constants'; import { ClassicNavItem, BuildClassicNavParameters } from '../types'; @@ -37,7 +40,7 @@ export const buildBaseClassicNavItems = ({ ), navLink: { - link: 'enterpriseSearch', + link: ENTERPRISE_SEARCH_APP_ID, shouldShowActiveForSubroutes: true, }, }); @@ -119,7 +122,7 @@ export const buildBaseClassicNavItems = ({ defaultMessage: 'Behavioral Analytics', }), navLink: { - link: 'enterpriseSearchAnalytics', + link: ENTERPRISE_SEARCH_ANALYTICS_APP_ID, shouldNotCreateHref: true, }, }, @@ -165,8 +168,8 @@ export const buildBaseClassicNavItems = ({ defaultMessage: 'Elasticsearch', }), navLink: { + link: SEARCH_ELASTICSEARCH, shouldNotCreateHref: true, - to: ELASTICSEARCH_PLUGIN.URL, }, }, { @@ -174,8 +177,8 @@ export const buildBaseClassicNavItems = ({ id: 'vectorSearch', name: VECTOR_SEARCH_PLUGIN.NAME, navLink: { + link: SEARCH_VECTOR_SEARCH, shouldNotCreateHref: true, - to: VECTOR_SEARCH_PLUGIN.URL, }, }, { @@ -183,8 +186,8 @@ export const buildBaseClassicNavItems = ({ id: 'semanticSearch', name: SEMANTIC_SEARCH_PLUGIN.NAME, navLink: { + link: SEARCH_SEMANTIC_SEARCH, shouldNotCreateHref: true, - to: SEMANTIC_SEARCH_PLUGIN.URL, }, }, { @@ -194,8 +197,8 @@ export const buildBaseClassicNavItems = ({ defaultMessage: 'AI Search', }), navLink: { + link: SEARCH_AI_SEARCH, shouldNotCreateHref: true, - to: AI_SEARCH_PLUGIN.URL, }, }, ], From 4055acf065bf2a92b0cf80ac7c5c59d651a44193 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Wed, 23 Oct 2024 13:08:07 +0000 Subject: [PATCH 05/23] search(nav): exclude empty parent nav items --- .../public/applications/shared/layout/nav.tsx | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx index 1c004166bd9ce..0e83fba8d5caa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx @@ -240,22 +240,27 @@ export const generateSideNavItems = ( sideNavChildItems.push(...subItems); } } + let sideNavItem: EuiSideNavItemTypeEnhanced | undefined; if (navLink) { const navLinkParams = getNavLinkParameters(navLink, deepLinks); if (navLinkParams !== undefined) { - sideNavItems.push({ + sideNavItem = { ...rest, ...generateNavLink({ ...navLinkParams, items: sideNavChildItems, }), - }); + }; } } else { - sideNavItems.push({ + sideNavItem = { ...rest, items: sideNavChildItems, - }); + }; + } + + if (isValidSideNavItem(sideNavItem)) { + sideNavItems.push(sideNavItem); } } @@ -285,3 +290,13 @@ function isGenerateNavLinkParameters( ): navLink is GenerateNavLinkParameters { return Object.hasOwn(navLink, 'to'); } + +function isValidSideNavItem( + item: EuiSideNavItemTypeEnhanced | undefined +): item is EuiSideNavItemTypeEnhanced { + if (item === undefined) return false; + if (item.href || item.onClick) return true; + if (item?.items?.length ?? 0 > 0) return true; + + return false; +} From 8d6fa57979b9b6fab8587699dab9f9e1e811dd59 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Wed, 23 Oct 2024 16:02:27 +0000 Subject: [PATCH 06/23] simplify class nav type --- .../applications/shared/layout/base_nav.tsx | 100 +++++------------- .../public/applications/shared/layout/nav.tsx | 35 +++--- .../public/applications/shared/types.ts | 19 +++- 3 files changed, 64 insertions(+), 90 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx index 54788175d7120..94f06029ce47d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx @@ -18,8 +18,6 @@ import { } from '@kbn/deeplinks-search'; import { i18n } from '@kbn/i18n'; -import { VECTOR_SEARCH_PLUGIN, SEMANTIC_SEARCH_PLUGIN } from '../../../../common/constants'; - import { ClassicNavItem, BuildClassicNavParameters } from '../types'; export const buildBaseClassicNavItems = ({ @@ -31,6 +29,10 @@ export const buildBaseClassicNavItems = ({ // Home navItems.push({ 'data-test-subj': 'searchSideNav-Home', + deepLink: { + link: ENTERPRISE_SEARCH_APP_ID, + shouldShowActiveForSubroutes: true, + }, id: 'home', name: ( @@ -39,10 +41,6 @@ export const buildBaseClassicNavItems = ({ })} ), - navLink: { - link: ENTERPRISE_SEARCH_APP_ID, - shouldShowActiveForSubroutes: true, - }, }); // Content @@ -53,35 +51,26 @@ export const buildBaseClassicNavItems = ({ { 'data-test-subj': 'searchSideNav-Indices', id: 'search_indices', - name: i18n.translate('xpack.enterpriseSearch.nav.searchIndicesTitle', { - defaultMessage: 'Indices', - }), - navLink: { + deepLink: { link: 'enterpriseSearchContent:searchIndices', shouldShowActiveForSubroutes: true, }, }, { 'data-test-subj': 'searchSideNav-Connectors', - id: 'connectors', - name: i18n.translate('xpack.enterpriseSearch.nav.connectorsTitle', { - defaultMessage: 'Connectors', - }), - navLink: { + deepLink: { link: 'enterpriseSearchContent:connectors', shouldShowActiveForSubroutes: true, }, + id: 'connectors', }, { 'data-test-subj': 'searchSideNav-Crawlers', - id: 'crawlers', - name: i18n.translate('xpack.enterpriseSearch.nav.crawlersTitle', { - defaultMessage: 'Web crawlers', - }), - navLink: { + deepLink: { link: 'enterpriseSearchContent:webCrawlers', shouldShowActiveForSubroutes: true, }, + id: 'crawlers', }, ], name: i18n.translate('xpack.enterpriseSearch.nav.contentTitle', { @@ -96,35 +85,25 @@ export const buildBaseClassicNavItems = ({ items: [ { 'data-test-subj': 'searchSideNav-Playground', - id: 'playground', - name: i18n.translate('xpack.enterpriseSearch.nav.PlaygroundTitle', { - defaultMessage: 'Playground', - }), - navLink: { + deepLink: { link: 'enterpriseSearchApplications:playground', shouldShowActiveForSubroutes: true, }, + id: 'playground', }, { 'data-test-subj': 'searchSideNav-SearchApplications', - id: 'searchApplications', - name: i18n.translate('xpack.enterpriseSearch.nav.searchApplicationsTitle', { - defaultMessage: 'Search Applications', - }), - navLink: { + deepLink: { link: 'enterpriseSearchApplications:searchApplications', }, + id: 'searchApplications', }, { 'data-test-subj': 'searchSideNav-BehavioralAnalytics', - id: 'analyticsCollections', - name: i18n.translate('xpack.enterpriseSearch.nav.analyticsTitle', { - defaultMessage: 'Behavioral Analytics', - }), - navLink: { + deepLink: { link: ENTERPRISE_SEARCH_ANALYTICS_APP_ID, - shouldNotCreateHref: true, }, + id: 'analyticsCollections', }, ], name: i18n.translate('xpack.enterpriseSearch.nav.applicationsTitle', { @@ -140,14 +119,11 @@ export const buildBaseClassicNavItems = ({ items: [ { 'data-test-subj': 'searchSideNav-InferenceEndpoints', - id: 'inference_endpoints', - name: i18n.translate('xpack.enterpriseSearch.nav.inferenceEndpointsTitle', { - defaultMessage: 'Inference Endpoints', - }), - navLink: { + deepLink: { link: 'enterpriseSearchRelevance:inferenceEndpoints', shouldShowActiveForSubroutes: true, }, + id: 'inference_endpoints', }, ], name: i18n.translate('xpack.enterpriseSearch.nav.relevanceTitle', { @@ -163,43 +139,31 @@ export const buildBaseClassicNavItems = ({ items: [ { 'data-test-subj': 'searchSideNav-Elasticsearch', - id: 'elasticsearch', - name: i18n.translate('xpack.enterpriseSearch.nav.elasticsearchTitle', { - defaultMessage: 'Elasticsearch', - }), - navLink: { + deepLink: { link: SEARCH_ELASTICSEARCH, - shouldNotCreateHref: true, }, + id: 'elasticsearch', }, { 'data-test-subj': 'searchSideNav-VectorSearch', - id: 'vectorSearch', - name: VECTOR_SEARCH_PLUGIN.NAME, - navLink: { + deepLink: { link: SEARCH_VECTOR_SEARCH, - shouldNotCreateHref: true, }, + id: 'vectorSearch', }, { 'data-test-subj': 'searchSideNav-SemanticSearch', - id: 'semanticSearch', - name: SEMANTIC_SEARCH_PLUGIN.NAME, - navLink: { + deepLink: { link: SEARCH_SEMANTIC_SEARCH, - shouldNotCreateHref: true, }, + id: 'semanticSearch', }, { 'data-test-subj': 'searchSideNav-AISearch', - id: 'aiSearch', - name: i18n.translate('xpack.enterpriseSearch.nav.aiSearchTitle', { - defaultMessage: 'AI Search', - }), - navLink: { + deepLink: { link: SEARCH_AI_SEARCH, - shouldNotCreateHref: true, }, + id: 'aiSearch', }, ], name: i18n.translate('xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle', { @@ -212,25 +176,19 @@ export const buildBaseClassicNavItems = ({ if (productAccess.hasAppSearchAccess) { entSearchItems.push({ 'data-test-subj': 'searchSideNav-AppSearch', - id: 'app_search', - name: i18n.translate('xpack.enterpriseSearch.nav.appSearchTitle', { - defaultMessage: 'App Search', - }), - navLink: { + deepLink: { link: 'appSearch:engines', }, + id: 'app_search', }); } if (productAccess.hasWorkplaceSearchAccess) { entSearchItems.push({ 'data-test-subj': 'searchSideNav-WorkplaceSearch', - id: 'workplace_search', - name: i18n.translate('xpack.enterpriseSearch.nav.workplaceSearchTitle', { - defaultMessage: 'Workplace Search', - }), - navLink: { + deepLink: { link: 'workplaceSearch', }, + id: 'workplace_search', }); } navItems.push({ diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx index 0e83fba8d5caa..bad70dfdb39ef 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx @@ -21,6 +21,7 @@ import { KibanaLogic } from '../kibana'; import { LicensingLogic } from '../licensing'; import { ClassicNavItem, + DeepLinkNavItem, GenerateNavLinkParameters, GenerateNavLinkFromDeepLinkParameters, } from '../types'; @@ -228,7 +229,7 @@ export const generateSideNavItems = ( for (const navItem of navItems) { let sideNavChildItems: Array> | undefined; - const { navLink, items, ...rest } = navItem; + const { items, ...rest } = navItem; const subItems = subItemsMap?.[navItem.id]; if (items || subItems) { @@ -241,10 +242,13 @@ export const generateSideNavItems = ( } } let sideNavItem: EuiSideNavItemTypeEnhanced | undefined; - if (navLink) { - const navLinkParams = getNavLinkParameters(navLink, deepLinks); + if (isDeepLinkNavItem(navItem)) { + const deepLink = navItem.deepLink; + const navLinkParams = getNavLinkParameters(deepLink, deepLinks); if (navLinkParams !== undefined) { + const name = getDeepLinkTitle(deepLink.link, deepLinks); sideNavItem = { + name, ...rest, ...generateNavLink({ ...navLinkParams, @@ -256,6 +260,7 @@ export const generateSideNavItems = ( sideNavItem = { ...rest, items: sideNavChildItems, + name: navItem.name, }; } @@ -267,29 +272,31 @@ export const generateSideNavItems = ( return sideNavItems; }; +function isDeepLinkNavItem(item: ClassicNavItem): item is DeepLinkNavItem { + return 'deepLink' in item; +} const getNavLinkParameters = ( - navLink: GenerateNavLinkParameters | GenerateNavLinkFromDeepLinkParameters, + navLink: GenerateNavLinkFromDeepLinkParameters, deepLinks: Record ): GenerateNavLinkParameters | undefined => { - if (isGenerateNavLinkParameters(navLink)) { - return navLink; - } const { link, ...navLinkProps } = navLink; const deepLink = deepLinks[link]; if (!deepLink || !deepLink.url) return undefined; return { ...navLinkProps, - shouldNotPrepend: true, shouldNotCreateHref: true, + shouldNotPrepend: true, to: deepLink.url, }; }; - -function isGenerateNavLinkParameters( - navLink: GenerateNavLinkParameters | GenerateNavLinkFromDeepLinkParameters -): navLink is GenerateNavLinkParameters { - return Object.hasOwn(navLink, 'to'); -} +const getDeepLinkTitle = ( + link: string, + deepLinks: Record +): string | undefined => { + const deepLink = deepLinks[link]; + if (!deepLink || !deepLink.url) return undefined; + return deepLink.title; +}; function isValidSideNavItem( item: EuiSideNavItemTypeEnhanced | undefined diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts index 76cc7aac500c0..034f13b1797ea 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts @@ -79,22 +79,31 @@ export type GenerateNavLinkParameters = { to: string; } & ReactRouterProps; -export type GenerateNavLinkFromDeepLinkParameters = { - items?: Array>; // Primarily passed if using `items` to determine isSelected - if not, you can just set `items` outside of this helper +export interface GenerateNavLinkFromDeepLinkParameters { link: AppDeepLinkId; shouldShowActiveForSubroutes?: boolean; -} & Omit; +} export interface BuildClassicNavParameters { hasEnterpriseLicense: boolean; productAccess: ProductAccess; } -export interface ClassicNavItem { +export type ClassicNavItem = BaseClassicNavItem | DeepLinkNavItem; + +export interface BaseClassicNavItem { 'data-test-subj'?: string; iconToString?: string; id: string; items?: ClassicNavItem[]; name: ReactNode; - navLink?: GenerateNavLinkParameters | GenerateNavLinkFromDeepLinkParameters; +} + +export interface DeepLinkNavItem { + 'data-test-subj'?: string; + deepLink: GenerateNavLinkFromDeepLinkParameters; + iconToString?: string; + id: string; + items?: ClassicNavItem[]; + name?: ReactNode; } From 8f375a1aca5913e0584466208fcf8668b998fa75 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Wed, 23 Oct 2024 17:52:58 +0000 Subject: [PATCH 07/23] search: classic navigation ftr tests --- .../page_objects/search_classic_navigation.ts | 96 ++++++++++++++++++- x-pack/test/functional_search/config.ts | 10 ++ x-pack/test/functional_search/index.ts | 2 +- .../tests/classic_navigation.ts | 7 ++ 4 files changed, 112 insertions(+), 3 deletions(-) diff --git a/x-pack/test/functional/page_objects/search_classic_navigation.ts b/x-pack/test/functional/page_objects/search_classic_navigation.ts index c401262fc7023..90ec1c6c46007 100644 --- a/x-pack/test/functional/page_objects/search_classic_navigation.ts +++ b/x-pack/test/functional/page_objects/search_classic_navigation.ts @@ -6,21 +6,113 @@ */ 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), + 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(`searchSideNav-${navItem.id}`); - const itemElement = await testSubjects.find(`searchSideNav-${navItem.id}`); + 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); + }; + }, }; } diff --git a/x-pack/test/functional_search/config.ts b/x-pack/test/functional_search/config.ts index f997aaea7c5e2..493dce6dbcfdf 100644 --- a/x-pack/test/functional_search/config.ts +++ b/x-pack/test/functional_search/config.ts @@ -17,7 +17,17 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { return { ...functionalConfig.getAll(), + junit: { + reportName: 'Search Solution UI Functional Tests', + }, testFiles: [require.resolve('.')], + esTestCluster: { + ...functionalTestsConfig.get('esTestCluster'), + serverArgs: [ + ...functionalTestsConfig.get('esTestCluster.serverArgs'), + 'xpack.security.enabled=true', + ], + }, kbnTestServer: { ...functionalConfig.get('kbnTestServer'), serverArgs: [ diff --git a/x-pack/test/functional_search/index.ts b/x-pack/test/functional_search/index.ts index d48bd1d695d16..c73488fed8a7a 100644 --- a/x-pack/test/functional_search/index.ts +++ b/x-pack/test/functional_search/index.ts @@ -11,6 +11,6 @@ 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')); + // loadTestFile(require.resolve('./tests/solution_navigation')); }); }; diff --git a/x-pack/test/functional_search/tests/classic_navigation.ts b/x-pack/test/functional_search/tests/classic_navigation.ts index 590c580b3b18b..ad2fcd8b9c809 100644 --- a/x-pack/test/functional_search/tests/classic_navigation.ts +++ b/x-pack/test/functional_search/tests/classic_navigation.ts @@ -56,5 +56,12 @@ export default function searchSolutionNavigation({ { id: 'AISearch', label: 'AI Search' }, ]); }); + it('has expected navigation', async () => { + const expectNoPageReload = await searchClassicNavigation.createNoPageReloadCheck(); + + await searchClassicNavigation.expectNavItemExists('Home'); + + await expectNoPageReload(); + }); }); } From cec769389673b46c044d77d2851d6b9652da9b80 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Wed, 23 Oct 2024 18:24:44 +0000 Subject: [PATCH 08/23] fix(search): include build in Behavioral Analytics breadcrumb trail --- .../shared/kibana_chrome/generate_breadcrumbs.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts index ea6bda26be450..68e9a17f9f504 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts @@ -22,6 +22,7 @@ import { VECTOR_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN, SEMANTIC_SEARCH_PLUGIN, + APPLICATIONS_PLUGIN, } from '../../../../common/constants'; import { stripLeadingSlash } from '../../../../common/strip_slashes'; @@ -126,7 +127,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([ From e269ff0db9bf6cc6de6b396f82574ec35c447caa Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Wed, 23 Oct 2024 19:07:44 +0000 Subject: [PATCH 09/23] fix(search): build pages breadcrumbs & titles --- .../components/playground/page_template.tsx | 106 ++++++++++++++++++ .../components/playground/playground.tsx | 8 +- .../applications/applications/constants.ts | 15 +++ .../kibana_chrome/generate_breadcrumbs.ts | 2 +- .../shared/kibana_chrome/generate_title.ts | 17 ++- .../shared/kibana_chrome/set_chrome.tsx | 25 ++++- 6 files changed, 163 insertions(+), 10 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/applications/components/playground/page_template.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/applications/constants.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/applications/components/playground/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/applications/components/playground/page_template.tsx new file mode 100644 index 0000000000000..4ee7f9cec1cd0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/applications/components/playground/page_template.tsx @@ -0,0 +1,106 @@ +/* + * 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, { useCallback, useEffect, useLayoutEffect } from 'react'; + +import { useValues } from 'kea'; + +import useObservable from 'react-use/lib/useObservable'; + +import type { EuiSideNavItemTypeEnhanced } from '@kbn/core-chrome-browser'; + +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 { useEnterpriseSearchApplicationNav } 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 = ({ + children, + pageChrome, + pageViewTelemetry, + searchApplicationName, + hasSchemaConflicts, + restrictWidth = true, + ...pageTemplateProps +}) => { + const alwaysReturnNavItems = true; + const navItems = useEnterpriseSearchApplicationNav( + searchApplicationName, + pageTemplateProps.isEmptyState, + hasSchemaConflicts, + alwaysReturnNavItems + ); + + const { renderHeaderActions, updateSideNavDefinition, getChromeStyle$ } = useValues(KibanaLogic); + const chromeStyle = useObservable(getChromeStyle$(), 'classic'); + + const getSelectedAppItems = useCallback( + ( + items?: Array> + ): Array> | undefined => { + if (!items) return undefined; + + const buildGroup = items.find((item) => item.id === 'build'); + if (!buildGroup || !buildGroup.items) return undefined; + + const searchAppsGroup = buildGroup.items.find((item) => item.id === 'searchApplications'); + + return searchAppsGroup?.items; + }, + [] + ); + + useLayoutEffect(() => { + renderHeaderActions(PlaygroundHeaderDocsAction); + + return () => { + renderHeaderActions(); + }; + }, []); + + useEffect(() => { + // We update the new side nav definition with the selected app items + updateSideNavDefinition({ searchApps: getSelectedAppItems(navItems) }); + }, [navItems, getSelectedAppItems, updateSideNavDefinition]); + + useEffect(() => { + return () => { + updateSideNavDefinition({ searchApps: undefined }); + }; + }, [updateSideNavDefinition]); + + return ( + } + useEndpointHeaderActions={false} + > + {pageViewTelemetry && ( + + )} + {children} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/applications/components/playground/playground.tsx b/x-pack/plugins/enterprise_search/public/applications/applications/components/playground/playground.tsx index b117518d3a6e0..e8e72e5dfb37a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/applications/components/playground/playground.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/applications/components/playground/playground.tsx @@ -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 ( - { panelled={false} customPageSections bottomBorder="extended" - docLink="playground" > - + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/applications/constants.ts b/x-pack/plugins/enterprise_search/public/applications/applications/constants.ts new file mode 100644 index 0000000000000..05a8eb0a612e8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/applications/constants.ts @@ -0,0 +1,15 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const SEARCH_APPS_BREADCRUMB = i18n.translate( + 'xpack.enterpriseSearch.searchApplications.breadcrumb', + { + defaultMessage: 'Search Applications', + } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts index 68e9a17f9f504..00e3df344b2ae 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts @@ -166,7 +166,7 @@ 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]); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts index eaeb30f1540d0..df7d16cddc4d4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_title.ts @@ -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', + }), + ]); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx index 8f7c71d1309c0..f8e342c34b2ef 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx @@ -9,8 +9,7 @@ import React, { useEffect } from 'react'; import { useValues } from 'kea'; -import { APPLICATIONS_PLUGIN } from '../../../../common/constants'; - +import { SEARCH_APPS_BREADCRUMB } from '../../applications/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 = ({ trail = [ return null; }; +export const SetSearchPlaygroundChrome: React.FC = ({ 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 = ({ 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(() => { From 3dc20ba2ea14090fc108a30a583febce5e5656ec Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Wed, 23 Oct 2024 19:48:33 +0000 Subject: [PATCH 10/23] fix(search): add getting started breadcrumb --- .../enterprise_search/common/constants.ts | 4 ++++ .../kibana_chrome/generate_breadcrumbs.ts | 19 ++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index eabefc0db45e5..6e6299767c951 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -345,3 +345,7 @@ 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', +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts index 00e3df344b2ae..189ca53e362e1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts @@ -23,6 +23,7 @@ import { WORKPLACE_SEARCH_PLUGIN, SEMANTIC_SEARCH_PLUGIN, APPLICATIONS_PLUGIN, + GETTING_STARTED_TITLE, } from '../../../../common/constants'; import { stripLeadingSlash } from '../../../../common/strip_slashes'; @@ -169,10 +170,22 @@ export const useEnterpriseSearchApplicationsBreadcrumbs = (breadcrumbs: Breadcru 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, + ]); From 19c8e47dc233409ce5a4f03d993265bcba4c8f50 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Wed, 23 Oct 2024 19:49:26 +0000 Subject: [PATCH 11/23] fix(search): add getting started elasticsearch breadcrumbs --- .../components/elasticsearch_guide/elasticsearch_guide.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/elasticsearch/components/elasticsearch_guide/elasticsearch_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/elasticsearch/components/elasticsearch_guide/elasticsearch_guide.tsx index 80a8de9acdc21..be470577cd519 100644 --- a/x-pack/plugins/enterprise_search/public/applications/elasticsearch/components/elasticsearch_guide/elasticsearch_guide.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/elasticsearch/components/elasticsearch_guide/elasticsearch_guide.tsx @@ -40,7 +40,7 @@ export const ElasticsearchGuide = () => { }, []); return ( - + {isFlyoutOpen && setIsFlyoutOpen(false)} />}

From e3b1f70bfde76e9672f23a317672ba9afa5c13db Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Wed, 23 Oct 2024 19:50:02 +0000 Subject: [PATCH 12/23] fix playground page template --- .../components/playground/page_template.tsx | 43 ++----------------- 1 file changed, 4 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/applications/components/playground/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/applications/components/playground/page_template.tsx index 4ee7f9cec1cd0..40698b273730b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/applications/components/playground/page_template.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/applications/components/playground/page_template.tsx @@ -5,19 +5,17 @@ * 2.0. */ -import React, { useCallback, useEffect, useLayoutEffect } from 'react'; +import React, { useLayoutEffect } from 'react'; import { useValues } from 'kea'; import useObservable from 'react-use/lib/useObservable'; -import type { EuiSideNavItemTypeEnhanced } from '@kbn/core-chrome-browser'; - 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 { useEnterpriseSearchApplicationNav } from '../../../shared/layout'; +import { useEnterpriseSearchNav } from '../../../shared/layout'; import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; import { PlaygroundHeaderDocsAction } from './header_docs_action'; @@ -40,33 +38,11 @@ export const SearchPlaygroundPageTemplate: React.FC { - const alwaysReturnNavItems = true; - const navItems = useEnterpriseSearchApplicationNav( - searchApplicationName, - pageTemplateProps.isEmptyState, - hasSchemaConflicts, - alwaysReturnNavItems - ); + const navItems = useEnterpriseSearchNav(); - const { renderHeaderActions, updateSideNavDefinition, getChromeStyle$ } = useValues(KibanaLogic); + const { renderHeaderActions, getChromeStyle$ } = useValues(KibanaLogic); const chromeStyle = useObservable(getChromeStyle$(), 'classic'); - const getSelectedAppItems = useCallback( - ( - items?: Array> - ): Array> | undefined => { - if (!items) return undefined; - - const buildGroup = items.find((item) => item.id === 'build'); - if (!buildGroup || !buildGroup.items) return undefined; - - const searchAppsGroup = buildGroup.items.find((item) => item.id === 'searchApplications'); - - return searchAppsGroup?.items; - }, - [] - ); - useLayoutEffect(() => { renderHeaderActions(PlaygroundHeaderDocsAction); @@ -75,17 +51,6 @@ export const SearchPlaygroundPageTemplate: React.FC { - // We update the new side nav definition with the selected app items - updateSideNavDefinition({ searchApps: getSelectedAppItems(navItems) }); - }, [navItems, getSelectedAppItems, updateSideNavDefinition]); - - useEffect(() => { - return () => { - updateSideNavDefinition({ searchApps: undefined }); - }; - }, [updateSideNavDefinition]); - return ( Date: Wed, 23 Oct 2024 19:50:28 +0000 Subject: [PATCH 13/23] fix nav item usage for elasticsearch guide --- .../elasticsearch/components/layout/page_template.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/elasticsearch/components/layout/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/elasticsearch/components/layout/page_template.tsx index 7f2eded8a6565..c5c777cb74773 100644 --- a/x-pack/plugins/enterprise_search/public/applications/elasticsearch/components/layout/page_template.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/elasticsearch/components/layout/page_template.tsx @@ -19,13 +19,14 @@ export const EnterpriseSearchElasticsearchPageTemplate: React.FC { + const navItems = useEnterpriseSearchNav(); return ( } > From 57f52bc392029d194910b1bc465a4d19a8229cf4 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Wed, 23 Oct 2024 19:51:08 +0000 Subject: [PATCH 14/23] fix(search): ensure nav items dont retain deepLink property --- .../applications/shared/layout/base_nav.tsx | 6 ++--- .../public/applications/shared/layout/nav.tsx | 24 +++++++------------ .../public/applications/shared/types.ts | 14 ++--------- 3 files changed, 14 insertions(+), 30 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx index 94f06029ce47d..771f3fdcbf467 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx @@ -18,6 +18,8 @@ import { } from '@kbn/deeplinks-search'; import { i18n } from '@kbn/i18n'; +import { GETTING_STARTED_TITLE } from '../../../../common/constants'; + import { ClassicNavItem, BuildClassicNavParameters } from '../types'; export const buildBaseClassicNavItems = ({ @@ -166,9 +168,7 @@ export const buildBaseClassicNavItems = ({ id: 'aiSearch', }, ], - name: i18n.translate('xpack.enterpriseSearch.nav.enterpriseSearchOverviewTitle', { - defaultMessage: 'Getting started', - }), + name: GETTING_STARTED_TITLE, }); if (productAccess.hasAppSearchAccess || productAccess.hasWorkplaceSearchAccess) { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx index bad70dfdb39ef..508d5ea808d0a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx @@ -21,7 +21,6 @@ import { KibanaLogic } from '../kibana'; import { LicensingLogic } from '../licensing'; import { ClassicNavItem, - DeepLinkNavItem, GenerateNavLinkParameters, GenerateNavLinkFromDeepLinkParameters, } from '../types'; @@ -42,17 +41,15 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { const indicesNavItems = useIndicesNav(); - const deepLinks = useMemo(() => { - return getNavLinks().reduce((links, link) => { + const navItems: Array> = useMemo(() => { + const baseNavItems = buildBaseClassicNavItems({ hasEnterpriseLicense, productAccess }); + const deepLinks = getNavLinks().reduce((links, link) => { links[link.id] = link; return links; }, {} as Record); - }, []); - const navItems: Array> = useMemo(() => { - const baseNavItems = buildBaseClassicNavItems({ hasEnterpriseLicense, productAccess }); return generateSideNavItems(baseNavItems, deepLinks, { search_indices: indicesNavItems }); - }, [hasEnterpriseLicense, productAccess, indicesNavItems, deepLinks]); + }, [hasEnterpriseLicense, productAccess, indicesNavItems]); if (!isSidebarEnabled && !alwaysReturn) return undefined; @@ -229,7 +226,7 @@ export const generateSideNavItems = ( for (const navItem of navItems) { let sideNavChildItems: Array> | undefined; - const { items, ...rest } = navItem; + const { deepLink, items, ...rest } = navItem; const subItems = subItemsMap?.[navItem.id]; if (items || subItems) { @@ -241,15 +238,15 @@ export const generateSideNavItems = ( sideNavChildItems.push(...subItems); } } + let sideNavItem: EuiSideNavItemTypeEnhanced | undefined; - if (isDeepLinkNavItem(navItem)) { - const deepLink = navItem.deepLink; + if (deepLink) { const navLinkParams = getNavLinkParameters(deepLink, deepLinks); if (navLinkParams !== undefined) { - const name = getDeepLinkTitle(deepLink.link, deepLinks); + const name = navItem.name ?? getDeepLinkTitle(deepLink.link, deepLinks); sideNavItem = { - name, ...rest, + name, ...generateNavLink({ ...navLinkParams, items: sideNavChildItems, @@ -272,9 +269,6 @@ export const generateSideNavItems = ( return sideNavItems; }; -function isDeepLinkNavItem(item: ClassicNavItem): item is DeepLinkNavItem { - return 'deepLink' in item; -} const getNavLinkParameters = ( navLink: GenerateNavLinkFromDeepLinkParameters, deepLinks: Record diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts index 034f13b1797ea..ac6ba0157cd6a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts @@ -89,19 +89,9 @@ export interface BuildClassicNavParameters { productAccess: ProductAccess; } -export type ClassicNavItem = BaseClassicNavItem | DeepLinkNavItem; - -export interface BaseClassicNavItem { - 'data-test-subj'?: string; - iconToString?: string; - id: string; - items?: ClassicNavItem[]; - name: ReactNode; -} - -export interface DeepLinkNavItem { +export interface ClassicNavItem { 'data-test-subj'?: string; - deepLink: GenerateNavLinkFromDeepLinkParameters; + deepLink?: GenerateNavLinkFromDeepLinkParameters; iconToString?: string; id: string; items?: ClassicNavItem[]; From 2876683748356ea3c86ff4d5f0f7136a99e162c1 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Wed, 23 Oct 2024 19:52:05 +0000 Subject: [PATCH 15/23] test: expand classic navigation tests --- x-pack/test/functional_search/config.ts | 4 +- x-pack/test/functional_search/index.ts | 2 +- .../tests/classic_navigation.ts | 68 ++++++++++++++++++- 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/x-pack/test/functional_search/config.ts b/x-pack/test/functional_search/config.ts index 493dce6dbcfdf..c7708363766b0 100644 --- a/x-pack/test/functional_search/config.ts +++ b/x-pack/test/functional_search/config.ts @@ -22,9 +22,9 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { }, testFiles: [require.resolve('.')], esTestCluster: { - ...functionalTestsConfig.get('esTestCluster'), + ...functionalConfig.get('esTestCluster'), serverArgs: [ - ...functionalTestsConfig.get('esTestCluster.serverArgs'), + ...functionalConfig.get('esTestCluster.serverArgs'), 'xpack.security.enabled=true', ], }, diff --git a/x-pack/test/functional_search/index.ts b/x-pack/test/functional_search/index.ts index c73488fed8a7a..d48bd1d695d16 100644 --- a/x-pack/test/functional_search/index.ts +++ b/x-pack/test/functional_search/index.ts @@ -11,6 +11,6 @@ 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')); + loadTestFile(require.resolve('./tests/solution_navigation')); }); }; diff --git a/x-pack/test/functional_search/tests/classic_navigation.ts b/x-pack/test/functional_search/tests/classic_navigation.ts index ad2fcd8b9c809..0fe5fefa51f51 100644 --- a/x-pack/test/functional_search/tests/classic_navigation.ts +++ b/x-pack/test/functional_search/tests/classic_navigation.ts @@ -47,8 +47,8 @@ export default function searchSolutionNavigation({ { id: 'Playground', label: 'Playground' }, { id: 'SearchApplications', label: 'Search Applications' }, { id: 'BehavioralAnalytics', label: 'Behavioral Analytics' }, - { id: 'Relevance', label: 'Relevance' }, - { id: 'InferenceEndpoints', label: 'Inference Endpoints' }, + // { id: 'Relevance', label: 'Relevance' }, + // { id: 'InferenceEndpoints', label: 'Inference Endpoints' }, { id: 'GettingStarted', label: 'Getting started' }, { id: 'Elasticsearch', label: 'Elasticsearch' }, { id: 'VectorSearch', label: 'Vector Search' }, @@ -61,6 +61,70 @@ export default function searchSolutionNavigation({ 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(); }); }); From e909efe7cf79b0c1652f1bcbf223f8f0a5cb4e61 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Wed, 23 Oct 2024 20:02:44 +0000 Subject: [PATCH 16/23] rely on navlink for license check --- .../applications/shared/layout/base_nav.tsx | 36 +++++++++---------- .../public/applications/shared/layout/nav.tsx | 7 ++-- .../public/applications/shared/types.ts | 1 - .../tests/classic_navigation.ts | 12 +++---- 4 files changed, 24 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx index 771f3fdcbf467..a2532082385c5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx @@ -23,7 +23,6 @@ import { GETTING_STARTED_TITLE } from '../../../../common/constants'; import { ClassicNavItem, BuildClassicNavParameters } from '../types'; export const buildBaseClassicNavItems = ({ - hasEnterpriseLicense, productAccess, }: BuildClassicNavParameters): ClassicNavItem[] => { const navItems: ClassicNavItem[] = []; @@ -113,26 +112,23 @@ export const buildBaseClassicNavItems = ({ }), }); - if (hasEnterpriseLicense) { - // Relevance - navItems.push({ - 'data-test-subj': 'searchSideNav-Relevance', - id: 'relevance', - items: [ - { - 'data-test-subj': 'searchSideNav-InferenceEndpoints', - deepLink: { - link: 'enterpriseSearchRelevance:inferenceEndpoints', - shouldShowActiveForSubroutes: true, - }, - id: 'inference_endpoints', + navItems.push({ + 'data-test-subj': 'searchSideNav-Relevance', + id: 'relevance', + items: [ + { + 'data-test-subj': 'searchSideNav-InferenceEndpoints', + deepLink: { + link: 'searchInferenceEndpoints:inferenceEndpoints', + shouldShowActiveForSubroutes: true, }, - ], - name: i18n.translate('xpack.enterpriseSearch.nav.relevanceTitle', { - defaultMessage: 'Relevance', - }), - }); - } + id: 'inference_endpoints', + }, + ], + name: i18n.translate('xpack.enterpriseSearch.nav.relevanceTitle', { + defaultMessage: 'Relevance', + }), + }); // Getting Started navItems.push({ diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx index 508d5ea808d0a..a8f42838785d6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx @@ -18,7 +18,6 @@ import { SEARCH_APPLICATIONS_PATH, SearchApplicationViewTabs } from '../../appli import { useIndicesNav } from '../../enterprise_search_content/components/search_index/indices/indices_nav'; import { KibanaLogic } from '../kibana'; -import { LicensingLogic } from '../licensing'; import { ClassicNavItem, GenerateNavLinkParameters, @@ -37,19 +36,17 @@ import { generateNavLink } from './nav_link_helpers'; export const useEnterpriseSearchNav = (alwaysReturn = false) => { const { isSidebarEnabled, productAccess, getNavLinks } = useValues(KibanaLogic); - const { hasEnterpriseLicense } = useValues(LicensingLogic); - const indicesNavItems = useIndicesNav(); const navItems: Array> = useMemo(() => { - const baseNavItems = buildBaseClassicNavItems({ hasEnterpriseLicense, productAccess }); + const baseNavItems = buildBaseClassicNavItems({ productAccess }); const deepLinks = getNavLinks().reduce((links, link) => { links[link.id] = link; return links; }, {} as Record); return generateSideNavItems(baseNavItems, deepLinks, { search_indices: indicesNavItems }); - }, [hasEnterpriseLicense, productAccess, indicesNavItems]); + }, [productAccess, indicesNavItems]); if (!isSidebarEnabled && !alwaysReturn) return undefined; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts index ac6ba0157cd6a..095f1dddfcc4a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts @@ -85,7 +85,6 @@ export interface GenerateNavLinkFromDeepLinkParameters { } export interface BuildClassicNavParameters { - hasEnterpriseLicense: boolean; productAccess: ProductAccess; } diff --git a/x-pack/test/functional_search/tests/classic_navigation.ts b/x-pack/test/functional_search/tests/classic_navigation.ts index 0fe5fefa51f51..7ec78394dae74 100644 --- a/x-pack/test/functional_search/tests/classic_navigation.ts +++ b/x-pack/test/functional_search/tests/classic_navigation.ts @@ -47,8 +47,8 @@ export default function searchSolutionNavigation({ { id: 'Playground', label: 'Playground' }, { id: 'SearchApplications', label: 'Search Applications' }, { id: 'BehavioralAnalytics', label: 'Behavioral Analytics' }, - // { id: 'Relevance', label: 'Relevance' }, - // { id: 'InferenceEndpoints', label: 'Inference Endpoints' }, + { id: 'Relevance', label: 'Relevance' }, + { id: 'InferenceEndpoints', label: 'Inference Endpoints' }, { id: 'GettingStarted', label: 'Getting started' }, { id: 'Elasticsearch', label: 'Elasticsearch' }, { id: 'VectorSearch', label: 'Vector Search' }, @@ -97,10 +97,10 @@ export default function searchSolutionNavigation({ // Check Relevance // > InferenceEndpoints - // await searchClassicNavigation.clickNavItem('InferenceEndpoints'); - // await searchClassicNavigation.expectNavItemActive('InferenceEndpoints'); - // await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Relevance'); - // await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Inference Endpoints'); + await searchClassicNavigation.clickNavItem('InferenceEndpoints'); + await searchClassicNavigation.expectNavItemActive('InferenceEndpoints'); + await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Relevance'); + await searchClassicNavigation.breadcrumbs.expectBreadcrumbExists('Inference Endpoints'); // Check Getting started // > Elasticsearch From fc0a455756aac0b3f78d430b71ba9f3f71149b97 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Wed, 23 Oct 2024 21:30:33 +0000 Subject: [PATCH 17/23] refactor: move breadcrumb constant to common file --- .../plugins/enterprise_search/common/constants.ts | 7 +++++++ .../public/applications/applications/constants.ts | 15 --------------- .../shared/kibana_chrome/set_chrome.tsx | 2 +- 3 files changed, 8 insertions(+), 16 deletions(-) delete mode 100644 x-pack/plugins/enterprise_search/public/applications/applications/constants.ts diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index 6e6299767c951..21bf85b32640d 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -349,3 +349,10 @@ 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', + } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/applications/constants.ts b/x-pack/plugins/enterprise_search/public/applications/applications/constants.ts deleted file mode 100644 index 05a8eb0a612e8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/applications/constants.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const SEARCH_APPS_BREADCRUMB = i18n.translate( - 'xpack.enterpriseSearch.searchApplications.breadcrumb', - { - defaultMessage: 'Search Applications', - } -); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx index f8e342c34b2ef..0c05cb0c02ca0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx @@ -9,7 +9,7 @@ import React, { useEffect } from 'react'; import { useValues } from 'kea'; -import { SEARCH_APPS_BREADCRUMB } from '../../applications/constants'; +import { SEARCH_APPS_BREADCRUMB } from '../../../../common/constants'; import { KibanaLogic } from '../kibana'; import { From aa5e39699c2ee42489346ac346465b3968181daf Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Thu, 24 Oct 2024 15:39:50 +0000 Subject: [PATCH 18/23] refactor: move util functions to helpers file --- .../shared/layout/classic_nav_helpers.ts | 102 ++++++++++++++++++ .../public/applications/shared/layout/nav.tsx | 92 +--------------- .../shared/layout/nav_link_helpers.ts | 6 +- 3 files changed, 107 insertions(+), 93 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.ts b/x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.ts new file mode 100644 index 0000000000000..89f3c2ab5b59a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.ts @@ -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, + subItemsMap: Record> | undefined> = {} +): Array> => { + const sideNavItems: Array> = []; + + for (const navItem of navItems) { + let sideNavChildItems: Array> | 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 | 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 +): 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 | undefined => { + const deepLink = deepLinks[link]; + if (!deepLink || !deepLink.url) return undefined; + return deepLink.title; +}; + +function isValidSideNavItem( + item: EuiSideNavItemTypeEnhanced | undefined +): item is EuiSideNavItemTypeEnhanced { + if (item === undefined) return false; + if (item.href || item.onClick) return true; + if (item?.items?.length ?? 0 > 0) return true; + + return false; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx index a8f42838785d6..8f83b6c73402e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx @@ -18,13 +18,9 @@ import { SEARCH_APPLICATIONS_PATH, SearchApplicationViewTabs } from '../../appli import { useIndicesNav } from '../../enterprise_search_content/components/search_index/indices/indices_nav'; import { KibanaLogic } from '../kibana'; -import { - ClassicNavItem, - GenerateNavLinkParameters, - GenerateNavLinkFromDeepLinkParameters, -} from '../types'; import { buildBaseClassicNavItems } from './base_nav'; +import { generateSideNavItems } from './classic_nav_helpers'; import { generateNavLink } from './nav_link_helpers'; /** @@ -212,89 +208,3 @@ export const useEnterpriseSearchAnalyticsNav = ( return navItems; }; - -export const generateSideNavItems = ( - navItems: ClassicNavItem[], - deepLinks: Record, - subItemsMap: Record> | undefined> = {} -): Array> => { - const sideNavItems: Array> = []; - - for (const navItem of navItems) { - let sideNavChildItems: Array> | 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 | 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 -): 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 | undefined => { - const deepLink = deepLinks[link]; - if (!deepLink || !deepLink.url) return undefined; - return deepLink.title; -}; - -function isValidSideNavItem( - item: EuiSideNavItemTypeEnhanced | undefined -): item is EuiSideNavItemTypeEnhanced { - if (item === undefined) return false; - if (item.href || item.onClick) return true; - if (item?.items?.length ?? 0 > 0) return true; - - return false; -} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav_link_helpers.ts b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav_link_helpers.ts index b2cf342e8cb93..36000307adcc3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav_link_helpers.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav_link_helpers.ts @@ -10,8 +10,10 @@ import { EuiSideNavItemTypeEnhanced } from '@kbn/core-chrome-browser'; import { stripTrailingSlash } from '../../../../common/strip_slashes'; import { KibanaLogic } from '../kibana'; -import { generateReactRouterProps } 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 { From e8d6344101f5eb21f2b9a034ceb49fca5bf5f193 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Thu, 24 Oct 2024 18:33:38 +0000 Subject: [PATCH 19/23] test: update nav tests with nav link titles --- .../applications/shared/layout/nav.test.tsx | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx index 2846ac9a63dd9..3305e92dd8d9e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx @@ -174,40 +174,69 @@ const mockNavLinks = [ }, { 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: 'enterpriseSearchRelevance:inferenceEndpoints', + 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 = { From 17b3d9d18e7d98fb148383045b55abb711dcfb88 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Thu, 24 Oct 2024 18:34:06 +0000 Subject: [PATCH 20/23] chore: fix linting issue with id location --- .../public/applications/shared/layout/base_nav.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx index a2532082385c5..b971ab6deff53 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/base_nav.tsx @@ -51,11 +51,11 @@ export const buildBaseClassicNavItems = ({ items: [ { 'data-test-subj': 'searchSideNav-Indices', - id: 'search_indices', deepLink: { link: 'enterpriseSearchContent:searchIndices', shouldShowActiveForSubroutes: true, }, + id: 'search_indices', }, { 'data-test-subj': 'searchSideNav-Connectors', From da27deda7f2641725b224c6fd0ae3b311d3c703a Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Thu, 24 Oct 2024 18:57:01 +0000 Subject: [PATCH 21/23] test: classic nav item generation --- .../shared/layout/classic_nav_helpers.test.ts | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.test.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.test.ts new file mode 100644 index 0000000000000..05ef2f5b552a5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.test.ts @@ -0,0 +1,186 @@ +/* + * 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 '../../__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', + }, + }; + 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', + }, + ]); + }); +}); From 08634d26a72e873f5ead1cdebb9a7c0e48da6a39 Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Thu, 24 Oct 2024 20:44:01 +0000 Subject: [PATCH 22/23] fix: update translations --- x-pack/plugins/translations/translations/fr-FR.json | 12 ------------ x-pack/plugins/translations/translations/ja-JP.json | 12 ------------ x-pack/plugins/translations/translations/zh-CN.json | 12 ------------ 3 files changed, 36 deletions(-) diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index dc00787a579c6..afb5db9b454c4 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -17361,27 +17361,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", @@ -17393,7 +17382,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", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8345a6b36ae8f..15b780790b99a 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17107,27 +17107,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": "関連性", "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": "スケジュール", @@ -17139,7 +17128,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": "コネクター", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 38fa5dab8a44a..f11d6d3d7278b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17136,27 +17136,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": "正在计划", @@ -17168,7 +17157,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": "连接器", From 3b462d70439a14a77429d47d4befdf760dd3d0cd Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Thu, 24 Oct 2024 22:29:31 +0000 Subject: [PATCH 23/23] fix: type errors --- .../applications/shared/layout/classic_nav_helpers.test.ts | 5 ++++- .../public/applications/shared/react_router_helpers/index.ts | 1 - .../applications/test_helpers/test_utils.test_helper.tsx | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.test.ts index 05ef2f5b552a5..514072ba297aa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/classic_nav_helpers.test.ts @@ -6,6 +6,9 @@ */ 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', () => ({ @@ -38,7 +41,7 @@ describe('generateSideNavItems', () => { title: 'Web crawlers', url: '/app/enterprise_search/content/crawlers', }, - }; + } as unknown as Record; beforeEach(() => { jest.clearAllMocks(); mockKibanaValues.history.location.pathname = '/'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts index ded9310fe361a..237e0d342ed1f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts @@ -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, diff --git a/x-pack/plugins/enterprise_search/public/applications/test_helpers/test_utils.test_helper.tsx b/x-pack/plugins/enterprise_search/public/applications/test_helpers/test_utils.test_helper.tsx index d1729a50909ed..da30e6e93fadb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/test_helpers/test_utils.test_helper.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/test_helpers/test_utils.test_helper.tsx @@ -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: () => {