From fdedae07b854280b37f142b652892f1b5ee44018 Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Mon, 9 Dec 2024 18:12:51 +0100 Subject: [PATCH 1/5] [SecuritySolution] Service Entity Store (#202344) ## Summary ### Service Definition: https://github.com/elastic/kibana/pull/202344/files#diff-42c7dd345e0500c97f85824904a70a11162827ea8f8df6982082a9047ca04ff1 ### Acceptance Criteria - [x] Upon installation of the entity store, the Service entity definition should be created by default - [x] The Service definition will be installed in the exact same way as the User and Host definitions - [x] The unique identifier for service entities will be `service.name` - [x] The fields captured for service entities should match the field mapping spreadsheet (see Implementation Notes below) ### Stored Entity ```json { "@timestamp": "2024-12-02T10:43:13.856Z", "event": { "ingested": "2024-12-02T10:51:28.987428Z" }, "entity": { "name": "test123 name", "id": "test123 name", "source": "logs-blito", "type": "service" }, "service": { "node": { "roles": [ "test123 node roles" ], "name": [ "test123 node name" ] }, "environment": [ "test123 environment" ], "address": [ "test123 address" ], "name": "test123 name", "id": [ "test123 id" ], "state": [ "test123 state" ], "ephemeral_id": [ "test123 ephemeral_id" ], "type": [ "test123 type" ], "version": [ "test123 version" ] } } ``` ### How to test it? * Start Kibana
Create mappings ``` PUT /logs-test { "mappings": { "properties": { "service.name": { "type": "keyword" }, "service.address": { "type": "keyword" }, "service.environment": { "type": "keyword" }, "service.ephemeral_id": { "type": "keyword" }, "service.id": { "type": "keyword" }, "service.node.name": { "type": "keyword" }, "service.node.roles": { "type": "keyword" }, "service.state": { "type": "keyword" }, "service.type": { "type": "keyword" }, "service.version": { "type": "keyword" }, "@timestamp": { "type": "date" } } } } ````
Create document ``` PUT /logs-test POST logs-test/_doc { "service": { "name": "test123 name", "address": "test123 address", "environment": "test123 environment", "ephemeral_id": "test123 ephemeral_id", "id": "test123 id", "node.roles": "test123 node roles", "node.name": "test123 node name", "state": "test123 state", "type": "test123 type", "version": "test123 version" }, "@timestamp": "2024-12-02T10:43:13.856Z" } ````
* Init the entity store * Wait... * Query the service index `GET .entities.v1.latest.security_service_default/_search` ### Open Questions * Can we merge this PR without first updating all other features that will use service entities? * If we merge it, the service engine will be installed together with other entities, but it won't provide any functionality * Do we need an experimental flag? --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- oas_docs/output/kibana.serverless.yaml | 1 + oas_docs/output/kibana.yaml | 1 + .../entity_store/common.gen.ts | 2 +- .../entity_store/common.schema.yaml | 1 + .../common/experimental_features.ts | 5 + ...alytics_api_2023_10_31.bundled.schema.yaml | 1 + ...alytics_api_2023_10_31.bundled.schema.yaml | 1 + .../entity_store/hooks/use_entity_store.ts | 29 +- .../pages/entity_store_management_page.tsx | 11 +- .../entity_store_data_client.test.ts | 2 + .../entity_store/entity_store_data_client.ts | 16 +- .../entity_types/index.ts | 1 + .../entity_types/service.ts | 29 ++ .../get_united_definition.test.ts | 311 ++++++++++++++++++ .../get_united_definition.ts | 10 +- .../entity_store/utils/entity_utils.ts | 15 +- .../server/request_context_factory.ts | 1 + 17 files changed, 417 insertions(+), 20 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/service.ts diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index bf3c74d906cef..0c89dc73bcd38 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -47102,6 +47102,7 @@ components: enum: - user - host + - service type: string Security_Entity_Analytics_API_HostEntity: type: object diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index aea9bacebd061..b83907699ec76 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -54783,6 +54783,7 @@ components: enum: - user - host + - service type: string Security_Entity_Analytics_API_HostEntity: type: object diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts index 25c47e838d85c..3511e1d166ede 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts @@ -17,7 +17,7 @@ import { z } from '@kbn/zod'; export type EntityType = z.infer; -export const EntityType = z.enum(['user', 'host']); +export const EntityType = z.enum(['user', 'host', 'service']); export type EntityTypeEnum = typeof EntityType.enum; export const EntityTypeEnum = EntityType.enum; diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml index 5adb6fe038dc9..0ddbf1c9b3fd0 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml @@ -11,6 +11,7 @@ components: enum: - user - host + - service EngineDescriptor: type: object diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 095324840fc5c..def958c07bd2d 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -243,6 +243,11 @@ export const allowedExperimentalValues = Object.freeze({ */ entityStoreDisabled: false, + /** + * Enables the Service Entity Store. The Entity Store feature will install the service engine by default. + */ + serviceEntityStoreEnabled: true, + /** * Enables the siem migrations feature */ diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml index b1b85b8222786..9a0c456e5efe3 100644 --- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml @@ -987,6 +987,7 @@ components: enum: - user - host + - service type: string HostEntity: type: object diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml index 4a3b3495467e9..356a63567c401 100644 --- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml @@ -987,6 +987,7 @@ components: enum: - user - host + - service type: string HostEntity: type: object diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts index ceb93d164af62..953bd811f8ab3 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts @@ -12,10 +12,11 @@ import type { IHttpFetchError } from '@kbn/core-http-browser'; import type { GetEntityStoreStatusResponse } from '../../../../../common/api/entity_analytics/entity_store/status.gen'; import type { InitEntityStoreResponse } from '../../../../../common/api/entity_analytics/entity_store/enable.gen'; import { useKibana } from '../../../../common/lib/kibana/kibana_react'; -import type { - DeleteEntityEngineResponse, - InitEntityEngineResponse, - StopEntityEngineResponse, +import type { EntityType } from '../../../../../common/api/entity_analytics'; +import { + type DeleteEntityEngineResponse, + type InitEntityEngineResponse, + type StopEntityEngineResponse, } from '../../../../../common/api/entity_analytics'; import { useEntityStoreRoutes } from '../../../api/entity_store'; import { EntityEventTypes } from '../../../../common/lib/telemetry'; @@ -68,13 +69,16 @@ export const useEnableEntityStoreMutation = (options?: UseMutationOptions<{}>) = }; export const INIT_ENTITY_ENGINE_STATUS_KEY = ['POST', 'INIT_ENTITY_ENGINE']; +/** + * @deprecated + * It will be deleted on a follow-up PR + */ export const useInitEntityEngineMutation = (options?: UseMutationOptions<{}>) => { const queryClient = useQueryClient(); const { initEntityEngine } = useEntityStoreRoutes(); return useMutation( () => Promise.all([initEntityEngine('user'), initEntityEngine('host')]), - { mutationKey: INIT_ENTITY_ENGINE_STATUS_KEY, onSuccess: () => queryClient.refetchQueries({ queryKey: ENTITY_STORE_STATUS }), @@ -84,7 +88,7 @@ export const useInitEntityEngineMutation = (options?: UseMutationOptions<{}>) => }; export const STOP_ENTITY_ENGINE_STATUS_KEY = ['POST', 'STOP_ENTITY_ENGINE']; -export const useStopEntityEngineMutation = (options?: UseMutationOptions<{}>) => { +export const useStopEntityEngineMutation = (entityTypes: EntityType[]) => { const { telemetry } = useKibana().services; const queryClient = useQueryClient(); @@ -95,23 +99,28 @@ export const useStopEntityEngineMutation = (options?: UseMutationOptions<{}>) => timestamp: new Date().toISOString(), action: 'stop', }); - return Promise.all([stopEntityEngine('user'), stopEntityEngine('host')]); + return Promise.all(entityTypes.map((entityType) => stopEntityEngine(entityType))); }, { mutationKey: STOP_ENTITY_ENGINE_STATUS_KEY, onSuccess: () => queryClient.refetchQueries({ queryKey: ENTITY_STORE_STATUS }), - ...options, } ); }; export const DELETE_ENTITY_ENGINE_STATUS_KEY = ['POST', 'STOP_ENTITY_ENGINE']; -export const useDeleteEntityEngineMutation = ({ onSuccess }: { onSuccess?: () => void }) => { +export const useDeleteEntityEngineMutation = ({ + onSuccess, + entityTypes, +}: { + onSuccess?: () => void; + entityTypes: EntityType[]; +}) => { const queryClient = useQueryClient(); const { deleteEntityEngine } = useEntityStoreRoutes(); return useMutation( - () => Promise.all([deleteEntityEngine('user', true), deleteEntityEngine('host', true)]), + () => Promise.all(entityTypes.map((entityType) => deleteEntityEngine(entityType, true))), { mutationKey: DELETE_ENTITY_ENGINE_STATUS_KEY, onSuccess: () => { diff --git a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx index 3cf3eb58355c6..67c75d5ff88b5 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/pages/entity_store_management_page.tsx @@ -33,7 +33,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import type { SecurityAppError } from '@kbn/securitysolution-t-grid'; -import type { StoreStatus } from '../../../common/api/entity_analytics'; +import { EntityType, EntityTypeEnum, type StoreStatus } from '../../../common/api/entity_analytics'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { ASSET_CRITICALITY_INDEX_PATTERN } from '../../../common/entity_analytics/asset_criticality'; import { useKibana } from '../../common/lib/kibana'; @@ -73,13 +73,20 @@ export const EntityStoreManagementPage = () => { const hasAssetCriticalityWritePermissions = assetCriticalityPrivileges?.has_write_permissions; const [selectedTabId, setSelectedTabId] = useState(TabId.Import); const entityStoreStatus = useEntityStoreStatus({}); + const isServiceEntityStoreEnabled = useIsExperimentalFeatureEnabled('serviceEntityStoreEnabled'); + const allEntityTypes = Object.values(EntityType.Values); + + const entityTypes = isServiceEntityStoreEnabled + ? allEntityTypes + : allEntityTypes.filter((value) => value !== EntityTypeEnum.service); const enableStoreMutation = useEnableEntityStoreMutation(); - const stopEntityEngineMutation = useStopEntityEngineMutation(); + const stopEntityEngineMutation = useStopEntityEngineMutation(entityTypes); const deleteEntityEngineMutation = useDeleteEntityEngineMutation({ onSuccess: () => { closeClearModal(); }, + entityTypes, }); const [isClearModalVisible, setIsClearModalVisible] = useState(false); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.test.ts index 733e85fd6ed55..86ac7d136d221 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.test.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.test.ts @@ -16,6 +16,7 @@ import type { EntityType } from '../../../../common/api/entity_analytics/entity_ import type { DataViewsService } from '@kbn/data-views-plugin/common'; import type { AppClient } from '../../..'; import type { EntityStoreConfig } from './types'; +import { mockGlobalState } from '../../../../public/common/mock'; describe('EntityStoreDataClient', () => { const mockSavedObjectClient = savedObjectsClientMock.create(); @@ -31,6 +32,7 @@ describe('EntityStoreDataClient', () => { dataViewsService: {} as DataViewsService, appClient: {} as AppClient, config: {} as EntityStoreConfig, + experimentalFeatures: mockGlobalState.app.enableExperimental, }); const defaultSearchParams = { diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts index dc4e9d7af2ff6..c18dc1863a8d1 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts @@ -23,6 +23,7 @@ import moment from 'moment'; import type { EntityDefinitionWithState } from '@kbn/entityManager-plugin/server/lib/entities/types'; import type { EntityDefinition } from '@kbn/entities-schema'; import type { estypes } from '@elastic/elasticsearch'; +import type { ExperimentalFeatures } from '../../../../common'; import type { GetEntityStoreStatusRequestQuery, GetEntityStoreStatusResponse, @@ -32,7 +33,10 @@ import type { InitEntityStoreResponse, } from '../../../../common/api/entity_analytics/entity_store/enable.gen'; import type { AppClient } from '../../..'; -import { EngineComponentResourceEnum, EntityType } from '../../../../common/api/entity_analytics'; +import { + EngineComponentResourceEnum, + EntityTypeEnum, +} from '../../../../common/api/entity_analytics'; import type { Entity, EngineDataviewUpdateResult, @@ -42,6 +46,7 @@ import type { ListEntityEnginesResponse, EngineComponentStatus, EngineComponentResource, + EntityType, } from '../../../../common/api/entity_analytics'; import { EngineDescriptorClient } from './saved_object/engine_descriptor'; import { ENGINE_STATUS, ENTITY_STORE_STATUS, MAX_SEARCH_RESPONSE_SIZE } from './constants'; @@ -108,6 +113,7 @@ interface EntityStoreClientOpts { dataViewsService: DataViewsService; appClient: AppClient; config: EntityStoreConfig; + experimentalFeatures: ExperimentalFeatures; telemetry?: AnalyticsServiceSetup; } @@ -204,7 +210,13 @@ export class EntityStoreDataClient { // Immediately defer the initialization to the next tick. This way we don't block on the init preflight checks const run = (fn: () => Promise) => new Promise((resolve) => setTimeout(() => fn().then(resolve), 0)); - const promises = Object.values(EntityType.Values).map((entity) => + + const { experimentalFeatures } = this.options; + const enginesTypes = experimentalFeatures.serviceEntityStoreEnabled + ? [EntityTypeEnum.host, EntityTypeEnum.user, EntityTypeEnum.service] + : [EntityTypeEnum.host, EntityTypeEnum.user]; + + const promises = enginesTypes.map((entity) => run(() => this.init(entity, { indexPattern, filter, fieldHistoryLength }, { pipelineDebugMode }) ) diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/index.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/index.ts index 4193c07f308fb..cf78a4b0e363b 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/index.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/index.ts @@ -7,4 +7,5 @@ export * from './host'; export * from './user'; +export * from './service'; export { getCommonUnitedFieldDefinitions } from './common'; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/service.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/service.ts new file mode 100644 index 0000000000000..26d10046b26ae --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/service.ts @@ -0,0 +1,29 @@ +/* + * 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 { collectValuesWithLength, newestValue } from '../definition_utils'; +import type { UnitedDefinitionBuilder } from '../types'; + +export const SERVICE_DEFINITION_VERSION = '1.0.0'; +export const getServiceUnitedDefinition: UnitedDefinitionBuilder = (fieldHistoryLength: number) => { + const collect = collectValuesWithLength(fieldHistoryLength); + return { + entityType: 'service', + version: SERVICE_DEFINITION_VERSION, + fields: [ + collect({ field: 'service.address' }), + collect({ field: 'service.environment' }), + collect({ field: 'service.ephemeral_id' }), + collect({ field: 'service.id' }), + collect({ field: 'service.node.name' }), + collect({ field: 'service.node.roles' }), + newestValue({ field: 'service.state' }), + collect({ field: 'service.type' }), + newestValue({ field: 'service.version' }), + ], + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/get_united_definition.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/get_united_definition.test.ts index 07c011b4791e6..16e5e06aea8f2 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/get_united_definition.test.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/get_united_definition.test.ts @@ -587,4 +587,315 @@ describe('getUnitedEntityDefinition', () => { `); }); }); + + describe('service', () => { + const unitedDefinition = getUnitedEntityDefinition({ + entityType: 'service', + namespace: 'test', + fieldHistoryLength: 10, + indexPatterns, + syncDelay: '1m', + frequency: '1m', + }); + + it('mapping', () => { + expect(unitedDefinition.indexMappings).toMatchInlineSnapshot(` + Object { + "properties": Object { + "@timestamp": Object { + "type": "date", + }, + "asset.criticality": Object { + "type": "keyword", + }, + "entity.name": Object { + "fields": Object { + "text": Object { + "type": "match_only_text", + }, + }, + "type": "keyword", + }, + "entity.source": Object { + "type": "keyword", + }, + "service.address": Object { + "type": "keyword", + }, + "service.environment": Object { + "type": "keyword", + }, + "service.ephemeral_id": Object { + "type": "keyword", + }, + "service.id": Object { + "type": "keyword", + }, + "service.name": Object { + "fields": Object { + "text": Object { + "type": "match_only_text", + }, + }, + "type": "keyword", + }, + "service.node.name": Object { + "type": "keyword", + }, + "service.node.roles": Object { + "type": "keyword", + }, + "service.risk.calculated_level": Object { + "type": "keyword", + }, + "service.risk.calculated_score": Object { + "type": "float", + }, + "service.risk.calculated_score_norm": Object { + "type": "float", + }, + "service.state": Object { + "type": "keyword", + }, + "service.type": Object { + "type": "keyword", + }, + "service.version": Object { + "type": "keyword", + }, + }, + } + `); + }); + it('fieldRetentionDefinition', () => { + expect(unitedDefinition.fieldRetentionDefinition).toMatchInlineSnapshot(` + Object { + "entityType": "service", + "fields": Array [ + Object { + "field": "service.address", + "maxLength": 10, + "operation": "collect_values", + }, + Object { + "field": "service.environment", + "maxLength": 10, + "operation": "collect_values", + }, + Object { + "field": "service.ephemeral_id", + "maxLength": 10, + "operation": "collect_values", + }, + Object { + "field": "service.id", + "maxLength": 10, + "operation": "collect_values", + }, + Object { + "field": "service.node.name", + "maxLength": 10, + "operation": "collect_values", + }, + Object { + "field": "service.node.roles", + "maxLength": 10, + "operation": "collect_values", + }, + Object { + "field": "service.state", + "operation": "prefer_newest_value", + }, + Object { + "field": "service.type", + "maxLength": 10, + "operation": "collect_values", + }, + Object { + "field": "service.version", + "operation": "prefer_newest_value", + }, + Object { + "field": "entity.source", + "operation": "prefer_oldest_value", + }, + Object { + "field": "asset.criticality", + "operation": "prefer_newest_value", + }, + Object { + "field": "service.risk.calculated_level", + "operation": "prefer_newest_value", + }, + Object { + "field": "service.risk.calculated_score", + "operation": "prefer_newest_value", + }, + Object { + "field": "service.risk.calculated_score_norm", + "operation": "prefer_newest_value", + }, + ], + "matchField": "service.name", + } + `); + }); + it('entityManagerDefinition', () => { + expect(unitedDefinition.entityManagerDefinition).toMatchInlineSnapshot(` + Object { + "displayNameTemplate": "{{service.name}}", + "id": "security_service_test", + "identityFields": Array [ + Object { + "field": "service.name", + "optional": false, + }, + ], + "indexPatterns": Array [ + "test*", + ], + "latest": Object { + "lookbackPeriod": "24h", + "settings": Object { + "frequency": "1m", + "syncDelay": "1m", + }, + "timestampField": "@timestamp", + }, + "managed": true, + "metadata": Array [ + Object { + "aggregation": Object { + "limit": 10, + "type": "terms", + }, + "destination": "service.address", + "source": "service.address", + }, + Object { + "aggregation": Object { + "limit": 10, + "type": "terms", + }, + "destination": "service.environment", + "source": "service.environment", + }, + Object { + "aggregation": Object { + "limit": 10, + "type": "terms", + }, + "destination": "service.ephemeral_id", + "source": "service.ephemeral_id", + }, + Object { + "aggregation": Object { + "limit": 10, + "type": "terms", + }, + "destination": "service.id", + "source": "service.id", + }, + Object { + "aggregation": Object { + "limit": 10, + "type": "terms", + }, + "destination": "service.node.name", + "source": "service.node.name", + }, + Object { + "aggregation": Object { + "limit": 10, + "type": "terms", + }, + "destination": "service.node.roles", + "source": "service.node.roles", + }, + Object { + "aggregation": Object { + "sort": Object { + "@timestamp": "desc", + }, + "type": "top_value", + }, + "destination": "service.state", + "source": "service.state", + }, + Object { + "aggregation": Object { + "limit": 10, + "type": "terms", + }, + "destination": "service.type", + "source": "service.type", + }, + Object { + "aggregation": Object { + "sort": Object { + "@timestamp": "desc", + }, + "type": "top_value", + }, + "destination": "service.version", + "source": "service.version", + }, + Object { + "aggregation": Object { + "sort": Object { + "@timestamp": "asc", + }, + "type": "top_value", + }, + "destination": "entity.source", + "source": "_index", + }, + Object { + "aggregation": Object { + "sort": Object { + "@timestamp": "desc", + }, + "type": "top_value", + }, + "destination": "asset.criticality", + "source": "asset.criticality", + }, + Object { + "aggregation": Object { + "sort": Object { + "@timestamp": "desc", + }, + "type": "top_value", + }, + "destination": "service.risk.calculated_level", + "source": "service.risk.calculated_level", + }, + Object { + "aggregation": Object { + "sort": Object { + "@timestamp": "desc", + }, + "type": "top_value", + }, + "destination": "service.risk.calculated_score", + "source": "service.risk.calculated_score", + }, + Object { + "aggregation": Object { + "sort": Object { + "@timestamp": "desc", + }, + "type": "top_value", + }, + "destination": "service.risk.calculated_score_norm", + "source": "service.risk.calculated_score_norm", + }, + ], + "name": "Security 'service' Entity Store Definition", + "type": "service", + "version": "1.0.0", + } + `); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/get_united_definition.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/get_united_definition.ts index ba4963d5fea0a..ae54e802574cf 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/get_united_definition.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/get_united_definition.ts @@ -12,12 +12,14 @@ import { getCommonUnitedFieldDefinitions, USER_DEFINITION_VERSION, HOST_DEFINITION_VERSION, + getServiceUnitedDefinition, } from './entity_types'; import type { UnitedDefinitionBuilder } from './types'; import { UnitedEntityDefinition } from './united_entity_definition'; const unitedDefinitionBuilders: Record = { host: getHostUnitedDefinition, user: getUserUnitedDefinition, + service: getServiceUnitedDefinition, }; interface Options { @@ -57,8 +59,14 @@ export const getUnitedEntityDefinition = memoize( } ); +const versionByEntityType: Record = { + host: HOST_DEFINITION_VERSION, + user: USER_DEFINITION_VERSION, + service: USER_DEFINITION_VERSION, +}; + export const getUnitedEntityDefinitionVersion = (entityType: EntityType): string => - entityType === 'host' ? HOST_DEFINITION_VERSION : USER_DEFINITION_VERSION; + versionByEntityType[entityType]; export const getAvailableEntityTypes = (): EntityType[] => Object.keys(unitedDefinitionBuilders) as EntityType[]; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/utils/entity_utils.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/utils/entity_utils.ts index 8fe21317f4ad8..1a10bddb428fa 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/utils/entity_utils.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/utils/entity_utils.ts @@ -14,13 +14,20 @@ import type { DataViewsService, DataView } from '@kbn/data-views-plugin/common'; import type { AppClient } from '../../../../types'; import { getRiskScoreLatestIndex } from '../../../../../common/entity_analytics/risk_engine'; import { getAssetCriticalityIndex } from '../../../../../common/entity_analytics/asset_criticality'; -import type { EntityType } from '../../../../../common/api/entity_analytics/entity_store/common.gen'; +import { + EntityTypeEnum, + type EntityType, +} from '../../../../../common/api/entity_analytics/entity_store/common.gen'; import { entityEngineDescriptorTypeName } from '../saved_object'; -export const getIdentityFieldForEntityType = (entityType: EntityType) => { - if (entityType === 'host') return 'host.name'; +const identityFieldMap: Record = { + [EntityTypeEnum.host]: 'host.name', + [EntityTypeEnum.user]: 'user.name', + [EntityTypeEnum.service]: 'service.name', +}; - return 'user.name'; +export const getIdentityFieldForEntityType = (entityType: EntityType) => { + return identityFieldMap[entityType]; }; export const buildIndexPatterns = async ( diff --git a/x-pack/plugins/security_solution/server/request_context_factory.ts b/x-pack/plugins/security_solution/server/request_context_factory.ts index c2b3782d405d0..a6b5e1b3e650a 100644 --- a/x-pack/plugins/security_solution/server/request_context_factory.ts +++ b/x-pack/plugins/security_solution/server/request_context_factory.ts @@ -237,6 +237,7 @@ export class RequestContextFactory implements IRequestContextFactory { auditLogger: getAuditLogger(), kibanaVersion: options.kibanaVersion, config: config.entityAnalytics.entityStore, + experimentalFeatures: config.experimentalFeatures, telemetry: core.analytics, }); }), From 7eb005242cd5b02a10023d204e3448719650808f Mon Sep 17 00:00:00 2001 From: Viduni Wickramarachchi Date: Mon, 9 Dec 2024 12:20:47 -0500 Subject: [PATCH 2/5] [Obs AI Assistant] Use cookie auth for internal APIs in serverless tests (#203275) ## Summary ### Problem Cookie authentication was introduced in Kibana for serverless internal API tests via https://github.com/elastic/kibana/pull/192727. The serverless tests for Obs AI Assistant still uses API key based auth. ### Solution Change authentication to cookie based auth for internal APIs in serverless tests. ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../observability_ai_assistant_api_client.ts | 65 +++++++--- .../ai_assistant/tests/chat/chat.spec.ts | 21 +++- .../tests/complete/complete.spec.ts | 47 ++++---- .../complete/functions/elasticsearch.spec.ts | 2 - .../tests/complete/functions/helpers.ts | 9 +- .../complete/functions/summarize.spec.ts | 6 +- .../tests/connectors/connectors.spec.ts | 13 +- .../tests/conversations/conversations.spec.ts | 62 +++------- .../tests/conversations/helpers.ts | 13 +- .../knowledge_base/knowledge_base.spec.ts | 73 ++++------- .../knowledge_base_setup.spec.ts | 20 +--- .../knowledge_base_status.spec.ts | 20 +--- .../knowledge_base_user_instructions.spec.ts | 113 +++++++----------- .../public_complete/public_complete.spec.ts | 4 +- 14 files changed, 185 insertions(+), 283 deletions(-) diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/common/observability_ai_assistant_api_client.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/common/observability_ai_assistant_api_client.ts index bd54243ab93f6..3ac941501ae7c 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/common/observability_ai_assistant_api_client.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/common/observability_ai_assistant_api_client.ts @@ -15,37 +15,47 @@ import supertest from 'supertest'; import { Subtract } from 'utility-types'; import { format } from 'url'; import { Config } from '@kbn/test'; +import { SupertestWithRoleScope } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services/role_scoped_supertest'; import { InheritedFtrProviderContext } from '../../../../services'; import type { InternalRequestHeader, RoleCredentials } from '../../../../../shared/services'; export function getObservabilityAIAssistantApiClient({ svlSharedConfig, + supertestUserWithCookieCredentials, }: { svlSharedConfig: Config; + supertestUserWithCookieCredentials?: SupertestWithRoleScope; }) { - const kibanaServer = svlSharedConfig.get('servers.kibana'); - const cAuthorities = svlSharedConfig.get('servers.kibana.certificateAuthorities'); - - const url = format({ - ...kibanaServer, - auth: false, // don't use auth in serverless - }); - - return createObservabilityAIAssistantApiClient(supertest.agent(url, { ca: cAuthorities })); + if (supertestUserWithCookieCredentials) { + return createObservabilityAIAssistantApiClient(supertestUserWithCookieCredentials); + } else { + const kibanaServer = svlSharedConfig.get('servers.kibana'); + const cAuthorities = svlSharedConfig.get('servers.kibana.certificateAuthorities'); + + const url = format({ + ...kibanaServer, + auth: false, // don't use auth in serverless + }); + return createObservabilityAIAssistantApiClient(supertest.agent(url, { ca: cAuthorities })); + } } -type ObservabilityAIAssistantApiClientKey = 'slsUser'; +type ObservabilityAIAssistantApiClientKey = 'slsAdmin' | 'slsEditor' | 'slsUser'; + export type ObservabilityAIAssistantApiClient = Record< ObservabilityAIAssistantApiClientKey, Awaited> >; -export function createObservabilityAIAssistantApiClient(st: supertest.Agent) { + +export function createObservabilityAIAssistantApiClient( + st: SupertestWithRoleScope | supertest.Agent +) { return ( options: { type?: 'form-data'; endpoint: TEndpoint; - roleAuthc: RoleCredentials; - internalReqHeader: InternalRequestHeader; + roleAuthc?: RoleCredentials; + internalReqHeader?: InternalRequestHeader; } & ObservabilityAIAssistantAPIClientRequestParamsOf & { params?: { query?: { _inspect?: boolean } }; } @@ -57,7 +67,8 @@ export function createObservabilityAIAssistantApiClient(st: supertest.Agent) { const { method, pathname, version } = formatRequest(endpoint, params.path); const url = format({ pathname, query: params?.query }); - const headers: Record = { ...internalReqHeader, ...roleAuthc.apiKeyHeader }; + const headers: Record = + roleAuthc && internalReqHeader ? { ...internalReqHeader, ...roleAuthc.apiKeyHeader } : {}; if (version) { headers['Elastic-Api-Version'] = version; @@ -182,10 +193,34 @@ export async function getObservabilityAIAssistantApiClientService({ getService, }: InheritedFtrProviderContext): Promise { const svlSharedConfig = getService('config'); - // defaults to elastic_admin user when used without auth + const roleScopedSupertest = getService('roleScopedSupertest'); + + const supertestAdminWithCookieCredentials: SupertestWithRoleScope = + await roleScopedSupertest.getSupertestWithRoleScope('admin', { + useCookieHeader: true, + withInternalHeaders: true, + }); + + const supertestEditorWithCookieCredentials: SupertestWithRoleScope = + await roleScopedSupertest.getSupertestWithRoleScope('editor', { + useCookieHeader: true, + withInternalHeaders: true, + }); + return { + // defaults to elastic_admin user when used without auth slsUser: await getObservabilityAIAssistantApiClient({ svlSharedConfig, }), + // cookie auth for internal apis + slsAdmin: await getObservabilityAIAssistantApiClient({ + svlSharedConfig, + supertestUserWithCookieCredentials: supertestAdminWithCookieCredentials, + }), + // cookie auth for internal apis + slsEditor: await getObservabilityAIAssistantApiClient({ + svlSharedConfig, + supertestUserWithCookieCredentials: supertestEditorWithCookieCredentials, + }), }; } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/chat/chat.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/chat/chat.spec.ts index 424df01b3c999..2a25a309e8174 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/chat/chat.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/chat/chat.spec.ts @@ -12,6 +12,7 @@ import { LlmProxy, createLlmProxy, } from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/common/create_llm_proxy'; +import { SupertestWithRoleScope } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services/role_scoped_supertest'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createProxyActionConnector, deleteActionConnector } from '../../common/action_connectors'; import type { InternalRequestHeader, RoleCredentials } from '../../../../../../shared/services'; @@ -21,6 +22,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { const svlUserManager = getService('svlUserManager'); const svlCommonApi = getService('svlCommonApi'); const log = getService('log'); + const roleScopedSupertest = getService('roleScopedSupertest'); + + let supertestEditorWithCookieCredentials: SupertestWithRoleScope; const CHAT_API_URL = `/internal/observability_ai_assistant/chat`; @@ -52,6 +56,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('editor'); internalReqHeader = svlCommonApi.getInternalRequestHeader(); + + supertestEditorWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope( + 'editor', + { + useCookieHeader: true, + withInternalHeaders: true, + } + ); + proxy = await createLlmProxy(log); connectorId = await createProxyActionConnector({ supertest: supertestWithoutAuth, @@ -75,10 +88,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it("returns a 4xx if the connector doesn't exist", async () => { - await supertestWithoutAuth + await supertestEditorWithCookieCredentials .post(CHAT_API_URL) - .set(roleAuthc.apiKeyHeader) - .set(internalReqHeader) .send({ name: 'my_api_call', messages, @@ -104,10 +115,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { const receivedChunks: Array> = []; const passThrough = new PassThrough(); - supertestWithoutAuth + supertestEditorWithCookieCredentials .post(CHAT_API_URL) - .set(roleAuthc.apiKeyHeader) - .set(internalReqHeader) .on('error', reject) .send({ name: 'my_api_call', diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/complete.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/complete.spec.ts index 80548c0369a83..d411d097a0d7f 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/complete.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/complete.spec.ts @@ -25,6 +25,7 @@ import { LlmResponseSimulator, } from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/common/create_llm_proxy'; import { createOpenAiChunk } from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/common/create_openai_chunk'; +import { SupertestWithRoleScope } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services/role_scoped_supertest'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { decodeEvents, @@ -39,6 +40,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { const log = getService('log'); const svlUserManager = getService('svlUserManager'); const svlCommonApi = getService('svlCommonApi'); + const roleScopedSupertest = getService('roleScopedSupertest'); + + let supertestEditorWithCookieCredentials: SupertestWithRoleScope; const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); @@ -82,10 +86,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { (body) => !isFunctionTitleRequest(body) ); const responsePromise = new Promise((resolve, reject) => { - supertestWithoutAuth + supertestEditorWithCookieCredentials .post(COMPLETE_API_URL) - .set(roleAuthc.apiKeyHeader) - .set(internalReqHeader) .send({ messages, connectorId, @@ -134,6 +136,14 @@ export default function ApiTest({ getService }: FtrProviderContext) { roleAuthc, internalReqHeader, }); + + supertestEditorWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope( + 'editor', + { + useCookieHeader: true, + withInternalHeaders: true, + } + ); }); after(async () => { @@ -155,10 +165,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { const passThrough = new PassThrough(); - supertestWithoutAuth + supertestEditorWithCookieCredentials .post(COMPLETE_API_URL) - .set(roleAuthc.apiKeyHeader) - .set(internalReqHeader) .send({ messages, connectorId, @@ -254,6 +262,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, }); }); + describe('when creating a new conversation', () => { let events: StreamingChatResponseEvent[]; @@ -273,12 +282,14 @@ export default function ApiTest({ getService }: FtrProviderContext) { content: 'Hello', }, }); + expect(omit(events[1], 'id')).to.eql({ type: StreamingChatResponseEventType.ChatCompletionChunk, message: { content: ' again', }, }); + expect(omit(events[2], 'id', 'message.@timestamp')).to.eql({ type: StreamingChatResponseEventType.ChatCompletionMessage, message: { @@ -329,10 +340,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { )[0]?.conversation.id; await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}', - roleAuthc, - internalReqHeader, params: { path: { conversationId: createdConversationId, @@ -417,10 +426,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { ).to.eql(0); const conversations = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'POST /internal/observability_ai_assistant/conversations', - roleAuthc, - internalReqHeader, }) .expect(200); @@ -449,10 +456,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { .completeAfterIntercept(); const createResponse = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'POST /internal/observability_ai_assistant/chat/complete', - roleAuthc, - internalReqHeader, params: { body: { messages, @@ -470,10 +475,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { conversationCreatedEvent = getConversationCreatedEvent(createResponse.body); const conversationId = conversationCreatedEvent.conversation.id; - const fullConversation = await observabilityAIAssistantAPIClient.slsUser({ + const fullConversation = await observabilityAIAssistantAPIClient.slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}', - internalReqHeader, - roleAuthc, params: { path: { conversationId, @@ -486,10 +489,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { .completeAfterIntercept(); const updatedResponse = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'POST /internal/observability_ai_assistant/chat/complete', - internalReqHeader, - roleAuthc, params: { body: { messages: [ @@ -519,10 +520,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { after(async () => { await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}', - internalReqHeader, - roleAuthc, params: { path: { conversationId: conversationCreatedEvent.conversation.id, diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/functions/elasticsearch.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/functions/elasticsearch.spec.ts index 4ba276333cc55..c0eca93351b49 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/functions/elasticsearch.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/functions/elasticsearch.spec.ts @@ -63,8 +63,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { const responseBody = await invokeChatCompleteWithFunctionRequest({ connectorId, observabilityAIAssistantAPIClient, - internalReqHeader, - roleAuthc, functionCall: { name: ELASTICSEARCH_FUNCTION_NAME, trigger: MessageRole.User, diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/functions/helpers.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/functions/helpers.ts index 758046de72f2b..4203a2312ce5d 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/functions/helpers.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/functions/helpers.ts @@ -13,7 +13,6 @@ import { } from '@kbn/observability-ai-assistant-plugin/common'; import type { AssistantScope } from '@kbn/ai-assistant-common'; import { Readable } from 'stream'; -import type { InternalRequestHeader, RoleCredentials } from '../../../../../../../shared/services'; import { ObservabilityAIAssistantApiClient } from '../../../common/observability_ai_assistant_api_client'; function decodeEvents(body: Readable | string) { @@ -34,22 +33,16 @@ export async function invokeChatCompleteWithFunctionRequest({ connectorId, observabilityAIAssistantAPIClient, functionCall, - roleAuthc, - internalReqHeader, scopes, }: { connectorId: string; observabilityAIAssistantAPIClient: ObservabilityAIAssistantApiClient; functionCall: Message['message']['function_call']; scopes?: AssistantScope[]; - roleAuthc: RoleCredentials; - internalReqHeader: InternalRequestHeader; }) { const { body } = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'POST /internal/observability_ai_assistant/chat/complete', - internalReqHeader, - roleAuthc, params: { body: { messages: [ diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/functions/summarize.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/functions/summarize.spec.ts index 6be39a36c62a5..f949268aa730a 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/functions/summarize.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/functions/summarize.spec.ts @@ -53,8 +53,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { await invokeChatCompleteWithFunctionRequest({ connectorId, observabilityAIAssistantAPIClient, - internalReqHeader, - roleAuthc, functionCall: { name: 'summarize', trigger: MessageRole.User, @@ -77,10 +75,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('persists entry in knowledge base', async () => { - const res = await observabilityAIAssistantAPIClient.slsUser({ + const res = await observabilityAIAssistantAPIClient.slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/kb/entries', - internalReqHeader, - roleAuthc, params: { query: { query: '', diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/connectors/connectors.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/connectors/connectors.spec.ts index 9305c0d1e14e9..2096abe74e2e8 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/connectors/connectors.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/connectors/connectors.spec.ts @@ -24,6 +24,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('List connectors', () => { let roleAuthc: RoleCredentials; let internalReqHeader: InternalRequestHeader; + before(async () => { roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('editor'); internalReqHeader = svlCommonApi.getInternalRequestHeader(); @@ -45,19 +46,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('Returns a 2xx for enterprise license', async () => { await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/connectors', - roleAuthc, - internalReqHeader, }) .expect(200); }); it('returns an empty list of connectors', async () => { - const res = await observabilityAIAssistantAPIClient.slsUser({ + const res = await observabilityAIAssistantAPIClient.slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/connectors', - roleAuthc, - internalReqHeader, }); expect(res.body.length).to.be(0); @@ -72,10 +69,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { roleAuthc, }); - const res = await observabilityAIAssistantAPIClient.slsUser({ + const res = await observabilityAIAssistantAPIClient.slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/connectors', - internalReqHeader, - roleAuthc, }); expect(res.body.length).to.be(1); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/conversations/conversations.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/conversations/conversations.spec.ts index b4426744e4082..7033e0660f5c6 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/conversations/conversations.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/conversations/conversations.spec.ts @@ -14,12 +14,9 @@ import { } from '@kbn/observability-ai-assistant-plugin/common/types'; import type { FtrProviderContext } from '../../common/ftr_provider_context'; import type { SupertestReturnType } from '../../common/observability_ai_assistant_api_client'; -import type { InternalRequestHeader, RoleCredentials } from '../../../../../../shared/services'; export default function ApiTest({ getService }: FtrProviderContext) { const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); - const svlUserManager = getService('svlUserManager'); - const svlCommonApi = getService('svlCommonApi'); const conversationCreate: ConversationCreateRequest = { '@timestamp': new Date().toISOString(), @@ -48,22 +45,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); describe('Conversations', () => { - let roleAuthc: RoleCredentials; - let internalReqHeader: InternalRequestHeader; - before(async () => { - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('editor'); - internalReqHeader = svlCommonApi.getInternalRequestHeader(); - }); - after(async () => { - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); describe('without conversations', () => { it('returns no conversations when listing', async () => { const response = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'POST /internal/observability_ai_assistant/conversations', - internalReqHeader, - roleAuthc, }) .expect(200); @@ -72,10 +58,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns a 404 for updating conversations', async () => { await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'PUT /internal/observability_ai_assistant/conversation/{conversationId}', - internalReqHeader, - roleAuthc, params: { path: { conversationId: 'non-existing-conversation-id', @@ -90,10 +74,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns a 404 for retrieving a conversation', async () => { await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}', - internalReqHeader, - roleAuthc, params: { path: { conversationId: 'my-conversation-id', @@ -108,12 +90,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { let createResponse: Awaited< SupertestReturnType<'POST /internal/observability_ai_assistant/conversation'> >; + before(async () => { createResponse = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'POST /internal/observability_ai_assistant/conversation', - roleAuthc, - internalReqHeader, params: { body: { conversation: conversationCreate, @@ -125,10 +106,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { after(async () => { await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}', - internalReqHeader, - roleAuthc, params: { path: { conversationId: createResponse.body.conversation.id, @@ -138,10 +117,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { .expect(200); await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}', - internalReqHeader, - roleAuthc, params: { path: { conversationId: createResponse.body.conversation.id, @@ -150,6 +127,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }) .expect(404); }); + it('returns the conversation', function () { // delete user from response to avoid comparing it as it will be different in MKI delete createResponse.body.user; @@ -170,10 +148,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns a 404 for updating a non-existing conversation', async () => { await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'PUT /internal/observability_ai_assistant/conversation/{conversationId}', - roleAuthc, - internalReqHeader, params: { path: { conversationId: 'non-existing-conversation-id', @@ -188,10 +164,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns a 404 for retrieving a non-existing conversation', async () => { await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}', - roleAuthc, - internalReqHeader, params: { path: { conversationId: 'non-existing-conversation-id', @@ -203,10 +177,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the conversation that was created', async () => { const response = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}', - internalReqHeader, - roleAuthc, params: { path: { conversationId: createResponse.body.conversation.id, @@ -222,10 +194,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the created conversation when listing', async () => { const response = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'POST /internal/observability_ai_assistant/conversations', - roleAuthc, - internalReqHeader, }) .expect(200); // delete user from response to avoid comparing it as it will be different in MKI @@ -243,10 +213,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { updateResponse = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'PUT /internal/observability_ai_assistant/conversation/{conversationId}', - internalReqHeader, - roleAuthc, params: { path: { conversationId: createResponse.body.conversation.id, @@ -269,10 +237,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the updated conversation after get', async () => { const updateAfterCreateResponse = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}', - internalReqHeader, - roleAuthc, params: { path: { conversationId: createResponse.body.conversation.id, diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/conversations/helpers.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/conversations/helpers.ts index 7e72b4ed57bf9..cc35bb1a71298 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/conversations/helpers.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/conversations/helpers.ts @@ -14,7 +14,6 @@ import { StreamingChatResponseEventType, } from '@kbn/observability-ai-assistant-plugin/common/conversation_complete'; import { ObservabilityAIAssistantApiClient } from '../../common/observability_ai_assistant_api_client'; -import type { InternalRequestHeader, RoleCredentials } from '../../../../../../shared/services'; export function decodeEvents(body: Readable | string) { return String(body) @@ -56,20 +55,14 @@ export function getConversationUpdatedEvent(body: Readable | string) { export async function deleteAllConversations({ observabilityAIAssistantAPIClient, - internalReqHeader, - roleAuthc, log, }: { observabilityAIAssistantAPIClient: ObservabilityAIAssistantApiClient; - internalReqHeader: InternalRequestHeader; - roleAuthc: RoleCredentials; log: ToolingLog; }) { const findConversationsResponse = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'POST /internal/observability_ai_assistant/conversations', - internalReqHeader, - roleAuthc, params: { body: { query: '', @@ -87,10 +80,8 @@ export async function deleteAllConversations({ conversations.map(async (conversation) => { try { await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}', - internalReqHeader, - roleAuthc, params: { path: { conversationId: conversation.conversation.id, diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base.spec.ts index 8f3569c9fc959..9dc0fba6a5685 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base.spec.ts @@ -13,59 +13,51 @@ import { deleteKnowledgeBaseModel, } from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/tests/knowledge_base/helpers'; import { FtrProviderContext } from '../../common/ftr_provider_context'; -import type { InternalRequestHeader, RoleCredentials } from '../../../../../../shared/services'; export default function ApiTest({ getService }: FtrProviderContext) { const ml = getService('ml'); const es = getService('es'); - const svlUserManager = getService('svlUserManager'); - const svlCommonApi = getService('svlCommonApi'); const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); // TODO: https://github.com/elastic/kibana/issues/192886 describe.skip('Knowledge base', function () { this.tags(['skipMKI']); - let roleAuthc: RoleCredentials; - let internalReqHeader: InternalRequestHeader; + before(async () => { - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('editor'); - internalReqHeader = svlCommonApi.getInternalRequestHeader(); await createKnowledgeBaseModel(ml); }); after(async () => { await deleteKnowledgeBaseModel(ml); await deleteInferenceEndpoint({ es }); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); }); it('returns 200 on knowledge base setup', async () => { const res = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'POST /internal/observability_ai_assistant/kb/setup', - roleAuthc, - internalReqHeader, }) .expect(200); expect(res.body).to.eql({}); }); + describe('when managing a single entry', () => { const knowledgeBaseEntry = { id: 'my-doc-id-1', title: 'My title', text: 'My content', }; + it('returns 200 on create', async () => { await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'POST /internal/observability_ai_assistant/kb/entries/save', params: { body: knowledgeBaseEntry }, - roleAuthc, - internalReqHeader, }) .expect(200); - const res = await observabilityAIAssistantAPIClient.slsUser({ + + const res = await observabilityAIAssistantAPIClient.slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/kb/entries', params: { query: { @@ -74,8 +66,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { sortDirection: 'asc', }, }, - roleAuthc, - internalReqHeader, }); const entry = res.body.entries[0]; expect(entry.id).to.equal(knowledgeBaseEntry.id); @@ -84,7 +74,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns 200 on get entries and entry exists', async () => { const res = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/kb/entries', params: { query: { @@ -93,8 +83,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { sortDirection: 'asc', }, }, - roleAuthc, - internalReqHeader, }) .expect(200); const entry = res.body.entries[0]; @@ -105,18 +93,16 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns 200 on delete', async () => { const entryId = 'my-doc-id-1'; await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'DELETE /internal/observability_ai_assistant/kb/entries/{entryId}', params: { path: { entryId }, }, - roleAuthc, - internalReqHeader, }) .expect(200); const res = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/kb/entries', params: { query: { @@ -125,8 +111,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { sortDirection: 'asc', }, }, - roleAuthc, - internalReqHeader, }) .expect(200); expect(res.body.entries.filter((entry) => entry.id.startsWith('my-doc-id')).length).to.eql( @@ -137,24 +121,25 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns 500 on delete not found', async () => { const entryId = 'my-doc-id-1'; await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'DELETE /internal/observability_ai_assistant/kb/entries/{entryId}', params: { path: { entryId }, }, - roleAuthc, - internalReqHeader, }) .expect(500); }); }); + describe('when managing multiple entries', () => { before(async () => { await clearKnowledgeBase(es); }); + afterEach(async () => { await clearKnowledgeBase(es); }); + const knowledgeBaseEntries = [ { id: 'my_doc_a', @@ -172,18 +157,17 @@ export default function ApiTest({ getService }: FtrProviderContext) { text: 'My content c', }, ]; + it('returns 200 on create', async () => { await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'POST /internal/observability_ai_assistant/kb/entries/import', params: { body: { entries: knowledgeBaseEntries } }, - roleAuthc, - internalReqHeader, }) .expect(200); const res = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/kb/entries', params: { query: { @@ -192,8 +176,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { sortDirection: 'asc', }, }, - roleAuthc, - internalReqHeader, }) .expect(200); expect(res.body.entries.filter((entry) => entry.id.startsWith('my_doc')).length).to.eql(3); @@ -201,16 +183,14 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('allows sorting', async () => { await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'POST /internal/observability_ai_assistant/kb/entries/import', params: { body: { entries: knowledgeBaseEntries } }, - roleAuthc, - internalReqHeader, }) .expect(200); const res = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/kb/entries', params: { query: { @@ -219,8 +199,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { sortDirection: 'desc', }, }, - roleAuthc, - internalReqHeader, }) .expect(200); @@ -231,7 +209,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { // asc const resAsc = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/kb/entries', params: { query: { @@ -240,8 +218,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { sortDirection: 'asc', }, }, - roleAuthc, - internalReqHeader, }) .expect(200); @@ -250,18 +226,17 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(entriesAsc[1].id).to.eql('my_doc_b'); expect(entriesAsc[2].id).to.eql('my_doc_c'); }); + it('allows searching', async () => { await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'POST /internal/observability_ai_assistant/kb/entries/import', params: { body: { entries: knowledgeBaseEntries } }, - roleAuthc, - internalReqHeader, }) .expect(200); const res = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/kb/entries', params: { query: { @@ -270,8 +245,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { sortDirection: 'asc', }, }, - roleAuthc, - internalReqHeader, }) .expect(200); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_setup.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_setup.spec.ts index eeef06464c9d6..88edb533ecb36 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_setup.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_setup.spec.ts @@ -14,44 +14,30 @@ import { } from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/tests/knowledge_base/helpers'; import { FtrProviderContext } from '../../common/ftr_provider_context'; -import type { InternalRequestHeader, RoleCredentials } from '../../../../../../shared/services'; export default function ApiTest({ getService }: FtrProviderContext) { const ml = getService('ml'); const es = getService('es'); - const svlUserManager = getService('svlUserManager'); - const svlCommonApi = getService('svlCommonApi'); const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); describe('/internal/observability_ai_assistant/kb/setup', function () { this.tags(['skipMKI']); - let roleAuthc: RoleCredentials; - let internalReqHeader: InternalRequestHeader; before(async () => { await deleteKnowledgeBaseModel(ml).catch(() => {}); await deleteInferenceEndpoint({ es }).catch(() => {}); - - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - internalReqHeader = svlCommonApi.getInternalRequestHeader(); - }); - - after(async () => { - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); }); it('returns empty object when successful', async () => { await createKnowledgeBaseModel(ml); const res = await observabilityAIAssistantAPIClient - .slsUser({ + .slsAdmin({ endpoint: 'POST /internal/observability_ai_assistant/kb/setup', params: { query: { model_id: TINY_ELSER.id, }, }, - roleAuthc, - internalReqHeader, }) .expect(200); @@ -64,15 +50,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns bad request if model cannot be installed', async () => { const res = await observabilityAIAssistantAPIClient - .slsUser({ + .slsAdmin({ endpoint: 'POST /internal/observability_ai_assistant/kb/setup', params: { query: { model_id: TINY_ELSER.id, }, }, - roleAuthc, - internalReqHeader, }) .expect(500); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_status.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_status.spec.ts index a7a7b55cf4e02..207badc1b855a 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_status.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_status.spec.ts @@ -14,34 +14,25 @@ import { } from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/tests/knowledge_base/helpers'; import { AI_ASSISTANT_KB_INFERENCE_ID } from '@kbn/observability-ai-assistant-plugin/server/service/inference_endpoint'; import { FtrProviderContext } from '../../common/ftr_provider_context'; -import type { InternalRequestHeader, RoleCredentials } from '../../../../../../shared/services'; export default function ApiTest({ getService }: FtrProviderContext) { const ml = getService('ml'); const es = getService('es'); - const svlUserManager = getService('svlUserManager'); - const svlCommonApi = getService('svlCommonApi'); const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); describe('/internal/observability_ai_assistant/kb/status', function () { this.tags(['skipMKI']); - let roleAuthc: RoleCredentials; - let internalReqHeader: InternalRequestHeader; before(async () => { - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - internalReqHeader = svlCommonApi.getInternalRequestHeader(); await createKnowledgeBaseModel(ml); await observabilityAIAssistantAPIClient - .slsUser({ + .slsAdmin({ endpoint: 'POST /internal/observability_ai_assistant/kb/setup', params: { query: { model_id: TINY_ELSER.id, }, }, - roleAuthc, - internalReqHeader, }) .expect(200); }); @@ -49,15 +40,12 @@ export default function ApiTest({ getService }: FtrProviderContext) { after(async () => { await deleteKnowledgeBaseModel(ml); await deleteInferenceEndpoint({ es, name: AI_ASSISTANT_KB_INFERENCE_ID }).catch((err) => {}); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); }); it('returns correct status after knowledge base is setup', async () => { const res = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/kb/status', - roleAuthc, - internalReqHeader, }) .expect(200); @@ -70,10 +58,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { await deleteInferenceEndpoint({ es, name: AI_ASSISTANT_KB_INFERENCE_ID }); const res = await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/kb/status', - roleAuthc, - internalReqHeader, }) .expect(200); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_user_instructions.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_user_instructions.spec.ts index fc9864ac8768c..0557d43830bc0 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_user_instructions.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_user_instructions.spec.ts @@ -34,26 +34,19 @@ export default function ApiTest({ getService }: FtrProviderContext) { const svlUserManager = getService('svlUserManager'); const svlCommonApi = getService('svlCommonApi'); - // TODO: https://github.com/elastic/kibana/issues/192711 cannot create custom users in serverless - // trying using built in users by using cookie auth - // TODO: https://github.com/elastic/kibana/issues/192757 describe.skip('Knowledge base user instructions', function () { this.tags(['skipMKI']); let editorRoleAuthc: RoleCredentials; - let johnRoleAuthc: RoleCredentials; let internalReqHeader: InternalRequestHeader; + before(async () => { - // Create API keys for 'editor' role, simulating different users - johnRoleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); editorRoleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('editor'); internalReqHeader = svlCommonApi.getInternalRequestHeader(); await createKnowledgeBaseModel(ml); await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'POST /internal/observability_ai_assistant/kb/setup', - roleAuthc: editorRoleAuthc, - internalReqHeader, }) .expect(200); }); @@ -63,7 +56,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { await deleteInferenceEndpoint({ es }); await clearKnowledgeBase(es); await clearConversations(es); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(johnRoleAuthc); await svlUserManager.invalidateM2mApiKeyWithRoleScope(editorRoleAuthc); }); @@ -72,37 +64,34 @@ export default function ApiTest({ getService }: FtrProviderContext) { await clearKnowledgeBase(es); const promises = [ - { roleAuthc: editorRoleAuthc, username: 'editor', isPublic: true }, - { roleAuthc: editorRoleAuthc, username: 'editor', isPublic: false }, - { roleAuthc: johnRoleAuthc, username: 'john', isPublic: true }, - { roleAuthc: johnRoleAuthc, username: 'john', isPublic: false }, - ].map(async ({ roleAuthc, username, isPublic }) => { + { username: 'editor', isPublic: true }, + { username: 'editor', isPublic: false }, + { username: 'john', isPublic: true }, + { username: 'john', isPublic: false }, + ].map(async ({ username, isPublic }) => { const visibility = isPublic ? 'Public' : 'Private'; - await observabilityAIAssistantAPIClient - .slsUser({ - endpoint: 'PUT /internal/observability_ai_assistant/kb/user_instructions', - params: { - body: { - id: `${visibility.toLowerCase()}-doc-from-${username}`, - text: `${visibility} user instruction from "${username}"`, - public: isPublic, - }, + const user = username === 'editor' ? 'slsEditor' : 'slsAdmin'; + + await observabilityAIAssistantAPIClient[user]({ + endpoint: 'PUT /internal/observability_ai_assistant/kb/user_instructions', + params: { + body: { + id: `${visibility.toLowerCase()}-doc-from-${username}`, + text: `${visibility} user instruction from "${username}"`, + public: isPublic, }, - roleAuthc, - internalReqHeader, - }) - .expect(200); + }, + }).expect(200); }); await Promise.all(promises); }); it('"editor" can retrieve their own private instructions and the public instruction', async () => { - const res = await observabilityAIAssistantAPIClient.slsUser({ + const res = await observabilityAIAssistantAPIClient.slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions', - roleAuthc: editorRoleAuthc, - internalReqHeader, }); + const instructions = res.body.userInstructions; const sortByDocId = (data: any) => sortBy(data, 'doc_id'); @@ -128,11 +117,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('"john" can retrieve their own private instructions and the public instruction', async () => { - const res = await observabilityAIAssistantAPIClient.slsUser({ + const res = await observabilityAIAssistantAPIClient.slsAdmin({ endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions', - roleAuthc: johnRoleAuthc, - internalReqHeader, }); + const instructions = res.body.userInstructions; const sortByDocId = (data: any) => sortBy(data, 'doc_id'); @@ -163,7 +151,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { await clearKnowledgeBase(es); await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'PUT /internal/observability_ai_assistant/kb/user_instructions', params: { body: { @@ -172,13 +160,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { public: true, }, }, - roleAuthc: editorRoleAuthc, - internalReqHeader, }) .expect(200); await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'PUT /internal/observability_ai_assistant/kb/user_instructions', params: { body: { @@ -187,18 +173,15 @@ export default function ApiTest({ getService }: FtrProviderContext) { public: false, }, }, - roleAuthc: editorRoleAuthc, - internalReqHeader, }) .expect(200); }); it('updates the user instruction', async () => { - const res = await observabilityAIAssistantAPIClient.slsUser({ + const res = await observabilityAIAssistantAPIClient.slsEditor({ endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions', - roleAuthc: editorRoleAuthc, - internalReqHeader, }); + const instructions = res.body.userInstructions; expect(instructions).to.eql([ @@ -218,10 +201,12 @@ export default function ApiTest({ getService }: FtrProviderContext) { const userInstructionText = 'Be polite and use language that is easy to understand. Never disagree with the user.'; - async function getConversationForUser(roleAuthc: RoleCredentials) { + async function getConversationForUser(username: string) { + const user = username === 'editor' ? 'slsEditor' : 'slsAdmin'; + // the user instruction is always created by "editor" user await observabilityAIAssistantAPIClient - .slsUser({ + .slsEditor({ endpoint: 'PUT /internal/observability_ai_assistant/kb/user_instructions', params: { body: { @@ -230,8 +215,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { public: false, }, }, - roleAuthc: editorRoleAuthc, - internalReqHeader, }) .expect(200); @@ -259,36 +242,30 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, ]; - const createResponse = await observabilityAIAssistantAPIClient - .slsUser({ - endpoint: 'POST /internal/observability_ai_assistant/chat/complete', - params: { - body: { - messages, - connectorId, - persist: true, - screenContexts: [], - scopes: ['observability'], - }, + const createResponse = await observabilityAIAssistantAPIClient[user]({ + endpoint: 'POST /internal/observability_ai_assistant/chat/complete', + params: { + body: { + messages, + connectorId, + persist: true, + screenContexts: [], + scopes: ['observability'], }, - roleAuthc, - internalReqHeader, - }) - .expect(200); + }, + }).expect(200); await proxy.waitForAllInterceptorsSettled(); const conversationCreatedEvent = getConversationCreatedEvent(createResponse.body); const conversationId = conversationCreatedEvent.conversation.id; - const res = await observabilityAIAssistantAPIClient.slsUser({ + const res = await observabilityAIAssistantAPIClient[user]({ endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}', params: { path: { conversationId, }, }, - roleAuthc, - internalReqHeader, }); // wait for all interceptors to be settled @@ -321,7 +298,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('adds the instruction to the system prompt', async () => { - const conversation = await getConversationForUser(editorRoleAuthc); + const conversation = await getConversationForUser('editor'); const systemMessage = conversation.messages.find( (message) => message.message.role === MessageRole.System )!; @@ -329,7 +306,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('does not add the instruction to the context', async () => { - const conversation = await getConversationForUser(editorRoleAuthc); + const conversation = await getConversationForUser('editor'); const contextMessage = conversation.messages.find( (message) => message.message.name === CONTEXT_FUNCTION_NAME ); @@ -343,7 +320,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('does not add the instruction conversation for other users', async () => { - const conversation = await getConversationForUser(johnRoleAuthc); + const conversation = await getConversationForUser('john'); const systemMessage = conversation.messages.find( (message) => message.message.role === MessageRole.System )!; diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/public_complete/public_complete.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/public_complete/public_complete.spec.ts index 72e46e179443e..a3134ab2e3014 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/public_complete/public_complete.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/public_complete/public_complete.spec.ts @@ -48,6 +48,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, }, ]; + describe('/api/observability_ai_assistant/chat/complete', function () { // TODO: https://github.com/elastic/kibana/issues/192751 this.tags(['skipMKI']); @@ -108,6 +109,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { await titleSimulator.complete(); await conversationSimulator.status(200); + if (conversationSimulatorCallback) { await conversationSimulatorCallback(conversationSimulator); } @@ -158,8 +160,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { after(async () => { await deleteAllConversations({ observabilityAIAssistantAPIClient, - internalReqHeader, - roleAuthc, log, }); await deleteActionConnector({ supertest, connectorId, log, roleAuthc, internalReqHeader }); From ebb4f503a5c9c0a3d883a706566920ae27fedfad Mon Sep 17 00:00:00 2001 From: Rodney Norris Date: Mon, 9 Dec 2024 12:36:16 -0600 Subject: [PATCH 3/5] [Search] Inference Endpoints - Align rendering to plugin (#203313) ## Summary This PR removes rendering of the Inference Endpoints UI from `enterprise_search` for stack and instead utilizes the `search_inference_endpoints` plugin to render the UI for both serverless and stack. This can be done by utilizing the `search_navigation` plugin for rendering the classic navigation for stack. To support this change the `xpack.searchInferenceEndpoints.ui.enabled` was updated to default to `true` instead of only being set for serverless search. To account for this change I have added `xpack.searchInferenceEndpoints.enabled: false` to the serverless configs for both security and observability to ensure the `search_inference_endpoints` plugin is disabled in both of those projects. ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine --- config/serverless.es.yml | 3 - config/serverless.oblt.yml | 1 + config/serverless.security.yml | 1 + packages/deeplinks/search/constants.ts | 1 - packages/deeplinks/search/deep_links.ts | 7 +- packages/deeplinks/search/index.ts | 1 - .../enterprise_search/common/constants.ts | 17 ---- .../common/locators/index.ts | 3 - .../common/locators/inference_locator.tsx | 30 ------- x-pack/plugins/enterprise_search/kibana.jsonc | 1 - .../__mocks__/kea_logic/kibana_logic.mock.ts | 1 - .../components/inference_endpoints.tsx | 41 ---------- .../components/layout/index.ts | 8 -- .../components/layout/page_template.test.tsx | 79 ------------------- .../components/layout/page_template.tsx | 37 --------- .../components/not_found/index.ts | 8 -- .../components/not_found/not_found.test.tsx | 38 --------- .../components/not_found/not_found.tsx | 23 ------ .../index.test.tsx | 23 ------ .../enterprise_search_relevance/index.tsx | 41 ---------- .../jest.config.js | 28 ------- .../enterprise_search_relevance/routes.ts | 10 --- .../public/applications/index.tsx | 1 - .../shared/kibana/kibana_logic.ts | 4 - .../kibana_chrome/generate_breadcrumbs.ts | 4 - .../shared/kibana_chrome/index.ts | 1 - .../shared/kibana_chrome/set_chrome.tsx | 14 ---- .../enterprise_search/public/plugin.ts | 57 +++---------- .../enterprise_search/server/plugin.ts | 2 - .../plugins/enterprise_search/tsconfig.json | 1 - .../common/translations.ts | 14 ++++ .../search_inference_endpoints/kibana.jsonc | 4 +- .../public/embeddable.tsx | 29 ------- .../use_inference_endpoints_breadcrumbs.ts | 27 +++++++ .../public/index.ts | 2 - .../public/inference_endpoints_overview.tsx | 17 ++-- .../public/plugin.ts | 44 ++++++++--- .../public/types.ts | 30 +++---- .../server/config.ts | 2 +- .../server/plugin.ts | 2 +- .../search_inference_endpoints/tsconfig.json | 8 +- .../hooks/use_playground_breadcrumbs.ts | 16 +++- .../translations/translations/fr-FR.json | 5 -- .../translations/translations/ja-JP.json | 4 - .../translations/translations/zh-CN.json | 5 -- 45 files changed, 133 insertions(+), 562 deletions(-) delete mode 100644 x-pack/plugins/enterprise_search/common/locators/inference_locator.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/inference_endpoints.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/index.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/index.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/jest.config.js delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/routes.ts delete mode 100644 x-pack/plugins/search_inference_endpoints/public/embeddable.tsx create mode 100644 x-pack/plugins/search_inference_endpoints/public/hooks/use_inference_endpoints_breadcrumbs.ts diff --git a/config/serverless.es.yml b/config/serverless.es.yml index 127a0c8362d7f..2d5ba84ece795 100644 --- a/config/serverless.es.yml +++ b/config/serverless.es.yml @@ -116,9 +116,6 @@ xpack.ml.compatibleModuleType: 'search' data_visualizer.resultLinks.fileBeat.enabled: false -# Search InferenceEndpoints -xpack.searchInferenceEndpoints.ui.enabled: true - # Search Notebooks xpack.search.notebooks.catalog.url: https://elastic-enterprise-search.s3.us-east-2.amazonaws.com/serverless/catalog.json diff --git a/config/serverless.oblt.yml b/config/serverless.oblt.yml index ef3c9fd23121e..937954a1c5e84 100644 --- a/config/serverless.oblt.yml +++ b/config/serverless.oblt.yml @@ -8,6 +8,7 @@ xpack.uptime.enabled: true xpack.securitySolution.enabled: false xpack.search.notebooks.enabled: false xpack.searchPlayground.enabled: false +xpack.searchInferenceEndpoints.enabled: false ## Fine-tune the observability solution feature privileges. Also, refer to `serverless.yml` for the project-agnostic overrides. xpack.features.overrides: diff --git a/config/serverless.security.yml b/config/serverless.security.yml index 1b75b4b06cb25..b9190df608540 100644 --- a/config/serverless.security.yml +++ b/config/serverless.security.yml @@ -9,6 +9,7 @@ xpack.observability.enabled: false xpack.observabilityAIAssistant.enabled: false xpack.search.notebooks.enabled: false xpack.searchPlayground.enabled: false +xpack.searchInferenceEndpoints.enabled: false ## Fine-tune the security solution feature privileges. Also, refer to `serverless.yml` for the project-agnostic overrides. xpack.features.overrides: diff --git a/packages/deeplinks/search/constants.ts b/packages/deeplinks/search/constants.ts index a458b38aff3cc..cd632c5b24a03 100644 --- a/packages/deeplinks/search/constants.ts +++ b/packages/deeplinks/search/constants.ts @@ -9,7 +9,6 @@ export const ENTERPRISE_SEARCH_APP_ID = 'enterpriseSearch'; export const ENTERPRISE_SEARCH_CONTENT_APP_ID = 'enterpriseSearchContent'; -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/packages/deeplinks/search/deep_links.ts b/packages/deeplinks/search/deep_links.ts index ede9bc303dbe5..dcba1f7888597 100644 --- a/packages/deeplinks/search/deep_links.ts +++ b/packages/deeplinks/search/deep_links.ts @@ -14,7 +14,6 @@ import { ENTERPRISE_SEARCH_APP_ID, ENTERPRISE_SEARCH_CONTENT_APP_ID, ENTERPRISE_SEARCH_APPLICATIONS_APP_ID, - ENTERPRISE_SEARCH_RELEVANCE_APP_ID, ENTERPRISE_SEARCH_ANALYTICS_APP_ID, ENTERPRISE_SEARCH_APPSEARCH_APP_ID, ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID, @@ -33,7 +32,6 @@ import { export type EnterpriseSearchApp = typeof ENTERPRISE_SEARCH_APP_ID; export type EnterpriseSearchContentApp = typeof ENTERPRISE_SEARCH_CONTENT_APP_ID; export type EnterpriseSearchApplicationsApp = typeof ENTERPRISE_SEARCH_APPLICATIONS_APP_ID; -export type EnterpriseSearchRelevanceApp = typeof ENTERPRISE_SEARCH_RELEVANCE_APP_ID; export type EnterpriseSearchAnalyticsApp = typeof ENTERPRISE_SEARCH_ANALYTICS_APP_ID; export type EnterpriseSearchAppsearchApp = typeof ENTERPRISE_SEARCH_APPSEARCH_APP_ID; export type EnterpriseSearchWorkplaceSearchApp = typeof ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID; @@ -56,7 +54,7 @@ export type ApplicationsLinkId = 'searchApplications'; export type AppsearchLinkId = 'engines'; -export type RelevanceLinkId = 'inferenceEndpoints'; +export type SearchInferenceEndpointsLinkId = 'inferenceEndpoints'; export type SearchIndicesLinkId = typeof SEARCH_INDICES_CREATE_INDEX; @@ -64,7 +62,6 @@ export type DeepLinkId = | EnterpriseSearchApp | EnterpriseSearchContentApp | EnterpriseSearchApplicationsApp - | EnterpriseSearchRelevanceApp | EnterpriseSearchAnalyticsApp | EnterpriseSearchAppsearchApp | EnterpriseSearchWorkplaceSearchApp @@ -77,7 +74,7 @@ export type DeepLinkId = | `${EnterpriseSearchContentApp}:${ContentLinkId}` | `${EnterpriseSearchApplicationsApp}:${ApplicationsLinkId}` | `${EnterpriseSearchAppsearchApp}:${AppsearchLinkId}` - | `${EnterpriseSearchRelevanceApp}:${RelevanceLinkId}` + | `${SearchInferenceEndpointsId}:${SearchInferenceEndpointsLinkId}` | SearchStart | SearchIndices | SearchElasticsearch diff --git a/packages/deeplinks/search/index.ts b/packages/deeplinks/search/index.ts index 69110f36deb45..2b3d392971a5f 100644 --- a/packages/deeplinks/search/index.ts +++ b/packages/deeplinks/search/index.ts @@ -10,7 +10,6 @@ export { ENTERPRISE_SEARCH_APP_ID, ENTERPRISE_SEARCH_CONTENT_APP_ID, - ENTERPRISE_SEARCH_RELEVANCE_APP_ID, ENTERPRISE_SEARCH_APPLICATIONS_APP_ID, ENTERPRISE_SEARCH_ANALYTICS_APP_ID, ENTERPRISE_SEARCH_APPSEARCH_APP_ID, diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index 2603ea3d89018..13afad3f1a342 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -10,7 +10,6 @@ import dedent from 'dedent'; import { ENTERPRISE_SEARCH_APP_ID, ENTERPRISE_SEARCH_CONTENT_APP_ID, - ENTERPRISE_SEARCH_RELEVANCE_APP_ID, ENTERPRISE_SEARCH_APPLICATIONS_APP_ID, ENTERPRISE_SEARCH_ANALYTICS_APP_ID, ENTERPRISE_SEARCH_APPSEARCH_APP_ID, @@ -200,22 +199,6 @@ export const SEMANTIC_SEARCH_PLUGIN = { URL: '/app/enterprise_search/semantic_search', }; -export const SEARCH_RELEVANCE_PLUGIN = { - ID: ENTERPRISE_SEARCH_RELEVANCE_APP_ID, - NAME: i18n.translate('xpack.enterpriseSearch.inferenceEndpoints.productName', { - defaultMessage: 'Inference Endpoints', - }), - NAV_TITLE: i18n.translate('xpack.enterpriseSearch.inferenceEndpoints.navTitle', { - defaultMessage: 'Relevance', - }), - DESCRIPTION: i18n.translate('xpack.enterpriseSearch.inferenceEndpoints.description', { - defaultMessage: 'Manage your inference endpoints for semantic search and AI use cases.', - }), - URL: '/app/elasticsearch/relevance', - LOGO: 'logoEnterpriseSearch', - SUPPORT_URL: 'https://discuss.elastic.co/c/enterprise-search/', -}; - export const CREATE_CONNECTOR_PLUGIN = { CLI_SNIPPET: dedent`./bin/connectors connector create --index-name my-index diff --git a/x-pack/plugins/enterprise_search/common/locators/index.ts b/x-pack/plugins/enterprise_search/common/locators/index.ts index 3c3597fe4f161..e89c31a2f589b 100644 --- a/x-pack/plugins/enterprise_search/common/locators/index.ts +++ b/x-pack/plugins/enterprise_search/common/locators/index.ts @@ -6,15 +6,12 @@ */ import type { SharePluginSetup } from '@kbn/share-plugin/public'; -import { SerializableRecord } from '@kbn/utility-types'; import { CreateIndexLocatorDefinition, type CreateIndexLocatorParams, } from './create_index_locator'; -import { SearchInferenceEndpointLocatorDefinition } from './inference_locator'; export function registerLocators(share: SharePluginSetup) { share.url.locators.create(new CreateIndexLocatorDefinition()); - share.url.locators.create(new SearchInferenceEndpointLocatorDefinition()); } diff --git a/x-pack/plugins/enterprise_search/common/locators/inference_locator.tsx b/x-pack/plugins/enterprise_search/common/locators/inference_locator.tsx deleted file mode 100644 index f20d628bf1899..0000000000000 --- a/x-pack/plugins/enterprise_search/common/locators/inference_locator.tsx +++ /dev/null @@ -1,30 +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 type { LocatorDefinition } from '@kbn/share-plugin/common'; -import type { SharePluginSetup } from '@kbn/share-plugin/public'; -import type { SerializableRecord } from '@kbn/utility-types'; - -import { SEARCH_RELEVANCE_PLUGIN } from '../constants'; - -export function registerLocators(share: SharePluginSetup) { - share.url.locators.create(new SearchInferenceEndpointLocatorDefinition()); -} - -export class SearchInferenceEndpointLocatorDefinition - implements LocatorDefinition -{ - public readonly getLocation = async () => { - return { - app: SEARCH_RELEVANCE_PLUGIN.ID, - path: '/inference_endpoints', - state: {}, - }; - }; - - public readonly id = 'SEARCH_INFERENCE_ENDPOINTS'; -} diff --git a/x-pack/plugins/enterprise_search/kibana.jsonc b/x-pack/plugins/enterprise_search/kibana.jsonc index 65343904ba7fc..42e4db528a759 100644 --- a/x-pack/plugins/enterprise_search/kibana.jsonc +++ b/x-pack/plugins/enterprise_search/kibana.jsonc @@ -34,7 +34,6 @@ "guidedOnboarding", "console", "searchConnectors", - "searchInferenceEndpoints", "searchNavigation", "searchPlayground", "embeddable", 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 7af3dbc94343d..4dde6a5be527e 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 @@ -65,7 +65,6 @@ export const mockKibanaValues = { hasWebCrawler: true, }, renderHeaderActions: jest.fn(), - searchInferenceEndpoints: null, security: securityMock.createStart(), setBreadcrumbs: jest.fn(), setChromeIsVisible: jest.fn(), diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/inference_endpoints.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/inference_endpoints.tsx deleted file mode 100644 index bc3ca80c1c751..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/inference_endpoints.tsx +++ /dev/null @@ -1,41 +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 React from 'react'; - -import { useValues } from 'kea'; - -import { i18n } from '@kbn/i18n'; - -import { KibanaLogic } from '../../shared/kibana'; - -import { EnterpriseSearchRelevancePageTemplate } from './layout/page_template'; - -export const InferenceEndpoints: React.FC = () => { - const { searchInferenceEndpoints } = useValues(KibanaLogic); - - if (!searchInferenceEndpoints) { - return null; - } - return ( - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/index.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/index.ts deleted file mode 100644 index 0a155ce20b555..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/index.ts +++ /dev/null @@ -1,8 +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. - */ - -export { EnterpriseSearchRelevancePageTemplate } from './page_template'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.test.tsx deleted file mode 100644 index 246c9835a015a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.test.tsx +++ /dev/null @@ -1,79 +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. - */ - -jest.mock('../../../shared/layout/nav', () => ({ - useEnterpriseSearchNav: () => [], -})); - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { i18n } from '@kbn/i18n'; - -import { SetEnterpriseSearchRelevanceChrome } from '../../../shared/kibana_chrome'; -import { EnterpriseSearchPageTemplateWrapper } from '../../../shared/layout'; -import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; - -import { EnterpriseSearchRelevancePageTemplate } from './page_template'; - -describe('EnterpriseSearchRelevancePageTemplate', () => { - it('renders', () => { - const wrapper = shallow( - -
- {i18n.translate('xpack.enterpriseSearch..div.worldLabel', { defaultMessage: 'world' })} -
-
- ); - - expect(wrapper.type()).toEqual(EnterpriseSearchPageTemplateWrapper); - expect(wrapper.prop('solutionNav')).toEqual({ items: [], name: 'Elasticsearch' }); - expect(wrapper.find('.hello').text()).toEqual('world'); - }); - - describe('page chrome', () => { - it('takes a breadcrumb array & renders a product-specific page chrome', () => { - const wrapper = shallow(); - const setPageChrome = wrapper - .find(EnterpriseSearchPageTemplateWrapper) - .prop('setPageChrome') as any; - - expect(setPageChrome.type).toEqual(SetEnterpriseSearchRelevanceChrome); - expect(setPageChrome.props.trail).toEqual(['Some page']); - }); - }); - - describe('page telemetry', () => { - it('takes a metric & renders product-specific telemetry viewed event', () => { - const wrapper = shallow( - - ); - - expect(wrapper.find(SendEnterpriseSearchTelemetry).prop('action')).toEqual('viewed'); - expect(wrapper.find(SendEnterpriseSearchTelemetry).prop('metric')).toEqual('some_page'); - }); - }); - - describe('props', () => { - it('passes down any ...pageTemplateProps that EnterpriseSearchPageTemplateWrapper accepts', () => { - const wrapper = shallow( - } - /> - ); - - expect( - wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('pageHeader')!.pageTitle - ).toEqual('hello world'); - expect(wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('isLoading')).toEqual(false); - expect(wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('emptyState')).toEqual(
); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.tsx deleted file mode 100644 index 258b9c9a68ae1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.tsx +++ /dev/null @@ -1,37 +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 React from 'react'; - -import { SEARCH_PRODUCT_NAME } from '../../../../../common/constants'; -import { SetEnterpriseSearchRelevanceChrome } from '../../../shared/kibana_chrome'; -import { EnterpriseSearchPageTemplateWrapper, PageTemplateProps } from '../../../shared/layout'; -import { useEnterpriseSearchNav } from '../../../shared/layout'; -import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; - -export const EnterpriseSearchRelevancePageTemplate: React.FC = ({ - children, - pageChrome, - pageViewTelemetry, - ...pageTemplateProps -}) => { - return ( - } - > - {pageViewTelemetry && ( - - )} - {children} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/index.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/index.ts deleted file mode 100644 index 482c1a58faa9c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/index.ts +++ /dev/null @@ -1,8 +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. - */ - -export { NotFound } from './not_found'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.test.tsx deleted file mode 100644 index 7b5436958edb7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.test.tsx +++ /dev/null @@ -1,38 +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 React from 'react'; - -import { shallow } from 'enzyme'; - -import { NotFoundPrompt } from '../../../shared/not_found'; -import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; -import { EnterpriseSearchRelevancePageTemplate } from '../layout'; - -import { NotFound } from '.'; - -describe('NotFound', () => { - const wrapper = shallow(); - - it('renders the shared not found prompt', () => { - expect(wrapper.find(NotFoundPrompt)).toHaveLength(1); - }); - - it('renders a telemetry error event', () => { - expect(wrapper.find(SendEnterpriseSearchTelemetry).prop('action')).toEqual('error'); - }); - - it('passes optional preceding page chrome', () => { - wrapper.setProps({ pageChrome: ['Inference Endpoints', 'some-index'] }); - - expect(wrapper.find(EnterpriseSearchRelevancePageTemplate).prop('pageChrome')).toEqual([ - 'Inference Endpoints', - 'some-index', - '404', - ]); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.tsx deleted file mode 100644 index 22f5e8d913bd8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.tsx +++ /dev/null @@ -1,23 +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 React from 'react'; - -import { SEARCH_RELEVANCE_PLUGIN } from '../../../../../common/constants'; -import { PageTemplateProps } from '../../../shared/layout'; -import { NotFoundPrompt } from '../../../shared/not_found'; -import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; -import { EnterpriseSearchRelevancePageTemplate } from '../layout'; - -export const NotFound: React.FC = ({ pageChrome = [] }) => { - return ( - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.test.tsx deleted file mode 100644 index 6294a60128e2d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.test.tsx +++ /dev/null @@ -1,23 +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 '../__mocks__/shallow_useeffect.mock'; -import '../__mocks__/enterprise_search_url.mock'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EnterpriseSearchRelevance, EnterpriseSearchRelevanceConfigured } from '.'; - -describe('EnterpriseSearchRelevance', () => { - it('renders EnterpriseSearchRelevanceConfigured', () => { - const wrapper = shallow(); - - expect(wrapper.find(EnterpriseSearchRelevanceConfigured)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.tsx deleted file mode 100644 index 7594e514af54f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.tsx +++ /dev/null @@ -1,41 +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 React from 'react'; -import { Redirect } from 'react-router-dom'; - -import { Route, Routes } from '@kbn/shared-ux-router'; - -import { InitialAppData } from '../../../common/types'; - -import { InferenceEndpoints } from './components/inference_endpoints'; -import { NotFound } from './components/not_found'; -import { INFERENCE_ENDPOINTS_PATH, ROOT_PATH } from './routes'; - -export const EnterpriseSearchRelevance: React.FC = (props) => { - return ( - - - )} /> - - - ); -}; - -export const EnterpriseSearchRelevanceConfigured: React.FC> = () => { - return ( - - - - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/jest.config.js b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/jest.config.js deleted file mode 100644 index d186396d14a07..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/jest.config.js +++ /dev/null @@ -1,28 +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. - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../../../../../..', - roots: [ - '/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance', - ], - collectCoverage: true, - coverageReporters: ['text', 'html'], - collectCoverageFrom: [ - '/x-pack/plugins/enterprise_search/public/applications/**/*.{ts,tsx}', - '!/x-pack/plugins/enterprise_search/public/*.ts', - '!/x-pack/plugins/enterprise_search/server/*.ts', - '!/x-pack/plugins/enterprise_search/public/applications/test_helpers/**/*.{ts,tsx}', - ], - coverageDirectory: - '/target/kibana-coverage/jest/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance', - modulePathIgnorePatterns: [ - '/x-pack/plugins/enterprise_search/public/applications/app_search/cypress', - '/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress', - ], -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/routes.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/routes.ts deleted file mode 100644 index 5ea7ad1c781a8..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/routes.ts +++ /dev/null @@ -1,10 +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. - */ - -export const ROOT_PATH = '/'; -export const ERROR_STATE_PATH = '/error_state'; -export const INFERENCE_ENDPOINTS_PATH = `${ROOT_PATH}inference_endpoints`; diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx index 8cefb4cb733e5..d934932dd163f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -129,7 +129,6 @@ export const renderApp = ( params.setHeaderActionMenu( HeaderActions ? renderHeaderActions.bind(null, HeaderActions, store, params) : undefined ), - searchInferenceEndpoints: plugins.searchInferenceEndpoints, security, setBreadcrumbs: chrome.setBreadcrumbs, setChromeIsVisible: chrome.setIsVisible, 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 02a00a3198ee2..2185f0df344cc 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 @@ -29,7 +29,6 @@ import { LensPublicStart } from '@kbn/lens-plugin/public'; import { MlPluginStart } from '@kbn/ml-plugin/public'; import { ELASTICSEARCH_URL_PLACEHOLDER } from '@kbn/search-api-panels/constants'; import { ConnectorDefinition } from '@kbn/search-connectors'; -import { SearchInferenceEndpointsPluginStart } from '@kbn/search-inference-endpoints/public'; import { AuthenticatedUser, SecurityPluginStart } from '@kbn/security-plugin/public'; import { SharePluginStart } from '@kbn/share-plugin/public'; @@ -66,7 +65,6 @@ export interface KibanaLogicProps { productAccess: ProductAccess; productFeatures: ProductFeatures; renderHeaderActions(HeaderActions?: FC): void; - searchInferenceEndpoints?: SearchInferenceEndpointsPluginStart; security?: SecurityPluginStart; setBreadcrumbs(crumbs: ChromeBreadcrumb[]): void; setChromeIsVisible(isVisible: boolean): void; @@ -100,7 +98,6 @@ export interface KibanaValues { productAccess: ProductAccess; productFeatures: ProductFeatures; renderHeaderActions(HeaderActions?: FC): void; - searchInferenceEndpoints: SearchInferenceEndpointsPluginStart | null; security: SecurityPluginStart | null; setBreadcrumbs(crumbs: ChromeBreadcrumb[]): void; setChromeIsVisible(isVisible: boolean): void; @@ -146,7 +143,6 @@ export const KibanaLogic = kea>({ productAccess: [props.productAccess, {}], productFeatures: [props.productFeatures, {}], renderHeaderActions: [props.renderHeaderActions, {}], - searchInferenceEndpoints: [props.searchInferenceEndpoints || null, {}], security: [props.security || null, {}], setBreadcrumbs: [props.setBreadcrumbs, {}], setChromeIsVisible: [props.setChromeIsVisible, {}], 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 189ca53e362e1..024a84858f108 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 @@ -14,7 +14,6 @@ import { ANALYTICS_PLUGIN, APP_SEARCH_PLUGIN, ENTERPRISE_SEARCH_CONTENT_PLUGIN, - SEARCH_RELEVANCE_PLUGIN, ENTERPRISE_SEARCH_PRODUCT_NAME, AI_SEARCH_PLUGIN, SEARCH_EXPERIENCES_PLUGIN, @@ -160,9 +159,6 @@ export const useEnterpriseSearchContentBreadcrumbs = (breadcrumbs: Breadcrumbs = ...breadcrumbs, ]); -export const useEnterpriseSearchRelevanceBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => - useSearchBreadcrumbs([{ text: SEARCH_RELEVANCE_PLUGIN.NAV_TITLE, path: '/' }, ...breadcrumbs]); - export const useSearchExperiencesBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => useSearchBreadcrumbs([{ text: SEARCH_EXPERIENCES_PLUGIN.NAV_TITLE, path: '/' }, ...breadcrumbs]); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts index f9a6564ab5f28..2da72eb28b6e9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts @@ -9,7 +9,6 @@ export { SetSearchChrome, SetAnalyticsChrome, SetEnterpriseSearchContentChrome, - SetEnterpriseSearchRelevanceChrome, SetElasticsearchChrome, SetAiSearchChrome, SetAppSearchChrome, 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 0c05cb0c02ca0..c556213ec8b42 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 @@ -18,7 +18,6 @@ import { useEnterpriseSearchApplicationsBreadcrumbs, useAnalyticsBreadcrumbs, useEnterpriseSearchContentBreadcrumbs, - useEnterpriseSearchRelevanceBreadcrumbs, useAiSearchBreadcrumbs, useElasticsearchBreadcrumbs, useAppSearchBreadcrumbs, @@ -181,19 +180,6 @@ export const SetEnterpriseSearchContentChrome: React.FC = ({ tra return null; }; -export const SetEnterpriseSearchRelevanceChrome: React.FC = ({ trail = [] }) => { - const { setBreadcrumbs } = useValues(KibanaLogic); - - const crumbs = useGenerateBreadcrumbs(trail); - const breadcrumbs = useEnterpriseSearchRelevanceBreadcrumbs(crumbs); - - useEffect(() => { - setBreadcrumbs(breadcrumbs); - }, [trail]); - - return null; -}; - export const SetSearchExperiencesChrome: React.FC = ({ trail = [] }) => { const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic); diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index aa0a2e37c599c..b483796540ad1 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { BehaviorSubject, firstValueFrom } from 'rxjs'; +import { BehaviorSubject, firstValueFrom, type Subscription } from 'rxjs'; import { ChartsPluginStart } from '@kbn/charts-plugin/public'; import { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; @@ -34,7 +34,6 @@ import { MlPluginStart } from '@kbn/ml-plugin/public'; import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; import { ELASTICSEARCH_URL_PLACEHOLDER } from '@kbn/search-api-panels/constants'; import { SearchConnectorsPluginStart } from '@kbn/search-connectors-plugin/public'; -import { SearchInferenceEndpointsPluginStart } from '@kbn/search-inference-endpoints/public'; import type { SearchNavigationPluginStart } from '@kbn/search-navigation/public'; import { SearchPlaygroundPluginStart } from '@kbn/search-playground/public'; import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public'; @@ -53,7 +52,6 @@ import { VECTOR_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN, SEMANTIC_SEARCH_PLUGIN, - SEARCH_RELEVANCE_PLUGIN, } from '../common/constants'; import { registerLocators } from '../common/locators'; import { ClientConfigType, InitialAppData, ProductAccess } from '../common/types'; @@ -67,7 +65,6 @@ import { CRAWLERS_PATH, } from './applications/enterprise_search_content/routes'; -import { INFERENCE_ENDPOINTS_PATH } from './applications/enterprise_search_relevance/routes'; import { docLinks } from './applications/shared/doc_links'; import type { DynamicSideNavItems } from './navigation_tree'; @@ -99,7 +96,6 @@ export interface PluginsStart { ml?: MlPluginStart; navigation: NavigationPublicPluginStart; searchConnectors?: SearchConnectorsPluginStart; - searchInferenceEndpoints?: SearchInferenceEndpointsPluginStart; searchNavigation?: SearchNavigationPluginStart; searchPlayground?: SearchPlaygroundPluginStart; security?: SecurityPluginStart; @@ -136,20 +132,6 @@ const contentLinks: AppDeepLink[] = [ }, ]; -const relevanceLinks: AppDeepLink[] = [ - { - id: 'inferenceEndpoints', - path: `/${INFERENCE_ENDPOINTS_PATH}`, - title: i18n.translate( - 'xpack.enterpriseSearch.navigation.relevanceInferenceEndpointsLinkLabel', - { - defaultMessage: 'Inference Endpoints', - } - ), - visibleIn: ['globalSearch'], - }, -]; - const applicationsLinks: AppDeepLink[] = [ { id: 'searchApplications', @@ -177,6 +159,7 @@ const appSearchLinks: AppDeepLink[] = [ export class EnterpriseSearchPlugin implements Plugin { private config: ClientConfigType; private enterpriseLicenseAppUpdater$ = new BehaviorSubject(() => ({})); + private licenseSubscription: Subscription | undefined; constructor(initializerContext: PluginInitializerContext) { this.config = initializerContext.config.get(); @@ -432,33 +415,6 @@ export class EnterpriseSearchPlugin implements Plugin { title: ANALYTICS_PLUGIN.NAME, }); - core.application.register({ - appRoute: SEARCH_RELEVANCE_PLUGIN.URL, - category: DEFAULT_APP_CATEGORIES.enterpriseSearch, - 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; - chrome.docTitle.change(SEARCH_RELEVANCE_PLUGIN.NAME); - - await this.getInitialData(http); - const pluginData = this.getPluginData(); - - const { renderApp } = await import('./applications'); - const { EnterpriseSearchRelevance } = await import( - './applications/enterprise_search_relevance' - ); - - return renderApp(EnterpriseSearchRelevance, kibanaDeps, pluginData); - }, - title: SEARCH_RELEVANCE_PLUGIN.NAV_TITLE, - visibleIn: [], - }); - core.application.register({ appRoute: SEARCH_EXPERIENCES_PLUGIN.URL, category: DEFAULT_APP_CATEGORIES.enterpriseSearch, @@ -636,7 +592,7 @@ export class EnterpriseSearchPlugin implements Plugin { }); } - plugins.licensing?.license$.subscribe((license) => { + this.licenseSubscription = plugins.licensing?.license$.subscribe((license) => { if (hasEnterpriseLicense(license)) { this.enterpriseLicenseAppUpdater$.next(() => ({ status: AppStatus.accessible, @@ -653,7 +609,12 @@ export class EnterpriseSearchPlugin implements Plugin { return {}; } - public stop() {} + public stop() { + if (this.licenseSubscription) { + this.licenseSubscription.unsubscribe(); + this.licenseSubscription = undefined; + } + } private updateSideNavDefinition = (items: Partial) => { this.sideNavDynamicItems$.next({ ...this.sideNavDynamicItems$.getValue(), ...items }); diff --git a/x-pack/plugins/enterprise_search/server/plugin.ts b/x-pack/plugins/enterprise_search/server/plugin.ts index 4900fe2af29a2..6079e53e618e6 100644 --- a/x-pack/plugins/enterprise_search/server/plugin.ts +++ b/x-pack/plugins/enterprise_search/server/plugin.ts @@ -47,7 +47,6 @@ import { AI_SEARCH_PLUGIN, APPLICATIONS_PLUGIN, SEARCH_PRODUCT_NAME, - SEARCH_RELEVANCE_PLUGIN, } from '../common/constants'; import { @@ -177,7 +176,6 @@ export class EnterpriseSearchPlugin implements Plugin { SEMANTIC_SEARCH_PLUGIN.ID, APPLICATIONS_PLUGIN.ID, AI_SEARCH_PLUGIN.ID, - SEARCH_RELEVANCE_PLUGIN.ID, ]; const isCloud = !!cloud?.cloudId; diff --git a/x-pack/plugins/enterprise_search/tsconfig.json b/x-pack/plugins/enterprise_search/tsconfig.json index de98a647e0a94..bd716c8ac169f 100644 --- a/x-pack/plugins/enterprise_search/tsconfig.json +++ b/x-pack/plugins/enterprise_search/tsconfig.json @@ -70,7 +70,6 @@ "@kbn/es-errors", "@kbn/search-connectors-plugin", "@kbn/search-playground", - "@kbn/search-inference-endpoints", "@kbn/utility-types", "@kbn/index-management-shared-types", "@kbn/deeplinks-search", diff --git a/x-pack/plugins/search_inference_endpoints/common/translations.ts b/x-pack/plugins/search_inference_endpoints/common/translations.ts index 8b63725c59f96..9d4238b494e6b 100644 --- a/x-pack/plugins/search_inference_endpoints/common/translations.ts +++ b/x-pack/plugins/search_inference_endpoints/common/translations.ts @@ -103,3 +103,17 @@ export const TRAINED_MODELS_STAT_GATHER_FAILED = i18n.translate( defaultMessage: 'Failed to retrieve trained model statistics', } ); + +export const BREADCRUMB_RELEVANCE = i18n.translate( + 'xpack.searchInferenceEndpoints.breadcrumbs.relevance', + { + defaultMessage: 'Relevance', + } +); + +export const BREADCRUMB_INFERENCE_ENDPOINTS = i18n.translate( + 'xpack.searchInferenceEndpoints.breadcrumbs.inferenceEndpoints', + { + defaultMessage: 'Inference Endpoints', + } +); diff --git a/x-pack/plugins/search_inference_endpoints/kibana.jsonc b/x-pack/plugins/search_inference_endpoints/kibana.jsonc index 25b7b391b955a..dca472a92d437 100644 --- a/x-pack/plugins/search_inference_endpoints/kibana.jsonc +++ b/x-pack/plugins/search_inference_endpoints/kibana.jsonc @@ -18,13 +18,15 @@ "requiredPlugins": [ "actions", "features", + "licensing", "ml", "share", ], "optionalPlugins": [ "cloud", "console", - "serverless" + "serverless", + "searchNavigation", ], "requiredBundles": [ "kibanaReact" diff --git a/x-pack/plugins/search_inference_endpoints/public/embeddable.tsx b/x-pack/plugins/search_inference_endpoints/public/embeddable.tsx deleted file mode 100644 index fb733ec7ff5e6..0000000000000 --- a/x-pack/plugins/search_inference_endpoints/public/embeddable.tsx +++ /dev/null @@ -1,29 +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 React from 'react'; -import { dynamic } from '@kbn/shared-ux-utility'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { CoreStart } from '@kbn/core-lifecycle-browser'; -import { AppPluginStartDependencies } from './types'; - -export const InferenceEndpoints = dynamic(async () => ({ - default: (await import('./components/app')).App, -})); - -export const InferenceEndpointsProvider = dynamic(async () => ({ - default: (await import('./providers/inference_endpoints_provider')).InferenceEndpointsProvider, -})); - -export const getInferenceEndpointsProvider = - (core: CoreStart, services: AppPluginStartDependencies) => - (props: React.ComponentProps) => - ( - - - - ); diff --git a/x-pack/plugins/search_inference_endpoints/public/hooks/use_inference_endpoints_breadcrumbs.ts b/x-pack/plugins/search_inference_endpoints/public/hooks/use_inference_endpoints_breadcrumbs.ts new file mode 100644 index 0000000000000..c64326b84bea1 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/hooks/use_inference_endpoints_breadcrumbs.ts @@ -0,0 +1,27 @@ +/* + * 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 { useEffect } from 'react'; + +import * as i18n from '../../common/translations'; +import { useKibana } from './use_kibana'; + +export const useInferenceEndpointsBreadcrumbs = () => { + const { searchNavigation } = useKibana().services; + + useEffect(() => { + searchNavigation?.breadcrumbs.setSearchBreadCrumbs( + [{ text: i18n.BREADCRUMB_RELEVANCE }, { text: i18n.BREADCRUMB_INFERENCE_ENDPOINTS }], + { forClassicChromeStyle: true } + ); + + return () => { + // Clear breadcrumbs on unmount; + searchNavigation?.breadcrumbs.clearBreadcrumbs(); + }; + }, [searchNavigation]); +}; diff --git a/x-pack/plugins/search_inference_endpoints/public/index.ts b/x-pack/plugins/search_inference_endpoints/public/index.ts index 1c9e267a18bde..b06f1f64b909d 100644 --- a/x-pack/plugins/search_inference_endpoints/public/index.ts +++ b/x-pack/plugins/search_inference_endpoints/public/index.ts @@ -17,5 +17,3 @@ export type { SearchInferenceEndpointsPluginSetup, SearchInferenceEndpointsPluginStart, } from './types'; - -export const INFERENCE_ENDPOINTS_UI_FLAG = 'inferenceEndpointsUi:enabled'; diff --git a/x-pack/plugins/search_inference_endpoints/public/inference_endpoints_overview.tsx b/x-pack/plugins/search_inference_endpoints/public/inference_endpoints_overview.tsx index 5dd017f263caa..cda1481765cd1 100644 --- a/x-pack/plugins/search_inference_endpoints/public/inference_endpoints_overview.tsx +++ b/x-pack/plugins/search_inference_endpoints/public/inference_endpoints_overview.tsx @@ -6,17 +6,18 @@ */ import React, { useMemo } from 'react'; - -import { EuiPageTemplate } from '@elastic/eui'; +import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import { App } from './components/app'; +import { useInferenceEndpointsBreadcrumbs } from './hooks/use_inference_endpoints_breadcrumbs'; import { useKibana } from './hooks/use_kibana'; import { InferenceEndpointsProvider } from './providers/inference_endpoints_provider'; export const InferenceEndpointsOverview: React.FC = () => { const { - services: { console: consolePlugin }, + services: { console: consolePlugin, history, searchNavigation }, } = useKibana(); + useInferenceEndpointsBreadcrumbs(); const embeddableConsole = useMemo( () => (consolePlugin?.EmbeddableConsole ? : null), @@ -25,10 +26,16 @@ export const InferenceEndpointsOverview: React.FC = () => { return ( - + {embeddableConsole} - + ); }; diff --git a/x-pack/plugins/search_inference_endpoints/public/plugin.ts b/x-pack/plugins/search_inference_endpoints/public/plugin.ts index cb60f611b3bb3..0fcd0ef082489 100644 --- a/x-pack/plugins/search_inference_endpoints/public/plugin.ts +++ b/x-pack/plugins/search_inference_endpoints/public/plugin.ts @@ -5,8 +5,12 @@ * 2.0. */ +import { BehaviorSubject, type Subscription } from 'rxjs'; + import { AppMountParameters, + AppStatus, + AppUpdater, CoreSetup, CoreStart, Plugin, @@ -15,7 +19,6 @@ import { import { i18n } from '@kbn/i18n'; import { PLUGIN_ID, PLUGIN_NAME } from '../common/constants'; import { docLinks } from '../common/doc_links'; -import { InferenceEndpoints, getInferenceEndpointsProvider } from './embeddable'; import { AppPluginSetupDependencies, AppPluginStartDependencies, @@ -23,7 +26,6 @@ import { SearchInferenceEndpointsPluginSetup, SearchInferenceEndpointsPluginStart, } from './types'; -import { INFERENCE_ENDPOINTS_UI_FLAG } from '.'; import { registerLocators } from './locators'; import { INFERENCE_ENDPOINTS_PATH } from './components/routes'; @@ -31,6 +33,8 @@ export class SearchInferenceEndpointsPlugin implements Plugin { private config: SearchInferenceEndpointsConfigType; + private readonly appUpdater$ = new BehaviorSubject(() => ({})); + private licenseSubscription: Subscription | undefined; constructor(initializerContext: PluginInitializerContext) { this.config = initializerContext.config.get(); @@ -40,11 +44,7 @@ export class SearchInferenceEndpointsPlugin core: CoreSetup, plugins: AppPluginSetupDependencies ): SearchInferenceEndpointsPluginSetup { - if ( - !this.config.ui?.enabled && - !core.uiSettings.get(INFERENCE_ENDPOINTS_UI_FLAG, false) - ) - return {}; + if (!this.config.ui?.enabled) return {}; core.application.register({ id: PLUGIN_ID, appRoute: '/app/elasticsearch/relevance', @@ -55,9 +55,12 @@ export class SearchInferenceEndpointsPlugin title: i18n.translate('xpack.searchInferenceEndpoints.InferenceEndpointsLinkLabel', { defaultMessage: 'Inference Endpoints', }), + visibleIn: ['globalSearch'], }, ], + status: AppStatus.inaccessible, title: PLUGIN_NAME, + updater$: this.appUpdater$, async mount({ element, history }: AppMountParameters) { const { renderApp } = await import('./application'); const [coreStart, depsStart] = await core.getStartServices(); @@ -66,8 +69,11 @@ export class SearchInferenceEndpointsPlugin history, }; + depsStart.searchNavigation?.handleOnAppMount(); + return renderApp(coreStart, startDeps, element); }, + visibleIn: [], }); registerLocators(plugins.share); @@ -79,13 +85,27 @@ export class SearchInferenceEndpointsPlugin core: CoreStart, deps: AppPluginStartDependencies ): SearchInferenceEndpointsPluginStart { + const { licensing } = deps; docLinks.setDocLinks(core.docLinks.links); - return { - InferenceEdnpointsProvider: getInferenceEndpointsProvider(core, deps), - InferenceEndpoints, - }; + this.licenseSubscription = licensing.license$.subscribe((license) => { + const status: AppStatus = + license && license.isAvailable && license.isActive && license.hasAtLeast('enterprise') + ? AppStatus.accessible + : AppStatus.inaccessible; + + this.appUpdater$.next(() => ({ + status, + })); + }); + + return {}; } - public stop() {} + public stop() { + if (this.licenseSubscription) { + this.licenseSubscription.unsubscribe(); + this.licenseSubscription = undefined; + } + } } diff --git a/x-pack/plugins/search_inference_endpoints/public/types.ts b/x-pack/plugins/search_inference_endpoints/public/types.ts index 9f73d0d0033b0..1ebd803229e50 100644 --- a/x-pack/plugins/search_inference_endpoints/public/types.ts +++ b/x-pack/plugins/search_inference_endpoints/public/types.ts @@ -6,29 +6,27 @@ */ import type { ConsolePluginSetup, ConsolePluginStart } from '@kbn/console-plugin/public'; -import { HttpStart } from '@kbn/core-http-browser'; -import { AppMountParameters } from '@kbn/core/public'; -import { MlPluginStart } from '@kbn/ml-plugin/public'; -import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; -import React from 'react'; - +import type { AppMountParameters, CoreStart } from '@kbn/core/public'; +import type { MlPluginStart } from '@kbn/ml-plugin/public'; +import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; +import type { SearchNavigationPluginStart } from '@kbn/search-navigation/public'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; -import type { App } from './components/app'; -import type { InferenceEndpointsProvider } from './providers/inference_endpoints_provider'; +import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; export * from '../common/types'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface SearchInferenceEndpointsPluginSetup {} -export interface SearchInferenceEndpointsPluginStart { - InferenceEdnpointsProvider: React.FC>; - InferenceEndpoints: React.FC>; -} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SearchInferenceEndpointsPluginStart {} export interface AppPluginStartDependencies { history: AppMountParameters['history']; share: SharePluginStart; console?: ConsolePluginStart; + licensing: LicensingPluginStart; + ml: MlPluginStart; + searchNavigation?: SearchNavigationPluginStart; serverless?: ServerlessPluginStart; } @@ -38,13 +36,7 @@ export interface AppPluginSetupDependencies { console?: ConsolePluginSetup; } -export interface AppServicesContext { - http: HttpStart; - ml?: MlPluginStart; - console?: ConsolePluginStart; - serverless?: ServerlessPluginStart; - share: SharePluginStart; -} +export type AppServicesContext = CoreStart & AppPluginStartDependencies; export interface InferenceUsageResponse { acknowledge: boolean; diff --git a/x-pack/plugins/search_inference_endpoints/server/config.ts b/x-pack/plugins/search_inference_endpoints/server/config.ts index 5593fb2251aba..5fa8fbf5cfcbf 100644 --- a/x-pack/plugins/search_inference_endpoints/server/config.ts +++ b/x-pack/plugins/search_inference_endpoints/server/config.ts @@ -13,7 +13,7 @@ export * from './types'; const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), ui: schema.object({ - enabled: schema.boolean({ defaultValue: false }), + enabled: schema.boolean({ defaultValue: true }), }), }); diff --git a/x-pack/plugins/search_inference_endpoints/server/plugin.ts b/x-pack/plugins/search_inference_endpoints/server/plugin.ts index 425820495e997..f7057ec6331c2 100644 --- a/x-pack/plugins/search_inference_endpoints/server/plugin.ts +++ b/x-pack/plugins/search_inference_endpoints/server/plugin.ts @@ -54,7 +54,7 @@ export class SearchInferenceEndpointsPlugin id: PLUGIN_ID, minimumLicense: 'enterprise', name: PLUGIN_NAME, - order: 0, + order: 2, category: DEFAULT_APP_CATEGORIES.enterpriseSearch, app: ['kibana', PLUGIN_ID], scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security], diff --git a/x-pack/plugins/search_inference_endpoints/tsconfig.json b/x-pack/plugins/search_inference_endpoints/tsconfig.json index 9a5b160779e7a..f448d36c7f463 100644 --- a/x-pack/plugins/search_inference_endpoints/tsconfig.json +++ b/x-pack/plugins/search_inference_endpoints/tsconfig.json @@ -12,7 +12,6 @@ "kbn_references": [ "@kbn/config-schema", "@kbn/core", - "@kbn/core-http-browser", "@kbn/i18n", "@kbn/i18n-react", "@kbn/kibana-react-plugin", @@ -22,8 +21,6 @@ "@kbn/core-http-server", "@kbn/share-plugin", "@kbn/actions-plugin", - "@kbn/shared-ux-utility", - "@kbn/core-lifecycle-browser", "@kbn/logging", "@kbn/react-kibana-context-render", "@kbn/doc-links", @@ -35,7 +32,10 @@ "@kbn/deeplinks-search", "@kbn/deeplinks-management", "@kbn/serverless", - "@kbn/utility-types" + "@kbn/utility-types", + "@kbn/search-navigation", + "@kbn/shared-ux-page-kibana-template", + "@kbn/licensing-plugin" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/search_playground/public/hooks/use_playground_breadcrumbs.ts b/x-pack/plugins/search_playground/public/hooks/use_playground_breadcrumbs.ts index 8e3467a968b36..3fd64c3a7c2a7 100644 --- a/x-pack/plugins/search_playground/public/hooks/use_playground_breadcrumbs.ts +++ b/x-pack/plugins/search_playground/public/hooks/use_playground_breadcrumbs.ts @@ -6,6 +6,9 @@ */ import { useEffect } from 'react'; + +import { i18n } from '@kbn/i18n'; + import { useKibana } from './use_kibana'; export const usePlaygroundBreadcrumbs = () => { @@ -13,7 +16,18 @@ export const usePlaygroundBreadcrumbs = () => { useEffect(() => { searchNavigation?.breadcrumbs.setSearchBreadCrumbs( - [{ text: 'Build' }, { text: 'Playground' }], + [ + { + text: i18n.translate('xpack.searchPlayground.breadcrumbs.build', { + defaultMessage: 'Build', + }), + }, + { + text: i18n.translate('xpack.searchPlayground.breadcrumbs.playground', { + defaultMessage: 'Playground', + }), + }, + ], { forClassicChromeStyle: true } ); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 397c73a512c10..81298783701e2 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -18243,9 +18243,6 @@ "xpack.enterpriseSearch.indexOverview.indexErrors.modelNotDeployedError": "Le modèle {modelId} pour le point de terminaison d'inférence {inferenceId} dans le champ {fieldName} n'a pas été démarré", "xpack.enterpriseSearch.indexOverview.indexErrors.showErrorsLabel": "Afficher l'erreur en intégralité", "xpack.enterpriseSearch.indexOverview.indexErrors.title": "L'index comporte des erreurs", - "xpack.enterpriseSearch.inferenceEndpoints.description": "Gérez vos points de terminaison d'inférence pour la recherche sémantique et les cas d'utilisation de l'IA.", - "xpack.enterpriseSearch.inferenceEndpoints.navTitle": "Pertinence", - "xpack.enterpriseSearch.inferenceEndpoints.productName": "Points de terminaison d'inférence", "xpack.enterpriseSearch.inferencePipelineCard.action.delete": "Supprimer un pipeline", "xpack.enterpriseSearch.inferencePipelineCard.action.delete.disabledDescription": "Ce pipeline d'inférence ne peut pas être supprimé car il est utilisé dans plusieurs pipelines [{indexReferences}]. Pour le supprimer, vous devez le détacher des autres pipelines pour ne garder qu'un seul pipeline d'ingestion.", "xpack.enterpriseSearch.inferencePipelineCard.action.detach": "Détacher le pipeline", @@ -18341,7 +18338,6 @@ "xpack.enterpriseSearch.navigation.contentConnectorsLinkLabel": "Connecteurs", "xpack.enterpriseSearch.navigation.contentIndicesLinkLabel": "Index", "xpack.enterpriseSearch.navigation.contentWebcrawlersLinkLabel": "Robots d'indexation", - "xpack.enterpriseSearch.navigation.relevanceInferenceEndpointsLinkLabel": "Points de terminaison d'inférence", "xpack.enterpriseSearch.notFound.action1": "Retour à votre tableau de bord", "xpack.enterpriseSearch.notFound.action2": "Contacter le support technique", "xpack.enterpriseSearch.notFound.description": "Impossible de trouver la page que vous recherchez.", @@ -18386,7 +18382,6 @@ "xpack.enterpriseSearch.productSelector.overview.title": "Ingérer votre contenu", "xpack.enterpriseSearch.productSelectorCalloutTitle": "Mettez à niveau pour obtenir des fonctionnalités de niveau entreprise pour votre équipe", "xpack.enterpriseSearch.readOnlyMode.warning": "Enterprise Search est en mode de lecture seule. Vous ne pourrez pas effectuer de changements tels que création, modification ou suppression.", - "xpack.enterpriseSearch.relevance.inferenceEndpoints.breadcrumb": "Points de terminaison d'inférence", "xpack.enterpriseSearch.required": "Obligatoire", "xpack.enterpriseSearch.researchConfiguration.euiText.checkRequirementsLabel": "Vérifier les exigences", "xpack.enterpriseSearch.researchConfiguration.p.referToTheDocumentationLabel": "Consultez la documentation de ce connecteur pour en apprendre plus sur les prérequis de connexion à {serviceType} ainsi que les prérequis de configuration.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 37f24051c6001..ca80aaa127e32 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -18103,8 +18103,6 @@ "xpack.enterpriseSearch.indexOverview.indexErrors.modelNotDeployedError": "フィールド{fieldName}で推論エンドポイント{inferenceId}のモデル{modelId}は開始されていません。", "xpack.enterpriseSearch.indexOverview.indexErrors.showErrorsLabel": "完全なエラーを表示", "xpack.enterpriseSearch.indexOverview.indexErrors.title": "インデックスにはエラーがあります", - "xpack.enterpriseSearch.inferenceEndpoints.description": "セマンティック検索やAIのユースケースに対応する推論エンドポイントを管理します。", - "xpack.enterpriseSearch.inferenceEndpoints.productName": "推論エンドポイント", "xpack.enterpriseSearch.inferencePipelineCard.action.delete": "パイプラインを削除", "xpack.enterpriseSearch.inferencePipelineCard.action.delete.disabledDescription": "この推論パイプラインは削除できません。複数のパイプライン[{indexReferences}]で使用されています。削除する前に、1つのインジェストパイプライン以外のすべてからこのパイプラインをデタッチする必要があります。", "xpack.enterpriseSearch.inferencePipelineCard.action.detach": "パイプラインのデタッチ", @@ -18199,7 +18197,6 @@ "xpack.enterpriseSearch.navigation.contentConnectorsLinkLabel": "コネクター", "xpack.enterpriseSearch.navigation.contentIndicesLinkLabel": "インデックス", "xpack.enterpriseSearch.navigation.contentWebcrawlersLinkLabel": "Webクローラー", - "xpack.enterpriseSearch.navigation.relevanceInferenceEndpointsLinkLabel": "推論エンドポイント", "xpack.enterpriseSearch.notFound.action1": "ダッシュボードに戻す", "xpack.enterpriseSearch.notFound.action2": "サポートに問い合わせる", "xpack.enterpriseSearch.notFound.description": "お探しのページは見つかりませんでした。", @@ -18244,7 +18241,6 @@ "xpack.enterpriseSearch.productSelector.overview.title": "データをインジェスト", "xpack.enterpriseSearch.productSelectorCalloutTitle": "チームのためのエンタープライズレベルの機能を実現できるようにアップグレード", "xpack.enterpriseSearch.readOnlyMode.warning": "エンタープライズ サーチは読み取り専用モードです。作成、編集、削除などの変更を実行できません。", - "xpack.enterpriseSearch.relevance.inferenceEndpoints.breadcrumb": "推論エンドポイント", "xpack.enterpriseSearch.required": "必須", "xpack.enterpriseSearch.researchConfiguration.euiText.checkRequirementsLabel": "要件を確認", "xpack.enterpriseSearch.researchConfiguration.p.referToTheDocumentationLabel": "{serviceType}に接続するための前提条件と構成要件については、このコネクターのドキュメントを参照してください。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 67f0cee7591ff..31eb102ba6780 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17800,9 +17800,6 @@ "xpack.enterpriseSearch.indexOverview.indexErrors.modelNotDeployedError": "字段 {fieldName} 中用于推理终端 {inferenceId} 的模型 {modelId} 尚未启动", "xpack.enterpriseSearch.indexOverview.indexErrors.showErrorsLabel": "显示完整错误", "xpack.enterpriseSearch.indexOverview.indexErrors.title": "索引包含错误", - "xpack.enterpriseSearch.inferenceEndpoints.description": "管理您用于语义搜索和 AI 用例的推理终端。", - "xpack.enterpriseSearch.inferenceEndpoints.navTitle": "相关性", - "xpack.enterpriseSearch.inferenceEndpoints.productName": "推理终端", "xpack.enterpriseSearch.inferencePipelineCard.action.delete": "删除管道", "xpack.enterpriseSearch.inferencePipelineCard.action.delete.disabledDescription": "无法删除此推理管道,因为它已用在多个管道中 [{indexReferences}]。您必须将此管道从所有管道(一个采集管道除外)分离,然后才能将其删除。", "xpack.enterpriseSearch.inferencePipelineCard.action.detach": "分离管道", @@ -17897,7 +17894,6 @@ "xpack.enterpriseSearch.navigation.contentConnectorsLinkLabel": "连接器", "xpack.enterpriseSearch.navigation.contentIndicesLinkLabel": "索引", "xpack.enterpriseSearch.navigation.contentWebcrawlersLinkLabel": "网络爬虫", - "xpack.enterpriseSearch.navigation.relevanceInferenceEndpointsLinkLabel": "推理终端", "xpack.enterpriseSearch.notFound.action1": "返回到您的仪表板", "xpack.enterpriseSearch.notFound.action2": "联系支持人员", "xpack.enterpriseSearch.notFound.description": "找不到您要查找的页面。", @@ -17942,7 +17938,6 @@ "xpack.enterpriseSearch.productSelector.overview.title": "采集您的内容", "xpack.enterpriseSearch.productSelectorCalloutTitle": "进行升级以便为您的团队获取企业级功能", "xpack.enterpriseSearch.readOnlyMode.warning": "企业搜索处于只读模式。您将无法执行更改,例如创建、编辑或删除。", - "xpack.enterpriseSearch.relevance.inferenceEndpoints.breadcrumb": "推理终端", "xpack.enterpriseSearch.required": "必需", "xpack.enterpriseSearch.researchConfiguration.euiText.checkRequirementsLabel": "检查要求", "xpack.enterpriseSearch.researchConfiguration.p.referToTheDocumentationLabel": "请参阅此连接器的文档了解连接到 {serviceType} 的先决条件和配置要求。", From 70a5bb33c438912b64259ea4c7a3c77c41f93f45 Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Mon, 9 Dec 2024 20:21:16 +0100 Subject: [PATCH 4/5] [Rules migration] Add sorting functionality to rules migration table (#11379) (#203396) ## Summary [Internal link](https://github.com/elastic/security-team/issues/10820) to the feature details These changes add sorting functionality to the migration rules table. It is possible to sort migration rules by next columns: `Updated`, `Name`, `Status`, `Risk Score`, `Severity` and `Author`. ### Other changes Next fixes and adjustments were also implemented as part of this PR: * `Installed` status in migration rules table to indicate whether the rule was installed * Rules selection and installation of selected rules * Disable selection for not fully translated rules * `Author` column to show whether the translated rule matched one of the existing Elastic prebuilt rules * `Install and enable` and `Install without enabling` buttons within the migration rule details flyout --- .../kbn-index-adapter/src/field_maps/types.ts | 1 + .../model/api/rules/rule_migration.gen.ts | 10 +- .../api/rules/rule_migration.schema.yaml | 29 +++- .../common/siem_migrations/rules/utils.ts | 6 +- .../public/siem_migrations/rules/api/index.ts | 23 +++- .../components/rule_details_flyout/index.tsx | 3 +- .../components/rules_table/bulk_actions.tsx | 17 ++- .../rules/components/rules_table/index.tsx | 119 ++++++++++++++-- .../components/rules_table/translations.ts | 28 ++++ .../components/rules_table_columns/author.tsx | 39 ++++++ .../components/rules_table_columns/index.tsx | 1 + .../components/rules_table_columns/name.tsx | 11 +- .../rules_table_columns/risk_score.tsx | 2 +- .../rules_table_columns/severity.tsx | 5 +- .../components/rules_table_columns/status.tsx | 4 +- .../rules_table_columns/translations.ts | 21 +++ .../components/status_badge/index.test.tsx | 19 --- .../rules/components/status_badge/index.tsx | 36 +++-- .../components/status_badge/translations.ts | 15 +++ .../use_migration_rules_table_columns.tsx | 2 + .../rules/logic/use_get_migration_rules.ts | 2 + .../logic/use_install_migration_rules.ts | 4 +- .../lib/siem_migrations/rules/api/get.ts | 9 +- .../lib/siem_migrations/rules/api/install.ts | 3 +- .../rules/api/install_translated.ts | 1 + .../rules/api/util/installation.ts | 19 ++- .../data/rule_migrations_data_rules_client.ts | 61 +++------ .../rules/data/rule_migrations_field_maps.ts | 4 +- .../lib/siem_migrations/rules/data/search.ts | 42 ++++++ .../lib/siem_migrations/rules/data/sort.ts | 127 ++++++++++++++++++ 30 files changed, 541 insertions(+), 122 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/author.tsx delete mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/translations.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/search.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/sort.ts diff --git a/packages/kbn-index-adapter/src/field_maps/types.ts b/packages/kbn-index-adapter/src/field_maps/types.ts index 1cdafc7c61809..90fb44873a342 100644 --- a/packages/kbn-index-adapter/src/field_maps/types.ts +++ b/packages/kbn-index-adapter/src/field_maps/types.ts @@ -46,6 +46,7 @@ export type FieldMap = Record< array?: boolean; doc_values?: boolean; enabled?: boolean; + fields?: Record; format?: string; ignore_above?: number; multi_fields?: MultiField[]; diff --git a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts index 58944ff7f2f95..95a81d4436d8a 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts +++ b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts @@ -59,6 +59,8 @@ export type GetRuleMigrationRequestQuery = z.infer; @@ -154,7 +156,13 @@ export type InstallMigrationRulesRequestParamsInput = z.input< >; export type InstallMigrationRulesRequestBody = z.infer; -export const InstallMigrationRulesRequestBody = z.array(NonEmptyString); +export const InstallMigrationRulesRequestBody = z.object({ + ids: z.array(NonEmptyString), + /** + * Indicates whether installed rules should be enabled + */ + enabled: z.boolean().optional(), +}); export type InstallMigrationRulesRequestBodyInput = z.input< typeof InstallMigrationRulesRequestBody >; diff --git a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml index dff6089b2b48f..b7e495e2ea898 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml +++ b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml @@ -133,6 +133,19 @@ paths: required: false schema: type: number + - name: sort_field + in: query + required: false + schema: + $ref: '../../../../../common/api/model/primitives.schema.yaml#/components/schemas/NonEmptyString' + - name: sort_direction + in: query + required: false + schema: + type: string + enum: + - asc + - desc - name: search_term in: query required: false @@ -180,10 +193,18 @@ paths: content: application/json: schema: - type: array - items: - description: The rule migration id - $ref: '../../../../../common/api/model/primitives.schema.yaml#/components/schemas/NonEmptyString' + type: object + required: + - ids + properties: + ids: + type: array + items: + description: The rule migration id + $ref: '../../../../../common/api/model/primitives.schema.yaml#/components/schemas/NonEmptyString' + enabled: + type: boolean + description: Indicates whether installed rules should be enabled responses: 200: description: Indicates rules migrations have been installed correctly. diff --git a/x-pack/plugins/security_solution/common/siem_migrations/rules/utils.ts b/x-pack/plugins/security_solution/common/siem_migrations/rules/utils.ts index a9b8666b19981..8763e057052b5 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/rules/utils.ts +++ b/x-pack/plugins/security_solution/common/siem_migrations/rules/utils.ts @@ -22,13 +22,17 @@ export const isMigrationCustomRule = (rule?: ElasticRule): rule is MigrationCust !isMigrationPrebuiltRule(rule) && !!(rule?.title && rule?.description && rule?.query && rule?.query_language); -export const convertMigrationCustomRuleToSecurityRulePayload = (rule: MigrationCustomRule) => { +export const convertMigrationCustomRuleToSecurityRulePayload = ( + rule: MigrationCustomRule, + enabled: boolean +) => { return { type: rule.query_language, language: rule.query_language, query: rule.query, name: rule.title, description: rule.description, + enabled, ...DEFAULT_TRANSLATION_FIELDS, severity: (rule.severity as Severity) ?? DEFAULT_TRANSLATION_SEVERITY, diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/index.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/index.ts index ac9e49ff861fc..57fb5d0422093 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/index.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/index.ts @@ -120,6 +120,10 @@ export interface GetRuleMigrationParams { page?: number; /** Optional number of documents per page to retrieve */ perPage?: number; + /** Optional field of the rule migration object to sort results by */ + sortField?: string; + /** Optional direction to sort results by */ + sortDirection?: 'asc' | 'desc'; /** Optional search term to filter documents */ searchTerm?: string; /** Optional AbortSignal for cancelling request */ @@ -130,12 +134,24 @@ export const getRuleMigrations = async ({ migrationId, page, perPage, + sortField, + sortDirection, searchTerm, signal, }: GetRuleMigrationParams): Promise => { return KibanaServices.get().http.get( replaceParams(SIEM_RULE_MIGRATION_PATH, { migration_id: migrationId }), - { version: '1', query: { page, per_page: perPage, search_term: searchTerm }, signal } + { + version: '1', + query: { + page, + per_page: perPage, + sort_field: sortField, + sort_direction: sortDirection, + search_term: searchTerm, + }, + signal, + } ); }; @@ -163,6 +179,8 @@ export interface InstallRulesParams { migrationId: string; /** The rule ids to install */ ids: string[]; + /** Optional indicator to enable the installed rule */ + enabled?: boolean; /** Optional AbortSignal for cancelling request */ signal?: AbortSignal; } @@ -170,11 +188,12 @@ export interface InstallRulesParams { export const installMigrationRules = async ({ migrationId, ids, + enabled, signal, }: InstallRulesParams): Promise => { return KibanaServices.get().http.post( replaceParams(SIEM_RULE_MIGRATION_INSTALL_PATH, { migration_id: migrationId }), - { version: '1', body: JSON.stringify(ids), signal } + { version: '1', body: JSON.stringify({ ids, enabled }), signal } ); }; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/index.tsx index 8fea17b51cb0e..9762cc578e0cc 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/index.tsx @@ -84,7 +84,8 @@ export const MigrationRuleDetailsFlyout: React.FC { if (isMigrationCustomRule(ruleMigration.elastic_rule)) { return convertMigrationCustomRuleToSecurityRulePayload( - ruleMigration.elastic_rule + ruleMigration.elastic_rule, + false ) as RuleResponse; // TODO: we need to adjust RuleOverviewTab to allow partial RuleResponse as a parameter; } return matchedPrebuiltRule; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/bulk_actions.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/bulk_actions.tsx index a58681b6e771f..8f32308ed52c4 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/bulk_actions.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/bulk_actions.tsx @@ -6,7 +6,13 @@ */ import React from 'react'; -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, +} from '@elastic/eui'; import * as i18n from './translations'; export interface BulkActionsProps { @@ -29,13 +35,14 @@ export const BulkActions: React.FC = React.memo( installSelectedRule, }) => { const disableInstallTranslatedRulesButton = isTableLoading || !numberOfTranslatedRules; - const showInstallSelectedRulesButton = - disableInstallTranslatedRulesButton && numberOfSelectedRules > 0; + const showInstallSelectedRulesButton = isTableLoading || numberOfSelectedRules > 0; return ( {showInstallSelectedRulesButton ? ( - = React.memo( > {i18n.INSTALL_SELECTED_RULES(numberOfSelectedRules)} {isTableLoading && } - + ) : null} diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx index 13b451c2918d8..106e7ba514d3f 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import type { CriteriaWithPagination } from '@elastic/eui'; +import type { CriteriaWithPagination, EuiTableSelectionType } from '@elastic/eui'; import { EuiSkeletonLoading, EuiSkeletonTitle, @@ -14,6 +14,7 @@ import { EuiFlexItem, EuiSpacer, EuiBasicTable, + EuiButton, } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; @@ -30,8 +31,12 @@ import { useGetMigrationPrebuiltRules } from '../../logic/use_get_migration_preb import * as logicI18n from '../../logic/translations'; import { BulkActions } from './bulk_actions'; import { SearchField } from './search_field'; +import { SiemMigrationRuleTranslationResult } from '../../../../../common/siem_migrations/constants'; +import * as i18n from './translations'; const DEFAULT_PAGE_SIZE = 10; +const DEFAULT_SORT_FIELD = 'translation_result'; +const DEFAULT_SORT_DIRECTION = 'desc'; export interface MigrationRulesTableProps { /** @@ -49,6 +54,8 @@ export const MigrationRulesTable: React.FC = React.mem const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); + const [sortField, setSortField] = useState(DEFAULT_SORT_FIELD); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>(DEFAULT_SORT_DIRECTION); const [searchTerm, setSearchTerm] = useState(); const { data: translationStats, isLoading: isStatsLoading } = @@ -64,10 +71,33 @@ export const MigrationRulesTable: React.FC = React.mem migrationId, page: pageIndex, perPage: pageSize, + sortField, + sortDirection, searchTerm, }); const [selectedRuleMigrations, setSelectedRuleMigrations] = useState([]); + const tableSelection: EuiTableSelectionType = useMemo( + () => ({ + selectable: (item: RuleMigration) => { + return ( + !item.elastic_rule?.id && + item.translation_result === SiemMigrationRuleTranslationResult.FULL + ); + }, + selectableMessage: (selectable: boolean, item: RuleMigration) => { + if (selectable) { + return ''; + } + return item.elastic_rule?.id + ? i18n.ALREADY_TRANSLATED_RULE_TOOLTIP + : i18n.NOT_FULLY_TRANSLATED_RULE_TOOLTIP; + }, + onSelectionChange: setSelectedRuleMigrations, + selected: selectedRuleMigrations, + }), + [selectedRuleMigrations] + ); const pagination = useMemo(() => { return { @@ -77,11 +107,25 @@ export const MigrationRulesTable: React.FC = React.mem }; }, [pageIndex, pageSize, total]); + const sorting = useMemo(() => { + return { + sort: { + field: sortField, + direction: sortDirection, + }, + }; + }, [sortDirection, sortField]); + const onTableChange = useCallback(({ page, sort }: CriteriaWithPagination) => { if (page) { setPageIndex(page.index); setPageSize(page.size); } + if (sort) { + const { field, direction } = sort; + setSortField(field); + setSortDirection(direction); + } }, []); const handleOnSearch = useCallback((value: string) => { @@ -94,10 +138,10 @@ export const MigrationRulesTable: React.FC = React.mem const [isTableLoading, setTableLoading] = useState(false); const installSingleRule = useCallback( - async (migrationRule: RuleMigration, enable?: boolean) => { + async (migrationRule: RuleMigration, enabled = false) => { setTableLoading(true); try { - await installMigrationRules([migrationRule.id]); + await installMigrationRules({ ids: [migrationRule.id], enabled }); } catch (error) { addError(error, { title: logicI18n.INSTALL_MIGRATION_RULES_FAILURE }); } finally { @@ -107,6 +151,24 @@ export const MigrationRulesTable: React.FC = React.mem [addError, installMigrationRules] ); + const installSelectedRule = useCallback( + async (enabled = false) => { + setTableLoading(true); + try { + await installMigrationRules({ + ids: selectedRuleMigrations.map((rule) => rule.id), + enabled, + }); + } catch (error) { + addError(error, { title: logicI18n.INSTALL_MIGRATION_RULES_FAILURE }); + } finally { + setTableLoading(false); + setSelectedRuleMigrations([]); + } + }, + [addError, installMigrationRules, selectedRuleMigrations] + ); + const installTranslatedRules = useCallback( async (enable?: boolean) => { setTableLoading(true); @@ -121,12 +183,45 @@ export const MigrationRulesTable: React.FC = React.mem [addError, installTranslatedMigrationRules] ); + const isLoading = isStatsLoading || isPrebuiltRulesLoading || isDataLoading || isTableLoading; + const ruleActionsFactory = useCallback( (ruleMigration: RuleMigration, closeRulePreview: () => void) => { - // TODO: Add flyout action buttons - return null; + const canMigrationRuleBeInstalled = + !isLoading && + !ruleMigration.elastic_rule?.id && + ruleMigration.translation_result === SiemMigrationRuleTranslationResult.FULL; + return ( + + + { + installSingleRule(ruleMigration); + closeRulePreview(); + }} + data-test-subj="installMigrationRuleFromFlyoutButton" + > + {i18n.INSTALL_WITHOUT_ENABLING_BUTTON_LABEL} + + + + { + installSingleRule(ruleMigration, true); + closeRulePreview(); + }} + fill + data-test-subj="installAndEnableMigrationRuleFromFlyoutButton" + > + {i18n.INSTALL_AND_ENABLE_BUTTON_LABEL} + + + + ); }, - [] + [installSingleRule, isLoading] ); const { @@ -143,8 +238,6 @@ export const MigrationRulesTable: React.FC = React.mem installMigrationRule: installSingleRule, }); - const isLoading = isStatsLoading || isPrebuiltRulesLoading || isDataLoading || isTableLoading; - return ( <> = React.mem @@ -178,12 +272,9 @@ export const MigrationRulesTable: React.FC = React.mem loading={isTableLoading} items={ruleMigrations} pagination={pagination} + sorting={sorting} onChange={onTableChange} - selection={{ - selectable: () => true, - onSelectionChange: setSelectedRuleMigrations, - initialSelected: selectedRuleMigrations, - }} + selection={tableSelection} itemId={'id'} data-test-subj={'rules-translation-table'} columns={rulesColumns} diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts index 6803deb895d9b..79b5a1fe00900 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts @@ -80,3 +80,31 @@ export const INSTALL_TRANSLATED_ARIA_LABEL = i18n.translate( defaultMessage: 'Install all translated rules', } ); + +export const ALREADY_TRANSLATED_RULE_TOOLTIP = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.table.alreadyTranslatedTooltip', + { + defaultMessage: 'Already translated migration rule', + } +); + +export const NOT_FULLY_TRANSLATED_RULE_TOOLTIP = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.table.notFullyTranslatedTooltip', + { + defaultMessage: 'Not fully translated migration rule', + } +); + +export const INSTALL_WITHOUT_ENABLING_BUTTON_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.table.installWithoutEnablingButtonLabel', + { + defaultMessage: 'Install without enabling', + } +); + +export const INSTALL_AND_ENABLE_BUTTON_LABEL = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.table.installAndEnableButtonLabel', + { + defaultMessage: 'Install and enable', + } +); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/author.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/author.tsx new file mode 100644 index 0000000000000..23980f5612f89 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/author.tsx @@ -0,0 +1,39 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui'; +import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen'; +import * as i18n from './translations'; +import type { TableColumn } from './constants'; + +const Author = ({ isPrebuiltRule }: { isPrebuiltRule: boolean }) => { + return ( + + {isPrebuiltRule && ( + + + + )} + + {isPrebuiltRule ? i18n.ELASTIC_AUTHOR_TITLE : i18n.CUSTOM_AUTHOR_TITLE} + + + ); +}; + +export const createAuthorColumn = (): TableColumn => { + return { + field: 'elastic_rule.prebuilt_rule_id', + name: i18n.COLUMN_AUTHOR, + render: (_, rule: RuleMigration) => { + return ; + }, + sortable: true, + width: '10%', + }; +}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/index.tsx index 4220a35ed4622..61af73ca7b9f8 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/index.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/index.tsx @@ -8,6 +8,7 @@ export * from './constants'; export * from './actions'; +export * from './author'; export * from './name'; export * from './risk_score'; export * from './severity'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx index 085a2f5c6a254..dd77636521eda 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx @@ -12,12 +12,11 @@ import * as i18n from './translations'; import type { TableColumn } from './constants'; interface NameProps { - name: string; rule: RuleMigration; openMigrationRuleDetails: (rule: RuleMigration) => void; } -const Name = ({ name, rule, openMigrationRuleDetails }: NameProps) => { +const Name = ({ rule, openMigrationRuleDetails }: NameProps) => { return ( { @@ -25,7 +24,7 @@ const Name = ({ name, rule, openMigrationRuleDetails }: NameProps) => { }} data-test-subj="ruleName" > - {name} + {rule.elastic_rule?.title} ); }; @@ -36,10 +35,10 @@ export const createNameColumn = ({ openMigrationRuleDetails: (rule: RuleMigration) => void; }): TableColumn => { return { - field: 'original_rule.title', + field: 'elastic_rule.title', name: i18n.COLUMN_NAME, - render: (value: RuleMigration['original_rule']['title'], rule: RuleMigration) => ( - + render: (_, rule: RuleMigration) => ( + ), sortable: true, truncateText: true, diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/risk_score.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/risk_score.tsx index e9eca65736a51..0fb78ae8bf709 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/risk_score.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/risk_score.tsx @@ -22,6 +22,6 @@ export const createRiskScoreColumn = (): TableColumn => { ), sortable: true, truncateText: true, - width: '75px', + width: '10%', }; }; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/severity.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/severity.tsx index 4ea737844ad53..9a6c0b98ff317 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/severity.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/severity.tsx @@ -8,9 +8,7 @@ import React from 'react'; import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; import { DEFAULT_TRANSLATION_SEVERITY } from '../../../../../common/siem_migrations/constants'; -import { getNormalizedSeverity } from '../../../../detection_engine/rule_management_ui/components/rules_table/helpers'; import { SeverityBadge } from '../../../../common/components/severity_badge'; -import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen'; import type { TableColumn } from './constants'; import * as i18n from './translations'; @@ -19,8 +17,7 @@ export const createSeverityColumn = (): TableColumn => { field: 'elastic_rule.severity', name: i18n.COLUMN_SEVERITY, render: (value?: Severity) => , - sortable: ({ elastic_rule: elasticRule }: RuleMigration) => - getNormalizedSeverity((elasticRule?.severity as Severity) ?? DEFAULT_TRANSLATION_SEVERITY), + sortable: true, truncateText: true, width: '12%', }; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/status.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/status.tsx index 982f6ba7580b2..5daec1f1b4fa9 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/status.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/status.tsx @@ -18,8 +18,8 @@ export const createStatusColumn = (): TableColumn => { render: (value: RuleMigration['translation_result'], rule: RuleMigration) => ( ), - sortable: false, + sortable: true, truncateText: true, - width: '12%', + width: '15%', }; }; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/translations.ts index 5b40abd3d7485..64e459a609143 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/translations.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/translations.ts @@ -14,6 +14,27 @@ export const COLUMN_ACTIONS = i18n.translate( } ); +export const COLUMN_AUTHOR = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.tableColumn.authorLabel', + { + defaultMessage: 'Author', + } +); + +export const ELASTIC_AUTHOR_TITLE = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.tableColumn.elasticAuthorTitle', + { + defaultMessage: 'Elastic', + } +); + +export const CUSTOM_AUTHOR_TITLE = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.tableColumn.customAuthorTitle', + { + defaultMessage: 'Custom', + } +); + export const ACTIONS_VIEW_LABEL = i18n.translate( 'xpack.securitySolution.siemMigrations.rules.tableColumn.actionsViewLabel', { diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.test.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.test.tsx deleted file mode 100644 index aaf256cfb60b5..0000000000000 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.test.tsx +++ /dev/null @@ -1,19 +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 React from 'react'; -import { shallow } from 'enzyme'; - -import { StatusBadge } from '.'; - -describe('StatusBadge', () => { - it('renders correctly', () => { - const wrapper = shallow(); - - expect(wrapper.find('HealthTruncateText')).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx index 60f0ed94862ca..8f8bcff40f674 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx @@ -8,9 +8,16 @@ import React from 'react'; import { euiLightVars } from '@kbn/ui-theme'; +import { EuiFlexGroup, EuiFlexItem, EuiHealth, EuiIcon, EuiToolTip } from '@elastic/eui'; +import { css } from '@emotion/css'; import type { RuleMigrationTranslationResult } from '../../../../../common/siem_migrations/model/rule_migration.gen'; -import { HealthTruncateText } from '../../../../common/components/health_truncate_text'; import { convertTranslationResultIntoText } from '../../utils/helpers'; +import * as i18n from './translations'; + +const statusTextWrapperClassName = css` + width: 100%; + display: inline-grid; +`; const { euiColorVis0, euiColorVis7, euiColorVis9 } = euiLightVars; const statusToColorMap: Record = { @@ -28,17 +35,28 @@ interface StatusBadgeProps { export const StatusBadge: React.FC = React.memo( ({ value, installedRuleId, 'data-test-subj': dataTestSubj = 'translation-result' }) => { const translationResult = installedRuleId ? 'full' : value ?? 'untranslatable'; - const displayValue = convertTranslationResultIntoText(translationResult); + const displayValue = installedRuleId + ? i18n.RULE_STATUS_INSTALLED + : convertTranslationResultIntoText(translationResult); const color = statusToColorMap[translationResult]; return ( - - {displayValue} - + + {installedRuleId ? ( + + + + + {displayValue} + + ) : ( + +
+ {displayValue} +
+
+ )} +
); } ); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/translations.ts new file mode 100644 index 0000000000000..0a7b1c37f7acf --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/translations.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 RULE_STATUS_INSTALLED = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.status.installedLabel', + { + defaultMessage: 'Installed', + } +); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_migration_rules_table_columns.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_migration_rules_table_columns.tsx index b8b37bccaffd3..3c2d5a696ccc1 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_migration_rules_table_columns.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_migration_rules_table_columns.tsx @@ -10,6 +10,7 @@ import type { RuleMigration } from '../../../../common/siem_migrations/model/rul import type { TableColumn } from '../components/rules_table_columns'; import { createActionsColumn, + createAuthorColumn, createNameColumn, createRiskScoreColumn, createSeverityColumn, @@ -33,6 +34,7 @@ export const useMigrationRulesTableColumns = ({ createStatusColumn(), createRiskScoreColumn(), createSeverityColumn(), + createAuthorColumn(), createActionsColumn({ disableActions, openMigrationRuleDetails, diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts index 5f59ceb9f76c2..4109575459233 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts @@ -18,6 +18,8 @@ export const useGetMigrationRules = (params: { migrationId: string; page?: number; perPage?: number; + sortField: string; + sortDirection: 'asc' | 'desc'; searchTerm?: string; }) => { const { addError } = useAppToasts(); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts index 755faa03bff14..2b28b3b944990 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts @@ -23,8 +23,8 @@ export const useInstallMigrationRules = (migrationId: string) => { const invalidateGetMigrationTranslationStats = useInvalidateGetMigrationTranslationStats(migrationId); - return useMutation( - (ids: string[]) => installMigrationRules({ migrationId, ids }), + return useMutation( + ({ ids, enabled = false }) => installMigrationRules({ migrationId, ids, enabled }), { mutationKey: INSTALL_MIGRATION_RULES_MUTATION_KEY, onError: (error) => { diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts index dd13a75cdf83a..30037aeea88ae 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts @@ -39,13 +39,20 @@ export const registerSiemRuleMigrationsGetRoute = ( }, withLicense(async (context, req, res): Promise> => { const { migration_id: migrationId } = req.params; - const { page, per_page: perPage, search_term: searchTerm } = req.query; + const { + page, + per_page: perPage, + sort_field: sortField, + sort_direction: sortDirection, + search_term: searchTerm, + } = req.query; try { const ctx = await context.resolve(['securitySolution']); const ruleMigrationsClient = ctx.securitySolution.getSiemRuleMigrationsClient(); const options: RuleMigrationGetOptions = { filters: { searchTerm }, + sort: { sortField, sortDirection }, size: perPage, from: page && perPage ? page * perPage : 0, }; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install.ts index 7b41ea536aadf..9fae2922b486f 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install.ts @@ -40,7 +40,7 @@ export const registerSiemRuleMigrationsInstallRoute = ( withLicense( async (context, req, res): Promise> => { const { migration_id: migrationId } = req.params; - const ids = req.body; + const { ids, enabled = false } = req.body; try { const ctx = await context.resolve(['core', 'alerting', 'securitySolution']); @@ -52,6 +52,7 @@ export const registerSiemRuleMigrationsInstallRoute = ( await installTranslated({ migrationId, ids, + enabled, securitySolutionContext, savedObjectsClient, rulesClient, diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install_translated.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install_translated.ts index ac6a598c4b92f..2cf2a2e2c8efd 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install_translated.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install_translated.ts @@ -50,6 +50,7 @@ export const registerSiemRuleMigrationsInstallTranslatedRoute = ( await installTranslated({ migrationId, + enabled: false, securitySolutionContext, savedObjectsClient, rulesClient, diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts index d74619e4c1251..8716c83ce6ba3 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts @@ -27,6 +27,7 @@ import { const installPrebuiltRules = async ( rulesToInstall: StoredRuleMigration[], + enabled: boolean, securitySolutionContext: SecuritySolutionApiRequestHandlerContext, rulesClient: RulesClient, savedObjectsClient: SavedObjectsClientContract, @@ -41,7 +42,7 @@ const installPrebuiltRules = async ( if (item.current) { acc.installed.push(item.current); } else { - acc.installable.push(item.target); + acc.installable.push({ ...item.target, enabled }); } return acc; }, @@ -85,6 +86,7 @@ const installPrebuiltRules = async ( export const installCustomRules = async ( rulesToInstall: StoredRuleMigration[], + enabled: boolean, detectionRulesClient: IDetectionRulesClient, logger: Logger ): Promise => { @@ -96,8 +98,11 @@ export const installCustomRules = async ( if (!isMigrationCustomRule(rule.elastic_rule)) { return; } - const payloadRule = convertMigrationCustomRuleToSecurityRulePayload(rule.elastic_rule); - const createdRule = await detectionRulesClient.createPrebuiltRule({ + const payloadRule = convertMigrationCustomRuleToSecurityRulePayload( + rule.elastic_rule, + enabled + ); + const createdRule = await detectionRulesClient.createCustomRule({ params: payloadRule, }); rulesToUpdate.push({ @@ -131,6 +136,11 @@ interface InstallTranslatedProps { */ ids?: string[]; + /** + * Indicates whether the installed migration rules should be enabled + */ + enabled: boolean; + /** * The security solution context */ @@ -155,6 +165,7 @@ interface InstallTranslatedProps { export const installTranslated = async ({ migrationId, ids, + enabled, securitySolutionContext, rulesClient, savedObjectsClient, @@ -186,6 +197,7 @@ export const installTranslated = async ({ const updatedPrebuiltRules = await installPrebuiltRules( prebuiltRulesToInstall, + enabled, securitySolutionContext, rulesClient, savedObjectsClient, @@ -194,6 +206,7 @@ export const installTranslated = async ({ const updatedCustomRules = await installCustomRules( customRulesToInstall, + enabled, detectionRulesClient, logger ); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts index f11b24e50b95a..1eeb3ced0572a 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts @@ -15,10 +15,7 @@ import type { QueryDslQueryContainer, } from '@elastic/elasticsearch/lib/api/types'; import type { StoredRuleMigration } from '../types'; -import { - SiemMigrationRuleTranslationResult, - SiemMigrationStatus, -} from '../../../../../common/siem_migrations/constants'; +import { SiemMigrationStatus } from '../../../../../common/siem_migrations/constants'; import type { ElasticRule, RuleMigration, @@ -26,6 +23,8 @@ import type { RuleMigrationTranslationStats, } from '../../../../../common/siem_migrations/model/rule_migration.gen'; import { RuleMigrationsDataBaseClient } from './rule_migrations_data_base_client'; +import { getSortingOptions, type RuleMigrationSort } from './sort'; +import { conditions as searchConditions } from './search'; export type CreateRuleMigrationInput = Omit< RuleMigration, @@ -47,6 +46,7 @@ export interface RuleMigrationFilters { } export interface RuleMigrationGetOptions { filters?: RuleMigrationFilters; + sort?: RuleMigrationSort; from?: number; size?: number; } @@ -120,13 +120,19 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient /** Retrieves an array of rule documents of a specific migrations */ async get( migrationId: string, - { filters = {}, from, size }: RuleMigrationGetOptions = {} + { filters = {}, sort = {}, from, size }: RuleMigrationGetOptions = {} ): Promise<{ total: number; data: StoredRuleMigration[] }> { const index = await this.getIndexName(); const query = this.getFilterQuery(migrationId, { ...filters }); const result = await this.esClient - .search({ index, query, sort: '_doc', from, size }) + .search({ + index, + query, + sort: sort.sortField ? getSortingOptions(sort) : undefined, + from, + size, + }) .catch((error) => { this.logger.error(`Error searching rule migrations: ${error.message}`); throw error; @@ -238,8 +244,8 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient const query = this.getFilterQuery(migrationId); const aggregations = { - prebuilt: { filter: conditions.isPrebuilt() }, - installable: { filter: { bool: { must: conditions.isInstallable() } } }, + prebuilt: { filter: searchConditions.isPrebuilt() }, + installable: { filter: { bool: { must: searchConditions.isInstallable() } } }, }; const result = await this.esClient .search({ index, query, aggregations, _source: false }) @@ -351,47 +357,14 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient filter.push({ terms: { _id: ids } }); } if (installable) { - filter.push(...conditions.isInstallable()); + filter.push(...searchConditions.isInstallable()); } if (prebuilt) { - filter.push(conditions.isPrebuilt()); + filter.push(searchConditions.isPrebuilt()); } if (searchTerm?.length) { - filter.push(conditions.matchTitle(searchTerm)); + filter.push(searchConditions.matchTitle(searchTerm)); } return { bool: { filter } }; } } - -const conditions = { - isFullyTranslated(): QueryDslQueryContainer { - return { term: { translation_result: SiemMigrationRuleTranslationResult.FULL } }; - }, - isNotInstalled(): QueryDslQueryContainer { - return { - nested: { - path: 'elastic_rule', - query: { bool: { must_not: { exists: { field: 'elastic_rule.id' } } } }, - }, - }; - }, - isPrebuilt(): QueryDslQueryContainer { - return { - nested: { - path: 'elastic_rule', - query: { exists: { field: 'elastic_rule.prebuilt_rule_id' } }, - }, - }; - }, - matchTitle(title: string): QueryDslQueryContainer { - return { - nested: { - path: 'elastic_rule', - query: { match: { 'elastic_rule.title': title } }, - }, - }; - }, - isInstallable(): QueryDslQueryContainer[] { - return [this.isFullyTranslated(), this.isNotInstalled()]; - }, -}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts index 7aca804c12890..952663c36123c 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts @@ -19,14 +19,14 @@ export const ruleMigrationsFieldMap: FieldMap + direction === 'desc' ? '_last' : '_first'; + +const sortingOptions = { + matchedPrebuiltRule(direction: estypes.SortOrder = 'asc'): estypes.SortCombinations[] { + return [ + { + 'elastic_rule.prebuilt_rule_id': { + order: direction, + nested: { path: 'elastic_rule' }, + missing: sortMissingValue(direction), + }, + }, + ]; + }, + severity(direction: estypes.SortOrder = 'asc'): estypes.SortCombinations[] { + const field = 'elastic_rule.severity'; + return [ + { + _script: { + order: direction, + type: 'number', + script: { + source: ` + if (doc.containsKey('${field}') && !doc['${field}'].empty) { + def value = doc['${field}'].value.toLowerCase(); + if (value == 'critical') { return 3 } + if (value == 'high') { return 2 } + if (value == 'medium') { return 1 } + if (value == 'low') { return 0 } + } + return -1; + `, + lang: 'painless', + }, + nested: { path: 'elastic_rule' }, + }, + }, + ]; + }, + status(direction: estypes.SortOrder = 'asc'): estypes.SortCombinations[] { + const field = 'translation_result'; + const installedRuleField = 'elastic_rule.id'; + return [ + { + _script: { + order: direction, + type: 'number', + script: { + source: ` + if (doc.containsKey('${field}') && !doc['${field}'].empty) { + def value = doc['${field}'].value.toLowerCase(); + if (value == 'full') { return 2 } + if (value == 'partial') { return 1 } + if (value == 'untranslatable') { return 0 } + } + return -1; + `, + lang: 'painless', + }, + }, + }, + { + _script: { + order: direction, + type: 'number', + script: { + source: ` + if (doc.containsKey('${installedRuleField}') && !doc['${installedRuleField}'].empty) { + return 0; + } + return -1; + `, + lang: 'painless', + }, + nested: { path: 'elastic_rule' }, + }, + }, + ]; + }, + updated(direction: estypes.SortOrder = 'asc'): estypes.SortCombinations[] { + return [{ updated_at: direction }]; + }, + name(direction: estypes.SortOrder = 'asc'): estypes.SortCombinations[] { + return [ + { 'elastic_rule.title.keyword': { order: direction, nested: { path: 'elastic_rule' } } }, + ]; + }, +}; + +const DEFAULT_SORTING: estypes.Sort = [ + ...sortingOptions.status('desc'), + ...sortingOptions.matchedPrebuiltRule('desc'), + ...sortingOptions.severity(), + ...sortingOptions.updated(), +]; + +const sortingOptionsMap: { + [key: string]: (direction?: estypes.SortOrder) => estypes.SortCombinations[]; +} = { + 'elastic_rule.title': sortingOptions.name, + 'elastic_rule.severity': sortingOptions.severity, + 'elastic_rule.prebuilt_rule_id': sortingOptions.matchedPrebuiltRule, + translation_result: sortingOptions.status, + updated_at: sortingOptions.updated, +}; + +export const getSortingOptions = (sort?: RuleMigrationSort): estypes.Sort => { + if (!sort?.sortField) { + return DEFAULT_SORTING; + } + return sortingOptionsMap[sort.sortField]?.(sort.sortDirection) ?? DEFAULT_SORTING; +}; From 598d3defd15442766ce1eec5b717f479ea62b5df Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Mon, 9 Dec 2024 13:03:23 -0700 Subject: [PATCH 5/5] Preparation for High Contrast Mode, Security domains (#202609) ## Summary **Reviewers: Please test the code paths affected by this PR. See the "Risks" section below.** Part of work for enabling "high contrast mode" in Kibana. See https://github.com/elastic/kibana/issues/176219. **Background:** Kibana will soon have a user profile setting to allow users to enable "high contrast mode." This setting will activate a flag with `` that causes EUI components to render with higher contrast visual elements. Consumer plugins and packages need to be updated selected places where `` is wrapped, to pass the `UserProfileService` service dependency from the CoreStart contract. **NOTE:** **EUI currently does not yet support the high-contrast mode flag**, but support for that is expected to come in around 2 weeks. These first PRs are simply preparing the code by wiring up the `UserProvideService`. ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [X] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [X] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ### Risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [medium/high] The implementor of this change did not manually test the affected code paths and relied on type-checking and functional tests to drive the changes. Code owners for this PR need to manually test the affected code paths. - [ ] [medium] The `UserProfileService` dependency comes from the CoreStart contract. If acquiring the service causes synchronous code to become asynchronous, check for race conditions or errors in rendering React components. Code owners for this PR need to manually test the affected code paths. --- packages/kbn-mock-idp-plugin/public/plugin.tsx | 4 ++-- .../kbn-mock-idp-plugin/public/reload_page_toast.tsx | 4 +++- packages/kbn-mock-idp-plugin/public/role_switcher.tsx | 1 + packages/kbn-mock-idp-plugin/tsconfig.json | 1 + packages/kbn-user-profile-components/src/services.tsx | 6 ++++-- packages/kbn-user-profile-components/tsconfig.json | 1 + x-pack/plugins/security/public/index.ts | 2 +- .../management/roles/edit_role/edit_role_page.test.tsx | 4 ++++ .../management/roles/roles_grid/roles_grid_page.test.tsx | 9 ++++++++- .../management/roles/roles_grid/roles_grid_page.tsx | 8 ++------ .../components/insecure_cluster_alert.tsx | 2 ++ .../public/security_checkup/security_checkup_service.tsx | 2 ++ x-pack/plugins/security/tsconfig.json | 1 + .../edit_space/edit_space_content_tab.test.tsx | 3 +++ .../edit_space/edit_space_general_tab.test.tsx | 3 +++ .../management/edit_space/edit_space_roles_tab.test.tsx | 3 +++ .../management/edit_space/edit_space_roles_tab.tsx | 4 +++- .../edit_space/provider/edit_space_provider.test.tsx | 3 +++ .../edit_space/provider/edit_space_provider.tsx | 5 ++++- .../component/space_assign_role_privilege_form.test.tsx | 3 +++ .../public/management/spaces_management_app.test.tsx | 2 +- .../spaces/public/management/spaces_management_app.tsx | 5 +++-- .../spaces/public/space_selector/space_selector.tsx | 2 +- x-pack/plugins/spaces/tsconfig.json | 3 ++- 24 files changed, 61 insertions(+), 20 deletions(-) diff --git a/packages/kbn-mock-idp-plugin/public/plugin.tsx b/packages/kbn-mock-idp-plugin/public/plugin.tsx index c1f733027f656..d27a9d0bc8ad5 100644 --- a/packages/kbn-mock-idp-plugin/public/plugin.tsx +++ b/packages/kbn-mock-idp-plugin/public/plugin.tsx @@ -49,7 +49,7 @@ export const plugin: PluginInitializer< ]); ReactDOM.render( - + @@ -69,7 +69,7 @@ export const plugin: PluginInitializer< order: 4000 + 1, // Make sure it comes after the user menu mount: (element: HTMLElement) => { ReactDOM.render( - + diff --git a/packages/kbn-mock-idp-plugin/public/reload_page_toast.tsx b/packages/kbn-mock-idp-plugin/public/reload_page_toast.tsx index c3f93f3269da1..e254e21d47cde 100644 --- a/packages/kbn-mock-idp-plugin/public/reload_page_toast.tsx +++ b/packages/kbn-mock-idp-plugin/public/reload_page_toast.tsx @@ -13,6 +13,7 @@ import React from 'react'; import type { I18nStart } from '@kbn/core-i18n-browser'; import type { ToastInput } from '@kbn/core-notifications-browser'; import type { ThemeServiceStart } from '@kbn/core-theme-browser'; +import type { UserProfileService } from '@kbn/core-user-profile-browser'; import { toMountPoint } from '@kbn/react-kibana-mount'; import type { AuthenticatedUser } from '@kbn/security-plugin-types-common'; @@ -26,6 +27,7 @@ export const DATA_TEST_SUBJ_PAGE_RELOAD_BUTTON = 'pageReloadButton'; */ export const createReloadPageToast = (options: { user: Pick; + userProfile: UserProfileService; theme: ThemeServiceStart; i18n: I18nStart; }): ToastInput => { @@ -43,7 +45,7 @@ export const createReloadPageToast = (options: { , - { i18n: options.i18n, theme: options.theme } + options ), color: 'success', toastLifeTimeMs: 0x7fffffff, // Do not auto-hide toast since page is in an unknown state diff --git a/packages/kbn-mock-idp-plugin/public/role_switcher.tsx b/packages/kbn-mock-idp-plugin/public/role_switcher.tsx index 347293abbc6c7..7a3845b0cc54a 100644 --- a/packages/kbn-mock-idp-plugin/public/role_switcher.tsx +++ b/packages/kbn-mock-idp-plugin/public/role_switcher.tsx @@ -69,6 +69,7 @@ export const RoleSwitcher = () => { services.notifications.toasts.add( createReloadPageToast({ user: authenticateUserState.value, + userProfile: services.userProfile, theme: services.theme, i18n: services.i18n, }) diff --git a/packages/kbn-mock-idp-plugin/tsconfig.json b/packages/kbn-mock-idp-plugin/tsconfig.json index 83c4023733404..d8b5fad09fb06 100644 --- a/packages/kbn-mock-idp-plugin/tsconfig.json +++ b/packages/kbn-mock-idp-plugin/tsconfig.json @@ -26,5 +26,6 @@ "@kbn/mock-idp-utils", "@kbn/cloud-plugin", "@kbn/es", + "@kbn/core-user-profile-browser", ] } diff --git a/packages/kbn-user-profile-components/src/services.tsx b/packages/kbn-user-profile-components/src/services.tsx index 7cf7a2d66c82f..f59caee26a034 100644 --- a/packages/kbn-user-profile-components/src/services.tsx +++ b/packages/kbn-user-profile-components/src/services.tsx @@ -13,6 +13,7 @@ import React, { useContext } from 'react'; import type { I18nStart } from '@kbn/core-i18n-browser'; import type { NotificationsStart, ToastOptions } from '@kbn/core-notifications-browser'; import type { ThemeServiceStart } from '@kbn/core-theme-browser'; +import type { UserProfileService } from '@kbn/core-user-profile-browser'; import type { toMountPoint } from '@kbn/react-kibana-mount'; import type { UserProfileAPIClient } from './types'; @@ -47,6 +48,7 @@ export interface UserProfilesKibanaDependencies { core: { notifications: NotificationsStart; theme: ThemeServiceStart; + userProfile: UserProfileService; i18n: I18nStart; }; security: { @@ -70,7 +72,7 @@ export const UserProfilesKibanaProvider: FC { const { - core: { notifications, i18n, theme }, + core: { notifications, ...startServices }, security: { userProfiles: userProfileApiClient }, toMountPoint: toMountPointUtility, } = services; @@ -86,7 +88,7 @@ export const UserProfilesKibanaProvider: FC = (initializerContext: PluginInitializerContext) => new SecurityPlugin(initializerContext); // services needed for rendering React using shared modules -export type StartServices = Pick; +export type StartServices = Pick; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx index 9a9abab064fa8..166b1de7cf057 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx @@ -15,6 +15,7 @@ import { coreMock, scopedHistoryMock } from '@kbn/core/public/mocks'; import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks'; import { i18nServiceMock } from '@kbn/core-i18n-browser-mocks'; import { themeServiceMock } from '@kbn/core-theme-browser-mocks'; +import { userProfileServiceMock } from '@kbn/core-user-profile-browser-mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { KibanaFeature } from '@kbn/features-plugin/public'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; @@ -196,6 +197,8 @@ function getProps({ const analyticsMock = analyticsServiceMock.createAnalyticsServiceStart(); const i18nMock = i18nServiceMock.createStartContract(); const themeMock = themeServiceMock.createStartContract(); + const userProfileMock = userProfileServiceMock.createStart(); + return { action, roleName: role?.name, @@ -214,6 +217,7 @@ function getProps({ history: scopedHistoryMock.create(), spacesApiUi, buildFlavor, + userProfile: userProfileMock, theme: themeMock, i18n: i18nMock, analytics: analyticsMock, diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx index 57281f5ec754c..1c965551a63dc 100644 --- a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx @@ -44,7 +44,7 @@ const waitForRender = async ( describe('', () => { let apiClientMock: jest.Mocked>; let history: ReturnType; - const { theme, i18n, analytics, notifications } = coreMock.createStart(); + const { userProfile, theme, i18n, analytics, notifications } = coreMock.createStart(); beforeEach(() => { history = scopedHistoryMock.create(); @@ -93,6 +93,7 @@ describe('', () => { buildFlavor={'traditional'} analytics={analytics} theme={theme} + userProfile={userProfile} /> ); const initialIconCount = wrapper.find(EuiIcon).length; @@ -115,6 +116,7 @@ describe('', () => { buildFlavor={'traditional'} analytics={analytics} theme={theme} + userProfile={userProfile} /> ); const initialIconCount = wrapper.find(EuiIcon).length; @@ -139,6 +141,7 @@ describe('', () => { buildFlavor={'traditional'} analytics={analytics} theme={theme} + userProfile={userProfile} /> ); await waitForRender(wrapper, (updatedWrapper) => { @@ -157,6 +160,7 @@ describe('', () => { buildFlavor={'traditional'} analytics={analytics} theme={theme} + userProfile={userProfile} /> ); const initialIconCount = wrapper.find(EuiIcon).length; @@ -201,6 +205,7 @@ describe('', () => { buildFlavor={'traditional'} analytics={analytics} theme={theme} + userProfile={userProfile} /> ); const initialIconCount = wrapper.find(EuiIcon).length; @@ -332,6 +337,7 @@ describe('', () => { buildFlavor={'traditional'} analytics={analytics} theme={theme} + userProfile={userProfile} /> ); const initialIconCount = wrapper.find(EuiIcon).length; @@ -441,6 +447,7 @@ describe('', () => { buildFlavor={'traditional'} analytics={analytics} theme={theme} + userProfile={userProfile} readOnly /> ); diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx index 457b9053a0ac8..cfea3a7f29804 100644 --- a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx @@ -75,9 +75,7 @@ export const RolesGridPage: FC = ({ readOnly, buildFlavor, cloudOrgUrl, - analytics, - theme, - i18n: i18nStart, + ...startServices }) => { const [roles, setRoles] = useState([]); const [visibleRoles, setVisibleRoles] = useState([]); @@ -409,9 +407,7 @@ export const RolesGridPage: FC = ({ notifications={notifications} rolesAPIClient={rolesAPIClient} buildFlavor={buildFlavor} - theme={theme} - analytics={analytics} - i18n={i18nStart} + {...startServices} /> ) : null} diff --git a/x-pack/plugins/security/public/security_checkup/components/insecure_cluster_alert.tsx b/x-pack/plugins/security/public/security_checkup/components/insecure_cluster_alert.tsx index 71e1a53e7d2fb..37b58eecc4d9e 100644 --- a/x-pack/plugins/security/public/security_checkup/components/insecure_cluster_alert.tsx +++ b/x-pack/plugins/security/public/security_checkup/components/insecure_cluster_alert.tsx @@ -22,6 +22,7 @@ import type { I18nStart, MountPoint, ThemeServiceStart, + UserProfileService, } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -37,6 +38,7 @@ interface Deps { analytics: Pick; i18n: I18nStart; theme: Pick; + userProfile: UserProfileService; } export const insecureClusterAlertText = (deps: Deps, onDismiss: (persist: boolean) => void) => diff --git a/x-pack/plugins/security/public/security_checkup/security_checkup_service.tsx b/x-pack/plugins/security/public/security_checkup/security_checkup_service.tsx index 4818e3721818a..295aef39f02e4 100644 --- a/x-pack/plugins/security/public/security_checkup/security_checkup_service.tsx +++ b/x-pack/plugins/security/public/security_checkup/security_checkup_service.tsx @@ -16,6 +16,7 @@ import type { NotificationsStart, ThemeServiceStart, Toast, + UserProfileService, } from '@kbn/core/public'; import { insecureClusterAlertText, insecureClusterAlertTitle } from './components'; @@ -33,6 +34,7 @@ interface StartDeps { analytics: Pick; i18n: I18nStart; theme: Pick; + userProfile: UserProfileService; } const DEFAULT_SECURITY_CHECKUP_STATE = Object.freeze({ diff --git a/x-pack/plugins/security/tsconfig.json b/x-pack/plugins/security/tsconfig.json index bfbf5df127597..518af73f96877 100644 --- a/x-pack/plugins/security/tsconfig.json +++ b/x-pack/plugins/security/tsconfig.json @@ -91,6 +91,7 @@ "@kbn/core-capabilities-server", "@kbn/core-elasticsearch-server", "@kbn/core-http-server-utils", + "@kbn/core-user-profile-browser-mocks", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/spaces/public/management/edit_space/edit_space_content_tab.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/edit_space_content_tab.test.tsx index 6f668b79756c8..5946706874c4a 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/edit_space_content_tab.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/edit_space_content_tab.test.tsx @@ -16,6 +16,7 @@ import { overlayServiceMock, themeServiceMock, } from '@kbn/core/public/mocks'; +import { userProfileServiceMock } from '@kbn/core-user-profile-browser-mocks'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { EditSpaceContentTab } from './edit_space_content_tab'; @@ -37,6 +38,7 @@ const http = httpServiceMock.createStartContract(); const notifications = notificationServiceMock.createStartContract(); const overlays = overlayServiceMock.createStartContract(); const theme = themeServiceMock.createStartContract(); +const userProfile = userProfileServiceMock.createStart(); const i18n = i18nServiceMock.createStartContract(); const logger = loggingSystemMock.createLogger(); @@ -62,6 +64,7 @@ const TestComponent: React.FC = ({ children }) => { getPrivilegesAPIClient={getPrivilegeAPIClient} getSecurityLicense={getSecurityLicenseMock} theme={theme} + userProfile={userProfile} i18n={i18n} logger={logger} > diff --git a/x-pack/plugins/spaces/public/management/edit_space/edit_space_general_tab.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/edit_space_general_tab.test.tsx index 2344b92832db6..353c64b835c0e 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/edit_space_general_tab.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/edit_space_general_tab.test.tsx @@ -19,6 +19,7 @@ import { themeServiceMock, } from '@kbn/core/public/mocks'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; +import { userProfileServiceMock } from '@kbn/core-user-profile-browser-mocks'; import { KibanaFeature } from '@kbn/features-plugin/common'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; @@ -43,6 +44,7 @@ const reloadWindow = jest.fn(); const http = httpServiceMock.createStartContract(); const notifications = notificationServiceMock.createStartContract(); const overlays = overlayServiceMock.createStartContract(); +const userProfile = userProfileServiceMock.createStart(); const theme = themeServiceMock.createStartContract(); const i18n = i18nServiceMock.createStartContract(); const logger = loggingSystemMock.createLogger(); @@ -83,6 +85,7 @@ describe('EditSpaceSettings', () => { getIsRoleManagementEnabled={() => Promise.resolve(() => undefined)} getPrivilegesAPIClient={getPrivilegeAPIClient} getSecurityLicense={getSecurityLicenseMock} + userProfile={userProfile} theme={theme} i18n={i18n} logger={logger} diff --git a/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.test.tsx index 542af2222c3f1..02a754e9d93f0 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.test.tsx @@ -16,6 +16,7 @@ import { overlayServiceMock, themeServiceMock, } from '@kbn/core/public/mocks'; +import { userProfileServiceMock } from '@kbn/core-user-profile-browser-mocks'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { EditSpaceAssignedRolesTab } from './edit_space_roles_tab'; @@ -34,6 +35,7 @@ const getPrivilegeAPIClient = getPrivilegeAPIClientMock; const http = httpServiceMock.createStartContract(); const notifications = notificationServiceMock.createStartContract(); const overlays = overlayServiceMock.createStartContract(); +const userProfile = userProfileServiceMock.createStart(); const theme = themeServiceMock.createStartContract(); const i18n = i18nServiceMock.createStartContract(); const logger = loggingSystemMock.createLogger(); @@ -77,6 +79,7 @@ describe('EditSpaceAssignedRolesTab', () => { getIsRoleManagementEnabled={getIsRoleManagementEnabled} getPrivilegesAPIClient={getPrivilegeAPIClient} getSecurityLicense={getSecurityLicenseMock} + userProfile={userProfile} theme={theme} i18n={i18n} logger={logger} diff --git a/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.tsx b/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.tsx index 18e11110d7564..34ebbf1989bbc 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/edit_space_roles_tab.tsx @@ -33,6 +33,7 @@ export const EditSpaceAssignedRolesTab: FC = ({ space, features, isReadOn const { getUrlForApp, overlays, + userProfile, theme, i18n: i18nStart, logger, @@ -99,7 +100,7 @@ export const EditSpaceAssignedRolesTab: FC = ({ space, features, isReadOn }} /> , - { theme, i18n: i18nStart } + { theme, i18n: i18nStart, userProfile } ), { size: 'm', @@ -117,6 +118,7 @@ export const EditSpaceAssignedRolesTab: FC = ({ space, features, isReadOn features, invokeClient, getUrlForApp, + userProfile, theme, i18nStart, notifications.toasts, diff --git a/x-pack/plugins/spaces/public/management/edit_space/provider/edit_space_provider.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/provider/edit_space_provider.test.tsx index 4da9806b0dee0..df2135e256970 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/provider/edit_space_provider.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/provider/edit_space_provider.test.tsx @@ -18,6 +18,7 @@ import { themeServiceMock, } from '@kbn/core/public/mocks'; import type { ApplicationStart } from '@kbn/core-application-browser'; +import { userProfileServiceMock } from '@kbn/core-user-profile-browser-mocks'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { @@ -33,6 +34,7 @@ import { getSecurityLicenseMock } from '../../security_license.mock'; const http = httpServiceMock.createStartContract(); const notifications = notificationServiceMock.createStartContract(); const overlays = overlayServiceMock.createStartContract(); +const userProfile = userProfileServiceMock.createStart(); const theme = themeServiceMock.createStartContract(); const i18n = i18nServiceMock.createStartContract(); const logger = loggingSystemMock.createLogger(); @@ -55,6 +57,7 @@ const SUTProvider = ({ logger, i18n, http, + userProfile, theme, overlays, notifications, diff --git a/x-pack/plugins/spaces/public/management/edit_space/provider/edit_space_provider.tsx b/x-pack/plugins/spaces/public/management/edit_space/provider/edit_space_provider.tsx index 7eafb4ae7e391..8de774797c974 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/provider/edit_space_provider.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/provider/edit_space_provider.tsx @@ -35,7 +35,10 @@ import { import type { SpacesManager } from '../../../spaces_manager'; export interface EditSpaceProviderRootProps - extends Pick { + extends Pick< + CoreStart, + 'userProfile' | 'theme' | 'i18n' | 'overlays' | 'http' | 'notifications' + > { logger: Logger; capabilities: ApplicationStart['capabilities']; getUrlForApp: ApplicationStart['getUrlForApp']; diff --git a/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.test.tsx index 5b1e263e20f16..ea24864bb092c 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/roles/component/space_assign_role_privilege_form.test.tsx @@ -19,6 +19,7 @@ import { overlayServiceMock, themeServiceMock, } from '@kbn/core/public/mocks'; +import { userProfileServiceMock } from '@kbn/core-user-profile-browser-mocks'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import type { Role, SecurityLicense } from '@kbn/security-plugin-types-common'; import { @@ -43,6 +44,7 @@ const privilegeAPIClient = createPrivilegeAPIClientMock(); const http = httpServiceMock.createStartContract(); const notifications = notificationServiceMock.createStartContract(); const overlays = overlayServiceMock.createStartContract(); +const userProfile = userProfileServiceMock.createStart(); const theme = themeServiceMock.createStartContract(); const i18n = i18nServiceMock.createStartContract(); const logger = loggingSystemMock.createLogger(); @@ -89,6 +91,7 @@ const renderPrivilegeRolesForm = ({ logger, i18n, http, + userProfile, theme, overlays, notifications, diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx index 67973542aae54..d721ff79600c3 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx @@ -192,7 +192,7 @@ describe('spacesManagementApp', () => { css="You have tried to stringify object returned from \`css\` function. It isn't supposed to be used directly (e.g. as value of the \`className\` prop), but rather handed to emotion so it can handle it (e.g. as value of \`css\` prop)." data-test-subj="kbnRedirectAppLink" > - Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"serverBasePath":"","http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}},"overlays":{"banners":{}},"notifications":{"toasts":{}},"theme":{"theme$":{}},"i18n":{},"logger":{"context":[]},"spacesManager":{"onActiveSpaceChange$":{}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}},"allowFeatureVisibility":true,"allowSolutionVisibility":true} + Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"serverBasePath":"","http":{"basePath":{"basePath":"","serverBasePath":"","assetsHrefBase":""},"anonymousPaths":{},"externalUrl":{},"staticAssets":{}},"overlays":{"banners":{}},"notifications":{"toasts":{}},"userProfile":{},"theme":{"theme$":{}},"i18n":{},"logger":{"context":[]},"spacesManager":{"onActiveSpaceChange$":{}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}},"allowFeatureVisibility":true,"allowSolutionVisibility":true}
`); diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx index 13546ef3e77f0..3e72ce63caeb9 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx @@ -82,7 +82,7 @@ export const spacesManagementApp = Object.freeze({ text: title, href: `/`, }; - const { notifications, application, chrome, http, overlays, theme } = coreStart; + const { notifications, application, chrome, http, overlays } = coreStart; chrome.docTitle.change(title); @@ -160,7 +160,8 @@ export const spacesManagementApp = Object.freeze({ http={http} overlays={overlays} notifications={notifications} - theme={theme} + userProfile={coreStart.userProfile} + theme={coreStart.theme} i18n={coreStart.i18n} logger={logger} spacesManager={spacesManager} diff --git a/x-pack/plugins/spaces/public/space_selector/space_selector.tsx b/x-pack/plugins/spaces/public/space_selector/space_selector.tsx index d334ee9efab35..8808aabd490aa 100644 --- a/x-pack/plugins/spaces/public/space_selector/space_selector.tsx +++ b/x-pack/plugins/spaces/public/space_selector/space_selector.tsx @@ -249,7 +249,7 @@ export class SpaceSelector extends Component { } export const renderSpaceSelectorApp = ( - services: Pick, + services: Pick, { element }: Pick, props: Props ) => { diff --git a/x-pack/plugins/spaces/tsconfig.json b/x-pack/plugins/spaces/tsconfig.json index 771f1b2e6139b..83c93cd898f10 100644 --- a/x-pack/plugins/spaces/tsconfig.json +++ b/x-pack/plugins/spaces/tsconfig.json @@ -54,7 +54,8 @@ "@kbn/core-application-browser-mocks", "@kbn/ui-theme", "@kbn/core-chrome-browser", - "@kbn/core-lifecycle-server" + "@kbn/core-lifecycle-server", + "@kbn/core-user-profile-browser-mocks", ], "exclude": [ "target/**/*",