From ad56ec5f1a838486d9b38c2d5aa92bbf77e127a3 Mon Sep 17 00:00:00 2001 From: Gerard Soldevila Date: Thu, 14 Nov 2024 12:49:21 +0100 Subject: [PATCH] Sustainable Kibana Architecture: Remove dependencies between plugins that are related by _App Links_ (#199492) ## Summary This PR introduces a Core API to check whether a given application has been registered. Plugins can use this for their _App Links_, without having to depend on the referenced plugin(s) anymore. This way, we can get rid of some inter-solution dependencies, and categorise plugins more appropriately. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../src/application_service.test.ts | 30 +- .../src/application_service.tsx | 3 + .../src/application_service.mock.ts | 2 + .../core-application-browser/src/contracts.ts | 15 +- .../src/plugin_context.ts | 1 + .../public/__mocks__/start_contract.ts | 1 + .../saved_objects_table.test.tsx.snap | 1 + x-pack/plugins/enterprise_search/kibana.jsonc | 8 +- .../enterprise_search/public/plugin.ts | 4 +- .../fleet/.storybook/context/application.ts | 1 + .../kibana.jsonc | 1 - .../public/plugin.ts | 10 +- .../components/search_connector_tab.tsx | 2 +- .../routes/components/settings_page.tsx | 5 +- .../tsconfig.json | 1 - .../search_inference_endpoints/kibana.jsonc | 7 +- x-pack/plugins/search_playground/kibana.jsonc | 7 +- x-pack/plugins/serverless_search/kibana.jsonc | 1 - .../public/navigation_tree.ts | 331 +++++++++--------- .../serverless_search/public/plugin.ts | 2 +- .../plugins/serverless_search/public/types.ts | 2 - .../plugins/serverless_search/tsconfig.json | 2 +- .../.storybook/context/application.tsx | 1 + 23 files changed, 242 insertions(+), 196 deletions(-) diff --git a/packages/core/application/core-application-browser-internal/src/application_service.test.ts b/packages/core/application/core-application-browser-internal/src/application_service.test.ts index 8cd9a61923240..13b2317605ad0 100644 --- a/packages/core/application/core-application-browser-internal/src/application_service.test.ts +++ b/packages/core/application/core-application-browser-internal/src/application_service.test.ts @@ -26,14 +26,14 @@ import { themeServiceMock } from '@kbn/core-theme-browser-mocks'; import { overlayServiceMock } from '@kbn/core-overlays-browser-mocks'; import { customBrandingServiceMock } from '@kbn/core-custom-branding-browser-mocks'; import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks'; -import { MockLifecycle } from './test_helpers/test_types'; +import type { MockLifecycle } from './test_helpers/test_types'; import { ApplicationService } from './application_service'; import { - App, - AppDeepLink, + type App, + type AppDeepLink, AppStatus, - AppUpdater, - PublicAppInfo, + type AppUpdater, + type PublicAppInfo, } from '@kbn/core-application-browser'; import { act } from 'react-dom/test-utils'; import { DEFAULT_APP_VISIBILITY } from './utils'; @@ -618,6 +618,26 @@ describe('#start()', () => { }); }); + describe('isAppRegistered', () => { + let isAppRegistered: any; + beforeEach(async () => { + const { register } = service.setup(setupDeps); + register(Symbol(), createApp({ id: 'one_app' })); + register(Symbol(), createApp({ id: 'another_app', appRoute: '/custom/path' })); + + const start = await service.start(startDeps); + isAppRegistered = start.isAppRegistered; + }); + + it('returns false for unregistered apps', () => { + expect(isAppRegistered('oneApp')).toEqual(false); + }); + + it('returns true for registered apps', () => { + expect(isAppRegistered('another_app')).toEqual(true); + }); + }); + describe('getUrlForApp', () => { it('creates URL for unregistered appId', async () => { service.setup(setupDeps); diff --git a/packages/core/application/core-application-browser-internal/src/application_service.tsx b/packages/core/application/core-application-browser-internal/src/application_service.tsx index 8c4bf16f6dff6..678b74f95265e 100644 --- a/packages/core/application/core-application-browser-internal/src/application_service.tsx +++ b/packages/core/application/core-application-browser-internal/src/application_service.tsx @@ -327,6 +327,9 @@ export class ApplicationService { takeUntil(this.stop$) ), history: this.history!, + isAppRegistered: (appId: string): boolean => { + return applications$.value.get(appId) !== undefined; + }, getUrlForApp: ( appId, { diff --git a/packages/core/application/core-application-browser-mocks/src/application_service.mock.ts b/packages/core/application/core-application-browser-mocks/src/application_service.mock.ts index fe50fe3733496..a0bc498de94c4 100644 --- a/packages/core/application/core-application-browser-mocks/src/application_service.mock.ts +++ b/packages/core/application/core-application-browser-mocks/src/application_service.mock.ts @@ -51,6 +51,7 @@ const createStartContractMock = (): jest.Mocked => { navigateToApp: jest.fn(), navigateToUrl: jest.fn(), getUrlForApp: jest.fn(), + isAppRegistered: jest.fn(), }; }; @@ -92,6 +93,7 @@ const createInternalStartContractMock = ( currentActionMenu$: new BehaviorSubject(undefined), getComponent: jest.fn(), getUrlForApp: jest.fn(), + isAppRegistered: jest.fn(), navigateToApp: jest.fn().mockImplementation((appId) => currentAppId$.next(appId)), navigateToUrl: jest.fn(), history: createHistoryMock(), diff --git a/packages/core/application/core-application-browser/src/contracts.ts b/packages/core/application/core-application-browser/src/contracts.ts index 9f10bb1206c97..e8b2cd77028f6 100644 --- a/packages/core/application/core-application-browser/src/contracts.ts +++ b/packages/core/application/core-application-browser/src/contracts.ts @@ -68,9 +68,12 @@ export interface ApplicationStart { applications$: Observable>; /** - * Navigate to a given app + * Navigate to a given app. + * If a plugin is disabled any applications it registers won't be available either. + * Before rendering a UI element that a user could use to navigate to another application, + * first check if the destination application is actually available using the isAppRegistered API. * - * @param appId + * @param appId - The identifier of the app to navigate to * @param options - navigation options */ navigateToApp(appId: string, options?: NavigateToAppOptions): Promise; @@ -114,6 +117,14 @@ export interface ApplicationStart { */ navigateToUrl(url: string, options?: NavigateToUrlOptions): Promise; + /** + * Checks whether a given application is registered. + * + * @param appId - The identifier of the app to check + * @returns true if the given appId is registered in the system, false otherwise. + */ + isAppRegistered(appId: string): boolean; + /** * Returns the absolute path (or URL) to a given app, including the global base path. * diff --git a/packages/core/plugins/core-plugins-browser-internal/src/plugin_context.ts b/packages/core/plugins/core-plugins-browser-internal/src/plugin_context.ts index b78e5cec0b276..cdd00d9996a40 100644 --- a/packages/core/plugins/core-plugins-browser-internal/src/plugin_context.ts +++ b/packages/core/plugins/core-plugins-browser-internal/src/plugin_context.ts @@ -143,6 +143,7 @@ export function createPluginStartContext< navigateToApp: deps.application.navigateToApp, navigateToUrl: deps.application.navigateToUrl, getUrlForApp: deps.application.getUrlForApp, + isAppRegistered: deps.application.isAppRegistered, currentLocation$: deps.application.currentLocation$, }, customBranding: deps.customBranding, diff --git a/src/plugins/discover/public/__mocks__/start_contract.ts b/src/plugins/discover/public/__mocks__/start_contract.ts index a9436797d616d..b7af207b2d20c 100644 --- a/src/plugins/discover/public/__mocks__/start_contract.ts +++ b/src/plugins/discover/public/__mocks__/start_contract.ts @@ -32,6 +32,7 @@ export const createStartContractMock = (): jest.Mocked => { capabilities, navigateToApp: jest.fn(), navigateToUrl: jest.fn(), + isAppRegistered: jest.fn(), getUrlForApp: jest.fn(), }; }; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap index c0035886e6b99..8b0a6f76f4624 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap @@ -156,6 +156,7 @@ exports[`SavedObjectsTable should render normally 1`] = ` }, }, "getUrlForApp": [MockFunction], + "isAppRegistered": [MockFunction], "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], }, diff --git a/x-pack/plugins/enterprise_search/kibana.jsonc b/x-pack/plugins/enterprise_search/kibana.jsonc index 14a36c85c6c87..e284ae1862144 100644 --- a/x-pack/plugins/enterprise_search/kibana.jsonc +++ b/x-pack/plugins/enterprise_search/kibana.jsonc @@ -2,9 +2,11 @@ "type": "plugin", "id": "@kbn/enterprise-search-plugin", "owner": "@elastic/search-kibana", - // Could be categorised as Search in the future, but it currently needs to run in Observability too - "group": "platform", - "visibility": "shared", + // TODO this is currently used from Observability too, must be refactored before solution-specific builds + // see x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/search_connector_tab.tsx + // cc sphilipse + "group": "search", + "visibility": "private", "description": "Adds dashboards for discovering and managing Enterprise Search products.", "plugin": { "id": "enterpriseSearch", diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index 06f14ba3d7037..1eb3384d4f9e3 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -80,8 +80,8 @@ export type EnterpriseSearchPublicStart = ReturnType { navigateToApp: async (app: string) => { action(`Navigate to: ${app}`); }, + isAppRegistered: (appId: string) => true, getUrlForApp: (url: string) => url, capabilities: { catalogue: {}, diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_management/kibana.jsonc b/x-pack/plugins/observability_solution/observability_ai_assistant_management/kibana.jsonc index 6699252ed69ed..cda6fdf0192fa 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_management/kibana.jsonc +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_management/kibana.jsonc @@ -21,7 +21,6 @@ "optionalPlugins": [ "home", "serverless", - "enterpriseSearch" ], "requiredBundles": [ "kibanaReact", diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/plugin.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/plugin.ts index 88d007045052e..b7c6bb089663a 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/plugin.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/plugin.ts @@ -6,11 +6,10 @@ */ import { i18n } from '@kbn/i18n'; -import { CoreSetup, Plugin, PluginInitializerContext } from '@kbn/core/public'; -import { ManagementSetup } from '@kbn/management-plugin/public'; -import { HomePublicPluginSetup } from '@kbn/home-plugin/public'; -import { ServerlessPluginStart } from '@kbn/serverless/public'; -import { EnterpriseSearchPublicStart } from '@kbn/enterprise-search-plugin/public'; +import type { CoreSetup, Plugin, PluginInitializerContext } from '@kbn/core/public'; +import type { ManagementSetup } from '@kbn/management-plugin/public'; +import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; +import type { ServerlessPluginStart } from '@kbn/serverless/public'; import type { ObservabilityAIAssistantPublicSetup, @@ -32,7 +31,6 @@ export interface SetupDependencies { export interface StartDependencies { observabilityAIAssistant: ObservabilityAIAssistantPublicStart; serverless?: ServerlessPluginStart; - enterpriseSearch?: EnterpriseSearchPublicStart; } export interface ConfigSchema { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/search_connector_tab.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/search_connector_tab.tsx index d8f6032985f4b..336176d40ab33 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/search_connector_tab.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/search_connector_tab.tsx @@ -16,7 +16,7 @@ export const SELECTED_CONNECTOR_LOCAL_STORAGE_KEY = export function SearchConnectorTab() { const { application } = useKibana().services; - const url = application.getUrlForApp('enterprise_search', { path: '/content/connectors' }); + const url = application.getUrlForApp('enterpriseSearch', { path: '/content/connectors' }); return ( <> diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_page.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_page.tsx index 57a167b1080fa..f9b750fcdc294 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_page.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_page.tsx @@ -22,9 +22,8 @@ export function SettingsPage() { const { setBreadcrumbs } = useAppContext(); const { services: { - application: { navigateToApp }, + application: { navigateToApp, isAppRegistered }, serverless, - enterpriseSearch, }, } = useKibana(); @@ -98,7 +97,7 @@ export function SettingsPage() { } ), content: , - disabled: enterpriseSearch == null, + disabled: !isAppRegistered('enterpriseSearch'), }, ]; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_management/tsconfig.json b/x-pack/plugins/observability_solution/observability_ai_assistant_management/tsconfig.json index f0ad230f6f1b3..bc5cf69357dce 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_management/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_management/tsconfig.json @@ -19,7 +19,6 @@ "@kbn/core-chrome-browser", "@kbn/observability-ai-assistant-plugin", "@kbn/serverless", - "@kbn/enterprise-search-plugin", "@kbn/management-settings-components-field-row", "@kbn/observability-shared-plugin", "@kbn/config-schema", diff --git a/x-pack/plugins/search_inference_endpoints/kibana.jsonc b/x-pack/plugins/search_inference_endpoints/kibana.jsonc index 7531316cd12e6..f535a9df27e99 100644 --- a/x-pack/plugins/search_inference_endpoints/kibana.jsonc +++ b/x-pack/plugins/search_inference_endpoints/kibana.jsonc @@ -2,8 +2,11 @@ "type": "plugin", "id": "@kbn/search-inference-endpoints", "owner": "@elastic/search-kibana", - "group": "platform", - "visibility": "shared", + // TODO enterpriseSearch depends on it, and Observability has a menu entry for enterpriseSearch + // must be refactored / fixed before solution-specific builds + // cc sphilipse + "group": "search", + "visibility": "private", "plugin": { "id": "searchInferenceEndpoints", "server": true, diff --git a/x-pack/plugins/search_playground/kibana.jsonc b/x-pack/plugins/search_playground/kibana.jsonc index 8b99add8587fa..37562347e9f37 100644 --- a/x-pack/plugins/search_playground/kibana.jsonc +++ b/x-pack/plugins/search_playground/kibana.jsonc @@ -2,9 +2,10 @@ "type": "plugin", "id": "@kbn/search-playground", "owner": "@elastic/search-kibana", - // @kbn/enterprise-search-plugin (platform) and @kbn/serverless-search (search) depend on it - "group": "platform", - "visibility": "shared", + // TODO @kbn/enterprise-search-plugin (platform) and @kbn/serverless-search (search) depend on it + // cc sphilipse + "group": "search", + "visibility": "private", "plugin": { "id": "searchPlayground", "server": true, diff --git a/x-pack/plugins/serverless_search/kibana.jsonc b/x-pack/plugins/serverless_search/kibana.jsonc index cae0a693846f1..f7b404edb37b1 100644 --- a/x-pack/plugins/serverless_search/kibana.jsonc +++ b/x-pack/plugins/serverless_search/kibana.jsonc @@ -35,7 +35,6 @@ "indexManagement", "searchConnectors", "searchInferenceEndpoints", - "searchPlayground", "usageCollection" ], "requiredBundles": ["kibanaReact"] diff --git a/x-pack/plugins/serverless_search/public/navigation_tree.ts b/x-pack/plugins/serverless_search/public/navigation_tree.ts index 066ab8e8c093e..ae8f41b8b17f8 100644 --- a/x-pack/plugins/serverless_search/public/navigation_tree.ts +++ b/x-pack/plugins/serverless_search/public/navigation_tree.ts @@ -5,172 +5,179 @@ * 2.0. */ -import type { NavigationTreeDefinition } from '@kbn/core-chrome-browser'; +import type { AppDeepLinkId, NavigationTreeDefinition } from '@kbn/core-chrome-browser'; +import type { ApplicationStart } from '@kbn/core-application-browser'; import { i18n } from '@kbn/i18n'; import { CONNECTORS_LABEL } from '../common/i18n_string'; -export const navigationTree = (): NavigationTreeDefinition => ({ - body: [ - { - type: 'navGroup', - id: 'search_project_nav', - title: 'Elasticsearch', - icon: 'logoElasticsearch', - defaultIsCollapsed: false, - isCollapsible: false, - breadcrumbStatus: 'hidden', - children: [ - { - id: 'data', - title: i18n.translate('xpack.serverlessSearch.nav.data', { - defaultMessage: 'Data', - }), - spaceBefore: 'm', - children: [ - { - title: i18n.translate('xpack.serverlessSearch.nav.content.indices', { - defaultMessage: 'Index Management', - }), - link: 'management:index_management', - breadcrumbStatus: - 'hidden' /* management sub-pages set their breadcrumbs themselves */, - getIsActive: ({ pathNameSerialized, prepend }) => { - return ( - pathNameSerialized.startsWith( - prepend('/app/management/data/index_management/') - ) || - pathNameSerialized.startsWith(prepend('/app/elasticsearch/indices')) || - pathNameSerialized.startsWith(prepend('/app/elasticsearch/start')) - ); +export const navigationTree = ({ isAppRegistered }: ApplicationStart): NavigationTreeDefinition => { + function isAvailable(appId: string, content: T): T[] { + return isAppRegistered(appId) ? [content] : []; + } + + return { + body: [ + { + type: 'navGroup', + id: 'search_project_nav', + title: 'Elasticsearch', + icon: 'logoElasticsearch', + defaultIsCollapsed: false, + isCollapsible: false, + breadcrumbStatus: 'hidden', + children: [ + { + id: 'data', + title: i18n.translate('xpack.serverlessSearch.nav.data', { + defaultMessage: 'Data', + }), + spaceBefore: 'm', + children: [ + { + title: i18n.translate('xpack.serverlessSearch.nav.content.indices', { + defaultMessage: 'Index Management', + }), + link: 'management:index_management', + breadcrumbStatus: + 'hidden' /* management sub-pages set their breadcrumbs themselves */, + getIsActive: ({ pathNameSerialized, prepend }) => { + return ( + pathNameSerialized.startsWith( + prepend('/app/management/data/index_management/') + ) || + pathNameSerialized.startsWith(prepend('/app/elasticsearch/indices')) || + pathNameSerialized.startsWith(prepend('/app/elasticsearch/start')) + ); + }, }, - }, - { - title: CONNECTORS_LABEL, - link: 'serverlessConnectors', - }, - ], - }, - { - id: 'build', - title: i18n.translate('xpack.serverlessSearch.nav.build', { - defaultMessage: 'Build', - }), - spaceBefore: 'm', - children: [ - { - id: 'dev_tools', - title: i18n.translate('xpack.serverlessSearch.nav.devTools', { - defaultMessage: 'Dev Tools', - }), - link: 'dev_tools', - getIsActive: ({ pathNameSerialized, prepend }) => { - return pathNameSerialized.startsWith(prepend('/app/dev_tools')); + { + title: CONNECTORS_LABEL, + link: 'serverlessConnectors', }, - }, - { - id: 'searchPlayground', - title: i18n.translate('xpack.serverlessSearch.nav.build.searchPlayground', { - defaultMessage: 'Playground', + ], + }, + { + id: 'build', + title: i18n.translate('xpack.serverlessSearch.nav.build', { + defaultMessage: 'Build', + }), + spaceBefore: 'm', + children: [ + { + id: 'dev_tools', + title: i18n.translate('xpack.serverlessSearch.nav.devTools', { + defaultMessage: 'Dev Tools', + }), + link: 'dev_tools', + getIsActive: ({ pathNameSerialized, prepend }) => { + return pathNameSerialized.startsWith(prepend('/app/dev_tools')); + }, + }, + ...isAvailable('searchPlayground', { + id: 'searchPlayground', + title: i18n.translate('xpack.serverlessSearch.nav.build.searchPlayground', { + defaultMessage: 'Playground', + }), + link: 'searchPlayground' as AppDeepLinkId, }), - link: 'searchPlayground', - }, - ], - }, - { - id: 'relevance', - title: i18n.translate('xpack.serverlessSearch.nav.relevance', { - defaultMessage: 'Relevance', - }), - spaceBefore: 'm', - children: [ - { - id: 'searchInferenceEndpoints', - title: i18n.translate( - 'xpack.serverlessSearch.nav.relevance.searchInferenceEndpoints', - { - defaultMessage: 'Inference Endpoints', - } - ), - link: 'searchInferenceEndpoints', - }, - ], - }, - { - id: 'analyze', - title: i18n.translate('xpack.serverlessSearch.nav.analyze', { - defaultMessage: 'Analyze', - }), - spaceBefore: 'm', - children: [ - { - link: 'discover', - }, - { - link: 'dashboards', - getIsActive: ({ pathNameSerialized, prepend }) => { - return pathNameSerialized.startsWith(prepend('/app/dashboards')); + ], + }, + { + id: 'relevance', + title: i18n.translate('xpack.serverlessSearch.nav.relevance', { + defaultMessage: 'Relevance', + }), + spaceBefore: 'm', + children: [ + { + id: 'searchInferenceEndpoints', + title: i18n.translate( + 'xpack.serverlessSearch.nav.relevance.searchInferenceEndpoints', + { + defaultMessage: 'Inference Endpoints', + } + ), + link: 'searchInferenceEndpoints', + }, + ], + }, + { + id: 'analyze', + title: i18n.translate('xpack.serverlessSearch.nav.analyze', { + defaultMessage: 'Analyze', + }), + spaceBefore: 'm', + children: [ + { + link: 'discover', + }, + { + link: 'dashboards', + getIsActive: ({ pathNameSerialized, prepend }) => { + return pathNameSerialized.startsWith(prepend('/app/dashboards')); + }, }, - }, - ], - }, - { - id: 'otherTools', - title: i18n.translate('xpack.serverlessSearch.nav.otherTools', { - defaultMessage: 'Other tools', - }), - spaceBefore: 'm', - children: [{ link: 'maps' }], - }, - ], - }, - ], - footer: [ - { - id: 'gettingStarted', - type: 'navItem', - title: i18n.translate('xpack.serverlessSearch.nav.gettingStarted', { - defaultMessage: 'Getting Started', - }), - link: 'serverlessElasticsearch', - icon: 'launch', - }, - { - type: 'navGroup', - id: 'project_settings_project_nav', - title: i18n.translate('xpack.serverlessSearch.nav.projectSettings', { - defaultMessage: 'Project settings', - }), - icon: 'gear', - breadcrumbStatus: 'hidden', - children: [ - { - link: 'ml:modelManagement', - title: i18n.translate('xpack.serverlessSearch.nav.trainedModels', { - defaultMessage: 'Trained models', - }), - }, - { - link: 'management', - title: i18n.translate('xpack.serverlessSearch.nav.mngt', { - defaultMessage: 'Management', - }), - }, - { - id: 'cloudLinkUserAndRoles', - cloudLink: 'userAndRoles', - }, - { - id: 'cloudLinkDeployment', - cloudLink: 'deployment', - title: i18n.translate('xpack.serverlessSearch.nav.performance', { - defaultMessage: 'Performance', - }), - }, - { - id: 'cloudLinkBilling', - cloudLink: 'billingAndSub', - }, - ], - }, - ], -}); + ], + }, + { + id: 'otherTools', + title: i18n.translate('xpack.serverlessSearch.nav.otherTools', { + defaultMessage: 'Other tools', + }), + spaceBefore: 'm', + children: [{ link: 'maps' }], + }, + ], + }, + ], + footer: [ + { + id: 'gettingStarted', + type: 'navItem', + title: i18n.translate('xpack.serverlessSearch.nav.gettingStarted', { + defaultMessage: 'Getting Started', + }), + link: 'serverlessElasticsearch', + icon: 'launch', + }, + { + type: 'navGroup', + id: 'project_settings_project_nav', + title: i18n.translate('xpack.serverlessSearch.nav.projectSettings', { + defaultMessage: 'Project settings', + }), + icon: 'gear', + breadcrumbStatus: 'hidden', + children: [ + { + link: 'ml:modelManagement', + title: i18n.translate('xpack.serverlessSearch.nav.trainedModels', { + defaultMessage: 'Trained models', + }), + }, + { + link: 'management', + title: i18n.translate('xpack.serverlessSearch.nav.mngt', { + defaultMessage: 'Management', + }), + }, + { + id: 'cloudLinkUserAndRoles', + cloudLink: 'userAndRoles', + }, + { + id: 'cloudLinkDeployment', + cloudLink: 'deployment', + title: i18n.translate('xpack.serverlessSearch.nav.performance', { + defaultMessage: 'Performance', + }), + }, + { + id: 'cloudLinkBilling', + cloudLink: 'billingAndSub', + }, + ], + }, + ], + }; +}; diff --git a/x-pack/plugins/serverless_search/public/plugin.ts b/x-pack/plugins/serverless_search/public/plugin.ts index 3d246e4be2929..d097cd1eb3ad4 100644 --- a/x-pack/plugins/serverless_search/public/plugin.ts +++ b/x-pack/plugins/serverless_search/public/plugin.ts @@ -148,7 +148,7 @@ export class ServerlessSearchPlugin const { serverless, management, indexManagement, security } = services; serverless.setProjectHome(services.searchIndices.startRoute); - const navigationTree$ = of(navigationTree()); + const navigationTree$ = of(navigationTree(core.application)); serverless.initNavigation('es', navigationTree$, { dataTestSubj: 'svlSearchSideNav' }); const extendCardNavDefinitions = serverless.getNavigationCards( diff --git a/x-pack/plugins/serverless_search/public/types.ts b/x-pack/plugins/serverless_search/public/types.ts index 19b3f0fa6baa5..b9c267874b3d9 100644 --- a/x-pack/plugins/serverless_search/public/types.ts +++ b/x-pack/plugins/serverless_search/public/types.ts @@ -8,7 +8,6 @@ import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; import type { ConsolePluginStart } from '@kbn/console-plugin/public'; import type { SearchInferenceEndpointsPluginStart } from '@kbn/search-inference-endpoints/public'; -import type { SearchPlaygroundPluginStart } from '@kbn/search-playground/public'; import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public'; import type { SecurityPluginStart } from '@kbn/security-plugin/public'; import type { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public'; @@ -37,7 +36,6 @@ export interface ServerlessSearchPluginSetupDependencies { export interface ServerlessSearchPluginStartDependencies { cloud: CloudStart; console: ConsolePluginStart; - searchPlayground: SearchPlaygroundPluginStart; searchInferenceEndpoints?: SearchInferenceEndpointsPluginStart; management: ManagementStart; security: SecurityPluginStart; diff --git a/x-pack/plugins/serverless_search/tsconfig.json b/x-pack/plugins/serverless_search/tsconfig.json index 0f7a803a68f7d..794f146299a0f 100644 --- a/x-pack/plugins/serverless_search/tsconfig.json +++ b/x-pack/plugins/serverless_search/tsconfig.json @@ -47,7 +47,6 @@ "@kbn/search-connectors-plugin", "@kbn/index-management-shared-types", "@kbn/react-kibana-context-render", - "@kbn/search-playground", "@kbn/security-api-key-management", "@kbn/search-inference-endpoints", "@kbn/security-plugin-types-common", @@ -55,5 +54,6 @@ "@kbn/core-http-server", "@kbn/logging", "@kbn/security-plugin-types-public", + "@kbn/core-application-browser", ] } diff --git a/x-pack/plugins/triggers_actions_ui/.storybook/context/application.tsx b/x-pack/plugins/triggers_actions_ui/.storybook/context/application.tsx index c05ca6a920cb8..313c7c8e3b1c6 100644 --- a/x-pack/plugins/triggers_actions_ui/.storybook/context/application.tsx +++ b/x-pack/plugins/triggers_actions_ui/.storybook/context/application.tsx @@ -24,6 +24,7 @@ export const getDefaultServicesApplication = ( navigateToApp: async (app: string) => { action(`Navigate to: ${app}`); }, + isAppRegistered: (appId: string) => true, getUrlForApp: (url: string) => url, capabilities: getDefaultCapabilities(), applications$: of(applications),