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 a3e46f5684135..c71718cfaa5a6 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/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts index a5ab52a7bdb76..2dbd905b4df93 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts @@ -25,7 +25,8 @@ import { } from '../../tasks/live_query'; import { generateRandomStringName, interceptCaseId } from '../../tasks/integrations'; -describe('Alert Event Details - Cases', { tags: ['@ess', '@serverless'] }, () => { +// Failing: See https://github.com/elastic/kibana/issues/197151 +describe.skip('Alert Event Details - Cases', { tags: ['@ess', '@serverless'] }, () => { let ruleId: string; let packId: string; let packName: string; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.tsx index be162a24dde20..e5a5fb12915a6 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.tsx @@ -8,9 +8,7 @@ import type { FC } from 'react'; import React, { useCallback, useMemo, useState } from 'react'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; -import styled from 'styled-components'; -import { euiThemeVars } from '@kbn/ui-theme'; -import { EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, useEuiTheme, EuiFlyoutFooter, EuiPanel } from '@elastic/eui'; import { find } from 'lodash/fp'; import { FLYOUT_FOOTER_TEST_ID } from './test_ids'; import type { Status } from '../../../../common/api/detection_engine'; @@ -27,10 +25,6 @@ import { useExceptionFlyout } from '../../../detections/components/alerts_table/ import { isActiveTimeline } from '../../../helpers'; import { useEventFilterModal } from '../../../detections/components/alerts_table/timeline_actions/use_event_filter_modal'; -const StyledEuiFlyoutFooter = styled('div')` - padding: ${euiThemeVars.euiPanelPaddingModifiers.paddingMedium}; -`; - interface AlertSummaryData { /** * Status of the alert (open, closed...) @@ -182,27 +176,29 @@ export const PanelFooter: FC = ({ isPreview }) => { return ( <> - - - - {dataAsNestedObject && ( - - )} - - - + + + + + {dataAsNestedObject && ( + + )} + + + + {openAddExceptionFlyout && alertSummaryData.ruleId != null && diff --git a/x-pack/plugins/task_manager/server/lib/create_managed_configuration.test.ts b/x-pack/plugins/task_manager/server/lib/create_managed_configuration.test.ts index cfbe43a8ecf21..d863fb9b300fd 100644 --- a/x-pack/plugins/task_manager/server/lib/create_managed_configuration.test.ts +++ b/x-pack/plugins/task_manager/server/lib/create_managed_configuration.test.ts @@ -184,6 +184,17 @@ describe('createManagedConfiguration()', () => { expect(subscription).toHaveBeenNthCalledWith(2, 8); }); + test('should decrease configuration at the next interval when a 503 error is emitted', async () => { + const { subscription, errors$ } = setupScenario(10); + errors$.next(SavedObjectsErrorHelpers.createGenericNotFoundEsUnavailableError('a', 'b')); + clock.tick(ADJUST_THROUGHPUT_INTERVAL - 1); + expect(subscription).toHaveBeenCalledTimes(1); + expect(subscription).toHaveBeenNthCalledWith(1, 10); + clock.tick(1); + expect(subscription).toHaveBeenCalledTimes(2); + expect(subscription).toHaveBeenNthCalledWith(2, 8); + }); + test('should log a warning when the configuration changes from the starting value', async () => { const { errors$ } = setupScenario(10); errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); @@ -235,6 +246,17 @@ describe('createManagedConfiguration()', () => { expect(subscription).toHaveBeenNthCalledWith(2, 8); }); + test('should decrease configuration at the next interval when a 503 error is emitted', async () => { + const { subscription, errors$ } = setupScenario(10, CLAIM_STRATEGY_MGET); + errors$.next(SavedObjectsErrorHelpers.createGenericNotFoundEsUnavailableError('a', 'b')); + clock.tick(ADJUST_THROUGHPUT_INTERVAL - 1); + expect(subscription).toHaveBeenCalledTimes(1); + expect(subscription).toHaveBeenNthCalledWith(1, 10); + clock.tick(1); + expect(subscription).toHaveBeenCalledTimes(2); + expect(subscription).toHaveBeenNthCalledWith(2, 8); + }); + test('should log a warning when the configuration changes from the starting value', async () => { const { errors$ } = setupScenario(10, CLAIM_STRATEGY_MGET); errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); @@ -305,6 +327,16 @@ describe('createManagedConfiguration()', () => { expect(subscription).toHaveBeenNthCalledWith(2, 120); }); + test('should increase configuration at the next interval when a 503 error is emitted', async () => { + const { subscription, errors$ } = setupScenario(100); + errors$.next(SavedObjectsErrorHelpers.createGenericNotFoundEsUnavailableError('a', 'b')); + clock.tick(ADJUST_THROUGHPUT_INTERVAL - 1); + expect(subscription).toHaveBeenCalledTimes(1); + clock.tick(1); + expect(subscription).toHaveBeenCalledTimes(2); + expect(subscription).toHaveBeenNthCalledWith(2, 120); + }); + test('should log a warning when the configuration changes from the starting value', async () => { const { errors$ } = setupScenario(100); errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); diff --git a/x-pack/plugins/task_manager/server/lib/create_managed_configuration.ts b/x-pack/plugins/task_manager/server/lib/create_managed_configuration.ts index 3036eb2008de6..a0adee6d05818 100644 --- a/x-pack/plugins/task_manager/server/lib/create_managed_configuration.ts +++ b/x-pack/plugins/task_manager/server/lib/create_managed_configuration.ts @@ -161,7 +161,10 @@ function countErrors(errors$: Observable, countInterval: number): Observa interval(countInterval).pipe(map(() => FLUSH_MARKER)), errors$.pipe( filter( - (e) => SavedObjectsErrorHelpers.isTooManyRequestsError(e) || isEsCannotExecuteScriptError(e) + (e) => + SavedObjectsErrorHelpers.isTooManyRequestsError(e) || + SavedObjectsErrorHelpers.isEsUnavailableError(e) || + isEsCannotExecuteScriptError(e) ) ) ).pipe( 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',