From 324b7bf5c0588b79e6d6d6bdc033a6cfa20af657 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 24 Oct 2024 12:52:20 +1100 Subject: [PATCH] [8.x] [Search][Fix] Inference Endpoints deep link & Side Nav access (#197461) (#197547) # Backport This will backport the following commits from `main` to `8.x`: - [[Search][Fix] Inference Endpoints deep link & Side Nav access (#197461)](https://github.com/elastic/kibana/pull/197461) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: Rodney Norris --- packages/deeplinks/search/constants.ts | 2 +- .../collectors/application_usage/schema.ts | 2 +- src/plugins/telemetry/schema/oss_plugins.json | 2 +- .../common/utils/licensing.test.ts | 96 +++++++++++++++++++ .../common/utils/licensing.ts | 16 ++++ .../public/navigation_tree.ts | 2 +- .../enterprise_search/public/plugin.ts | 21 +++- .../tests/solution_navigation.ts | 32 +++---- .../security_and_spaces/tests/catalogue.ts | 1 - 9 files changed, 151 insertions(+), 23 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/common/utils/licensing.test.ts create mode 100644 x-pack/plugins/enterprise_search/common/utils/licensing.ts diff --git a/packages/deeplinks/search/constants.ts b/packages/deeplinks/search/constants.ts index c4a598145c87c..a2a17b20efba8 100644 --- a/packages/deeplinks/search/constants.ts +++ b/packages/deeplinks/search/constants.ts @@ -9,7 +9,7 @@ export const ENTERPRISE_SEARCH_APP_ID = 'enterpriseSearch'; export const ENTERPRISE_SEARCH_CONTENT_APP_ID = 'enterpriseSearchContent'; -export const ENTERPRISE_SEARCH_RELEVANCE_APP_ID = 'enterpriseSearchRelevance'; +export const ENTERPRISE_SEARCH_RELEVANCE_APP_ID = 'searchInferenceEndpoints'; export const ENTERPRISE_SEARCH_APPLICATIONS_APP_ID = 'enterpriseSearchApplications'; export const ENTERPRISE_SEARCH_ANALYTICS_APP_ID = 'enterpriseSearchAnalytics'; export const ENTERPRISE_SEARCH_APPSEARCH_APP_ID = 'appSearch'; diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts index 3c40e197dbad3..88d60b1a86b2e 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts @@ -136,7 +136,7 @@ export const applicationUsageSchema = { canvas: commonSchema, enterpriseSearch: commonSchema, enterpriseSearchContent: commonSchema, - enterpriseSearchRelevance: commonSchema, + searchInferenceEndpoints: commonSchema, enterpriseSearchAnalytics: commonSchema, enterpriseSearchApplications: commonSchema, enterpriseSearchAISearch: commonSchema, diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index fe09ac9cac1eb..4e533249c7346 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -2098,7 +2098,7 @@ } } }, - "enterpriseSearchRelevance": { + "searchInferenceEndpoints": { "properties": { "appId": { "type": "keyword", diff --git a/x-pack/plugins/enterprise_search/common/utils/licensing.test.ts b/x-pack/plugins/enterprise_search/common/utils/licensing.test.ts new file mode 100644 index 0000000000000..7b5fbc3088984 --- /dev/null +++ b/x-pack/plugins/enterprise_search/common/utils/licensing.test.ts @@ -0,0 +1,96 @@ +/* + * 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 type { ILicense } from '@kbn/licensing-plugin/public'; + +import { hasEnterpriseLicense } from './licensing'; + +describe('licensing utils', () => { + const baseLicense: ILicense = { + isActive: true, + type: 'trial', + isAvailable: true, + signature: 'fake', + toJSON: jest.fn(), + getUnavailableReason: jest.fn().mockReturnValue(undefined), + hasAtLeast: jest.fn().mockReturnValue(false), + check: jest.fn().mockReturnValue({ state: 'valid' }), + getFeature: jest.fn().mockReturnValue({ isAvailable: false, isEnabled: false }), + }; + describe('hasEnterpriseLicense', () => { + let license: ILicense; + beforeEach(() => { + jest.resetAllMocks(); + license = { + ...baseLicense, + }; + }); + it('returns true for active enterprise license', () => { + license.type = 'enterprise'; + + expect(hasEnterpriseLicense(license)).toEqual(true); + }); + it('returns true for active trial license', () => { + expect(hasEnterpriseLicense(license)).toEqual(true); + }); + it('returns false for active basic license', () => { + license.type = 'basic'; + + expect(hasEnterpriseLicense(license)).toEqual(false); + }); + it('returns false for active gold license', () => { + license.type = 'gold'; + + expect(hasEnterpriseLicense(license)).toEqual(false); + }); + it('returns false for active platinum license', () => { + license.type = 'platinum'; + + expect(hasEnterpriseLicense(license)).toEqual(false); + }); + it('returns false for inactive enterprise license', () => { + license.type = 'enterprise'; + license.isActive = false; + + expect(hasEnterpriseLicense(license)).toEqual(false); + }); + it('returns false for inactive trial license', () => { + license.isActive = false; + + expect(hasEnterpriseLicense(license)).toEqual(false); + }); + it('returns false for inactive basic license', () => { + license.type = 'basic'; + license.isActive = false; + + expect(hasEnterpriseLicense(license)).toEqual(false); + }); + it('returns false for inactive gold license', () => { + license.type = 'gold'; + license.isActive = false; + + expect(hasEnterpriseLicense(license)).toEqual(false); + }); + it('returns false for inactive platinum license', () => { + license.type = 'platinum'; + license.isActive = false; + + expect(hasEnterpriseLicense(license)).toEqual(false); + }); + it('returns false for active license is missing type', () => { + delete license.type; + + expect(hasEnterpriseLicense(license)).toEqual(false); + }); + it('returns false for null license', () => { + expect(hasEnterpriseLicense(null)).toEqual(false); + }); + it('returns false for undefined license', () => { + expect(hasEnterpriseLicense(undefined)).toEqual(false); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/common/utils/licensing.ts b/x-pack/plugins/enterprise_search/common/utils/licensing.ts new file mode 100644 index 0000000000000..a78e603b3650d --- /dev/null +++ b/x-pack/plugins/enterprise_search/common/utils/licensing.ts @@ -0,0 +1,16 @@ +/* + * 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 type { ILicense } from '@kbn/licensing-plugin/public'; + +/* hasEnterpriseLicense return if the given license is an active `enterprise` or `trial` license + */ +export function hasEnterpriseLicense(license: ILicense | null | undefined): boolean { + if (license === undefined || license === null) return false; + const qualifyingLicenses = ['enterprise', 'trial']; + return license.isActive && qualifyingLicenses.includes(license?.type ?? ''); +} diff --git a/x-pack/plugins/enterprise_search/public/navigation_tree.ts b/x-pack/plugins/enterprise_search/public/navigation_tree.ts index 2f4e5a17ab335..2f41db6bef486 100644 --- a/x-pack/plugins/enterprise_search/public/navigation_tree.ts +++ b/x-pack/plugins/enterprise_search/public/navigation_tree.ts @@ -209,7 +209,7 @@ export const getNavigationTreeDefinition = ({ }), }, { - children: [{ link: 'enterpriseSearchRelevance:inferenceEndpoints' }], + children: [{ link: 'searchInferenceEndpoints:inferenceEndpoints' }], id: 'relevance', title: i18n.translate('xpack.enterpriseSearch.searchNav.relevance', { defaultMessage: 'Relevance', diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index a1558c4855aa7..06f14ba3d7037 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -19,6 +19,8 @@ import { PluginInitializerContext, DEFAULT_APP_CATEGORIES, AppDeepLink, + type AppUpdater, + AppStatus, } from '@kbn/core/public'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; @@ -53,8 +55,8 @@ import { SEARCH_RELEVANCE_PLUGIN, } from '../common/constants'; import { registerLocators } from '../common/locators'; - import { ClientConfigType, InitialAppData } from '../common/types'; +import { hasEnterpriseLicense } from '../common/utils/licensing'; import { ENGINES_PATH } from './applications/app_search/routes'; import { SEARCH_APPLICATIONS_PATH, PLAYGROUND_PATH } from './applications/applications/routes'; @@ -134,7 +136,7 @@ const contentLinks: AppDeepLink[] = [ const relevanceLinks: AppDeepLink[] = [ { - id: 'searchInferenceEndpoints', + id: 'inferenceEndpoints', path: `/${INFERENCE_ENDPOINTS_PATH}`, title: i18n.translate( 'xpack.enterpriseSearch.navigation.relevanceInferenceEndpointsLinkLabel', @@ -180,6 +182,7 @@ const appSearchLinks: AppDeepLink[] = [ export class EnterpriseSearchPlugin implements Plugin { private config: ClientConfigType; + private enterpriseLicenseAppUpdater$ = new BehaviorSubject(() => ({})); constructor(initializerContext: PluginInitializerContext) { this.config = initializerContext.config.get(); @@ -440,6 +443,8 @@ export class EnterpriseSearchPlugin implements Plugin { deepLinks: relevanceLinks, euiIconType: SEARCH_RELEVANCE_PLUGIN.LOGO, id: SEARCH_RELEVANCE_PLUGIN.ID, + status: AppStatus.inaccessible, + updater$: this.enterpriseLicenseAppUpdater$, mount: async (params: AppMountParameters) => { const kibanaDeps = await this.getKibanaDeps(core, params, cloud); const { chrome, http } = kibanaDeps.core; @@ -615,6 +620,18 @@ export class EnterpriseSearchPlugin implements Plugin { ); }); + plugins.licensing?.license$.subscribe((license) => { + if (hasEnterpriseLicense(license)) { + this.enterpriseLicenseAppUpdater$.next(() => ({ + status: AppStatus.accessible, + })); + } else { + this.enterpriseLicenseAppUpdater$.next(() => ({ + status: AppStatus.inaccessible, + })); + } + }); + // Return empty start contract rather than void in order for plugins // that depend on the enterprise search plugin to determine whether it is enabled or not return {}; diff --git a/x-pack/test/functional_search/tests/solution_navigation.ts b/x-pack/test/functional_search/tests/solution_navigation.ts index 8a06ad1193372..66bf8369b668f 100644 --- a/x-pack/test/functional_search/tests/solution_navigation.ts +++ b/x-pack/test/functional_search/tests/solution_navigation.ts @@ -47,6 +47,7 @@ export default function searchSolutionNavigation({ await solutionNavigation.sidenav.expectLinkExists({ text: 'Playground' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Search applications' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Behavioral Analytics' }); + await solutionNavigation.sidenav.expectLinkExists({ text: 'Inference Endpoints' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'App Search' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Workplace Search' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Other tools' }); @@ -184,20 +185,19 @@ export default function searchSolutionNavigation({ // check Relevance // > Inference Endpoints - // TODO: FTRs don't have enterprise license, so inference endpoints not shown - // await solutionNavigation.sidenav.clickLink({ - // deepLinkId: 'enterpriseSearchRelevance:inferenceEndpoints', - // }); - // await solutionNavigation.sidenav.expectLinkActive({ - // deepLinkId: 'enterpriseSearchRelevance:inferenceEndpoints', - // }); - // await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Relevance' }); - // await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ - // text: 'Inference Endpoints', - // }); - // await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ - // deepLinkId: 'enterpriseSearchRelevance:inferenceEndpoints', - // }); + await solutionNavigation.sidenav.clickLink({ + deepLinkId: 'searchInferenceEndpoints:inferenceEndpoints', + }); + await solutionNavigation.sidenav.expectLinkActive({ + deepLinkId: 'searchInferenceEndpoints:inferenceEndpoints', + }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Relevance' }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ + text: 'Inference Endpoints', + }); + await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ + deepLinkId: 'searchInferenceEndpoints:inferenceEndpoints', + }); // check Enterprise Search // > App Search @@ -296,8 +296,8 @@ export default function searchSolutionNavigation({ 'enterpriseSearchApplications:playground', 'enterpriseSearchApplications:searchApplications', 'enterpriseSearchAnalytics', - // 'relevance', - // 'enterpriseSearchRelevance:inferenceEndpoints', + 'relevance', + 'searchInferenceEndpoints:inferenceEndpoints', 'entsearch', 'appSearch:engines', 'workplaceSearch', diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts index c8182c4310c33..ddcc187fad6a4 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts @@ -93,7 +93,6 @@ export default function catalogueTests({ getService }: FtrProviderContext) { 'enterpriseSearchVectorSearch', 'enterpriseSearchSemanticSearch', 'enterpriseSearchElasticsearch', - 'enterpriseSearchRelevance', 'searchInferenceEndpoints', 'appSearch', 'observabilityAIAssistant',